From c4cd348dfb0682555840343a7e430fd3baae45c7 Mon Sep 17 00:00:00 2001 From: shima004 Date: Sat, 10 Aug 2024 12:04:00 +0900 Subject: [PATCH 001/249] chore: Update .gitignore to ignore .vscode folder --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 5268a8b..4939007 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,6 @@ cython_debug/ # ruff .ruff_cache/ + +# vscode +.vscode/ From 16772f53c954e4b25b7f9e04db860f9f5686237d Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 12 Aug 2024 15:39:16 +0900 Subject: [PATCH 002/249] chore: Update pyproject.toml with new dependencies --- poetry.lock | 123 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 + 2 files changed, 123 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index c0cd004..6564d1f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -11,6 +11,17 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "mslex" version = "1.2.0" @@ -79,6 +90,32 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "protobuf" version = "3.20.3" @@ -138,6 +175,88 @@ files = [ [package.extras] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +[[package]] +name = "pytest" +version = "8.3.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + [[package]] name = "rcrs_core" version = "0.1.0" @@ -155,7 +274,7 @@ rtree = "*" type = "git" url = "https://github.com/adf-python/rcrs-core-python" reference = "HEAD" -resolved_reference = "db980d7e3487be063545fd95b3495fd4acbc0b11" +resolved_reference = "4fb11912097abccd68fc2ee7440eaa16adbcdc23" [[package]] name = "rtree" @@ -255,4 +374,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "694c9005401e153d18e3d6699b5f77a2789d41cbbbc8d06445640ba26b4f5e7e" +content-hash = "10cdddfced36790cc4e140d9707f7cee2ea68368dbc1b56aa8c33bec0848e7cf" diff --git a/pyproject.toml b/pyproject.toml index 87ca343..cdc7976 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,8 @@ package-mode = true [tool.poetry.dependencies] python = "^3.12" rcrs_core = {git = "https://github.com/adf-python/rcrs-core-python"} +pyyaml = "^6.0.2" +pytest = "^8.3.2" [tool.poetry.group.dev.dependencies] From 7a04fbb9c1544b245475e24718d792eb41a9edc0 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 12 Aug 2024 15:39:44 +0900 Subject: [PATCH 003/249] refactor: Add ModuleConfig class for reading configuration from YAML file --- adf_core_python/core/config/module_config.py | 92 ++++++++++++++ tests/core/config/module.yaml | 123 +++++++++++++++++++ tests/core/config/test_module_config.py | 48 ++++++++ 3 files changed, 263 insertions(+) create mode 100644 adf_core_python/core/config/module_config.py create mode 100644 tests/core/config/module.yaml create mode 100644 tests/core/config/test_module_config.py diff --git a/adf_core_python/core/config/module_config.py b/adf_core_python/core/config/module_config.py new file mode 100644 index 0000000..258c6f0 --- /dev/null +++ b/adf_core_python/core/config/module_config.py @@ -0,0 +1,92 @@ +from typing import Any, Dict, Final + +from rcrs_core.config.config import Config +from yaml import safe_load + + +class ModuleConfig(Config): + DEFAULT_CONFIG_FILE_NAME: Final[str] = "config/module.yaml" + + def __init__(self, config_file_name: str = DEFAULT_CONFIG_FILE_NAME): + """ + Constructor + + Parameters + ---------- + config_file_name : str, optional + Configuration file name, by default DEFAULT_CONFIG_FILE_NAME + + Raises + ------ + FileNotFoundError + If config file not found + Exception + If error reading config file + + Examples + -------- + >>> config = ModuleConfig("config/module.yaml") + >>> config.get_value("DefaultTacticsPoliceOffice.TargetAllocator") + "sample_team.module.complex.SamplePoliceTargetAllocator" + """ + super().__init__() + data = self._read_from_yaml(config_file_name) + flatten_data = self._flatten(data) + for key, value in flatten_data.items(): + self.set_value(key, value) + + for key, value in flatten_data.items(): + print(f"{key}: {self.get_value(key)}") + + def _read_from_yaml(self, file_name: str) -> Dict[str, Any]: + """ + Read configuration from yaml file + + Parameters + ---------- + file_name : str + Configuration file name + + Returns + ------- + Dict[str, Any] + Configuration data + """ + try: + with open(file_name, mode="r", encoding="utf-8") as file: + data = safe_load(file) + except FileNotFoundError: + raise FileNotFoundError(f"Config file not found: {file_name}") + except Exception as e: + raise Exception(f"Error reading config file: {file_name}, {e}") + + return data + + def _flatten( + self, data: Dict[str, Any], parent_key: str = "", sep: str = "." + ) -> Dict[str, Any]: + """ + Flatten nested dictionary to a single level dictionary + + Parameters + ---------- + data : Dict[str, Any] + Nested dictionary + parent_key : str, optional + Parent key, by default "" + sep : str, optional + Separator, by default "." + + Returns + ------- + Dict[str, Any] + Flattened dictionary + """ + flatten_data = {} + for key, value in data.items(): + new_key = f"{parent_key}{sep}{key}" if parent_key else key + if isinstance(value, dict): + flatten_data.update(self._flatten(value, new_key, sep=sep)) + else: + flatten_data[new_key] = value + return flatten_data diff --git a/tests/core/config/module.yaml b/tests/core/config/module.yaml new file mode 100644 index 0000000..6e27f99 --- /dev/null +++ b/tests/core/config/module.yaml @@ -0,0 +1,123 @@ +## DefaultTacticsAmbulanceTeam +DefaultTacticsAmbulanceTeam: + HumanDetector: sample_team.module.complex.SampleHumanDetector + Search: sample_team.module.complex.SampleSearch + ExtActionTransport: adf.impl.extaction.DefaultExtActionTransport + ExtActionMove: adf.impl.extaction.DefaultExtActionMove + CommandExecutorAmbulance: adf.impl.centralized.DefaultCommandExecutorAmbulance + CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScout + +## DefaultTacticsFireBrigade +DefaultTacticsFireBrigade: + HumanDetector: sample_team.module.complex.SampleHumanDetector + Search: sample_team.module.complex.SampleSearch + ExtActionFireRescue: adf.impl.extaction.DefaultExtActionFireRescue + ExtActionMove: adf.impl.extaction.DefaultExtActionMove + CommandExecutorFire: adf.impl.centralized.DefaultCommandExecutorFire + CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScout + +## DefaultTacticsPoliceForce +DefaultTacticsPoliceForce: + RoadDetector: sample_team.module.complex.SampleRoadDetector + Search: sample_team.module.complex.SampleSearch + ExtActionClear: adf.impl.extaction.DefaultExtActionClear + ExtActionMove: adf.impl.extaction.DefaultExtActionMove + CommandExecutorPolice: adf.impl.centralized.DefaultCommandExecutorPolice + CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScoutPolice + +## DefaultTacticsAmbulanceCentre +DefaultTacticsAmbulanceCentre: + TargetAllocator: sample_team.module.complex.SampleAmbulanceTargetAllocator + CommandPicker: adf.impl.centralized.DefaultCommandPickerAmbulance + +## DefaultTacticsFireStation +DefaultTacticsFireStation: + TargetAllocator: sample_team.module.complex.SampleFireTargetAllocator + CommandPicker: adf.impl.centralized.DefaultCommandPickerFire + +## DefaultTacticsPoliceOffice +DefaultTacticsPoliceOffice: + TargetAllocator: sample_team.module.complex.SamplePoliceTargetAllocator + CommandPicker: adf.impl.centralized.DefaultCommandPickerPolice + +## SampleSearch +SampleSearch: + PathPlanning: + Ambulance: adf.impl.module.algorithm.DijkstraPathPlanning + Fire: adf.impl.module.algorithm.DijkstraPathPlanning + Police: adf.impl.module.algorithm.DijkstraPathPlanning + Clustering: + Ambulance: adf.impl.module.algorithm.KMeansClustering + Fire: adf.impl.module.algorithm.KMeansClustering + Police: adf.impl.module.algorithm.KMeansClustering + +## SampleBuildDetector +SampleBuildingDetector: + Clustering: adf.impl.module.algorithm.KMeansClustering + +## SampleRoadDetector +SampleRoadDetector: + Clustering: adf.impl.module.algorithm.KMeansClustering + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## SampleHumanDetector +SampleHumanDetector: + Clustering: adf.impl.module.algorithm.KMeansClustering + +## DefaultExtActionClear +DefaultExtActionClear: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultExtActionFireFighting +DefaultExtActionFireFighting: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultExtActionFireRescue +DefaultExtActionFireRescue: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultExtActionMove +DefaultExtActionMove: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultExtActionTransport +DefaultExtActionTransport: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultCommandExecutorAmbulance +DefaultCommandExecutorAmbulance: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + ExtActionTransport: adf.impl.extaction.DefaultExtActionTransport + ExtActionMove: adf.impl.extaction.DefaultExtActionMove + +## DefaultCommandExecutorFire +DefaultCommandExecutorFire: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + EtxActionFireRescue: adf.impl.extaction.DefaultExtActionFireRescue + EtxActionFireFighting: adf.impl.extaction.DefaultExtActionFireFighting + ExtActionMove: adf.impl.extaction.DefaultExtActionMove + +## DefaultCommandExecutorPolice +DefaultCommandExecutorPolice: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + ExtActionClear: adf.impl.extaction.DefaultExtActionClear + ExtActionMove: adf.impl.extaction.DefaultExtActionMove + +## DefaultCommandExecutorScout +DefaultCommandExecutorScout: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultCommandExecutorScoutPolice +DefaultCommandExecutorScoutPolice: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + ExtActionClear: adf.impl.extaction.DefaultExtActionClear + +## MessageManager +MessageManager: + PlatoonChannelSubscriber: adf.impl.module.comm.DefaultChannelSubscriber + CenterChannelSubscriber: adf.impl.module.comm.DefaultChannelSubscriber + PlatoonMessageCoordinator: adf.impl.module.comm.DefaultMessageCoordinator + CenterMessageCoordinator: adf.impl.module.comm.DefaultMessageCoordinator + +## VisualDebug +VisualDebug: true diff --git a/tests/core/config/test_module_config.py b/tests/core/config/test_module_config.py new file mode 100644 index 0000000..f9665e7 --- /dev/null +++ b/tests/core/config/test_module_config.py @@ -0,0 +1,48 @@ +import os + +import pytest + +from adf_core_python.core.config.module_config import ModuleConfig + + +class TestModuleConfig: + def test_can_read_from_yaml(self): + script_dir = os.path.dirname(os.path.abspath(__file__)) + config_file_path = os.path.join(script_dir, "module.yaml") + config = ModuleConfig(config_file_path) + assert ( + config.get_value("DefaultTacticsPoliceOffice.TargetAllocator") + == "sample_team.module.complex.SamplePoliceTargetAllocator" + ) + assert ( + config.get_value("DefaultTacticsPoliceOffice.CommandPicker") + == "adf.impl.centralized.DefaultCommandPickerPolice" + ) + assert ( + config.get_value("SampleSearch.PathPlanning.Ambulance") + == "adf.impl.module.algorithm.DijkstraPathPlanning" + ) + assert ( + config.get_value("SampleSearch.PathPlanning.Fire") + == "adf.impl.module.algorithm.DijkstraPathPlanning" + ) + assert ( + config.get_value("SampleSearch.PathPlanning.Police") + == "adf.impl.module.algorithm.DijkstraPathPlanning" + ) + assert ( + config.get_value("SampleSearch.Clustering.Ambulance") + == "adf.impl.module.algorithm.KMeansClustering" + ) + assert ( + config.get_value("SampleSearch.Clustering.Fire") + == "adf.impl.module.algorithm.KMeansClustering" + ) + assert ( + config.get_value("SampleSearch.Clustering.Police") + == "adf.impl.module.algorithm.KMeansClustering" + ) + + def test_if_file_not_found(self): + with pytest.raises(FileNotFoundError): + ModuleConfig("not_found.yaml") From 50c8267a84391d83c2a0e899adf191e8fed1c9a9 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 12 Aug 2024 15:47:43 +0900 Subject: [PATCH 004/249] chore: Update pyproject.toml and poetry.lock with types-pyyaml package version 6.0.12.20240808 --- poetry.lock | 13 ++++++++++++- pyproject.toml | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 6564d1f..da47b83 100644 --- a/poetry.lock +++ b/poetry.lock @@ -360,6 +360,17 @@ files = [ {file = "types_protobuf-4.25.0.20240417-py3-none-any.whl", hash = "sha256:e9b613227c2127e3d4881d75d93c93b4d6fd97b5f6a099a0b654a05351c8685d"}, ] +[[package]] +name = "types-pyyaml" +version = "6.0.12.20240808" +description = "Typing stubs for PyYAML" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-PyYAML-6.0.12.20240808.tar.gz", hash = "sha256:b8f76ddbd7f65440a8bda5526a9607e4c7a322dc2f8e1a8c405644f9a6f4b9af"}, + {file = "types_PyYAML-6.0.12.20240808-py3-none-any.whl", hash = "sha256:deda34c5c655265fc517b546c902aa6eed2ef8d3e921e4765fe606fe2afe8d35"}, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -374,4 +385,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "10cdddfced36790cc4e140d9707f7cee2ea68368dbc1b56aa8c33bec0848e7cf" +content-hash = "eaa45b01747d5585b46d2a0043f81002b7c88f0f7b6fc9920c3bda4c52506d6d" diff --git a/pyproject.toml b/pyproject.toml index cdc7976..1f891df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ python = "^3.12" rcrs_core = {git = "https://github.com/adf-python/rcrs-core-python"} pyyaml = "^6.0.2" pytest = "^8.3.2" +types-pyyaml = "^6.0.12.20240808" [tool.poetry.group.dev.dependencies] From acc6f5a1a670242f98822a433c17087c345d1f59 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 12 Aug 2024 15:54:47 +0900 Subject: [PATCH 005/249] fix: add __init__.py --- adf_core_python/__init__.py | 0 adf_core_python/core/__init__.py | 0 adf_core_python/core/config/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 adf_core_python/__init__.py create mode 100644 adf_core_python/core/__init__.py create mode 100644 adf_core_python/core/config/__init__.py diff --git a/adf_core_python/__init__.py b/adf_core_python/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/__init__.py b/adf_core_python/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/config/__init__.py b/adf_core_python/core/config/__init__.py new file mode 100644 index 0000000..e69de29 From 935d34dd2caa649d0beccc6cbd3ff4b1f5267430 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 12 Aug 2024 15:54:53 +0900 Subject: [PATCH 006/249] refactor: Update test_module_config.py to include type annotations --- tests/core/config/test_module_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/config/test_module_config.py b/tests/core/config/test_module_config.py index f9665e7..c400e1e 100644 --- a/tests/core/config/test_module_config.py +++ b/tests/core/config/test_module_config.py @@ -6,7 +6,7 @@ class TestModuleConfig: - def test_can_read_from_yaml(self): + def test_can_read_from_yaml(self) -> None: script_dir = os.path.dirname(os.path.abspath(__file__)) config_file_path = os.path.join(script_dir, "module.yaml") config = ModuleConfig(config_file_path) @@ -43,6 +43,6 @@ def test_can_read_from_yaml(self): == "adf.impl.module.algorithm.KMeansClustering" ) - def test_if_file_not_found(self): + def test_if_file_not_found(self) -> None: with pytest.raises(FileNotFoundError): ModuleConfig("not_found.yaml") From a644cbde9b6aa16cb2fc780352818171a798b0b8 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 12 Aug 2024 17:11:38 +0900 Subject: [PATCH 007/249] feat: Add pytest workflow for running tests --- .github/workflows/ci.yaml | 20 ++++++++++++++++++++ pyproject.toml | 3 ++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5f70644..c67fc75 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -123,3 +123,23 @@ jobs: run: | source .venv/bin/activate poetry run mypy . + + pytest: + needs: setup + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Restore virtual environment cache + uses: actions/cache@v4 + with: + path: .venv + key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + ${{ runner.os }}-venv- + + - name: Run pytest + run: | + source .venv/bin/activate + poetry run pytest tests/ diff --git a/pyproject.toml b/pyproject.toml index 1f891df..014b2f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,8 @@ build-backend = "poetry.core.masonry.api" format = "ruff format ." lint = "ruff check ." typecheck = "mypy ." -precommit = "task format && task lint && task typecheck" +test = "pytest" +precommit = "task format && task lint && task typecheck && task test" [tool.ruff] # Exclude a variety of commonly ignored directories. From ceb369a2f60ab4152fa2bed14819ef37eaa5c567 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 12 Aug 2024 22:57:42 +0900 Subject: [PATCH 008/249] feature: Add DevelopData class for managing development mode and data --- adf_core_python/core/develop/__init__.py | 0 adf_core_python/core/develop/develop_data.py | 70 ++++++++++++++++++++ tests/core/develop/develop.json | 12 ++++ tests/core/develop/test_develop.py | 23 +++++++ 4 files changed, 105 insertions(+) create mode 100644 adf_core_python/core/develop/__init__.py create mode 100644 adf_core_python/core/develop/develop_data.py create mode 100644 tests/core/develop/develop.json create mode 100644 tests/core/develop/test_develop.py diff --git a/adf_core_python/core/develop/__init__.py b/adf_core_python/core/develop/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/develop/develop_data.py b/adf_core_python/core/develop/develop_data.py new file mode 100644 index 0000000..f66bc3b --- /dev/null +++ b/adf_core_python/core/develop/develop_data.py @@ -0,0 +1,70 @@ +import json +from typing import Any, Dict, Final + + +class DevelopData: + DEFAULT_FILE_NAME: Final[str] = "config/develop.json" + + def __init__( + self, is_develop_mode: bool = False, file_name: str = DEFAULT_FILE_NAME + ) -> None: + """ + Constructor + """ + self._develop_data: Dict[str, Any] = self._set_data_from_json(file_name) + self._is_develop_mode: bool = is_develop_mode + + self._set_data_from_json(file_name) + + def _set_data_from_json(self, file_name: str) -> Dict[str, Any]: + """ + Set data from json + + Parameters + ---------- + file_name : str, optional + Develop file name, by default DEFAULT_FILE_NAME + + Returns + ------- + Dict[str, Any] + Develop data + """ + try: + with open(file_name, mode="r", encoding="utf-8") as file: + return json.load(file) + except FileNotFoundError: + raise FileNotFoundError(f"Develop file not found: {file_name}") + except Exception as e: + raise Exception(f"Error reading develop file: {file_name}, {e}") + + def is_develop_mode(self) -> bool: + """ + Check if develop mode is enabled + + Returns + ------- + bool + True if develop mode is enabled + """ + return self._is_develop_mode + + def get_value(self, key: str, default_value: Any = None) -> Any: + """ + Get value from develop data + If develop mode is disabled, return default value + + Parameters + ---------- + key : str + Key + + Returns + ------- + Any + Value + """ + if not self._is_develop_mode: + return default_value + + return self._develop_data.get(key, default_value) diff --git a/tests/core/develop/develop.json b/tests/core/develop/develop.json new file mode 100644 index 0000000..21ca75b --- /dev/null +++ b/tests/core/develop/develop.json @@ -0,0 +1,12 @@ +{ + "string" : "test", + "number" : 1, + "boolean" : true, + "dict" : { + "test" : "test" + }, + "array" : [ + "test", + "test" + ] +} \ No newline at end of file diff --git a/tests/core/develop/test_develop.py b/tests/core/develop/test_develop.py new file mode 100644 index 0000000..03cbb18 --- /dev/null +++ b/tests/core/develop/test_develop.py @@ -0,0 +1,23 @@ +import os + +import pytest + +from adf_core_python.core.develop.develop_data import DevelopData + + +class TestDevelopData: + def test_can_read_from_yaml(self) -> None: + script_dir = os.path.dirname(os.path.abspath(__file__)) + develop_file_path = os.path.join(script_dir, "develop.json") + develop_data = DevelopData(True, develop_file_path) + print(develop_data._develop_data) + + assert develop_data.get_value("string") == "test" + assert develop_data.get_value("number") == 1 + assert develop_data.get_value("boolean") is True + assert develop_data.get_value("dict") == {"test": "test"} + assert develop_data.get_value("array") == ["test", "test"] + + def test_if_file_not_found(self) -> None: + with pytest.raises(FileNotFoundError): + DevelopData(True, "not_found.json") From 4694c90a8329d88e1e6046fb83d6072ad498ee4f Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 12 Aug 2024 23:05:49 +0900 Subject: [PATCH 009/249] refactor: add newline at end of file --- tests/core/develop/develop.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/develop/develop.json b/tests/core/develop/develop.json index 21ca75b..3e2918f 100644 --- a/tests/core/develop/develop.json +++ b/tests/core/develop/develop.json @@ -9,4 +9,4 @@ "test", "test" ] -} \ No newline at end of file +} From 7f480159efb128f0d38b2138cd17ee2458bf650e Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 12 Aug 2024 23:20:32 +0900 Subject: [PATCH 010/249] fix: Move module_config and develop_data to agent package --- adf_core_python/core/{ => agent}/config/__init__.py | 0 adf_core_python/core/{ => agent}/config/module_config.py | 0 adf_core_python/core/{ => agent}/develop/__init__.py | 0 adf_core_python/core/{ => agent}/develop/develop_data.py | 0 tests/core/{ => agent}/config/module.yaml | 0 tests/core/{ => agent}/config/test_module_config.py | 2 +- tests/core/{ => agent}/develop/develop.json | 0 tests/core/{ => agent}/develop/test_develop.py | 2 +- 8 files changed, 2 insertions(+), 2 deletions(-) rename adf_core_python/core/{ => agent}/config/__init__.py (100%) rename adf_core_python/core/{ => agent}/config/module_config.py (100%) rename adf_core_python/core/{ => agent}/develop/__init__.py (100%) rename adf_core_python/core/{ => agent}/develop/develop_data.py (100%) rename tests/core/{ => agent}/config/module.yaml (100%) rename tests/core/{ => agent}/config/test_module_config.py (95%) rename tests/core/{ => agent}/develop/develop.json (100%) rename tests/core/{ => agent}/develop/test_develop.py (91%) diff --git a/adf_core_python/core/config/__init__.py b/adf_core_python/core/agent/config/__init__.py similarity index 100% rename from adf_core_python/core/config/__init__.py rename to adf_core_python/core/agent/config/__init__.py diff --git a/adf_core_python/core/config/module_config.py b/adf_core_python/core/agent/config/module_config.py similarity index 100% rename from adf_core_python/core/config/module_config.py rename to adf_core_python/core/agent/config/module_config.py diff --git a/adf_core_python/core/develop/__init__.py b/adf_core_python/core/agent/develop/__init__.py similarity index 100% rename from adf_core_python/core/develop/__init__.py rename to adf_core_python/core/agent/develop/__init__.py diff --git a/adf_core_python/core/develop/develop_data.py b/adf_core_python/core/agent/develop/develop_data.py similarity index 100% rename from adf_core_python/core/develop/develop_data.py rename to adf_core_python/core/agent/develop/develop_data.py diff --git a/tests/core/config/module.yaml b/tests/core/agent/config/module.yaml similarity index 100% rename from tests/core/config/module.yaml rename to tests/core/agent/config/module.yaml diff --git a/tests/core/config/test_module_config.py b/tests/core/agent/config/test_module_config.py similarity index 95% rename from tests/core/config/test_module_config.py rename to tests/core/agent/config/test_module_config.py index c400e1e..fcdd099 100644 --- a/tests/core/config/test_module_config.py +++ b/tests/core/agent/config/test_module_config.py @@ -2,7 +2,7 @@ import pytest -from adf_core_python.core.config.module_config import ModuleConfig +from adf_core_python.core.agent.config.module_config import ModuleConfig class TestModuleConfig: diff --git a/tests/core/develop/develop.json b/tests/core/agent/develop/develop.json similarity index 100% rename from tests/core/develop/develop.json rename to tests/core/agent/develop/develop.json diff --git a/tests/core/develop/test_develop.py b/tests/core/agent/develop/test_develop.py similarity index 91% rename from tests/core/develop/test_develop.py rename to tests/core/agent/develop/test_develop.py index 03cbb18..90d8026 100644 --- a/tests/core/develop/test_develop.py +++ b/tests/core/agent/develop/test_develop.py @@ -2,7 +2,7 @@ import pytest -from adf_core_python.core.develop.develop_data import DevelopData +from adf_core_python.core.agent.develop.develop_data import DevelopData class TestDevelopData: From 3c36c48f8abc6eff065850158afdad3626b64d6c Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 13 Aug 2024 20:18:42 +0900 Subject: [PATCH 011/249] feat: add ConfigKey --- adf_core_python/core/launcher/config_key.py | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 adf_core_python/core/launcher/config_key.py diff --git a/adf_core_python/core/launcher/config_key.py b/adf_core_python/core/launcher/config_key.py new file mode 100644 index 0000000..bcb2577 --- /dev/null +++ b/adf_core_python/core/launcher/config_key.py @@ -0,0 +1,24 @@ +from typing import Final + + +class ConfigKey: + # General + KEY_LOADER_CLASS: Final[str] = "adf.launcher.loader" + KEY_KERNEL_HOST: Final[str] = "kernel.host" + KEY_KERNEL_PORT: Final[str] = "kernel.port" + KEY_TEAM_NAME: Final[str] = "team.name" + KEY_DEBUG_FLAG: Final[str] = "adf.debug.flag" + KEY_DEVELOP_FLAG: Final[str] = "adf.develop.flag" + KEY_DEVELOP_DATA_FILE_NAME: Final[str] = "adf.develop.filename" + KEY_DEVELOP_DATA: Final[str] = "adf.develop.data" + KEY_MODULE_CONFIG_FILE_NAME: Final[str] = "adf.agent.moduleconfig.filename" + KEY_MODULE_DATA: Final[str] = "adf.agent.moduleconfig.data" + KEY_PRECOMPUTE: Final[str] = "adf.launcher.precompute" + # Platoon + KEY_AMBULANCE_TEAM_COUNT: Final[str] = "adf.team.platoon.ambulance.count" + KEY_FIRE_BRIGADE_COUNT: Final[str] = "adf.team.platoon.fire.count" + KEY_POLICE_FORCE_COUNT: Final[str] = "adf.team.platoon.police.count" + # Office + KEY_AMBULANCE_CENTRE_COUNT: Final[str] = "adf.team.office.ambulance.count" + KEY_FIRE_STATION_COUNT: Final[str] = "adf.team.office.fire.count" + KEY_POLICE_OFFICE_COUNT: Final[str] = "adf.team.office.police.count" From abe7547687351371b4587243b85ffc304596616d Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 13 Aug 2024 22:00:14 +0900 Subject: [PATCH 012/249] feat: Update package --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index da47b83..733bc53 100644 --- a/poetry.lock +++ b/poetry.lock @@ -274,7 +274,7 @@ rtree = "*" type = "git" url = "https://github.com/adf-python/rcrs-core-python" reference = "HEAD" -resolved_reference = "4fb11912097abccd68fc2ee7440eaa16adbcdc23" +resolved_reference = "6771354084ce60b3dd96a77f509ad1e0314ac886" [[package]] name = "rtree" From 5e0f690e0e3275d9d71f5325fc2c695deb264da2 Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 13 Aug 2024 22:00:24 +0900 Subject: [PATCH 013/249] feat: Add abstract loader and connector classes --- adf_core_python/core/component/__init__.py | 0 .../core/component/abstract_loader.py | 36 ++++++++++ .../core/launcher/connect/connector.py | 18 +++++ .../connect/connector_ambulance_centre.py | 65 +++++++++++++++++++ 4 files changed, 119 insertions(+) create mode 100644 adf_core_python/core/component/__init__.py create mode 100644 adf_core_python/core/component/abstract_loader.py create mode 100644 adf_core_python/core/launcher/connect/connector.py create mode 100644 adf_core_python/core/launcher/connect/connector_ambulance_centre.py diff --git a/adf_core_python/core/component/__init__.py b/adf_core_python/core/component/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/component/abstract_loader.py b/adf_core_python/core/component/abstract_loader.py new file mode 100644 index 0000000..364117b --- /dev/null +++ b/adf_core_python/core/component/abstract_loader.py @@ -0,0 +1,36 @@ +from abc import ABC, abstractmethod + +class AbstractLoader(ABC): + def __init__(self) -> None: + self._team_name: str = "" + + def get_team_name(self) -> str: + return self._team_name + + def set_team_name(self, team_name: str) -> None: + self._team_name = team_name + + # TODO: Add more abstract methods here + @abstractmethod + def get_tactics_ambulance_team(self) -> None: + raise NotImplementedError + + @abstractmethod + def get_tactics_fire_brigade(self) -> None: + raise NotImplementedError + + @abstractmethod + def get_tactics_police_force(self) -> None: + raise NotImplementedError + + @abstractmethod + def get_tactics_ambulance_centre(self) -> None: + raise NotImplementedError + + @abstractmethod + def get_tactics_fire_station(self) -> None: + raise NotImplementedError + + @abstractmethod + def get_tactics_police_office(self) -> None: + raise NotImplementedError diff --git a/adf_core_python/core/launcher/connect/connector.py b/adf_core_python/core/launcher/connect/connector.py new file mode 100644 index 0000000..d95f2fe --- /dev/null +++ b/adf_core_python/core/launcher/connect/connector.py @@ -0,0 +1,18 @@ +from abc import ABC, abstractmethod + +from rcrs_core.connection.componentLauncher import ComponentLauncher +from rcrs_core.config.config import Config + +from adf_core_python.core.component.abstract_loader import AbstractLoader + + +class Connector(ABC): + def __init__(self) -> None: + self.connected_agent_count = 0 + + @abstractmethod + def connect(self, component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader) -> None: + raise NotImplementedError + + def get_connected_agent_count(self) -> int: + return self.connected_agent_count diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py new file mode 100644 index 0000000..d946531 --- /dev/null +++ b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py @@ -0,0 +1,65 @@ +from logging import Logger, getLogger + +from rcrs_core.agents.ambulanceCenterAgent import AmbulanceCenterAgent +from rcrs_core.config.config import Config +from rcrs_core.connection.componentLauncher import ComponentLauncher + +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.launcher.connect.connector import Connector + + +class ConnectorAmbulanceCentre(Connector): + def __init__(self): + super().__init__() + self.logger: Logger = getLogger(__name__) + + def connect( + self, + component_launcher: ComponentLauncher, + config: Config, + loader: AbstractLoader, + ) -> None: + count: int = config.get_int_value_or_default( + ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0 + ) + if count == 0: + return + + for i in range(count): + # tactics_ambulance_centre: TacticsAmbulanceCentre + if loader.get_tactics_ambulance_centre() is not None: + self.logger.error("Cannot load ambulance centre tactics") + # tactics_ambulance_centre = loader.get_tactics_ambulance_centre() + else: + # tactics_ambulance_centre = DummyTacticsAmbulanceCentre() + pass + + module_config: ModuleConfig = ModuleConfig( + config.get_value_or_default( + ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, + ModuleConfig.DEFAULT_CONFIG_FILE_NAME, + ) + ) + + develop_data: DevelopData = DevelopData( + config.get_boolean_value_or_default(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value_or_default( + ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME + ), + ) + + # TODO: component_launcher.generate_request_ID can cause race condition + component_launcher.connect( + # TODO: AmbulanceCenterAgent is not implemented precompute method and other methods + AmbulanceCenterAgent( + config.get_boolean_value_or_default( + ConfigKey.KEY_PRECOMPUTE, False + ), + ), # type: ignore + component_launcher.generate_request_ID(), + ) + + self.logger.info("Connected ambulance centre (count: %d)" % count) From b597488c262409074a597aa8553bb3adcdd19cd4 Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 13 Aug 2024 22:03:07 +0900 Subject: [PATCH 014/249] refactor: Improve code formatting and style in abstract_loader and connector classes --- .../core/component/abstract_loader.py | 67 ++++++++++--------- .../core/launcher/connect/connector.py | 19 ++++-- .../connect/connector_ambulance_centre.py | 6 +- 3 files changed, 49 insertions(+), 43 deletions(-) diff --git a/adf_core_python/core/component/abstract_loader.py b/adf_core_python/core/component/abstract_loader.py index 364117b..1a8c210 100644 --- a/adf_core_python/core/component/abstract_loader.py +++ b/adf_core_python/core/component/abstract_loader.py @@ -1,36 +1,37 @@ from abc import ABC, abstractmethod + class AbstractLoader(ABC): - def __init__(self) -> None: - self._team_name: str = "" - - def get_team_name(self) -> str: - return self._team_name - - def set_team_name(self, team_name: str) -> None: - self._team_name = team_name - - # TODO: Add more abstract methods here - @abstractmethod - def get_tactics_ambulance_team(self) -> None: - raise NotImplementedError - - @abstractmethod - def get_tactics_fire_brigade(self) -> None: - raise NotImplementedError - - @abstractmethod - def get_tactics_police_force(self) -> None: - raise NotImplementedError - - @abstractmethod - def get_tactics_ambulance_centre(self) -> None: - raise NotImplementedError - - @abstractmethod - def get_tactics_fire_station(self) -> None: - raise NotImplementedError - - @abstractmethod - def get_tactics_police_office(self) -> None: - raise NotImplementedError + def __init__(self) -> None: + self._team_name: str = "" + + def get_team_name(self) -> str: + return self._team_name + + def set_team_name(self, team_name: str) -> None: + self._team_name = team_name + + # TODO: Add more abstract methods here + @abstractmethod + def get_tactics_ambulance_team(self) -> None: + raise NotImplementedError + + @abstractmethod + def get_tactics_fire_brigade(self) -> None: + raise NotImplementedError + + @abstractmethod + def get_tactics_police_force(self) -> None: + raise NotImplementedError + + @abstractmethod + def get_tactics_ambulance_centre(self) -> None: + raise NotImplementedError + + @abstractmethod + def get_tactics_fire_station(self) -> None: + raise NotImplementedError + + @abstractmethod + def get_tactics_police_office(self) -> None: + raise NotImplementedError diff --git a/adf_core_python/core/launcher/connect/connector.py b/adf_core_python/core/launcher/connect/connector.py index d95f2fe..b8ea5c4 100644 --- a/adf_core_python/core/launcher/connect/connector.py +++ b/adf_core_python/core/launcher/connect/connector.py @@ -7,12 +7,17 @@ class Connector(ABC): - def __init__(self) -> None: - self.connected_agent_count = 0 + def __init__(self) -> None: + self.connected_agent_count = 0 - @abstractmethod - def connect(self, component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader) -> None: - raise NotImplementedError + @abstractmethod + def connect( + self, + component_launcher: ComponentLauncher, + config: Config, + loader: AbstractLoader, + ) -> None: + raise NotImplementedError - def get_connected_agent_count(self) -> int: - return self.connected_agent_count + def get_connected_agent_count(self) -> int: + return self.connected_agent_count diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py index d946531..0ed2b33 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py @@ -12,7 +12,7 @@ class ConnectorAmbulanceCentre(Connector): - def __init__(self): + def __init__(self) -> None: super().__init__() self.logger: Logger = getLogger(__name__) @@ -37,14 +37,14 @@ def connect( # tactics_ambulance_centre = DummyTacticsAmbulanceCentre() pass - module_config: ModuleConfig = ModuleConfig( + module_config: ModuleConfig = ModuleConfig( # noqa: F841 config.get_value_or_default( ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, ModuleConfig.DEFAULT_CONFIG_FILE_NAME, ) ) - develop_data: DevelopData = DevelopData( + develop_data: DevelopData = DevelopData( # noqa: F841 config.get_boolean_value_or_default(ConfigKey.KEY_DEBUG_FLAG, False), config.get_value_or_default( ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME From 08ee7c46292b3a6eb5927a055eb8305ac1644c21 Mon Sep 17 00:00:00 2001 From: shima004 Date: Sat, 17 Aug 2024 20:59:51 +0900 Subject: [PATCH 015/249] feat: implements connect classes --- .../connect/connector_ambulance_centre.py | 2 +- .../connect/connector_ambulance_team.py | 64 +++++++++++++++++++ .../connect/connector_fire_brigade.py | 64 +++++++++++++++++++ .../connect/connector_fire_station.py | 64 +++++++++++++++++++ .../connect/connector_police_force.py | 64 +++++++++++++++++++ .../connect/connector_police_office.py | 64 +++++++++++++++++++ 6 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 adf_core_python/core/launcher/connect/connector_ambulance_team.py create mode 100644 adf_core_python/core/launcher/connect/connector_fire_brigade.py create mode 100644 adf_core_python/core/launcher/connect/connector_fire_station.py create mode 100644 adf_core_python/core/launcher/connect/connector_police_force.py create mode 100644 adf_core_python/core/launcher/connect/connector_police_office.py diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py index 0ed2b33..702056b 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py @@ -28,7 +28,7 @@ def connect( if count == 0: return - for i in range(count): + for _ in range(count): # tactics_ambulance_centre: TacticsAmbulanceCentre if loader.get_tactics_ambulance_centre() is not None: self.logger.error("Cannot load ambulance centre tactics") diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py new file mode 100644 index 0000000..d29a15d --- /dev/null +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -0,0 +1,64 @@ +from logging import Logger, getLogger + +from rcrs_core.agents.ambulanceTeamAgent import AmbulanceTeamAgent +from rcrs_core.config.config import Config +from rcrs_core.connection.componentLauncher import ComponentLauncher + +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.launcher.connect.connector import Connector + + +class ConnectorAmbulanceTeam(Connector): + def __init__(self) -> None: + super().__init__() + self.logger: Logger = getLogger(__name__) + + def connect( + self, + component_launcher: ComponentLauncher, + config: Config, + loader: AbstractLoader, + ) -> None: + count: int = config.get_int_value_or_default( + ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0 + ) + if count == 0: + return + + for _ in range(count): + # tactics_ambulance_team: TacticsAmbulanceTeam + if loader.get_tactics_ambulance_team() is not None: + self.logger.error("Cannot load ambulance team tactics") + # tactics_ambulance_team = loader.get_tactics_ambulance_team() + else: + # tactics_ambulance_team = DummyTacticsAmbulanceTeam() + pass + + module_config: ModuleConfig = ModuleConfig( # noqa: F841 + config.get_value_or_default( + ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, + ModuleConfig.DEFAULT_CONFIG_FILE_NAME, + ) + ) + + develop_data: DevelopData = DevelopData( # noqa: F841 + config.get_boolean_value_or_default(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value_or_default( + ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME + ), + ) + + # TODO: component_launcher.generate_request_ID can cause race condition + component_launcher.connect( + AmbulanceTeamAgent( + config.get_boolean_value_or_default( + ConfigKey.KEY_PRECOMPUTE, False + ), + ), + component_launcher.generate_request_ID(), + ) + + self.logger.info("Connected ambulance team (count: %d)" % count) diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/adf_core_python/core/launcher/connect/connector_fire_brigade.py new file mode 100644 index 0000000..8c2784c --- /dev/null +++ b/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -0,0 +1,64 @@ +from logging import Logger, getLogger + +from rcrs_core.agents.fireBrigadeAgent import FireBrigadeAgent +from rcrs_core.config.config import Config +from rcrs_core.connection.componentLauncher import ComponentLauncher + +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.launcher.connect.connector import Connector + + +class ConnectorFIreBrigade(Connector): + def __init__(self) -> None: + super().__init__() + self.logger: Logger = getLogger(__name__) + + def connect( + self, + component_launcher: ComponentLauncher, + config: Config, + loader: AbstractLoader, + ) -> None: + count: int = config.get_int_value_or_default( + ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0 + ) + if count == 0: + return + + for _ in range(count): + # tactics_fire_brigade: TacticsFireBrigade + if loader.get_tactics_fire_brigade() is not None: + self.logger.error("Cannot load fire brigade tactics") + # tactics_fire_brigade = loader.get_tactics_fire_brigade() + else: + # tactics_fire_brigade = DummyTacticsFireBrigade() + pass + + module_config: ModuleConfig = ModuleConfig( # noqa: F841 + config.get_value_or_default( + ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, + ModuleConfig.DEFAULT_CONFIG_FILE_NAME, + ) + ) + + develop_data: DevelopData = DevelopData( # noqa: F841 + config.get_boolean_value_or_default(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value_or_default( + ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME + ), + ) + + # TODO: component_launcher.generate_request_ID can cause race condition + component_launcher.connect( + FireBrigadeAgent( + config.get_boolean_value_or_default( + ConfigKey.KEY_PRECOMPUTE, False + ), + ), + component_launcher.generate_request_ID(), + ) + + self.logger.info("Connected fire brigade (count: %d)" % count) diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/adf_core_python/core/launcher/connect/connector_fire_station.py new file mode 100644 index 0000000..f3c197a --- /dev/null +++ b/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -0,0 +1,64 @@ +from logging import Logger, getLogger + +from rcrs_core.agents.fireStationAgent import FireStationAgent +from rcrs_core.config.config import Config +from rcrs_core.connection.componentLauncher import ComponentLauncher + +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.launcher.connect.connector import Connector + + +class ConnectorFireStation(Connector): + def __init__(self) -> None: + super().__init__() + self.logger: Logger = getLogger(__name__) + + def connect( + self, + component_launcher: ComponentLauncher, + config: Config, + loader: AbstractLoader, + ) -> None: + count: int = config.get_int_value_or_default( + ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0 + ) + if count == 0: + return + + for _ in range(count): + # tactics_fire_station: TacticsFireStation + if loader.get_tactics_fire_station() is not None: + self.logger.error("Cannot load fire station tactics") + # tactics_fire_station = loader.get_tactics_fire_station() + else: + # tactics_fire_station = DummyTacticsFireStation() + pass + + module_config: ModuleConfig = ModuleConfig( # noqa: F841 + config.get_value_or_default( + ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, + ModuleConfig.DEFAULT_CONFIG_FILE_NAME, + ) + ) + + develop_data: DevelopData = DevelopData( # noqa: F841 + config.get_boolean_value_or_default(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value_or_default( + ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME + ), + ) + + # TODO: component_launcher.generate_request_ID can cause race condition + component_launcher.connect( + FireStationAgent( + config.get_boolean_value_or_default( + ConfigKey.KEY_PRECOMPUTE, False + ), + ), # type: ignore + component_launcher.generate_request_ID(), + ) + + self.logger.info("Connected fire station (count: %d)" % count) diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py new file mode 100644 index 0000000..429c15c --- /dev/null +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -0,0 +1,64 @@ +from logging import Logger, getLogger + +from rcrs_core.agents.policeForceAgent import PoliceForceAgent +from rcrs_core.config.config import Config +from rcrs_core.connection.componentLauncher import ComponentLauncher + +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.launcher.connect.connector import Connector + + +class ConnectorPoliceForce(Connector): + def __init__(self) -> None: + super().__init__() + self.logger: Logger = getLogger(__name__) + + def connect( + self, + component_launcher: ComponentLauncher, + config: Config, + loader: AbstractLoader, + ) -> None: + count: int = config.get_int_value_or_default( + ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0 + ) + if count == 0: + return + + for _ in range(count): + # tactics_police_force: TacticsPoliceForce + if loader.get_tactics_police_force() is not None: + self.logger.error("Cannot load police force tactics") + # tactics_police_force = loader.get_tactics_police_force() + else: + # tactics_police_force = DummyTacticsPoliceForce() + pass + + module_config: ModuleConfig = ModuleConfig( # noqa: F841 + config.get_value_or_default( + ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, + ModuleConfig.DEFAULT_CONFIG_FILE_NAME, + ) + ) + + develop_data: DevelopData = DevelopData( # noqa: F841 + config.get_boolean_value_or_default(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value_or_default( + ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME + ), + ) + + # TODO: component_launcher.generate_request_ID can cause race condition + component_launcher.connect( + PoliceForceAgent( + config.get_boolean_value_or_default( + ConfigKey.KEY_PRECOMPUTE, False + ), + ), + component_launcher.generate_request_ID(), + ) + + self.logger.info("Connected ambulance centre (count: %d)" % count) diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py new file mode 100644 index 0000000..dd43031 --- /dev/null +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -0,0 +1,64 @@ +from logging import Logger, getLogger + +from rcrs_core.agents.policeOfficeAgent import PoliceOfficeAgent +from rcrs_core.config.config import Config +from rcrs_core.connection.componentLauncher import ComponentLauncher + +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.launcher.connect.connector import Connector + + +class ConnectorPoliceOffice(Connector): + def __init__(self) -> None: + super().__init__() + self.logger: Logger = getLogger(__name__) + + def connect( + self, + component_launcher: ComponentLauncher, + config: Config, + loader: AbstractLoader, + ) -> None: + count: int = config.get_int_value_or_default( + ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0 + ) + if count == 0: + return + + for _ in range(count): + # tactics_police_office: TacticsPoliceOffice + if loader.get_tactics_police_office() is not None: + self.logger.error("Cannot load police office tactics") + # tactics_police_office = loader.get_tactics_police_office() + else: + # tactics_police_office = DummyTacticsPoliceOffice() + pass + + module_config: ModuleConfig = ModuleConfig( # noqa: F841 + config.get_value_or_default( + ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, + ModuleConfig.DEFAULT_CONFIG_FILE_NAME, + ) + ) + + develop_data: DevelopData = DevelopData( # noqa: F841 + config.get_boolean_value_or_default(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value_or_default( + ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME + ), + ) + + # TODO: component_launcher.generate_request_ID can cause race condition + component_launcher.connect( + PoliceOfficeAgent( + config.get_boolean_value_or_default( + ConfigKey.KEY_PRECOMPUTE, False + ), + ), + component_launcher.generate_request_ID(), + ) + + self.logger.info("Connected ambulance centre (count: %d)" % count) From 8863a7e3b59274540d7467303c954664f830b86e Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 28 Aug 2024 17:12:06 +0900 Subject: [PATCH 016/249] feat: Add Action, AgentInfo, ScenarioInfo, and WorldInfo classes --- adf_core_python/core/agent/action/action.py | 10 + adf_core_python/core/agent/info/agent_info.py | 180 ++++++++++++++++++ .../core/agent/info/scenario_info.py | 74 +++++++ adf_core_python/core/agent/info/world_info.py | 30 +++ 4 files changed, 294 insertions(+) create mode 100644 adf_core_python/core/agent/action/action.py create mode 100644 adf_core_python/core/agent/info/agent_info.py create mode 100644 adf_core_python/core/agent/info/scenario_info.py create mode 100644 adf_core_python/core/agent/info/world_info.py diff --git a/adf_core_python/core/agent/action/action.py b/adf_core_python/core/agent/action/action.py new file mode 100644 index 0000000..84cef3a --- /dev/null +++ b/adf_core_python/core/agent/action/action.py @@ -0,0 +1,10 @@ +from abc import ABC, abstractmethod + +from rcrs_core.messages.message import Message +from rcrs_core.worldmodel.entityID import EntityID + + +class Action(ABC): + @abstractmethod + def get_command(self, agent_id: EntityID, time: int) -> Message: + raise NotImplementedError diff --git a/adf_core_python/core/agent/info/agent_info.py b/adf_core_python/core/agent/info/agent_info.py new file mode 100644 index 0000000..96f49f0 --- /dev/null +++ b/adf_core_python/core/agent/info/agent_info.py @@ -0,0 +1,180 @@ +from time import time +from typing import Any, Dict, List + +from rcrs_core.agents.agent import Agent +from rcrs_core.entities.human import Human +from rcrs_core.worldmodel.worldmodel import ChangeSet, Entity, EntityID, WorldModel + +from adf_core_python.core.agent.action.action import Action + + +class AgentInfo: + # TODO: Replace Any with the actual type + def __init__(self, agent: Agent, world_model: WorldModel): + self._agent: Agent = agent + self._world_model: WorldModel = world_model + self._time: int = 0 + self._action_history: Dict[int, Action] = {} + self._heard_commands: List[Any] = [] + self._change_set: ChangeSet = ChangeSet() + self._start_think_time: float = 0.0 + + def set_time(self, time: int) -> None: + """ + Set the current time of the agent + + Parameters + ---------- + time : int + Current time + """ + self._time = time + + def get_time(self) -> int: + """ + Get the current time of the agent + + Returns + ------- + int + Current time + """ + return self._time + + def set_heard_commands(self, heard_commands: List[Any]) -> None: + """ + Set the heard commands + + Parameters + ---------- + heard_commands : List[Any] + Heard commands + """ + self._heard_commands = heard_commands + + def get_heard_commands(self) -> List[Any]: + """ + Get the heard commands + + Returns + ------- + List[Any] + Heard commands + """ + return self._heard_commands + + def get_entity_id(self) -> EntityID: + """ + Get the entity ID of the agent + + Returns + ------- + EntityID + Entity ID of the agent + """ + return self._agent.get_id() # type: ignore TODO: Agent class should return EntityID instead of EntityID | None + + def get_myself(self) -> Entity: + """ + Get the entity of the agent + + Returns + ------- + Entity + Entity of the agent + """ + return self._world_model.get_entity(self.get_entity_id()) + + def get_position_entity_id(self) -> EntityID: + """ + Get the position entity ID of the agent + + Returns + ------- + EntityID + Position entity ID of the agent + """ + entity = self._world_model.get_entity(self.get_entity_id()) + if isinstance(entity, Human): + return entity.get_position_property() + else: + return entity.get_id() + + def set_change_set(self, change_set: ChangeSet) -> None: + """ + Set the change set + + Parameters + ---------- + change_set : ChangeSet + Change set + """ + self._change_set = change_set + + def get_change_set(self) -> ChangeSet: + """ + Get the change set + + Returns + ------- + ChangeSet + Change set + """ + return self._change_set + + def some_one_on_board(self) -> Human | None: + """ + Get the human if someone is on board + + Returns + ------- + Human | None + Human if someone is on board, None otherwise + """ + entity_id: EntityID = self.get_entity_id() + for entity in self._world_model.get_entities(): + if isinstance(entity, Human): + if entity.get_position_property() == entity_id: + return entity + return None + + def get_executed_action(self, time: int) -> Action | None: + """ + Get the executed action at the given time + + Parameters + ---------- + time : int + Time + """ + return self._action_history.get(time) + + def set_executed_action(self, time: int, action: Action) -> None: + """ + Set the executed action at the given time + + Parameters + ---------- + time : int + Time + action : Action + Executed action + """ + self._action_history[time] = action + + def record_think_start_time(self) -> None: + """ + Record the start time of thinking + """ + self._start_think_time = time() + + def get_think_time(self) -> float: + """ + Get the time taken for thinking + + Returns + ------- + float + Time taken for thinking + """ + return time() - self._start_think_time diff --git a/adf_core_python/core/agent/info/scenario_info.py b/adf_core_python/core/agent/info/scenario_info.py new file mode 100644 index 0000000..6a2d699 --- /dev/null +++ b/adf_core_python/core/agent/info/scenario_info.py @@ -0,0 +1,74 @@ +from enum import Enum + +from rcrs_core.config.config import Config + + +class Mode(Enum): + NON_PRECOMPUTE = 0 + PRECOMPUTED = 1 + PRECOMPUTATION = 2 + + +class ScenarioInfo: + def __init__(self, config: Config, mode: Mode): + """ + Constructor + + Parameters + ---------- + config : Config + Configuration + mode : Mode + Mode of the scenario + """ + self._config: Config = config + self._mode: Mode = mode + + def set_config(self, config: Config) -> None: + """ + Set the configuration + + Parameters + ---------- + config : Config + Configuration + """ + self._config = config + + def get_config(self) -> Config: + """ + Get the configuration + + Returns + ------- + Config + Configuration + """ + return self._config + + def get_mode(self) -> Mode: + """ + Get the mode of the scenario + + Returns + ------- + Mode + Mode of the scenario + """ + return self._mode + + def get_config_value(self, key: str, default: str) -> str: + """ + Get the value of the configuration + + Parameters + ---------- + key : str + Key of the configuration + + Returns + ------- + str + Value of the configuration + """ + return self._config.get_value_or_default(key, default) diff --git a/adf_core_python/core/agent/info/world_info.py b/adf_core_python/core/agent/info/world_info.py new file mode 100644 index 0000000..28a1612 --- /dev/null +++ b/adf_core_python/core/agent/info/world_info.py @@ -0,0 +1,30 @@ +from typing import Any, Dict + +from rcrs_core.worldmodel.entityID import EntityID +from rcrs_core.worldmodel.worldmodel import WorldModel + + +class WorldInfo: + def __init__(self, world_model: WorldModel): + self._world_model: WorldModel = world_model + self._time: int = 0 + self._is_run_rollback: bool = False + self._rollback: Dict[EntityID, Dict[int, Dict[int, Any]]] = {} + + def index_class(self): + """ + Index class + """ + self._world_model.index + + # TODO: Implement the worldmodel access methods + def get_world_model(self) -> WorldModel: + """ + Get the world model + + Returns + ------- + WorldModel + World model + """ + return self._world_model From c4c4556fccd1bdcca36f1d77c59e154e44b6ac58 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 28 Aug 2024 17:29:38 +0900 Subject: [PATCH 017/249] refactor: Update AgentInfo and WorldInfo classes --- adf_core_python/core/agent/info/agent_info.py | 3 ++- adf_core_python/core/agent/info/world_info.py | 6 ------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/adf_core_python/core/agent/info/agent_info.py b/adf_core_python/core/agent/info/agent_info.py index 96f49f0..8d64006 100644 --- a/adf_core_python/core/agent/info/agent_info.py +++ b/adf_core_python/core/agent/info/agent_info.py @@ -72,7 +72,8 @@ def get_entity_id(self) -> EntityID: EntityID Entity ID of the agent """ - return self._agent.get_id() # type: ignore TODO: Agent class should return EntityID instead of EntityID | None + # TODO: Agent class should return EntityID instead of EntityID | None + return self._agent.get_id() # type: ignore def get_myself(self) -> Entity: """ diff --git a/adf_core_python/core/agent/info/world_info.py b/adf_core_python/core/agent/info/world_info.py index 28a1612..87c5a62 100644 --- a/adf_core_python/core/agent/info/world_info.py +++ b/adf_core_python/core/agent/info/world_info.py @@ -11,12 +11,6 @@ def __init__(self, world_model: WorldModel): self._is_run_rollback: bool = False self._rollback: Dict[EntityID, Dict[int, Dict[int, Any]]] = {} - def index_class(self): - """ - Index class - """ - self._world_model.index - # TODO: Implement the worldmodel access methods def get_world_model(self) -> WorldModel: """ From 81fe1319755999347f8d818f154420a71f8baf15 Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 3 Sep 2024 18:12:17 +0900 Subject: [PATCH 018/249] feat: Add TacticsAgent, TacticsCenter, and related classes --- .../core/component/tactics/tactics_agent.py | 140 ++++++++++++++++++ .../tactics/tactics_ambulance_center.py | 11 ++ .../tactics/tactics_ambulance_team.py | 11 ++ .../core/component/tactics/tactics_center.py | 111 ++++++++++++++ .../component/tactics/tactics_fire_brigade.py | 11 ++ .../component/tactics/tactics_fire_station.py | 11 ++ .../component/tactics/tactics_police_force.py | 11 ++ .../tactics/tactics_police_office.py | 11 ++ 8 files changed, 317 insertions(+) create mode 100644 adf_core_python/core/component/tactics/tactics_agent.py create mode 100644 adf_core_python/core/component/tactics/tactics_ambulance_center.py create mode 100644 adf_core_python/core/component/tactics/tactics_ambulance_team.py create mode 100644 adf_core_python/core/component/tactics/tactics_center.py create mode 100644 adf_core_python/core/component/tactics/tactics_fire_brigade.py create mode 100644 adf_core_python/core/component/tactics/tactics_fire_station.py create mode 100644 adf_core_python/core/component/tactics/tactics_police_force.py create mode 100644 adf_core_python/core/component/tactics/tactics_police_office.py diff --git a/adf_core_python/core/component/tactics/tactics_agent.py b/adf_core_python/core/component/tactics/tactics_agent.py new file mode 100644 index 0000000..d1c74bf --- /dev/null +++ b/adf_core_python/core/component/tactics/tactics_agent.py @@ -0,0 +1,140 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any, List, Optional + +if TYPE_CHECKING: + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.component.extaction.ext_action import ExtAction + from adf_core_python.core.component.module.abstract_module import AbstractModule + + +class TacticsAgent(ABC): + def __init__(self, parent: Optional[TacticsAgent] = None) -> None: + self._parent = parent + self._modules: List[AbstractModule] = [] + self._actions: List[ExtAction] = [] + self._command_executor: Any = None + + @abstractmethod + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + @abstractmethod + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + @abstractmethod + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + @abstractmethod + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + @abstractmethod + def update_info( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def get_parent_tactics(self) -> Optional[TacticsAgent]: + return self._parent + + def register_module(self, module: AbstractModule) -> None: + self._modules.append(module) + + def unregister_module(self, module: AbstractModule) -> None: + self._modules.remove(module) + + def register_action(self, action: ExtAction) -> None: + self._actions.append(action) + + def unregister_action(self, action: ExtAction) -> None: + self._actions.remove(action) + + def register_command_executor(self, command_executor: Any) -> None: + self._command_executor = command_executor + + def unregister_command_executor(self) -> None: + self._command_executor = None + + def module_precompute(self, precompute_data: PrecomputeData) -> None: + for module in self._modules: + module.precompute(precompute_data) + for action in self._actions: + action.precompute(precompute_data) + for executor in self._command_executor: + executor.precompute(precompute_data) + + def module_resume(self, precompute_data: PrecomputeData) -> None: + for module in self._modules: + module.resume(precompute_data) + for action in self._actions: + action.resume(precompute_data) + for executor in self._command_executor: + executor.resume(precompute_data) + + def module_prepare(self) -> None: + for module in self._modules: + module.prepare() + for action in self._actions: + action.prepare() + for executor in self._command_executor: + executor.prepare() + + def module_update_info(self, message_manager: MessageManager) -> None: + for module in self._modules: + module.update_info(message_manager) + for action in self._actions: + action.update_info(message_manager) + for executor in self._command_executor: + executor.update_info(message_manager) diff --git a/adf_core_python/core/component/tactics/tactics_ambulance_center.py b/adf_core_python/core/component/tactics/tactics_ambulance_center.py new file mode 100644 index 0000000..371c2e7 --- /dev/null +++ b/adf_core_python/core/component/tactics/tactics_ambulance_center.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +if TYPE_CHECKING: + from adf_core_python.core.component.tactics.tactics_center import TacticsCenter + + +class TacticsAmbulanceCenter(TacticsCenter): + def __init__(self, parent: Optional[TacticsAmbulanceCenter] = None) -> None: + super().__init__(parent) diff --git a/adf_core_python/core/component/tactics/tactics_ambulance_team.py b/adf_core_python/core/component/tactics/tactics_ambulance_team.py new file mode 100644 index 0000000..f596dc0 --- /dev/null +++ b/adf_core_python/core/component/tactics/tactics_ambulance_team.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +if TYPE_CHECKING: + from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent + + +class TacticsAmbulanceTeam(TacticsAgent): + def __init__(self, parent: Optional[TacticsAmbulanceTeam] = None) -> None: + super().__init__(parent) diff --git a/adf_core_python/core/component/tactics/tactics_center.py b/adf_core_python/core/component/tactics/tactics_center.py new file mode 100644 index 0000000..cd6de03 --- /dev/null +++ b/adf_core_python/core/component/tactics/tactics_center.py @@ -0,0 +1,111 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any, List, Optional + +if TYPE_CHECKING: + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.component.module.abstract_module import AbstractModule + + +class TacticsCenter(ABC): + def __init__(self, parent: Optional[TacticsCenter] = None) -> None: + self._parent = parent + self._modules: List[AbstractModule] = [] + self._command_pickers: List[Any] = [] + + @abstractmethod + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + @abstractmethod + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + @abstractmethod + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + @abstractmethod + def think( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def get_parent_tactics(self) -> Optional[TacticsCenter]: + return self._parent + + def register_module(self, module: AbstractModule) -> None: + self._modules.append(module) + + def unregister_module(self, module: AbstractModule) -> None: + self._modules.remove(module) + + def register_command_picker(self, command_picker: Any) -> None: + self._command_pickers.append(command_picker) + + def unregister_command_picker(self, command_picker: Any) -> None: + self._command_pickers.remove(command_picker) + + def module_precompute(self, precompute_data: PrecomputeData) -> None: + for module in self._modules: + module.precompute(precompute_data) + for command_picker in self._command_pickers: + command_picker.precompute(precompute_data) + + def module_resume(self, precompute_data: PrecomputeData) -> None: + for module in self._modules: + module.resume(precompute_data) + for command_picker in self._command_pickers: + command_picker.resume(precompute_data) + + def module_prepare(self) -> None: + for module in self._modules: + module.prepare() + for command_picker in self._command_pickers: + command_picker.prepare() + + def module_update_info(self, message_manager: MessageManager) -> None: + for module in self._modules: + module.update_info(message_manager) + for command_picker in self._command_pickers: + command_picker.update_info(message_manager) diff --git a/adf_core_python/core/component/tactics/tactics_fire_brigade.py b/adf_core_python/core/component/tactics/tactics_fire_brigade.py new file mode 100644 index 0000000..ce2fc7a --- /dev/null +++ b/adf_core_python/core/component/tactics/tactics_fire_brigade.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +if TYPE_CHECKING: + from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent + + +class TacticsFireBrigade(TacticsAgent): + def __init__(self, parent: Optional[TacticsFireBrigade] = None) -> None: + super().__init__(parent) diff --git a/adf_core_python/core/component/tactics/tactics_fire_station.py b/adf_core_python/core/component/tactics/tactics_fire_station.py new file mode 100644 index 0000000..8723289 --- /dev/null +++ b/adf_core_python/core/component/tactics/tactics_fire_station.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +if TYPE_CHECKING: + from adf_core_python.core.component.tactics.tactics_center import TacticsCenter + + +class TacticsFireStation(TacticsCenter): + def __init__(self, parent: Optional[TacticsFireStation] = None) -> None: + super().__init__(parent) diff --git a/adf_core_python/core/component/tactics/tactics_police_force.py b/adf_core_python/core/component/tactics/tactics_police_force.py new file mode 100644 index 0000000..e64950b --- /dev/null +++ b/adf_core_python/core/component/tactics/tactics_police_force.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +if TYPE_CHECKING: + from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent + + +class TacticsPoliceForce(TacticsAgent): + def __init__(self, parent: Optional[TacticsPoliceForce] = None) -> None: + super().__init__(parent) diff --git a/adf_core_python/core/component/tactics/tactics_police_office.py b/adf_core_python/core/component/tactics/tactics_police_office.py new file mode 100644 index 0000000..dc41629 --- /dev/null +++ b/adf_core_python/core/component/tactics/tactics_police_office.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +if TYPE_CHECKING: + from adf_core_python.core.component.tactics.tactics_center import TacticsCenter + + +class TacticsPoliceOffice(TacticsCenter): + def __init__(self, parent: Optional[TacticsPoliceOffice] = None) -> None: + super().__init__(parent) From f8b123b37b2f5a7abacc390fcf4a07b21119519f Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 3 Sep 2024 18:40:16 +0900 Subject: [PATCH 019/249] feat: Add CommunicationMessage class for handling communication messages --- .../core/agent/communication/__init__.py | 0 .../communication/communication_message.py | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 adf_core_python/core/agent/communication/__init__.py create mode 100644 adf_core_python/core/component/communication/communication_message.py diff --git a/adf_core_python/core/agent/communication/__init__.py b/adf_core_python/core/agent/communication/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/component/communication/communication_message.py b/adf_core_python/core/component/communication/communication_message.py new file mode 100644 index 0000000..2909fcf --- /dev/null +++ b/adf_core_python/core/component/communication/communication_message.py @@ -0,0 +1,19 @@ +from abc import ABC, abstractmethod + + +class CommunicationMessage(ABC): + def __init__(self, is_wireless_message: bool) -> None: + self._is_wireless_message = is_wireless_message + + def is_wireless_message(self) -> bool: + return self._is_wireless_message + + @abstractmethod + def get_byte_size(self) -> int: + raise NotImplementedError + + @abstractmethod + def to_bytes(self) -> bytes: + raise NotImplementedError + + # TODO: Implement the toBitOutputStream and getCheckKey methods From aed647a1736cb97f723e5a617ff5dcc2a3917fbc Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 3 Sep 2024 18:41:31 +0900 Subject: [PATCH 020/249] feat: Provisional MessageManager and PrecomputeData classes --- adf_core_python/core/agent/communication/message_manager.py | 4 ++++ adf_core_python/core/agent/precompute/precompute_data.py | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 adf_core_python/core/agent/communication/message_manager.py create mode 100644 adf_core_python/core/agent/precompute/precompute_data.py diff --git a/adf_core_python/core/agent/communication/message_manager.py b/adf_core_python/core/agent/communication/message_manager.py new file mode 100644 index 0000000..7883136 --- /dev/null +++ b/adf_core_python/core/agent/communication/message_manager.py @@ -0,0 +1,4 @@ +# TODO: Implement MessageManager class +class MessageManager: + def __init__(self) -> None: + pass diff --git a/adf_core_python/core/agent/precompute/precompute_data.py b/adf_core_python/core/agent/precompute/precompute_data.py new file mode 100644 index 0000000..ba5840f --- /dev/null +++ b/adf_core_python/core/agent/precompute/precompute_data.py @@ -0,0 +1,4 @@ +# TODO: Implement the PrecomputeData class +class PrecomputeData: + def __init__(self, file_path: str) -> None: + self._file_path = file_path From b2e571ed70d44413aae891e6e1ab4904a4798b39 Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 3 Sep 2024 18:42:19 +0900 Subject: [PATCH 021/249] feat: Add ModuleManager, ExtAction, and AbstractModule classes --- adf_core_python/core/agent/module/__init__.py | 0 .../core/agent/module/module_manager.py | 98 +++++++++++++++++++ .../core/component/extaction/ext_action.py | 89 +++++++++++++++++ .../core/component/module/abstract_module.py | 94 ++++++++++++++++++ 4 files changed, 281 insertions(+) create mode 100644 adf_core_python/core/agent/module/__init__.py create mode 100644 adf_core_python/core/agent/module/module_manager.py create mode 100644 adf_core_python/core/component/extaction/ext_action.py create mode 100644 adf_core_python/core/component/module/abstract_module.py diff --git a/adf_core_python/core/agent/module/__init__.py b/adf_core_python/core/agent/module/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/agent/module/module_manager.py b/adf_core_python/core/agent/module/module_manager.py new file mode 100644 index 0000000..e98c911 --- /dev/null +++ b/adf_core_python/core/agent/module/module_manager.py @@ -0,0 +1,98 @@ +from __future__ import annotations + +import importlib +from typing import TYPE_CHECKING, Any, Dict, Type, TypeVar + +from adf_core_python.core.component.module.abstract_module import AbstractModule + +if TYPE_CHECKING: + from adf_core_python.core.agent.config.module_config import ModuleConfig + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.component.extaction.ext_action import ExtAction + +T = TypeVar("T") + + +class ModuleManager: + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_config: ModuleConfig, + develop_data: DevelopData, + ) -> None: + self._agent_info = agent_info + self._world_info = world_info + self._scenario_info = scenario_info + self._module_config = module_config + self._develop_data = develop_data + self._modules: Dict[str, AbstractModule] = {} + self._actions: Dict[str, ExtAction] = {} + + self._executors: Dict[str, Any] = {} + self._pickers: Dict[str, Any] = {} + self._channel_subscribers: Dict[str, Any] = {} + self._message_coordinators: Dict[str, Any] = {} + + def get_module(self, module_name: str, default_module_name: str) -> AbstractModule: + class_name = self._module_config.get_value_or_default( + module_name, default_module_name + ) + + try: + module_class: Type[AbstractModule] = self._load_module(class_name) + except (ImportError, AttributeError) as e: + raise RuntimeError(f"Failed to load module {class_name}") from e + + instance = self._modules.get(module_name) + if instance is not None: + return instance + + if issubclass(module_class, AbstractModule): + instance = module_class( + self._agent_info, + self._world_info, + self._scenario_info, + self, + self._develop_data, + ) + self._modules[module_name] = instance + return instance + + raise RuntimeError(f"Module {class_name} is not a subclass of AbstractModule") + + def get_ext_action(self, action_name: str, default_action_name: str) -> ExtAction: + class_name = self._module_config.get_value_or_default( + action_name, default_action_name + ) + + try: + action_class: Type[ExtAction] = self._load_module(class_name) + except (ImportError, AttributeError) as e: + raise RuntimeError(f"Failed to load action {class_name}") from e + + instance = self._actions.get(action_name) + if instance is not None: + return instance + + if issubclass(action_class, ExtAction): + instance = action_class( + self._agent_info, + self._world_info, + self._scenario_info, + self, + self._develop_data, + ) + self._actions[action_name] = instance + return instance + + raise RuntimeError(f"Action {class_name} is not a subclass of ExtAction") + + def _load_module(self, class_name: str) -> Type[T]: + module_name, module_class_name = class_name.rsplit(".", 1) + module = importlib.import_module(module_name) + return getattr(module, module_class_name) diff --git a/adf_core_python/core/component/extaction/ext_action.py b/adf_core_python/core/component/extaction/ext_action.py new file mode 100644 index 0000000..e474a8f --- /dev/null +++ b/adf_core_python/core/component/extaction/ext_action.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Optional + +if TYPE_CHECKING: + from rcrs_core.worldmodel.entityID import EntityID + + from adf_core_python.core.agent.action.action import Action + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + + +class ExtAction(ABC): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ): + self.world_info = world_info + self.agent_info = agent_info + self.scenario_info = scenario_info + self.module_manager = module_manager + self.develop_data = develop_data + self.result: Optional[Action] = None + self.count_precompute: int = 0 + self.count_resume: int = 0 + self.count_prepare: int = 0 + self.count_update_info: int = 0 + self.count_update_info_current_time: int = 0 + + @abstractmethod + def set_target_entity_id(self, target_entity_id: EntityID) -> ExtAction: + raise NotImplementedError + + @abstractmethod + def calc(self) -> ExtAction: + raise NotImplementedError + + def get_action(self) -> Optional[Action]: + return self.result + + def precompute(self, precompute_data: PrecomputeData) -> ExtAction: + self.count_precompute += 1 + return self + + def resume(self, precompute_data: PrecomputeData) -> ExtAction: + self.count_resume += 1 + return self + + def prepare(self) -> ExtAction: + self.count_prepare += 1 + return self + + def update_info(self, message_manager: MessageManager) -> ExtAction: + self.count_update_info += 1 + return self + + def get_count_precompute(self) -> int: + return self.count_precompute + + def get_count_resume(self) -> int: + return self.count_resume + + def get_count_prepare(self) -> int: + return self.count_prepare + + def get_count_update_info(self) -> int: + return self.count_update_info + + def reset_count_precompute(self) -> None: + self.count_precompute = 0 + + def reset_count_resume(self) -> None: + self.count_resume = 0 + + def reset_count_prepare(self) -> None: + self.count_prepare = 0 + + def reset_count_update_info(self) -> None: + self.count_update_info = 0 diff --git a/adf_core_python/core/component/module/abstract_module.py b/adf_core_python/core/component/module/abstract_module.py new file mode 100644 index 0000000..b04f271 --- /dev/null +++ b/adf_core_python/core/component/module/abstract_module.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, List + +if TYPE_CHECKING: + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + + +class AbstractModule(ABC): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + self._agent_info = agent_info + self._world_info = world_info + self._scenario_info = scenario_info + self._module_manager = module_manager + self._develop_data = develop_data + self._count_precompute: int = 0 + self._count_resume: int = 0 + self._count_prepare: int = 0 + self._count_update_info: int = 0 + self._count_update_info_current_time: int = 0 + + self._sub_modules: List[AbstractModule] = [] + + def register_sub_module(self, sub_module: AbstractModule) -> None: + self._sub_modules.append(sub_module) + + def unregister_sub_module(self, sub_module: AbstractModule) -> None: + self._sub_modules.remove(sub_module) + + def precompute(self, precompute_data: PrecomputeData) -> AbstractModule: + self._count_precompute += 1 + for sub_module in self._sub_modules: + sub_module.precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> AbstractModule: + self._count_resume += 1 + for sub_module in self._sub_modules: + sub_module.resume(precompute_data) + return self + + def prepare(self) -> AbstractModule: + self._count_prepare += 1 + for sub_module in self._sub_modules: + sub_module.prepare() + return self + + def update_info(self, message_manager: MessageManager) -> AbstractModule: + self._count_update_info += 1 + for sub_module in self._sub_modules: + sub_module.update_info(message_manager) + return self + + @abstractmethod + def calculate(self) -> AbstractModule: + raise NotImplementedError + + def get_count_precompute(self) -> int: + return self._count_precompute + + def get_count_resume(self) -> int: + return self._count_resume + + def get_count_prepare(self) -> int: + return self._count_prepare + + def get_count_update_info(self) -> int: + return self._count_update_info + + def reset_count_precompute(self) -> None: + self._count_precompute = 0 + + def reset_count_resume(self) -> None: + self._count_resume = 0 + + def reset_count_prepare(self) -> None: + self._count_prepare = 0 + + def reset_count_update_info(self) -> None: + self._count_update_info = 0 From 92ef7a76748e24c8421b3746c2dedf9ba4c7f0de Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 3 Sep 2024 18:42:27 +0900 Subject: [PATCH 022/249] feat: Add AStarPathPlanning module for path planning --- .../module/algorithm/path_planning.py | 61 +++++++++++++++++++ .../implement/module/astar_path_planning.py | 23 +++++++ 2 files changed, 84 insertions(+) create mode 100644 adf_core_python/core/component/module/algorithm/path_planning.py create mode 100644 adf_core_python/implement/module/astar_path_planning.py diff --git a/adf_core_python/core/component/module/algorithm/path_planning.py b/adf_core_python/core/component/module/algorithm/path_planning.py new file mode 100644 index 0000000..fcd0b8a --- /dev/null +++ b/adf_core_python/core/component/module/algorithm/path_planning.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING, List + +from adf_core_python.core.component.module.abstract_module import AbstractModule + +if TYPE_CHECKING: + from rcrs_core.worldmodel.entityID import EntityID + + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + + +class PathPlanning(AbstractModule): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + @abstractmethod + def get_path( + self, from_entity_id: EntityID, to_entity_id: EntityID + ) -> List[EntityID]: + pass + + @abstractmethod + def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: + pass + + def precompute(self, precompute_data: PrecomputeData) -> PathPlanning: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> PathPlanning: + super().resume(precompute_data) + return self + + def prepare(self) -> PathPlanning: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> PathPlanning: + super().update_info(message_manager) + return self + + @abstractmethod + def calculate(self) -> PathPlanning: + pass diff --git a/adf_core_python/implement/module/astar_path_planning.py b/adf_core_python/implement/module/astar_path_planning.py new file mode 100644 index 0000000..025ddd6 --- /dev/null +++ b/adf_core_python/implement/module/astar_path_planning.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List + +from adf_core_python.core.component.module.algorithm.path_planning import ( + PathPlanning, +) + +if TYPE_CHECKING: + from rcrs_core.worldmodel.entityID import EntityID + + +class AStarPathPlanning(PathPlanning): + def get_path( + self, from_entity_id: EntityID, to_entity_id: EntityID + ) -> List[EntityID]: + return [] + + def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: + return 0.0 + + def calculate(self) -> AStarPathPlanning: + return self From 258e7a4fb47901cf4092a66b9bf1b4129a03b768 Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 3 Sep 2024 18:42:52 +0900 Subject: [PATCH 023/249] feat: module_manager test --- tests/core/agent/module/module.yaml | 123 ++++++++++++++++++ .../core/agent/module/test_module_manager.py | 29 +++++ 2 files changed, 152 insertions(+) create mode 100644 tests/core/agent/module/module.yaml create mode 100644 tests/core/agent/module/test_module_manager.py diff --git a/tests/core/agent/module/module.yaml b/tests/core/agent/module/module.yaml new file mode 100644 index 0000000..6e27f99 --- /dev/null +++ b/tests/core/agent/module/module.yaml @@ -0,0 +1,123 @@ +## DefaultTacticsAmbulanceTeam +DefaultTacticsAmbulanceTeam: + HumanDetector: sample_team.module.complex.SampleHumanDetector + Search: sample_team.module.complex.SampleSearch + ExtActionTransport: adf.impl.extaction.DefaultExtActionTransport + ExtActionMove: adf.impl.extaction.DefaultExtActionMove + CommandExecutorAmbulance: adf.impl.centralized.DefaultCommandExecutorAmbulance + CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScout + +## DefaultTacticsFireBrigade +DefaultTacticsFireBrigade: + HumanDetector: sample_team.module.complex.SampleHumanDetector + Search: sample_team.module.complex.SampleSearch + ExtActionFireRescue: adf.impl.extaction.DefaultExtActionFireRescue + ExtActionMove: adf.impl.extaction.DefaultExtActionMove + CommandExecutorFire: adf.impl.centralized.DefaultCommandExecutorFire + CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScout + +## DefaultTacticsPoliceForce +DefaultTacticsPoliceForce: + RoadDetector: sample_team.module.complex.SampleRoadDetector + Search: sample_team.module.complex.SampleSearch + ExtActionClear: adf.impl.extaction.DefaultExtActionClear + ExtActionMove: adf.impl.extaction.DefaultExtActionMove + CommandExecutorPolice: adf.impl.centralized.DefaultCommandExecutorPolice + CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScoutPolice + +## DefaultTacticsAmbulanceCentre +DefaultTacticsAmbulanceCentre: + TargetAllocator: sample_team.module.complex.SampleAmbulanceTargetAllocator + CommandPicker: adf.impl.centralized.DefaultCommandPickerAmbulance + +## DefaultTacticsFireStation +DefaultTacticsFireStation: + TargetAllocator: sample_team.module.complex.SampleFireTargetAllocator + CommandPicker: adf.impl.centralized.DefaultCommandPickerFire + +## DefaultTacticsPoliceOffice +DefaultTacticsPoliceOffice: + TargetAllocator: sample_team.module.complex.SamplePoliceTargetAllocator + CommandPicker: adf.impl.centralized.DefaultCommandPickerPolice + +## SampleSearch +SampleSearch: + PathPlanning: + Ambulance: adf.impl.module.algorithm.DijkstraPathPlanning + Fire: adf.impl.module.algorithm.DijkstraPathPlanning + Police: adf.impl.module.algorithm.DijkstraPathPlanning + Clustering: + Ambulance: adf.impl.module.algorithm.KMeansClustering + Fire: adf.impl.module.algorithm.KMeansClustering + Police: adf.impl.module.algorithm.KMeansClustering + +## SampleBuildDetector +SampleBuildingDetector: + Clustering: adf.impl.module.algorithm.KMeansClustering + +## SampleRoadDetector +SampleRoadDetector: + Clustering: adf.impl.module.algorithm.KMeansClustering + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## SampleHumanDetector +SampleHumanDetector: + Clustering: adf.impl.module.algorithm.KMeansClustering + +## DefaultExtActionClear +DefaultExtActionClear: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultExtActionFireFighting +DefaultExtActionFireFighting: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultExtActionFireRescue +DefaultExtActionFireRescue: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultExtActionMove +DefaultExtActionMove: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultExtActionTransport +DefaultExtActionTransport: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultCommandExecutorAmbulance +DefaultCommandExecutorAmbulance: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + ExtActionTransport: adf.impl.extaction.DefaultExtActionTransport + ExtActionMove: adf.impl.extaction.DefaultExtActionMove + +## DefaultCommandExecutorFire +DefaultCommandExecutorFire: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + EtxActionFireRescue: adf.impl.extaction.DefaultExtActionFireRescue + EtxActionFireFighting: adf.impl.extaction.DefaultExtActionFireFighting + ExtActionMove: adf.impl.extaction.DefaultExtActionMove + +## DefaultCommandExecutorPolice +DefaultCommandExecutorPolice: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + ExtActionClear: adf.impl.extaction.DefaultExtActionClear + ExtActionMove: adf.impl.extaction.DefaultExtActionMove + +## DefaultCommandExecutorScout +DefaultCommandExecutorScout: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultCommandExecutorScoutPolice +DefaultCommandExecutorScoutPolice: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + ExtActionClear: adf.impl.extaction.DefaultExtActionClear + +## MessageManager +MessageManager: + PlatoonChannelSubscriber: adf.impl.module.comm.DefaultChannelSubscriber + CenterChannelSubscriber: adf.impl.module.comm.DefaultChannelSubscriber + PlatoonMessageCoordinator: adf.impl.module.comm.DefaultMessageCoordinator + CenterMessageCoordinator: adf.impl.module.comm.DefaultMessageCoordinator + +## VisualDebug +VisualDebug: true diff --git a/tests/core/agent/module/test_module_manager.py b/tests/core/agent/module/test_module_manager.py new file mode 100644 index 0000000..458791e --- /dev/null +++ b/tests/core/agent/module/test_module_manager.py @@ -0,0 +1,29 @@ +import os + +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.module.abstract_module import AbstractModule + + +class TestModuleManager: + def test_can_get_module(self) -> None: + script_dir = os.path.dirname(os.path.abspath(__file__)) + config_file_path = os.path.join(script_dir, "module.yaml") + config = ModuleConfig(config_file_path) + config.set_value( + "test_module", + "adf_core_python.implement.module.astar_path_planning.AStarPathPlanning", + ) + module_manager = self.create_module_manager(config) + module = module_manager.get_module("test_module", "test_module") + assert isinstance(module, AbstractModule) + assert module.__class__.__name__ == "AStarPathPlanning" + + def create_module_manager(self, config: ModuleConfig) -> ModuleManager: + return ModuleManager( + None, # type: ignore + None, # type: ignore + None, # type: ignore + config, + None, # type: ignore + ) From 72f42e4055485c3a68a3145673867dc31bf941f3 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 9 Sep 2024 21:01:46 +0900 Subject: [PATCH 024/249] refactor: replace deprecated type alias --- adf_core_python/core/agent/config/module_config.py | 14 +++++++------- adf_core_python/core/agent/develop/develop_data.py | 8 ++++---- adf_core_python/core/agent/info/agent_info.py | 14 +++++++------- adf_core_python/core/agent/info/world_info.py | 4 ++-- .../core/agent/module/module_manager.py | 14 +++++++------- .../core/component/module/abstract_module.py | 4 ++-- .../component/module/algorithm/path_planning.py | 4 ++-- .../core/component/tactics/tactics_agent.py | 6 +++--- .../core/component/tactics/tactics_center.py | 6 +++--- .../implement/module/astar_path_planning.py | 4 ++-- 10 files changed, 39 insertions(+), 39 deletions(-) diff --git a/adf_core_python/core/agent/config/module_config.py b/adf_core_python/core/agent/config/module_config.py index 258c6f0..a97ef30 100644 --- a/adf_core_python/core/agent/config/module_config.py +++ b/adf_core_python/core/agent/config/module_config.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Final +from typing import Any, Final from rcrs_core.config.config import Config from yaml import safe_load @@ -38,7 +38,7 @@ def __init__(self, config_file_name: str = DEFAULT_CONFIG_FILE_NAME): for key, value in flatten_data.items(): print(f"{key}: {self.get_value(key)}") - def _read_from_yaml(self, file_name: str) -> Dict[str, Any]: + def _read_from_yaml(self, file_name: str) -> dict[str, Any]: """ Read configuration from yaml file @@ -49,7 +49,7 @@ def _read_from_yaml(self, file_name: str) -> Dict[str, Any]: Returns ------- - Dict[str, Any] + dict[str, Any] Configuration data """ try: @@ -63,14 +63,14 @@ def _read_from_yaml(self, file_name: str) -> Dict[str, Any]: return data def _flatten( - self, data: Dict[str, Any], parent_key: str = "", sep: str = "." - ) -> Dict[str, Any]: + self, data: dict[str, Any], parent_key: str = "", sep: str = "." + ) -> dict[str, Any]: """ Flatten nested dictionary to a single level dictionary Parameters ---------- - data : Dict[str, Any] + data : dict[str, Any] Nested dictionary parent_key : str, optional Parent key, by default "" @@ -79,7 +79,7 @@ def _flatten( Returns ------- - Dict[str, Any] + dict[str, Any] Flattened dictionary """ flatten_data = {} diff --git a/adf_core_python/core/agent/develop/develop_data.py b/adf_core_python/core/agent/develop/develop_data.py index f66bc3b..7d7c551 100644 --- a/adf_core_python/core/agent/develop/develop_data.py +++ b/adf_core_python/core/agent/develop/develop_data.py @@ -1,5 +1,5 @@ import json -from typing import Any, Dict, Final +from typing import Any, Final class DevelopData: @@ -11,12 +11,12 @@ def __init__( """ Constructor """ - self._develop_data: Dict[str, Any] = self._set_data_from_json(file_name) + self._develop_data: dict[str, Any] = self._set_data_from_json(file_name) self._is_develop_mode: bool = is_develop_mode self._set_data_from_json(file_name) - def _set_data_from_json(self, file_name: str) -> Dict[str, Any]: + def _set_data_from_json(self, file_name: str) -> dict[str, Any]: """ Set data from json @@ -27,7 +27,7 @@ def _set_data_from_json(self, file_name: str) -> Dict[str, Any]: Returns ------- - Dict[str, Any] + dict[str, Any] Develop data """ try: diff --git a/adf_core_python/core/agent/info/agent_info.py b/adf_core_python/core/agent/info/agent_info.py index 8d64006..d65070c 100644 --- a/adf_core_python/core/agent/info/agent_info.py +++ b/adf_core_python/core/agent/info/agent_info.py @@ -1,5 +1,5 @@ from time import time -from typing import Any, Dict, List +from typing import Any from rcrs_core.agents.agent import Agent from rcrs_core.entities.human import Human @@ -14,8 +14,8 @@ def __init__(self, agent: Agent, world_model: WorldModel): self._agent: Agent = agent self._world_model: WorldModel = world_model self._time: int = 0 - self._action_history: Dict[int, Action] = {} - self._heard_commands: List[Any] = [] + self._action_history: dict[int, Action] = {} + self._heard_commands: list[Any] = [] self._change_set: ChangeSet = ChangeSet() self._start_think_time: float = 0.0 @@ -41,24 +41,24 @@ def get_time(self) -> int: """ return self._time - def set_heard_commands(self, heard_commands: List[Any]) -> None: + def set_heard_commands(self, heard_commands: list[Any]) -> None: """ Set the heard commands Parameters ---------- - heard_commands : List[Any] + heard_commands : list[Any] Heard commands """ self._heard_commands = heard_commands - def get_heard_commands(self) -> List[Any]: + def get_heard_commands(self) -> list[Any]: """ Get the heard commands Returns ------- - List[Any] + list[Any] Heard commands """ return self._heard_commands diff --git a/adf_core_python/core/agent/info/world_info.py b/adf_core_python/core/agent/info/world_info.py index 87c5a62..c765a82 100644 --- a/adf_core_python/core/agent/info/world_info.py +++ b/adf_core_python/core/agent/info/world_info.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any from rcrs_core.worldmodel.entityID import EntityID from rcrs_core.worldmodel.worldmodel import WorldModel @@ -9,7 +9,7 @@ def __init__(self, world_model: WorldModel): self._world_model: WorldModel = world_model self._time: int = 0 self._is_run_rollback: bool = False - self._rollback: Dict[EntityID, Dict[int, Dict[int, Any]]] = {} + self._rollback: dict[EntityID, dict[int, dict[int, Any]]] = {} # TODO: Implement the worldmodel access methods def get_world_model(self) -> WorldModel: diff --git a/adf_core_python/core/agent/module/module_manager.py b/adf_core_python/core/agent/module/module_manager.py index e98c911..dfccd67 100644 --- a/adf_core_python/core/agent/module/module_manager.py +++ b/adf_core_python/core/agent/module/module_manager.py @@ -1,7 +1,7 @@ from __future__ import annotations import importlib -from typing import TYPE_CHECKING, Any, Dict, Type, TypeVar +from typing import TYPE_CHECKING, Any, Type, TypeVar from adf_core_python.core.component.module.abstract_module import AbstractModule @@ -30,13 +30,13 @@ def __init__( self._scenario_info = scenario_info self._module_config = module_config self._develop_data = develop_data - self._modules: Dict[str, AbstractModule] = {} - self._actions: Dict[str, ExtAction] = {} + self._modules: dict[str, AbstractModule] = {} + self._actions: dict[str, ExtAction] = {} - self._executors: Dict[str, Any] = {} - self._pickers: Dict[str, Any] = {} - self._channel_subscribers: Dict[str, Any] = {} - self._message_coordinators: Dict[str, Any] = {} + self._executors: dict[str, Any] = {} + self._pickers: dict[str, Any] = {} + self._channel_subscribers: dict[str, Any] = {} + self._message_coordinators: dict[str, Any] = {} def get_module(self, module_name: str, default_module_name: str) -> AbstractModule: class_name = self._module_config.get_value_or_default( diff --git a/adf_core_python/core/component/module/abstract_module.py b/adf_core_python/core/component/module/abstract_module.py index b04f271..7660519 100644 --- a/adf_core_python/core/component/module/abstract_module.py +++ b/adf_core_python/core/component/module/abstract_module.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING if TYPE_CHECKING: from adf_core_python.core.agent.communication.message_manager import MessageManager @@ -33,7 +33,7 @@ def __init__( self._count_update_info: int = 0 self._count_update_info_current_time: int = 0 - self._sub_modules: List[AbstractModule] = [] + self._sub_modules: list[AbstractModule] = [] def register_sub_module(self, sub_module: AbstractModule) -> None: self._sub_modules.append(sub_module) diff --git a/adf_core_python/core/component/module/algorithm/path_planning.py b/adf_core_python/core/component/module/algorithm/path_planning.py index fcd0b8a..b741862 100644 --- a/adf_core_python/core/component/module/algorithm/path_planning.py +++ b/adf_core_python/core/component/module/algorithm/path_planning.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import abstractmethod -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING from adf_core_python.core.component.module.abstract_module import AbstractModule @@ -33,7 +33,7 @@ def __init__( @abstractmethod def get_path( self, from_entity_id: EntityID, to_entity_id: EntityID - ) -> List[EntityID]: + ) -> list[EntityID]: pass @abstractmethod diff --git a/adf_core_python/core/component/tactics/tactics_agent.py b/adf_core_python/core/component/tactics/tactics_agent.py index d1c74bf..5a92fa7 100644 --- a/adf_core_python/core/component/tactics/tactics_agent.py +++ b/adf_core_python/core/component/tactics/tactics_agent.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, List, Optional +from typing import TYPE_CHECKING, Any, Optional if TYPE_CHECKING: from adf_core_python.core.agent.communication.message_manager import MessageManager @@ -18,8 +18,8 @@ class TacticsAgent(ABC): def __init__(self, parent: Optional[TacticsAgent] = None) -> None: self._parent = parent - self._modules: List[AbstractModule] = [] - self._actions: List[ExtAction] = [] + self._modules: list[AbstractModule] = [] + self._actions: list[ExtAction] = [] self._command_executor: Any = None @abstractmethod diff --git a/adf_core_python/core/component/tactics/tactics_center.py b/adf_core_python/core/component/tactics/tactics_center.py index cd6de03..ea95edf 100644 --- a/adf_core_python/core/component/tactics/tactics_center.py +++ b/adf_core_python/core/component/tactics/tactics_center.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, List, Optional +from typing import TYPE_CHECKING, Any, Optional if TYPE_CHECKING: from adf_core_python.core.agent.communication.message_manager import MessageManager @@ -17,8 +17,8 @@ class TacticsCenter(ABC): def __init__(self, parent: Optional[TacticsCenter] = None) -> None: self._parent = parent - self._modules: List[AbstractModule] = [] - self._command_pickers: List[Any] = [] + self._modules: list[AbstractModule] = [] + self._command_pickers: list[Any] = [] @abstractmethod def initialize( diff --git a/adf_core_python/implement/module/astar_path_planning.py b/adf_core_python/implement/module/astar_path_planning.py index 025ddd6..24c7afb 100644 --- a/adf_core_python/implement/module/astar_path_planning.py +++ b/adf_core_python/implement/module/astar_path_planning.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING from adf_core_python.core.component.module.algorithm.path_planning import ( PathPlanning, @@ -13,7 +13,7 @@ class AStarPathPlanning(PathPlanning): def get_path( self, from_entity_id: EntityID, to_entity_id: EntityID - ) -> List[EntityID]: + ) -> list[EntityID]: return [] def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: From 9b6c23fa8ce41da4249a57fce95dfaae0277b189 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 9 Sep 2024 22:13:55 +0900 Subject: [PATCH 025/249] refactor: Replace deprecated type alias in ModuleManager --- adf_core_python/core/agent/module/module_manager.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/adf_core_python/core/agent/module/module_manager.py b/adf_core_python/core/agent/module/module_manager.py index dfccd67..b6f325b 100644 --- a/adf_core_python/core/agent/module/module_manager.py +++ b/adf_core_python/core/agent/module/module_manager.py @@ -1,7 +1,7 @@ from __future__ import annotations import importlib -from typing import TYPE_CHECKING, Any, Type, TypeVar +from typing import TYPE_CHECKING, Any from adf_core_python.core.component.module.abstract_module import AbstractModule @@ -13,8 +13,6 @@ from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.component.extaction.ext_action import ExtAction -T = TypeVar("T") - class ModuleManager: def __init__( @@ -44,7 +42,7 @@ def get_module(self, module_name: str, default_module_name: str) -> AbstractModu ) try: - module_class: Type[AbstractModule] = self._load_module(class_name) + module_class: type = self._load_module(class_name) except (ImportError, AttributeError) as e: raise RuntimeError(f"Failed to load module {class_name}") from e @@ -71,7 +69,7 @@ def get_ext_action(self, action_name: str, default_action_name: str) -> ExtActio ) try: - action_class: Type[ExtAction] = self._load_module(class_name) + action_class: type = self._load_module(class_name) except (ImportError, AttributeError) as e: raise RuntimeError(f"Failed to load action {class_name}") from e @@ -92,7 +90,7 @@ def get_ext_action(self, action_name: str, default_action_name: str) -> ExtActio raise RuntimeError(f"Action {class_name} is not a subclass of ExtAction") - def _load_module(self, class_name: str) -> Type[T]: + def _load_module(self, class_name: str) -> type: module_name, module_class_name = class_name.rsplit(".", 1) module = importlib.import_module(module_name) return getattr(module, module_class_name) From beb36103e537c848fea7c0feaae7e9a3a6d96b7b Mon Sep 17 00:00:00 2001 From: shima004 Date: Sun, 15 Sep 2024 01:33:02 +0900 Subject: [PATCH 026/249] feat: Introduce new action classes for ambulance, fire, and police operations --- adf_core_python/core/agent/action/action.py | 11 +++++-- .../agent/action/ambulance/action_load.py | 22 +++++++++++++ .../agent/action/ambulance/action_rescue.py | 22 +++++++++++++ .../agent/action/ambulance/action_unload.py | 19 +++++++++++ .../core/agent/action/common/action_move.py | 33 +++++++++++++++++++ .../core/agent/action/common/action_rest.py | 19 +++++++++++ .../agent/action/fire/action_extinguish.py | 26 +++++++++++++++ .../core/agent/action/fire/action_refill.py | 19 +++++++++++ .../core/agent/action/fire/action_rescue.py | 22 +++++++++++++ .../core/agent/action/police/action_clear.py | 21 ++++++++++++ .../agent/action/police/action_clear_area.py | 21 ++++++++++++ 11 files changed, 232 insertions(+), 3 deletions(-) create mode 100644 adf_core_python/core/agent/action/ambulance/action_load.py create mode 100644 adf_core_python/core/agent/action/ambulance/action_rescue.py create mode 100644 adf_core_python/core/agent/action/ambulance/action_unload.py create mode 100644 adf_core_python/core/agent/action/common/action_move.py create mode 100644 adf_core_python/core/agent/action/common/action_rest.py create mode 100644 adf_core_python/core/agent/action/fire/action_extinguish.py create mode 100644 adf_core_python/core/agent/action/fire/action_refill.py create mode 100644 adf_core_python/core/agent/action/fire/action_rescue.py create mode 100644 adf_core_python/core/agent/action/police/action_clear.py create mode 100644 adf_core_python/core/agent/action/police/action_clear_area.py diff --git a/adf_core_python/core/agent/action/action.py b/adf_core_python/core/agent/action/action.py index 84cef3a..eb4a04c 100644 --- a/adf_core_python/core/agent/action/action.py +++ b/adf_core_python/core/agent/action/action.py @@ -1,10 +1,15 @@ from abc import ABC, abstractmethod +from typing import TYPE_CHECKING -from rcrs_core.messages.message import Message -from rcrs_core.worldmodel.entityID import EntityID +if TYPE_CHECKING: + from rcrs_core.commands.Command import Command + from rcrs_core.worldmodel.entityID import EntityID class Action(ABC): + def __init__(self) -> None: + pass + @abstractmethod - def get_command(self, agent_id: EntityID, time: int) -> Message: + def get_command(self, agent_id: EntityID, time: int) -> Command: raise NotImplementedError diff --git a/adf_core_python/core/agent/action/ambulance/action_load.py b/adf_core_python/core/agent/action/ambulance/action_load.py new file mode 100644 index 0000000..c7ee299 --- /dev/null +++ b/adf_core_python/core/agent/action/ambulance/action_load.py @@ -0,0 +1,22 @@ +from typing import TYPE_CHECKING + +from rcrs_core.commands.AKLoad import AKLoad +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.action import Action + +if TYPE_CHECKING: + from rcrs_core.commands.Command import Command + from rcrs_core.worldmodel.entityID import EntityID + + +class ActionLoad(Action): + def __init__(self, target_id: EntityID) -> None: + self.target_id = target_id + + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKLoad(agent_id, time, self.target_id) + + def __str__(self) -> str: + return f"ActionLoad(target_id={self.target_id})" diff --git a/adf_core_python/core/agent/action/ambulance/action_rescue.py b/adf_core_python/core/agent/action/ambulance/action_rescue.py new file mode 100644 index 0000000..e4bfb1d --- /dev/null +++ b/adf_core_python/core/agent/action/ambulance/action_rescue.py @@ -0,0 +1,22 @@ +from typing import TYPE_CHECKING + +from rcrs_core.commands.AKRescue import AKRescue +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.action import Action + +if TYPE_CHECKING: + from rcrs_core.commands.Command import Command + from rcrs_core.worldmodel.entityID import EntityID + + +class ActionRescue(Action): + def __init__(self, target_id: EntityID) -> None: + self.target_id = target_id + + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKRescue(agent_id, time, self.target_id) + + def __str__(self) -> str: + return f"ActionRescue(target_id={self.target_id})" diff --git a/adf_core_python/core/agent/action/ambulance/action_unload.py b/adf_core_python/core/agent/action/ambulance/action_unload.py new file mode 100644 index 0000000..afe3f1a --- /dev/null +++ b/adf_core_python/core/agent/action/ambulance/action_unload.py @@ -0,0 +1,19 @@ +from typing import TYPE_CHECKING + +from rcrs_core.commands.AKUnload import AKUnload +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.action import Action + +if TYPE_CHECKING: + from rcrs_core.commands.Command import Command + from rcrs_core.worldmodel.entityID import EntityID + + +class ActionUnload(Action): + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKUnload(agent_id, time) + + def __str__(self) -> str: + return "ActionUnload()" diff --git a/adf_core_python/core/agent/action/common/action_move.py b/adf_core_python/core/agent/action/common/action_move.py new file mode 100644 index 0000000..06dc150 --- /dev/null +++ b/adf_core_python/core/agent/action/common/action_move.py @@ -0,0 +1,33 @@ +from typing import TYPE_CHECKING, Optional + +from rcrs_core.commands.AKMove import AKMove +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.action import Action + +if TYPE_CHECKING: + from rcrs_core.commands.Command import Command + from rcrs_core.worldmodel.entityID import EntityID + + +class ActionMove(Action): + def __init__( + self, + path: list[EntityID], + destinationX: Optional[int] = None, + destinationY: Optional[int] = None, + ) -> None: + self.path = path + self.destinationX = destinationX + self.destinationY = destinationY + + def get_command(self, agent_id: EntityID, time: int) -> Command: + path: list[int] = [p.get_value() for p in self.path] + if self.destinationX is not None and self.destinationY is not None: + return AKMove(agent_id, time, path, self.destinationX, self.destinationY) + else: + return AKMove(agent_id, time, path) + + def __str__(self) -> str: + return f"ActionMove(path={self.path}, destinationX={self.destinationX}, destinationY={self.destinationY})" diff --git a/adf_core_python/core/agent/action/common/action_rest.py b/adf_core_python/core/agent/action/common/action_rest.py new file mode 100644 index 0000000..ab86c82 --- /dev/null +++ b/adf_core_python/core/agent/action/common/action_rest.py @@ -0,0 +1,19 @@ +from typing import TYPE_CHECKING + +from rcrs_core.commands.AKRest import AKRest +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.action import Action + +if TYPE_CHECKING: + from rcrs_core.commands.Command import Command + from rcrs_core.worldmodel.entityID import EntityID + + +class ActionRest(Action): + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKRest(agent_id, time) + + def __str__(self) -> str: + return "ActionRest()" diff --git a/adf_core_python/core/agent/action/fire/action_extinguish.py b/adf_core_python/core/agent/action/fire/action_extinguish.py new file mode 100644 index 0000000..296bef6 --- /dev/null +++ b/adf_core_python/core/agent/action/fire/action_extinguish.py @@ -0,0 +1,26 @@ +from typing import TYPE_CHECKING + +from rcrs_core.commands.AKExtinguish import AKExtinguish +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.action import Action + +if TYPE_CHECKING: + from rcrs_core.commands.Command import Command + from rcrs_core.worldmodel.entityID import EntityID + + +class ActionExtinguish(Action): + def __init__(self, target_id: EntityID, max_power: int) -> None: + self.target_id = target_id + self.max_power = max_power + + def get_command(self, agent_id: EntityID, time: int) -> Command: + # TODO: Implement AKEExtinguish + return AKExtinguish() + + def __str__(self) -> str: + return ( + f"ActionExtinguish(target_id={self.target_id}, max_power={self.max_power})" + ) diff --git a/adf_core_python/core/agent/action/fire/action_refill.py b/adf_core_python/core/agent/action/fire/action_refill.py new file mode 100644 index 0000000..bae2cc8 --- /dev/null +++ b/adf_core_python/core/agent/action/fire/action_refill.py @@ -0,0 +1,19 @@ +from typing import TYPE_CHECKING + +from rcrs_core.commands.AKRest import AKRest +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.action import Action + +if TYPE_CHECKING: + from rcrs_core.commands.Command import Command + from rcrs_core.worldmodel.entityID import EntityID + + +class ActionRefill(Action): + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKRest(agent_id, time) + + def __str__(self) -> str: + return "ActionRefill()" diff --git a/adf_core_python/core/agent/action/fire/action_rescue.py b/adf_core_python/core/agent/action/fire/action_rescue.py new file mode 100644 index 0000000..e4bfb1d --- /dev/null +++ b/adf_core_python/core/agent/action/fire/action_rescue.py @@ -0,0 +1,22 @@ +from typing import TYPE_CHECKING + +from rcrs_core.commands.AKRescue import AKRescue +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.action import Action + +if TYPE_CHECKING: + from rcrs_core.commands.Command import Command + from rcrs_core.worldmodel.entityID import EntityID + + +class ActionRescue(Action): + def __init__(self, target_id: EntityID) -> None: + self.target_id = target_id + + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKRescue(agent_id, time, self.target_id) + + def __str__(self) -> str: + return f"ActionRescue(target_id={self.target_id})" diff --git a/adf_core_python/core/agent/action/police/action_clear.py b/adf_core_python/core/agent/action/police/action_clear.py new file mode 100644 index 0000000..dcee14a --- /dev/null +++ b/adf_core_python/core/agent/action/police/action_clear.py @@ -0,0 +1,21 @@ +from typing import TYPE_CHECKING + +from rcrs_core.commands.AKClear import AKClear + +from adf_core_python.core.agent.action.action import Action + +if TYPE_CHECKING: + from rcrs_core.commands.Command import Command + from rcrs_core.entities.blockade import Blockade + from rcrs_core.worldmodel.entityID import EntityID + + +class ActionClear(Action): + def __init__(self, blockade: Blockade) -> None: + self.blockade = blockade + + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKClear(agent_id, time, self.blockade.get_id()) + + def __str__(self) -> str: + return f"ActionClear(blockade={self.blockade})" diff --git a/adf_core_python/core/agent/action/police/action_clear_area.py b/adf_core_python/core/agent/action/police/action_clear_area.py new file mode 100644 index 0000000..f87726f --- /dev/null +++ b/adf_core_python/core/agent/action/police/action_clear_area.py @@ -0,0 +1,21 @@ +from typing import TYPE_CHECKING + +from rcrs_core.commands.AKClearArea import AKClearArea + +from adf_core_python.core.agent.action.action import Action + +if TYPE_CHECKING: + from rcrs_core.commands.Command import Command + from rcrs_core.worldmodel.entityID import EntityID + + +class ActionClearArea(Action): + def __init__(self, position_x: int, position_y: int) -> None: + self.position_x = position_x + self.position_y = position_y + + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKClearArea(agent_id, time, self.position_x, self.position_y) + + def __str__(self) -> str: + return f"ActionClearArea(position_x={self.position_x}, position_y={self.position_y})" From 90686329bae11f94a05773b3b9bda11809ee807c Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 18 Sep 2024 07:01:18 +0900 Subject: [PATCH 027/249] feat: Add Config class for managing configuration settings from YAML files --- adf_core_python/core/config/config.py | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 adf_core_python/core/config/config.py diff --git a/adf_core_python/core/config/config.py b/adf_core_python/core/config/config.py new file mode 100644 index 0000000..38c414e --- /dev/null +++ b/adf_core_python/core/config/config.py @@ -0,0 +1,37 @@ +from typing import Any + +from yaml import safe_load + + +class Config: + def __init__(self): + self.config: dict[str, Any] = {} + + def set_value(self, key: str, value: Any): + self.config[key] = value + + def get_value(self, key: str, default: Any = None) -> Any: + return self.config.get(key, default) + + def read_from_yaml(self, file_name: str) -> dict[str, Any]: + try: + with open(file_name, mode="r", encoding="utf-8") as file: + data = safe_load(file) + except FileNotFoundError: + raise FileNotFoundError(f"Config file not found: {file_name}") + except Exception as e: + raise Exception(f"Error reading config file: {file_name}, {e}") + + return data + + def flatten( + self, data: dict[str, Any], parent_key: str = "", sep: str = "." + ) -> dict[str, Any]: + flatten_data = {} + for key, value in data.items(): + new_key = f"{parent_key}{sep}{key}" if parent_key else key + if isinstance(value, dict): + flatten_data.update(self.flatten(value, new_key, sep=sep)) + else: + flatten_data[new_key] = value + return flatten_data From 92648e6b4594b1d75ebb40dc4d47905142fbbe3e Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 18 Sep 2024 07:01:40 +0900 Subject: [PATCH 028/249] feat: Implement DefaultTacticsAmbulanceTeam for ambulance team operations --- .../default_tactics_ambulance_center.py | 0 .../tactics/default_tactics_ambulance_team.py | 130 ++++++++++++++++++ .../tactics/default_tactics_fire_brigade.py | 0 .../tactics/default_tactics_fire_station.py | 0 .../tactics/default_tactics_police_force.py | 0 .../tactics/default_tactics_police_office.py | 0 6 files changed, 130 insertions(+) create mode 100644 adf_core_python/implement/tactics/default_tactics_ambulance_center.py create mode 100644 adf_core_python/implement/tactics/default_tactics_ambulance_team.py create mode 100644 adf_core_python/implement/tactics/default_tactics_fire_brigade.py create mode 100644 adf_core_python/implement/tactics/default_tactics_fire_station.py create mode 100644 adf_core_python/implement/tactics/default_tactics_police_force.py create mode 100644 adf_core_python/implement/tactics/default_tactics_police_office.py diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py b/adf_core_python/implement/tactics/default_tactics_ambulance_center.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py new file mode 100644 index 0000000..fb98aa1 --- /dev/null +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -0,0 +1,130 @@ +from typing import cast + +from rcrs_core.entities.ambulanceTeam import AmbulanceTeamEntity + +from adf_core_python.core.agent.action.action import Action +from adf_core_python.core.agent.action.common.action_rest import ActionRest +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.module.complex.human_detector import HumanDetector +from adf_core_python.core.component.module.complex.search import Search +from adf_core_python.core.component.tactics.tactics_ambulance_team import ( + TacticsAmbulanceTeam, +) + + +class DefaultTacticsAmbulanceTeam(TacticsAmbulanceTeam): + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + # world_info.index_class() + match scenario_info.get_mode(): + case Mode.NON_PRECOMPUTE: + self._search: Search = cast( + Search, + module_manager.get_module( + "DefaultTacticsAmbulanceTeam.Search", + "adf.impl.module.complex.DefaultSearch", + ), + ) + self._human_detector: HumanDetector = cast( + HumanDetector, + module_manager.get_module( + "DefaultTacticsAmbulanceTeam.HumanDetector", + "adf.impl.module.complex.DefaultHumanDetector", + ), + ) + self._actionTransport = module_manager.get_ext_action( + "DefaultTacticsAmbulanceTeam.ExtActionMove", + "adf.impl.extaction.DefaultExtActionMove", + ) + self._actionExtMove = module_manager.get_ext_action( + "DefaultTacticsAmbulanceTeam.ExtActionMove", + "adf.impl.extaction.DefaultExtActionMove", + ) + self.register_module(self._search) + self.register_module(self._human_detector) + self.register_action(self._actionTransport) + self.register_action(self._actionExtMove) + + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_precompute(precompute_data) + + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_resume(precompute_data) + + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + self.module_prepare() + + def think( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> Action: + self.module_update_info(message_manager) + + agent: AmbulanceTeamEntity = cast(AmbulanceTeamEntity, agent_info.get_myself()) + entity_id = agent_info.get_entity_id() + + target_entity_id = self._human_detector.calculate().get_target_entity_id() + action = ( + self._actionTransport.set_target_entity_id(target_entity_id) + .calc() + .get_action() + ) + if action is not None: + return action + + target_entity_id = self._search.calculate().get_target_entity_id() + action = ( + self._actionExtMove.set_target_entity_id(target_entity_id) + .calc() + .get_action() + ) + if action is not None: + return action + + return ActionRest() diff --git a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/implement/tactics/default_tactics_fire_station.py b/adf_core_python/implement/tactics/default_tactics_fire_station.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/implement/tactics/default_tactics_police_office.py b/adf_core_python/implement/tactics/default_tactics_police_office.py new file mode 100644 index 0000000..e69de29 From cd47c7bedf537889ce1e82672844b195ed0da9be Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 18 Sep 2024 07:01:49 +0900 Subject: [PATCH 029/249] feat: Add HumanDetector and Search classes for target detection functionality --- .../module/complex/human_detector.py | 38 ++++++++++++ .../core/component/module/complex/search.py | 38 ++++++++++++ .../module/complex/target_detector.py | 59 +++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 adf_core_python/core/component/module/complex/human_detector.py create mode 100644 adf_core_python/core/component/module/complex/search.py create mode 100644 adf_core_python/core/component/module/complex/target_detector.py diff --git a/adf_core_python/core/component/module/complex/human_detector.py b/adf_core_python/core/component/module/complex/human_detector.py new file mode 100644 index 0000000..f69dd51 --- /dev/null +++ b/adf_core_python/core/component/module/complex/human_detector.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +from abc import abstractmethod + +from rcrs_core.entities.human import Human + +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.module.complex.target_detector import TargetDetector + + +class HumanDetector(TargetDetector[Human]): + def __init__( + self, + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + ): + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + def precompute(self, precompute_data: PrecomputeData) -> HumanDetector: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> HumanDetector: + super().resume(precompute_data) + return self + + def prepare(self) -> HumanDetector: + super().prepare() + return self + + @abstractmethod + def calculate(self) -> HumanDetector: + return self diff --git a/adf_core_python/core/component/module/complex/search.py b/adf_core_python/core/component/module/complex/search.py new file mode 100644 index 0000000..9049d8b --- /dev/null +++ b/adf_core_python/core/component/module/complex/search.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +from abc import abstractmethod + +from rcrs_core.entities.area import Area + +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.module.complex.target_detector import TargetDetector + + +class Search(TargetDetector[Area]): + def __init__( + self, + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + ): + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + def precompute(self, precompute_data: PrecomputeData) -> Search: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> Search: + super().resume(precompute_data) + return self + + def prepare(self) -> Search: + super().prepare() + return self + + @abstractmethod + def calculate(self) -> Search: + return self diff --git a/adf_core_python/core/component/module/complex/target_detector.py b/adf_core_python/core/component/module/complex/target_detector.py new file mode 100644 index 0000000..2a405af --- /dev/null +++ b/adf_core_python/core/component/module/complex/target_detector.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING, Generic, TypeVar + +from rcrs_core.entities.entity import Entity + +from adf_core_python.core.component.module.abstract_module import AbstractModule + +if TYPE_CHECKING: + from rcrs_core.worldmodel.entityID import EntityID + + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + +T = TypeVar("T", bound=Entity) + + +class TargetDetector(AbstractModule, Generic[T]): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + @abstractmethod + def get_target_entity_id(self) -> EntityID: + pass + + @abstractmethod + def calculate(self) -> TargetDetector[T]: + pass + + def precompute(self, precompute_data: PrecomputeData) -> TargetDetector[T]: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> TargetDetector[T]: + super().resume(precompute_data) + return self + + def prepare(self) -> TargetDetector[T]: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> TargetDetector[T]: + super().update_info(message_manager) + return self From d697edf797f8c004dbae49f3abbde7ced70fed0c Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 18 Sep 2024 07:01:55 +0900 Subject: [PATCH 030/249] feat: Rename update_info to think and change return type to Action --- adf_core_python/core/component/tactics/tactics_agent.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/adf_core_python/core/component/tactics/tactics_agent.py b/adf_core_python/core/component/tactics/tactics_agent.py index 5a92fa7..2bcf5c5 100644 --- a/adf_core_python/core/component/tactics/tactics_agent.py +++ b/adf_core_python/core/component/tactics/tactics_agent.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any, Optional if TYPE_CHECKING: + from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -74,7 +75,7 @@ def prepare( raise NotImplementedError @abstractmethod - def update_info( + def think( self, agent_info: AgentInfo, world_info: WorldInfo, @@ -83,7 +84,7 @@ def update_info( precompute_data: PrecomputeData, message_manager: MessageManager, develop_data: DevelopData, - ) -> None: + ) -> Action: raise NotImplementedError def get_parent_tactics(self) -> Optional[TacticsAgent]: From 9339b98a46f1569ef8356a42466b0e62df8af3cc Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 18 Sep 2024 07:02:01 +0900 Subject: [PATCH 031/249] fix: Correct typo in ConnectorFireBrigade class name --- adf_core_python/core/launcher/connect/connector_fire_brigade.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/adf_core_python/core/launcher/connect/connector_fire_brigade.py index 8c2784c..fd46fac 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_brigade.py +++ b/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -11,7 +11,7 @@ from adf_core_python.core.launcher.connect.connector import Connector -class ConnectorFIreBrigade(Connector): +class ConnectorFireBrigade(Connector): def __init__(self) -> None: super().__init__() self.logger: Logger = getLogger(__name__) From 48084266ccefde66117419ebbb9c34650f7f8398 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 18 Sep 2024 07:02:12 +0900 Subject: [PATCH 032/249] feat: Enhance AbstractLoader with optional team name and type hints; add DefaultLoader implementation --- .../core/component/abstract_loader.py | 41 ++++++++++++----- adf_core_python/implement/default_loader.py | 46 +++++++++++++++++++ 2 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 adf_core_python/implement/default_loader.py diff --git a/adf_core_python/core/component/abstract_loader.py b/adf_core_python/core/component/abstract_loader.py index 1a8c210..006db23 100644 --- a/adf_core_python/core/component/abstract_loader.py +++ b/adf_core_python/core/component/abstract_loader.py @@ -1,37 +1,54 @@ from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Optional + +if TYPE_CHECKING: + from adf_core_python.core.component.tactics.tactics_ambulance_center import ( + TacticsAmbulanceCenter, + ) + from adf_core_python.core.component.tactics.tactics_ambulance_team import ( + TacticsAmbulanceTeam, + ) + from adf_core_python.core.component.tactics.tactics_fire_brigade import ( + TacticsFireBrigade, + ) + from adf_core_python.core.component.tactics.tactics_fire_station import ( + TacticsFireStation, + ) + from adf_core_python.core.component.tactics.tactics_police_force import ( + TacticsPoliceForce, + ) + from adf_core_python.core.component.tactics.tactics_police_office import ( + TacticsPoliceOffice, + ) class AbstractLoader(ABC): - def __init__(self) -> None: - self._team_name: str = "" + def __init__(self, team_name: Optional[str] = None): + self._team_name: str = "" if team_name is None else team_name def get_team_name(self) -> str: return self._team_name - def set_team_name(self, team_name: str) -> None: - self._team_name = team_name - - # TODO: Add more abstract methods here @abstractmethod - def get_tactics_ambulance_team(self) -> None: + def get_tactics_ambulance_team(self) -> TacticsAmbulanceTeam: raise NotImplementedError @abstractmethod - def get_tactics_fire_brigade(self) -> None: + def get_tactics_fire_brigade(self) -> TacticsFireBrigade: raise NotImplementedError @abstractmethod - def get_tactics_police_force(self) -> None: + def get_tactics_police_force(self) -> TacticsPoliceForce: raise NotImplementedError @abstractmethod - def get_tactics_ambulance_centre(self) -> None: + def get_tactics_ambulance_center(self) -> TacticsAmbulanceCenter: raise NotImplementedError @abstractmethod - def get_tactics_fire_station(self) -> None: + def get_tactics_fire_station(self) -> TacticsFireStation: raise NotImplementedError @abstractmethod - def get_tactics_police_office(self) -> None: + def get_tactics_police_office(self) -> TacticsPoliceOffice: raise NotImplementedError diff --git a/adf_core_python/implement/default_loader.py b/adf_core_python/implement/default_loader.py new file mode 100644 index 0000000..1f5df5d --- /dev/null +++ b/adf_core_python/implement/default_loader.py @@ -0,0 +1,46 @@ +from typing import TYPE_CHECKING + +from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.implement.tactics.default_tactics_ambulance_team import ( + DefaultTacticsAmbulanceTeam, +) + +if TYPE_CHECKING: + from adf_core_python.core.component.tactics.tactics_ambulance_center import ( + TacticsAmbulanceCenter, + ) + from adf_core_python.core.component.tactics.tactics_ambulance_team import ( + TacticsAmbulanceTeam, + ) + from adf_core_python.core.component.tactics.tactics_fire_brigade import ( + TacticsFireBrigade, + ) + from adf_core_python.core.component.tactics.tactics_fire_station import ( + TacticsFireStation, + ) + from adf_core_python.core.component.tactics.tactics_police_force import ( + TacticsPoliceForce, + ) + from adf_core_python.core.component.tactics.tactics_police_office import ( + TacticsPoliceOffice, + ) + + +class DefaultLoader(AbstractLoader): + def get_tactics_ambulance_team(self) -> TacticsAmbulanceTeam: + return DefaultTacticsAmbulanceTeam() + + # def get_tactics_fire_brigade(self) -> TacticsFireBrigade: + # pass + + # def get_tactics_police_force(self) -> TacticsPoliceForce: + # pass + + # def get_tactics_ambulance_centre(self) -> TacticsAmbulanceCenter: + # pass + + # def get_tactics_fire_station(self) -> TacticsFireStation: + # pass + + # def get_tactics_police_office(self) -> TacticsPoliceOffice: + # pass From 8d338a16de9f6274857d21476a3c119820b82511 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 18 Sep 2024 07:02:30 +0900 Subject: [PATCH 033/249] feat: Implement AgentLauncher for managing connectors and launching agents; add command-line arguments and configuration file --- .../core/launcher/agent_launcher.py | 71 +++++++++++++++++++ adf_core_python/main.py | 62 +++++++++++++++- config/launcher.yaml | 36 ++++++++++ 3 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 adf_core_python/core/launcher/agent_launcher.py create mode 100644 config/launcher.yaml diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py new file mode 100644 index 0000000..f56b9a3 --- /dev/null +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -0,0 +1,71 @@ +import importlib +import threading + +from rcrs_core.connection.componentLauncher import ComponentLauncher + +from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.config.config import Config +from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.launcher.connect.connector import Connector +from adf_core_python.core.launcher.connect.connector_ambulance_centre import ( + ConnectorAmbulanceCentre, +) +from adf_core_python.core.launcher.connect.connector_ambulance_team import ( + ConnectorAmbulanceTeam, +) +from adf_core_python.core.launcher.connect.connector_fire_brigade import ( + ConnectorFireBrigade, +) +from adf_core_python.core.launcher.connect.connector_fire_station import ( + ConnectorFireStation, +) +from adf_core_python.core.launcher.connect.connector_police_force import ( + ConnectorPoliceForce, +) +from adf_core_python.core.launcher.connect.connector_police_office import ( + ConnectorPoliceOffice, +) + + +class AgentLauncher: + def __init__(self, config: Config): + self.config = config + self.connectors: list[Connector] = [] + + def initConnector(self): + loader_name, loader_class_name = self.config.get_value( + ConfigKey.KEY_LOADER_CLASS + ).split(".") + self.loader: AbstractLoader = importlib.import_module( + loader_name, + ).__getattr__( + loader_class_name, + )( + self.config.get_value(ConfigKey.KEY_TEAM_NAME), + ) + + self.connectors.append(ConnectorAmbulanceCentre()) + self.connectors.append(ConnectorAmbulanceTeam()) + self.connectors.append(ConnectorFireBrigade()) + self.connectors.append(ConnectorFireStation()) + self.connectors.append(ConnectorPoliceForce()) + self.connectors.append(ConnectorPoliceOffice()) + + def launch(self): + host: str = self.config.get_value(ConfigKey.KEY_KERNEL_HOST, "localhost") + port: int = self.config.get_value(ConfigKey.KEY_KERNEL_PORT, 27931) + component_launcher: ComponentLauncher = ComponentLauncher(port, host) + + thread_list: list[threading.Thread] = [] + for connector in self.connectors: + thread = threading.Thread( + target=connector.connect, + args=(component_launcher, self.config, self.loader), + ) + thread.start() + thread_list.append(thread) + + for thread in thread_list: + thread.join() + + connected_agent_count = 0 diff --git a/adf_core_python/main.py b/adf_core_python/main.py index 7df869a..9f38f91 100644 --- a/adf_core_python/main.py +++ b/adf_core_python/main.py @@ -1 +1,61 @@ -print("Hello, World!") +import argparse + + +class Main: + def __init__(self): + parser = argparse.ArgumentParser(description="Agent Launcher") + + parser.add_argument( + "--host", + type=str, + default="localhost", + help="host name(Default: localhost)", + metavar="", + ) + parser.add_argument( + "--port", + type=int, + default=27931, + help="port number(Default: 27931)", + metavar="", + ) + parser.add_argument( + "-a", + "--ambulance", + type=int, + default=-1, + help="number of ambulance agents(Default: -1 means all ambulance)", + metavar="", + ) + parser.add_argument( + "-f", + "--firebrigade", + type=int, + default=-1, + help="number of firebrigade agents(Default: -1 means all firebrigade)", + metavar="", + ) + parser.add_argument( + "-p", + "--policeforce", + type=int, + default=-1, + help="number of policeforce agents(Default: -1 means all policeforce)", + metavar="", + ) + parser.add_argument( + "--precompute", + type=bool, + default=False, + help="precompute flag", + metavar="", + ) + parser.add_argument( + "--varbose", type=bool, default=False, help="varbose flag", metavar="" + ) + args = parser.parse_args() + print(args) + + +if __name__ == "__main__": + main = Main() diff --git a/config/launcher.yaml b/config/launcher.yaml new file mode 100644 index 0000000..c1d461e --- /dev/null +++ b/config/launcher.yaml @@ -0,0 +1,36 @@ +kernel: + host: localhost + port: 27931 + +team: + name: AIT-Rescue + +adf: + launcher: + precompute: 0 + adf: + debug: + flag: 0 + agent: + moduleconfig: + filename: config/module.yaml + + develop: + flag: 1 + filename: config/develop.json + + team: + platoon: + ambulance: + count: -1 + fire: + count: -1 + police: + count: -1 + office: + ambulance: + count: -1 + fire: + count: -1 + police: + count: -1 From f5fcc285c28637fe0877ab963d80a41c06087181 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 18 Sep 2024 10:33:29 +0900 Subject: [PATCH 034/249] chore: Fix typo in verbose flag name --- adf_core_python/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adf_core_python/main.py b/adf_core_python/main.py index 9f38f91..fcbfc23 100644 --- a/adf_core_python/main.py +++ b/adf_core_python/main.py @@ -51,7 +51,7 @@ def __init__(self): metavar="", ) parser.add_argument( - "--varbose", type=bool, default=False, help="varbose flag", metavar="" + "--verbose", type=bool, default=False, help="verbose flag", metavar="" ) args = parser.parse_args() print(args) From e879b328f64114b80a8156f2d6770451dc9dd858 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 18 Sep 2024 15:02:55 +0900 Subject: [PATCH 035/249] feat: Refactor TacticsCenter inheritance hierarchy --- .../core/component/tactics/tactics_ambulance_center.py | 5 ++--- .../core/component/tactics/tactics_ambulance_team.py | 5 ++--- .../core/component/tactics/tactics_fire_brigade.py | 5 ++--- .../core/component/tactics/tactics_fire_station.py | 5 ++--- .../core/component/tactics/tactics_police_force.py | 5 ++--- .../core/component/tactics/tactics_police_office.py | 5 ++--- 6 files changed, 12 insertions(+), 18 deletions(-) diff --git a/adf_core_python/core/component/tactics/tactics_ambulance_center.py b/adf_core_python/core/component/tactics/tactics_ambulance_center.py index 371c2e7..da9b2b8 100644 --- a/adf_core_python/core/component/tactics/tactics_ambulance_center.py +++ b/adf_core_python/core/component/tactics/tactics_ambulance_center.py @@ -1,9 +1,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import Optional -if TYPE_CHECKING: - from adf_core_python.core.component.tactics.tactics_center import TacticsCenter +from adf_core_python.core.component.tactics.tactics_center import TacticsCenter class TacticsAmbulanceCenter(TacticsCenter): diff --git a/adf_core_python/core/component/tactics/tactics_ambulance_team.py b/adf_core_python/core/component/tactics/tactics_ambulance_team.py index f596dc0..c5ad2b4 100644 --- a/adf_core_python/core/component/tactics/tactics_ambulance_team.py +++ b/adf_core_python/core/component/tactics/tactics_ambulance_team.py @@ -1,9 +1,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import Optional -if TYPE_CHECKING: - from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent +from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent class TacticsAmbulanceTeam(TacticsAgent): diff --git a/adf_core_python/core/component/tactics/tactics_fire_brigade.py b/adf_core_python/core/component/tactics/tactics_fire_brigade.py index ce2fc7a..1df16b3 100644 --- a/adf_core_python/core/component/tactics/tactics_fire_brigade.py +++ b/adf_core_python/core/component/tactics/tactics_fire_brigade.py @@ -1,9 +1,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import Optional -if TYPE_CHECKING: - from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent +from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent class TacticsFireBrigade(TacticsAgent): diff --git a/adf_core_python/core/component/tactics/tactics_fire_station.py b/adf_core_python/core/component/tactics/tactics_fire_station.py index 8723289..0b6ed40 100644 --- a/adf_core_python/core/component/tactics/tactics_fire_station.py +++ b/adf_core_python/core/component/tactics/tactics_fire_station.py @@ -1,9 +1,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import Optional -if TYPE_CHECKING: - from adf_core_python.core.component.tactics.tactics_center import TacticsCenter +from adf_core_python.core.component.tactics.tactics_center import TacticsCenter class TacticsFireStation(TacticsCenter): diff --git a/adf_core_python/core/component/tactics/tactics_police_force.py b/adf_core_python/core/component/tactics/tactics_police_force.py index e64950b..a098128 100644 --- a/adf_core_python/core/component/tactics/tactics_police_force.py +++ b/adf_core_python/core/component/tactics/tactics_police_force.py @@ -1,9 +1,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import Optional -if TYPE_CHECKING: - from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent +from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent class TacticsPoliceForce(TacticsAgent): diff --git a/adf_core_python/core/component/tactics/tactics_police_office.py b/adf_core_python/core/component/tactics/tactics_police_office.py index dc41629..70950e1 100644 --- a/adf_core_python/core/component/tactics/tactics_police_office.py +++ b/adf_core_python/core/component/tactics/tactics_police_office.py @@ -1,9 +1,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import Optional -if TYPE_CHECKING: - from adf_core_python.core.component.tactics.tactics_center import TacticsCenter +from adf_core_python.core.component.tactics.tactics_center import TacticsCenter class TacticsPoliceOffice(TacticsCenter): From 9f0aa2d081f091875bde5070e4bdb319a26881b8 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 18 Sep 2024 15:03:08 +0900 Subject: [PATCH 036/249] feat: Add YAML configuration support to Config class --- adf_core_python/core/config/config.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/adf_core_python/core/config/config.py b/adf_core_python/core/config/config.py index 38c414e..e85d7d3 100644 --- a/adf_core_python/core/config/config.py +++ b/adf_core_python/core/config/config.py @@ -1,11 +1,14 @@ -from typing import Any +from typing import Any, Optional from yaml import safe_load class Config: - def __init__(self): + def __init__(self, config_file: Optional[str] = None) -> None: self.config: dict[str, Any] = {} + if config_file: + self.config = self.read_from_yaml(config_file) + self.config = self.flatten(self.config) def set_value(self, key: str, value: Any): self.config[key] = value @@ -35,3 +38,6 @@ def flatten( else: flatten_data[new_key] = value return flatten_data + + def __str__(self) -> str: + return str(self.config) From e3f3cd4e78eef961ea2836500bbd23494fee8b09 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 18 Sep 2024 15:03:27 +0900 Subject: [PATCH 037/249] feat: Update connector classes to use Config from adf_core_python.core.config.config --- .../core/launcher/connect/connector.py | 2 +- .../connect/connector_ambulance_centre.py | 18 +++++------ .../connect/connector_ambulance_team.py | 30 +++++++++---------- .../connect/connector_fire_brigade.py | 16 ++++------ .../connect/connector_fire_station.py | 16 ++++------ .../connect/connector_police_force.py | 16 ++++------ .../connect/connector_police_office.py | 16 ++++------ 7 files changed, 46 insertions(+), 68 deletions(-) diff --git a/adf_core_python/core/launcher/connect/connector.py b/adf_core_python/core/launcher/connect/connector.py index b8ea5c4..dbf4f29 100644 --- a/adf_core_python/core/launcher/connect/connector.py +++ b/adf_core_python/core/launcher/connect/connector.py @@ -1,9 +1,9 @@ from abc import ABC, abstractmethod from rcrs_core.connection.componentLauncher import ComponentLauncher -from rcrs_core.config.config import Config from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.config.config import Config class Connector(ABC): diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py index 702056b..3475174 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py @@ -1,12 +1,12 @@ from logging import Logger, getLogger from rcrs_core.agents.ambulanceCenterAgent import AmbulanceCenterAgent -from rcrs_core.config.config import Config from rcrs_core.connection.componentLauncher import ComponentLauncher from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.connector import Connector @@ -22,15 +22,13 @@ def connect( config: Config, loader: AbstractLoader, ) -> None: - count: int = config.get_int_value_or_default( - ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0 - ) + count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) if count == 0: return for _ in range(count): # tactics_ambulance_centre: TacticsAmbulanceCentre - if loader.get_tactics_ambulance_centre() is not None: + if loader.get_tactics_ambulance_center() is not None: self.logger.error("Cannot load ambulance centre tactics") # tactics_ambulance_centre = loader.get_tactics_ambulance_centre() else: @@ -38,15 +36,15 @@ def connect( pass module_config: ModuleConfig = ModuleConfig( # noqa: F841 - config.get_value_or_default( + config.get_value( ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, ModuleConfig.DEFAULT_CONFIG_FILE_NAME, ) ) develop_data: DevelopData = DevelopData( # noqa: F841 - config.get_boolean_value_or_default(ConfigKey.KEY_DEBUG_FLAG, False), - config.get_value_or_default( + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value( ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME ), ) @@ -55,9 +53,7 @@ def connect( component_launcher.connect( # TODO: AmbulanceCenterAgent is not implemented precompute method and other methods AmbulanceCenterAgent( - config.get_boolean_value_or_default( - ConfigKey.KEY_PRECOMPUTE, False - ), + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), ), # type: ignore component_launcher.generate_request_ID(), ) diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index d29a15d..49960d8 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -1,12 +1,15 @@ from logging import Logger, getLogger from rcrs_core.agents.ambulanceTeamAgent import AmbulanceTeamAgent -from rcrs_core.config.config import Config from rcrs_core.connection.componentLauncher import ComponentLauncher from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.component.tactics.tactics_ambulance_team import ( + TacticsAmbulanceTeam, +) +from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.connector import Connector @@ -22,31 +25,28 @@ def connect( config: Config, loader: AbstractLoader, ) -> None: - count: int = config.get_int_value_or_default( - ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0 - ) + count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) if count == 0: return for _ in range(count): - # tactics_ambulance_team: TacticsAmbulanceTeam - if loader.get_tactics_ambulance_team() is not None: + if loader.get_tactics_ambulance_team() is None: self.logger.error("Cannot load ambulance team tactics") - # tactics_ambulance_team = loader.get_tactics_ambulance_team() - else: - # tactics_ambulance_team = DummyTacticsAmbulanceTeam() - pass + + tactics_ambulance_team: TacticsAmbulanceTeam = ( + loader.get_tactics_ambulance_team() + ) module_config: ModuleConfig = ModuleConfig( # noqa: F841 - config.get_value_or_default( + config.get_value( ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, ModuleConfig.DEFAULT_CONFIG_FILE_NAME, ) ) develop_data: DevelopData = DevelopData( # noqa: F841 - config.get_boolean_value_or_default(ConfigKey.KEY_DEBUG_FLAG, False), - config.get_value_or_default( + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value( ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME ), ) @@ -54,9 +54,7 @@ def connect( # TODO: component_launcher.generate_request_ID can cause race condition component_launcher.connect( AmbulanceTeamAgent( - config.get_boolean_value_or_default( - ConfigKey.KEY_PRECOMPUTE, False - ), + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), ), component_launcher.generate_request_ID(), ) diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/adf_core_python/core/launcher/connect/connector_fire_brigade.py index fd46fac..920a805 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_brigade.py +++ b/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -1,12 +1,12 @@ from logging import Logger, getLogger from rcrs_core.agents.fireBrigadeAgent import FireBrigadeAgent -from rcrs_core.config.config import Config from rcrs_core.connection.componentLauncher import ComponentLauncher from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.connector import Connector @@ -22,9 +22,7 @@ def connect( config: Config, loader: AbstractLoader, ) -> None: - count: int = config.get_int_value_or_default( - ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0 - ) + count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) if count == 0: return @@ -38,15 +36,15 @@ def connect( pass module_config: ModuleConfig = ModuleConfig( # noqa: F841 - config.get_value_or_default( + config.get_value( ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, ModuleConfig.DEFAULT_CONFIG_FILE_NAME, ) ) develop_data: DevelopData = DevelopData( # noqa: F841 - config.get_boolean_value_or_default(ConfigKey.KEY_DEBUG_FLAG, False), - config.get_value_or_default( + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value( ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME ), ) @@ -54,9 +52,7 @@ def connect( # TODO: component_launcher.generate_request_ID can cause race condition component_launcher.connect( FireBrigadeAgent( - config.get_boolean_value_or_default( - ConfigKey.KEY_PRECOMPUTE, False - ), + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), ), component_launcher.generate_request_ID(), ) diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/adf_core_python/core/launcher/connect/connector_fire_station.py index f3c197a..a2e5860 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_station.py +++ b/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -1,12 +1,12 @@ from logging import Logger, getLogger from rcrs_core.agents.fireStationAgent import FireStationAgent -from rcrs_core.config.config import Config from rcrs_core.connection.componentLauncher import ComponentLauncher from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.connector import Connector @@ -22,9 +22,7 @@ def connect( config: Config, loader: AbstractLoader, ) -> None: - count: int = config.get_int_value_or_default( - ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0 - ) + count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) if count == 0: return @@ -38,15 +36,15 @@ def connect( pass module_config: ModuleConfig = ModuleConfig( # noqa: F841 - config.get_value_or_default( + config.get_value( ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, ModuleConfig.DEFAULT_CONFIG_FILE_NAME, ) ) develop_data: DevelopData = DevelopData( # noqa: F841 - config.get_boolean_value_or_default(ConfigKey.KEY_DEBUG_FLAG, False), - config.get_value_or_default( + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value( ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME ), ) @@ -54,9 +52,7 @@ def connect( # TODO: component_launcher.generate_request_ID can cause race condition component_launcher.connect( FireStationAgent( - config.get_boolean_value_or_default( - ConfigKey.KEY_PRECOMPUTE, False - ), + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), ), # type: ignore component_launcher.generate_request_ID(), ) diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py index 429c15c..dda4fb6 100644 --- a/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -1,12 +1,12 @@ from logging import Logger, getLogger from rcrs_core.agents.policeForceAgent import PoliceForceAgent -from rcrs_core.config.config import Config from rcrs_core.connection.componentLauncher import ComponentLauncher from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.connector import Connector @@ -22,9 +22,7 @@ def connect( config: Config, loader: AbstractLoader, ) -> None: - count: int = config.get_int_value_or_default( - ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0 - ) + count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) if count == 0: return @@ -38,15 +36,15 @@ def connect( pass module_config: ModuleConfig = ModuleConfig( # noqa: F841 - config.get_value_or_default( + config.get_value( ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, ModuleConfig.DEFAULT_CONFIG_FILE_NAME, ) ) develop_data: DevelopData = DevelopData( # noqa: F841 - config.get_boolean_value_or_default(ConfigKey.KEY_DEBUG_FLAG, False), - config.get_value_or_default( + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value( ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME ), ) @@ -54,9 +52,7 @@ def connect( # TODO: component_launcher.generate_request_ID can cause race condition component_launcher.connect( PoliceForceAgent( - config.get_boolean_value_or_default( - ConfigKey.KEY_PRECOMPUTE, False - ), + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), ), component_launcher.generate_request_ID(), ) diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py index dd43031..8cac435 100644 --- a/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -1,12 +1,12 @@ from logging import Logger, getLogger from rcrs_core.agents.policeOfficeAgent import PoliceOfficeAgent -from rcrs_core.config.config import Config from rcrs_core.connection.componentLauncher import ComponentLauncher from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.connector import Connector @@ -22,9 +22,7 @@ def connect( config: Config, loader: AbstractLoader, ) -> None: - count: int = config.get_int_value_or_default( - ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0 - ) + count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) if count == 0: return @@ -38,15 +36,15 @@ def connect( pass module_config: ModuleConfig = ModuleConfig( # noqa: F841 - config.get_value_or_default( + config.get_value( ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, ModuleConfig.DEFAULT_CONFIG_FILE_NAME, ) ) develop_data: DevelopData = DevelopData( # noqa: F841 - config.get_boolean_value_or_default(ConfigKey.KEY_DEBUG_FLAG, False), - config.get_value_or_default( + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value( ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME ), ) @@ -54,9 +52,7 @@ def connect( # TODO: component_launcher.generate_request_ID can cause race condition component_launcher.connect( PoliceOfficeAgent( - config.get_boolean_value_or_default( - ConfigKey.KEY_PRECOMPUTE, False - ), + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), ), component_launcher.generate_request_ID(), ) From 743d53ceced33f75de4044dd6d2981f3f62b461a Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 18 Sep 2024 15:04:08 +0900 Subject: [PATCH 038/249] feat: Add logger to AgentLauncher for better debugging and monitoring --- .../core/launcher/agent_launcher.py | 25 +++++++++++++------ config/launcher.yaml | 11 ++++---- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index f56b9a3..e05d692 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -1,5 +1,6 @@ import importlib import threading +from logging import Logger, getLogger from rcrs_core.connection.componentLauncher import ComponentLauncher @@ -30,15 +31,18 @@ class AgentLauncher: def __init__(self, config: Config): self.config = config + self.logger: Logger = getLogger(__name__) self.connectors: list[Connector] = [] + self.thread_list: list[threading.Thread] = [] def initConnector(self): loader_name, loader_class_name = self.config.get_value( - ConfigKey.KEY_LOADER_CLASS - ).split(".") - self.loader: AbstractLoader = importlib.import_module( - loader_name, - ).__getattr__( + ConfigKey.KEY_LOADER_CLASS, + "adf_core_python.implement.default_loader.DefaultLoader", + ).rsplit(".", 1) + loader_module = importlib.import_module(loader_name) + self.loader: AbstractLoader = getattr( + loader_module, loader_class_name, )( self.config.get_value(ConfigKey.KEY_TEAM_NAME), @@ -54,18 +58,23 @@ def initConnector(self): def launch(self): host: str = self.config.get_value(ConfigKey.KEY_KERNEL_HOST, "localhost") port: int = self.config.get_value(ConfigKey.KEY_KERNEL_PORT, 27931) + self.logger.info(f"Start agent launcher (host: {host}, port: {port})") + component_launcher: ComponentLauncher = ComponentLauncher(port, host) - thread_list: list[threading.Thread] = [] for connector in self.connectors: thread = threading.Thread( target=connector.connect, args=(component_launcher, self.config, self.loader), ) thread.start() - thread_list.append(thread) + self.thread_list.append(thread) - for thread in thread_list: + for thread in self.thread_list: thread.join() connected_agent_count = 0 + for connector in self.connectors: + connected_agent_count += connector.get_connected_agent_count() + + self.logger.info(f"Connected agent count: {connected_agent_count}") diff --git a/config/launcher.yaml b/config/launcher.yaml index c1d461e..532a4cf 100644 --- a/config/launcher.yaml +++ b/config/launcher.yaml @@ -8,12 +8,11 @@ team: adf: launcher: precompute: 0 - adf: - debug: - flag: 0 - agent: - moduleconfig: - filename: config/module.yaml + debug: + flag: 0 + agent: + moduleconfig: + filename: config/module.yaml develop: flag: 1 From baff75ad066160d2dac34670a328642e88250610 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 18 Sep 2024 15:25:03 +0900 Subject: [PATCH 039/249] Refactor class constructors to include type hints --- .../module/complex/human_detector.py | 25 ++++--- .../core/component/module/complex/search.py | 25 ++++--- adf_core_python/core/config/config.py | 2 +- .../core/launcher/agent_launcher.py | 4 +- .../connect/connector_ambulance_team.py | 2 +- adf_core_python/implement/default_loader.py | 35 ++++++--- .../default_tactics_ambulance_center.py | 59 +++++++++++++++ .../tactics/default_tactics_ambulance_team.py | 4 +- .../tactics/default_tactics_fire_brigade.py | 72 +++++++++++++++++++ .../tactics/default_tactics_fire_station.py | 59 +++++++++++++++ .../tactics/default_tactics_police_force.py | 72 +++++++++++++++++++ .../tactics/default_tactics_police_office.py | 59 +++++++++++++++ adf_core_python/main.py | 2 +- 13 files changed, 387 insertions(+), 33 deletions(-) diff --git a/adf_core_python/core/component/module/complex/human_detector.py b/adf_core_python/core/component/module/complex/human_detector.py index f69dd51..623bd69 100644 --- a/adf_core_python/core/component/module/complex/human_detector.py +++ b/adf_core_python/core/component/module/complex/human_detector.py @@ -1,22 +1,31 @@ from __future__ import annotations from abc import abstractmethod +from typing import TYPE_CHECKING from rcrs_core.entities.human import Human -from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData -from adf_core_python.core.component.module.complex.target_detector import TargetDetector +if TYPE_CHECKING: + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.component.module.complex.target_detector import ( + TargetDetector, + ) class HumanDetector(TargetDetector[Human]): def __init__( self, - agent_info, - world_info, - scenario_info, - module_manager, - develop_data, - ): + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) diff --git a/adf_core_python/core/component/module/complex/search.py b/adf_core_python/core/component/module/complex/search.py index 9049d8b..604cd69 100644 --- a/adf_core_python/core/component/module/complex/search.py +++ b/adf_core_python/core/component/module/complex/search.py @@ -1,22 +1,31 @@ from __future__ import annotations from abc import abstractmethod +from typing import TYPE_CHECKING from rcrs_core.entities.area import Area -from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData -from adf_core_python.core.component.module.complex.target_detector import TargetDetector +if TYPE_CHECKING: + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.component.module.complex.target_detector import ( + TargetDetector, + ) class Search(TargetDetector[Area]): def __init__( self, - agent_info, - world_info, - scenario_info, - module_manager, - develop_data, - ): + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) diff --git a/adf_core_python/core/config/config.py b/adf_core_python/core/config/config.py index e85d7d3..5f55aea 100644 --- a/adf_core_python/core/config/config.py +++ b/adf_core_python/core/config/config.py @@ -10,7 +10,7 @@ def __init__(self, config_file: Optional[str] = None) -> None: self.config = self.read_from_yaml(config_file) self.config = self.flatten(self.config) - def set_value(self, key: str, value: Any): + def set_value(self, key: str, value: Any) -> None: self.config[key] = value def get_value(self, key: str, default: Any = None) -> Any: diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index e05d692..d73a499 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -35,7 +35,7 @@ def __init__(self, config: Config): self.connectors: list[Connector] = [] self.thread_list: list[threading.Thread] = [] - def initConnector(self): + def initConnector(self) -> None: loader_name, loader_class_name = self.config.get_value( ConfigKey.KEY_LOADER_CLASS, "adf_core_python.implement.default_loader.DefaultLoader", @@ -55,7 +55,7 @@ def initConnector(self): self.connectors.append(ConnectorPoliceForce()) self.connectors.append(ConnectorPoliceOffice()) - def launch(self): + def launch(self) -> None: host: str = self.config.get_value(ConfigKey.KEY_KERNEL_HOST, "localhost") port: int = self.config.get_value(ConfigKey.KEY_KERNEL_PORT, 27931) self.logger.info(f"Start agent launcher (host: {host}, port: {port})") diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index 49960d8..5fcb3d5 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -33,7 +33,7 @@ def connect( if loader.get_tactics_ambulance_team() is None: self.logger.error("Cannot load ambulance team tactics") - tactics_ambulance_team: TacticsAmbulanceTeam = ( + tactics_ambulance_team: TacticsAmbulanceTeam = ( # noqa: F841 loader.get_tactics_ambulance_team() ) diff --git a/adf_core_python/implement/default_loader.py b/adf_core_python/implement/default_loader.py index 1f5df5d..2b79b7b 100644 --- a/adf_core_python/implement/default_loader.py +++ b/adf_core_python/implement/default_loader.py @@ -1,9 +1,24 @@ from typing import TYPE_CHECKING from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.implement.tactics.default_tactics_ambulance_center import ( + DefaultTacticsAmbulanceCenter, +) from adf_core_python.implement.tactics.default_tactics_ambulance_team import ( DefaultTacticsAmbulanceTeam, ) +from adf_core_python.implement.tactics.default_tactics_fire_brigade import ( + DefaultTacticsFireBrigade, +) +from adf_core_python.implement.tactics.default_tactics_fire_station import ( + DefaultTacticsFireStation, +) +from adf_core_python.implement.tactics.default_tactics_police_force import ( + DefaultTacticsPoliceForce, +) +from adf_core_python.implement.tactics.default_tactics_police_office import ( + DefaultTacticsPoliceOffice, +) if TYPE_CHECKING: from adf_core_python.core.component.tactics.tactics_ambulance_center import ( @@ -30,17 +45,17 @@ class DefaultLoader(AbstractLoader): def get_tactics_ambulance_team(self) -> TacticsAmbulanceTeam: return DefaultTacticsAmbulanceTeam() - # def get_tactics_fire_brigade(self) -> TacticsFireBrigade: - # pass + def get_tactics_fire_brigade(self) -> TacticsFireBrigade: + return DefaultTacticsFireBrigade() - # def get_tactics_police_force(self) -> TacticsPoliceForce: - # pass + def get_tactics_police_force(self) -> TacticsPoliceForce: + return DefaultTacticsPoliceForce() - # def get_tactics_ambulance_centre(self) -> TacticsAmbulanceCenter: - # pass + def get_tactics_ambulance_centre(self) -> TacticsAmbulanceCenter: + return DefaultTacticsAmbulanceCenter() - # def get_tactics_fire_station(self) -> TacticsFireStation: - # pass + def get_tactics_fire_station(self) -> TacticsFireStation: + return DefaultTacticsFireStation() - # def get_tactics_police_office(self) -> TacticsPoliceOffice: - # pass + def get_tactics_police_office(self) -> TacticsPoliceOffice: + return DefaultTacticsPoliceOffice() diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py b/adf_core_python/implement/tactics/default_tactics_ambulance_center.py index e69de29..0882f7a 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_center.py @@ -0,0 +1,59 @@ +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.tactics.tactics_ambulance_center import ( + TacticsAmbulanceCenter, +) + + +class DefaultTacticsAmbulanceCenter(TacticsAmbulanceCenter): + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def think( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index fb98aa1..0a69057 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -106,8 +106,8 @@ def think( ) -> Action: self.module_update_info(message_manager) - agent: AmbulanceTeamEntity = cast(AmbulanceTeamEntity, agent_info.get_myself()) - entity_id = agent_info.get_entity_id() + agent: AmbulanceTeamEntity = cast(AmbulanceTeamEntity, agent_info.get_myself()) # noqa: F841 + entity_id = agent_info.get_entity_id() # noqa: F841 target_entity_id = self._human_detector.calculate().get_target_entity_id() action = ( diff --git a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py index e69de29..ca13eeb 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py @@ -0,0 +1,72 @@ +from adf_core_python.core.agent.action.action import Action +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.tactics.tactics_fire_brigade import ( + TacticsFireBrigade, +) + + +class DefaultTacticsFireBrigade(TacticsFireBrigade): + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def think( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> Action: + raise NotImplementedError diff --git a/adf_core_python/implement/tactics/default_tactics_fire_station.py b/adf_core_python/implement/tactics/default_tactics_fire_station.py index e69de29..09d4fd4 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_station.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_station.py @@ -0,0 +1,59 @@ +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.tactics.tactics_fire_station import ( + TacticsFireStation, +) + + +class DefaultTacticsFireStation(TacticsFireStation): + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def think( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py index e69de29..3906725 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -0,0 +1,72 @@ +from adf_core_python.core.agent.action.action import Action +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.tactics.tactics_police_force import ( + TacticsPoliceForce, +) + + +class DefaultTacticsPoliceForce(TacticsPoliceForce): + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def think( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> Action: + raise NotImplementedError diff --git a/adf_core_python/implement/tactics/default_tactics_police_office.py b/adf_core_python/implement/tactics/default_tactics_police_office.py index e69de29..0264404 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_office.py +++ b/adf_core_python/implement/tactics/default_tactics_police_office.py @@ -0,0 +1,59 @@ +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.tactics.tactics_police_office import ( + TacticsPoliceOffice, +) + + +class DefaultTacticsPoliceOffice(TacticsPoliceOffice): + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def think( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError diff --git a/adf_core_python/main.py b/adf_core_python/main.py index fcbfc23..d4f477a 100644 --- a/adf_core_python/main.py +++ b/adf_core_python/main.py @@ -2,7 +2,7 @@ class Main: - def __init__(self): + def __init__(self) -> None: parser = argparse.ArgumentParser(description="Agent Launcher") parser.add_argument( From e47e9f3620d8a07e0896cf7ed4286aa58356423a Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 19 Sep 2024 00:10:04 +0900 Subject: [PATCH 040/249] feat: Implement Agent class with initialization parameters and precomputation mode --- adf_core_python/core/agent/agent.py | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 adf_core_python/core/agent/agent.py diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py new file mode 100644 index 0000000..ae19b1e --- /dev/null +++ b/adf_core_python/core/agent/agent.py @@ -0,0 +1,30 @@ +from rcrs_core.entities.entity import Entity + +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.scenario_info import Mode +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + + +class Agent(Entity): + def __init__( + self, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + ): + self._tema_name = team_name + self._is_precompute = is_precompute + self._is_debug = is_debug + + if self._is_precompute: + # PrecomputeData.remove_data(data_storage_name) + self._mode = Mode.PRECOMPUTATION + + self._module_config: ModuleConfig = module_config + self._develop_data: DevelopData = develop_data + self._precompute_data: PrecomputeData = PrecomputeData(data_storage_name) + self._message_manager = None From f6a853e00a5ca5dc3c9b036ec120185beba413f5 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 25 Sep 2024 02:09:24 +0900 Subject: [PATCH 041/249] feat: Refactor imports and add Platoon class with initialization and logic for agent management --- .../core/agent/info/scenario_info.py | 4 +- adf_core_python/core/agent/info/world_info.py | 13 +++ adf_core_python/core/agent/platoon/platoon.py | 100 ++++++++++++++++++ 3 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 adf_core_python/core/agent/platoon/platoon.py diff --git a/adf_core_python/core/agent/info/scenario_info.py b/adf_core_python/core/agent/info/scenario_info.py index 6a2d699..099b7f2 100644 --- a/adf_core_python/core/agent/info/scenario_info.py +++ b/adf_core_python/core/agent/info/scenario_info.py @@ -1,6 +1,6 @@ from enum import Enum -from rcrs_core.config.config import Config +from adf_core_python.core.config.config import Config class Mode(Enum): @@ -71,4 +71,4 @@ def get_config_value(self, key: str, default: str) -> str: str Value of the configuration """ - return self._config.get_value_or_default(key, default) + return self._config.get_value(key, default) diff --git a/adf_core_python/core/agent/info/world_info.py b/adf_core_python/core/agent/info/world_info.py index c765a82..076fe0e 100644 --- a/adf_core_python/core/agent/info/world_info.py +++ b/adf_core_python/core/agent/info/world_info.py @@ -1,5 +1,6 @@ from typing import Any +from rcrs_core.worldmodel.changeSet import ChangeSet from rcrs_core.worldmodel.entityID import EntityID from rcrs_core.worldmodel.worldmodel import WorldModel @@ -10,6 +11,7 @@ def __init__(self, world_model: WorldModel): self._time: int = 0 self._is_run_rollback: bool = False self._rollback: dict[EntityID, dict[int, dict[int, Any]]] = {} + self._change_set: ChangeSet # TODO: Implement the worldmodel access methods def get_world_model(self) -> WorldModel: @@ -22,3 +24,14 @@ def get_world_model(self) -> WorldModel: World model """ return self._world_model + + def set_change_set(self, change_set: ChangeSet) -> None: + """ + Set the change set + + Parameters + ---------- + change_set : ChangeSet + Change set + """ + self._change_set = change_set diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py new file mode 100644 index 0000000..6c6b8e7 --- /dev/null +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -0,0 +1,100 @@ +from logging import Logger, getLogger + +from rcrs_core.agents.agent import Agent + +from adf_core_python.core.agent.action.action import Action +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent + + +class Platoon(Agent): + def __init__( + self, + tactics_agent: TacticsAgent, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + ): + super().__init__( + is_precompute, + ) + self._tactics_agent = tactics_agent + self._team_name = team_name + self._is_precompute = is_precompute + self._is_debug = is_debug + self._data_storage_name = data_storage_name + self._module_config = module_config + self._develop_data = develop_data + + def post_connect(self): + self._logger: Logger = getLogger(__name__) + self._agent_info: AgentInfo = AgentInfo(self, self.world_model) + self._world_info: WorldInfo = WorldInfo(self.world_model) + self._precompute_data: PrecomputeData = PrecomputeData(self._data_storage_name) + + if self._is_precompute: + self._mode = Mode.PRECOMPUTATION + else: + # if self._precompute_data.is_ready(): + # self._mode = Mode.PRECOMPUTED + # else: + # self._mode = Mode.NON_PRECOMPUTE + self._mode = Mode.NON_PRECOMPUTE + + self._scenario_info: ScenarioInfo = ScenarioInfo(self.config, self._mode) # type: ignore + self._module_manager: ModuleManager = ModuleManager( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_config, + self._develop_data, + ) + + self._tactics_agent.initialize( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + MessageManager(), + self._develop_data, + ) + + match self._mode: + case Mode.PRECOMPUTATION: + pass + case Mode.PRECOMPUTED: + pass + case Mode.NON_PRECOMPUTE: + self._tactics_agent.prepare( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + self._develop_data, + ) + + def think(self, time, change_set, hear): + action: Action = self._tactics_agent.think( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + MessageManager(), + self._develop_data, + ) + if action is not None and self.agent_id is not None: + self._agent_info.set_executed_action(time, action) + self.send_msg(action.get_command(self.agent_id, time)) From 1ac267542bfe4f5d5b2eba82b5b881afb53a04c3 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 25 Sep 2024 10:44:22 +0900 Subject: [PATCH 042/249] feat: add lint N8 check --- .../core/agent/action/common/action_move.py | 14 +++++++------- adf_core_python/core/launcher/agent_launcher.py | 2 +- pyproject.toml | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/adf_core_python/core/agent/action/common/action_move.py b/adf_core_python/core/agent/action/common/action_move.py index 06dc150..a693817 100644 --- a/adf_core_python/core/agent/action/common/action_move.py +++ b/adf_core_python/core/agent/action/common/action_move.py @@ -15,19 +15,19 @@ class ActionMove(Action): def __init__( self, path: list[EntityID], - destinationX: Optional[int] = None, - destinationY: Optional[int] = None, + destination_x: Optional[int] = None, + destination_y: Optional[int] = None, ) -> None: self.path = path - self.destinationX = destinationX - self.destinationY = destinationY + self.destination_x = destination_x + self.destination_y = destination_y def get_command(self, agent_id: EntityID, time: int) -> Command: path: list[int] = [p.get_value() for p in self.path] - if self.destinationX is not None and self.destinationY is not None: - return AKMove(agent_id, time, path, self.destinationX, self.destinationY) + if self.destination_x is not None and self.destination_y is not None: + return AKMove(agent_id, time, path, self.destination_x, self.destination_y) else: return AKMove(agent_id, time, path) def __str__(self) -> str: - return f"ActionMove(path={self.path}, destinationX={self.destinationX}, destinationY={self.destinationY})" + return f"ActionMove(path={self.path}, destination_x={self.destination_x}, destination_y{self.destination_y})" diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index d73a499..f5b7fb1 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -35,7 +35,7 @@ def __init__(self, config: Config): self.connectors: list[Connector] = [] self.thread_list: list[threading.Thread] = [] - def initConnector(self) -> None: + def init_connector(self) -> None: loader_name, loader_class_name = self.config.get_value( ConfigKey.KEY_LOADER_CLASS, "adf_core_python.implement.default_loader.DefaultLoader", diff --git a/pyproject.toml b/pyproject.toml index 014b2f8..e9179d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ target-version = "py312" # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or # McCabe complexity (`C901`) by default. -select = ["E4", "E7", "E9", "F"] +select = ["E4", "E7", "E9", "F", "N8"] ignore = [] # Allow fix for all enabled rules (when `--fix`) is provided. From 435a9858752b24a5ea52f63754f22ec9db6cd241 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 25 Sep 2024 10:52:37 +0900 Subject: [PATCH 043/249] remove: Cache pip in CI workflow --- .github/workflows/ci.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c67fc75..b0da406 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,14 +22,6 @@ jobs: with: python-version: 3.12 - - name: Cache pip - uses: actions/cache@v4 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - name: Cache virtual environment uses: actions/cache@v4 with: From 949d2b01b83ef4878ecb6fca2d6716e9ae3fc480 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 25 Sep 2024 10:44:22 +0900 Subject: [PATCH 044/249] feat: add lint N8 check --- .../core/agent/action/common/action_move.py | 14 +++++++------- adf_core_python/core/launcher/agent_launcher.py | 2 +- pyproject.toml | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/adf_core_python/core/agent/action/common/action_move.py b/adf_core_python/core/agent/action/common/action_move.py index 06dc150..a693817 100644 --- a/adf_core_python/core/agent/action/common/action_move.py +++ b/adf_core_python/core/agent/action/common/action_move.py @@ -15,19 +15,19 @@ class ActionMove(Action): def __init__( self, path: list[EntityID], - destinationX: Optional[int] = None, - destinationY: Optional[int] = None, + destination_x: Optional[int] = None, + destination_y: Optional[int] = None, ) -> None: self.path = path - self.destinationX = destinationX - self.destinationY = destinationY + self.destination_x = destination_x + self.destination_y = destination_y def get_command(self, agent_id: EntityID, time: int) -> Command: path: list[int] = [p.get_value() for p in self.path] - if self.destinationX is not None and self.destinationY is not None: - return AKMove(agent_id, time, path, self.destinationX, self.destinationY) + if self.destination_x is not None and self.destination_y is not None: + return AKMove(agent_id, time, path, self.destination_x, self.destination_y) else: return AKMove(agent_id, time, path) def __str__(self) -> str: - return f"ActionMove(path={self.path}, destinationX={self.destinationX}, destinationY={self.destinationY})" + return f"ActionMove(path={self.path}, destination_x={self.destination_x}, destination_y{self.destination_y})" diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index d73a499..f5b7fb1 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -35,7 +35,7 @@ def __init__(self, config: Config): self.connectors: list[Connector] = [] self.thread_list: list[threading.Thread] = [] - def initConnector(self) -> None: + def init_connector(self) -> None: loader_name, loader_class_name = self.config.get_value( ConfigKey.KEY_LOADER_CLASS, "adf_core_python.implement.default_loader.DefaultLoader", diff --git a/pyproject.toml b/pyproject.toml index 014b2f8..e9179d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ target-version = "py312" # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or # McCabe complexity (`C901`) by default. -select = ["E4", "E7", "E9", "F"] +select = ["E4", "E7", "E9", "F", "N8"] ignore = [] # Allow fix for all enabled rules (when `--fix`) is provided. From 32734d6e93cdbb366d8bc93012fef9e8924e8b58 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 25 Sep 2024 10:52:37 +0900 Subject: [PATCH 045/249] remove: Cache pip in CI workflow --- .github/workflows/ci.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c67fc75..b0da406 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,14 +22,6 @@ jobs: with: python-version: 3.12 - - name: Cache pip - uses: actions/cache@v4 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - name: Cache virtual environment uses: actions/cache@v4 with: From 82e549af42a4f904c099f8e66bcb3ba9e412cc03 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 25 Sep 2024 12:05:52 +0900 Subject: [PATCH 046/249] feat: Add PlatoonAmbulance class --- .../core/agent/platoon/platoon_ambulance.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 adf_core_python/core/agent/platoon/platoon_ambulance.py diff --git a/adf_core_python/core/agent/platoon/platoon_ambulance.py b/adf_core_python/core/agent/platoon/platoon_ambulance.py new file mode 100644 index 0000000..33c7731 --- /dev/null +++ b/adf_core_python/core/agent/platoon/platoon_ambulance.py @@ -0,0 +1,34 @@ +from rcrs_core.connection import URN + +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.platoon.platoon import Platoon +from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent + + +class PlatoonAmbulance(Platoon): + def __init__( + self, + tactics_agent: TacticsAgent, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + ): + super().__init__( + tactics_agent, + team_name, + is_precompute, + is_debug, + data_storage_name, + module_config, + develop_data, + ) + + def precompute(self): + pass + + def get_requested_entities(self): + return [URN.Entity.AMBULANCE_TEAM] From da95b860d890d32777288150f2843a34e13ba707 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 25 Sep 2024 12:22:42 +0900 Subject: [PATCH 047/249] feat: Refactor Platoon class and add type hints --- adf_core_python/core/agent/platoon/platoon.py | 8 +++++--- adf_core_python/core/agent/platoon/platoon_ambulance.py | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py index 6c6b8e7..4b231e6 100644 --- a/adf_core_python/core/agent/platoon/platoon.py +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -1,6 +1,8 @@ from logging import Logger, getLogger from rcrs_core.agents.agent import Agent +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.changeSet import ChangeSet from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.communication.message_manager import MessageManager @@ -24,7 +26,7 @@ def __init__( data_storage_name: str, module_config: ModuleConfig, develop_data: DevelopData, - ): + ) -> None: super().__init__( is_precompute, ) @@ -36,7 +38,7 @@ def __init__( self._module_config = module_config self._develop_data = develop_data - def post_connect(self): + def post_connect(self) -> None: self._logger: Logger = getLogger(__name__) self._agent_info: AgentInfo = AgentInfo(self, self.world_model) self._world_info: WorldInfo = WorldInfo(self.world_model) @@ -85,7 +87,7 @@ def post_connect(self): self._develop_data, ) - def think(self, time, change_set, hear): + def think(self, time: int, change_set: ChangeSet, hear: list[Command]) -> None: action: Action = self._tactics_agent.think( self._agent_info, self._world_info, diff --git a/adf_core_python/core/agent/platoon/platoon_ambulance.py b/adf_core_python/core/agent/platoon/platoon_ambulance.py index 33c7731..e0d7fd7 100644 --- a/adf_core_python/core/agent/platoon/platoon_ambulance.py +++ b/adf_core_python/core/agent/platoon/platoon_ambulance.py @@ -27,8 +27,8 @@ def __init__( develop_data, ) - def precompute(self): + def precompute(self) -> None: pass - def get_requested_entities(self): + def get_requested_entities(self) -> list[str]: return [URN.Entity.AMBULANCE_TEAM] From e5d91f0342b66b7f7b2df152ffc9635483730499 Mon Sep 17 00:00:00 2001 From: harrki Date: Wed, 25 Sep 2024 16:09:19 +0900 Subject: [PATCH 048/249] feat: Add RoadDetector and TargetAllocator --- .../component/module/complex/road_detector.py | 47 +++++++++++++++ .../module/complex/target_allocator.py | 59 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 adf_core_python/core/component/module/complex/road_detector.py create mode 100644 adf_core_python/core/component/module/complex/target_allocator.py diff --git a/adf_core_python/core/component/module/complex/road_detector.py b/adf_core_python/core/component/module/complex/road_detector.py new file mode 100644 index 0000000..940fbfb --- /dev/null +++ b/adf_core_python/core/component/module/complex/road_detector.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING + +from rcrs_core.entities.road import Road + +if TYPE_CHECKING: + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.component.module.complex.target_detector import ( + TargetDetector, + ) + + +class RoadDetector(TargetDetector[Road]): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + def precompute(self, precompute_data: PrecomputeData) -> RoadDetector: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> RoadDetector: + super().resume(precompute_data) + return self + + def prepare(self) -> RoadDetector: + super().prepare() + return self + + @abstractmethod + def calculate(self) -> RoadDetector: + return self diff --git a/adf_core_python/core/component/module/complex/target_allocator.py b/adf_core_python/core/component/module/complex/target_allocator.py new file mode 100644 index 0000000..2548a2d --- /dev/null +++ b/adf_core_python/core/component/module/complex/target_allocator.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING, Generic, TypeVar + +from rcrs_core.entities.entity import Entity + +from adf_core_python.core.component.module.abstract_module import AbstractModule + +if TYPE_CHECKING: + from rcrs_core.worldmodel.entityID import EntityID + + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + +T = TypeVar("T", bound=Entity) + + +class TargetAllocator(AbstractModule, Generic[T]): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + @abstractmethod + def get_result(self) -> dict[EntityID, EntityID]: + pass + + @abstractmethod + def calculate(self) -> TargetAllocator[T]: + pass + + def precompute(self, precompute_data: PrecomputeData) -> TargetAllocator[T]: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> TargetAllocator[T]: + super().resume(precompute_data) + return self + + def prepare(self) -> TargetAllocator[T]: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> TargetAllocator[T]: + super().update_info(message_manager) + return self From 8a49f1b4f3717e05a34ae6e289ed0f20094f3a12 Mon Sep 17 00:00:00 2001 From: harrki Date: Wed, 25 Sep 2024 16:10:49 +0900 Subject: [PATCH 049/249] feat: Update DefaultTactics classes --- .../default_tactics_ambulance_center.py | 22 +++++- .../tactics/default_tactics_ambulance_team.py | 26 +++---- .../tactics/default_tactics_fire_brigade.py | 70 ++++++++++++++++-- .../tactics/default_tactics_fire_station.py | 22 +++++- .../tactics/default_tactics_police_force.py | 74 +++++++++++++++++-- .../tactics/default_tactics_police_office.py | 22 +++++- 6 files changed, 199 insertions(+), 37 deletions(-) diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py b/adf_core_python/implement/tactics/default_tactics_ambulance_center.py index 0882f7a..7fbb294 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_center.py @@ -1,10 +1,15 @@ +from typing import cast + from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.module.complex.target_allocator import ( + TargetAllocator, +) from adf_core_python.core.component.tactics.tactics_ambulance_center import ( TacticsAmbulanceCenter, ) @@ -21,7 +26,16 @@ def initialize( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + match scenario_info.get_mode(): + case Mode.NON_PRECOMPUTE: + self._allocator: TargetAllocator = cast( + TargetAllocator, + module_manager.get_module( + "DefaultTacticsAmbulanceCenter.TargetAllocator", + "adf_core_python.implement.module.complex.DefaultTargetAllocator", + ), + ) + self.register_module(self._allocator) def resume( self, @@ -33,7 +47,7 @@ def resume( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + self.module_resume(precompute_data) def prepare( self, @@ -44,7 +58,7 @@ def prepare( precompute_data: PrecomputeData, develop_data: DevelopData, ) -> None: - raise NotImplementedError + self.module_prepare() def think( self, diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 0a69057..4171198 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -36,28 +36,28 @@ def initialize( Search, module_manager.get_module( "DefaultTacticsAmbulanceTeam.Search", - "adf.impl.module.complex.DefaultSearch", + "adf_core_python.implement.module.complex.DefaultSearch", ), ) self._human_detector: HumanDetector = cast( HumanDetector, module_manager.get_module( "DefaultTacticsAmbulanceTeam.HumanDetector", - "adf.impl.module.complex.DefaultHumanDetector", + "adf_core_python.implement.module.complex.DefaultHumanDetector", ), ) - self._actionTransport = module_manager.get_ext_action( - "DefaultTacticsAmbulanceTeam.ExtActionMove", - "adf.impl.extaction.DefaultExtActionMove", + self._action_transport = module_manager.get_ext_action( + "DefaultTacticsAmbulanceTeam.ExtActionTransport", + "adf_core_python.implement.extaction.DefaultExtActionTransport", ) - self._actionExtMove = module_manager.get_ext_action( + self._action_ext_move = module_manager.get_ext_action( "DefaultTacticsAmbulanceTeam.ExtActionMove", - "adf.impl.extaction.DefaultExtActionMove", + "adf_core_python.implement.extaction.DefaultExtActionMove", ) - self.register_module(self._search) - self.register_module(self._human_detector) - self.register_action(self._actionTransport) - self.register_action(self._actionExtMove) + self.register_module(self._search) + self.register_module(self._human_detector) + self.register_action(self._action_transport) + self.register_action(self._action_ext_move) def precompute( self, @@ -111,7 +111,7 @@ def think( target_entity_id = self._human_detector.calculate().get_target_entity_id() action = ( - self._actionTransport.set_target_entity_id(target_entity_id) + self._action_transport.set_target_entity_id(target_entity_id) .calc() .get_action() ) @@ -120,7 +120,7 @@ def think( target_entity_id = self._search.calculate().get_target_entity_id() action = ( - self._actionExtMove.set_target_entity_id(target_entity_id) + self._action_ext_move.set_target_entity_id(target_entity_id) .calc() .get_action() ) diff --git a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py index ca13eeb..11b38af 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py @@ -1,11 +1,18 @@ +from typing import cast + +from rcrs_core.entities.fireBrigade import FireBrigadeEntity + from adf_core_python.core.agent.action.action import Action +from adf_core_python.core.agent.action.common.action_rest import ActionRest from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.module.complex.human_detector import HumanDetector +from adf_core_python.core.component.module.complex.search import Search from adf_core_python.core.component.tactics.tactics_fire_brigade import ( TacticsFireBrigade, ) @@ -22,7 +29,35 @@ def initialize( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + # world_info.index_class() + match scenario_info.get_mode(): + case Mode.NON_PRECOMPUTE: + self._search: Search = cast( + Search, + module_manager.get_module( + "DefaultTacticsFireBrigade.Search", + "adf_core_python.impl.module.complex.DefaultSearch", + ), + ) + self._human_detector: HumanDetector = cast( + HumanDetector, + module_manager.get_module( + "DefaultTacticsFireBrigade.HumanDetector", + "adf_core_python.impl.module.complex.DefaultHumanDetector", + ), + ) + self._action_fire_rescue = module_manager.get_ext_action( + "DefaultTacticsFireBrigade.ExtActionFireRescue", + "adf_core_python.impl.extaction.DefaultExtActionFireRescue", + ) + self._action_ext_move = module_manager.get_ext_action( + "DefaultTacticsAmbulanceTeam.ExtActionMove", + "adf_core_python.impl.extaction.DefaultExtActionMove", + ) + self.register_module(self._search) + self.register_module(self._human_detector) + self.register_action(self._action_fire_rescue) + self.register_action(self._action_ext_move) def precompute( self, @@ -34,7 +69,7 @@ def precompute( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + self.module_precompute(precompute_data) def resume( self, @@ -46,7 +81,7 @@ def resume( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + self.module_resume(precompute_data) def prepare( self, @@ -57,7 +92,7 @@ def prepare( precompute_data: PrecomputeData, develop_data: DevelopData, ) -> None: - raise NotImplementedError + self.module_prepare() def think( self, @@ -69,4 +104,27 @@ def think( message_manager: MessageManager, develop_data: DevelopData, ) -> Action: - raise NotImplementedError + self.module_update_info(message_manager) + + agent: FireBrigadeEntity = cast(FireBrigadeEntity, agent_info.get_myself()) # noqa: F841 + entity_id = agent_info.get_entity_id() # noqa: F841 + + target_entity_id = self._human_detector.calculate().get_target_entity_id() + action = ( + self._action_fire_rescue.set_target_entity_id(target_entity_id) + .calc() + .get_action() + ) + if action is not None: + return action + + target_entity_id = self._search.calculate().get_target_entity_id() + action = ( + self._action_ext_move.set_target_entity_id(target_entity_id) + .calc() + .get_action() + ) + if action is not None: + return action + + return ActionRest() diff --git a/adf_core_python/implement/tactics/default_tactics_fire_station.py b/adf_core_python/implement/tactics/default_tactics_fire_station.py index 09d4fd4..41f5ccc 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_station.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_station.py @@ -1,10 +1,15 @@ +from typing import cast + from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.module.complex.target_allocator import ( + TargetAllocator, +) from adf_core_python.core.component.tactics.tactics_fire_station import ( TacticsFireStation, ) @@ -21,7 +26,16 @@ def initialize( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + match scenario_info.get_mode(): + case Mode.NON_PRECOMPUTE: + self._allocator: TargetAllocator = cast( + TargetAllocator, + module_manager.get_module( + "DefaultTacticsFireStation.TargetAllocator", + "adf_core_python.implement.module.complex.DefaultFireTargetAllocator", + ), + ) + self.register_module(self._allocator) def resume( self, @@ -33,7 +47,7 @@ def resume( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + self.module_resume(precompute_data) def prepare( self, @@ -44,7 +58,7 @@ def prepare( precompute_data: PrecomputeData, develop_data: DevelopData, ) -> None: - raise NotImplementedError + self.module_prepare() def think( self, diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py index 3906725..ecd2134 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -1,11 +1,18 @@ +from typing import cast + +from rcrs_core.entities.policeForce import PoliceForceEntity + from adf_core_python.core.agent.action.action import Action +from adf_core_python.core.agent.action.common.action_rest import ActionRest from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.module.complex.road_detector import RoadDetector +from adf_core_python.core.component.module.complex.search import Search from adf_core_python.core.component.tactics.tactics_police_force import ( TacticsPoliceForce, ) @@ -22,7 +29,39 @@ def initialize( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + # world_info.index_class() + self._clear_distance = int( + scenario_info.get_config_value("clear.repair.distance", "null") + ) + + match scenario_info.get_mode(): + case Mode.NON_PRECOMPUTE: + self._search: Search = cast( + Search, + module_manager.get_module( + "DefaultTacticsPoliceForce.Search", + "adf_core_python.implement.module.complex.DefaultSearch", + ), + ) + self._road_detector: RoadDetector = cast( + RoadDetector, + module_manager.get_module( + "DefaultTacticsPoliceForce.RoadDetector", + "adf_core_python.implement.module.complex.DefaultRoadDetector", + ), + ) + self._action_ext_clear = module_manager.get_ext_action( + "DefaultTacticsPoliceForce.ExtActionClear", + "adf_core_python.implement.extaction.DefaultExtActionClear", + ) + self._action_ext_move = module_manager.get_ext_action( + "DefaultTacticsPoliceForce.ExtActionMove", + "adf_core_python.implement.extaction.DefaultExtActionMove", + ) + self.register_module(self._search) + self.register_module(self._road_detector) + self.register_action(self._action_ext_clear) + self.register_action(self._action_ext_move) def precompute( self, @@ -34,7 +73,7 @@ def precompute( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + self.module_precompute(precompute_data) def resume( self, @@ -46,7 +85,7 @@ def resume( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + self.module_resume(precompute_data) def prepare( self, @@ -57,7 +96,7 @@ def prepare( precompute_data: PrecomputeData, develop_data: DevelopData, ) -> None: - raise NotImplementedError + self.module_prepare() def think( self, @@ -69,4 +108,27 @@ def think( message_manager: MessageManager, develop_data: DevelopData, ) -> Action: - raise NotImplementedError + self.module_update_info(message_manager) + + agent: PoliceForceEntity = cast(PoliceForceEntity, agent_info.get_myself()) # noqa: F841 + entity_id = agent_info.get_entity_id() # noqa: F841 + + target_entity_id = self._road_detector.calculate().get_target_entity_id() + action = ( + self._action_ext_clear.set_target_entity_id(target_entity_id) + .calc() + .get_action() + ) + if action is not None: + return action + + target_entity_id = self._search.calculate().get_target_entity_id() + action = ( + self._action_ext_clear.set_target_entity_id(target_entity_id) + .calc() + .get_action() + ) + if action is not None: + return action + + return ActionRest() diff --git a/adf_core_python/implement/tactics/default_tactics_police_office.py b/adf_core_python/implement/tactics/default_tactics_police_office.py index 0264404..bf06a37 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_office.py +++ b/adf_core_python/implement/tactics/default_tactics_police_office.py @@ -1,10 +1,15 @@ +from typing import cast + from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.module.complex.target_allocator import ( + TargetAllocator, +) from adf_core_python.core.component.tactics.tactics_police_office import ( TacticsPoliceOffice, ) @@ -21,7 +26,16 @@ def initialize( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + match scenario_info.get_mode(): + case Mode.NON_PRECOMPUTE: + self._allocator: TargetAllocator = cast( + TargetAllocator, + module_manager.get_module( + "DefaultTacticsPoliceOffice.TargetAllocator", + "adf_core_python.implement.module.complex.DefaultPoliceTargetAllocator", + ), + ) + self.register_module(self._allocator) def resume( self, @@ -33,7 +47,7 @@ def resume( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + self.module_resume(precompute_data) def prepare( self, @@ -44,7 +58,7 @@ def prepare( precompute_data: PrecomputeData, develop_data: DevelopData, ) -> None: - raise NotImplementedError + self.module_prepare() def think( self, From 3182e341fd9fef19c74f5019851d88156af956d9 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 25 Sep 2024 16:25:03 +0900 Subject: [PATCH 050/249] Refactor WorldInfo class to include methods for retrieving entities and entity IDs --- adf_core_python/core/agent/info/world_info.py | 40 ++++++++++- poetry.lock | 70 +++++++++---------- 2 files changed, 74 insertions(+), 36 deletions(-) diff --git a/adf_core_python/core/agent/info/world_info.py b/adf_core_python/core/agent/info/world_info.py index 076fe0e..1ca617d 100644 --- a/adf_core_python/core/agent/info/world_info.py +++ b/adf_core_python/core/agent/info/world_info.py @@ -1,5 +1,6 @@ -from typing import Any +from typing import Any, Optional +from rcrs_core.entities.entity import Entity from rcrs_core.worldmodel.changeSet import ChangeSet from rcrs_core.worldmodel.entityID import EntityID from rcrs_core.worldmodel.worldmodel import WorldModel @@ -35,3 +36,40 @@ def set_change_set(self, change_set: ChangeSet) -> None: Change set """ self._change_set = change_set + + def get_entity(self, entity_id: EntityID) -> Optional[Entity]: + """ + Get the entity + + Parameters + ---------- + entity_id : EntityID + Entity ID + + Returns + ------- + Optional[Entity] + Entity + """ + return self._world_model.get_entity(entity_id) + + def get_entity_ids_of_type(self, entity_type: type[Entity]) -> list[EntityID]: + """ + Get the entity IDs of the specified type + + Parameters + ---------- + entity_type : type[Entity] + Entity type + + Returns + ------- + list[EntityID] + Entity IDs + """ + entity_ids: list[EntityID] = [] + for entity in self._world_model.get_entities(): + if isinstance(entity, entity_type): + entity_ids.append(entity.get_id()) + + return entity_ids diff --git a/poetry.lock b/poetry.lock index 733bc53..b074efb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -35,38 +35,38 @@ files = [ [[package]] name = "mypy" -version = "1.11.1" +version = "1.11.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"}, - {file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"}, - {file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"}, - {file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"}, - {file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"}, - {file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"}, - {file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"}, - {file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"}, - {file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"}, - {file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"}, - {file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"}, - {file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"}, - {file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"}, - {file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"}, - {file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"}, - {file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"}, - {file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"}, - {file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"}, - {file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"}, - {file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"}, - {file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"}, - {file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"}, - {file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"}, - {file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"}, - {file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"}, - {file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"}, - {file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, + {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, + {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, + {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, + {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, + {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, + {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, + {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, + {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, + {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, + {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, + {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, + {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, + {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, + {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, + {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, + {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, + {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, ] [package.dependencies] @@ -177,13 +177,13 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] [[package]] name = "pytest" -version = "8.3.2" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, - {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -274,7 +274,7 @@ rtree = "*" type = "git" url = "https://github.com/adf-python/rcrs-core-python" reference = "HEAD" -resolved_reference = "6771354084ce60b3dd96a77f509ad1e0314ac886" +resolved_reference = "7b0d4ef8d6c1205c00f9e197c5833a3939f482b2" [[package]] name = "rtree" @@ -362,13 +362,13 @@ files = [ [[package]] name = "types-pyyaml" -version = "6.0.12.20240808" +version = "6.0.12.20240917" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.8" files = [ - {file = "types-PyYAML-6.0.12.20240808.tar.gz", hash = "sha256:b8f76ddbd7f65440a8bda5526a9607e4c7a322dc2f8e1a8c405644f9a6f4b9af"}, - {file = "types_PyYAML-6.0.12.20240808-py3-none-any.whl", hash = "sha256:deda34c5c655265fc517b546c902aa6eed2ef8d3e921e4765fe606fe2afe8d35"}, + {file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"}, + {file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"}, ] [[package]] From 5b70068175cf0efc95e618eac71ecdaef58b6ed9 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 25 Sep 2024 16:25:07 +0900 Subject: [PATCH 051/249] feat: Implement DefaultExtendActionMove class for extending action move functionality --- .../default_extend_action_move.py | 106 ++++++++ .../default_extend_action_transport.py | 254 ++++++++++++++++++ 2 files changed, 360 insertions(+) create mode 100644 adf_core_python/implement/extend_action/default_extend_action_move.py create mode 100644 adf_core_python/implement/extend_action/default_extend_action_transport.py diff --git a/adf_core_python/implement/extend_action/default_extend_action_move.py b/adf_core_python/implement/extend_action/default_extend_action_move.py new file mode 100644 index 0000000..a8df764 --- /dev/null +++ b/adf_core_python/implement/extend_action/default_extend_action_move.py @@ -0,0 +1,106 @@ +from typing import Optional, cast + +from rcrs_core.entities.area import Area +from rcrs_core.entities.blockade import Blockade +from rcrs_core.entities.entity import Entity +from rcrs_core.entities.human import Human +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.common.action_move import ActionMove +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.extaction.ext_action import ExtAction +from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning + + +class DefaultExtendActionMove(ExtAction): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._target_entity_id: Optional[EntityID] = None + self._threshold_to_rest: int = develop_data.get_value("threshold_to_rest", 100) + + match self.scenario_info.get_mode(): + case Mode.NON_PRECOMPUTE: + self._path_planning: PathPlanning = cast( + PathPlanning, + self.module_manager.get_module( + "DefaultExtendActionMove.PathPlanning", + "adf_core_python.implement.module.astar_path_planning.AStarPathPlanning", + ), + ) + case Mode.PRECOMPUTATION: + pass + case Mode.PRECOMPUTED: + pass + + def precompute(self, precompute_data: PrecomputeData) -> ExtAction: + super().precompute(precompute_data) + if self.get_count_precompute() > 1: + return self + self._path_planning.precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> ExtAction: + super().resume(precompute_data) + if self.get_count_resume() > 1: + return self + self._path_planning.resume(precompute_data) + return self + + def prepare(self) -> ExtAction: + super().prepare() + if self.get_count_prepare() > 1: + return self + self._path_planning.prepare() + return self + + def update_info(self, message_manager: MessageManager) -> ExtAction: + super().update_info(message_manager) + if self.get_count_update_info() > 1: + return self + self._path_planning.update_info(message_manager) + return self + + def set_target_entity_id(self, target_entity_id: EntityID) -> ExtAction: + entity: Optional[Entity] = self.world_info.get_entity(target_entity_id) + self._target_entity_id = None + + if entity is None: + return self + + if isinstance(entity, Blockade): + entity = self.world_info.get_entity(cast(Blockade, entity).get_position()) + elif isinstance(entity, Human): + entity = entity.get_position() + + if entity is None and isinstance(entity, Area): + self._target_entity_id = None + + return self + + def calc(self) -> ExtAction: + self._result = None + agent: Human = cast(Human, self.agent_info.get_myself()) + + path: list[EntityID] = self._path_planning.get_path( + agent.get_position(), self._target_entity_id + ) + + if path is not None or len(path) != 0: + self.result = ActionMove(path) + + return self diff --git a/adf_core_python/implement/extend_action/default_extend_action_transport.py b/adf_core_python/implement/extend_action/default_extend_action_transport.py new file mode 100644 index 0000000..f770b48 --- /dev/null +++ b/adf_core_python/implement/extend_action/default_extend_action_transport.py @@ -0,0 +1,254 @@ +from typing import Optional, Union, cast + +from rcrs_core.entities.ambulanceTeam import AmbulanceTeamEntity +from rcrs_core.entities.area import Area +from rcrs_core.entities.civilian import Civilian +from rcrs_core.entities.entity import Entity +from rcrs_core.entities.human import Human +from rcrs_core.entities.refuge import Refuge +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.ambulance.action_load import ActionLoad +from adf_core_python.core.agent.action.ambulance.action_unload import ActionUnload +from adf_core_python.core.agent.action.common.action_move import ActionMove +from adf_core_python.core.agent.action.common.action_rest import ActionRest +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.extaction.ext_action import ExtAction +from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning + + +class DefaultExtendActionTransport(ExtAction): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._target_entity_id: Optional[EntityID] = None + self._threshold_to_rest: int = develop_data.get_value("threshold_to_rest", 100) + + match self.scenario_info.get_mode(): + case Mode.NON_PRECOMPUTE: + self._path_planning: PathPlanning = cast( + PathPlanning, + self.module_manager.get_module( + "DefaultExtendActionMove.PathPlanning", + "adf_core_python.implement.module.astar_path_planning.AStarPathPlanning", + ), + ) + case Mode.PRECOMPUTATION: + pass + case Mode.PRECOMPUTED: + pass + + def precompute(self, precompute_data: PrecomputeData) -> ExtAction: + super().precompute(precompute_data) + if self.get_count_precompute() > 1: + return self + self._path_planning.precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> ExtAction: + super().resume(precompute_data) + if self.get_count_resume() > 1: + return self + self._path_planning.resume(precompute_data) + return self + + def prepare(self) -> ExtAction: + super().prepare() + if self.get_count_prepare() > 1: + return self + self._path_planning.prepare() + return self + + def update_info(self, message_manager: MessageManager) -> ExtAction: + super().update_info(message_manager) + if self.get_count_update_info() > 1: + return self + self._path_planning.update_info(message_manager) + return self + + def set_target_entity_id(self, target_entity_id: EntityID) -> ExtAction: + entity: Optional[Entity] = self.world_info.get_world_model().get_entity( + target_entity_id + ) + self._target_entity_id = None + + if entity is None: + return self + + if isinstance(entity, Human) or isinstance(entity, Area): + self._target_entity_id = target_entity_id + + return self + + def calc(self) -> ExtAction: + self._result = None + agent: AmbulanceTeamEntity = cast( + AmbulanceTeamEntity, self.agent_info.get_myself() + ) + transport_human: Human = self.agent_info.some_one_on_board() + if transport_human is not None: + self.result = self.calc_unload( + agent, self._path_planning, transport_human, self._target_entity_id + ) + if self.result is not None: + return self + + if self._target_entity_id is not None: + self.result = self.calc_rescue( + agent, self._path_planning, self._target_entity_id + ) + + return self + + def calc_rescue( + self, + agent: AmbulanceTeamEntity, + path_planning: PathPlanning, + target_id: EntityID, + ) -> Optional[Union[ActionMove, ActionLoad]]: + target_entity = self.world_info.get_entity(target_id) + if target_entity is None: + return None + + agent_position = agent.get_position() + if isinstance(target_entity, Human): + human = target_entity + if human.get_position() is None: + return None + if human.get_hp() is None or human.get_hp() == 0: + return None + + target_position = human.get_position() + if agent_position == target_position: + if isinstance(human, Civilian) and ( + human.get_buriedness() is not None and human.get_buriedness() > 0 + ): + return ActionLoad(human.get_id()) + else: + path = path_planning.get_path(agent_position, target_position) + if path is not None and len(path) > 0: + return ActionMove(path) + return None + + if isinstance(target_entity, Area): + path = path_planning.get_path(agent_position, target_entity.get_id()) + if path is not None and len(path) > 0: + return ActionMove(path) + + return None + + def calc_unload( + self, + agent: AmbulanceTeamEntity, + path_planning: PathPlanning, + transport_human: Optional[Human], + target_id: Optional[EntityID], + ) -> Optional[ActionMove | ActionUnload | ActionRest]: + if transport_human is None: + return None + + if not transport_human.get_hp() or transport_human.get_hp() == 0: + return ActionUnload() + + agent_position = agent.get_position() + if ( + target_id is None + or transport_human.get_id().get_value() == target_id.get_value() + ): + position = self.world_info.get_entity(agent_position) + if position is None: + return None + + if isinstance(position, Refuge): + return ActionUnload() + else: + path = path_planning.get_path( + agent_position, self.world_info.get_entity_ids_of_type(Refuge) + ) + if path is not None and len(path) > 0: + return ActionMove(path) + + if target_id is None: + return None + + target_entity = self.world_info.get_entity(target_id) + + if isinstance(target_entity, Human): + human = cast(Human, target_entity) + if human.get_position() is not None: + return self.calc_refuge_action( + agent, path_planning, [human.get_position()], True + ) + path = self.get_nearest_refuge_path(agent, path_planning) + if path is not None and len(path) > 0: + return ActionMove(path) + + return None + + def calc_refuge_action( + self, + human: Human, + path_planning: PathPlanning, + target_entity_id: EntityID, + is_unload: bool, + ) -> Optional[ActionMove | ActionUnload | ActionRest]: + position = human.get_position() + refuges = self.world_info.get_entity_ids_of_type(Refuge) + size = len(refuges) + + if position in refuges: + return ActionUnload() if is_unload else ActionRest() + + first_result = None + while len(refuges) > 0: + path = path_planning.get_path(position, refuges[0]) + + if path is not None and len(path) > 0: + if first_result is None: + first_result = path.copy() + + refuge_id = path[-1] + from_refuge_to_target = path_planning.get_path( + refuge_id, target_entity_id + ) + + if from_refuge_to_target is not None and len(from_refuge_to_target) > 0: + return ActionMove(path) + + refuges.remove(refuge_id) + if size == len(refuges): + break + size = len(refuges) + else: + break + + return ActionMove(first_result) if first_result is not None else None + + def get_nearest_refuge_path( + self, human: Human, path_planning: PathPlanning + ) -> list[EntityID]: + position = human.get_position() + refuges = self.world_info.get_entity_ids_of_type(Refuge) + nearest_path = None + + for refuge_id in refuges: + path: list[EntityID] = path_planning.get_path(position, refuge_id) + if len(path) > 0: + if nearest_path is None or len(path) < len(nearest_path): + nearest_path = path + + return nearest_path if nearest_path is not None else [] From ee1787df5704f647aebb92a13e7490ed1bf25047 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 25 Sep 2024 16:49:58 +0900 Subject: [PATCH 052/249] fix: fix wrong type type checking import statement --- .../core/component/module/complex/human_detector.py | 7 ++++--- .../core/component/module/complex/road_detector.py | 7 ++++--- adf_core_python/core/component/module/complex/search.py | 7 ++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/adf_core_python/core/component/module/complex/human_detector.py b/adf_core_python/core/component/module/complex/human_detector.py index 623bd69..10a31e2 100644 --- a/adf_core_python/core/component/module/complex/human_detector.py +++ b/adf_core_python/core/component/module/complex/human_detector.py @@ -5,6 +5,10 @@ from rcrs_core.entities.human import Human +from adf_core_python.core.component.module.complex.target_detector import ( + TargetDetector, +) + if TYPE_CHECKING: from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -12,9 +16,6 @@ from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.component.module.complex.target_detector import ( - TargetDetector, - ) class HumanDetector(TargetDetector[Human]): diff --git a/adf_core_python/core/component/module/complex/road_detector.py b/adf_core_python/core/component/module/complex/road_detector.py index 940fbfb..08b552d 100644 --- a/adf_core_python/core/component/module/complex/road_detector.py +++ b/adf_core_python/core/component/module/complex/road_detector.py @@ -5,6 +5,10 @@ from rcrs_core.entities.road import Road +from adf_core_python.core.component.module.complex.target_detector import ( + TargetDetector, +) + if TYPE_CHECKING: from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -12,9 +16,6 @@ from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.component.module.complex.target_detector import ( - TargetDetector, - ) class RoadDetector(TargetDetector[Road]): diff --git a/adf_core_python/core/component/module/complex/search.py b/adf_core_python/core/component/module/complex/search.py index 604cd69..7715831 100644 --- a/adf_core_python/core/component/module/complex/search.py +++ b/adf_core_python/core/component/module/complex/search.py @@ -5,6 +5,10 @@ from rcrs_core.entities.area import Area +from adf_core_python.core.component.module.complex.target_detector import ( + TargetDetector, +) + if TYPE_CHECKING: from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -12,9 +16,6 @@ from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.component.module.complex.target_detector import ( - TargetDetector, - ) class Search(TargetDetector[Area]): From 3c3d8121dd03945438085567cedf6e7c2b48fd0c Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 25 Sep 2024 17:06:44 +0900 Subject: [PATCH 053/249] feat: Update main.py to use AgentLauncher for launching agents --- adf_core_python/main.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/adf_core_python/main.py b/adf_core_python/main.py index d4f477a..c988075 100644 --- a/adf_core_python/main.py +++ b/adf_core_python/main.py @@ -1,5 +1,9 @@ import argparse +from adf_core_python.core.config.config import Config +from adf_core_python.core.launcher.agent_launcher import AgentLauncher +from adf_core_python.core.launcher.config_key import ConfigKey + class Main: def __init__(self) -> None: @@ -55,7 +59,23 @@ def __init__(self) -> None: ) args = parser.parse_args() print(args) + self.config = Config() + self.config.set_value(ConfigKey.KEY_KERNEL_HOST, args.host) + self.config.set_value(ConfigKey.KEY_KERNEL_PORT, args.port) + self.config.set_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, args.ambulance) + self.config.set_value(ConfigKey.KEY_FIRE_STATION_COUNT, args.firebrigade) + self.config.set_value(ConfigKey.KEY_POLICE_OFFICE_COUNT, args.policeforce) + self.config.set_value(ConfigKey.KEY_PRECOMPUTE, args.precompute) + self.config.set_value(ConfigKey.KEY_DEBUG_FLAG, args.verbose) + + def launch(self): + agent_launcher: AgentLauncher = AgentLauncher( + self.config, + ) + agent_launcher.init_connector() + agent_launcher.launch() if __name__ == "__main__": main = Main() + main.launch() From c6748ec2d7f7bf4822081b073185ef11800f708c Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 25 Sep 2024 17:11:18 +0900 Subject: [PATCH 054/249] feat: Update launch method in main.py to use AgentLauncher --- adf_core_python/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adf_core_python/main.py b/adf_core_python/main.py index c988075..0e6d588 100644 --- a/adf_core_python/main.py +++ b/adf_core_python/main.py @@ -68,7 +68,7 @@ def __init__(self) -> None: self.config.set_value(ConfigKey.KEY_PRECOMPUTE, args.precompute) self.config.set_value(ConfigKey.KEY_DEBUG_FLAG, args.verbose) - def launch(self): + def launch(self) -> None: agent_launcher: AgentLauncher = AgentLauncher( self.config, ) From 54457c0acd0445bb65e17644cae5aa971245ee98 Mon Sep 17 00:00:00 2001 From: harrki Date: Mon, 30 Sep 2024 18:12:50 +0900 Subject: [PATCH 055/249] feat: Update .gitignore to ignore .idea --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 4939007..9a270b3 100644 --- a/.gitignore +++ b/.gitignore @@ -166,3 +166,6 @@ cython_debug/ # vscode .vscode/ + +# pycharm +.idea/ \ No newline at end of file From 045a889f79c128706e37ed4d970fed470d5ba0d9 Mon Sep 17 00:00:00 2001 From: harrki Date: Mon, 30 Sep 2024 18:14:35 +0900 Subject: [PATCH 056/249] feat: Add DefaultRoadDetector class --- adf_core_python/core/agent/info/world_info.py | 21 +++ .../module/algorithm/path_planning.py | 8 + .../implement/module/complex/__init__.py | 0 .../module/complex/default_road_detector.py | 153 ++++++++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 adf_core_python/implement/module/complex/__init__.py create mode 100644 adf_core_python/implement/module/complex/default_road_detector.py diff --git a/adf_core_python/core/agent/info/world_info.py b/adf_core_python/core/agent/info/world_info.py index 1ca617d..6feb8cb 100644 --- a/adf_core_python/core/agent/info/world_info.py +++ b/adf_core_python/core/agent/info/world_info.py @@ -73,3 +73,24 @@ def get_entity_ids_of_type(self, entity_type: type[Entity]) -> list[EntityID]: entity_ids.append(entity.get_id()) return entity_ids + + def get_entities_of_type(self, entity_type: type[Entity]) -> list[Entity]: + """ + Get the entities of the specified type + + Parameters + ---------- + entity_type : type[Entity] + Entity type + + Returns + ------- + list[Entity] + Entities + """ + entities: list[Entity] = [] + for entity in self._world_model.get_entities(): + if isinstance(entity, entity_type): + entities.append(entity) + + return entities diff --git a/adf_core_python/core/component/module/algorithm/path_planning.py b/adf_core_python/core/component/module/algorithm/path_planning.py index b741862..839b007 100644 --- a/adf_core_python/core/component/module/algorithm/path_planning.py +++ b/adf_core_python/core/component/module/algorithm/path_planning.py @@ -40,6 +40,14 @@ def get_path( def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: pass + @abstractmethod + def set_from(self, from_entity_id: EntityID) -> PathPlanning: + pass + + @abstractmethod + def set_destination(self, destination_entity_ids: list[EntityID]) -> PathPlanning: + pass + def precompute(self, precompute_data: PrecomputeData) -> PathPlanning: super().precompute(precompute_data) return self diff --git a/adf_core_python/implement/module/complex/__init__.py b/adf_core_python/implement/module/complex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/implement/module/complex/default_road_detector.py b/adf_core_python/implement/module/complex/default_road_detector.py new file mode 100644 index 0000000..86dd473 --- /dev/null +++ b/adf_core_python/implement/module/complex/default_road_detector.py @@ -0,0 +1,153 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, cast + +from rcrs_core.entities.refuge import Refuge +from rcrs_core.entities.building import Building +from rcrs_core.entities.gassStation import GasStation +from rcrs_core.entities.road import Road +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.module.complex.road_detector import RoadDetector +from adf_core_python.core.component.module.complex.target_detector import TargetDetector + +if TYPE_CHECKING: + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.component.module.algorithm.path_planning import ( + PathPlanning, + ) + from adf_core_python.core.component.module.complex.road_detector import RoadDetector + + +class DefaultRoadDetector(RoadDetector): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + match scenario_info.get_mode(): + case Mode.NON_PRECOMPUTE: + self._path_planning: PathPlanning = cast( + PathPlanning, + self.module_manager.get_module( + "DefaultRoadDetector.PathPlanning", + "adf_core_python.implement.module.algorithm.DijkstraPathPlanning", + ), + ) + + self.register_sub_module(self._path_planning) + self._result = None + + def precompute(self, precompute_data: PrecomputeData) -> RoadDetector: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> RoadDetector: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + + self._target_areas = set() + entities = ( + self._world_info.get_entities_of_type(Refuge) + + self._world_info.get_entities_of_type(Building) + + self._world_info.get_entities_of_type(GasStation) + ) + for entity in entities: + if not isinstance(entity, Building): + continue + for entity_id in entity.get_neighbours(): + neighbour = self._world_info.get_entity(entity_id) + if isinstance(neighbour, Road): + self._target_areas.add(entity_id) + + self._priority_roads = set() + for entity in self._world_info.get_entities_of_type(Refuge): + if not isinstance(entity, Building): + continue + for entity_id in entity.get_neighbours(): + if isinstance(neighbour, Road): + self._priority_roads.add(entity_id) + + return self + + def prepare(self) -> RoadDetector: + super().prepare() + if self.get_count_prepare() >= 2: + return self + + self._target_areas = set() + entities = ( + self._world_info.get_entities_of_type(Refuge) + + self._world_info.get_entities_of_type(Building) + + self._world_info.get_entities_of_type(GasStation) + ) + for entity in entities: + building: Building = cast(Building, entity) + for entity_id in building.get_neighbours(): + neighbour = self._world_info.get_entity(entity_id) + if isinstance(neighbour, Road): + self._target_areas.add(entity_id) + + self._priority_roads = set() + for entity in self._world_info.get_entities_of_type(Refuge): + refuge: Refuge = cast(Refuge, entity) + for entity_id in refuge.get_neighbours(): + if isinstance(neighbour, Road): + self._priority_roads.add(entity_id) + + return self + + def update_info(self, message_manager: MessageManager) -> RoadDetector: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self + + if self._result is not None: + if self._agent_info.get_position_entity_id == self._result: + entity = self._world_info.get_entity(self._result) + if isinstance(entity, Building): + self._result = None + elif isinstance(entity, Road): + road: Road = cast(Road, entity) + if road.get_blockades() == []: + self._target_areas.remove(self._result) + self._result = None + + return self + + def calc(self) -> RoadDetector: + if self._result is None: + position_entity_id: EntityID = self._agent_info.get_position_entity_id() + if position_entity_id in self._target_areas: + self._result = position_entity_id + return self + remove_list = [] + for entity_id in self._priority_roads: + if entity_id not in self._target_areas: + remove_list.append(entity_id) + + self._priority_roads = self._priority_roads - set(remove_list) + if len(self._priority_roads) > 0: + self._path_planning.set_from(position_entity_id) + self._path_planning.set_destination(list(self._target_areas)) + path: list[EntityID] = self._path_planning.calculate().get_path() + if path is not None and len(path) > 0: + self._result = path[-1] + + return self + + def get_target(self) -> EntityID: + return self._result From 73e0bbbf82c2d391c7dac153602a8b6091454979 Mon Sep 17 00:00:00 2001 From: harrki Date: Mon, 30 Sep 2024 18:43:24 +0900 Subject: [PATCH 057/249] fix: DefaultRoadDetector class and dependencies --- adf_core_python/core/agent/info/world_info.py | 2 +- .../core/component/module/algorithm/path_planning.py | 4 ++++ .../implement/module/complex/default_road_detector.py | 5 ++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/adf_core_python/core/agent/info/world_info.py b/adf_core_python/core/agent/info/world_info.py index 6feb8cb..46ec4bd 100644 --- a/adf_core_python/core/agent/info/world_info.py +++ b/adf_core_python/core/agent/info/world_info.py @@ -73,7 +73,7 @@ def get_entity_ids_of_type(self, entity_type: type[Entity]) -> list[EntityID]: entity_ids.append(entity.get_id()) return entity_ids - + def get_entities_of_type(self, entity_type: type[Entity]) -> list[Entity]: """ Get the entities of the specified type diff --git a/adf_core_python/core/component/module/algorithm/path_planning.py b/adf_core_python/core/component/module/algorithm/path_planning.py index 839b007..191631e 100644 --- a/adf_core_python/core/component/module/algorithm/path_planning.py +++ b/adf_core_python/core/component/module/algorithm/path_planning.py @@ -30,6 +30,10 @@ def __init__( agent_info, world_info, scenario_info, module_manager, develop_data ) + @abstractmethod + def get_result(self) -> list[EntityID]: + pass + @abstractmethod def get_path( self, from_entity_id: EntityID, to_entity_id: EntityID diff --git a/adf_core_python/implement/module/complex/default_road_detector.py b/adf_core_python/implement/module/complex/default_road_detector.py index 86dd473..3457b72 100644 --- a/adf_core_python/implement/module/complex/default_road_detector.py +++ b/adf_core_python/implement/module/complex/default_road_detector.py @@ -11,7 +11,6 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.module.complex.road_detector import RoadDetector -from adf_core_python.core.component.module.complex.target_detector import TargetDetector if TYPE_CHECKING: from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -41,7 +40,7 @@ def __init__( case Mode.NON_PRECOMPUTE: self._path_planning: PathPlanning = cast( PathPlanning, - self.module_manager.get_module( + module_manager.get_module( "DefaultRoadDetector.PathPlanning", "adf_core_python.implement.module.algorithm.DijkstraPathPlanning", ), @@ -143,7 +142,7 @@ def calc(self) -> RoadDetector: if len(self._priority_roads) > 0: self._path_planning.set_from(position_entity_id) self._path_planning.set_destination(list(self._target_areas)) - path: list[EntityID] = self._path_planning.calculate().get_path() + path: list[EntityID] = self._path_planning.calculate().get_result() if path is not None and len(path) > 0: self._result = path[-1] From a8994415fcb41797896666354bf812b73c928338 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 30 Sep 2024 21:47:42 +0900 Subject: [PATCH 058/249] feat: Add scikit-learn dependency to pyproject.toml --- poetry.lock | 181 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 181 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index b074efb..2f0e6c9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -22,6 +22,17 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "joblib" +version = "1.4.2" +description = "Lightweight pipelining with Python functions" +optional = false +python-versions = ">=3.8" +files = [ + {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, + {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, +] + [[package]] name = "mslex" version = "1.2.0" @@ -90,6 +101,68 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "numpy" +version = "2.1.1" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "numpy-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8a0e34993b510fc19b9a2ce7f31cb8e94ecf6e924a40c0c9dd4f62d0aac47d9"}, + {file = "numpy-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dd86dfaf7c900c0bbdcb8b16e2f6ddf1eb1fe39c6c8cca6e94844ed3152a8fd"}, + {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:5889dd24f03ca5a5b1e8a90a33b5a0846d8977565e4ae003a63d22ecddf6782f"}, + {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:59ca673ad11d4b84ceb385290ed0ebe60266e356641428c845b39cd9df6713ab"}, + {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ce49a34c44b6de5241f0b38b07e44c1b2dcacd9e36c30f9c2fcb1bb5135db7"}, + {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913cc1d311060b1d409e609947fa1b9753701dac96e6581b58afc36b7ee35af6"}, + {file = "numpy-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:caf5d284ddea7462c32b8d4a6b8af030b6c9fd5332afb70e7414d7fdded4bfd0"}, + {file = "numpy-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:57eb525e7c2a8fdee02d731f647146ff54ea8c973364f3b850069ffb42799647"}, + {file = "numpy-2.1.1-cp310-cp310-win32.whl", hash = "sha256:9a8e06c7a980869ea67bbf551283bbed2856915f0a792dc32dd0f9dd2fb56728"}, + {file = "numpy-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:d10c39947a2d351d6d466b4ae83dad4c37cd6c3cdd6d5d0fa797da56f710a6ae"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d07841fd284718feffe7dd17a63a2e6c78679b2d386d3e82f44f0108c905550"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b5613cfeb1adfe791e8e681128f5f49f22f3fcaa942255a6124d58ca59d9528f"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0b8cc2715a84b7c3b161f9ebbd942740aaed913584cae9cdc7f8ad5ad41943d0"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b49742cdb85f1f81e4dc1b39dcf328244f4d8d1ded95dea725b316bd2cf18c95"}, + {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8d5f8a8e3bc87334f025194c6193e408903d21ebaeb10952264943a985066ca"}, + {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d51fc141ddbe3f919e91a096ec739f49d686df8af254b2053ba21a910ae518bf"}, + {file = "numpy-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:98ce7fb5b8063cfdd86596b9c762bf2b5e35a2cdd7e967494ab78a1fa7f8b86e"}, + {file = "numpy-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:24c2ad697bd8593887b019817ddd9974a7f429c14a5469d7fad413f28340a6d2"}, + {file = "numpy-2.1.1-cp311-cp311-win32.whl", hash = "sha256:397bc5ce62d3fb73f304bec332171535c187e0643e176a6e9421a6e3eacef06d"}, + {file = "numpy-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:ae8ce252404cdd4de56dcfce8b11eac3c594a9c16c231d081fb705cf23bd4d9e"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c803b7934a7f59563db459292e6aa078bb38b7ab1446ca38dd138646a38203e"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6435c48250c12f001920f0751fe50c0348f5f240852cfddc5e2f97e007544cbe"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3269c9eb8745e8d975980b3a7411a98976824e1fdef11f0aacf76147f662b15f"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fac6e277a41163d27dfab5f4ec1f7a83fac94e170665a4a50191b545721c6521"}, + {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd8f556cdc8cfe35e70efb92463082b7f43dd7e547eb071ffc36abc0ca4699b"}, + {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b9cd92c8f8e7b313b80e93cedc12c0112088541dcedd9197b5dee3738c1201"}, + {file = "numpy-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:afd9c680df4de71cd58582b51e88a61feed4abcc7530bcd3d48483f20fc76f2a"}, + {file = "numpy-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8661c94e3aad18e1ea17a11f60f843a4933ccaf1a25a7c6a9182af70610b2313"}, + {file = "numpy-2.1.1-cp312-cp312-win32.whl", hash = "sha256:950802d17a33c07cba7fd7c3dcfa7d64705509206be1606f196d179e539111ed"}, + {file = "numpy-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:3fc5eabfc720db95d68e6646e88f8b399bfedd235994016351b1d9e062c4b270"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:046356b19d7ad1890c751b99acad5e82dc4a02232013bd9a9a712fddf8eb60f5"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6e5a9cb2be39350ae6c8f79410744e80154df658d5bea06e06e0ac5bb75480d5"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d4c57b68c8ef5e1ebf47238e99bf27657511ec3f071c465f6b1bccbef12d4136"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:8ae0fd135e0b157365ac7cc31fff27f07a5572bdfc38f9c2d43b2aff416cc8b0"}, + {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981707f6b31b59c0c24bcda52e5605f9701cb46da4b86c2e8023656ad3e833cb"}, + {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ca4b53e1e0b279142113b8c5eb7d7a877e967c306edc34f3b58e9be12fda8df"}, + {file = "numpy-2.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e097507396c0be4e547ff15b13dc3866f45f3680f789c1a1301b07dadd3fbc78"}, + {file = "numpy-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7506387e191fe8cdb267f912469a3cccc538ab108471291636a96a54e599556"}, + {file = "numpy-2.1.1-cp313-cp313-win32.whl", hash = "sha256:251105b7c42abe40e3a689881e1793370cc9724ad50d64b30b358bbb3a97553b"}, + {file = "numpy-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:f212d4f46b67ff604d11fff7cc62d36b3e8714edf68e44e9760e19be38c03eb0"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:920b0911bb2e4414c50e55bd658baeb78281a47feeb064ab40c2b66ecba85553"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bab7c09454460a487e631ffc0c42057e3d8f2a9ddccd1e60c7bb8ed774992480"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:cea427d1350f3fd0d2818ce7350095c1a2ee33e30961d2f0fef48576ddbbe90f"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:e30356d530528a42eeba51420ae8bf6c6c09559051887196599d96ee5f536468"}, + {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8dfa9e94fc127c40979c3eacbae1e61fda4fe71d84869cc129e2721973231ef"}, + {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910b47a6d0635ec1bd53b88f86120a52bf56dcc27b51f18c7b4a2e2224c29f0f"}, + {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:13cc11c00000848702322af4de0147ced365c81d66053a67c2e962a485b3717c"}, + {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53e27293b3a2b661c03f79aa51c3987492bd4641ef933e366e0f9f6c9bf257ec"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7be6a07520b88214ea85d8ac8b7d6d8a1839b0b5cb87412ac9f49fa934eb15d5"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:52ac2e48f5ad847cd43c4755520a2317f3380213493b9d8a4c5e37f3b87df504"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a95ca3560a6058d6ea91d4629a83a897ee27c00630aed9d933dff191f170cd"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:99f4a9ee60eed1385a86e82288971a51e71df052ed0b2900ed30bc840c0f2e39"}, + {file = "numpy-2.1.1.tar.gz", hash = "sha256:d0cf7d55b1051387807405b3898efafa862997b4cba8aa5dbe657be794afeafd"}, +] + [[package]] name = "packaging" version = "24.1" @@ -321,6 +394,101 @@ files = [ {file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"}, ] +[[package]] +name = "scikit-learn" +version = "1.5.2" +description = "A set of python modules for machine learning and data mining" +optional = false +python-versions = ">=3.9" +files = [ + {file = "scikit_learn-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:299406827fb9a4f862626d0fe6c122f5f87f8910b86fe5daa4c32dcd742139b6"}, + {file = "scikit_learn-1.5.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:2d4cad1119c77930b235579ad0dc25e65c917e756fe80cab96aa3b9428bd3fb0"}, + {file = "scikit_learn-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c412ccc2ad9bf3755915e3908e677b367ebc8d010acbb3f182814524f2e5540"}, + {file = "scikit_learn-1.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a686885a4b3818d9e62904d91b57fa757fc2bed3e465c8b177be652f4dd37c8"}, + {file = "scikit_learn-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:c15b1ca23d7c5f33cc2cb0a0d6aaacf893792271cddff0edbd6a40e8319bc113"}, + {file = "scikit_learn-1.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03b6158efa3faaf1feea3faa884c840ebd61b6484167c711548fce208ea09445"}, + {file = "scikit_learn-1.5.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1ff45e26928d3b4eb767a8f14a9a6efbf1cbff7c05d1fb0f95f211a89fd4f5de"}, + {file = "scikit_learn-1.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f763897fe92d0e903aa4847b0aec0e68cadfff77e8a0687cabd946c89d17e675"}, + {file = "scikit_learn-1.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8b0ccd4a902836493e026c03256e8b206656f91fbcc4fde28c57a5b752561f1"}, + {file = "scikit_learn-1.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6c16d84a0d45e4894832b3c4d0bf73050939e21b99b01b6fd59cbb0cf39163b6"}, + {file = "scikit_learn-1.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f932a02c3f4956dfb981391ab24bda1dbd90fe3d628e4b42caef3e041c67707a"}, + {file = "scikit_learn-1.5.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3b923d119d65b7bd555c73be5423bf06c0105678ce7e1f558cb4b40b0a5502b1"}, + {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f60021ec1574e56632be2a36b946f8143bf4e5e6af4a06d85281adc22938e0dd"}, + {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:394397841449853c2290a32050382edaec3da89e35b3e03d6cc966aebc6a8ae6"}, + {file = "scikit_learn-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:57cc1786cfd6bd118220a92ede80270132aa353647684efa385a74244a41e3b1"}, + {file = "scikit_learn-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:757c7d514ddb00ae249832fe87100d9c73c6ea91423802872d9e74970a0e40b9"}, + {file = "scikit_learn-1.5.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:52788f48b5d8bca5c0736c175fa6bdaab2ef00a8f536cda698db61bd89c551c1"}, + {file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:643964678f4b5fbdc95cbf8aec638acc7aa70f5f79ee2cdad1eec3df4ba6ead8"}, + {file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca64b3089a6d9b9363cd3546f8978229dcbb737aceb2c12144ee3f70f95684b7"}, + {file = "scikit_learn-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:3bed4909ba187aca80580fe2ef370d9180dcf18e621a27c4cf2ef10d279a7efe"}, + {file = "scikit_learn-1.5.2.tar.gz", hash = "sha256:b4237ed7b3fdd0a4882792e68ef2545d5baa50aca3bb45aa7df468138ad8f94d"}, +] + +[package.dependencies] +joblib = ">=1.2.0" +numpy = ">=1.19.5" +scipy = ">=1.6.0" +threadpoolctl = ">=3.1.0" + +[package.extras] +benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"] +build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"] +docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-design (>=0.6.0)", "sphinx-gallery (>=0.16.0)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)"] +examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] +install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"] +maintenance = ["conda-lock (==2.5.6)"] +tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.2.1)", "scikit-image (>=0.17.2)"] + +[[package]] +name = "scipy" +version = "1.14.1" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"}, + {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"}, + {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"}, + {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"}, + {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"}, + {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"}, + {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"}, + {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"}, + {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"}, + {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.3" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + [[package]] name = "taskipy" version = "1.13.0" @@ -338,6 +506,17 @@ mslex = {version = ">=1.1.0,<2.0.0", markers = "sys_platform == \"win32\""} psutil = ">=5.7.2,<6.0.0" tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +[[package]] +name = "threadpoolctl" +version = "3.5.0" +description = "threadpoolctl" +optional = false +python-versions = ">=3.8" +files = [ + {file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"}, + {file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"}, +] + [[package]] name = "tomli" version = "2.0.1" @@ -385,4 +564,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "eaa45b01747d5585b46d2a0043f81002b7c88f0f7b6fc9920c3bda4c52506d6d" +content-hash = "88cd8709372c0c07eff53e51b00b3740518bb32125f72d9a2e7dd0717067d45a" diff --git a/pyproject.toml b/pyproject.toml index e9179d8..908a0b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ rcrs_core = {git = "https://github.com/adf-python/rcrs-core-python"} pyyaml = "^6.0.2" pytest = "^8.3.2" types-pyyaml = "^6.0.12.20240808" +scikit-learn = "^1.5.2" [tool.poetry.group.dev.dependencies] From 6935e2e38de16932a0093df92791034d1eb5c084 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 30 Sep 2024 21:48:25 +0900 Subject: [PATCH 059/249] fix: move directory --- .../{astar_path_planning.py => algorithm/a_star_path_planning.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename adf_core_python/implement/module/{astar_path_planning.py => algorithm/a_star_path_planning.py} (100%) diff --git a/adf_core_python/implement/module/astar_path_planning.py b/adf_core_python/implement/module/algorithm/a_star_path_planning.py similarity index 100% rename from adf_core_python/implement/module/astar_path_planning.py rename to adf_core_python/implement/module/algorithm/a_star_path_planning.py From 382a47e453ea9cf6ba52398667fe2b871ac2cfc8 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 30 Sep 2024 21:54:11 +0900 Subject: [PATCH 060/249] feat: add complex module --- .../core/agent/info/scenario_info.py | 7 +- adf_core_python/core/agent/info/world_info.py | 90 ++++++++++++- .../component/module/algorithm/clustering.py | 68 ++++++++++ .../module/complex/target_detector.py | 4 +- .../default_extend_action_transport.py | 17 +-- .../module/algorithm/k_means_clustering.py | 123 ++++++++++++++++++ .../module/complex/default_human_detector.py | 110 ++++++++++++++++ .../module/complex/default_search.py | 98 ++++++++++++++ .../tactics/default_tactics_police_force.py | 2 +- .../core/agent/module/test_module_manager.py | 2 +- 10 files changed, 500 insertions(+), 21 deletions(-) create mode 100644 adf_core_python/core/component/module/algorithm/clustering.py create mode 100644 adf_core_python/implement/module/algorithm/k_means_clustering.py create mode 100644 adf_core_python/implement/module/complex/default_human_detector.py create mode 100644 adf_core_python/implement/module/complex/default_search.py diff --git a/adf_core_python/core/agent/info/scenario_info.py b/adf_core_python/core/agent/info/scenario_info.py index 099b7f2..fe085bf 100644 --- a/adf_core_python/core/agent/info/scenario_info.py +++ b/adf_core_python/core/agent/info/scenario_info.py @@ -1,4 +1,5 @@ from enum import Enum +from typing import Any from adf_core_python.core.config.config import Config @@ -57,7 +58,7 @@ def get_mode(self) -> Mode: """ return self._mode - def get_config_value(self, key: str, default: str) -> str: + def get_value(self, key: str, default: Any) -> Any: """ Get the value of the configuration @@ -65,10 +66,12 @@ def get_config_value(self, key: str, default: str) -> str: ---------- key : str Key of the configuration + default : Any + Default value of the configuration Returns ------- - str + Any Value of the configuration """ return self._config.get_value(key, default) diff --git a/adf_core_python/core/agent/info/world_info.py b/adf_core_python/core/agent/info/world_info.py index 1ca617d..9a66517 100644 --- a/adf_core_python/core/agent/info/world_info.py +++ b/adf_core_python/core/agent/info/world_info.py @@ -53,14 +53,16 @@ def get_entity(self, entity_id: EntityID) -> Optional[Entity]: """ return self._world_model.get_entity(entity_id) - def get_entity_ids_of_type(self, entity_type: type[Entity]) -> list[EntityID]: + def get_entity_ids_of_types( + self, entity_types: list[type[Entity]] + ) -> list[EntityID]: """ - Get the entity IDs of the specified type + Get the entity IDs of the specified types Parameters ---------- - entity_type : type[Entity] - Entity type + entity_types : list[type[Entity]] + List of entity types Returns ------- @@ -69,7 +71,85 @@ def get_entity_ids_of_type(self, entity_type: type[Entity]) -> list[EntityID]: """ entity_ids: list[EntityID] = [] for entity in self._world_model.get_entities(): - if isinstance(entity, entity_type): + if any(isinstance(entity, entity_type) for entity_type in entity_types): entity_ids.append(entity.get_id()) return entity_ids + + def get_entities_of_types(self, entity_types: list[type[Entity]]) -> list[Entity]: + """ + Get the entities of the specified types + + Parameters + ---------- + entity_types : list[type[Entity]] + List of entity types + + Returns + ------- + list[Entity] + Entities + """ + entities: list[Entity] = [] + for entity in self._world_model.get_entities(): + if any(isinstance(entity, entity_type) for entity_type in entity_types): + entities.append(entity) + + return entities + + def get_distance(self, entity_id1: EntityID, entity_id2: EntityID) -> float: + """ + Get the distance between two entities + + Parameters + ---------- + entity_id1 : EntityID + Entity ID 1 + entity_id2 : EntityID + Entity ID 2 + + Returns + ------- + float + Distance + + Raises + ------ + ValueError + If one or both entities are invalid or the location is invalid + """ + entity1: Optional[Entity] = self.get_entity(entity_id1) + entity2: Optional[Entity] = self.get_entity(entity_id2) + if entity1 is None or entity2 is None: + raise ValueError( + f"One or both entities are invalid: entity_id1={entity_id1}, entity_id2={entity_id2}, entity1={entity1}, entity2={entity2}" + ) + + location1_x, location1_y = entity1.get_location() + location2_x, location2_y = entity2.get_location() + if ( + location1_x is None + or location1_y is None + or location2_x is None + or location2_y is None + ): + raise ValueError( + f"Invalid location: entity_id1={entity_id1}, entity_id2={entity_id2}, location1_x={location1_x}, location1_y={location1_y}, location2_x={location2_x}, location2_y={location2_y}" + ) + + distance: float = ( + (location1_x - location2_x) ** 2 + (location1_y - location2_y) ** 2 + ) ** 0.5 + + return distance + + def get_change_set(self) -> ChangeSet: + """ + Get the change set + + Returns + ------- + ChangeSet + Change set + """ + return self._change_set diff --git a/adf_core_python/core/component/module/algorithm/clustering.py b/adf_core_python/core/component/module/algorithm/clustering.py new file mode 100644 index 0000000..0a6bfdc --- /dev/null +++ b/adf_core_python/core/component/module/algorithm/clustering.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING + +from adf_core_python.core.component.module.abstract_module import AbstractModule + +if TYPE_CHECKING: + from rcrs_core.entities.entity import Entity + from rcrs_core.worldmodel.entityID import EntityID + + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + + +class Clustering(AbstractModule): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + @abstractmethod + def get_cluster_number(self) -> int: + pass + + @abstractmethod + def get_cluster_index(self, entity_id: EntityID) -> int: + pass + + @abstractmethod + def get_cluster_entities(self, cluster_index: int) -> list[Entity]: + pass + + @abstractmethod + def get_cluster_entity_ids(self, cluster_index: int) -> list[EntityID]: + pass + + @abstractmethod + def calculate(self) -> Clustering: + pass + + def precompute(self, precompute_data: PrecomputeData) -> Clustering: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> Clustering: + super().resume(precompute_data) + return self + + def prepare(self) -> Clustering: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> Clustering: + super().update_info(message_manager) + return self diff --git a/adf_core_python/core/component/module/complex/target_detector.py b/adf_core_python/core/component/module/complex/target_detector.py index 2a405af..29fb4df 100644 --- a/adf_core_python/core/component/module/complex/target_detector.py +++ b/adf_core_python/core/component/module/complex/target_detector.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import abstractmethod -from typing import TYPE_CHECKING, Generic, TypeVar +from typing import TYPE_CHECKING, Generic, Optional, TypeVar from rcrs_core.entities.entity import Entity @@ -35,7 +35,7 @@ def __init__( ) @abstractmethod - def get_target_entity_id(self) -> EntityID: + def get_target_entity_id(self) -> Optional[EntityID]: pass @abstractmethod diff --git a/adf_core_python/implement/extend_action/default_extend_action_transport.py b/adf_core_python/implement/extend_action/default_extend_action_transport.py index f770b48..aa75d22 100644 --- a/adf_core_python/implement/extend_action/default_extend_action_transport.py +++ b/adf_core_python/implement/extend_action/default_extend_action_transport.py @@ -23,6 +23,7 @@ from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning +# TODO: refactor this class class DefaultExtendActionTransport(ExtAction): def __init__( self, @@ -99,7 +100,7 @@ def calc(self) -> ExtAction: agent: AmbulanceTeamEntity = cast( AmbulanceTeamEntity, self.agent_info.get_myself() ) - transport_human: Human = self.agent_info.some_one_on_board() + transport_human: Optional[Human] = self.agent_info.some_one_on_board() if transport_human is not None: self.result = self.calc_unload( agent, self._path_planning, transport_human, self._target_entity_id @@ -134,9 +135,7 @@ def calc_rescue( target_position = human.get_position() if agent_position == target_position: - if isinstance(human, Civilian) and ( - human.get_buriedness() is not None and human.get_buriedness() > 0 - ): + if isinstance(human, Civilian) and ((human.get_buriedness() or 0) > 0): return ActionLoad(human.get_id()) else: path = path_planning.get_path(agent_position, target_position) @@ -176,9 +175,7 @@ def calc_unload( if isinstance(position, Refuge): return ActionUnload() else: - path = path_planning.get_path( - agent_position, self.world_info.get_entity_ids_of_type(Refuge) - ) + path = self.get_nearest_refuge_path(agent, path_planning) if path is not None and len(path) > 0: return ActionMove(path) @@ -191,7 +188,7 @@ def calc_unload( human = cast(Human, target_entity) if human.get_position() is not None: return self.calc_refuge_action( - agent, path_planning, [human.get_position()], True + agent, path_planning, human.get_position(), True ) path = self.get_nearest_refuge_path(agent, path_planning) if path is not None and len(path) > 0: @@ -207,7 +204,7 @@ def calc_refuge_action( is_unload: bool, ) -> Optional[ActionMove | ActionUnload | ActionRest]: position = human.get_position() - refuges = self.world_info.get_entity_ids_of_type(Refuge) + refuges = self.world_info.get_entity_ids_of_types([Refuge]) size = len(refuges) if position in refuges: @@ -242,7 +239,7 @@ def get_nearest_refuge_path( self, human: Human, path_planning: PathPlanning ) -> list[EntityID]: position = human.get_position() - refuges = self.world_info.get_entity_ids_of_type(Refuge) + refuges = self.world_info.get_entity_ids_of_types([Refuge]) nearest_path = None for refuge_id in refuges: diff --git a/adf_core_python/implement/module/algorithm/k_means_clustering.py b/adf_core_python/implement/module/algorithm/k_means_clustering.py new file mode 100644 index 0000000..afb18be --- /dev/null +++ b/adf_core_python/implement/module/algorithm/k_means_clustering.py @@ -0,0 +1,123 @@ +import numpy as np +from rcrs_core.connection.URN import Entity as EntityURN +from rcrs_core.entities.ambulanceCenter import AmbulanceCentreEntity +from rcrs_core.entities.building import Building +from rcrs_core.entities.entity import Entity +from rcrs_core.entities.fireStation import FireStationEntity +from rcrs_core.entities.gassStation import GasStation +from rcrs_core.entities.hydrant import Hydrant +from rcrs_core.entities.policeOffice import PoliceOfficeEntity +from rcrs_core.entities.refuge import Refuge +from rcrs_core.entities.road import Road +from rcrs_core.worldmodel.entityID import EntityID +from sklearn.cluster import KMeans + +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.module.algorithm.clustering import Clustering + + +class KMeansClustering(Clustering): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + match agent_info.get_myself().get_urn(): + case EntityURN.AMBULANCE_TEAM: + self._cluster_number = scenario_info.get_value( + "scenario.agents.at", + 1, + ) + case EntityURN.POLICE_FORCE: + self._cluster_number = scenario_info.get_value( + "scenario.agents.pf", + 1, + ) + case EntityURN.FIRE_BRIGADE: + self._cluster_number = scenario_info.get_value( + "scenario.agents.fb", + 1, + ) + case _: + self._cluster_number = 1 + + sorted_entities = sorted( + world_info.get_entities_of_types( + [ + agent_info.get_myself().__class__, + ] + ), + key=lambda entity: entity.get_id().get_value(), + ) + self.entity_cluster_indices = { + entity.get_id(): idx for idx, entity in enumerate(sorted_entities) + } + + self.cluster_entities: list[list[Entity]] = [] + self.entities: list[Entity] = world_info.get_entities_of_types( + [ + AmbulanceCentreEntity, + FireStationEntity, + GasStation, + Hydrant, + PoliceOfficeEntity, + Refuge, + Road, + Building, + ] + ) + + def calculate(self) -> Clustering: + return self + + def get_cluster_number(self) -> int: + return self._cluster_number + + def get_cluster_index(self, entity_id: EntityID) -> int: + return self.entity_cluster_indices.get(entity_id, 0) + + def get_cluster_entities(self, cluster_index: int) -> list[Entity]: + if cluster_index >= len(self.cluster_entities): + return [] + return self.cluster_entities[cluster_index] + + def get_cluster_entity_ids(self, cluster_index: int) -> list[EntityID]: + if cluster_index >= len(self.cluster_entities): + return [] + return [entity.get_id() for entity in self.cluster_entities[cluster_index]] + + def prepare(self) -> Clustering: + super().prepare() + if self.get_count_prepare() > 1: + return self + self.cluster_entities = self.create_cluster(self._cluster_number, self.entities) + return self + + def create_cluster( + self, cluster_number: int, entities: list[Entity] + ) -> list[list[Entity]]: + kmeans = KMeans(n_clusters=cluster_number) + entity_positions: np.ndarray = np.array([]) + for entity in entities: + location1_x, location1_y = entity.get_location() + if location1_x is None or location1_y is None: + continue + entity_positions = np.append(entity_positions, [location1_x, location1_y]) + + kmeans.fit(entity_positions.reshape(-1, 2)) + + clusters: list[list[Entity]] = [[] for _ in range(cluster_number)] + for entity, label in zip(entities, kmeans.labels_): + clusters[label].append(entity) + + return clusters diff --git a/adf_core_python/implement/module/complex/default_human_detector.py b/adf_core_python/implement/module/complex/default_human_detector.py new file mode 100644 index 0000000..05e58ce --- /dev/null +++ b/adf_core_python/implement/module/complex/default_human_detector.py @@ -0,0 +1,110 @@ +from typing import Optional, cast + +from rcrs_core.connection.URN import Entity as EntityURN +from rcrs_core.entities.entity import Entity +from rcrs_core.entities.human import Human +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.module.algorithm.clustering import Clustering +from adf_core_python.core.component.module.complex.human_detector import HumanDetector + + +class DefaultHumanDetector(HumanDetector): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._clustering: Clustering = cast( + Clustering, + module_manager.get_module( + "DefaultHumanDetector.Clustering", + "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", + ), + ) + self.register_sub_module(self._clustering) + + self._result: Optional[EntityID] = None + + def calculate(self) -> HumanDetector: + transport_human: Optional[Human] = self._agent_info.some_one_on_board() + if transport_human is not None: + self._result = transport_human.get_id() + return self + + if self._result is not None: + if self._is_valid_human(self._result): + self._result = self._select_target() + + return self + + def _select_target(self) -> Optional[EntityID]: + if self._result is not None and self._is_valid_human(self._result): + return self._result + + cluster_index: int = self._clustering.get_cluster_index( + self._agent_info.get_entity_id() + ) + cluster_entities: list[Entity] = self._clustering.get_cluster_entities( + cluster_index + ) + cluster_valid_human_entities: list[Entity] = [ + entity + for entity in cluster_entities + if self._is_valid_human(entity.get_id()) + ] + if len(cluster_valid_human_entities) == 0: + return None + + nearest_human_entity: Optional[Entity] = None + nearest_distance: float = 10**10 + for entity in cluster_valid_human_entities: + distance: float = self._world_info.get_distance( + self._agent_info.get_entity_id(), + entity.get_id(), + ) + if distance < nearest_distance: + nearest_distance = distance + nearest_human_entity = entity + + return ( + nearest_human_entity.get_id() if nearest_human_entity is not None else None + ) + + def _is_valid_human(self, target_entity_id: EntityID) -> bool: + target: Optional[Entity] = self._world_info.get_entity(target_entity_id) + if target is None: + return False + if not isinstance(target, Human): + return False + hp: Optional[int] = target.get_hp() + if hp is None or hp <= 0: + return False + buriedness: Optional[int] = target.get_buriedness() + if buriedness is None or buriedness > 0: + return False + position_entity_id: Optional[EntityID] = target.get_position() + if position_entity_id is None: + return False + position: Optional[Entity] = self._world_info.get_entity(position_entity_id) + if position is None: + return False + urn: EntityURN = position.get_urn() + if urn == EntityURN.REFUGE or urn == EntityURN.AMBULANCE_TEAM: + return False + + return True + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._result diff --git a/adf_core_python/implement/module/complex/default_search.py b/adf_core_python/implement/module/complex/default_search.py new file mode 100644 index 0000000..3c35a4a --- /dev/null +++ b/adf_core_python/implement/module/complex/default_search.py @@ -0,0 +1,98 @@ +from typing import Optional, cast + +from rcrs_core.connection.URN import Entity as EntityURN +from rcrs_core.entities.entity import Entity +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.module.algorithm.clustering import Clustering +from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning +from adf_core_python.core.component.module.complex.search import Search + + +class DefaultSearch(Search): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + self._unsearched_building_ids: set[EntityID] = set() + self._result: Optional[EntityID] = None + + self._clustering: Clustering = cast( + Clustering, + module_manager.get_module( + "DefaultSearch.Clustering", + "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", + ), + ) + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultSearch.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + + self.register_sub_module(self._clustering) + self.register_sub_module(self._path_planning) + + def update_info(self, message_manager: MessageManager) -> Search: + super().update_info(message_manager) + if self.get_count_update_info() > 1: + return self + + searched_building_ids = self._world_info.get_change_set().get_changed_entities() + self._unsearched_building_ids = self._unsearched_building_ids.difference( + searched_building_ids + ) + + if len(self._unsearched_building_ids) == 0: + self._unsearched_building_ids = self._get_search_targets() + + return self + + def calculate(self) -> Search: + nearest_building_id: Optional[EntityID] = None + nearest_distance: Optional[float] = None + for building_id in self._unsearched_building_ids: + distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), building_id + ) + if nearest_distance is None or distance < nearest_distance: + nearest_building_id = building_id + nearest_distance = distance + self._result = nearest_building_id + return self + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._result + + def _get_search_targets(self) -> set[EntityID]: + cluster_index: int = self._clustering.get_cluster_index( + self._agent_info.get_entity_id() + ) + cluster_entities: list[Entity] = self._clustering.get_cluster_entities( + cluster_index + ) + building_entity_ids: list[EntityID] = [ + entity.get_id() + for entity in cluster_entities + if entity.get_urn() == EntityURN.BUILDING + and entity.get_urn() != EntityURN.REFUGE + ] + + return set(building_entity_ids) diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py index ecd2134..bd4f29d 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -31,7 +31,7 @@ def initialize( ) -> None: # world_info.index_class() self._clear_distance = int( - scenario_info.get_config_value("clear.repair.distance", "null") + scenario_info.get_value("clear.repair.distance", "null") ) match scenario_info.get_mode(): diff --git a/tests/core/agent/module/test_module_manager.py b/tests/core/agent/module/test_module_manager.py index 458791e..530b990 100644 --- a/tests/core/agent/module/test_module_manager.py +++ b/tests/core/agent/module/test_module_manager.py @@ -12,7 +12,7 @@ def test_can_get_module(self) -> None: config = ModuleConfig(config_file_path) config.set_value( "test_module", - "adf_core_python.implement.module.astar_path_planning.AStarPathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", ) module_manager = self.create_module_manager(config) module = module_manager.get_module("test_module", "test_module") From 154f227dce4039c4f633a8e28f3085553494bd5f Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 30 Sep 2024 22:13:02 +0900 Subject: [PATCH 061/249] feat: Update launch method in main.py to use AgentLauncher --- adf_core_python/core/agent/action/action.py | 6 +- .../core/agent/config/module_config.py | 3 - .../core/component/abstract_loader.py | 41 +++--- .../core/launcher/agent_launcher.py | 10 +- .../connect/connector_ambulance_team.py | 10 +- adf_core_python/implement/default_loader.py | 40 +++--- .../tactics/default_tactics_ambulance_team.py | 8 +- config/develop.json | 3 + config/module.yaml | 122 ++++++++++++++++++ 9 files changed, 183 insertions(+), 60 deletions(-) create mode 100644 config/develop.json create mode 100644 config/module.yaml diff --git a/adf_core_python/core/agent/action/action.py b/adf_core_python/core/agent/action/action.py index eb4a04c..c7ab198 100644 --- a/adf_core_python/core/agent/action/action.py +++ b/adf_core_python/core/agent/action/action.py @@ -1,9 +1,7 @@ from abc import ABC, abstractmethod -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from rcrs_core.commands.Command import Command - from rcrs_core.worldmodel.entityID import EntityID +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID class Action(ABC): diff --git a/adf_core_python/core/agent/config/module_config.py b/adf_core_python/core/agent/config/module_config.py index a97ef30..095cb50 100644 --- a/adf_core_python/core/agent/config/module_config.py +++ b/adf_core_python/core/agent/config/module_config.py @@ -35,9 +35,6 @@ def __init__(self, config_file_name: str = DEFAULT_CONFIG_FILE_NAME): for key, value in flatten_data.items(): self.set_value(key, value) - for key, value in flatten_data.items(): - print(f"{key}: {self.get_value(key)}") - def _read_from_yaml(self, file_name: str) -> dict[str, Any]: """ Read configuration from yaml file diff --git a/adf_core_python/core/component/abstract_loader.py b/adf_core_python/core/component/abstract_loader.py index 006db23..dc0feae 100644 --- a/adf_core_python/core/component/abstract_loader.py +++ b/adf_core_python/core/component/abstract_loader.py @@ -1,25 +1,24 @@ from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Optional - -if TYPE_CHECKING: - from adf_core_python.core.component.tactics.tactics_ambulance_center import ( - TacticsAmbulanceCenter, - ) - from adf_core_python.core.component.tactics.tactics_ambulance_team import ( - TacticsAmbulanceTeam, - ) - from adf_core_python.core.component.tactics.tactics_fire_brigade import ( - TacticsFireBrigade, - ) - from adf_core_python.core.component.tactics.tactics_fire_station import ( - TacticsFireStation, - ) - from adf_core_python.core.component.tactics.tactics_police_force import ( - TacticsPoliceForce, - ) - from adf_core_python.core.component.tactics.tactics_police_office import ( - TacticsPoliceOffice, - ) +from typing import Optional + +from adf_core_python.core.component.tactics.tactics_ambulance_center import ( + TacticsAmbulanceCenter, +) +from adf_core_python.core.component.tactics.tactics_ambulance_team import ( + TacticsAmbulanceTeam, +) +from adf_core_python.core.component.tactics.tactics_fire_brigade import ( + TacticsFireBrigade, +) +from adf_core_python.core.component.tactics.tactics_fire_station import ( + TacticsFireStation, +) +from adf_core_python.core.component.tactics.tactics_police_force import ( + TacticsPoliceForce, +) +from adf_core_python.core.component.tactics.tactics_police_office import ( + TacticsPoliceOffice, +) class AbstractLoader(ABC): diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index f5b7fb1..dc5c1cd 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -48,12 +48,12 @@ def init_connector(self) -> None: self.config.get_value(ConfigKey.KEY_TEAM_NAME), ) - self.connectors.append(ConnectorAmbulanceCentre()) + # self.connectors.append(ConnectorAmbulanceCentre()) self.connectors.append(ConnectorAmbulanceTeam()) - self.connectors.append(ConnectorFireBrigade()) - self.connectors.append(ConnectorFireStation()) - self.connectors.append(ConnectorPoliceForce()) - self.connectors.append(ConnectorPoliceOffice()) + # self.connectors.append(ConnectorFireBrigade()) + # self.connectors.append(ConnectorFireStation()) + # self.connectors.append(ConnectorPoliceForce()) + # self.connectors.append(ConnectorPoliceOffice()) def launch(self) -> None: host: str = self.config.get_value(ConfigKey.KEY_KERNEL_HOST, "localhost") diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index 5fcb3d5..59af7c1 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -1,10 +1,10 @@ from logging import Logger, getLogger -from rcrs_core.agents.ambulanceTeamAgent import AmbulanceTeamAgent from rcrs_core.connection.componentLauncher import ComponentLauncher from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.platoon.platoon_ambulance import PlatoonAmbulance from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.component.tactics.tactics_ambulance_team import ( TacticsAmbulanceTeam, @@ -53,8 +53,14 @@ def connect( # TODO: component_launcher.generate_request_ID can cause race condition component_launcher.connect( - AmbulanceTeamAgent( + PlatoonAmbulance( + tactics_ambulance_team, + "ambulance_team", config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + "test", + module_config, + develop_data, ), component_launcher.generate_request_ID(), ) diff --git a/adf_core_python/implement/default_loader.py b/adf_core_python/implement/default_loader.py index 2b79b7b..73c1d5f 100644 --- a/adf_core_python/implement/default_loader.py +++ b/adf_core_python/implement/default_loader.py @@ -1,6 +1,24 @@ from typing import TYPE_CHECKING from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.component.tactics.tactics_ambulance_center import ( + TacticsAmbulanceCenter, +) +from adf_core_python.core.component.tactics.tactics_ambulance_team import ( + TacticsAmbulanceTeam, +) +from adf_core_python.core.component.tactics.tactics_fire_brigade import ( + TacticsFireBrigade, +) +from adf_core_python.core.component.tactics.tactics_fire_station import ( + TacticsFireStation, +) +from adf_core_python.core.component.tactics.tactics_police_force import ( + TacticsPoliceForce, +) +from adf_core_python.core.component.tactics.tactics_police_office import ( + TacticsPoliceOffice, +) from adf_core_python.implement.tactics.default_tactics_ambulance_center import ( DefaultTacticsAmbulanceCenter, ) @@ -20,26 +38,6 @@ DefaultTacticsPoliceOffice, ) -if TYPE_CHECKING: - from adf_core_python.core.component.tactics.tactics_ambulance_center import ( - TacticsAmbulanceCenter, - ) - from adf_core_python.core.component.tactics.tactics_ambulance_team import ( - TacticsAmbulanceTeam, - ) - from adf_core_python.core.component.tactics.tactics_fire_brigade import ( - TacticsFireBrigade, - ) - from adf_core_python.core.component.tactics.tactics_fire_station import ( - TacticsFireStation, - ) - from adf_core_python.core.component.tactics.tactics_police_force import ( - TacticsPoliceForce, - ) - from adf_core_python.core.component.tactics.tactics_police_office import ( - TacticsPoliceOffice, - ) - class DefaultLoader(AbstractLoader): def get_tactics_ambulance_team(self) -> TacticsAmbulanceTeam: @@ -51,7 +49,7 @@ def get_tactics_fire_brigade(self) -> TacticsFireBrigade: def get_tactics_police_force(self) -> TacticsPoliceForce: return DefaultTacticsPoliceForce() - def get_tactics_ambulance_centre(self) -> TacticsAmbulanceCenter: + def get_tactics_ambulance_center(self) -> TacticsAmbulanceCenter: return DefaultTacticsAmbulanceCenter() def get_tactics_fire_station(self) -> TacticsFireStation: diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 4171198..0985c7d 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -36,23 +36,23 @@ def initialize( Search, module_manager.get_module( "DefaultTacticsAmbulanceTeam.Search", - "adf_core_python.implement.module.complex.DefaultSearch", + "adf_core_python.core.component.module.complex.search.Search", ), ) self._human_detector: HumanDetector = cast( HumanDetector, module_manager.get_module( "DefaultTacticsAmbulanceTeam.HumanDetector", - "adf_core_python.implement.module.complex.DefaultHumanDetector", + "adf_core_python.core.component.module.complex.human_detector.HumanDetector", ), ) self._action_transport = module_manager.get_ext_action( "DefaultTacticsAmbulanceTeam.ExtActionTransport", - "adf_core_python.implement.extaction.DefaultExtActionTransport", + "adf_core_python.implement.extend_action.default_extend_action_transport.DefaultExtActionTransport", ) self._action_ext_move = module_manager.get_ext_action( "DefaultTacticsAmbulanceTeam.ExtActionMove", - "adf_core_python.implement.extaction.DefaultExtActionMove", + "adf_core_python.implement.extend_action.default_extend_action_move.DefaultExtActionMove", ) self.register_module(self._search) self.register_module(self._human_detector) diff --git a/config/develop.json b/config/develop.json new file mode 100644 index 0000000..d0ae716 --- /dev/null +++ b/config/develop.json @@ -0,0 +1,3 @@ +{ + "test": "test" +} diff --git a/config/module.yaml b/config/module.yaml new file mode 100644 index 0000000..43806fa --- /dev/null +++ b/config/module.yaml @@ -0,0 +1,122 @@ +## DefaultTacticsAmbulanceTeam +DefaultTacticsAmbulanceTeam: + HumanDetector: adf_core_python.core.component.module.complex.human_detector.HumanDetector + Search: adf_core_python.core.component.module.complex.search.Search + ExtActionTransport: adf_core_python.implement.extend_action.default_extend_action_transport.DefaultExtActionTransport + ExtActionMove: adf_core_python.implement.extend_action.default_extend_action_move.DefaultExtActionMove + CommandExecutorAmbulance: adf.impl.centralized.DefaultCommandExecutorAmbulance + CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScout + +# ## DefaultTacticsFireBrigade +# DefaultTacticsFireBrigade: +# HumanDetector: sample_team.module.complex.SampleHumanDetector +# Search: sample_team.module.complex.SampleSearch +# ExtActionFireRescue: adf.impl.extaction.DefaultExtActionFireRescue +# ExtActionMove: adf.impl.extaction.DefaultExtActionMove +# CommandExecutorFire: adf.impl.centralized.DefaultCommandExecutorFire +# CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScout + +# ## DefaultTacticsPoliceForce +# DefaultTacticsPoliceForce: +# RoadDetector: sample_team.module.complex.SampleRoadDetector +# Search: sample_team.module.complex.SampleSearch +# ExtActionClear: adf.impl.extaction.DefaultExtActionClear +# ExtActionMove: adf.impl.extaction.DefaultExtActionMove +# CommandExecutorPolice: adf.impl.centralized.DefaultCommandExecutorPolice +# CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScoutPolice + +# ## DefaultTacticsAmbulanceCentre +# DefaultTacticsAmbulanceCentre: +# TargetAllocator: sample_team.module.complex.SampleAmbulanceTargetAllocator +# CommandPicker: adf.impl.centralized.DefaultCommandPickerAmbulance + +# ## DefaultTacticsFireStation +# DefaultTacticsFireStation: +# TargetAllocator: sample_team.module.complex.SampleFireTargetAllocator +# CommandPicker: adf.impl.centralized.DefaultCommandPickerFire + +# ## DefaultTacticsPoliceOffice +# DefaultTacticsPoliceOffice: +# TargetAllocator: sample_team.module.complex.SamplePoliceTargetAllocator +# CommandPicker: adf.impl.centralized.DefaultCommandPickerPolice + +## SampleSearch +SampleSearch: + PathPlanning: + Ambulance: adf.impl.module.algorithm.DijkstraPathPlanning + Fire: adf.impl.module.algorithm.DijkstraPathPlanning + Police: adf.impl.module.algorithm.DijkstraPathPlanning + Clustering: + Ambulance: adf.impl.module.algorithm.KMeansClustering + Fire: adf.impl.module.algorithm.KMeansClustering + Police: adf.impl.module.algorithm.KMeansClustering + +## SampleBuildDetector +SampleBuildingDetector: + Clustering: adf.impl.module.algorithm.KMeansClustering + +## SampleRoadDetector +SampleRoadDetector: + Clustering: adf.impl.module.algorithm.KMeansClustering + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## SampleHumanDetector +SampleHumanDetector: + Clustering: adf.impl.module.algorithm.KMeansClustering + +## DefaultExtActionClear +DefaultExtActionClear: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultExtActionFireFighting +DefaultExtActionFireFighting: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultExtActionFireRescue +DefaultExtActionFireRescue: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultExtActionMove +DefaultExtActionMove: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +## DefaultExtActionTransport +DefaultExtActionTransport: + PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +# ## DefaultCommandExecutorAmbulance +# DefaultCommandExecutorAmbulance: +# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +# ExtActionTransport: adf.impl.extaction.DefaultExtActionTransport +# ExtActionMove: adf.impl.extaction.DefaultExtActionMove + +# ## DefaultCommandExecutorFire +# DefaultCommandExecutorFire: +# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +# EtxActionFireRescue: adf.impl.extaction.DefaultExtActionFireRescue +# EtxActionFireFighting: adf.impl.extaction.DefaultExtActionFireFighting +# ExtActionMove: adf.impl.extaction.DefaultExtActionMove + +# ## DefaultCommandExecutorPolice +# DefaultCommandExecutorPolice: +# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +# ExtActionClear: adf.impl.extaction.DefaultExtActionClear +# ExtActionMove: adf.impl.extaction.DefaultExtActionMove + +# ## DefaultCommandExecutorScout +# DefaultCommandExecutorScout: +# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +# ## DefaultCommandExecutorScoutPolice +# DefaultCommandExecutorScoutPolice: +# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +# ExtActionClear: adf.impl.extaction.DefaultExtActionClear + +# ## MessageManager +# MessageManager: +# PlatoonChannelSubscriber: adf.impl.module.comm.DefaultChannelSubscriber +# CenterChannelSubscriber: adf.impl.module.comm.DefaultChannelSubscriber +# PlatoonMessageCoordinator: adf.impl.module.comm.DefaultMessageCoordinator +# CenterMessageCoordinator: adf.impl.module.comm.DefaultMessageCoordinator + +# ## VisualDebug +# VisualDebug: true From 9cbd07cde10712421430e0109a014531cd9a2ff1 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 2 Oct 2024 02:15:22 +0900 Subject: [PATCH 062/249] feat: update connector methods to return threading.Thread lists --- .../core/launcher/connect/connector.py | 3 +- .../connect/connector_ambulance_centre.py | 23 ++++++++----- .../connect/connector_ambulance_team.py | 33 ++++++++++++------- .../connect/connector_fire_brigade.py | 20 +++++++---- .../connect/connector_fire_station.py | 22 +++++++++---- .../connect/connector_police_force.py | 20 +++++++---- .../connect/connector_police_office.py | 20 +++++++---- 7 files changed, 95 insertions(+), 46 deletions(-) diff --git a/adf_core_python/core/launcher/connect/connector.py b/adf_core_python/core/launcher/connect/connector.py index dbf4f29..e2e562f 100644 --- a/adf_core_python/core/launcher/connect/connector.py +++ b/adf_core_python/core/launcher/connect/connector.py @@ -1,3 +1,4 @@ +import threading from abc import ABC, abstractmethod from rcrs_core.connection.componentLauncher import ComponentLauncher @@ -16,7 +17,7 @@ def connect( component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader, - ) -> None: + ) -> list[threading.Thread]: raise NotImplementedError def get_connected_agent_count(self) -> int: diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py index 3475174..e7909b4 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py @@ -1,3 +1,4 @@ +import threading from logging import Logger, getLogger from rcrs_core.agents.ambulanceCenterAgent import AmbulanceCenterAgent @@ -21,10 +22,12 @@ def connect( component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader, - ) -> None: + ) -> list[threading.Thread]: count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) if count == 0: - return + return [] + + threads: list[threading.Thread] = [] for _ in range(count): # tactics_ambulance_centre: TacticsAmbulanceCentre @@ -50,12 +53,16 @@ def connect( ) # TODO: component_launcher.generate_request_ID can cause race condition - component_launcher.connect( - # TODO: AmbulanceCenterAgent is not implemented precompute method and other methods - AmbulanceCenterAgent( - config.get_value(ConfigKey.KEY_PRECOMPUTE, False), - ), # type: ignore - component_launcher.generate_request_ID(), + thread = threading.Thread( + target=component_launcher.connect, + args=( + AmbulanceCenterAgent( + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + ), # type: ignore + component_launcher.generate_request_ID(), + ), ) + threads.append(thread) self.logger.info("Connected ambulance centre (count: %d)" % count) + return threads diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index 59af7c1..1c09775 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -1,3 +1,4 @@ +import threading from logging import Logger, getLogger from rcrs_core.connection.componentLauncher import ComponentLauncher @@ -24,10 +25,12 @@ def connect( component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader, - ) -> None: + ) -> list[threading.Thread]: count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) if count == 0: - return + return [] + + threads: list[threading.Thread] = [] for _ in range(count): if loader.get_tactics_ambulance_team() is None: @@ -52,17 +55,23 @@ def connect( ) # TODO: component_launcher.generate_request_ID can cause race condition - component_launcher.connect( - PlatoonAmbulance( - tactics_ambulance_team, - "ambulance_team", - config.get_value(ConfigKey.KEY_PRECOMPUTE, False), - config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - "test", - module_config, - develop_data, + # threading + thread = threading.Thread( + target=component_launcher.connect, + args=( + PlatoonAmbulance( + tactics_ambulance_team, + "ambulance_team", + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + "test", + module_config, + develop_data, + ), + component_launcher.generate_request_ID(), ), - component_launcher.generate_request_ID(), ) + threads.append(thread) self.logger.info("Connected ambulance team (count: %d)" % count) + return threads diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/adf_core_python/core/launcher/connect/connector_fire_brigade.py index 920a805..f210a63 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_brigade.py +++ b/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -1,3 +1,4 @@ +import threading from logging import Logger, getLogger from rcrs_core.agents.fireBrigadeAgent import FireBrigadeAgent @@ -21,10 +22,12 @@ def connect( component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader, - ) -> None: + ) -> list[threading.Thread]: count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) if count == 0: - return + return [] + + threads: list[threading.Thread] = [] for _ in range(count): # tactics_fire_brigade: TacticsFireBrigade @@ -50,11 +53,16 @@ def connect( ) # TODO: component_launcher.generate_request_ID can cause race condition - component_launcher.connect( - FireBrigadeAgent( - config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + thread = threading.Thread( + target=component_launcher.connect, + args=( + FireBrigadeAgent( + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + ), + component_launcher.generate_request_ID(), ), - component_launcher.generate_request_ID(), ) + threads.append(thread) self.logger.info("Connected fire brigade (count: %d)" % count) + return threads diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/adf_core_python/core/launcher/connect/connector_fire_station.py index a2e5860..86ac1dc 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_station.py +++ b/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -1,3 +1,4 @@ +import threading from logging import Logger, getLogger from rcrs_core.agents.fireStationAgent import FireStationAgent @@ -21,10 +22,12 @@ def connect( component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader, - ) -> None: + ) -> list[threading.Thread]: count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) if count == 0: - return + return [] + + threads: list[threading.Thread] = [] for _ in range(count): # tactics_fire_station: TacticsFireStation @@ -50,11 +53,16 @@ def connect( ) # TODO: component_launcher.generate_request_ID can cause race condition - component_launcher.connect( - FireStationAgent( - config.get_value(ConfigKey.KEY_PRECOMPUTE, False), - ), # type: ignore - component_launcher.generate_request_ID(), + thread = threading.Thread( + target=component_launcher.connect, + args=( + FireStationAgent( + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + ), # type: ignore + component_launcher.generate_request_ID(), + ), ) + threads.append(thread) self.logger.info("Connected fire station (count: %d)" % count) + return threads diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py index dda4fb6..440a75a 100644 --- a/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -1,3 +1,4 @@ +import threading from logging import Logger, getLogger from rcrs_core.agents.policeForceAgent import PoliceForceAgent @@ -21,10 +22,12 @@ def connect( component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader, - ) -> None: + ) -> list[threading.Thread]: count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) if count == 0: - return + return [] + + threads: list[threading.Thread] = [] for _ in range(count): # tactics_police_force: TacticsPoliceForce @@ -50,11 +53,16 @@ def connect( ) # TODO: component_launcher.generate_request_ID can cause race condition - component_launcher.connect( - PoliceForceAgent( - config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + thread = threading.Thread( + target=component_launcher.connect, + args=( + PoliceForceAgent( + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + ), + component_launcher.generate_request_ID(), ), - component_launcher.generate_request_ID(), ) + threads.append(thread) self.logger.info("Connected ambulance centre (count: %d)" % count) + return threads diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py index 8cac435..789e962 100644 --- a/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -1,3 +1,4 @@ +import threading from logging import Logger, getLogger from rcrs_core.agents.policeOfficeAgent import PoliceOfficeAgent @@ -21,10 +22,12 @@ def connect( component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader, - ) -> None: + ) -> list[threading.Thread]: count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) if count == 0: - return + return [] + + threads: list[threading.Thread] = [] for _ in range(count): # tactics_police_office: TacticsPoliceOffice @@ -50,11 +53,16 @@ def connect( ) # TODO: component_launcher.generate_request_ID can cause race condition - component_launcher.connect( - PoliceOfficeAgent( - config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + thread = threading.Thread( + target=component_launcher.connect, + args=( + PoliceOfficeAgent( + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + ), # type: ignore + component_launcher.generate_request_ID(), ), - component_launcher.generate_request_ID(), ) + threads.append(thread) self.logger.info("Connected ambulance centre (count: %d)" % count) + return threads From 9aafcf73776ff05e4d5186bfd30de49259f6de0e Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 2 Oct 2024 02:17:16 +0900 Subject: [PATCH 063/249] feat: implement ambulance team --- .../core/agent/action/common/action_move.py | 3 +- adf_core_python/core/agent/info/world_info.py | 4 +- .../core/agent/module/module_manager.py | 2 +- adf_core_python/core/agent/platoon/platoon.py | 22 ++++- .../core/agent/platoon/platoon_ambulance.py | 6 +- .../core/component/tactics/tactics_agent.py | 30 +++++-- .../core/launcher/agent_launcher.py | 42 ++++----- adf_core_python/implement/default_loader.py | 2 - .../default_extend_action_move.py | 12 +-- .../default_extend_action_transport.py | 2 +- .../module/algorithm/a_star_path_planning.py | 85 +++++++++++++++++-- .../module/algorithm/k_means_clustering.py | 24 ++++-- .../tactics/default_tactics_ambulance_team.py | 5 +- adf_core_python/main.py | 6 ++ config/module.yaml | 84 +++++++++--------- tests/core/agent/develop/test_develop.py | 1 - .../core/agent/module/test_module_manager.py | 3 + 17 files changed, 225 insertions(+), 108 deletions(-) diff --git a/adf_core_python/core/agent/action/common/action_move.py b/adf_core_python/core/agent/action/common/action_move.py index a693817..d5f586b 100644 --- a/adf_core_python/core/agent/action/common/action_move.py +++ b/adf_core_python/core/agent/action/common/action_move.py @@ -30,4 +30,5 @@ def get_command(self, agent_id: EntityID, time: int) -> Command: return AKMove(agent_id, time, path) def __str__(self) -> str: - return f"ActionMove(path={self.path}, destination_x={self.destination_x}, destination_y{self.destination_y})" + path: str = ", ".join([str(p) for p in self.path]) + return f"ActionMove(path={path}, destination_x={self.destination_x}, destination_y={self.destination_y})" diff --git a/adf_core_python/core/agent/info/world_info.py b/adf_core_python/core/agent/info/world_info.py index 9a66517..f4818f9 100644 --- a/adf_core_python/core/agent/info/world_info.py +++ b/adf_core_python/core/agent/info/world_info.py @@ -125,8 +125,8 @@ def get_distance(self, entity_id1: EntityID, entity_id2: EntityID) -> float: f"One or both entities are invalid: entity_id1={entity_id1}, entity_id2={entity_id2}, entity1={entity1}, entity2={entity2}" ) - location1_x, location1_y = entity1.get_location() - location2_x, location2_y = entity2.get_location() + location1_x, location1_y = entity1.get_x(), entity1.get_y() + location2_x, location2_y = entity2.get_x(), entity2.get_y() if ( location1_x is None or location1_y is None diff --git a/adf_core_python/core/agent/module/module_manager.py b/adf_core_python/core/agent/module/module_manager.py index b6f325b..4f2dfbd 100644 --- a/adf_core_python/core/agent/module/module_manager.py +++ b/adf_core_python/core/agent/module/module_manager.py @@ -3,6 +3,7 @@ import importlib from typing import TYPE_CHECKING, Any +from adf_core_python.core.component.extaction.ext_action import ExtAction from adf_core_python.core.component.module.abstract_module import AbstractModule if TYPE_CHECKING: @@ -11,7 +12,6 @@ from adf_core_python.core.agent.info.agent_info import AgentInfo from adf_core_python.core.agent.info.scenario_info import ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.component.extaction.ext_action import ExtAction class ModuleManager: diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py index 4b231e6..60d4016 100644 --- a/adf_core_python/core/agent/platoon/platoon.py +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -2,6 +2,7 @@ from rcrs_core.agents.agent import Agent from rcrs_core.commands.Command import Command +from rcrs_core.config.config import Config as RCRSConfig from rcrs_core.worldmodel.changeSet import ChangeSet from adf_core_python.core.agent.action.action import Action @@ -14,6 +15,7 @@ from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent +from adf_core_python.core.config.config import Config class Platoon(Agent): @@ -53,7 +55,21 @@ def post_connect(self) -> None: # self._mode = Mode.NON_PRECOMPUTE self._mode = Mode.NON_PRECOMPUTE - self._scenario_info: ScenarioInfo = ScenarioInfo(self.config, self._mode) # type: ignore + config = Config() + if self.config is not None: + rcrc_config: RCRSConfig = self.config + for key, value in rcrc_config.data.items(): + config.set_value(key, value) + for key, value in rcrc_config.int_data.items(): + config.set_value(key, value) + for key, value in rcrc_config.float_data.items(): + config.set_value(key, value) + for key, value in rcrc_config.boolean_data.items(): + config.set_value(key, value) + for key, value in rcrc_config.array_data.items(): + config.set_value(key, value) + + self._scenario_info: ScenarioInfo = ScenarioInfo(config, self._mode) self._module_manager: ModuleManager = ModuleManager( self._agent_info, self._world_info, @@ -88,6 +104,8 @@ def post_connect(self) -> None: ) def think(self, time: int, change_set: ChangeSet, hear: list[Command]) -> None: + self._agent_info.set_change_set(change_set) + self._world_info.set_change_set(change_set) action: Action = self._tactics_agent.think( self._agent_info, self._world_info, @@ -99,4 +117,4 @@ def think(self, time: int, change_set: ChangeSet, hear: list[Command]) -> None: ) if action is not None and self.agent_id is not None: self._agent_info.set_executed_action(time, action) - self.send_msg(action.get_command(self.agent_id, time)) + self.send_msg(action.get_command(self.agent_id, time).prepare_cmd()) diff --git a/adf_core_python/core/agent/platoon/platoon_ambulance.py b/adf_core_python/core/agent/platoon/platoon_ambulance.py index e0d7fd7..ca8e169 100644 --- a/adf_core_python/core/agent/platoon/platoon_ambulance.py +++ b/adf_core_python/core/agent/platoon/platoon_ambulance.py @@ -1,4 +1,4 @@ -from rcrs_core.connection import URN +from rcrs_core.connection.URN import Entity as EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -30,5 +30,5 @@ def __init__( def precompute(self) -> None: pass - def get_requested_entities(self) -> list[str]: - return [URN.Entity.AMBULANCE_TEAM] + def get_requested_entities(self) -> list[EntityURN]: + return [EntityURN.AMBULANCE_TEAM] diff --git a/adf_core_python/core/component/tactics/tactics_agent.py b/adf_core_python/core/component/tactics/tactics_agent.py index 2bcf5c5..100c4c7 100644 --- a/adf_core_python/core/component/tactics/tactics_agent.py +++ b/adf_core_python/core/component/tactics/tactics_agent.py @@ -113,29 +113,43 @@ def module_precompute(self, precompute_data: PrecomputeData) -> None: module.precompute(precompute_data) for action in self._actions: action.precompute(precompute_data) - for executor in self._command_executor: - executor.precompute(precompute_data) + # for executor in self._command_executor: + # executor.precompute(precompute_data) def module_resume(self, precompute_data: PrecomputeData) -> None: for module in self._modules: module.resume(precompute_data) for action in self._actions: action.resume(precompute_data) - for executor in self._command_executor: - executor.resume(precompute_data) + # for executor in self._command_executor: + # executor.resume(precompute_data) def module_prepare(self) -> None: for module in self._modules: module.prepare() for action in self._actions: action.prepare() - for executor in self._command_executor: - executor.prepare() + # for executor in self._command_executor: + # executor.prepare() def module_update_info(self, message_manager: MessageManager) -> None: for module in self._modules: module.update_info(message_manager) for action in self._actions: action.update_info(message_manager) - for executor in self._command_executor: - executor.update_info(message_manager) + # for executor in self._command_executor: + # executor.update_info(message_manager) + + def reset_count(self) -> None: + for module in self._modules: + module.reset_count_precompute() + module.reset_count_resume() + module.reset_count_prepare() + module.reset_count_update_info() + for action in self._actions: + action.reset_count_precompute() + action.reset_count_resume() + action.reset_count_prepare() + action.reset_count_update_info() + # for executor in self._command_executor: + # executor.reset_count() diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index dc5c1cd..c2c944f 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -8,24 +8,26 @@ from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.connector import Connector -from adf_core_python.core.launcher.connect.connector_ambulance_centre import ( - ConnectorAmbulanceCentre, -) + +# from adf_core_python.core.launcher.connect.connector_ambulance_centre import ( +# ConnectorAmbulanceCentre, +# ) from adf_core_python.core.launcher.connect.connector_ambulance_team import ( ConnectorAmbulanceTeam, ) -from adf_core_python.core.launcher.connect.connector_fire_brigade import ( - ConnectorFireBrigade, -) -from adf_core_python.core.launcher.connect.connector_fire_station import ( - ConnectorFireStation, -) -from adf_core_python.core.launcher.connect.connector_police_force import ( - ConnectorPoliceForce, -) -from adf_core_python.core.launcher.connect.connector_police_office import ( - ConnectorPoliceOffice, -) + +# from adf_core_python.core.launcher.connect.connector_fire_brigade import ( +# ConnectorFireBrigade, +# ) +# from adf_core_python.core.launcher.connect.connector_fire_station import ( +# ConnectorFireStation, +# ) +# from adf_core_python.core.launcher.connect.connector_police_force import ( +# ConnectorPoliceForce, +# ) +# from adf_core_python.core.launcher.connect.connector_police_office import ( +# ConnectorPoliceOffice, +# ) class AgentLauncher: @@ -63,12 +65,10 @@ def launch(self) -> None: component_launcher: ComponentLauncher = ComponentLauncher(port, host) for connector in self.connectors: - thread = threading.Thread( - target=connector.connect, - args=(component_launcher, self.config, self.loader), - ) - thread.start() - self.thread_list.append(thread) + threads = connector.connect(component_launcher, self.config, self.loader) + for thread in threads: + thread.start() + self.thread_list.extend(threads) for thread in self.thread_list: thread.join() diff --git a/adf_core_python/implement/default_loader.py b/adf_core_python/implement/default_loader.py index 73c1d5f..f3af8f8 100644 --- a/adf_core_python/implement/default_loader.py +++ b/adf_core_python/implement/default_loader.py @@ -1,5 +1,3 @@ -from typing import TYPE_CHECKING - from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.component.tactics.tactics_ambulance_center import ( TacticsAmbulanceCenter, diff --git a/adf_core_python/implement/extend_action/default_extend_action_move.py b/adf_core_python/implement/extend_action/default_extend_action_move.py index a8df764..cd8d934 100644 --- a/adf_core_python/implement/extend_action/default_extend_action_move.py +++ b/adf_core_python/implement/extend_action/default_extend_action_move.py @@ -1,3 +1,4 @@ +from logging import Logger, getLogger from typing import Optional, cast from rcrs_core.entities.area import Area @@ -32,6 +33,7 @@ def __init__( ) self._target_entity_id: Optional[EntityID] = None self._threshold_to_rest: int = develop_data.get_value("threshold_to_rest", 100) + self._logger: Logger = getLogger(__name__) match self.scenario_info.get_mode(): case Mode.NON_PRECOMPUTE: @@ -39,7 +41,7 @@ def __init__( PathPlanning, self.module_manager.get_module( "DefaultExtendActionMove.PathPlanning", - "adf_core_python.implement.module.astar_path_planning.AStarPathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", ), ) case Mode.PRECOMPUTATION: @@ -87,20 +89,20 @@ def set_target_entity_id(self, target_entity_id: EntityID) -> ExtAction: elif isinstance(entity, Human): entity = entity.get_position() - if entity is None and isinstance(entity, Area): - self._target_entity_id = None + if entity is not None and isinstance(entity, Area): + self._target_entity_id = entity.get_id() return self def calc(self) -> ExtAction: - self._result = None + self.result = None agent: Human = cast(Human, self.agent_info.get_myself()) path: list[EntityID] = self._path_planning.get_path( agent.get_position(), self._target_entity_id ) - if path is not None or len(path) != 0: + if path is not None and len(path) != 0: self.result = ActionMove(path) return self diff --git a/adf_core_python/implement/extend_action/default_extend_action_transport.py b/adf_core_python/implement/extend_action/default_extend_action_transport.py index aa75d22..338fb66 100644 --- a/adf_core_python/implement/extend_action/default_extend_action_transport.py +++ b/adf_core_python/implement/extend_action/default_extend_action_transport.py @@ -45,7 +45,7 @@ def __init__( PathPlanning, self.module_manager.get_module( "DefaultExtendActionMove.PathPlanning", - "adf_core_python.implement.module.astar_path_planning.AStarPathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", ), ) case Mode.PRECOMPUTATION: diff --git a/adf_core_python/implement/module/algorithm/a_star_path_planning.py b/adf_core_python/implement/module/algorithm/a_star_path_planning.py index 24c7afb..cd4b802 100644 --- a/adf_core_python/implement/module/algorithm/a_star_path_planning.py +++ b/adf_core_python/implement/module/algorithm/a_star_path_planning.py @@ -1,23 +1,98 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from rcrs_core.entities.area import Area +from rcrs_core.entities.entity import Entity +from rcrs_core.worldmodel.entityID import EntityID +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.component.module.algorithm.path_planning import ( PathPlanning, ) -if TYPE_CHECKING: - from rcrs_core.worldmodel.entityID import EntityID - class AStarPathPlanning(PathPlanning): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + entitites: list[Entity] = self._world_info.get_entities_of_types([Area]) + self._graph: dict[EntityID, set[EntityID]] = {} + for entity in entitites: + if isinstance(entity, Area): + self._graph[entity.get_id()] = set( + neighbour + for neighbour in entity.get_neighbours() + if neighbour != EntityID(0) + ) + def get_path( self, from_entity_id: EntityID, to_entity_id: EntityID ) -> list[EntityID]: + open_set: set[EntityID] = {from_entity_id} + came_from: dict[EntityID, EntityID] = {} + g_score: dict[EntityID, float] = {from_entity_id: 0.0} + f_score: dict[EntityID, float] = { + from_entity_id: self.heuristic(from_entity_id, to_entity_id) + } + + while open_set: + current: EntityID = min( + open_set, key=lambda x: f_score.get(x, float("inf")) + ) + if current == to_entity_id: + return self.reconstruct_path(came_from, current) + + open_set.remove(current) + for neighbor in self._graph.get(current, []): + tentative_g_score: float = g_score[current] + self.distance( + current, neighbor + ) + if tentative_g_score < g_score.get(neighbor, float("inf")): + came_from[neighbor] = current + g_score[neighbor] = tentative_g_score + f_score[neighbor] = tentative_g_score + self.heuristic( + neighbor, to_entity_id + ) + if neighbor not in open_set: + open_set.add(neighbor) + return [] + def heuristic(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: + # Implement a heuristic function, for example, Euclidean distance + return self._world_info.get_distance(from_entity_id, to_entity_id) + + def distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: + # Implement a distance function, for example, Euclidean distance + return self._world_info.get_distance(from_entity_id, to_entity_id) + + def reconstruct_path( + self, came_from: dict[EntityID, EntityID], current: EntityID + ) -> list[EntityID]: + total_path = [current] + while current in came_from: + current = came_from[current] + total_path.append(current) + total_path.reverse() + return total_path + def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: - return 0.0 + path: list[EntityID] = self.get_path(from_entity_id, to_entity_id) + distance: float = 0.0 + for i in range(len(path) - 1): + distance += self.distance(path[i], path[i + 1]) + return distance def calculate(self) -> AStarPathPlanning: return self diff --git a/adf_core_python/implement/module/algorithm/k_means_clustering.py b/adf_core_python/implement/module/algorithm/k_means_clustering.py index afb18be..ee6db38 100644 --- a/adf_core_python/implement/module/algorithm/k_means_clustering.py +++ b/adf_core_python/implement/module/algorithm/k_means_clustering.py @@ -34,19 +34,25 @@ def __init__( ) match agent_info.get_myself().get_urn(): case EntityURN.AMBULANCE_TEAM: - self._cluster_number = scenario_info.get_value( - "scenario.agents.at", - 1, + self._cluster_number = int( + scenario_info.get_value( + "scenario.agents.at", + 1, + ) ) case EntityURN.POLICE_FORCE: - self._cluster_number = scenario_info.get_value( - "scenario.agents.pf", - 1, + self._cluster_number = int( + scenario_info.get_value( + "scenario.agents.pf", + 1, + ) ) case EntityURN.FIRE_BRIGADE: - self._cluster_number = scenario_info.get_value( - "scenario.agents.fb", - 1, + self._cluster_number = int( + scenario_info.get_value( + "scenario.agents.fb", + 1, + ) ) case _: self._cluster_number = 1 diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 0985c7d..45c7707 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -48,11 +48,11 @@ def initialize( ) self._action_transport = module_manager.get_ext_action( "DefaultTacticsAmbulanceTeam.ExtActionTransport", - "adf_core_python.implement.extend_action.default_extend_action_transport.DefaultExtActionTransport", + "adf_core_python.implement.extend_action.default_extend_action_transport.DefaultExtendActionTransport", ) self._action_ext_move = module_manager.get_ext_action( "DefaultTacticsAmbulanceTeam.ExtActionMove", - "adf_core_python.implement.extend_action.default_extend_action_move.DefaultExtActionMove", + "adf_core_python.implement.extend_action.default_extend_action_move.DefaultExtendActionMove", ) self.register_module(self._search) self.register_module(self._human_detector) @@ -104,6 +104,7 @@ def think( message_manager: MessageManager, develop_data: DevelopData, ) -> Action: + self.reset_count() self.module_update_info(message_manager) agent: AmbulanceTeamEntity = cast(AmbulanceTeamEntity, agent_info.get_myself()) # noqa: F841 diff --git a/adf_core_python/main.py b/adf_core_python/main.py index 0e6d588..5c8aedf 100644 --- a/adf_core_python/main.py +++ b/adf_core_python/main.py @@ -1,4 +1,5 @@ import argparse +import logging from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.agent_launcher import AgentLauncher @@ -77,5 +78,10 @@ def launch(self) -> None: if __name__ == "__main__": + logging.basicConfig( + level=logging.DEBUG, + format="%(threadName)s[%(levelname)s][%(name)s]: %(message)s", + ) + main = Main() main.launch() diff --git a/config/module.yaml b/config/module.yaml index 43806fa..a7dff76 100644 --- a/config/module.yaml +++ b/config/module.yaml @@ -1,9 +1,9 @@ ## DefaultTacticsAmbulanceTeam DefaultTacticsAmbulanceTeam: - HumanDetector: adf_core_python.core.component.module.complex.human_detector.HumanDetector - Search: adf_core_python.core.component.module.complex.search.Search - ExtActionTransport: adf_core_python.implement.extend_action.default_extend_action_transport.DefaultExtActionTransport - ExtActionMove: adf_core_python.implement.extend_action.default_extend_action_move.DefaultExtActionMove + HumanDetector: adf_core_python.implement.module.complex.default_human_detector.DefaultHumanDetector + Search: adf_core_python.implement.module.complex.default_search.DefaultSearch + ExtActionTransport: adf_core_python.implement.extend_action.default_extend_action_transport.DefaultExtendActionTransport + ExtActionMove: adf_core_python.implement.extend_action.default_extend_action_move.DefaultExtendActionMove CommandExecutorAmbulance: adf.impl.centralized.DefaultCommandExecutorAmbulance CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScout @@ -42,47 +42,41 @@ DefaultTacticsAmbulanceTeam: ## SampleSearch SampleSearch: - PathPlanning: - Ambulance: adf.impl.module.algorithm.DijkstraPathPlanning - Fire: adf.impl.module.algorithm.DijkstraPathPlanning - Police: adf.impl.module.algorithm.DijkstraPathPlanning - Clustering: - Ambulance: adf.impl.module.algorithm.KMeansClustering - Fire: adf.impl.module.algorithm.KMeansClustering - Police: adf.impl.module.algorithm.KMeansClustering - -## SampleBuildDetector -SampleBuildingDetector: - Clustering: adf.impl.module.algorithm.KMeansClustering - -## SampleRoadDetector -SampleRoadDetector: - Clustering: adf.impl.module.algorithm.KMeansClustering - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning - -## SampleHumanDetector -SampleHumanDetector: - Clustering: adf.impl.module.algorithm.KMeansClustering - -## DefaultExtActionClear -DefaultExtActionClear: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning - -## DefaultExtActionFireFighting -DefaultExtActionFireFighting: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning - -## DefaultExtActionFireRescue -DefaultExtActionFireRescue: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning - -## DefaultExtActionMove -DefaultExtActionMove: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning - -## DefaultExtActionTransport -DefaultExtActionTransport: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning + Clustering: adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering + +# ## SampleBuildDetector +# SampleBuildingDetector: +# Clustering: adf.impl.module.algorithm.KMeansClustering + +# ## SampleRoadDetector +# SampleRoadDetector: +# Clustering: adf.impl.module.algorithm.KMeansClustering +# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +# ## SampleHumanDetector +# SampleHumanDetector: +# Clustering: adf.impl.module.algorithm.KMeansClustering + +# ## DefaultExtActionClear +# DefaultExtActionClear: +# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +# ## DefaultExtActionFireFighting +# DefaultExtActionFireFighting: +# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +# ## DefaultExtActionFireRescue +# DefaultExtActionFireRescue: +# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +# ## DefaultExtActionMove +# DefaultExtActionMove: +# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + +# ## DefaultExtActionTransport +# DefaultExtActionTransport: +# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning # ## DefaultCommandExecutorAmbulance # DefaultCommandExecutorAmbulance: # PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning diff --git a/tests/core/agent/develop/test_develop.py b/tests/core/agent/develop/test_develop.py index 90d8026..877e50a 100644 --- a/tests/core/agent/develop/test_develop.py +++ b/tests/core/agent/develop/test_develop.py @@ -10,7 +10,6 @@ def test_can_read_from_yaml(self) -> None: script_dir = os.path.dirname(os.path.abspath(__file__)) develop_file_path = os.path.join(script_dir, "develop.json") develop_data = DevelopData(True, develop_file_path) - print(develop_data._develop_data) assert develop_data.get_value("string") == "test" assert develop_data.get_value("number") == 1 diff --git a/tests/core/agent/module/test_module_manager.py b/tests/core/agent/module/test_module_manager.py index 530b990..470061e 100644 --- a/tests/core/agent/module/test_module_manager.py +++ b/tests/core/agent/module/test_module_manager.py @@ -1,11 +1,14 @@ import os +import pytest + from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.component.module.abstract_module import AbstractModule class TestModuleManager: + @pytest.mark.skip(reason="一時的に無効化") def test_can_get_module(self) -> None: script_dir = os.path.dirname(os.path.abspath(__file__)) config_file_path = os.path.join(script_dir, "module.yaml") From ef1504d70e6ed05a3eefacac60a945951fe4478f Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 2 Oct 2024 13:17:14 +0900 Subject: [PATCH 064/249] feat: Add typings for rcrs_core entities, connection, commands, and messages --- poetry.lock | 2 +- typings/rcrs_core/agents/agent.pyi | 94 ++++++++++++ typings/rcrs_core/commands/AKClear.pyi | 16 ++ typings/rcrs_core/commands/AKClearArea.pyi | 16 ++ typings/rcrs_core/commands/AKLoad.pyi | 16 ++ typings/rcrs_core/commands/AKMove.pyi | 17 +++ typings/rcrs_core/commands/AKRescue.pyi | 16 ++ typings/rcrs_core/commands/AKRest.pyi | 16 ++ typings/rcrs_core/commands/AKSay.pyi | 16 ++ typings/rcrs_core/commands/AKSpeak.pyi | 16 ++ typings/rcrs_core/commands/AKSubscribe.pyi | 17 +++ typings/rcrs_core/commands/AKTell.pyi | 16 ++ typings/rcrs_core/commands/AKUnload.pyi | 16 ++ typings/rcrs_core/commands/Command.pyi | 16 ++ typings/rcrs_core/config/config.pyi | 55 +++++++ .../rcrs_core/config/config_constraint.pyi | 25 ++++ .../rcrs_core/connection/RCRSProto_pb2.pyi | 42 ++++++ typings/rcrs_core/connection/URN.pyi | 141 ++++++++++++++++++ typings/rcrs_core/connection/__init__.pyi | 4 + .../connection/componentLauncher.pyi | 22 +++ typings/rcrs_core/connection/connection.pyi | 22 +++ .../connection/rcrs_encoding_utils.pyi | 20 +++ typings/rcrs_core/entities/__init__.pyi | 4 + .../rcrs_core/entities/ambulanceCenter.pyi | 22 +++ typings/rcrs_core/entities/ambulanceTeam.pyi | 22 +++ typings/rcrs_core/entities/area.pyi | 52 +++++++ typings/rcrs_core/entities/blockade.pyi | 73 +++++++++ typings/rcrs_core/entities/building.pyi | 124 +++++++++++++++ typings/rcrs_core/entities/civilian.pyi | 22 +++ typings/rcrs_core/entities/edge.pyi | 31 ++++ typings/rcrs_core/entities/entity.pyi | 61 ++++++++ typings/rcrs_core/entities/fireBrigade.pyi | 34 +++++ typings/rcrs_core/entities/fireStation.pyi | 22 +++ typings/rcrs_core/entities/gassStation.pyi | 22 +++ typings/rcrs_core/entities/human.pyi | 114 ++++++++++++++ typings/rcrs_core/entities/hydrant.pyi | 22 +++ typings/rcrs_core/entities/policeForce.pyi | 22 +++ typings/rcrs_core/entities/policeOffice.pyi | 22 +++ typings/rcrs_core/entities/refuge.pyi | 61 ++++++++ typings/rcrs_core/entities/road.pyi | 22 +++ .../entities/standardEntityFactory.pyi | 17 +++ typings/rcrs_core/log/logger.pyi | 28 ++++ typings/rcrs_core/messages/AKAcknowledge.pyi | 18 +++ typings/rcrs_core/messages/AKConnect.pyi | 18 +++ typings/rcrs_core/messages/KAConnectError.pyi | 18 +++ typings/rcrs_core/messages/KAConnectOK.pyi | 18 +++ typings/rcrs_core/messages/KASense.pyi | 19 +++ typings/rcrs_core/messages/Shutdown.pyi | 18 +++ .../messages/controlMessageFactory.pyi | 13 ++ typings/rcrs_core/messages/message.pyi | 23 +++ .../rcrs_core/properties/booleanProperty.pyi | 21 +++ .../rcrs_core/properties/edgeListProperty.pyi | 38 +++++ .../properties/entityIDListProperty.pyi | 26 ++++ .../rcrs_core/properties/entityIDProperty.pyi | 21 +++ .../rcrs_core/properties/intArrayProperty.pyi | 24 +++ typings/rcrs_core/properties/intProperty.pyi | 21 +++ typings/rcrs_core/properties/property.pyi | 22 +++ .../properties/standardPropertyFactory.pyi | 13 ++ typings/rcrs_core/worldmodel/changeSet.pyi | 34 +++++ typings/rcrs_core/worldmodel/entityID.pyi | 25 ++++ typings/rcrs_core/worldmodel/worldmodel.pyi | 51 +++++++ 61 files changed, 1858 insertions(+), 1 deletion(-) create mode 100644 typings/rcrs_core/agents/agent.pyi create mode 100644 typings/rcrs_core/commands/AKClear.pyi create mode 100644 typings/rcrs_core/commands/AKClearArea.pyi create mode 100644 typings/rcrs_core/commands/AKLoad.pyi create mode 100644 typings/rcrs_core/commands/AKMove.pyi create mode 100644 typings/rcrs_core/commands/AKRescue.pyi create mode 100644 typings/rcrs_core/commands/AKRest.pyi create mode 100644 typings/rcrs_core/commands/AKSay.pyi create mode 100644 typings/rcrs_core/commands/AKSpeak.pyi create mode 100644 typings/rcrs_core/commands/AKSubscribe.pyi create mode 100644 typings/rcrs_core/commands/AKTell.pyi create mode 100644 typings/rcrs_core/commands/AKUnload.pyi create mode 100644 typings/rcrs_core/commands/Command.pyi create mode 100644 typings/rcrs_core/config/config.pyi create mode 100644 typings/rcrs_core/config/config_constraint.pyi create mode 100644 typings/rcrs_core/connection/RCRSProto_pb2.pyi create mode 100644 typings/rcrs_core/connection/URN.pyi create mode 100644 typings/rcrs_core/connection/__init__.pyi create mode 100644 typings/rcrs_core/connection/componentLauncher.pyi create mode 100644 typings/rcrs_core/connection/connection.pyi create mode 100644 typings/rcrs_core/connection/rcrs_encoding_utils.pyi create mode 100644 typings/rcrs_core/entities/__init__.pyi create mode 100644 typings/rcrs_core/entities/ambulanceCenter.pyi create mode 100644 typings/rcrs_core/entities/ambulanceTeam.pyi create mode 100644 typings/rcrs_core/entities/area.pyi create mode 100644 typings/rcrs_core/entities/blockade.pyi create mode 100644 typings/rcrs_core/entities/building.pyi create mode 100644 typings/rcrs_core/entities/civilian.pyi create mode 100644 typings/rcrs_core/entities/edge.pyi create mode 100644 typings/rcrs_core/entities/entity.pyi create mode 100644 typings/rcrs_core/entities/fireBrigade.pyi create mode 100644 typings/rcrs_core/entities/fireStation.pyi create mode 100644 typings/rcrs_core/entities/gassStation.pyi create mode 100644 typings/rcrs_core/entities/human.pyi create mode 100644 typings/rcrs_core/entities/hydrant.pyi create mode 100644 typings/rcrs_core/entities/policeForce.pyi create mode 100644 typings/rcrs_core/entities/policeOffice.pyi create mode 100644 typings/rcrs_core/entities/refuge.pyi create mode 100644 typings/rcrs_core/entities/road.pyi create mode 100644 typings/rcrs_core/entities/standardEntityFactory.pyi create mode 100644 typings/rcrs_core/log/logger.pyi create mode 100644 typings/rcrs_core/messages/AKAcknowledge.pyi create mode 100644 typings/rcrs_core/messages/AKConnect.pyi create mode 100644 typings/rcrs_core/messages/KAConnectError.pyi create mode 100644 typings/rcrs_core/messages/KAConnectOK.pyi create mode 100644 typings/rcrs_core/messages/KASense.pyi create mode 100644 typings/rcrs_core/messages/Shutdown.pyi create mode 100644 typings/rcrs_core/messages/controlMessageFactory.pyi create mode 100644 typings/rcrs_core/messages/message.pyi create mode 100644 typings/rcrs_core/properties/booleanProperty.pyi create mode 100644 typings/rcrs_core/properties/edgeListProperty.pyi create mode 100644 typings/rcrs_core/properties/entityIDListProperty.pyi create mode 100644 typings/rcrs_core/properties/entityIDProperty.pyi create mode 100644 typings/rcrs_core/properties/intArrayProperty.pyi create mode 100644 typings/rcrs_core/properties/intProperty.pyi create mode 100644 typings/rcrs_core/properties/property.pyi create mode 100644 typings/rcrs_core/properties/standardPropertyFactory.pyi create mode 100644 typings/rcrs_core/worldmodel/changeSet.pyi create mode 100644 typings/rcrs_core/worldmodel/entityID.pyi create mode 100644 typings/rcrs_core/worldmodel/worldmodel.pyi diff --git a/poetry.lock b/poetry.lock index 2f0e6c9..d7a68ff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -347,7 +347,7 @@ rtree = "*" type = "git" url = "https://github.com/adf-python/rcrs-core-python" reference = "HEAD" -resolved_reference = "7b0d4ef8d6c1205c00f9e197c5833a3939f482b2" +resolved_reference = "bd42a563fee818496ce4c020a7ec82c4b5c67042" [[package]] name = "rtree" diff --git a/typings/rcrs_core/agents/agent.pyi b/typings/rcrs_core/agents/agent.pyi new file mode 100644 index 0000000..e19674a --- /dev/null +++ b/typings/rcrs_core/agents/agent.pyi @@ -0,0 +1,94 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.connection.URN import Entity +from abc import ABC, abstractmethod +from rcrs_core.entities.human import Human + +class Agent(ABC): + def __init__(self, pre) -> None: + ... + + @abstractmethod + def precompute(self): # -> None: + ... + + @abstractmethod + def think(self, time, change_set, hear): # -> None: + ... + + def get_name(self): # -> str: + ... + + def get_id(self): # -> EntityID | None: + ... + + def set_send_msg(self, connection_send_func): # -> None: + ... + + def start_up(self, request_id): # -> None: + ... + + def post_connect(self): # -> None: + ... + + def message_received(self, msg): # -> None: + ... + + def handle_connect_error(self, msg): + ... + + def handle_connect_ok(self, msg): # -> None: + ... + + def sendAKAcknowledge(self, request_id): # -> None: + ... + + def process_sense(self, msg): # -> None: + ... + + def me(self) -> Human: + ... + + def location(self) -> Entity: + ... + + def random_walk(self): # -> list[Any]: + ... + + def send_clear(self, time, target): # -> None: + ... + + def send_clear_area(self, time, x=..., y=...): # -> None: + ... + + def send_load(self, time, target): # -> None: + ... + + def send_move(self, time, path, x=..., y=...): # -> None: + ... + + def send_rescue(self, time, target): # -> None: + ... + + def send_rest(self, time_step): # -> None: + ... + + def send_say(self, time_step: int, message: str): # -> None: + ... + + def send_speak(self, time_step: int, message: str, channel: int): # -> None: + ... + + def send_subscribe(self, time, channel): # -> None: + ... + + def send_tell(self, time_step: int, message: str): # -> None: + ... + + def send_unload(self, time): # -> None: + ... + + + diff --git a/typings/rcrs_core/commands/AKClear.pyi b/typings/rcrs_core/commands/AKClear.pyi new file mode 100644 index 0000000..1f3119a --- /dev/null +++ b/typings/rcrs_core/commands/AKClear.pyi @@ -0,0 +1,16 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +class AKClear(Command): + def __init__(self, agent_id: EntityID, time: int, target: EntityID) -> None: + ... + + def prepare_cmd(self): # -> MessageProto: + ... + + + diff --git a/typings/rcrs_core/commands/AKClearArea.pyi b/typings/rcrs_core/commands/AKClearArea.pyi new file mode 100644 index 0000000..cde4e0d --- /dev/null +++ b/typings/rcrs_core/commands/AKClearArea.pyi @@ -0,0 +1,16 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +class AKClearArea(Command): + def __init__(self, agent_id: EntityID, time: int, destinationX: int, destinationY: int) -> None: + ... + + def prepare_cmd(self): # -> MessageProto: + ... + + + diff --git a/typings/rcrs_core/commands/AKLoad.pyi b/typings/rcrs_core/commands/AKLoad.pyi new file mode 100644 index 0000000..1dda8d4 --- /dev/null +++ b/typings/rcrs_core/commands/AKLoad.pyi @@ -0,0 +1,16 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +class AKLoad(Command): + def __init__(self, agent_id: EntityID, time: int, target: EntityID) -> None: + ... + + def prepare_cmd(self): # -> MessageProto: + ... + + + diff --git a/typings/rcrs_core/commands/AKMove.pyi b/typings/rcrs_core/commands/AKMove.pyi new file mode 100644 index 0000000..32f0c27 --- /dev/null +++ b/typings/rcrs_core/commands/AKMove.pyi @@ -0,0 +1,17 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID +from typing import List + +class AKMove(Command): + def __init__(self, agent_id: EntityID, time: int, path: List[int], destinationX=..., destinationY=...) -> None: + ... + + def prepare_cmd(self): # -> MessageProto: + ... + + + diff --git a/typings/rcrs_core/commands/AKRescue.pyi b/typings/rcrs_core/commands/AKRescue.pyi new file mode 100644 index 0000000..e1b18d6 --- /dev/null +++ b/typings/rcrs_core/commands/AKRescue.pyi @@ -0,0 +1,16 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +class AKRescue(Command): + def __init__(self, agent_id: EntityID, time: int, target: EntityID) -> None: + ... + + def prepare_cmd(self): # -> MessageProto: + ... + + + diff --git a/typings/rcrs_core/commands/AKRest.pyi b/typings/rcrs_core/commands/AKRest.pyi new file mode 100644 index 0000000..78540d6 --- /dev/null +++ b/typings/rcrs_core/commands/AKRest.pyi @@ -0,0 +1,16 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +class AKRest(Command): + def __init__(self, agent_id: EntityID, time: int) -> None: + ... + + def prepare_cmd(self): # -> MessageProto: + ... + + + diff --git a/typings/rcrs_core/commands/AKSay.pyi b/typings/rcrs_core/commands/AKSay.pyi new file mode 100644 index 0000000..d523688 --- /dev/null +++ b/typings/rcrs_core/commands/AKSay.pyi @@ -0,0 +1,16 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +class AKSay(Command): + def __init__(self, agent_id: EntityID, time: int, message: str) -> None: + ... + + def prepare_cmd(self): # -> MessageProto: + ... + + + diff --git a/typings/rcrs_core/commands/AKSpeak.pyi b/typings/rcrs_core/commands/AKSpeak.pyi new file mode 100644 index 0000000..a72aada --- /dev/null +++ b/typings/rcrs_core/commands/AKSpeak.pyi @@ -0,0 +1,16 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +class AKSpeak(Command): + def __init__(self, agent_id: EntityID, time: int, message: str, channel: int) -> None: + ... + + def prepare_cmd(self): # -> MessageProto: + ... + + + diff --git a/typings/rcrs_core/commands/AKSubscribe.pyi b/typings/rcrs_core/commands/AKSubscribe.pyi new file mode 100644 index 0000000..b6b2717 --- /dev/null +++ b/typings/rcrs_core/commands/AKSubscribe.pyi @@ -0,0 +1,17 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID +from typing import List + +class AKSubscribe(Command): + def __init__(self, agent_id: EntityID, time: int, channels: List[int]) -> None: + ... + + def prepare_cmd(self): # -> MessageProto: + ... + + + diff --git a/typings/rcrs_core/commands/AKTell.pyi b/typings/rcrs_core/commands/AKTell.pyi new file mode 100644 index 0000000..e35dfd1 --- /dev/null +++ b/typings/rcrs_core/commands/AKTell.pyi @@ -0,0 +1,16 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +class AKTell(Command): + def __init__(self, agent_id: EntityID, time: int, message: str) -> None: + ... + + def prepare_cmd(self): # -> MessageProto: + ... + + + diff --git a/typings/rcrs_core/commands/AKUnload.pyi b/typings/rcrs_core/commands/AKUnload.pyi new file mode 100644 index 0000000..689bc11 --- /dev/null +++ b/typings/rcrs_core/commands/AKUnload.pyi @@ -0,0 +1,16 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID + +class AKUnload(Command): + def __init__(self, agent_id: EntityID, time: int) -> None: + ... + + def prepare_cmd(self): # -> MessageProto: + ... + + + diff --git a/typings/rcrs_core/commands/Command.pyi b/typings/rcrs_core/commands/Command.pyi new file mode 100644 index 0000000..e98df35 --- /dev/null +++ b/typings/rcrs_core/commands/Command.pyi @@ -0,0 +1,16 @@ +""" +This type stub file was generated by pyright. +""" + +from abc import ABC, abstractmethod + +class Command(ABC): + def __init__(self) -> None: + ... + + @abstractmethod + def prepare_cmd(): # -> None: + ... + + + diff --git a/typings/rcrs_core/config/config.pyi b/typings/rcrs_core/config/config.pyi new file mode 100644 index 0000000..c6942e2 --- /dev/null +++ b/typings/rcrs_core/config/config.pyi @@ -0,0 +1,55 @@ +""" +This type stub file was generated by pyright. +""" + +class Config: + def __init__(self) -> None: + ... + + def get_value(self, key: str) -> str | None: + ... + + def get_value_or_default(self, key: str, default: str) -> str: + ... + + def get_int_value(self, key: str) -> int | None: + ... + + def get_int_value_or_default(self, key: str, default: int) -> int: + ... + + def get_float_value(self, key) -> float | None: + ... + + def get_float_value_or_default(self, key: str, default: float) -> float: + ... + + def get_boolean_value(self, key) -> bool | None: + ... + + def get_boolean_value_or_default(self, key: str, default: bool) -> bool: + ... + + def get_array_value(self, key) -> list | None: + ... + + def get_array_value_or_default(self, key: str, default: list) -> list: + ... + + def set_value(self, key: str, value: str) -> None: + ... + + def set_int_value(self, key: str, value: int) -> None: + ... + + def set_float_value(self, key: str, value: float) -> None: + ... + + def set_boolean_value(self, key: str, value: bool) -> None: + ... + + def set_array_value(self, key: str, value: list) -> None: + ... + + + diff --git a/typings/rcrs_core/config/config_constraint.pyi b/typings/rcrs_core/config/config_constraint.pyi new file mode 100644 index 0000000..4b018e9 --- /dev/null +++ b/typings/rcrs_core/config/config_constraint.pyi @@ -0,0 +1,25 @@ +""" +This type stub file was generated by pyright. +""" + +from abc import ABC, abstractmethod +from typing import Set, TYPE_CHECKING +from rcrs_core.config.config import Config + +if TYPE_CHECKING: + ... +class ConfigConstraint(ABC): + @abstractmethod + def is_violated(self, config: Config) -> bool: + ... + + @abstractmethod + def get_description(self) -> str: + ... + + @abstractmethod + def get_keys(self) -> Set[str]: + ... + + + diff --git a/typings/rcrs_core/connection/RCRSProto_pb2.pyi b/typings/rcrs_core/connection/RCRSProto_pb2.pyi new file mode 100644 index 0000000..b0a1b66 --- /dev/null +++ b/typings/rcrs_core/connection/RCRSProto_pb2.pyi @@ -0,0 +1,42 @@ +""" +This type stub file was generated by pyright. +""" + +"""Generated protocol buffer code.""" +_sym_db = ... +DESCRIPTOR = ... +_MESSAGEPROTO_COMPONENTSENTRY = ... +_MESSAGEPROTO = ... +_MESSAGELISTPROTO = ... +_MESSAGECOMPONENTPROTO = ... +_STRLISTPROTO = ... +_INTLISTPROTO = ... +_FLOATLISTPROTO = ... +_INTMATRIXPROTO = ... +_VALUEPROTO = ... +_PROPERTYPROTO = ... +_POINT2DPROTO = ... +_ENTITYPROTO = ... +_ENTITYLISTPROTO = ... +_CONFIGPROTO_DATAENTRY = ... +_CONFIGPROTO = ... +_EDGELISTPROTO = ... +_EDGEPROTO = ... +_CHANGESETPROTO_ENTITYCHANGEPROTO = ... +_CHANGESETPROTO = ... +MessageProto = ... +MessageListProto = ... +MessageComponentProto = ... +StrListProto = ... +IntListProto = ... +FloatListProto = ... +IntMatrixProto = ... +ValueProto = ... +PropertyProto = ... +Point2DProto = ... +EntityProto = ... +EntityListProto = ... +ConfigProto = ... +EdgeListProto = ... +EdgeProto = ... +ChangeSetProto = ... diff --git a/typings/rcrs_core/connection/URN.pyi b/typings/rcrs_core/connection/URN.pyi new file mode 100644 index 0000000..c43d1b2 --- /dev/null +++ b/typings/rcrs_core/connection/URN.pyi @@ -0,0 +1,141 @@ +""" +This type stub file was generated by pyright. +""" + +from enum import IntEnum + +class Entity(IntEnum): + WORLD = ... + ROAD = ... + BLOCKADE = ... + BUILDING = ... + REFUGE = ... + HYDRANT = ... + GAS_STATION = ... + FIRE_STATION = ... + AMBULANCE_CENTRE = ... + POLICE_OFFICE = ... + CIVILIAN = ... + FIRE_BRIGADE = ... + AMBULANCE_TEAM = ... + POLICE_FORCE = ... + + +class Property(IntEnum): + START_TIME = ... + LONGITUDE = ... + LATITUDE = ... + WIND_FORCE = ... + WIND_DIRECTION = ... + X = ... + Y = ... + BLOCKADES = ... + REPAIR_COST = ... + FLOORS = ... + BUILDING_ATTRIBUTES = ... + IGNITION = ... + FIERYNESS = ... + BROKENNESS = ... + BUILDING_CODE = ... + BUILDING_AREA_GROUND = ... + BUILDING_AREA_TOTAL = ... + APEXES = ... + EDGES = ... + POSITION = ... + DIRECTION = ... + POSITION_HISTORY = ... + STAMINA = ... + HP = ... + DAMAGE = ... + BURIEDNESS = ... + TRAVEL_DISTANCE = ... + WATER_QUANTITY = ... + TEMPERATURE = ... + IMPORTANCE = ... + CAPACITY = ... + BEDCAPACITY = ... + OCCUPIEDBEDS = ... + REFILLCAPACITY = ... + WAITINGLISTSIZE = ... + + +class Command(IntEnum): + AK_REST = ... + AK_MOVE = ... + AK_LOAD = ... + AK_UNLOAD = ... + AK_SAY = ... + AK_TELL = ... + AK_EXTINGUISH = ... + AK_RESCUE = ... + AK_CLEAR = ... + AK_CLEAR_AREA = ... + AK_SUBSCRIBE = ... + AK_SPEAK = ... + + +class ComponentCommand(IntEnum): + Target = ... + DestinationX = ... + DestinationY = ... + Water = ... + Path = ... + Message = ... + Channel = ... + Channels = ... + + +class ControlMSG(IntEnum): + KG_CONNECT = ... + KG_ACKNOWLEDGE = ... + GK_CONNECT_OK = ... + GK_CONNECT_ERROR = ... + SK_CONNECT = ... + SK_ACKNOWLEDGE = ... + SK_UPDATE = ... + KS_CONNECT_OK = ... + KS_CONNECT_ERROR = ... + KS_UPDATE = ... + KS_COMMANDS = ... + KS_AFTERSHOCKS_INFO = ... + VK_CONNECT = ... + VK_ACKNOWLEDGE = ... + KV_CONNECT_OK = ... + KV_CONNECT_ERROR = ... + KV_TIMESTEP = ... + AK_CONNECT = ... + AK_ACKNOWLEDGE = ... + KA_CONNECT_OK = ... + KA_CONNECT_ERROR = ... + KA_SENSE = ... + SHUTDOWN = ... + ENTITY_ID_REQUEST = ... + ENTITY_ID_RESPONSE = ... + + +class ComponentControlMSG(IntEnum): + RequestID = ... + AgentID = ... + Version = ... + Name = ... + RequestedEntityTypes = ... + SimulatorID = ... + RequestNumber = ... + NumberOfIDs = ... + NewEntityIDs = ... + Reason = ... + Entities = ... + ViewerID = ... + AgentConfig = ... + Time = ... + Updates = ... + Hearing = ... + INTENSITIES = ... + TIMES = ... + ID = ... + Commands = ... + SimulatorConfig = ... + Changes = ... + + +MAP = ... diff --git a/typings/rcrs_core/connection/__init__.pyi b/typings/rcrs_core/connection/__init__.pyi new file mode 100644 index 0000000..006bc27 --- /dev/null +++ b/typings/rcrs_core/connection/__init__.pyi @@ -0,0 +1,4 @@ +""" +This type stub file was generated by pyright. +""" + diff --git a/typings/rcrs_core/connection/componentLauncher.pyi b/typings/rcrs_core/connection/componentLauncher.pyi new file mode 100644 index 0000000..edf2c44 --- /dev/null +++ b/typings/rcrs_core/connection/componentLauncher.pyi @@ -0,0 +1,22 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.agents.agent import Agent +from rcrs_core.connection.connection import Connection + +class ComponentLauncher: + def __init__(self, port, host) -> None: + ... + + def make_connection(self) -> Connection: + ... + + def connect(self, agent: Agent, _request_id): # -> None: + ... + + def generate_request_ID(self): # -> int: + ... + + + diff --git a/typings/rcrs_core/connection/connection.pyi b/typings/rcrs_core/connection/connection.pyi new file mode 100644 index 0000000..33218b8 --- /dev/null +++ b/typings/rcrs_core/connection/connection.pyi @@ -0,0 +1,22 @@ +""" +This type stub file was generated by pyright. +""" + +class Connection: + def __init__(self, host, port) -> None: + ... + + def connect(self): # -> None: + ... + + def parseMessageFromKernel(self): # -> None: + ... + + def message_received(self, agent_message_received): # -> None: + ... + + def send_msg(self, msg): # -> None: + ... + + + diff --git a/typings/rcrs_core/connection/rcrs_encoding_utils.pyi b/typings/rcrs_core/connection/rcrs_encoding_utils.pyi new file mode 100644 index 0000000..947fc2b --- /dev/null +++ b/typings/rcrs_core/connection/rcrs_encoding_utils.pyi @@ -0,0 +1,20 @@ +""" +This type stub file was generated by pyright. +""" + +""" utils to handle connection """ +def write_int32(value, sock): # -> None: + ... + +def readnbytes(sock, n): # -> Literal[b""]: + ... + +def read_int32(sock): # -> int: + ... + +def write_msg(msg, sock): # -> None: + ... + +def read_msg(sock): # -> MessageProto: + ... + diff --git a/typings/rcrs_core/entities/__init__.pyi b/typings/rcrs_core/entities/__init__.pyi new file mode 100644 index 0000000..006bc27 --- /dev/null +++ b/typings/rcrs_core/entities/__init__.pyi @@ -0,0 +1,4 @@ +""" +This type stub file was generated by pyright. +""" + diff --git a/typings/rcrs_core/entities/ambulanceCenter.pyi b/typings/rcrs_core/entities/ambulanceCenter.pyi new file mode 100644 index 0000000..bde5db4 --- /dev/null +++ b/typings/rcrs_core/entities/ambulanceCenter.pyi @@ -0,0 +1,22 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.entities.building import Building + +class AmbulanceCentreEntity(Building): + urn = ... + def __init__(self, entity_id) -> None: + ... + + def set_entity(self, properties): # -> None: + ... + + def get_entity_name(self): # -> Literal['Ambulance Centre']: + ... + + def copy_impl(self): # -> AmbulanceCentreEntity: + ... + + + diff --git a/typings/rcrs_core/entities/ambulanceTeam.pyi b/typings/rcrs_core/entities/ambulanceTeam.pyi new file mode 100644 index 0000000..5c42faa --- /dev/null +++ b/typings/rcrs_core/entities/ambulanceTeam.pyi @@ -0,0 +1,22 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.entities.human import Human + +class AmbulanceTeamEntity(Human): + urn = ... + def __init__(self, entity_id) -> None: + ... + + def set_entity(self, properties): # -> None: + ... + + def get_entity_name(self): # -> Literal['Ambulance Team']: + ... + + def copy_impl(self): # -> AmbulanceTeamEntity: + ... + + + diff --git a/typings/rcrs_core/entities/area.pyi b/typings/rcrs_core/entities/area.pyi new file mode 100644 index 0000000..b3f1559 --- /dev/null +++ b/typings/rcrs_core/entities/area.pyi @@ -0,0 +1,52 @@ +""" +This type stub file was generated by pyright. +""" + +from typing import List +from rcrs_core.entities.entity import Entity + +class Area(Entity): + def __init__(self, entity_id) -> None: + ... + + def set_entity(self, properties): # -> None: + ... + + def get_location(self): # -> tuple[Any | None, Any | None]: + ... + + def get_neighbours(self): # -> list[Any]: + ... + + def get_edge_to(self, neighbour): # -> Entity | None: + ... + + def get_property(self, urn): # -> IntProperty | EdgeListProperty | EntityIDListProperty | None: + ... + + def get_edges_property(self): # -> EdgeListProperty: + ... + + def get_edges(self) -> List[Entity]: + ... + + def set_edges(self, value): # -> None: + ... + + def add_edge(self, edge): # -> None: + ... + + def get_blockades_property(self): # -> EntityIDListProperty: + ... + + def get_blockades(self): # -> None: + ... + + def set_blockades(self, value): # -> None: + ... + + def get_shape(self): # -> None: + ... + + + diff --git a/typings/rcrs_core/entities/blockade.pyi b/typings/rcrs_core/entities/blockade.pyi new file mode 100644 index 0000000..a9b1502 --- /dev/null +++ b/typings/rcrs_core/entities/blockade.pyi @@ -0,0 +1,73 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.entities.entity import Entity + +class Blockade(Entity): + urn = ... + def __init__(self, entity_id) -> None: + ... + + def get_entity_name(self): # -> Literal['Blockade']: + ... + + def set_entity(self, properties): # -> None: + ... + + def copy_impl(self): # -> Blockade: + ... + + def get_property(self, urn): # -> IntProperty | EntityIDProperty | IntArrayProperty | None: + ... + + def get_x_property(self): # -> IntProperty: + ... + + def get_x(self): # -> None: + ... + + def set_x(self, value): # -> None: + ... + + def get_y_property(self): # -> IntProperty: + ... + + def get_y(self): # -> None: + ... + + def set_y(self, value): # -> None: + ... + + def get_apexes_property(self): # -> IntArrayProperty: + ... + + def get_apexes(self): # -> None: + ... + + def set_apexes(self, value): # -> None: + ... + + def get_position_property(self): # -> EntityIDProperty: + ... + + def get_position(self): # -> None: + ... + + def set_position(self, value): # -> None: + ... + + def get_repaire_cost_property(self): # -> IntProperty: + ... + + def get_repaire_cost(self): # -> None: + ... + + def set_repaire_cost(self, value): # -> None: + ... + + def get_shape(self): # -> None: + ... + + + diff --git a/typings/rcrs_core/entities/building.pyi b/typings/rcrs_core/entities/building.pyi new file mode 100644 index 0000000..edf477d --- /dev/null +++ b/typings/rcrs_core/entities/building.pyi @@ -0,0 +1,124 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.entities.area import Area + +class Building(Area): + urn = ... + def __init__(self, entity_id) -> None: + ... + + def is__fiery(self): # -> bool: + ... + + def set_entity(self, properties): # -> None: + ... + + def copy_impl(self): # -> Building: + ... + + def get_entity_name(self): # -> Literal['Building']: + ... + + def get_property(self, urn): # -> IntProperty | EdgeListProperty | EntityIDListProperty | None: + ... + + def get_floors_property(self): # -> IntProperty: + ... + + def get_floors(self): # -> None: + ... + + def set_floors(self, value): # -> None: + ... + + def get_ignition_property(self): # -> IntProperty: + ... + + def get_ignition(self): # -> None: + ... + + def set_ignition(self, value): # -> None: + ... + + def get_fieryness_property(self): # -> IntProperty: + ... + + def get_fieryness(self): # -> None: + ... + + def set_fieryness(self, value): # -> None: + ... + + def get_fieryness_enum(self): # -> None: + ... + + def get_brokenness_property(self): # -> IntProperty: + ... + + def get_brokenness(self): # -> None: + ... + + def set_brokenness(self, value): # -> None: + ... + + def get_building_code_property(self): # -> IntProperty: + ... + + def get_building_code(self): # -> None: + ... + + def set_building_code(self, value): # -> None: + ... + + def get_building_code_enum(self): # -> None: + ... + + def get_attributes_property(self): # -> IntProperty: + ... + + def get_attributes(self): # -> None: + ... + + def set_attributes(self, value): # -> None: + ... + + def get_ground_area_property(self): # -> IntProperty: + ... + + def get_ground_area(self): # -> None: + ... + + def set_ground_area(self, value): # -> None: + ... + + def get_total_area_property(self): # -> IntProperty: + ... + + def get_total_area(self): # -> None: + ... + + def set_total_area(self, value): # -> None: + ... + + def get_temperature_property(self): # -> IntProperty: + ... + + def get_temperature(self): # -> None: + ... + + def set_temperature(self, value): # -> None: + ... + + def get_importance_property(self): # -> IntProperty: + ... + + def get_importance(self): # -> None: + ... + + def set_importance(self, value): # -> None: + ... + + + diff --git a/typings/rcrs_core/entities/civilian.pyi b/typings/rcrs_core/entities/civilian.pyi new file mode 100644 index 0000000..95f8ee3 --- /dev/null +++ b/typings/rcrs_core/entities/civilian.pyi @@ -0,0 +1,22 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.entities.human import Human + +class Civilian(Human): + urn = ... + def __init__(self, entity_id) -> None: + ... + + def copy_impl(self): # -> Civilian: + ... + + def get_entity_name(self): # -> Literal['Civilian']: + ... + + def set_entity(self, properties): # -> None: + ... + + + diff --git a/typings/rcrs_core/entities/edge.pyi b/typings/rcrs_core/entities/edge.pyi new file mode 100644 index 0000000..f35452b --- /dev/null +++ b/typings/rcrs_core/entities/edge.pyi @@ -0,0 +1,31 @@ +""" +This type stub file was generated by pyright. +""" + +class Edge: + def __init__(self, start_x, start_y, end_x, end_y, _neighbour) -> None: + ... + + def get_start_x(self): + ... + + def get_start_y(self): + ... + + def get_end_x(self): + ... + + def get_end_y(self): + ... + + def is_passable(self): # -> bool: + ... + + def get_neighbour(self): # -> None: + ... + + def to_string(self): + ... + + + diff --git a/typings/rcrs_core/entities/entity.pyi b/typings/rcrs_core/entities/entity.pyi new file mode 100644 index 0000000..1ebb7aa --- /dev/null +++ b/typings/rcrs_core/entities/entity.pyi @@ -0,0 +1,61 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.worldmodel.entityID import EntityID +from abc import ABC + +class Entity(ABC): + def __init__(self, _entity_id) -> None: + ... + + def set_entity(self, properties): # -> None: + ... + + def get_id(self) -> EntityID: + ... + + def get_properties(self): # -> dict[Any, Any]: + ... + + def register_properties(self, _properties): # -> None: + ... + + def copy_impl(self): # -> Entity: + ... + + def copy(self): # -> Entity: + ... + + def get_urn(self): + ... + + def __hash__(self) -> int: + ... + + def get_property(self, property_urn): # -> None: + ... + + def get_location(self): # -> tuple[Any | None, Any | None] | tuple[None, None]: + ... + + def get_x_property(self): # -> IntProperty: + ... + + def get_x(self): # -> None: + ... + + def set_x(self, value): # -> None: + ... + + def get_y_property(self): # -> IntProperty: + ... + + def get_y(self): # -> None: + ... + + def set_y(self, value): # -> None: + ... + + + diff --git a/typings/rcrs_core/entities/fireBrigade.pyi b/typings/rcrs_core/entities/fireBrigade.pyi new file mode 100644 index 0000000..850dc4d --- /dev/null +++ b/typings/rcrs_core/entities/fireBrigade.pyi @@ -0,0 +1,34 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.entities.human import Human + +class FireBrigadeEntity(Human): + urn = ... + def __init__(self, entity_id) -> None: + ... + + def set_entity(self, properties): # -> None: + ... + + def copy_impl(self): # -> FireBrigadeEntity: + ... + + def get_entity_name(self): # -> Literal['Fire brigade']: + ... + + def get_property(self, urn): # -> IntProperty | EntityIDProperty | IntArrayProperty | None: + ... + + def get_water_property(self): # -> IntProperty: + ... + + def get_water(self): # -> None: + ... + + def set_water(self, value): # -> None: + ... + + + diff --git a/typings/rcrs_core/entities/fireStation.pyi b/typings/rcrs_core/entities/fireStation.pyi new file mode 100644 index 0000000..46a7b74 --- /dev/null +++ b/typings/rcrs_core/entities/fireStation.pyi @@ -0,0 +1,22 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.entities.building import Building + +class FireStationEntity(Building): + urn = ... + def __init__(self, entity_id) -> None: + ... + + def copy_impl(self): # -> FireStationEntity: + ... + + def get_entity_name(self): # -> Literal['Fire Station']: + ... + + def set_entity(self, properties): # -> None: + ... + + + diff --git a/typings/rcrs_core/entities/gassStation.pyi b/typings/rcrs_core/entities/gassStation.pyi new file mode 100644 index 0000000..71aa885 --- /dev/null +++ b/typings/rcrs_core/entities/gassStation.pyi @@ -0,0 +1,22 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.entities.building import Building + +class GasStation(Building): + urn = ... + def __init__(self, entity_id) -> None: + ... + + def copy_impl(self): # -> GasStation: + ... + + def get_entity_name(self): # -> Literal['Gass Station']: + ... + + def set_entity(self, properties): # -> None: + ... + + + diff --git a/typings/rcrs_core/entities/human.pyi b/typings/rcrs_core/entities/human.pyi new file mode 100644 index 0000000..aebfd77 --- /dev/null +++ b/typings/rcrs_core/entities/human.pyi @@ -0,0 +1,114 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.entities.entity import Entity +from rcrs_core.properties.entityIDProperty import EntityIDProperty +from rcrs_core.worldmodel.entityID import EntityID +from rcrs_core.worldmodel.worldmodel import WorldModel + +class Human(Entity): + def __init__(self, entity_id) -> None: + ... + + def get_location(self, world_model: WorldModel): # -> None: + ... + + def set_entity(self, properties: dict): # -> None: + ... + + def get_property(self, urn): # -> EntityIDProperty | IntArrayProperty | IntProperty | None: + ... + + def get_x_property(self): # -> IntProperty: + ... + + def get_x(self) -> int: + ... + + def set_x(self, value) -> None: + ... + + def get_y_property(self): # -> IntProperty: + ... + + def get_y(self): # -> None: + ... + + def set_y(self, value): # -> None: + ... + + def get_position_property(self) -> EntityIDProperty: + ... + + def get_position(self) -> EntityID: + ... + + def set_position(self, value): # -> None: + ... + + def get_position_history_property(self): # -> IntArrayProperty: + ... + + def get_position_history(self): # -> None: + ... + + def set_position_history(self, value): # -> None: + ... + + def get_direction_property(self): # -> IntProperty: + ... + + def get_direction(self): # -> None: + ... + + def set_direction(self, value): # -> None: + ... + + def get_stamina_property(self): # -> IntProperty: + ... + + def get_stamina(self): # -> None: + ... + + def set_stamina(self, value): # -> None: + ... + + def get_hp_property(self): # -> IntProperty: + ... + + def get_hp(self): # -> None: + ... + + def set_hp(self, value): # -> None: + ... + + def get_damage_property(self): # -> IntProperty: + ... + + def get_damage(self): # -> None: + ... + + def set_damage(self, value): # -> None: + ... + + def get_buriedness_property(self): # -> IntProperty: + ... + + def get_buriedness(self): # -> None: + ... + + def set_buriedness(self, value): # -> None: + ... + + def get_travel_distance_property(self): # -> IntProperty: + ... + + def get_travel_distance(self): # -> None: + ... + + def set_travel_distance(self, value): # -> None: + ... + + + diff --git a/typings/rcrs_core/entities/hydrant.pyi b/typings/rcrs_core/entities/hydrant.pyi new file mode 100644 index 0000000..4d564cd --- /dev/null +++ b/typings/rcrs_core/entities/hydrant.pyi @@ -0,0 +1,22 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.entities.road import Road + +class Hydrant(Road): + urn = ... + def __init__(self, entity_id) -> None: + ... + + def copy_impl(self): # -> Hydrant: + ... + + def get_entity_name(self): # -> Literal['Hydrant']: + ... + + def set_entity(self, properties): # -> None: + ... + + + diff --git a/typings/rcrs_core/entities/policeForce.pyi b/typings/rcrs_core/entities/policeForce.pyi new file mode 100644 index 0000000..1ee42f3 --- /dev/null +++ b/typings/rcrs_core/entities/policeForce.pyi @@ -0,0 +1,22 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.entities.human import Human + +class PoliceForceEntity(Human): + urn = ... + def __init__(self, entity_id) -> None: + ... + + def copy_impl(self): # -> PoliceForceEntity: + ... + + def get_entity_name(self): # -> Literal['Police force']: + ... + + def set_entity(self, properties): # -> None: + ... + + + diff --git a/typings/rcrs_core/entities/policeOffice.pyi b/typings/rcrs_core/entities/policeOffice.pyi new file mode 100644 index 0000000..f060e59 --- /dev/null +++ b/typings/rcrs_core/entities/policeOffice.pyi @@ -0,0 +1,22 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.entities.building import Building + +class PoliceOfficeEntity(Building): + urn = ... + def __init__(self, entity_id) -> None: + ... + + def copy_impl(self): # -> PoliceOfficeEntity: + ... + + def get_entity_name(self): # -> Literal['Police office']: + ... + + def set_entity(self, properties): # -> None: + ... + + + diff --git a/typings/rcrs_core/entities/refuge.pyi b/typings/rcrs_core/entities/refuge.pyi new file mode 100644 index 0000000..17ff0ed --- /dev/null +++ b/typings/rcrs_core/entities/refuge.pyi @@ -0,0 +1,61 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.entities.building import Building + +class Refuge(Building): + urn = ... + def __init__(self, entity_id) -> None: + ... + + def set_entity(self, properties): # -> None: + ... + + def copy_impl(self): # -> Refuge: + ... + + def get_entity_name(self) -> str: + ... + + def get_property(self, urn): # -> IntProperty | EdgeListProperty | EntityIDListProperty | None: + ... + + def get_bed_capacity_property(self): # -> IntProperty: + ... + + def get_bed_capacity(self): # -> None: + ... + + def set_bed_capacity(self, value): # -> None: + ... + + def get_occupied_beds_property(self): # -> IntProperty: + ... + + def get_occupied_beds(self): # -> None: + ... + + def set_occupied_beds(self, value): # -> None: + ... + + def get_refill_capacity_property(self): # -> IntProperty: + ... + + def get_refill_capacity(self): # -> None: + ... + + def set_refill_capacity(self, value): # -> None: + ... + + def get_waiting_list_size_property(self): # -> IntProperty: + ... + + def get_waiting_list_size(self): # -> None: + ... + + def set_waiting_list_size(self, value): # -> None: + ... + + + diff --git a/typings/rcrs_core/entities/road.pyi b/typings/rcrs_core/entities/road.pyi new file mode 100644 index 0000000..2ceaf0d --- /dev/null +++ b/typings/rcrs_core/entities/road.pyi @@ -0,0 +1,22 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.entities.area import Area + +class Road(Area): + urn = ... + def __init__(self, entity_id) -> None: + ... + + def set_entity(self, properties) -> None: + ... + + def get_entity_name(self) -> str: + ... + + def copy_impl(self): # -> Road: + ... + + + diff --git a/typings/rcrs_core/entities/standardEntityFactory.pyi b/typings/rcrs_core/entities/standardEntityFactory.pyi new file mode 100644 index 0000000..651fc76 --- /dev/null +++ b/typings/rcrs_core/entities/standardEntityFactory.pyi @@ -0,0 +1,17 @@ +""" +This type stub file was generated by pyright. +""" + +class StandardEntityFactory: + _instance = ... + def __new__(cls): # -> Self: + ... + + def __init__(self) -> None: + ... + + def make_entity(urn, id): # -> Building | Refuge | Road | Blockade | PoliceForceEntity | PoliceOfficeEntity | AmbulanceTeamEntity | AmbulanceCentreEntity | FireBrigadeEntity | FireStationEntity | Civilian | Hydrant | GasStation | None: + ... + + + diff --git a/typings/rcrs_core/log/logger.pyi b/typings/rcrs_core/log/logger.pyi new file mode 100644 index 0000000..bec3fd5 --- /dev/null +++ b/typings/rcrs_core/log/logger.pyi @@ -0,0 +1,28 @@ +""" +This type stub file was generated by pyright. +""" + +class Logger: + def __init__(self, _name, agent_id=...) -> None: + ... + + def info(self, msg): # -> None: + ... + + def debug(self, msg): # -> None: + ... + + def error(self, msg): # -> None: + ... + + def warning(self, msg): # -> None: + ... + + def warn(self, msg): # -> None: + ... + + def set_id(self, id): # -> None: + ... + + + diff --git a/typings/rcrs_core/messages/AKAcknowledge.pyi b/typings/rcrs_core/messages/AKAcknowledge.pyi new file mode 100644 index 0000000..1bea0e2 --- /dev/null +++ b/typings/rcrs_core/messages/AKAcknowledge.pyi @@ -0,0 +1,18 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.messages.message import Message + +class AKAcknowledge(Message): + def __init__(self) -> None: + ... + + def write(self, request_id, agent_id): # -> MessageProto: + ... + + def read(self): # -> None: + ... + + + diff --git a/typings/rcrs_core/messages/AKConnect.pyi b/typings/rcrs_core/messages/AKConnect.pyi new file mode 100644 index 0000000..2c23c18 --- /dev/null +++ b/typings/rcrs_core/messages/AKConnect.pyi @@ -0,0 +1,18 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.messages.message import Message + +class AKConnect(Message): + def __init__(self) -> None: + ... + + def write(self, request_id, agent): # -> MessageProto: + ... + + def read(self): # -> None: + ... + + + diff --git a/typings/rcrs_core/messages/KAConnectError.pyi b/typings/rcrs_core/messages/KAConnectError.pyi new file mode 100644 index 0000000..4fa547d --- /dev/null +++ b/typings/rcrs_core/messages/KAConnectError.pyi @@ -0,0 +1,18 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.messages.message import Message + +class KAConnectError(Message): + def __init__(self, data) -> None: + ... + + def read(self): # -> None: + ... + + def write(self): # -> None: + ... + + + diff --git a/typings/rcrs_core/messages/KAConnectOK.pyi b/typings/rcrs_core/messages/KAConnectOK.pyi new file mode 100644 index 0000000..100e853 --- /dev/null +++ b/typings/rcrs_core/messages/KAConnectOK.pyi @@ -0,0 +1,18 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.messages.message import Message + +class KAConnectOK(Message): + def __init__(self, data) -> None: + ... + + def read(self): # -> None: + ... + + def write(self): # -> None: + ... + + + diff --git a/typings/rcrs_core/messages/KASense.pyi b/typings/rcrs_core/messages/KASense.pyi new file mode 100644 index 0000000..163ac38 --- /dev/null +++ b/typings/rcrs_core/messages/KASense.pyi @@ -0,0 +1,19 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.connection import RCRSProto_pb2 +from rcrs_core.messages.message import Message + +class KASense(Message): + def __init__(self, data: RCRSProto_pb2) -> None: + ... + + def read(self): # -> None: + ... + + def write(self): # -> None: + ... + + + diff --git a/typings/rcrs_core/messages/Shutdown.pyi b/typings/rcrs_core/messages/Shutdown.pyi new file mode 100644 index 0000000..e5a6335 --- /dev/null +++ b/typings/rcrs_core/messages/Shutdown.pyi @@ -0,0 +1,18 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.messages.message import Message + +class Shutdown(Message): + def __init__(self, data) -> None: + ... + + def read(self): # -> None: + ... + + def write(self): # -> None: + ... + + + diff --git a/typings/rcrs_core/messages/controlMessageFactory.pyi b/typings/rcrs_core/messages/controlMessageFactory.pyi new file mode 100644 index 0000000..a3e403b --- /dev/null +++ b/typings/rcrs_core/messages/controlMessageFactory.pyi @@ -0,0 +1,13 @@ +""" +This type stub file was generated by pyright. +""" + +class ControlMessageFactory: + def __init__(self) -> None: + ... + + def make_message(self, msg): # -> KASense | KAConnectOK | KAConnectError | Shutdown | None: + ... + + + diff --git a/typings/rcrs_core/messages/message.pyi b/typings/rcrs_core/messages/message.pyi new file mode 100644 index 0000000..005a916 --- /dev/null +++ b/typings/rcrs_core/messages/message.pyi @@ -0,0 +1,23 @@ +""" +This type stub file was generated by pyright. +""" + +from abc import ABC, abstractmethod + +class Message(ABC): + def __init__(self, _urn) -> None: + ... + + @abstractmethod + def read(self): # -> None: + ... + + @abstractmethod + def write(self): # -> None: + ... + + def get_urn(self): # -> Any: + ... + + + diff --git a/typings/rcrs_core/properties/booleanProperty.pyi b/typings/rcrs_core/properties/booleanProperty.pyi new file mode 100644 index 0000000..7bd816f --- /dev/null +++ b/typings/rcrs_core/properties/booleanProperty.pyi @@ -0,0 +1,21 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.properties.property import Property + +class BooleanProperty(Property): + def __init__(self, urn) -> None: + ... + + def set_fields(self, value): # -> None: + ... + + def copy(self): # -> BooleanProperty: + ... + + def take_value(self, _property): # -> None: + ... + + + diff --git a/typings/rcrs_core/properties/edgeListProperty.pyi b/typings/rcrs_core/properties/edgeListProperty.pyi new file mode 100644 index 0000000..2af9f10 --- /dev/null +++ b/typings/rcrs_core/properties/edgeListProperty.pyi @@ -0,0 +1,38 @@ +""" +This type stub file was generated by pyright. +""" + +from typing import List +from rcrs_core.properties.property import Property +from rcrs_core.entities.edge import Edge + +class EdgeListProperty(Property): + def __init__(self, urn) -> None: + ... + + def get_fields(self): # -> None: + ... + + def set_fields(self, data): # -> None: + ... + + def set_value(self, _value: List[Edge]): # -> None: + ... + + def set_edges(self, _edges: List[Edge]): # -> None: + ... + + def add_edge(self, _edge): # -> None: + ... + + def clear_edges(self): # -> None: + ... + + def take_value(self, _value): # -> None: + ... + + def copy(self): # -> EdgeListProperty: + ... + + + diff --git a/typings/rcrs_core/properties/entityIDListProperty.pyi b/typings/rcrs_core/properties/entityIDListProperty.pyi new file mode 100644 index 0000000..9f629f1 --- /dev/null +++ b/typings/rcrs_core/properties/entityIDListProperty.pyi @@ -0,0 +1,26 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.properties.property import Property +from rcrs_core.worldmodel.entityID import EntityID +from typing import List + +class EntityIDListProperty(Property): + def __init__(self, urn) -> None: + ... + + def set_fields(self, data): # -> None: + ... + + def set_value(self, _value: List[EntityID]): # -> None: + ... + + def copy(self): # -> EntityIDListProperty: + ... + + def take_value(self, _property): # -> None: + ... + + + diff --git a/typings/rcrs_core/properties/entityIDProperty.pyi b/typings/rcrs_core/properties/entityIDProperty.pyi new file mode 100644 index 0000000..da7b261 --- /dev/null +++ b/typings/rcrs_core/properties/entityIDProperty.pyi @@ -0,0 +1,21 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.properties.property import Property + +class EntityIDProperty(Property): + def __init__(self, urn) -> None: + ... + + def set_fields(self, value): # -> None: + ... + + def copy(self): # -> EntityIDProperty: + ... + + def take_value(self, _property): # -> None: + ... + + + diff --git a/typings/rcrs_core/properties/intArrayProperty.pyi b/typings/rcrs_core/properties/intArrayProperty.pyi new file mode 100644 index 0000000..a4c7a6c --- /dev/null +++ b/typings/rcrs_core/properties/intArrayProperty.pyi @@ -0,0 +1,24 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.properties.property import Property + +class IntArrayProperty(Property): + def __init__(self, urn) -> None: + ... + + def set_fields(self, data): # -> None: + ... + + def set_value(self, data): # -> None: + ... + + def copy(self): # -> IntArrayProperty: + ... + + def take_value(self, _property): # -> None: + ... + + + diff --git a/typings/rcrs_core/properties/intProperty.pyi b/typings/rcrs_core/properties/intProperty.pyi new file mode 100644 index 0000000..83cf474 --- /dev/null +++ b/typings/rcrs_core/properties/intProperty.pyi @@ -0,0 +1,21 @@ +""" +This type stub file was generated by pyright. +""" + +from rcrs_core.properties.property import Property + +class IntProperty(Property): + def __init__(self, urn) -> None: + ... + + def set_fields(self, value): # -> None: + ... + + def copy(self): # -> IntProperty: + ... + + def take_value(self, _property): # -> None: + ... + + + diff --git a/typings/rcrs_core/properties/property.pyi b/typings/rcrs_core/properties/property.pyi new file mode 100644 index 0000000..cbff460 --- /dev/null +++ b/typings/rcrs_core/properties/property.pyi @@ -0,0 +1,22 @@ +""" +This type stub file was generated by pyright. +""" + +class Property: + def __init__(self, _urn) -> None: + ... + + def get_urn(self): # -> Any: + ... + + def get_value(self): # -> None: + ... + + def set_value(self, _value): # -> None: + ... + + def to_string(self): # -> None: + ... + + + diff --git a/typings/rcrs_core/properties/standardPropertyFactory.pyi b/typings/rcrs_core/properties/standardPropertyFactory.pyi new file mode 100644 index 0000000..be77e22 --- /dev/null +++ b/typings/rcrs_core/properties/standardPropertyFactory.pyi @@ -0,0 +1,13 @@ +""" +This type stub file was generated by pyright. +""" + +class StandardPropertyFactory: + def __init__(self) -> None: + ... + + def make_property(urn): # -> IntProperty | IntArrayProperty | BooleanProperty | EntityIDProperty | EntityIDListProperty | EdgeListProperty | None: + ... + + + diff --git a/typings/rcrs_core/worldmodel/changeSet.pyi b/typings/rcrs_core/worldmodel/changeSet.pyi new file mode 100644 index 0000000..d1788f1 --- /dev/null +++ b/typings/rcrs_core/worldmodel/changeSet.pyi @@ -0,0 +1,34 @@ +""" +This type stub file was generated by pyright. +""" + +class ChangeSet: + def __init__(self, _change_set=...) -> None: + ... + + def add_change(self, entity_id, entity_urn, property): # -> None: + ... + + def entity_deleted(self, _entity_id): # -> None: + ... + + def get_changed_properties(self, _entity_id): # -> list[Any]: + ... + + def get_changed_property(self, _entity_id, prop_urn): # -> None: + ... + + def get_changed_entities(self): # -> dict_keys[Any, Any]: + ... + + def get_deleted_entities(self): # -> set[Any]: + ... + + def get_entity_urn(self, _entity_id): # -> None: + ... + + def merge(self, _change_set): # -> None: + ... + + + diff --git a/typings/rcrs_core/worldmodel/entityID.pyi b/typings/rcrs_core/worldmodel/entityID.pyi new file mode 100644 index 0000000..e570b8e --- /dev/null +++ b/typings/rcrs_core/worldmodel/entityID.pyi @@ -0,0 +1,25 @@ +""" +This type stub file was generated by pyright. +""" + +class EntityID: + def __init__(self, id: int) -> None: + ... + + def __eq__(self, other) -> bool: + ... + + def __hash__(self) -> int: + ... + + def get_value(self): # -> | int: + ... + + def __str__(self) -> str: + ... + + def equals(self, o): # -> bool: + ... + + + diff --git a/typings/rcrs_core/worldmodel/worldmodel.pyi b/typings/rcrs_core/worldmodel/worldmodel.pyi new file mode 100644 index 0000000..e9ccce0 --- /dev/null +++ b/typings/rcrs_core/worldmodel/worldmodel.pyi @@ -0,0 +1,51 @@ +""" +This type stub file was generated by pyright. +""" + +from typing import List +from rcrs_core.entities.entity import Entity +from rcrs_core.worldmodel.changeSet import ChangeSet +from rcrs_core.worldmodel.entityID import EntityID + +class WorldModel: + def __init__(self) -> None: + ... + + def add_entities(self, entities: List[Entity]): # -> None: + ... + + def get_entity(self, entity_id: EntityID) -> Entity: + ... + + def add_entity(self, entity): # -> None: + ... + + def remove_entity(self, entity_id): # -> None: + ... + + def get_entities(self): # -> dict_values[Any, Any]: + ... + + def merge(self, change_set: ChangeSet): # -> None: + ... + + def index_entities(self): # -> None: + ... + + def make_rectangle(self, entity): # -> tuple[None, None, None, None] | tuple[float, float, float, float]: + ... + + def get_rect_bounds(self): # -> tuple[Any, Any, Any, Any]: + ... + + def get_world_bounds(self): # -> tuple[Any, Any, Any, Any]: + ... + + def get_objects_in_rectangle(self, x1, y1, x2, y2): # -> list[Any]: + ... + + def get_objects_in_range(self, entity, range): # -> list[Any]: + ... + + + From ae4e8fa2238e24b5dedf4b740a1e46c3bc30d2c1 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 2 Oct 2024 14:09:56 +0900 Subject: [PATCH 065/249] Delete unused type stub files --- typings/rcrs_core/agents/agent.pyi | 94 ------------ typings/rcrs_core/commands/AKClear.pyi | 16 -- typings/rcrs_core/commands/AKClearArea.pyi | 16 -- typings/rcrs_core/commands/AKLoad.pyi | 16 -- typings/rcrs_core/commands/AKMove.pyi | 17 --- typings/rcrs_core/commands/AKRescue.pyi | 16 -- typings/rcrs_core/commands/AKRest.pyi | 16 -- typings/rcrs_core/commands/AKSay.pyi | 16 -- typings/rcrs_core/commands/AKSpeak.pyi | 16 -- typings/rcrs_core/commands/AKSubscribe.pyi | 17 --- typings/rcrs_core/commands/AKTell.pyi | 16 -- typings/rcrs_core/commands/AKUnload.pyi | 16 -- typings/rcrs_core/commands/Command.pyi | 16 -- typings/rcrs_core/config/config.pyi | 55 ------- .../rcrs_core/config/config_constraint.pyi | 25 ---- .../rcrs_core/connection/RCRSProto_pb2.pyi | 42 ------ typings/rcrs_core/connection/URN.pyi | 141 ------------------ typings/rcrs_core/connection/__init__.pyi | 4 - .../connection/componentLauncher.pyi | 22 --- typings/rcrs_core/connection/connection.pyi | 22 --- .../connection/rcrs_encoding_utils.pyi | 20 --- typings/rcrs_core/entities/__init__.pyi | 4 - .../rcrs_core/entities/ambulanceCenter.pyi | 22 --- typings/rcrs_core/entities/ambulanceTeam.pyi | 22 --- typings/rcrs_core/entities/area.pyi | 52 ------- typings/rcrs_core/entities/blockade.pyi | 73 --------- typings/rcrs_core/entities/building.pyi | 124 --------------- typings/rcrs_core/entities/civilian.pyi | 22 --- typings/rcrs_core/entities/edge.pyi | 31 ---- typings/rcrs_core/entities/entity.pyi | 61 -------- typings/rcrs_core/entities/fireBrigade.pyi | 34 ----- typings/rcrs_core/entities/fireStation.pyi | 22 --- typings/rcrs_core/entities/gassStation.pyi | 22 --- typings/rcrs_core/entities/human.pyi | 114 -------------- typings/rcrs_core/entities/hydrant.pyi | 22 --- typings/rcrs_core/entities/policeForce.pyi | 22 --- typings/rcrs_core/entities/policeOffice.pyi | 22 --- typings/rcrs_core/entities/refuge.pyi | 61 -------- typings/rcrs_core/entities/road.pyi | 22 --- .../entities/standardEntityFactory.pyi | 17 --- typings/rcrs_core/log/logger.pyi | 28 ---- typings/rcrs_core/messages/AKAcknowledge.pyi | 18 --- typings/rcrs_core/messages/AKConnect.pyi | 18 --- typings/rcrs_core/messages/KAConnectError.pyi | 18 --- typings/rcrs_core/messages/KAConnectOK.pyi | 18 --- typings/rcrs_core/messages/KASense.pyi | 19 --- typings/rcrs_core/messages/Shutdown.pyi | 18 --- .../messages/controlMessageFactory.pyi | 13 -- typings/rcrs_core/messages/message.pyi | 23 --- .../rcrs_core/properties/booleanProperty.pyi | 21 --- .../rcrs_core/properties/edgeListProperty.pyi | 38 ----- .../properties/entityIDListProperty.pyi | 26 ---- .../rcrs_core/properties/entityIDProperty.pyi | 21 --- .../rcrs_core/properties/intArrayProperty.pyi | 24 --- typings/rcrs_core/properties/intProperty.pyi | 21 --- typings/rcrs_core/properties/property.pyi | 22 --- .../properties/standardPropertyFactory.pyi | 13 -- typings/rcrs_core/worldmodel/changeSet.pyi | 34 ----- typings/rcrs_core/worldmodel/entityID.pyi | 25 ---- typings/rcrs_core/worldmodel/worldmodel.pyi | 51 ------- 60 files changed, 1857 deletions(-) delete mode 100644 typings/rcrs_core/agents/agent.pyi delete mode 100644 typings/rcrs_core/commands/AKClear.pyi delete mode 100644 typings/rcrs_core/commands/AKClearArea.pyi delete mode 100644 typings/rcrs_core/commands/AKLoad.pyi delete mode 100644 typings/rcrs_core/commands/AKMove.pyi delete mode 100644 typings/rcrs_core/commands/AKRescue.pyi delete mode 100644 typings/rcrs_core/commands/AKRest.pyi delete mode 100644 typings/rcrs_core/commands/AKSay.pyi delete mode 100644 typings/rcrs_core/commands/AKSpeak.pyi delete mode 100644 typings/rcrs_core/commands/AKSubscribe.pyi delete mode 100644 typings/rcrs_core/commands/AKTell.pyi delete mode 100644 typings/rcrs_core/commands/AKUnload.pyi delete mode 100644 typings/rcrs_core/commands/Command.pyi delete mode 100644 typings/rcrs_core/config/config.pyi delete mode 100644 typings/rcrs_core/config/config_constraint.pyi delete mode 100644 typings/rcrs_core/connection/RCRSProto_pb2.pyi delete mode 100644 typings/rcrs_core/connection/URN.pyi delete mode 100644 typings/rcrs_core/connection/__init__.pyi delete mode 100644 typings/rcrs_core/connection/componentLauncher.pyi delete mode 100644 typings/rcrs_core/connection/connection.pyi delete mode 100644 typings/rcrs_core/connection/rcrs_encoding_utils.pyi delete mode 100644 typings/rcrs_core/entities/__init__.pyi delete mode 100644 typings/rcrs_core/entities/ambulanceCenter.pyi delete mode 100644 typings/rcrs_core/entities/ambulanceTeam.pyi delete mode 100644 typings/rcrs_core/entities/area.pyi delete mode 100644 typings/rcrs_core/entities/blockade.pyi delete mode 100644 typings/rcrs_core/entities/building.pyi delete mode 100644 typings/rcrs_core/entities/civilian.pyi delete mode 100644 typings/rcrs_core/entities/edge.pyi delete mode 100644 typings/rcrs_core/entities/entity.pyi delete mode 100644 typings/rcrs_core/entities/fireBrigade.pyi delete mode 100644 typings/rcrs_core/entities/fireStation.pyi delete mode 100644 typings/rcrs_core/entities/gassStation.pyi delete mode 100644 typings/rcrs_core/entities/human.pyi delete mode 100644 typings/rcrs_core/entities/hydrant.pyi delete mode 100644 typings/rcrs_core/entities/policeForce.pyi delete mode 100644 typings/rcrs_core/entities/policeOffice.pyi delete mode 100644 typings/rcrs_core/entities/refuge.pyi delete mode 100644 typings/rcrs_core/entities/road.pyi delete mode 100644 typings/rcrs_core/entities/standardEntityFactory.pyi delete mode 100644 typings/rcrs_core/log/logger.pyi delete mode 100644 typings/rcrs_core/messages/AKAcknowledge.pyi delete mode 100644 typings/rcrs_core/messages/AKConnect.pyi delete mode 100644 typings/rcrs_core/messages/KAConnectError.pyi delete mode 100644 typings/rcrs_core/messages/KAConnectOK.pyi delete mode 100644 typings/rcrs_core/messages/KASense.pyi delete mode 100644 typings/rcrs_core/messages/Shutdown.pyi delete mode 100644 typings/rcrs_core/messages/controlMessageFactory.pyi delete mode 100644 typings/rcrs_core/messages/message.pyi delete mode 100644 typings/rcrs_core/properties/booleanProperty.pyi delete mode 100644 typings/rcrs_core/properties/edgeListProperty.pyi delete mode 100644 typings/rcrs_core/properties/entityIDListProperty.pyi delete mode 100644 typings/rcrs_core/properties/entityIDProperty.pyi delete mode 100644 typings/rcrs_core/properties/intArrayProperty.pyi delete mode 100644 typings/rcrs_core/properties/intProperty.pyi delete mode 100644 typings/rcrs_core/properties/property.pyi delete mode 100644 typings/rcrs_core/properties/standardPropertyFactory.pyi delete mode 100644 typings/rcrs_core/worldmodel/changeSet.pyi delete mode 100644 typings/rcrs_core/worldmodel/entityID.pyi delete mode 100644 typings/rcrs_core/worldmodel/worldmodel.pyi diff --git a/typings/rcrs_core/agents/agent.pyi b/typings/rcrs_core/agents/agent.pyi deleted file mode 100644 index e19674a..0000000 --- a/typings/rcrs_core/agents/agent.pyi +++ /dev/null @@ -1,94 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.connection.URN import Entity -from abc import ABC, abstractmethod -from rcrs_core.entities.human import Human - -class Agent(ABC): - def __init__(self, pre) -> None: - ... - - @abstractmethod - def precompute(self): # -> None: - ... - - @abstractmethod - def think(self, time, change_set, hear): # -> None: - ... - - def get_name(self): # -> str: - ... - - def get_id(self): # -> EntityID | None: - ... - - def set_send_msg(self, connection_send_func): # -> None: - ... - - def start_up(self, request_id): # -> None: - ... - - def post_connect(self): # -> None: - ... - - def message_received(self, msg): # -> None: - ... - - def handle_connect_error(self, msg): - ... - - def handle_connect_ok(self, msg): # -> None: - ... - - def sendAKAcknowledge(self, request_id): # -> None: - ... - - def process_sense(self, msg): # -> None: - ... - - def me(self) -> Human: - ... - - def location(self) -> Entity: - ... - - def random_walk(self): # -> list[Any]: - ... - - def send_clear(self, time, target): # -> None: - ... - - def send_clear_area(self, time, x=..., y=...): # -> None: - ... - - def send_load(self, time, target): # -> None: - ... - - def send_move(self, time, path, x=..., y=...): # -> None: - ... - - def send_rescue(self, time, target): # -> None: - ... - - def send_rest(self, time_step): # -> None: - ... - - def send_say(self, time_step: int, message: str): # -> None: - ... - - def send_speak(self, time_step: int, message: str, channel: int): # -> None: - ... - - def send_subscribe(self, time, channel): # -> None: - ... - - def send_tell(self, time_step: int, message: str): # -> None: - ... - - def send_unload(self, time): # -> None: - ... - - - diff --git a/typings/rcrs_core/commands/AKClear.pyi b/typings/rcrs_core/commands/AKClear.pyi deleted file mode 100644 index 1f3119a..0000000 --- a/typings/rcrs_core/commands/AKClear.pyi +++ /dev/null @@ -1,16 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID - -class AKClear(Command): - def __init__(self, agent_id: EntityID, time: int, target: EntityID) -> None: - ... - - def prepare_cmd(self): # -> MessageProto: - ... - - - diff --git a/typings/rcrs_core/commands/AKClearArea.pyi b/typings/rcrs_core/commands/AKClearArea.pyi deleted file mode 100644 index cde4e0d..0000000 --- a/typings/rcrs_core/commands/AKClearArea.pyi +++ /dev/null @@ -1,16 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID - -class AKClearArea(Command): - def __init__(self, agent_id: EntityID, time: int, destinationX: int, destinationY: int) -> None: - ... - - def prepare_cmd(self): # -> MessageProto: - ... - - - diff --git a/typings/rcrs_core/commands/AKLoad.pyi b/typings/rcrs_core/commands/AKLoad.pyi deleted file mode 100644 index 1dda8d4..0000000 --- a/typings/rcrs_core/commands/AKLoad.pyi +++ /dev/null @@ -1,16 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID - -class AKLoad(Command): - def __init__(self, agent_id: EntityID, time: int, target: EntityID) -> None: - ... - - def prepare_cmd(self): # -> MessageProto: - ... - - - diff --git a/typings/rcrs_core/commands/AKMove.pyi b/typings/rcrs_core/commands/AKMove.pyi deleted file mode 100644 index 32f0c27..0000000 --- a/typings/rcrs_core/commands/AKMove.pyi +++ /dev/null @@ -1,17 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID -from typing import List - -class AKMove(Command): - def __init__(self, agent_id: EntityID, time: int, path: List[int], destinationX=..., destinationY=...) -> None: - ... - - def prepare_cmd(self): # -> MessageProto: - ... - - - diff --git a/typings/rcrs_core/commands/AKRescue.pyi b/typings/rcrs_core/commands/AKRescue.pyi deleted file mode 100644 index e1b18d6..0000000 --- a/typings/rcrs_core/commands/AKRescue.pyi +++ /dev/null @@ -1,16 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID - -class AKRescue(Command): - def __init__(self, agent_id: EntityID, time: int, target: EntityID) -> None: - ... - - def prepare_cmd(self): # -> MessageProto: - ... - - - diff --git a/typings/rcrs_core/commands/AKRest.pyi b/typings/rcrs_core/commands/AKRest.pyi deleted file mode 100644 index 78540d6..0000000 --- a/typings/rcrs_core/commands/AKRest.pyi +++ /dev/null @@ -1,16 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID - -class AKRest(Command): - def __init__(self, agent_id: EntityID, time: int) -> None: - ... - - def prepare_cmd(self): # -> MessageProto: - ... - - - diff --git a/typings/rcrs_core/commands/AKSay.pyi b/typings/rcrs_core/commands/AKSay.pyi deleted file mode 100644 index d523688..0000000 --- a/typings/rcrs_core/commands/AKSay.pyi +++ /dev/null @@ -1,16 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID - -class AKSay(Command): - def __init__(self, agent_id: EntityID, time: int, message: str) -> None: - ... - - def prepare_cmd(self): # -> MessageProto: - ... - - - diff --git a/typings/rcrs_core/commands/AKSpeak.pyi b/typings/rcrs_core/commands/AKSpeak.pyi deleted file mode 100644 index a72aada..0000000 --- a/typings/rcrs_core/commands/AKSpeak.pyi +++ /dev/null @@ -1,16 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID - -class AKSpeak(Command): - def __init__(self, agent_id: EntityID, time: int, message: str, channel: int) -> None: - ... - - def prepare_cmd(self): # -> MessageProto: - ... - - - diff --git a/typings/rcrs_core/commands/AKSubscribe.pyi b/typings/rcrs_core/commands/AKSubscribe.pyi deleted file mode 100644 index b6b2717..0000000 --- a/typings/rcrs_core/commands/AKSubscribe.pyi +++ /dev/null @@ -1,17 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID -from typing import List - -class AKSubscribe(Command): - def __init__(self, agent_id: EntityID, time: int, channels: List[int]) -> None: - ... - - def prepare_cmd(self): # -> MessageProto: - ... - - - diff --git a/typings/rcrs_core/commands/AKTell.pyi b/typings/rcrs_core/commands/AKTell.pyi deleted file mode 100644 index e35dfd1..0000000 --- a/typings/rcrs_core/commands/AKTell.pyi +++ /dev/null @@ -1,16 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID - -class AKTell(Command): - def __init__(self, agent_id: EntityID, time: int, message: str) -> None: - ... - - def prepare_cmd(self): # -> MessageProto: - ... - - - diff --git a/typings/rcrs_core/commands/AKUnload.pyi b/typings/rcrs_core/commands/AKUnload.pyi deleted file mode 100644 index 689bc11..0000000 --- a/typings/rcrs_core/commands/AKUnload.pyi +++ /dev/null @@ -1,16 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID - -class AKUnload(Command): - def __init__(self, agent_id: EntityID, time: int) -> None: - ... - - def prepare_cmd(self): # -> MessageProto: - ... - - - diff --git a/typings/rcrs_core/commands/Command.pyi b/typings/rcrs_core/commands/Command.pyi deleted file mode 100644 index e98df35..0000000 --- a/typings/rcrs_core/commands/Command.pyi +++ /dev/null @@ -1,16 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from abc import ABC, abstractmethod - -class Command(ABC): - def __init__(self) -> None: - ... - - @abstractmethod - def prepare_cmd(): # -> None: - ... - - - diff --git a/typings/rcrs_core/config/config.pyi b/typings/rcrs_core/config/config.pyi deleted file mode 100644 index c6942e2..0000000 --- a/typings/rcrs_core/config/config.pyi +++ /dev/null @@ -1,55 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -class Config: - def __init__(self) -> None: - ... - - def get_value(self, key: str) -> str | None: - ... - - def get_value_or_default(self, key: str, default: str) -> str: - ... - - def get_int_value(self, key: str) -> int | None: - ... - - def get_int_value_or_default(self, key: str, default: int) -> int: - ... - - def get_float_value(self, key) -> float | None: - ... - - def get_float_value_or_default(self, key: str, default: float) -> float: - ... - - def get_boolean_value(self, key) -> bool | None: - ... - - def get_boolean_value_or_default(self, key: str, default: bool) -> bool: - ... - - def get_array_value(self, key) -> list | None: - ... - - def get_array_value_or_default(self, key: str, default: list) -> list: - ... - - def set_value(self, key: str, value: str) -> None: - ... - - def set_int_value(self, key: str, value: int) -> None: - ... - - def set_float_value(self, key: str, value: float) -> None: - ... - - def set_boolean_value(self, key: str, value: bool) -> None: - ... - - def set_array_value(self, key: str, value: list) -> None: - ... - - - diff --git a/typings/rcrs_core/config/config_constraint.pyi b/typings/rcrs_core/config/config_constraint.pyi deleted file mode 100644 index 4b018e9..0000000 --- a/typings/rcrs_core/config/config_constraint.pyi +++ /dev/null @@ -1,25 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from abc import ABC, abstractmethod -from typing import Set, TYPE_CHECKING -from rcrs_core.config.config import Config - -if TYPE_CHECKING: - ... -class ConfigConstraint(ABC): - @abstractmethod - def is_violated(self, config: Config) -> bool: - ... - - @abstractmethod - def get_description(self) -> str: - ... - - @abstractmethod - def get_keys(self) -> Set[str]: - ... - - - diff --git a/typings/rcrs_core/connection/RCRSProto_pb2.pyi b/typings/rcrs_core/connection/RCRSProto_pb2.pyi deleted file mode 100644 index b0a1b66..0000000 --- a/typings/rcrs_core/connection/RCRSProto_pb2.pyi +++ /dev/null @@ -1,42 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -"""Generated protocol buffer code.""" -_sym_db = ... -DESCRIPTOR = ... -_MESSAGEPROTO_COMPONENTSENTRY = ... -_MESSAGEPROTO = ... -_MESSAGELISTPROTO = ... -_MESSAGECOMPONENTPROTO = ... -_STRLISTPROTO = ... -_INTLISTPROTO = ... -_FLOATLISTPROTO = ... -_INTMATRIXPROTO = ... -_VALUEPROTO = ... -_PROPERTYPROTO = ... -_POINT2DPROTO = ... -_ENTITYPROTO = ... -_ENTITYLISTPROTO = ... -_CONFIGPROTO_DATAENTRY = ... -_CONFIGPROTO = ... -_EDGELISTPROTO = ... -_EDGEPROTO = ... -_CHANGESETPROTO_ENTITYCHANGEPROTO = ... -_CHANGESETPROTO = ... -MessageProto = ... -MessageListProto = ... -MessageComponentProto = ... -StrListProto = ... -IntListProto = ... -FloatListProto = ... -IntMatrixProto = ... -ValueProto = ... -PropertyProto = ... -Point2DProto = ... -EntityProto = ... -EntityListProto = ... -ConfigProto = ... -EdgeListProto = ... -EdgeProto = ... -ChangeSetProto = ... diff --git a/typings/rcrs_core/connection/URN.pyi b/typings/rcrs_core/connection/URN.pyi deleted file mode 100644 index c43d1b2..0000000 --- a/typings/rcrs_core/connection/URN.pyi +++ /dev/null @@ -1,141 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from enum import IntEnum - -class Entity(IntEnum): - WORLD = ... - ROAD = ... - BLOCKADE = ... - BUILDING = ... - REFUGE = ... - HYDRANT = ... - GAS_STATION = ... - FIRE_STATION = ... - AMBULANCE_CENTRE = ... - POLICE_OFFICE = ... - CIVILIAN = ... - FIRE_BRIGADE = ... - AMBULANCE_TEAM = ... - POLICE_FORCE = ... - - -class Property(IntEnum): - START_TIME = ... - LONGITUDE = ... - LATITUDE = ... - WIND_FORCE = ... - WIND_DIRECTION = ... - X = ... - Y = ... - BLOCKADES = ... - REPAIR_COST = ... - FLOORS = ... - BUILDING_ATTRIBUTES = ... - IGNITION = ... - FIERYNESS = ... - BROKENNESS = ... - BUILDING_CODE = ... - BUILDING_AREA_GROUND = ... - BUILDING_AREA_TOTAL = ... - APEXES = ... - EDGES = ... - POSITION = ... - DIRECTION = ... - POSITION_HISTORY = ... - STAMINA = ... - HP = ... - DAMAGE = ... - BURIEDNESS = ... - TRAVEL_DISTANCE = ... - WATER_QUANTITY = ... - TEMPERATURE = ... - IMPORTANCE = ... - CAPACITY = ... - BEDCAPACITY = ... - OCCUPIEDBEDS = ... - REFILLCAPACITY = ... - WAITINGLISTSIZE = ... - - -class Command(IntEnum): - AK_REST = ... - AK_MOVE = ... - AK_LOAD = ... - AK_UNLOAD = ... - AK_SAY = ... - AK_TELL = ... - AK_EXTINGUISH = ... - AK_RESCUE = ... - AK_CLEAR = ... - AK_CLEAR_AREA = ... - AK_SUBSCRIBE = ... - AK_SPEAK = ... - - -class ComponentCommand(IntEnum): - Target = ... - DestinationX = ... - DestinationY = ... - Water = ... - Path = ... - Message = ... - Channel = ... - Channels = ... - - -class ControlMSG(IntEnum): - KG_CONNECT = ... - KG_ACKNOWLEDGE = ... - GK_CONNECT_OK = ... - GK_CONNECT_ERROR = ... - SK_CONNECT = ... - SK_ACKNOWLEDGE = ... - SK_UPDATE = ... - KS_CONNECT_OK = ... - KS_CONNECT_ERROR = ... - KS_UPDATE = ... - KS_COMMANDS = ... - KS_AFTERSHOCKS_INFO = ... - VK_CONNECT = ... - VK_ACKNOWLEDGE = ... - KV_CONNECT_OK = ... - KV_CONNECT_ERROR = ... - KV_TIMESTEP = ... - AK_CONNECT = ... - AK_ACKNOWLEDGE = ... - KA_CONNECT_OK = ... - KA_CONNECT_ERROR = ... - KA_SENSE = ... - SHUTDOWN = ... - ENTITY_ID_REQUEST = ... - ENTITY_ID_RESPONSE = ... - - -class ComponentControlMSG(IntEnum): - RequestID = ... - AgentID = ... - Version = ... - Name = ... - RequestedEntityTypes = ... - SimulatorID = ... - RequestNumber = ... - NumberOfIDs = ... - NewEntityIDs = ... - Reason = ... - Entities = ... - ViewerID = ... - AgentConfig = ... - Time = ... - Updates = ... - Hearing = ... - INTENSITIES = ... - TIMES = ... - ID = ... - Commands = ... - SimulatorConfig = ... - Changes = ... - - -MAP = ... diff --git a/typings/rcrs_core/connection/__init__.pyi b/typings/rcrs_core/connection/__init__.pyi deleted file mode 100644 index 006bc27..0000000 --- a/typings/rcrs_core/connection/__init__.pyi +++ /dev/null @@ -1,4 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - diff --git a/typings/rcrs_core/connection/componentLauncher.pyi b/typings/rcrs_core/connection/componentLauncher.pyi deleted file mode 100644 index edf2c44..0000000 --- a/typings/rcrs_core/connection/componentLauncher.pyi +++ /dev/null @@ -1,22 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.agents.agent import Agent -from rcrs_core.connection.connection import Connection - -class ComponentLauncher: - def __init__(self, port, host) -> None: - ... - - def make_connection(self) -> Connection: - ... - - def connect(self, agent: Agent, _request_id): # -> None: - ... - - def generate_request_ID(self): # -> int: - ... - - - diff --git a/typings/rcrs_core/connection/connection.pyi b/typings/rcrs_core/connection/connection.pyi deleted file mode 100644 index 33218b8..0000000 --- a/typings/rcrs_core/connection/connection.pyi +++ /dev/null @@ -1,22 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -class Connection: - def __init__(self, host, port) -> None: - ... - - def connect(self): # -> None: - ... - - def parseMessageFromKernel(self): # -> None: - ... - - def message_received(self, agent_message_received): # -> None: - ... - - def send_msg(self, msg): # -> None: - ... - - - diff --git a/typings/rcrs_core/connection/rcrs_encoding_utils.pyi b/typings/rcrs_core/connection/rcrs_encoding_utils.pyi deleted file mode 100644 index 947fc2b..0000000 --- a/typings/rcrs_core/connection/rcrs_encoding_utils.pyi +++ /dev/null @@ -1,20 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -""" utils to handle connection """ -def write_int32(value, sock): # -> None: - ... - -def readnbytes(sock, n): # -> Literal[b""]: - ... - -def read_int32(sock): # -> int: - ... - -def write_msg(msg, sock): # -> None: - ... - -def read_msg(sock): # -> MessageProto: - ... - diff --git a/typings/rcrs_core/entities/__init__.pyi b/typings/rcrs_core/entities/__init__.pyi deleted file mode 100644 index 006bc27..0000000 --- a/typings/rcrs_core/entities/__init__.pyi +++ /dev/null @@ -1,4 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - diff --git a/typings/rcrs_core/entities/ambulanceCenter.pyi b/typings/rcrs_core/entities/ambulanceCenter.pyi deleted file mode 100644 index bde5db4..0000000 --- a/typings/rcrs_core/entities/ambulanceCenter.pyi +++ /dev/null @@ -1,22 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.entities.building import Building - -class AmbulanceCentreEntity(Building): - urn = ... - def __init__(self, entity_id) -> None: - ... - - def set_entity(self, properties): # -> None: - ... - - def get_entity_name(self): # -> Literal['Ambulance Centre']: - ... - - def copy_impl(self): # -> AmbulanceCentreEntity: - ... - - - diff --git a/typings/rcrs_core/entities/ambulanceTeam.pyi b/typings/rcrs_core/entities/ambulanceTeam.pyi deleted file mode 100644 index 5c42faa..0000000 --- a/typings/rcrs_core/entities/ambulanceTeam.pyi +++ /dev/null @@ -1,22 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.entities.human import Human - -class AmbulanceTeamEntity(Human): - urn = ... - def __init__(self, entity_id) -> None: - ... - - def set_entity(self, properties): # -> None: - ... - - def get_entity_name(self): # -> Literal['Ambulance Team']: - ... - - def copy_impl(self): # -> AmbulanceTeamEntity: - ... - - - diff --git a/typings/rcrs_core/entities/area.pyi b/typings/rcrs_core/entities/area.pyi deleted file mode 100644 index b3f1559..0000000 --- a/typings/rcrs_core/entities/area.pyi +++ /dev/null @@ -1,52 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from typing import List -from rcrs_core.entities.entity import Entity - -class Area(Entity): - def __init__(self, entity_id) -> None: - ... - - def set_entity(self, properties): # -> None: - ... - - def get_location(self): # -> tuple[Any | None, Any | None]: - ... - - def get_neighbours(self): # -> list[Any]: - ... - - def get_edge_to(self, neighbour): # -> Entity | None: - ... - - def get_property(self, urn): # -> IntProperty | EdgeListProperty | EntityIDListProperty | None: - ... - - def get_edges_property(self): # -> EdgeListProperty: - ... - - def get_edges(self) -> List[Entity]: - ... - - def set_edges(self, value): # -> None: - ... - - def add_edge(self, edge): # -> None: - ... - - def get_blockades_property(self): # -> EntityIDListProperty: - ... - - def get_blockades(self): # -> None: - ... - - def set_blockades(self, value): # -> None: - ... - - def get_shape(self): # -> None: - ... - - - diff --git a/typings/rcrs_core/entities/blockade.pyi b/typings/rcrs_core/entities/blockade.pyi deleted file mode 100644 index a9b1502..0000000 --- a/typings/rcrs_core/entities/blockade.pyi +++ /dev/null @@ -1,73 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.entities.entity import Entity - -class Blockade(Entity): - urn = ... - def __init__(self, entity_id) -> None: - ... - - def get_entity_name(self): # -> Literal['Blockade']: - ... - - def set_entity(self, properties): # -> None: - ... - - def copy_impl(self): # -> Blockade: - ... - - def get_property(self, urn): # -> IntProperty | EntityIDProperty | IntArrayProperty | None: - ... - - def get_x_property(self): # -> IntProperty: - ... - - def get_x(self): # -> None: - ... - - def set_x(self, value): # -> None: - ... - - def get_y_property(self): # -> IntProperty: - ... - - def get_y(self): # -> None: - ... - - def set_y(self, value): # -> None: - ... - - def get_apexes_property(self): # -> IntArrayProperty: - ... - - def get_apexes(self): # -> None: - ... - - def set_apexes(self, value): # -> None: - ... - - def get_position_property(self): # -> EntityIDProperty: - ... - - def get_position(self): # -> None: - ... - - def set_position(self, value): # -> None: - ... - - def get_repaire_cost_property(self): # -> IntProperty: - ... - - def get_repaire_cost(self): # -> None: - ... - - def set_repaire_cost(self, value): # -> None: - ... - - def get_shape(self): # -> None: - ... - - - diff --git a/typings/rcrs_core/entities/building.pyi b/typings/rcrs_core/entities/building.pyi deleted file mode 100644 index edf477d..0000000 --- a/typings/rcrs_core/entities/building.pyi +++ /dev/null @@ -1,124 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.entities.area import Area - -class Building(Area): - urn = ... - def __init__(self, entity_id) -> None: - ... - - def is__fiery(self): # -> bool: - ... - - def set_entity(self, properties): # -> None: - ... - - def copy_impl(self): # -> Building: - ... - - def get_entity_name(self): # -> Literal['Building']: - ... - - def get_property(self, urn): # -> IntProperty | EdgeListProperty | EntityIDListProperty | None: - ... - - def get_floors_property(self): # -> IntProperty: - ... - - def get_floors(self): # -> None: - ... - - def set_floors(self, value): # -> None: - ... - - def get_ignition_property(self): # -> IntProperty: - ... - - def get_ignition(self): # -> None: - ... - - def set_ignition(self, value): # -> None: - ... - - def get_fieryness_property(self): # -> IntProperty: - ... - - def get_fieryness(self): # -> None: - ... - - def set_fieryness(self, value): # -> None: - ... - - def get_fieryness_enum(self): # -> None: - ... - - def get_brokenness_property(self): # -> IntProperty: - ... - - def get_brokenness(self): # -> None: - ... - - def set_brokenness(self, value): # -> None: - ... - - def get_building_code_property(self): # -> IntProperty: - ... - - def get_building_code(self): # -> None: - ... - - def set_building_code(self, value): # -> None: - ... - - def get_building_code_enum(self): # -> None: - ... - - def get_attributes_property(self): # -> IntProperty: - ... - - def get_attributes(self): # -> None: - ... - - def set_attributes(self, value): # -> None: - ... - - def get_ground_area_property(self): # -> IntProperty: - ... - - def get_ground_area(self): # -> None: - ... - - def set_ground_area(self, value): # -> None: - ... - - def get_total_area_property(self): # -> IntProperty: - ... - - def get_total_area(self): # -> None: - ... - - def set_total_area(self, value): # -> None: - ... - - def get_temperature_property(self): # -> IntProperty: - ... - - def get_temperature(self): # -> None: - ... - - def set_temperature(self, value): # -> None: - ... - - def get_importance_property(self): # -> IntProperty: - ... - - def get_importance(self): # -> None: - ... - - def set_importance(self, value): # -> None: - ... - - - diff --git a/typings/rcrs_core/entities/civilian.pyi b/typings/rcrs_core/entities/civilian.pyi deleted file mode 100644 index 95f8ee3..0000000 --- a/typings/rcrs_core/entities/civilian.pyi +++ /dev/null @@ -1,22 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.entities.human import Human - -class Civilian(Human): - urn = ... - def __init__(self, entity_id) -> None: - ... - - def copy_impl(self): # -> Civilian: - ... - - def get_entity_name(self): # -> Literal['Civilian']: - ... - - def set_entity(self, properties): # -> None: - ... - - - diff --git a/typings/rcrs_core/entities/edge.pyi b/typings/rcrs_core/entities/edge.pyi deleted file mode 100644 index f35452b..0000000 --- a/typings/rcrs_core/entities/edge.pyi +++ /dev/null @@ -1,31 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -class Edge: - def __init__(self, start_x, start_y, end_x, end_y, _neighbour) -> None: - ... - - def get_start_x(self): - ... - - def get_start_y(self): - ... - - def get_end_x(self): - ... - - def get_end_y(self): - ... - - def is_passable(self): # -> bool: - ... - - def get_neighbour(self): # -> None: - ... - - def to_string(self): - ... - - - diff --git a/typings/rcrs_core/entities/entity.pyi b/typings/rcrs_core/entities/entity.pyi deleted file mode 100644 index 1ebb7aa..0000000 --- a/typings/rcrs_core/entities/entity.pyi +++ /dev/null @@ -1,61 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.worldmodel.entityID import EntityID -from abc import ABC - -class Entity(ABC): - def __init__(self, _entity_id) -> None: - ... - - def set_entity(self, properties): # -> None: - ... - - def get_id(self) -> EntityID: - ... - - def get_properties(self): # -> dict[Any, Any]: - ... - - def register_properties(self, _properties): # -> None: - ... - - def copy_impl(self): # -> Entity: - ... - - def copy(self): # -> Entity: - ... - - def get_urn(self): - ... - - def __hash__(self) -> int: - ... - - def get_property(self, property_urn): # -> None: - ... - - def get_location(self): # -> tuple[Any | None, Any | None] | tuple[None, None]: - ... - - def get_x_property(self): # -> IntProperty: - ... - - def get_x(self): # -> None: - ... - - def set_x(self, value): # -> None: - ... - - def get_y_property(self): # -> IntProperty: - ... - - def get_y(self): # -> None: - ... - - def set_y(self, value): # -> None: - ... - - - diff --git a/typings/rcrs_core/entities/fireBrigade.pyi b/typings/rcrs_core/entities/fireBrigade.pyi deleted file mode 100644 index 850dc4d..0000000 --- a/typings/rcrs_core/entities/fireBrigade.pyi +++ /dev/null @@ -1,34 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.entities.human import Human - -class FireBrigadeEntity(Human): - urn = ... - def __init__(self, entity_id) -> None: - ... - - def set_entity(self, properties): # -> None: - ... - - def copy_impl(self): # -> FireBrigadeEntity: - ... - - def get_entity_name(self): # -> Literal['Fire brigade']: - ... - - def get_property(self, urn): # -> IntProperty | EntityIDProperty | IntArrayProperty | None: - ... - - def get_water_property(self): # -> IntProperty: - ... - - def get_water(self): # -> None: - ... - - def set_water(self, value): # -> None: - ... - - - diff --git a/typings/rcrs_core/entities/fireStation.pyi b/typings/rcrs_core/entities/fireStation.pyi deleted file mode 100644 index 46a7b74..0000000 --- a/typings/rcrs_core/entities/fireStation.pyi +++ /dev/null @@ -1,22 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.entities.building import Building - -class FireStationEntity(Building): - urn = ... - def __init__(self, entity_id) -> None: - ... - - def copy_impl(self): # -> FireStationEntity: - ... - - def get_entity_name(self): # -> Literal['Fire Station']: - ... - - def set_entity(self, properties): # -> None: - ... - - - diff --git a/typings/rcrs_core/entities/gassStation.pyi b/typings/rcrs_core/entities/gassStation.pyi deleted file mode 100644 index 71aa885..0000000 --- a/typings/rcrs_core/entities/gassStation.pyi +++ /dev/null @@ -1,22 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.entities.building import Building - -class GasStation(Building): - urn = ... - def __init__(self, entity_id) -> None: - ... - - def copy_impl(self): # -> GasStation: - ... - - def get_entity_name(self): # -> Literal['Gass Station']: - ... - - def set_entity(self, properties): # -> None: - ... - - - diff --git a/typings/rcrs_core/entities/human.pyi b/typings/rcrs_core/entities/human.pyi deleted file mode 100644 index aebfd77..0000000 --- a/typings/rcrs_core/entities/human.pyi +++ /dev/null @@ -1,114 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.entities.entity import Entity -from rcrs_core.properties.entityIDProperty import EntityIDProperty -from rcrs_core.worldmodel.entityID import EntityID -from rcrs_core.worldmodel.worldmodel import WorldModel - -class Human(Entity): - def __init__(self, entity_id) -> None: - ... - - def get_location(self, world_model: WorldModel): # -> None: - ... - - def set_entity(self, properties: dict): # -> None: - ... - - def get_property(self, urn): # -> EntityIDProperty | IntArrayProperty | IntProperty | None: - ... - - def get_x_property(self): # -> IntProperty: - ... - - def get_x(self) -> int: - ... - - def set_x(self, value) -> None: - ... - - def get_y_property(self): # -> IntProperty: - ... - - def get_y(self): # -> None: - ... - - def set_y(self, value): # -> None: - ... - - def get_position_property(self) -> EntityIDProperty: - ... - - def get_position(self) -> EntityID: - ... - - def set_position(self, value): # -> None: - ... - - def get_position_history_property(self): # -> IntArrayProperty: - ... - - def get_position_history(self): # -> None: - ... - - def set_position_history(self, value): # -> None: - ... - - def get_direction_property(self): # -> IntProperty: - ... - - def get_direction(self): # -> None: - ... - - def set_direction(self, value): # -> None: - ... - - def get_stamina_property(self): # -> IntProperty: - ... - - def get_stamina(self): # -> None: - ... - - def set_stamina(self, value): # -> None: - ... - - def get_hp_property(self): # -> IntProperty: - ... - - def get_hp(self): # -> None: - ... - - def set_hp(self, value): # -> None: - ... - - def get_damage_property(self): # -> IntProperty: - ... - - def get_damage(self): # -> None: - ... - - def set_damage(self, value): # -> None: - ... - - def get_buriedness_property(self): # -> IntProperty: - ... - - def get_buriedness(self): # -> None: - ... - - def set_buriedness(self, value): # -> None: - ... - - def get_travel_distance_property(self): # -> IntProperty: - ... - - def get_travel_distance(self): # -> None: - ... - - def set_travel_distance(self, value): # -> None: - ... - - - diff --git a/typings/rcrs_core/entities/hydrant.pyi b/typings/rcrs_core/entities/hydrant.pyi deleted file mode 100644 index 4d564cd..0000000 --- a/typings/rcrs_core/entities/hydrant.pyi +++ /dev/null @@ -1,22 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.entities.road import Road - -class Hydrant(Road): - urn = ... - def __init__(self, entity_id) -> None: - ... - - def copy_impl(self): # -> Hydrant: - ... - - def get_entity_name(self): # -> Literal['Hydrant']: - ... - - def set_entity(self, properties): # -> None: - ... - - - diff --git a/typings/rcrs_core/entities/policeForce.pyi b/typings/rcrs_core/entities/policeForce.pyi deleted file mode 100644 index 1ee42f3..0000000 --- a/typings/rcrs_core/entities/policeForce.pyi +++ /dev/null @@ -1,22 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.entities.human import Human - -class PoliceForceEntity(Human): - urn = ... - def __init__(self, entity_id) -> None: - ... - - def copy_impl(self): # -> PoliceForceEntity: - ... - - def get_entity_name(self): # -> Literal['Police force']: - ... - - def set_entity(self, properties): # -> None: - ... - - - diff --git a/typings/rcrs_core/entities/policeOffice.pyi b/typings/rcrs_core/entities/policeOffice.pyi deleted file mode 100644 index f060e59..0000000 --- a/typings/rcrs_core/entities/policeOffice.pyi +++ /dev/null @@ -1,22 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.entities.building import Building - -class PoliceOfficeEntity(Building): - urn = ... - def __init__(self, entity_id) -> None: - ... - - def copy_impl(self): # -> PoliceOfficeEntity: - ... - - def get_entity_name(self): # -> Literal['Police office']: - ... - - def set_entity(self, properties): # -> None: - ... - - - diff --git a/typings/rcrs_core/entities/refuge.pyi b/typings/rcrs_core/entities/refuge.pyi deleted file mode 100644 index 17ff0ed..0000000 --- a/typings/rcrs_core/entities/refuge.pyi +++ /dev/null @@ -1,61 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.entities.building import Building - -class Refuge(Building): - urn = ... - def __init__(self, entity_id) -> None: - ... - - def set_entity(self, properties): # -> None: - ... - - def copy_impl(self): # -> Refuge: - ... - - def get_entity_name(self) -> str: - ... - - def get_property(self, urn): # -> IntProperty | EdgeListProperty | EntityIDListProperty | None: - ... - - def get_bed_capacity_property(self): # -> IntProperty: - ... - - def get_bed_capacity(self): # -> None: - ... - - def set_bed_capacity(self, value): # -> None: - ... - - def get_occupied_beds_property(self): # -> IntProperty: - ... - - def get_occupied_beds(self): # -> None: - ... - - def set_occupied_beds(self, value): # -> None: - ... - - def get_refill_capacity_property(self): # -> IntProperty: - ... - - def get_refill_capacity(self): # -> None: - ... - - def set_refill_capacity(self, value): # -> None: - ... - - def get_waiting_list_size_property(self): # -> IntProperty: - ... - - def get_waiting_list_size(self): # -> None: - ... - - def set_waiting_list_size(self, value): # -> None: - ... - - - diff --git a/typings/rcrs_core/entities/road.pyi b/typings/rcrs_core/entities/road.pyi deleted file mode 100644 index 2ceaf0d..0000000 --- a/typings/rcrs_core/entities/road.pyi +++ /dev/null @@ -1,22 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.entities.area import Area - -class Road(Area): - urn = ... - def __init__(self, entity_id) -> None: - ... - - def set_entity(self, properties) -> None: - ... - - def get_entity_name(self) -> str: - ... - - def copy_impl(self): # -> Road: - ... - - - diff --git a/typings/rcrs_core/entities/standardEntityFactory.pyi b/typings/rcrs_core/entities/standardEntityFactory.pyi deleted file mode 100644 index 651fc76..0000000 --- a/typings/rcrs_core/entities/standardEntityFactory.pyi +++ /dev/null @@ -1,17 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -class StandardEntityFactory: - _instance = ... - def __new__(cls): # -> Self: - ... - - def __init__(self) -> None: - ... - - def make_entity(urn, id): # -> Building | Refuge | Road | Blockade | PoliceForceEntity | PoliceOfficeEntity | AmbulanceTeamEntity | AmbulanceCentreEntity | FireBrigadeEntity | FireStationEntity | Civilian | Hydrant | GasStation | None: - ... - - - diff --git a/typings/rcrs_core/log/logger.pyi b/typings/rcrs_core/log/logger.pyi deleted file mode 100644 index bec3fd5..0000000 --- a/typings/rcrs_core/log/logger.pyi +++ /dev/null @@ -1,28 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -class Logger: - def __init__(self, _name, agent_id=...) -> None: - ... - - def info(self, msg): # -> None: - ... - - def debug(self, msg): # -> None: - ... - - def error(self, msg): # -> None: - ... - - def warning(self, msg): # -> None: - ... - - def warn(self, msg): # -> None: - ... - - def set_id(self, id): # -> None: - ... - - - diff --git a/typings/rcrs_core/messages/AKAcknowledge.pyi b/typings/rcrs_core/messages/AKAcknowledge.pyi deleted file mode 100644 index 1bea0e2..0000000 --- a/typings/rcrs_core/messages/AKAcknowledge.pyi +++ /dev/null @@ -1,18 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.messages.message import Message - -class AKAcknowledge(Message): - def __init__(self) -> None: - ... - - def write(self, request_id, agent_id): # -> MessageProto: - ... - - def read(self): # -> None: - ... - - - diff --git a/typings/rcrs_core/messages/AKConnect.pyi b/typings/rcrs_core/messages/AKConnect.pyi deleted file mode 100644 index 2c23c18..0000000 --- a/typings/rcrs_core/messages/AKConnect.pyi +++ /dev/null @@ -1,18 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.messages.message import Message - -class AKConnect(Message): - def __init__(self) -> None: - ... - - def write(self, request_id, agent): # -> MessageProto: - ... - - def read(self): # -> None: - ... - - - diff --git a/typings/rcrs_core/messages/KAConnectError.pyi b/typings/rcrs_core/messages/KAConnectError.pyi deleted file mode 100644 index 4fa547d..0000000 --- a/typings/rcrs_core/messages/KAConnectError.pyi +++ /dev/null @@ -1,18 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.messages.message import Message - -class KAConnectError(Message): - def __init__(self, data) -> None: - ... - - def read(self): # -> None: - ... - - def write(self): # -> None: - ... - - - diff --git a/typings/rcrs_core/messages/KAConnectOK.pyi b/typings/rcrs_core/messages/KAConnectOK.pyi deleted file mode 100644 index 100e853..0000000 --- a/typings/rcrs_core/messages/KAConnectOK.pyi +++ /dev/null @@ -1,18 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.messages.message import Message - -class KAConnectOK(Message): - def __init__(self, data) -> None: - ... - - def read(self): # -> None: - ... - - def write(self): # -> None: - ... - - - diff --git a/typings/rcrs_core/messages/KASense.pyi b/typings/rcrs_core/messages/KASense.pyi deleted file mode 100644 index 163ac38..0000000 --- a/typings/rcrs_core/messages/KASense.pyi +++ /dev/null @@ -1,19 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.connection import RCRSProto_pb2 -from rcrs_core.messages.message import Message - -class KASense(Message): - def __init__(self, data: RCRSProto_pb2) -> None: - ... - - def read(self): # -> None: - ... - - def write(self): # -> None: - ... - - - diff --git a/typings/rcrs_core/messages/Shutdown.pyi b/typings/rcrs_core/messages/Shutdown.pyi deleted file mode 100644 index e5a6335..0000000 --- a/typings/rcrs_core/messages/Shutdown.pyi +++ /dev/null @@ -1,18 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.messages.message import Message - -class Shutdown(Message): - def __init__(self, data) -> None: - ... - - def read(self): # -> None: - ... - - def write(self): # -> None: - ... - - - diff --git a/typings/rcrs_core/messages/controlMessageFactory.pyi b/typings/rcrs_core/messages/controlMessageFactory.pyi deleted file mode 100644 index a3e403b..0000000 --- a/typings/rcrs_core/messages/controlMessageFactory.pyi +++ /dev/null @@ -1,13 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -class ControlMessageFactory: - def __init__(self) -> None: - ... - - def make_message(self, msg): # -> KASense | KAConnectOK | KAConnectError | Shutdown | None: - ... - - - diff --git a/typings/rcrs_core/messages/message.pyi b/typings/rcrs_core/messages/message.pyi deleted file mode 100644 index 005a916..0000000 --- a/typings/rcrs_core/messages/message.pyi +++ /dev/null @@ -1,23 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from abc import ABC, abstractmethod - -class Message(ABC): - def __init__(self, _urn) -> None: - ... - - @abstractmethod - def read(self): # -> None: - ... - - @abstractmethod - def write(self): # -> None: - ... - - def get_urn(self): # -> Any: - ... - - - diff --git a/typings/rcrs_core/properties/booleanProperty.pyi b/typings/rcrs_core/properties/booleanProperty.pyi deleted file mode 100644 index 7bd816f..0000000 --- a/typings/rcrs_core/properties/booleanProperty.pyi +++ /dev/null @@ -1,21 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.properties.property import Property - -class BooleanProperty(Property): - def __init__(self, urn) -> None: - ... - - def set_fields(self, value): # -> None: - ... - - def copy(self): # -> BooleanProperty: - ... - - def take_value(self, _property): # -> None: - ... - - - diff --git a/typings/rcrs_core/properties/edgeListProperty.pyi b/typings/rcrs_core/properties/edgeListProperty.pyi deleted file mode 100644 index 2af9f10..0000000 --- a/typings/rcrs_core/properties/edgeListProperty.pyi +++ /dev/null @@ -1,38 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from typing import List -from rcrs_core.properties.property import Property -from rcrs_core.entities.edge import Edge - -class EdgeListProperty(Property): - def __init__(self, urn) -> None: - ... - - def get_fields(self): # -> None: - ... - - def set_fields(self, data): # -> None: - ... - - def set_value(self, _value: List[Edge]): # -> None: - ... - - def set_edges(self, _edges: List[Edge]): # -> None: - ... - - def add_edge(self, _edge): # -> None: - ... - - def clear_edges(self): # -> None: - ... - - def take_value(self, _value): # -> None: - ... - - def copy(self): # -> EdgeListProperty: - ... - - - diff --git a/typings/rcrs_core/properties/entityIDListProperty.pyi b/typings/rcrs_core/properties/entityIDListProperty.pyi deleted file mode 100644 index 9f629f1..0000000 --- a/typings/rcrs_core/properties/entityIDListProperty.pyi +++ /dev/null @@ -1,26 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.properties.property import Property -from rcrs_core.worldmodel.entityID import EntityID -from typing import List - -class EntityIDListProperty(Property): - def __init__(self, urn) -> None: - ... - - def set_fields(self, data): # -> None: - ... - - def set_value(self, _value: List[EntityID]): # -> None: - ... - - def copy(self): # -> EntityIDListProperty: - ... - - def take_value(self, _property): # -> None: - ... - - - diff --git a/typings/rcrs_core/properties/entityIDProperty.pyi b/typings/rcrs_core/properties/entityIDProperty.pyi deleted file mode 100644 index da7b261..0000000 --- a/typings/rcrs_core/properties/entityIDProperty.pyi +++ /dev/null @@ -1,21 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.properties.property import Property - -class EntityIDProperty(Property): - def __init__(self, urn) -> None: - ... - - def set_fields(self, value): # -> None: - ... - - def copy(self): # -> EntityIDProperty: - ... - - def take_value(self, _property): # -> None: - ... - - - diff --git a/typings/rcrs_core/properties/intArrayProperty.pyi b/typings/rcrs_core/properties/intArrayProperty.pyi deleted file mode 100644 index a4c7a6c..0000000 --- a/typings/rcrs_core/properties/intArrayProperty.pyi +++ /dev/null @@ -1,24 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.properties.property import Property - -class IntArrayProperty(Property): - def __init__(self, urn) -> None: - ... - - def set_fields(self, data): # -> None: - ... - - def set_value(self, data): # -> None: - ... - - def copy(self): # -> IntArrayProperty: - ... - - def take_value(self, _property): # -> None: - ... - - - diff --git a/typings/rcrs_core/properties/intProperty.pyi b/typings/rcrs_core/properties/intProperty.pyi deleted file mode 100644 index 83cf474..0000000 --- a/typings/rcrs_core/properties/intProperty.pyi +++ /dev/null @@ -1,21 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from rcrs_core.properties.property import Property - -class IntProperty(Property): - def __init__(self, urn) -> None: - ... - - def set_fields(self, value): # -> None: - ... - - def copy(self): # -> IntProperty: - ... - - def take_value(self, _property): # -> None: - ... - - - diff --git a/typings/rcrs_core/properties/property.pyi b/typings/rcrs_core/properties/property.pyi deleted file mode 100644 index cbff460..0000000 --- a/typings/rcrs_core/properties/property.pyi +++ /dev/null @@ -1,22 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -class Property: - def __init__(self, _urn) -> None: - ... - - def get_urn(self): # -> Any: - ... - - def get_value(self): # -> None: - ... - - def set_value(self, _value): # -> None: - ... - - def to_string(self): # -> None: - ... - - - diff --git a/typings/rcrs_core/properties/standardPropertyFactory.pyi b/typings/rcrs_core/properties/standardPropertyFactory.pyi deleted file mode 100644 index be77e22..0000000 --- a/typings/rcrs_core/properties/standardPropertyFactory.pyi +++ /dev/null @@ -1,13 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -class StandardPropertyFactory: - def __init__(self) -> None: - ... - - def make_property(urn): # -> IntProperty | IntArrayProperty | BooleanProperty | EntityIDProperty | EntityIDListProperty | EdgeListProperty | None: - ... - - - diff --git a/typings/rcrs_core/worldmodel/changeSet.pyi b/typings/rcrs_core/worldmodel/changeSet.pyi deleted file mode 100644 index d1788f1..0000000 --- a/typings/rcrs_core/worldmodel/changeSet.pyi +++ /dev/null @@ -1,34 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -class ChangeSet: - def __init__(self, _change_set=...) -> None: - ... - - def add_change(self, entity_id, entity_urn, property): # -> None: - ... - - def entity_deleted(self, _entity_id): # -> None: - ... - - def get_changed_properties(self, _entity_id): # -> list[Any]: - ... - - def get_changed_property(self, _entity_id, prop_urn): # -> None: - ... - - def get_changed_entities(self): # -> dict_keys[Any, Any]: - ... - - def get_deleted_entities(self): # -> set[Any]: - ... - - def get_entity_urn(self, _entity_id): # -> None: - ... - - def merge(self, _change_set): # -> None: - ... - - - diff --git a/typings/rcrs_core/worldmodel/entityID.pyi b/typings/rcrs_core/worldmodel/entityID.pyi deleted file mode 100644 index e570b8e..0000000 --- a/typings/rcrs_core/worldmodel/entityID.pyi +++ /dev/null @@ -1,25 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -class EntityID: - def __init__(self, id: int) -> None: - ... - - def __eq__(self, other) -> bool: - ... - - def __hash__(self) -> int: - ... - - def get_value(self): # -> | int: - ... - - def __str__(self) -> str: - ... - - def equals(self, o): # -> bool: - ... - - - diff --git a/typings/rcrs_core/worldmodel/worldmodel.pyi b/typings/rcrs_core/worldmodel/worldmodel.pyi deleted file mode 100644 index e9ccce0..0000000 --- a/typings/rcrs_core/worldmodel/worldmodel.pyi +++ /dev/null @@ -1,51 +0,0 @@ -""" -This type stub file was generated by pyright. -""" - -from typing import List -from rcrs_core.entities.entity import Entity -from rcrs_core.worldmodel.changeSet import ChangeSet -from rcrs_core.worldmodel.entityID import EntityID - -class WorldModel: - def __init__(self) -> None: - ... - - def add_entities(self, entities: List[Entity]): # -> None: - ... - - def get_entity(self, entity_id: EntityID) -> Entity: - ... - - def add_entity(self, entity): # -> None: - ... - - def remove_entity(self, entity_id): # -> None: - ... - - def get_entities(self): # -> dict_values[Any, Any]: - ... - - def merge(self, change_set: ChangeSet): # -> None: - ... - - def index_entities(self): # -> None: - ... - - def make_rectangle(self, entity): # -> tuple[None, None, None, None] | tuple[float, float, float, float]: - ... - - def get_rect_bounds(self): # -> tuple[Any, Any, Any, Any]: - ... - - def get_world_bounds(self): # -> tuple[Any, Any, Any, Any]: - ... - - def get_objects_in_rectangle(self, x1, y1, x2, y2): # -> list[Any]: - ... - - def get_objects_in_range(self, entity, range): # -> list[Any]: - ... - - - From 17f6f34db1b57be641bbf175edf9bcca4753170b Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 2 Oct 2024 14:10:09 +0900 Subject: [PATCH 066/249] refactor: Remove unused Agent class and related imports --- adf_core_python/core/agent/agent.py | 30 ----------------------------- 1 file changed, 30 deletions(-) delete mode 100644 adf_core_python/core/agent/agent.py diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py deleted file mode 100644 index ae19b1e..0000000 --- a/adf_core_python/core/agent/agent.py +++ /dev/null @@ -1,30 +0,0 @@ -from rcrs_core.entities.entity import Entity - -from adf_core_python.core.agent.config.module_config import ModuleConfig -from adf_core_python.core.agent.develop.develop_data import DevelopData -from adf_core_python.core.agent.info.scenario_info import Mode -from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - - -class Agent(Entity): - def __init__( - self, - team_name: str, - is_precompute: bool, - is_debug: bool, - data_storage_name: str, - module_config: ModuleConfig, - develop_data: DevelopData, - ): - self._tema_name = team_name - self._is_precompute = is_precompute - self._is_debug = is_debug - - if self._is_precompute: - # PrecomputeData.remove_data(data_storage_name) - self._mode = Mode.PRECOMPUTATION - - self._module_config: ModuleConfig = module_config - self._develop_data: DevelopData = develop_data - self._precompute_data: PrecomputeData = PrecomputeData(data_storage_name) - self._message_manager = None From cdea89918768af565958319935313d140aa7166f Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 2 Oct 2024 14:10:23 +0900 Subject: [PATCH 067/249] feat: Add pyrightconfig.json for standard type checking mode and exclude site-packages --- pyrightconfig.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 pyrightconfig.json diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 0000000..8a1eb70 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,4 @@ +{ + "typeCheckingMode": "standard", + "exclude": ["**/site-packages"] +} From 2b778558e1d20ab7f5136b8b0d12b5d7dcbdb274 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 2 Oct 2024 14:10:40 +0900 Subject: [PATCH 068/249] feat: fix type --- .../core/agent/config/module_config.py | 5 ++-- adf_core_python/core/agent/info/agent_info.py | 7 +++-- .../tactics/default_tactics_ambulance_team.py | 30 ++++++++++--------- .../tactics/default_tactics_fire_brigade.py | 30 ++++++++++--------- .../tactics/default_tactics_police_force.py | 30 ++++++++++--------- 5 files changed, 56 insertions(+), 46 deletions(-) diff --git a/adf_core_python/core/agent/config/module_config.py b/adf_core_python/core/agent/config/module_config.py index 095cb50..72a292a 100644 --- a/adf_core_python/core/agent/config/module_config.py +++ b/adf_core_python/core/agent/config/module_config.py @@ -79,11 +79,12 @@ def _flatten( dict[str, Any] Flattened dictionary """ - flatten_data = {} + flatten_data: dict[str, Any] = {} for key, value in data.items(): new_key = f"{parent_key}{sep}{key}" if parent_key else key if isinstance(value, dict): - flatten_data.update(self._flatten(value, new_key, sep=sep)) + v: dict[str, Any] = value + flatten_data.update(self._flatten(v, new_key, sep=sep)) else: flatten_data[new_key] = value return flatten_data diff --git a/adf_core_python/core/agent/info/agent_info.py b/adf_core_python/core/agent/info/agent_info.py index d65070c..030de23 100644 --- a/adf_core_python/core/agent/info/agent_info.py +++ b/adf_core_python/core/agent/info/agent_info.py @@ -2,8 +2,11 @@ from typing import Any from rcrs_core.agents.agent import Agent +from rcrs_core.entities.entity import Entity from rcrs_core.entities.human import Human -from rcrs_core.worldmodel.worldmodel import ChangeSet, Entity, EntityID, WorldModel +from rcrs_core.worldmodel.changeSet import ChangeSet +from rcrs_core.worldmodel.entityID import EntityID +from rcrs_core.worldmodel.worldmodel import WorldModel from adf_core_python.core.agent.action.action import Action @@ -97,7 +100,7 @@ def get_position_entity_id(self) -> EntityID: """ entity = self._world_model.get_entity(self.get_entity_id()) if isinstance(entity, Human): - return entity.get_position_property() + return entity.get_position() else: return entity.get_id() diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 45c7707..8666e14 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -111,21 +111,23 @@ def think( entity_id = agent_info.get_entity_id() # noqa: F841 target_entity_id = self._human_detector.calculate().get_target_entity_id() - action = ( - self._action_transport.set_target_entity_id(target_entity_id) - .calc() - .get_action() - ) - if action is not None: - return action + if target_entity_id is not None: + action = ( + self._action_transport.set_target_entity_id(target_entity_id) + .calc() + .get_action() + ) + if action is not None: + return action target_entity_id = self._search.calculate().get_target_entity_id() - action = ( - self._action_ext_move.set_target_entity_id(target_entity_id) - .calc() - .get_action() - ) - if action is not None: - return action + if target_entity_id is not None: + action = ( + self._action_ext_move.set_target_entity_id(target_entity_id) + .calc() + .get_action() + ) + if action is not None: + return action return ActionRest() diff --git a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py index 11b38af..2af3378 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py @@ -110,21 +110,23 @@ def think( entity_id = agent_info.get_entity_id() # noqa: F841 target_entity_id = self._human_detector.calculate().get_target_entity_id() - action = ( - self._action_fire_rescue.set_target_entity_id(target_entity_id) - .calc() - .get_action() - ) - if action is not None: - return action + if target_entity_id is not None: + action = ( + self._action_fire_rescue.set_target_entity_id(target_entity_id) + .calc() + .get_action() + ) + if action is not None: + return action target_entity_id = self._search.calculate().get_target_entity_id() - action = ( - self._action_ext_move.set_target_entity_id(target_entity_id) - .calc() - .get_action() - ) - if action is not None: - return action + if target_entity_id is not None: + action = ( + self._action_ext_move.set_target_entity_id(target_entity_id) + .calc() + .get_action() + ) + if action is not None: + return action return ActionRest() diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py index bd4f29d..3253aeb 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -114,21 +114,23 @@ def think( entity_id = agent_info.get_entity_id() # noqa: F841 target_entity_id = self._road_detector.calculate().get_target_entity_id() - action = ( - self._action_ext_clear.set_target_entity_id(target_entity_id) - .calc() - .get_action() - ) - if action is not None: - return action + if target_entity_id is not None: + action = ( + self._action_ext_clear.set_target_entity_id(target_entity_id) + .calc() + .get_action() + ) + if action is not None: + return action target_entity_id = self._search.calculate().get_target_entity_id() - action = ( - self._action_ext_clear.set_target_entity_id(target_entity_id) - .calc() - .get_action() - ) - if action is not None: - return action + if target_entity_id is not None: + action = ( + self._action_ext_clear.set_target_entity_id(target_entity_id) + .calc() + .get_action() + ) + if action is not None: + return action return ActionRest() From c1b8295eb94d6874e7eea10980208af326914e29 Mon Sep 17 00:00:00 2001 From: harrki Date: Thu, 3 Oct 2024 20:02:11 +0900 Subject: [PATCH 069/249] fix: .gitignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9a270b3..dd7a3b1 100644 --- a/.gitignore +++ b/.gitignore @@ -168,4 +168,7 @@ cython_debug/ .vscode/ # pycharm -.idea/ \ No newline at end of file +.idea/ + +# MacOS +.DS_Store \ No newline at end of file From e1c026b1a1fc46943224868f237e887fae257be1 Mon Sep 17 00:00:00 2001 From: harrki Date: Thu, 3 Oct 2024 20:04:35 +0900 Subject: [PATCH 070/249] fix: wrong module paths --- .../implement/extend_action/default_extend_action_move.py | 2 +- .../implement/extend_action/default_extend_action_transport.py | 2 +- tests/core/agent/module/test_module_manager.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/adf_core_python/implement/extend_action/default_extend_action_move.py b/adf_core_python/implement/extend_action/default_extend_action_move.py index a8df764..c6d7e0e 100644 --- a/adf_core_python/implement/extend_action/default_extend_action_move.py +++ b/adf_core_python/implement/extend_action/default_extend_action_move.py @@ -39,7 +39,7 @@ def __init__( PathPlanning, self.module_manager.get_module( "DefaultExtendActionMove.PathPlanning", - "adf_core_python.implement.module.astar_path_planning.AStarPathPlanning", + "adf_core_python.implement.module.algorithm.astar_path_planning.AStarPathPlanning", ), ) case Mode.PRECOMPUTATION: diff --git a/adf_core_python/implement/extend_action/default_extend_action_transport.py b/adf_core_python/implement/extend_action/default_extend_action_transport.py index f770b48..73655a0 100644 --- a/adf_core_python/implement/extend_action/default_extend_action_transport.py +++ b/adf_core_python/implement/extend_action/default_extend_action_transport.py @@ -44,7 +44,7 @@ def __init__( PathPlanning, self.module_manager.get_module( "DefaultExtendActionMove.PathPlanning", - "adf_core_python.implement.module.astar_path_planning.AStarPathPlanning", + "adf_core_python.implement.module.algorithm.astar_path_planning.AStarPathPlanning", ), ) case Mode.PRECOMPUTATION: diff --git a/tests/core/agent/module/test_module_manager.py b/tests/core/agent/module/test_module_manager.py index 458791e..41a0a57 100644 --- a/tests/core/agent/module/test_module_manager.py +++ b/tests/core/agent/module/test_module_manager.py @@ -12,7 +12,7 @@ def test_can_get_module(self) -> None: config = ModuleConfig(config_file_path) config.set_value( "test_module", - "adf_core_python.implement.module.astar_path_planning.AStarPathPlanning", + "adf_core_python.implement.module.algorithm.astar_path_planning.AStarPathPlanning", ) module_manager = self.create_module_manager(config) module = module_manager.get_module("test_module", "test_module") From 4157c4eabca38e7202b8516bc43774a53dbe951a Mon Sep 17 00:00:00 2001 From: harrki Date: Thu, 3 Oct 2024 20:07:42 +0900 Subject: [PATCH 071/249] fix: create new directory and move astar path planning module --- adf_core_python/implement/module/__init__.py | 0 .../implement/module/algorithm/__init__.py | 0 .../{ => algorithm}/astar_path_planning.py | 17 +++++++++++++++++ 3 files changed, 17 insertions(+) create mode 100644 adf_core_python/implement/module/__init__.py create mode 100644 adf_core_python/implement/module/algorithm/__init__.py rename adf_core_python/implement/module/{ => algorithm}/astar_path_planning.py (53%) diff --git a/adf_core_python/implement/module/__init__.py b/adf_core_python/implement/module/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/implement/module/algorithm/__init__.py b/adf_core_python/implement/module/algorithm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/implement/module/astar_path_planning.py b/adf_core_python/implement/module/algorithm/astar_path_planning.py similarity index 53% rename from adf_core_python/implement/module/astar_path_planning.py rename to adf_core_python/implement/module/algorithm/astar_path_planning.py index 24c7afb..4947366 100644 --- a/adf_core_python/implement/module/astar_path_planning.py +++ b/adf_core_python/implement/module/algorithm/astar_path_planning.py @@ -2,6 +2,8 @@ from typing import TYPE_CHECKING +from rcrs_core.worldmodel.entityID import EntityID + from adf_core_python.core.component.module.algorithm.path_planning import ( PathPlanning, ) @@ -11,6 +13,9 @@ class AStarPathPlanning(PathPlanning): + def get_result(self) -> list[EntityID]: + return self._result + def get_path( self, from_entity_id: EntityID, to_entity_id: EntityID ) -> list[EntityID]: @@ -19,5 +24,17 @@ def get_path( def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: return 0.0 + def set_from(self, from_entity_id: EntityID) -> PathPlanning: + self._from = from_entity_id + return self + + def set_destination(self, destination_entity_ids: list[EntityID]) -> PathPlanning: + self._targets = destination_entity_ids + return self + def calculate(self) -> AStarPathPlanning: return self + + +# TODO: Implement the A* path planning algorithm +# TODO: Move this file to algorithm directory From 12610f639faf9165ba7483e64d14d73f9b591c34 Mon Sep 17 00:00:00 2001 From: harrki Date: Thu, 3 Oct 2024 20:09:46 +0900 Subject: [PATCH 072/249] feat: Create target allocator modules --- .../complex/ambulance_target_allocator.py | 46 +++++++++++++++++++ .../module/complex/fire_target_allocator.py | 46 +++++++++++++++++++ .../module/complex/police_target_allocator.py | 46 +++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 adf_core_python/core/component/module/complex/ambulance_target_allocator.py create mode 100644 adf_core_python/core/component/module/complex/fire_target_allocator.py create mode 100644 adf_core_python/core/component/module/complex/police_target_allocator.py diff --git a/adf_core_python/core/component/module/complex/ambulance_target_allocator.py b/adf_core_python/core/component/module/complex/ambulance_target_allocator.py new file mode 100644 index 0000000..9bd48e7 --- /dev/null +++ b/adf_core_python/core/component/module/complex/ambulance_target_allocator.py @@ -0,0 +1,46 @@ +from abc import abstractmethod +from rcrs_core.worldmodel.entityID import EntityID +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.module.complex.target_allocator import ( + TargetAllocator, +) + + +class AmbulanceTargetAllocator(TargetAllocator): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + @abstractmethod + def get_result(self) -> dict[EntityID, EntityID]: + pass + + @abstractmethod + def calculate(self) -> TargetAllocator: + pass + + def resume(self, precompute_data: PrecomputeData) -> TargetAllocator: + super().resume(precompute_data) + return self + + def prepare(self) -> TargetAllocator: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> TargetAllocator: + super().update_info(message_manager) + return self diff --git a/adf_core_python/core/component/module/complex/fire_target_allocator.py b/adf_core_python/core/component/module/complex/fire_target_allocator.py new file mode 100644 index 0000000..6804030 --- /dev/null +++ b/adf_core_python/core/component/module/complex/fire_target_allocator.py @@ -0,0 +1,46 @@ +from abc import abstractmethod +from rcrs_core.worldmodel.entityID import EntityID +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.module.complex.target_allocator import ( + TargetAllocator, +) + + +class FireTargetAllocator(TargetAllocator): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + @abstractmethod + def get_result(self) -> dict[EntityID, EntityID]: + pass + + @abstractmethod + def calculate(self) -> TargetAllocator: + pass + + def resume(self, precompute_data: PrecomputeData) -> TargetAllocator: + super().resume(precompute_data) + return self + + def prepare(self) -> TargetAllocator: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> TargetAllocator: + super().update_info(message_manager) + return self diff --git a/adf_core_python/core/component/module/complex/police_target_allocator.py b/adf_core_python/core/component/module/complex/police_target_allocator.py new file mode 100644 index 0000000..d199ca3 --- /dev/null +++ b/adf_core_python/core/component/module/complex/police_target_allocator.py @@ -0,0 +1,46 @@ +from abc import abstractmethod +from rcrs_core.worldmodel.entityID import EntityID +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.module.complex.target_allocator import ( + TargetAllocator, +) + + +class PoliceTargetAllocator(TargetAllocator): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + @abstractmethod + def get_result(self) -> dict[EntityID, EntityID]: + pass + + @abstractmethod + def calculate(self) -> TargetAllocator: + pass + + def resume(self, precompute_data: PrecomputeData) -> TargetAllocator: + super().resume(precompute_data) + return self + + def prepare(self) -> TargetAllocator: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> TargetAllocator: + super().update_info(message_manager) + return self From 124245c04543bad1fc666b50c089e00c5e1334ba Mon Sep 17 00:00:00 2001 From: harrki Date: Fri, 4 Oct 2024 20:56:14 +0900 Subject: [PATCH 073/249] fix: Add deprecated message to old functions --- .../implement/module/algorithm/astar_path_planning.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/adf_core_python/implement/module/algorithm/astar_path_planning.py b/adf_core_python/implement/module/algorithm/astar_path_planning.py index 4947366..d9166ea 100644 --- a/adf_core_python/implement/module/algorithm/astar_path_planning.py +++ b/adf_core_python/implement/module/algorithm/astar_path_planning.py @@ -1,5 +1,6 @@ from __future__ import annotations +from warnings import warn from typing import TYPE_CHECKING from rcrs_core.worldmodel.entityID import EntityID @@ -26,15 +27,14 @@ def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> floa def set_from(self, from_entity_id: EntityID) -> PathPlanning: self._from = from_entity_id + warn("This method is deprecated. Use get_path instead.", DeprecationWarning) return self def set_destination(self, destination_entity_ids: list[EntityID]) -> PathPlanning: self._targets = destination_entity_ids + warn("This method is deprecated. Use get_path instead.", DeprecationWarning) return self def calculate(self) -> AStarPathPlanning: + warn("This method is deprecated. Use get_path instead.", DeprecationWarning) return self - - -# TODO: Implement the A* path planning algorithm -# TODO: Move this file to algorithm directory From d0c520bbfe8224f1d4f26b6f52b6062f0ab2990e Mon Sep 17 00:00:00 2001 From: harrki Date: Fri, 4 Oct 2024 20:56:42 +0900 Subject: [PATCH 074/249] feat: Create default target allocator modules --- .../default_ambulance_target_allocator.py | 121 ++++++++++++++ .../complex/default_fire_target_allocator.py | 121 ++++++++++++++ .../default_police_target_allocator.py | 156 ++++++++++++++++++ 3 files changed, 398 insertions(+) create mode 100644 adf_core_python/implement/module/complex/default_ambulance_target_allocator.py create mode 100644 adf_core_python/implement/module/complex/default_fire_target_allocator.py create mode 100644 adf_core_python/implement/module/complex/default_police_target_allocator.py diff --git a/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py b/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py new file mode 100644 index 0000000..37e258d --- /dev/null +++ b/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py @@ -0,0 +1,121 @@ +from functools import cmp_to_key + +from rcrs_core.entities.ambulanceTeam import AmbulanceTeamEntity +from rcrs_core.entities.human import Human +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.component.module.complex.ambulance_target_allocator import ( + AmbulanceTargetAllocator, +) + + +class DefaultAmbulanceTargetAllocator(AmbulanceTargetAllocator): + def __init__( + self, + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + ): + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._priority_humans = set() + self._target_humans = set() + self._ambulance_team_info_map = {} + + def resume(self, precompute_data): + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + for entity_id in self._world_info.get_entity_ids_of_type(AmbulanceTeamEntity): + self._ambulance_team_info_map[entity_id] = self.AmbulanceTeamInfo(entity_id) + return self + + def prepare(self): + super().prepare() + if self.get_count_prepare() >= 2: + return self + for entity_id in self._world_info.get_entity_ids_of_type(AmbulanceTeamEntity): + self._ambulance_team_info_map[entity_id] = self.AmbulanceTeamInfo(entity_id) + return self + + def update_info(self, message_manager): + super().update_info(message_manager) + # TODO: implement after message_manager is implemented + return self + + def calculate(self): + agents = self.get_action_agents(self._ambulance_team_info_map) + removes = [] + current_time = self._agent_info.get_time() + + for target in self._priority_humans: + if len(agents) > 0: + target_entity = self._world_info.get_entity(target) + if target_entity is not None and isinstance(target_entity, Human): + agents = sorted( + agents, key=cmp_to_key(self._compare_by_distance(target_entity)) + ) + result = agents.pop(0) + info = self._ambulance_team_info_map[result.get_id()] + if info is not None: + info._can_new_action = False + info._target = target + info.command_time = current_time + self._ambulance_team_info_map[result.get_id()] = info + removes.append(target) + + for r in removes: + self._priority_humans.remove(r) + removes.clear() + + for target in self._target_humans: + if len(agents) > 0: + target_entity = self._world_info.get_entity(target) + if target_entity is not None and isinstance(target_entity, Human): + agents = sorted( + agents, key=cmp_to_key(self._compare_by_distance(target_entity)) + ) + result = agents.pop(0) + info = self._ambulance_team_info_map[result.get_id()] + if info is not None: + info._can_new_action = False + info._target = target + info.command_time = current_time + self._ambulance_team_info_map[result.get_id()] = info + removes.append(target) + + for r in removes: + self._target_humans.remove(r) + + return self + + def get_result(self): + return self._convert(self._ambulance_team_info_map) + + def _compare_by_distance(self, target_entity): + def _cmp_func(self, entity_a, entity_b): + distance_a = self._world_info.get_distance(target_entity, entity_a) + distance_b = self._world_info.get_distance(target_entity, entity_b) + if distance_a < distance_b: + return -1 + elif distance_a > distance_b: + return 1 + else: + return 0 + + def _convert(self, info_map): + result = {} + for entity_id in info_map.keys(): + info = info_map[entity_id] + if info is not None and info._target is not None: + result[entity_id] = info._target + + class AmbulanceTeamInfo: + def __init__(self, entity_id: EntityID): + self._agent_id = entity_id + self._target = None + self._can_new_action = True + self.command_time = -1 diff --git a/adf_core_python/implement/module/complex/default_fire_target_allocator.py b/adf_core_python/implement/module/complex/default_fire_target_allocator.py new file mode 100644 index 0000000..b5155f6 --- /dev/null +++ b/adf_core_python/implement/module/complex/default_fire_target_allocator.py @@ -0,0 +1,121 @@ +from functools import cmp_to_key + +from rcrs_core.entities.fireBrigade import FireBrigadeEntity +from rcrs_core.entities.human import Human +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.component.module.complex.fire_target_allocator import ( + FireTargetAllocator, +) + + +class DefaultFireTargetAllocator(FireTargetAllocator): + def __init__( + self, + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + ): + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._priority_humans = set() + self._target_humans = set() + self._fire_brigade_info_map = {} + + def resume(self, precompute_data): + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + for entity_id in self._world_info.get_entity_ids_of_type(FireBrigadeEntity): + self._fire_brigade_info_map[entity_id] = self.FireBrigadeInfo(entity_id) + return self + + def prepare(self): + super().prepare() + if self.get_count_prepare() >= 2: + return self + for entity_id in self._world_info.get_entity_ids_of_type(FireBrigadeEntity): + self._fire_brigade_info_map[entity_id] = self.FireBrigadeInfo(entity_id) + return self + + def update_info(self, message_manager): + super().update_info(message_manager) + # TODO: implement after message_manager is implemented + return self + + def calculate(self): + agents = self.get_action_agents(self._fire_brigade_info_map) + removes = [] + current_time = self._agent_info.get_time() + + for target in self._priority_humans: + if len(agents) > 0: + target_entity = self._world_info.get_entity(target) + if target_entity is not None and isinstance(target_entity, Human): + agents = sorted( + agents, key=cmp_to_key(self._compare_by_distance(target_entity)) + ) + result = agents.pop(0) + info = self._fire_brigade_info_map[result.get_id()] + if info is not None: + info._can_new_action = False + info._target = target + info.command_time = current_time + self._fire_brigade_info_map[result.get_id()] = info + removes.append(target) + + for r in removes: + self._priority_humans.remove(r) + removes.clear() + + for target in self._target_humans: + if len(agents) > 0: + target_entity = self._world_info.get_entity(target) + if target_entity is not None and isinstance(target_entity, Human): + agents = sorted( + agents, key=cmp_to_key(self._compare_by_distance(target_entity)) + ) + result = agents.pop(0) + info = self._fire_brigade_info_map[result.get_id()] + if info is not None: + info._can_new_action = False + info._target = target + info.command_time = current_time + self._fire_brigade_info_map[result.get_id()] = info + removes.append(target) + + for r in removes: + self._target_humans.remove(r) + + return self + + def get_result(self): + return self._convert(self._fire_brigade_info_map) + + def _compare_by_distance(self, target_entity): + def _cmp_func(self, entity_a, entity_b): + distance_a = self._world_info.get_distance(target_entity, entity_a) + distance_b = self._world_info.get_distance(target_entity, entity_b) + if distance_a < distance_b: + return -1 + elif distance_a > distance_b: + return 1 + else: + return 0 + + def _convert(self, info_map): + result = {} + for entity_id in info_map.keys(): + info = info_map[entity_id] + if info is not None and info._target is not None: + result[entity_id] = info._target + + class FireBrigadeInfo: + def __init__(self, entity_id: EntityID): + self._agent_id = entity_id + self._target = None + self._can_new_action = True + self.command_time = -1 diff --git a/adf_core_python/implement/module/complex/default_police_target_allocator.py b/adf_core_python/implement/module/complex/default_police_target_allocator.py new file mode 100644 index 0000000..08c4ae5 --- /dev/null +++ b/adf_core_python/implement/module/complex/default_police_target_allocator.py @@ -0,0 +1,156 @@ +from functools import cmp_to_key + +from rcrs_core.entities.policeForce import PoliceForceEntity +from rcrs_core.worldmodel.entityID import EntityID +from rcrs_core.entities.refuge import Refuge +from rcrs_core.entities.building import Building +from rcrs_core.entities.gassStation import GasStation +from rcrs_core.entities.road import Road + +from adf_core_python.core.component.module.complex.police_target_allocator import ( + PoliceTargetAllocator, +) + + +class DefaultPoliceTargetAllocator(PoliceTargetAllocator): + def __init__( + self, + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + ): + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._priority_areas = set() + self._target_areas = set() + self._agent_info_map = {} + + def resume(self, precompute_data): + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + + for entity_id in self._world_info.get_entity_ids_of_type(PoliceForceEntity): + self._agent_info_map[entity_id] = self.PoliceForceInfo(entity_id) + for entity in ( + self._world_info.get_entities_of_type(Refuge) + + self._world_info.get_entities_of_type(Building) + + self._world_info.get_entities_of_type(GasStation) + ): + for entity_id in entity.get_neighbours(): + neighbour = self._world_info.get_entity(entity_id) + if isinstance(neighbour, Road): + self._target_areas.add(entity_id) + + for entity in self._world_info.get_entities_of_type(Refuge): + for entity_id in entity.get_neighbours(): + neighbour = self._world_info.get_entity(entity_id) + if isinstance(neighbour, Road): + self._priority_areas.add(entity_id) + return self + + def prepare(self): + super().prepare() + if self.get_count_prepare() >= 2: + return self + + for entity_id in self._world_info.get_entity_ids_of_type(PoliceForceEntity): + self._agent_info_map[entity_id] = self.PoliceForceInfo(entity_id) + + for entity in ( + self._world_info.get_entities_of_type(Refuge) + + self._world_info.get_entities_of_type(Building) + + self._world_info.get_entities_of_type(GasStation) + ): + for entity_id in entity.get_neighbours(): + neighbour = self._world_info.get_entity(entity_id) + if isinstance(neighbour, Road): + self._target_areas.add(entity_id) + + for entity in self._world_info.get_entities_of_type(Refuge): + for entity_id in entity.get_neighbours(): + neighbour = self._world_info.get_entity(entity_id) + if isinstance(neighbour, Road): + self._priority_areas.add(entity_id) + + return self + + def update_info(self, message_manager): + super().update_info(message_manager) + # TODO: implement after message_manager is implemented + return self + + def calculate(self): + agents = self.get_action_agents(self._agent_info_map) + removes = [] + current_time = self._agent_info.get_time() + + for target in self._priority_areas: + if len(agents) > 0: + target_entity = self._world_info.get_entity(target) + if target_entity is not None: + agents = sorted( + agents, key=cmp_to_key(self._compare_by_distance(target_entity)) + ) + result = agents.pop(0) + info = self._agent_info_map[result.get_id()] + if info is not None: + info._can_new_action = False + info._target = target + info.command_time = current_time + self._agent_info_map[result.get_id()] = info + removes.append(target) + + for r in removes: + self._priority_areas.remove(r) + + areas = [] + for target in self._target_areas: + target_entity = self._world_info.get_entity(target) + if target_entity is not None: + areas.append(target_entity) + + for agent in agents: + if len(areas) > 0: + areas.sort(key=cmp_to_key(self._compare_by_distance(agent))) + result = areas.pop(0) + self._target_areas.remove(result.get_id()) + info = self._agent_info_map[agent.get_id()] + if info is not None: + info._can_new_action = False + info._target = result.get_id() + info.command_time = current_time + self._agent_info_map[agent.get_id()] = info + + return self + + def get_result(self): + return self._convert(self._agent_info_map) + + def _compare_by_distance(self, target_entity): + def _cmp_func(self, entity_a, entity_b): + distance_a = self._world_info.get_distance(target_entity, entity_a) + distance_b = self._world_info.get_distance(target_entity, entity_b) + if distance_a < distance_b: + return -1 + elif distance_a > distance_b: + return 1 + else: + return 0 + + def _convert(self, info_map): + result = {} + for entity_id in info_map.keys(): + info = info_map[entity_id] + if info is not None and info._target is not None: + result[entity_id] = info._target + + class PoliceForceInfo: + def __init__(self, entity_id: EntityID): + self._agent_id = entity_id + self._target = None + self._can_new_action = True + self.command_time = -1 From f3079b88600d12c50b6239e4d0289ce0813c557b Mon Sep 17 00:00:00 2001 From: harrki Date: Tue, 8 Oct 2024 16:45:26 +0900 Subject: [PATCH 075/249] fix: Fixed inconsistencies from merge request --- .../module/algorithm/path_planning.py | 12 -- .../complex/ambulance_target_allocator.py | 2 + .../module/complex/fire_target_allocator.py | 2 + .../module/complex/police_target_allocator.py | 2 + .../module/algorithm/astar_path_planning.py | 40 ------ .../default_ambulance_target_allocator.py | 92 +++++++++---- .../complex/default_fire_target_allocator.py | 86 ++++++++---- .../default_police_target_allocator.py | 122 +++++++++++------- .../module/complex/default_road_detector.py | 64 +++++---- 9 files changed, 243 insertions(+), 179 deletions(-) delete mode 100644 adf_core_python/implement/module/algorithm/astar_path_planning.py diff --git a/adf_core_python/core/component/module/algorithm/path_planning.py b/adf_core_python/core/component/module/algorithm/path_planning.py index 191631e..b741862 100644 --- a/adf_core_python/core/component/module/algorithm/path_planning.py +++ b/adf_core_python/core/component/module/algorithm/path_planning.py @@ -30,10 +30,6 @@ def __init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - @abstractmethod - def get_result(self) -> list[EntityID]: - pass - @abstractmethod def get_path( self, from_entity_id: EntityID, to_entity_id: EntityID @@ -44,14 +40,6 @@ def get_path( def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: pass - @abstractmethod - def set_from(self, from_entity_id: EntityID) -> PathPlanning: - pass - - @abstractmethod - def set_destination(self, destination_entity_ids: list[EntityID]) -> PathPlanning: - pass - def precompute(self, precompute_data: PrecomputeData) -> PathPlanning: super().precompute(precompute_data) return self diff --git a/adf_core_python/core/component/module/complex/ambulance_target_allocator.py b/adf_core_python/core/component/module/complex/ambulance_target_allocator.py index 9bd48e7..2c36e74 100644 --- a/adf_core_python/core/component/module/complex/ambulance_target_allocator.py +++ b/adf_core_python/core/component/module/complex/ambulance_target_allocator.py @@ -1,5 +1,7 @@ from abc import abstractmethod + from rcrs_core.worldmodel.entityID import EntityID + from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo diff --git a/adf_core_python/core/component/module/complex/fire_target_allocator.py b/adf_core_python/core/component/module/complex/fire_target_allocator.py index 6804030..a537ef6 100644 --- a/adf_core_python/core/component/module/complex/fire_target_allocator.py +++ b/adf_core_python/core/component/module/complex/fire_target_allocator.py @@ -1,5 +1,7 @@ from abc import abstractmethod + from rcrs_core.worldmodel.entityID import EntityID + from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo diff --git a/adf_core_python/core/component/module/complex/police_target_allocator.py b/adf_core_python/core/component/module/complex/police_target_allocator.py index d199ca3..8403d8f 100644 --- a/adf_core_python/core/component/module/complex/police_target_allocator.py +++ b/adf_core_python/core/component/module/complex/police_target_allocator.py @@ -1,5 +1,7 @@ from abc import abstractmethod + from rcrs_core.worldmodel.entityID import EntityID + from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo diff --git a/adf_core_python/implement/module/algorithm/astar_path_planning.py b/adf_core_python/implement/module/algorithm/astar_path_planning.py deleted file mode 100644 index d9166ea..0000000 --- a/adf_core_python/implement/module/algorithm/astar_path_planning.py +++ /dev/null @@ -1,40 +0,0 @@ -from __future__ import annotations - -from warnings import warn -from typing import TYPE_CHECKING - -from rcrs_core.worldmodel.entityID import EntityID - -from adf_core_python.core.component.module.algorithm.path_planning import ( - PathPlanning, -) - -if TYPE_CHECKING: - from rcrs_core.worldmodel.entityID import EntityID - - -class AStarPathPlanning(PathPlanning): - def get_result(self) -> list[EntityID]: - return self._result - - def get_path( - self, from_entity_id: EntityID, to_entity_id: EntityID - ) -> list[EntityID]: - return [] - - def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: - return 0.0 - - def set_from(self, from_entity_id: EntityID) -> PathPlanning: - self._from = from_entity_id - warn("This method is deprecated. Use get_path instead.", DeprecationWarning) - return self - - def set_destination(self, destination_entity_ids: list[EntityID]) -> PathPlanning: - self._targets = destination_entity_ids - warn("This method is deprecated. Use get_path instead.", DeprecationWarning) - return self - - def calculate(self) -> AStarPathPlanning: - warn("This method is deprecated. Use get_path instead.", DeprecationWarning) - return self diff --git a/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py b/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py index 37e258d..1fc40f6 100644 --- a/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py +++ b/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py @@ -1,9 +1,18 @@ from functools import cmp_to_key +from typing import Callable, Optional from rcrs_core.entities.ambulanceTeam import AmbulanceTeamEntity +from rcrs_core.entities.entity import Entity from rcrs_core.entities.human import Human from rcrs_core.worldmodel.entityID import EntityID +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.module.complex.ambulance_target_allocator import ( AmbulanceTargetAllocator, ) @@ -12,42 +21,48 @@ class DefaultAmbulanceTargetAllocator(AmbulanceTargetAllocator): def __init__( self, - agent_info, - world_info, - scenario_info, - module_manager, - develop_data, - ): + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - self._priority_humans = set() - self._target_humans = set() - self._ambulance_team_info_map = {} + self._priority_humans: set[EntityID] = set() + self._target_humans: set[EntityID] = set() + self._ambulance_team_info_map: dict[ + EntityID, DefaultAmbulanceTargetAllocator.AmbulanceTeamInfo + ] = {} - def resume(self, precompute_data): + def resume(self, precompute_data: PrecomputeData) -> AmbulanceTargetAllocator: super().resume(precompute_data) if self.get_count_resume() >= 2: return self - for entity_id in self._world_info.get_entity_ids_of_type(AmbulanceTeamEntity): + for entity_id in self._world_info.get_entity_ids_of_types( + [AmbulanceTeamEntity] + ): self._ambulance_team_info_map[entity_id] = self.AmbulanceTeamInfo(entity_id) return self - def prepare(self): + def prepare(self) -> AmbulanceTargetAllocator: super().prepare() if self.get_count_prepare() >= 2: return self - for entity_id in self._world_info.get_entity_ids_of_type(AmbulanceTeamEntity): + for entity_id in self._world_info.get_entity_ids_of_types( + [AmbulanceTeamEntity] + ): self._ambulance_team_info_map[entity_id] = self.AmbulanceTeamInfo(entity_id) return self - def update_info(self, message_manager): + def update_info(self, message_manager: MessageManager) -> AmbulanceTargetAllocator: super().update_info(message_manager) # TODO: implement after message_manager is implemented return self - def calculate(self): - agents = self.get_action_agents(self._ambulance_team_info_map) + def calculate(self) -> AmbulanceTargetAllocator: + agents = self._get_action_agents(self._ambulance_team_info_map) removes = [] current_time = self._agent_info.get_time() @@ -92,13 +107,30 @@ def calculate(self): return self - def get_result(self): + def get_result(self) -> dict[EntityID, EntityID]: return self._convert(self._ambulance_team_info_map) - def _compare_by_distance(self, target_entity): - def _cmp_func(self, entity_a, entity_b): - distance_a = self._world_info.get_distance(target_entity, entity_a) - distance_b = self._world_info.get_distance(target_entity, entity_b) + def _get_action_agents( + self, + info_map: dict[EntityID, "DefaultAmbulanceTargetAllocator.AmbulanceTeamInfo"], + ) -> list[AmbulanceTeamEntity]: + result = [] + for entity in self._world_info.get_entities_of_types([AmbulanceTeamEntity]): + info = info_map[entity.get_id()] + if info is not None and info._can_new_action: + result.append(entity) + return result + + def _compare_by_distance( + self, target_entity: Entity + ) -> Callable[[Entity, Entity], int]: + def _cmp_func(entity_a: Entity, entity_b: Entity) -> int: + distance_a = self._world_info.get_distance( + target_entity.get_id(), entity_a.get_id() + ) + distance_b = self._world_info.get_distance( + target_entity.get_id(), entity_b.get_id() + ) if distance_a < distance_b: return -1 elif distance_a > distance_b: @@ -106,16 +138,22 @@ def _cmp_func(self, entity_a, entity_b): else: return 0 - def _convert(self, info_map): + return _cmp_func + + def _convert( + self, + info_map: dict[EntityID, "DefaultAmbulanceTargetAllocator.AmbulanceTeamInfo"], + ) -> dict[EntityID, EntityID]: result = {} for entity_id in info_map.keys(): info = info_map[entity_id] if info is not None and info._target is not None: result[entity_id] = info._target + return result class AmbulanceTeamInfo: - def __init__(self, entity_id: EntityID): - self._agent_id = entity_id - self._target = None - self._can_new_action = True - self.command_time = -1 + def __init__(self, entity_id: EntityID) -> None: + self._agent_id: EntityID = entity_id + self._target: Optional[EntityID] = None + self._can_new_action: bool = True + self.command_time: int = -1 diff --git a/adf_core_python/implement/module/complex/default_fire_target_allocator.py b/adf_core_python/implement/module/complex/default_fire_target_allocator.py index b5155f6..aaf96b3 100644 --- a/adf_core_python/implement/module/complex/default_fire_target_allocator.py +++ b/adf_core_python/implement/module/complex/default_fire_target_allocator.py @@ -1,9 +1,18 @@ from functools import cmp_to_key +from typing import Callable, Optional +from rcrs_core.entities.entity import Entity from rcrs_core.entities.fireBrigade import FireBrigadeEntity from rcrs_core.entities.human import Human from rcrs_core.worldmodel.entityID import EntityID +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.module.complex.fire_target_allocator import ( FireTargetAllocator, ) @@ -12,42 +21,44 @@ class DefaultFireTargetAllocator(FireTargetAllocator): def __init__( self, - agent_info, - world_info, - scenario_info, - module_manager, - develop_data, - ): + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - self._priority_humans = set() - self._target_humans = set() - self._fire_brigade_info_map = {} + self._priority_humans: set[EntityID] = set() + self._target_humans: set[EntityID] = set() + self._fire_brigade_info_map: dict[ + EntityID, DefaultFireTargetAllocator.FireBrigadeInfo + ] = {} - def resume(self, precompute_data): + def resume(self, precompute_data: PrecomputeData) -> FireTargetAllocator: super().resume(precompute_data) if self.get_count_resume() >= 2: return self - for entity_id in self._world_info.get_entity_ids_of_type(FireBrigadeEntity): + for entity_id in self._world_info.get_entity_ids_of_types([FireBrigadeEntity]): self._fire_brigade_info_map[entity_id] = self.FireBrigadeInfo(entity_id) return self - def prepare(self): + def prepare(self) -> FireTargetAllocator: super().prepare() if self.get_count_prepare() >= 2: return self - for entity_id in self._world_info.get_entity_ids_of_type(FireBrigadeEntity): + for entity_id in self._world_info.get_entity_ids_of_types([FireBrigadeEntity]): self._fire_brigade_info_map[entity_id] = self.FireBrigadeInfo(entity_id) return self - def update_info(self, message_manager): + def update_info(self, message_manager: MessageManager) -> FireTargetAllocator: super().update_info(message_manager) # TODO: implement after message_manager is implemented return self - def calculate(self): - agents = self.get_action_agents(self._fire_brigade_info_map) + def calculate(self) -> FireTargetAllocator: + agents = self._get_action_agents(self._fire_brigade_info_map) removes = [] current_time = self._agent_info.get_time() @@ -92,13 +103,29 @@ def calculate(self): return self - def get_result(self): + def get_result(self) -> dict[EntityID, EntityID]: return self._convert(self._fire_brigade_info_map) - def _compare_by_distance(self, target_entity): - def _cmp_func(self, entity_a, entity_b): - distance_a = self._world_info.get_distance(target_entity, entity_a) - distance_b = self._world_info.get_distance(target_entity, entity_b) + def _get_action_agents( + self, info_map: dict[EntityID, "DefaultFireTargetAllocator.FireBrigadeInfo"] + ) -> list[FireBrigadeEntity]: + result = [] + for entity in self._world_info.get_entities_of_types([FireBrigadeEntity]): + info = info_map[entity.get_id()] + if info is not None and info._can_new_action: + result.append(entity) + return result + + def _compare_by_distance( + self, target_entity: Entity + ) -> Callable[[Entity, Entity], int]: + def _cmp_func(entity_a: Entity, entity_b: Entity) -> int: + distance_a = self._world_info.get_distance( + target_entity.get_id(), entity_a.get_id() + ) + distance_b = self._world_info.get_distance( + target_entity.get_id(), entity_b.get_id() + ) if distance_a < distance_b: return -1 elif distance_a > distance_b: @@ -106,16 +133,21 @@ def _cmp_func(self, entity_a, entity_b): else: return 0 - def _convert(self, info_map): + return _cmp_func + + def _convert( + self, info_map: dict[EntityID, "DefaultFireTargetAllocator.FireBrigadeInfo"] + ) -> dict[EntityID, EntityID]: result = {} for entity_id in info_map.keys(): info = info_map[entity_id] if info is not None and info._target is not None: result[entity_id] = info._target + return result class FireBrigadeInfo: - def __init__(self, entity_id: EntityID): - self._agent_id = entity_id - self._target = None - self._can_new_action = True - self.command_time = -1 + def __init__(self, entity_id: EntityID) -> None: + self._agent_id: EntityID = entity_id + self._target: Optional[EntityID] = None + self._can_new_action: bool = True + self.command_time: int = -1 diff --git a/adf_core_python/implement/module/complex/default_police_target_allocator.py b/adf_core_python/implement/module/complex/default_police_target_allocator.py index 08c4ae5..acd2c4b 100644 --- a/adf_core_python/implement/module/complex/default_police_target_allocator.py +++ b/adf_core_python/implement/module/complex/default_police_target_allocator.py @@ -1,12 +1,21 @@ from functools import cmp_to_key +from typing import Callable, Optional, cast -from rcrs_core.entities.policeForce import PoliceForceEntity -from rcrs_core.worldmodel.entityID import EntityID -from rcrs_core.entities.refuge import Refuge from rcrs_core.entities.building import Building +from rcrs_core.entities.entity import Entity from rcrs_core.entities.gassStation import GasStation +from rcrs_core.entities.policeForce import PoliceForceEntity +from rcrs_core.entities.refuge import Refuge from rcrs_core.entities.road import Road +from rcrs_core.worldmodel.entityID import EntityID +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.module.complex.police_target_allocator import ( PoliceTargetAllocator, ) @@ -15,76 +24,78 @@ class DefaultPoliceTargetAllocator(PoliceTargetAllocator): def __init__( self, - agent_info, - world_info, - scenario_info, - module_manager, - develop_data, - ): + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - self._priority_areas = set() - self._target_areas = set() - self._agent_info_map = {} + self._priority_areas: set[EntityID] = set() + self._target_areas: set[EntityID] = set() + self._agent_info_map: dict[ + EntityID, DefaultPoliceTargetAllocator.PoliceForceInfo + ] = {} - def resume(self, precompute_data): + def resume(self, precompute_data: PrecomputeData) -> PoliceTargetAllocator: super().resume(precompute_data) if self.get_count_resume() >= 2: return self - for entity_id in self._world_info.get_entity_ids_of_type(PoliceForceEntity): + for entity_id in self._world_info.get_entity_ids_of_types([PoliceForceEntity]): self._agent_info_map[entity_id] = self.PoliceForceInfo(entity_id) - for entity in ( - self._world_info.get_entities_of_type(Refuge) - + self._world_info.get_entities_of_type(Building) - + self._world_info.get_entities_of_type(GasStation) + for entity in self._world_info.get_entities_of_types( + [Refuge, Building, GasStation] ): - for entity_id in entity.get_neighbours(): + building: Building = cast(Building, entity) + for entity_id in building.get_neighbours(): neighbour = self._world_info.get_entity(entity_id) if isinstance(neighbour, Road): self._target_areas.add(entity_id) - for entity in self._world_info.get_entities_of_type(Refuge): - for entity_id in entity.get_neighbours(): + for entity in self._world_info.get_entities_of_types([Refuge]): + refuge: Refuge = cast(Refuge, entity) + for entity_id in refuge.get_neighbours(): neighbour = self._world_info.get_entity(entity_id) if isinstance(neighbour, Road): self._priority_areas.add(entity_id) return self - def prepare(self): + def prepare(self) -> PoliceTargetAllocator: super().prepare() if self.get_count_prepare() >= 2: return self - for entity_id in self._world_info.get_entity_ids_of_type(PoliceForceEntity): + for entity_id in self._world_info.get_entity_ids_of_types([PoliceForceEntity]): self._agent_info_map[entity_id] = self.PoliceForceInfo(entity_id) - for entity in ( - self._world_info.get_entities_of_type(Refuge) - + self._world_info.get_entities_of_type(Building) - + self._world_info.get_entities_of_type(GasStation) + for entity in self._world_info.get_entities_of_types( + [Refuge, Building, GasStation] ): - for entity_id in entity.get_neighbours(): + building: Building = cast(Building, entity) + for entity_id in building.get_neighbours(): neighbour = self._world_info.get_entity(entity_id) if isinstance(neighbour, Road): self._target_areas.add(entity_id) - for entity in self._world_info.get_entities_of_type(Refuge): - for entity_id in entity.get_neighbours(): + for entity in self._world_info.get_entities_of_types([Refuge]): + refuge: Refuge = cast(Refuge, entity) + for entity_id in refuge.get_neighbours(): neighbour = self._world_info.get_entity(entity_id) if isinstance(neighbour, Road): self._priority_areas.add(entity_id) return self - def update_info(self, message_manager): + def update_info(self, message_manager: MessageManager) -> PoliceTargetAllocator: super().update_info(message_manager) # TODO: implement after message_manager is implemented return self - def calculate(self): - agents = self.get_action_agents(self._agent_info_map) + def calculate(self) -> PoliceTargetAllocator: + agents = self._get_action_agents(self._agent_info_map) removes = [] current_time = self._agent_info.get_time() @@ -127,13 +138,29 @@ def calculate(self): return self - def get_result(self): + def get_result(self) -> dict[EntityID, EntityID]: return self._convert(self._agent_info_map) - def _compare_by_distance(self, target_entity): - def _cmp_func(self, entity_a, entity_b): - distance_a = self._world_info.get_distance(target_entity, entity_a) - distance_b = self._world_info.get_distance(target_entity, entity_b) + def _get_action_agents( + self, info_map: dict[EntityID, "DefaultPoliceTargetAllocator.PoliceForceInfo"] + ) -> list[PoliceForceEntity]: + result = [] + for entity in self._world_info.get_entities_of_types([PoliceForceEntity]): + info = info_map[entity.get_id()] + if info is not None and info._can_new_action: + result.append(entity) + return result + + def _compare_by_distance( + self, target_entity: Entity + ) -> Callable[[Entity, Entity], int]: + def _cmp_func(entity_a: Entity, entity_b: Entity) -> int: + distance_a = self._world_info.get_distance( + target_entity.get_id(), entity_a.get_id() + ) + distance_b = self._world_info.get_distance( + target_entity.get_id(), entity_b.get_id() + ) if distance_a < distance_b: return -1 elif distance_a > distance_b: @@ -141,16 +168,21 @@ def _cmp_func(self, entity_a, entity_b): else: return 0 - def _convert(self, info_map): - result = {} + return _cmp_func + + def _convert( + self, info_map: dict[EntityID, "DefaultPoliceTargetAllocator.PoliceForceInfo"] + ) -> dict[EntityID, EntityID]: + result: dict[EntityID, EntityID] = {} for entity_id in info_map.keys(): info = info_map[entity_id] if info is not None and info._target is not None: result[entity_id] = info._target + return result class PoliceForceInfo: - def __init__(self, entity_id: EntityID): - self._agent_id = entity_id - self._target = None - self._can_new_action = True - self.command_time = -1 + def __init__(self, entity_id: EntityID) -> None: + self._agent_id: EntityID = entity_id + self._target: Optional[EntityID] = None + self._can_new_action: bool = True + self.command_time: int = -1 diff --git a/adf_core_python/implement/module/complex/default_road_detector.py b/adf_core_python/implement/module/complex/default_road_detector.py index 3457b72..339cc35 100644 --- a/adf_core_python/implement/module/complex/default_road_detector.py +++ b/adf_core_python/implement/module/complex/default_road_detector.py @@ -1,28 +1,25 @@ from __future__ import annotations -from typing import TYPE_CHECKING, cast +from typing import Optional, cast -from rcrs_core.entities.refuge import Refuge from rcrs_core.entities.building import Building from rcrs_core.entities.gassStation import GasStation +from rcrs_core.entities.refuge import Refuge from rcrs_core.entities.road import Road from rcrs_core.worldmodel.entityID import EntityID from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.module.algorithm.path_planning import ( + PathPlanning, +) from adf_core_python.core.component.module.complex.road_detector import RoadDetector -if TYPE_CHECKING: - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.component.module.algorithm.path_planning import ( - PathPlanning, - ) - from adf_core_python.core.component.module.complex.road_detector import RoadDetector - class DefaultRoadDetector(RoadDetector): def __init__( @@ -58,11 +55,9 @@ def resume(self, precompute_data: PrecomputeData) -> RoadDetector: if self.get_count_resume() >= 2: return self - self._target_areas = set() - entities = ( - self._world_info.get_entities_of_type(Refuge) - + self._world_info.get_entities_of_type(Building) - + self._world_info.get_entities_of_type(GasStation) + self._target_areas: set[EntityID] = set() + entities = self._world_info.get_entities_of_types( + [Refuge, Building, GasStation] ) for entity in entities: if not isinstance(entity, Building): @@ -73,10 +68,11 @@ def resume(self, precompute_data: PrecomputeData) -> RoadDetector: self._target_areas.add(entity_id) self._priority_roads = set() - for entity in self._world_info.get_entities_of_type(Refuge): + for entity in self._world_info.get_entities_of_types([Refuge]): if not isinstance(entity, Building): continue for entity_id in entity.get_neighbours(): + neighbour = self._world_info.get_entity(entity_id) if isinstance(neighbour, Road): self._priority_roads.add(entity_id) @@ -88,10 +84,8 @@ def prepare(self) -> RoadDetector: return self self._target_areas = set() - entities = ( - self._world_info.get_entities_of_type(Refuge) - + self._world_info.get_entities_of_type(Building) - + self._world_info.get_entities_of_type(GasStation) + entities = self._world_info.get_entities_of_types( + [Refuge, Building, GasStation] ) for entity in entities: building: Building = cast(Building, entity) @@ -101,9 +95,10 @@ def prepare(self) -> RoadDetector: self._target_areas.add(entity_id) self._priority_roads = set() - for entity in self._world_info.get_entities_of_type(Refuge): + for entity in self._world_info.get_entities_of_types([Refuge]): refuge: Refuge = cast(Refuge, entity) for entity_id in refuge.get_neighbours(): + neighbour = self._world_info.get_entity(entity_id) if isinstance(neighbour, Road): self._priority_roads.add(entity_id) @@ -140,13 +135,26 @@ def calc(self) -> RoadDetector: self._priority_roads = self._priority_roads - set(remove_list) if len(self._priority_roads) > 0: - self._path_planning.set_from(position_entity_id) - self._path_planning.set_destination(list(self._target_areas)) - path: list[EntityID] = self._path_planning.calculate().get_result() + _nearest_target_area = self._agent_info.get_position_entity_id() + _nearest_distance = float("inf") + for target_area in self._target_areas: + if ( + self._world_info.get_distance( + self._agent_info.get_position_entity_id(), target_area + ) + < _nearest_distance + ): + _nearest_target_area = target_area + _nearest_distance = self._world_info.get_distance( + self._agent_info.get_position_entity_id(), target_area + ) + path: list[EntityID] = self._path_planning.get_path( + self._agent_info.get_position_entity_id(), _nearest_target_area + ) if path is not None and len(path) > 0: self._result = path[-1] return self - def get_target(self) -> EntityID: + def get_target(self) -> Optional[EntityID]: return self._result From 981b9445de13ee7713adb6f2b087bff4bdb1f55a Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 8 Oct 2024 19:20:57 +0900 Subject: [PATCH 076/249] feat: Add structlog dependency --- poetry.lock | 137 +++++++++++++++++++++++++++---------------------- pyproject.toml | 1 + 2 files changed, 78 insertions(+), 60 deletions(-) diff --git a/poetry.lock b/poetry.lock index d7a68ff..d96029f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -103,64 +103,64 @@ files = [ [[package]] name = "numpy" -version = "2.1.1" +version = "2.1.2" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.10" files = [ - {file = "numpy-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8a0e34993b510fc19b9a2ce7f31cb8e94ecf6e924a40c0c9dd4f62d0aac47d9"}, - {file = "numpy-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dd86dfaf7c900c0bbdcb8b16e2f6ddf1eb1fe39c6c8cca6e94844ed3152a8fd"}, - {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:5889dd24f03ca5a5b1e8a90a33b5a0846d8977565e4ae003a63d22ecddf6782f"}, - {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:59ca673ad11d4b84ceb385290ed0ebe60266e356641428c845b39cd9df6713ab"}, - {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ce49a34c44b6de5241f0b38b07e44c1b2dcacd9e36c30f9c2fcb1bb5135db7"}, - {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913cc1d311060b1d409e609947fa1b9753701dac96e6581b58afc36b7ee35af6"}, - {file = "numpy-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:caf5d284ddea7462c32b8d4a6b8af030b6c9fd5332afb70e7414d7fdded4bfd0"}, - {file = "numpy-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:57eb525e7c2a8fdee02d731f647146ff54ea8c973364f3b850069ffb42799647"}, - {file = "numpy-2.1.1-cp310-cp310-win32.whl", hash = "sha256:9a8e06c7a980869ea67bbf551283bbed2856915f0a792dc32dd0f9dd2fb56728"}, - {file = "numpy-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:d10c39947a2d351d6d466b4ae83dad4c37cd6c3cdd6d5d0fa797da56f710a6ae"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d07841fd284718feffe7dd17a63a2e6c78679b2d386d3e82f44f0108c905550"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b5613cfeb1adfe791e8e681128f5f49f22f3fcaa942255a6124d58ca59d9528f"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0b8cc2715a84b7c3b161f9ebbd942740aaed913584cae9cdc7f8ad5ad41943d0"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b49742cdb85f1f81e4dc1b39dcf328244f4d8d1ded95dea725b316bd2cf18c95"}, - {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8d5f8a8e3bc87334f025194c6193e408903d21ebaeb10952264943a985066ca"}, - {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d51fc141ddbe3f919e91a096ec739f49d686df8af254b2053ba21a910ae518bf"}, - {file = "numpy-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:98ce7fb5b8063cfdd86596b9c762bf2b5e35a2cdd7e967494ab78a1fa7f8b86e"}, - {file = "numpy-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:24c2ad697bd8593887b019817ddd9974a7f429c14a5469d7fad413f28340a6d2"}, - {file = "numpy-2.1.1-cp311-cp311-win32.whl", hash = "sha256:397bc5ce62d3fb73f304bec332171535c187e0643e176a6e9421a6e3eacef06d"}, - {file = "numpy-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:ae8ce252404cdd4de56dcfce8b11eac3c594a9c16c231d081fb705cf23bd4d9e"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c803b7934a7f59563db459292e6aa078bb38b7ab1446ca38dd138646a38203e"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6435c48250c12f001920f0751fe50c0348f5f240852cfddc5e2f97e007544cbe"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3269c9eb8745e8d975980b3a7411a98976824e1fdef11f0aacf76147f662b15f"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fac6e277a41163d27dfab5f4ec1f7a83fac94e170665a4a50191b545721c6521"}, - {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd8f556cdc8cfe35e70efb92463082b7f43dd7e547eb071ffc36abc0ca4699b"}, - {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b9cd92c8f8e7b313b80e93cedc12c0112088541dcedd9197b5dee3738c1201"}, - {file = "numpy-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:afd9c680df4de71cd58582b51e88a61feed4abcc7530bcd3d48483f20fc76f2a"}, - {file = "numpy-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8661c94e3aad18e1ea17a11f60f843a4933ccaf1a25a7c6a9182af70610b2313"}, - {file = "numpy-2.1.1-cp312-cp312-win32.whl", hash = "sha256:950802d17a33c07cba7fd7c3dcfa7d64705509206be1606f196d179e539111ed"}, - {file = "numpy-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:3fc5eabfc720db95d68e6646e88f8b399bfedd235994016351b1d9e062c4b270"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:046356b19d7ad1890c751b99acad5e82dc4a02232013bd9a9a712fddf8eb60f5"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6e5a9cb2be39350ae6c8f79410744e80154df658d5bea06e06e0ac5bb75480d5"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d4c57b68c8ef5e1ebf47238e99bf27657511ec3f071c465f6b1bccbef12d4136"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:8ae0fd135e0b157365ac7cc31fff27f07a5572bdfc38f9c2d43b2aff416cc8b0"}, - {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981707f6b31b59c0c24bcda52e5605f9701cb46da4b86c2e8023656ad3e833cb"}, - {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ca4b53e1e0b279142113b8c5eb7d7a877e967c306edc34f3b58e9be12fda8df"}, - {file = "numpy-2.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e097507396c0be4e547ff15b13dc3866f45f3680f789c1a1301b07dadd3fbc78"}, - {file = "numpy-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7506387e191fe8cdb267f912469a3cccc538ab108471291636a96a54e599556"}, - {file = "numpy-2.1.1-cp313-cp313-win32.whl", hash = "sha256:251105b7c42abe40e3a689881e1793370cc9724ad50d64b30b358bbb3a97553b"}, - {file = "numpy-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:f212d4f46b67ff604d11fff7cc62d36b3e8714edf68e44e9760e19be38c03eb0"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:920b0911bb2e4414c50e55bd658baeb78281a47feeb064ab40c2b66ecba85553"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bab7c09454460a487e631ffc0c42057e3d8f2a9ddccd1e60c7bb8ed774992480"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:cea427d1350f3fd0d2818ce7350095c1a2ee33e30961d2f0fef48576ddbbe90f"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:e30356d530528a42eeba51420ae8bf6c6c09559051887196599d96ee5f536468"}, - {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8dfa9e94fc127c40979c3eacbae1e61fda4fe71d84869cc129e2721973231ef"}, - {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910b47a6d0635ec1bd53b88f86120a52bf56dcc27b51f18c7b4a2e2224c29f0f"}, - {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:13cc11c00000848702322af4de0147ced365c81d66053a67c2e962a485b3717c"}, - {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53e27293b3a2b661c03f79aa51c3987492bd4641ef933e366e0f9f6c9bf257ec"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7be6a07520b88214ea85d8ac8b7d6d8a1839b0b5cb87412ac9f49fa934eb15d5"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:52ac2e48f5ad847cd43c4755520a2317f3380213493b9d8a4c5e37f3b87df504"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a95ca3560a6058d6ea91d4629a83a897ee27c00630aed9d933dff191f170cd"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:99f4a9ee60eed1385a86e82288971a51e71df052ed0b2900ed30bc840c0f2e39"}, - {file = "numpy-2.1.1.tar.gz", hash = "sha256:d0cf7d55b1051387807405b3898efafa862997b4cba8aa5dbe657be794afeafd"}, + {file = "numpy-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30d53720b726ec36a7f88dc873f0eec8447fbc93d93a8f079dfac2629598d6ee"}, + {file = "numpy-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d3ca0a72dd8846eb6f7dfe8f19088060fcb76931ed592d29128e0219652884"}, + {file = "numpy-2.1.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:fc44e3c68ff00fd991b59092a54350e6e4911152682b4782f68070985aa9e648"}, + {file = "numpy-2.1.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7c1c60328bd964b53f8b835df69ae8198659e2b9302ff9ebb7de4e5a5994db3d"}, + {file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdb606a7478f9ad91c6283e238544451e3a95f30fb5467fbf715964341a8a86"}, + {file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d666cb72687559689e9906197e3bec7b736764df6a2e58ee265e360663e9baf7"}, + {file = "numpy-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6eef7a2dbd0abfb0d9eaf78b73017dbfd0b54051102ff4e6a7b2980d5ac1a03"}, + {file = "numpy-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:12edb90831ff481f7ef5f6bc6431a9d74dc0e5ff401559a71e5e4611d4f2d466"}, + {file = "numpy-2.1.2-cp310-cp310-win32.whl", hash = "sha256:a65acfdb9c6ebb8368490dbafe83c03c7e277b37e6857f0caeadbbc56e12f4fb"}, + {file = "numpy-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:860ec6e63e2c5c2ee5e9121808145c7bf86c96cca9ad396c0bd3e0f2798ccbe2"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:13602b3174432a35b16c4cfb5de9a12d229727c3dd47a6ce35111f2ebdf66ff4"}, + {file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebec5fd716c5a5b3d8dfcc439be82a8407b7b24b230d0ad28a81b61c2f4659a"}, + {file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2b49c3c0804e8ecb05d59af8386ec2f74877f7ca8fd9c1e00be2672e4d399b1"}, + {file = "numpy-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cbba4b30bf31ddbe97f1c7205ef976909a93a66bb1583e983adbd155ba72ac2"}, + {file = "numpy-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8e00ea6fc82e8a804433d3e9cedaa1051a1422cb6e443011590c14d2dea59146"}, + {file = "numpy-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5006b13a06e0b38d561fab5ccc37581f23c9511879be7693bd33c7cd15ca227c"}, + {file = "numpy-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:f1eb068ead09f4994dec71c24b2844f1e4e4e013b9629f812f292f04bd1510d9"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426"}, + {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0"}, + {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df"}, + {file = "numpy-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366"}, + {file = "numpy-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142"}, + {file = "numpy-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550"}, + {file = "numpy-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3"}, + {file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8"}, + {file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a"}, + {file = "numpy-2.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98"}, + {file = "numpy-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe"}, + {file = "numpy-2.1.2-cp313-cp313-win32.whl", hash = "sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a"}, + {file = "numpy-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6"}, + {file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8"}, + {file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35"}, + {file = "numpy-2.1.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62"}, + {file = "numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bdd407c40483463898b84490770199d5714dcc9dd9b792f6c6caccc523c00952"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:da65fb46d4cbb75cb417cddf6ba5e7582eb7bb0b47db4b99c9fe5787ce5d91f5"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c193d0b0238638e6fc5f10f1b074a6993cb13b0b431f64079a509d63d3aa8b7"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a7d80b2e904faa63068ead63107189164ca443b42dd1930299e0d1cb041cec2e"}, + {file = "numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c"}, ] [[package]] @@ -347,7 +347,7 @@ rtree = "*" type = "git" url = "https://github.com/adf-python/rcrs-core-python" reference = "HEAD" -resolved_reference = "bd42a563fee818496ce4c020a7ec82c4b5c67042" +resolved_reference = "3ceb2db8ed14842db1394c8c92900720eb334600" [[package]] name = "rtree" @@ -489,6 +489,23 @@ dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodest doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +[[package]] +name = "structlog" +version = "24.4.0" +description = "Structured Logging for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "structlog-24.4.0-py3-none-any.whl", hash = "sha256:597f61e80a91cc0749a9fd2a098ed76715a1c8a01f73e336b746504d1aad7610"}, + {file = "structlog-24.4.0.tar.gz", hash = "sha256:b27bfecede327a6d2da5fbc96bd859f114ecc398a6389d664f62085ee7ae6fc4"}, +] + +[package.extras] +dev = ["freezegun (>=0.2.8)", "mypy (>=1.4)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "rich", "simplejson", "twisted"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "sphinxext-opengraph", "twisted"] +tests = ["freezegun (>=0.2.8)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "simplejson"] +typing = ["mypy (>=1.4)", "rich", "twisted"] + [[package]] name = "taskipy" version = "1.13.0" @@ -519,13 +536,13 @@ files = [ [[package]] name = "tomli" -version = "2.0.1" +version = "2.0.2" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] [[package]] @@ -564,4 +581,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "88cd8709372c0c07eff53e51b00b3740518bb32125f72d9a2e7dd0717067d45a" +content-hash = "93caa36e663f9d18358e1aefabd4d5b3e71ad1864663ad2a5f956b65c6cf77be" diff --git a/pyproject.toml b/pyproject.toml index 908a0b6..04ae6e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ pyyaml = "^6.0.2" pytest = "^8.3.2" types-pyyaml = "^6.0.12.20240808" scikit-learn = "^1.5.2" +structlog = "^24.4.0" [tool.poetry.group.dev.dependencies] From 682d53aae578ea4f1817b92c35ba25b732ff778e Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 8 Oct 2024 19:23:03 +0900 Subject: [PATCH 077/249] feat: Add structlog dependency --- adf_core_python/core/agent/platoon/platoon.py | 11 ++- .../core/component/tactics/tactics_agent.py | 6 +- .../core/launcher/agent_launcher.py | 11 +-- .../connect/connector_ambulance_team.py | 4 +- adf_core_python/core/logger/logger.py | 80 +++++++++++++++++++ .../tactics/default_tactics_ambulance_team.py | 11 +++ adf_core_python/main.py | 28 ++++--- 7 files changed, 128 insertions(+), 23 deletions(-) create mode 100644 adf_core_python/core/logger/logger.py diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py index 60d4016..1d2e341 100644 --- a/adf_core_python/core/agent/platoon/platoon.py +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -1,5 +1,3 @@ -from logging import Logger, getLogger - from rcrs_core.agents.agent import Agent from rcrs_core.commands.Command import Command from rcrs_core.config.config import Config as RCRSConfig @@ -16,6 +14,7 @@ from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent from adf_core_python.core.config.config import Config +from adf_core_python.core.logger.logger import get_agent_logger class Platoon(Agent): @@ -41,7 +40,10 @@ def __init__( self._develop_data = develop_data def post_connect(self) -> None: - self._logger: Logger = getLogger(__name__) + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) self._agent_info: AgentInfo = AgentInfo(self, self.world_model) self._world_info: WorldInfo = WorldInfo(self.world_model) self._precompute_data: PrecomputeData = PrecomputeData(self._data_storage_name) @@ -106,6 +108,9 @@ def post_connect(self) -> None: def think(self, time: int, change_set: ChangeSet, hear: list[Command]) -> None: self._agent_info.set_change_set(change_set) self._world_info.set_change_set(change_set) + self._agent_info.set_time(time) + self._agent_info.set_heard_commands(hear) + action: Action = self._tactics_agent.think( self._agent_info, self._world_info, diff --git a/adf_core_python/core/component/tactics/tactics_agent.py b/adf_core_python/core/component/tactics/tactics_agent.py index 100c4c7..fab20dc 100644 --- a/adf_core_python/core/component/tactics/tactics_agent.py +++ b/adf_core_python/core/component/tactics/tactics_agent.py @@ -3,6 +3,8 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Any, Optional +from adf_core_python.core.logger.logger import get_agent_logger + if TYPE_CHECKING: from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.communication.message_manager import MessageManager @@ -34,7 +36,9 @@ def initialize( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", agent_info + ) @abstractmethod def precompute( diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index c2c944f..3b66c1b 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -1,6 +1,5 @@ import importlib import threading -from logging import Logger, getLogger from rcrs_core.connection.componentLauncher import ComponentLauncher @@ -15,6 +14,7 @@ from adf_core_python.core.launcher.connect.connector_ambulance_team import ( ConnectorAmbulanceTeam, ) +from adf_core_python.core.logger.logger import get_logger # from adf_core_python.core.launcher.connect.connector_fire_brigade import ( # ConnectorFireBrigade, @@ -33,7 +33,7 @@ class AgentLauncher: def __init__(self, config: Config): self.config = config - self.logger: Logger = getLogger(__name__) + self.logger = get_logger(__name__) self.connectors: list[Connector] = [] self.thread_list: list[threading.Thread] = [] @@ -67,14 +67,9 @@ def launch(self) -> None: for connector in self.connectors: threads = connector.connect(component_launcher, self.config, self.loader) for thread in threads: + thread.daemon = True thread.start() self.thread_list.extend(threads) for thread in self.thread_list: thread.join() - - connected_agent_count = 0 - for connector in self.connectors: - connected_agent_count += connector.get_connected_agent_count() - - self.logger.info(f"Connected agent count: {connected_agent_count}") diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index 1c09775..1d457b3 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -1,5 +1,4 @@ import threading -from logging import Logger, getLogger from rcrs_core.connection.componentLauncher import ComponentLauncher @@ -13,12 +12,13 @@ from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.connector import Connector +from adf_core_python.core.logger.logger import get_logger class ConnectorAmbulanceTeam(Connector): def __init__(self) -> None: super().__init__() - self.logger: Logger = getLogger(__name__) + self.logger = get_logger(__name__) def connect( self, diff --git a/adf_core_python/core/logger/logger.py b/adf_core_python/core/logger/logger.py new file mode 100644 index 0000000..7e5ff40 --- /dev/null +++ b/adf_core_python/core/logger/logger.py @@ -0,0 +1,80 @@ +import logging +import sys + +import structlog +from structlog.dev import ConsoleRenderer +from structlog.processors import JSONRenderer + +from adf_core_python.core.agent.info.agent_info import AgentInfo + + +def get_logger(name: str) -> structlog.BoundLogger: + """ + Get a logger with the given name. + For kernel logging, use this function to get a logger. + + Parameters + ---------- + name : str + The name of the logger. + + Returns + ------- + structlog.BoundLogger + The logger with the given name. + """ + return structlog.get_logger(name) + + +def get_agent_logger(name: str, agent_info: AgentInfo) -> structlog.BoundLogger: + """ + Get a logger with the given name and agent information. + For agent logging, use this function to get a logger. + + Parameters + ---------- + name : str + The name of the logger. + agent_info : AgentInfo + The agent information. + + Returns + ------- + structlog.BoundLogger + The logger with the given name and agent information. + """ + return structlog.get_logger(name).bind( + agent_id=str(agent_info.get_entity_id()), + agent_type=str(agent_info.get_myself().get_urn().name), + ) + + +def configure_logger(): + structlog.configure( + processors=[ + structlog.stdlib.add_log_level, + structlog.stdlib.add_logger_name, + structlog.stdlib.PositionalArgumentsFormatter(), + structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M.%S", utc=False), + structlog.processors.StackInfoRenderer(), + structlog.processors.UnicodeDecoder(), + structlog.stdlib.ProcessorFormatter.wrap_for_formatter, + ], + logger_factory=structlog.stdlib.LoggerFactory(), + wrapper_class=structlog.stdlib.BoundLogger, + cache_logger_on_first_use=True, + ) + handler_stdout = logging.StreamHandler(sys.stdout) + handler_stdout.setFormatter( + structlog.stdlib.ProcessorFormatter(processor=ConsoleRenderer()) + ) + + handler_file = logging.FileHandler("agent.log") + handler_file.setFormatter( + structlog.stdlib.ProcessorFormatter(processor=JSONRenderer()) + ) + + root_logger = logging.getLogger() + root_logger.addHandler(handler_stdout) + root_logger.addHandler(handler_file) + root_logger.setLevel(logging.DEBUG) diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 8666e14..0d45df8 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -30,6 +30,15 @@ def initialize( develop_data: DevelopData, ) -> None: # world_info.index_class() + super().initialize( + agent_info, + world_info, + scenario_info, + module_manager, + precompute_data, + message_manager, + develop_data, + ) match scenario_info.get_mode(): case Mode.NON_PRECOMPUTE: self._search: Search = cast( @@ -118,6 +127,7 @@ def think( .get_action() ) if action is not None: + self._logger.debug(f"action: {action}", time=agent_info.get_time()) return action target_entity_id = self._search.calculate().get_target_entity_id() @@ -128,6 +138,7 @@ def think( .get_action() ) if action is not None: + self._logger.debug(f"action: {action}", time=agent_info.get_time()) return action return ActionRest() diff --git a/adf_core_python/main.py b/adf_core_python/main.py index 5c8aedf..8ba028c 100644 --- a/adf_core_python/main.py +++ b/adf_core_python/main.py @@ -1,13 +1,14 @@ import argparse -import logging from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.agent_launcher import AgentLauncher from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.logger.logger import configure_logger, get_logger class Main: def __init__(self) -> None: + self.logger = get_logger(__name__) parser = argparse.ArgumentParser(description="Agent Launcher") parser.add_argument( @@ -56,10 +57,11 @@ def __init__(self) -> None: metavar="", ) parser.add_argument( - "--verbose", type=bool, default=False, help="verbose flag", metavar="" + "--debug", type=bool, default=False, help="debug flag", metavar="" ) args = parser.parse_args() - print(args) + self.logger.info(f"Arguments: {args}") + self.config = Config() self.config.set_value(ConfigKey.KEY_KERNEL_HOST, args.host) self.config.set_value(ConfigKey.KEY_KERNEL_PORT, args.port) @@ -67,21 +69,29 @@ def __init__(self) -> None: self.config.set_value(ConfigKey.KEY_FIRE_STATION_COUNT, args.firebrigade) self.config.set_value(ConfigKey.KEY_POLICE_OFFICE_COUNT, args.policeforce) self.config.set_value(ConfigKey.KEY_PRECOMPUTE, args.precompute) - self.config.set_value(ConfigKey.KEY_DEBUG_FLAG, args.verbose) + self.config.set_value(ConfigKey.KEY_DEBUG_FLAG, args.debug) + self.logger.info(f"Config: {self.config}") def launch(self) -> None: agent_launcher: AgentLauncher = AgentLauncher( self.config, ) agent_launcher.init_connector() - agent_launcher.launch() + + try: + agent_launcher.launch() + except KeyboardInterrupt: + self.logger.info("Agent launcher interrupted") + except Exception as e: + self.logger.exception("Agent launcher failed", exc_info=e) + raise e + self.logger.info("Agent launcher finished") if __name__ == "__main__": - logging.basicConfig( - level=logging.DEBUG, - format="%(threadName)s[%(levelname)s][%(name)s]: %(message)s", - ) + configure_logger() + logger = get_logger(__name__) + logger.info("Starting the agent launcher") main = Main() main.launch() From 8af12d026837c20cf3cf88065ea87150132a423f Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 8 Oct 2024 23:43:32 +0900 Subject: [PATCH 078/249] refactor: Reorganize Platoon class initialization and logger setup --- adf_core_python/core/agent/platoon/platoon.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py index 1d2e341..1b7748c 100644 --- a/adf_core_python/core/agent/platoon/platoon.py +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -40,13 +40,14 @@ def __init__( self._develop_data = develop_data def post_connect(self) -> None: + self._agent_info: AgentInfo = AgentInfo(self, self.world_model) + self._world_info: WorldInfo = WorldInfo(self.world_model) + self._precompute_data: PrecomputeData = PrecomputeData(self._data_storage_name) + self._logger = get_agent_logger( f"{self.__class__.__module__}.{self.__class__.__qualname__}", self._agent_info, ) - self._agent_info: AgentInfo = AgentInfo(self, self.world_model) - self._world_info: WorldInfo = WorldInfo(self.world_model) - self._precompute_data: PrecomputeData = PrecomputeData(self._data_storage_name) if self._is_precompute: self._mode = Mode.PRECOMPUTATION From 988d7a4c3e61d7960d9e659112e8e9e40bc1cfd1 Mon Sep 17 00:00:00 2001 From: harrki Date: Wed, 9 Oct 2024 10:45:35 +0900 Subject: [PATCH 079/249] fix: Fixed some files from suggestions --- .gitignore | 4 ++-- .../implement/module/complex/default_road_detector.py | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index dd7a3b1..16e8308 100644 --- a/.gitignore +++ b/.gitignore @@ -159,7 +159,7 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ # ruff .ruff_cache/ @@ -171,4 +171,4 @@ cython_debug/ .idea/ # MacOS -.DS_Store \ No newline at end of file +.DS_Store diff --git a/adf_core_python/implement/module/complex/default_road_detector.py b/adf_core_python/implement/module/complex/default_road_detector.py index 339cc35..d6089a2 100644 --- a/adf_core_python/implement/module/complex/default_road_detector.py +++ b/adf_core_python/implement/module/complex/default_road_detector.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Optional, cast from rcrs_core.entities.building import Building From c000530ef327d679ff2c9a15d5029392b3a28265 Mon Sep 17 00:00:00 2001 From: harrki Date: Wed, 9 Oct 2024 10:58:41 +0900 Subject: [PATCH 080/249] fix: Fixed gitignore --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index 16e8308..675f361 100644 --- a/.gitignore +++ b/.gitignore @@ -167,8 +167,5 @@ cython_debug/ # vscode .vscode/ -# pycharm -.idea/ - # MacOS .DS_Store From bae36db705c39c072b1791b5aa5aea7f9f4c7560 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 9 Oct 2024 11:22:49 +0900 Subject: [PATCH 081/249] fix: Add return type annotation to configure_logger function --- adf_core_python/core/logger/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adf_core_python/core/logger/logger.py b/adf_core_python/core/logger/logger.py index 7e5ff40..fe29e0a 100644 --- a/adf_core_python/core/logger/logger.py +++ b/adf_core_python/core/logger/logger.py @@ -49,7 +49,7 @@ def get_agent_logger(name: str, agent_info: AgentInfo) -> structlog.BoundLogger: ) -def configure_logger(): +def configure_logger() -> None: structlog.configure( processors=[ structlog.stdlib.add_log_level, From 40501b1054291f3cde31b274e78338afeb903834 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 9 Oct 2024 01:41:46 +0900 Subject: [PATCH 082/249] refactor: Update AgentInfo class to use Civilian instead of Human for entity comparison --- adf_core_python/core/agent/info/agent_info.py | 5 +- .../default_extend_action_transport.py | 8 ++- .../module/complex/default_human_detector.py | 70 ++++++++++++++----- .../module/complex/default_search.py | 12 ++-- .../tactics/default_tactics_ambulance_team.py | 7 ++ 5 files changed, 76 insertions(+), 26 deletions(-) diff --git a/adf_core_python/core/agent/info/agent_info.py b/adf_core_python/core/agent/info/agent_info.py index 030de23..79e8cf2 100644 --- a/adf_core_python/core/agent/info/agent_info.py +++ b/adf_core_python/core/agent/info/agent_info.py @@ -2,6 +2,7 @@ from typing import Any from rcrs_core.agents.agent import Agent +from rcrs_core.entities.civilian import Civilian from rcrs_core.entities.entity import Entity from rcrs_core.entities.human import Human from rcrs_core.worldmodel.changeSet import ChangeSet @@ -137,8 +138,8 @@ def some_one_on_board(self) -> Human | None: """ entity_id: EntityID = self.get_entity_id() for entity in self._world_model.get_entities(): - if isinstance(entity, Human): - if entity.get_position_property() == entity_id: + if isinstance(entity, Civilian): + if entity.get_position() == entity_id: return entity return None diff --git a/adf_core_python/implement/extend_action/default_extend_action_transport.py b/adf_core_python/implement/extend_action/default_extend_action_transport.py index 338fb66..cad0aeb 100644 --- a/adf_core_python/implement/extend_action/default_extend_action_transport.py +++ b/adf_core_python/implement/extend_action/default_extend_action_transport.py @@ -21,6 +21,7 @@ from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.extaction.ext_action import ExtAction from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning +from adf_core_python.core.logger.logger import get_agent_logger # TODO: refactor this class @@ -38,6 +39,10 @@ def __init__( ) self._target_entity_id: Optional[EntityID] = None self._threshold_to_rest: int = develop_data.get_value("threshold_to_rest", 100) + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self.agent_info, + ) match self.scenario_info.get_mode(): case Mode.NON_PRECOMPUTE: @@ -102,6 +107,7 @@ def calc(self) -> ExtAction: ) transport_human: Optional[Human] = self.agent_info.some_one_on_board() if transport_human is not None: + self._logger.debug(f"transport_human: {transport_human.get_id()}") self.result = self.calc_unload( agent, self._path_planning, transport_human, self._target_entity_id ) @@ -135,7 +141,7 @@ def calc_rescue( target_position = human.get_position() if agent_position == target_position: - if isinstance(human, Civilian) and ((human.get_buriedness() or 0) > 0): + if isinstance(human, Civilian) and ((human.get_buriedness() or 0) == 0): return ActionLoad(human.get_id()) else: path = path_planning.get_path(agent_position, target_position) diff --git a/adf_core_python/implement/module/complex/default_human_detector.py b/adf_core_python/implement/module/complex/default_human_detector.py index 05e58ce..5a96b23 100644 --- a/adf_core_python/implement/module/complex/default_human_detector.py +++ b/adf_core_python/implement/module/complex/default_human_detector.py @@ -1,6 +1,7 @@ from typing import Optional, cast from rcrs_core.connection.URN import Entity as EntityURN +from rcrs_core.entities.civilian import Civilian from rcrs_core.entities.entity import Entity from rcrs_core.entities.human import Human from rcrs_core.worldmodel.entityID import EntityID @@ -12,6 +13,7 @@ from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.component.module.algorithm.clustering import Clustering from adf_core_python.core.component.module.complex.human_detector import HumanDetector +from adf_core_python.core.logger.logger import get_agent_logger class DefaultHumanDetector(HumanDetector): @@ -36,6 +38,10 @@ def __init__( self.register_sub_module(self._clustering) self._result: Optional[EntityID] = None + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) def calculate(self) -> HumanDetector: transport_human: Optional[Human] = self._agent_info.some_one_on_board() @@ -44,8 +50,11 @@ def calculate(self) -> HumanDetector: return self if self._result is not None: - if self._is_valid_human(self._result): - self._result = self._select_target() + if not self._is_valid_human(self._result): + self._result = None + + if self._result is None: + self._result = self._select_target() return self @@ -59,28 +68,54 @@ def _select_target(self) -> Optional[EntityID]: cluster_entities: list[Entity] = self._clustering.get_cluster_entities( cluster_index ) + + self._logger.debug( + f"cluster_entities: {[str(entity.get_id()) for entity in cluster_entities]}" + ) + cluster_valid_human_entities: list[Entity] = [ entity for entity in cluster_entities - if self._is_valid_human(entity.get_id()) + if self._is_valid_human(entity.get_id()) and isinstance(entity, Civilian) ] - if len(cluster_valid_human_entities) == 0: - return None + if len(cluster_valid_human_entities) != 0: + nearest_human_entity: Optional[Entity] = cluster_valid_human_entities[0] + nearest_distance: float = self._world_info.get_distance( + self._agent_info.get_entity_id(), + nearest_human_entity.get_id(), + ) + for entity in cluster_valid_human_entities: + distance: float = self._world_info.get_distance( + self._agent_info.get_entity_id(), + entity.get_id(), + ) + if distance < nearest_distance: + nearest_distance = distance + nearest_human_entity = entity + return nearest_human_entity.get_id() - nearest_human_entity: Optional[Entity] = None - nearest_distance: float = 10**10 - for entity in cluster_valid_human_entities: - distance: float = self._world_info.get_distance( + world_valid_human_entities: list[Entity] = [ + entity + for entity in self._world_info.get_entities_of_types([Civilian]) + if self._is_valid_human(entity.get_id()) + ] + if len(world_valid_human_entities) != 0: + nearest_human_entity: Optional[Entity] = world_valid_human_entities[0] + nearest_distance: float = self._world_info.get_distance( self._agent_info.get_entity_id(), - entity.get_id(), + nearest_human_entity.get_id(), ) - if distance < nearest_distance: - nearest_distance = distance - nearest_human_entity = entity + for entity in world_valid_human_entities: + distance: float = self._world_info.get_distance( + self._agent_info.get_entity_id(), + entity.get_id(), + ) + if distance < nearest_distance: + nearest_distance = distance + nearest_human_entity = entity + return nearest_human_entity.get_id() - return ( - nearest_human_entity.get_id() if nearest_human_entity is not None else None - ) + return None def _is_valid_human(self, target_entity_id: EntityID) -> bool: target: Optional[Entity] = self._world_info.get_entity(target_entity_id) @@ -94,6 +129,9 @@ def _is_valid_human(self, target_entity_id: EntityID) -> bool: buriedness: Optional[int] = target.get_buriedness() if buriedness is None or buriedness > 0: return False + damage: Optional[int] = target.get_damage() + if damage is None or damage == 0: + return False position_entity_id: Optional[EntityID] = target.get_position() if position_entity_id is None: return False diff --git a/adf_core_python/implement/module/complex/default_search.py b/adf_core_python/implement/module/complex/default_search.py index 3c35a4a..d57f6e1 100644 --- a/adf_core_python/implement/module/complex/default_search.py +++ b/adf_core_python/implement/module/complex/default_search.py @@ -1,7 +1,8 @@ from typing import Optional, cast -from rcrs_core.connection.URN import Entity as EntityURN +from rcrs_core.entities.building import Building from rcrs_core.entities.entity import Entity +from rcrs_core.entities.refuge import Refuge from rcrs_core.worldmodel.entityID import EntityID from adf_core_python.core.agent.communication.message_manager import MessageManager @@ -55,10 +56,8 @@ def update_info(self, message_manager: MessageManager) -> Search: if self.get_count_update_info() > 1: return self - searched_building_ids = self._world_info.get_change_set().get_changed_entities() - self._unsearched_building_ids = self._unsearched_building_ids.difference( - searched_building_ids - ) + searched_building_id = self._agent_info.get_position_entity_id() + self._unsearched_building_ids.discard(searched_building_id) if len(self._unsearched_building_ids) == 0: self._unsearched_building_ids = self._get_search_targets() @@ -91,8 +90,7 @@ def _get_search_targets(self) -> set[EntityID]: building_entity_ids: list[EntityID] = [ entity.get_id() for entity in cluster_entities - if entity.get_urn() == EntityURN.BUILDING - and entity.get_urn() != EntityURN.REFUGE + if isinstance(entity, Building) and not isinstance(entity, Refuge) ] return set(building_entity_ids) diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 0d45df8..ad34f68 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -120,6 +120,10 @@ def think( entity_id = agent_info.get_entity_id() # noqa: F841 target_entity_id = self._human_detector.calculate().get_target_entity_id() + self._logger.debug( + f"human detector target_entity_id: {target_entity_id}", + time=agent_info.get_time(), + ) if target_entity_id is not None: action = ( self._action_transport.set_target_entity_id(target_entity_id) @@ -131,6 +135,9 @@ def think( return action target_entity_id = self._search.calculate().get_target_entity_id() + self._logger.debug( + f"search target_entity_id: {target_entity_id}", time=agent_info.get_time() + ) if target_entity_id is not None: action = ( self._action_ext_move.set_target_entity_id(target_entity_id) From a234f0b418088e2b31b22521f3a0c30bd919eb20 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 9 Oct 2024 10:33:25 +0900 Subject: [PATCH 083/249] refactor: Update KMeansClustering to set random_state and rename unsearched_building_ids in DefaultSearch --- .../module/algorithm/k_means_clustering.py | 2 +- .../module/complex/default_human_detector.py | 4 ---- .../module/complex/default_search.py | 20 ++++++++++++++----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/adf_core_python/implement/module/algorithm/k_means_clustering.py b/adf_core_python/implement/module/algorithm/k_means_clustering.py index ee6db38..0a432cb 100644 --- a/adf_core_python/implement/module/algorithm/k_means_clustering.py +++ b/adf_core_python/implement/module/algorithm/k_means_clustering.py @@ -112,7 +112,7 @@ def prepare(self) -> Clustering: def create_cluster( self, cluster_number: int, entities: list[Entity] ) -> list[list[Entity]]: - kmeans = KMeans(n_clusters=cluster_number) + kmeans = KMeans(n_clusters=cluster_number, random_state=0) entity_positions: np.ndarray = np.array([]) for entity in entities: location1_x, location1_y = entity.get_location() diff --git a/adf_core_python/implement/module/complex/default_human_detector.py b/adf_core_python/implement/module/complex/default_human_detector.py index 5a96b23..38ddb64 100644 --- a/adf_core_python/implement/module/complex/default_human_detector.py +++ b/adf_core_python/implement/module/complex/default_human_detector.py @@ -69,10 +69,6 @@ def _select_target(self) -> Optional[EntityID]: cluster_index ) - self._logger.debug( - f"cluster_entities: {[str(entity.get_id()) for entity in cluster_entities]}" - ) - cluster_valid_human_entities: list[Entity] = [ entity for entity in cluster_entities diff --git a/adf_core_python/implement/module/complex/default_search.py b/adf_core_python/implement/module/complex/default_search.py index d57f6e1..05d7c88 100644 --- a/adf_core_python/implement/module/complex/default_search.py +++ b/adf_core_python/implement/module/complex/default_search.py @@ -14,6 +14,7 @@ from adf_core_python.core.component.module.algorithm.clustering import Clustering from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning from adf_core_python.core.component.module.complex.search import Search +from adf_core_python.core.logger.logger import get_agent_logger class DefaultSearch(Search): @@ -29,7 +30,7 @@ def __init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - self._unsearched_building_ids: set[EntityID] = set() + self._unreached_building_ids: set[EntityID] = set() self._result: Optional[EntityID] = None self._clustering: Clustering = cast( @@ -48,6 +49,11 @@ def __init__( ), ) + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + self.register_sub_module(self._clustering) self.register_sub_module(self._path_planning) @@ -56,18 +62,22 @@ def update_info(self, message_manager: MessageManager) -> Search: if self.get_count_update_info() > 1: return self + self._logger.debug( + f"unreached_building_ids: {[str(id) for id in self._unreached_building_ids]}" + ) + searched_building_id = self._agent_info.get_position_entity_id() - self._unsearched_building_ids.discard(searched_building_id) + self._unreached_building_ids.discard(searched_building_id) - if len(self._unsearched_building_ids) == 0: - self._unsearched_building_ids = self._get_search_targets() + if len(self._unreached_building_ids) == 0: + self._unreached_building_ids = self._get_search_targets() return self def calculate(self) -> Search: nearest_building_id: Optional[EntityID] = None nearest_distance: Optional[float] = None - for building_id in self._unsearched_building_ids: + for building_id in self._unreached_building_ids: distance = self._world_info.get_distance( self._agent_info.get_entity_id(), building_id ) From d97ca891ac8f535716b4dcbadb03b0cda403b15a Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 9 Oct 2024 12:21:13 +0900 Subject: [PATCH 084/249] refactor: Remove type annotations for nearest_human_entity and nearest_distance in DefaultHumanDetector --- .../module/complex/default_human_detector.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/adf_core_python/implement/module/complex/default_human_detector.py b/adf_core_python/implement/module/complex/default_human_detector.py index 38ddb64..fa55bc6 100644 --- a/adf_core_python/implement/module/complex/default_human_detector.py +++ b/adf_core_python/implement/module/complex/default_human_detector.py @@ -75,13 +75,13 @@ def _select_target(self) -> Optional[EntityID]: if self._is_valid_human(entity.get_id()) and isinstance(entity, Civilian) ] if len(cluster_valid_human_entities) != 0: - nearest_human_entity: Optional[Entity] = cluster_valid_human_entities[0] - nearest_distance: float = self._world_info.get_distance( + nearest_human_entity = cluster_valid_human_entities[0] + nearest_distance = self._world_info.get_distance( self._agent_info.get_entity_id(), nearest_human_entity.get_id(), ) for entity in cluster_valid_human_entities: - distance: float = self._world_info.get_distance( + distance = self._world_info.get_distance( self._agent_info.get_entity_id(), entity.get_id(), ) @@ -96,13 +96,13 @@ def _select_target(self) -> Optional[EntityID]: if self._is_valid_human(entity.get_id()) ] if len(world_valid_human_entities) != 0: - nearest_human_entity: Optional[Entity] = world_valid_human_entities[0] - nearest_distance: float = self._world_info.get_distance( + nearest_human_entity = world_valid_human_entities[0] + nearest_distance = self._world_info.get_distance( self._agent_info.get_entity_id(), nearest_human_entity.get_id(), ) for entity in world_valid_human_entities: - distance: float = self._world_info.get_distance( + distance = self._world_info.get_distance( self._agent_info.get_entity_id(), entity.get_id(), ) From 9b4279685e9b80adf5031b11837a222089457f45 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 9 Oct 2024 12:56:10 +0900 Subject: [PATCH 085/249] refactor: Simplify entity position handling in DefaultExtendActionMove --- .../extend_action/default_extend_action_move.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/adf_core_python/implement/extend_action/default_extend_action_move.py b/adf_core_python/implement/extend_action/default_extend_action_move.py index cd8d934..c46db5f 100644 --- a/adf_core_python/implement/extend_action/default_extend_action_move.py +++ b/adf_core_python/implement/extend_action/default_extend_action_move.py @@ -84,10 +84,11 @@ def set_target_entity_id(self, target_entity_id: EntityID) -> ExtAction: if entity is None: return self - if isinstance(entity, Blockade): - entity = self.world_info.get_entity(cast(Blockade, entity).get_position()) - elif isinstance(entity, Human): - entity = entity.get_position() + if isinstance(entity, Blockade) or isinstance(entity, Human): + position: Optional[EntityID] = entity.get_position() + if position is None: + return self + entity = self.world_info.get_entity(position) if entity is not None and isinstance(entity, Area): self._target_entity_id = entity.get_id() @@ -98,6 +99,9 @@ def calc(self) -> ExtAction: self.result = None agent: Human = cast(Human, self.agent_info.get_myself()) + if self._target_entity_id is None: + return self + path: list[EntityID] = self._path_planning.get_path( agent.get_position(), self._target_entity_id ) From bb8bf61a25260122535d96f77b0bf1b2669b17ff Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 9 Oct 2024 13:00:49 +0900 Subject: [PATCH 086/249] refactor: fix typo --- .../implement/module/algorithm/a_star_path_planning.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/adf_core_python/implement/module/algorithm/a_star_path_planning.py b/adf_core_python/implement/module/algorithm/a_star_path_planning.py index cd4b802..63a258f 100644 --- a/adf_core_python/implement/module/algorithm/a_star_path_planning.py +++ b/adf_core_python/implement/module/algorithm/a_star_path_planning.py @@ -26,14 +26,14 @@ def __init__( super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - entitites: list[Entity] = self._world_info.get_entities_of_types([Area]) + entities: list[Entity] = self._world_info.get_entities_of_types([Area]) self._graph: dict[EntityID, set[EntityID]] = {} - for entity in entitites: + for entity in entities: if isinstance(entity, Area): self._graph[entity.get_id()] = set( - neighbour - for neighbour in entity.get_neighbours() - if neighbour != EntityID(0) + neighbor + for neighbor in entity.get_neighbours() # TODO: Fix rcrs_core typo + if neighbor != EntityID(0) ) def get_path( From 26da37641e3bc105b5a2b1c121504d44908a6a24 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 9 Oct 2024 13:02:35 +0900 Subject: [PATCH 087/249] refactor: Fix variable name typo from 'neighbour' to 'neighbor' in DefaultPoliceTargetAllocator --- .../complex/default_police_target_allocator.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/adf_core_python/implement/module/complex/default_police_target_allocator.py b/adf_core_python/implement/module/complex/default_police_target_allocator.py index acd2c4b..72e65e1 100644 --- a/adf_core_python/implement/module/complex/default_police_target_allocator.py +++ b/adf_core_python/implement/module/complex/default_police_target_allocator.py @@ -51,15 +51,15 @@ def resume(self, precompute_data: PrecomputeData) -> PoliceTargetAllocator: ): building: Building = cast(Building, entity) for entity_id in building.get_neighbours(): - neighbour = self._world_info.get_entity(entity_id) - if isinstance(neighbour, Road): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): self._target_areas.add(entity_id) for entity in self._world_info.get_entities_of_types([Refuge]): refuge: Refuge = cast(Refuge, entity) for entity_id in refuge.get_neighbours(): - neighbour = self._world_info.get_entity(entity_id) - if isinstance(neighbour, Road): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): self._priority_areas.add(entity_id) return self @@ -76,15 +76,15 @@ def prepare(self) -> PoliceTargetAllocator: ): building: Building = cast(Building, entity) for entity_id in building.get_neighbours(): - neighbour = self._world_info.get_entity(entity_id) - if isinstance(neighbour, Road): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): self._target_areas.add(entity_id) for entity in self._world_info.get_entities_of_types([Refuge]): refuge: Refuge = cast(Refuge, entity) for entity_id in refuge.get_neighbours(): - neighbour = self._world_info.get_entity(entity_id) - if isinstance(neighbour, Road): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): self._priority_areas.add(entity_id) return self From 267f3bd0af1a68aa7d248cdd75a9ed407e155a01 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 9 Oct 2024 13:03:09 +0900 Subject: [PATCH 088/249] refactor: Fix variable name typo from 'neighbour' to 'neighbor' in DefaultRoadDetector --- .../module/complex/default_road_detector.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/adf_core_python/implement/module/complex/default_road_detector.py b/adf_core_python/implement/module/complex/default_road_detector.py index d6089a2..e4514ac 100644 --- a/adf_core_python/implement/module/complex/default_road_detector.py +++ b/adf_core_python/implement/module/complex/default_road_detector.py @@ -61,8 +61,8 @@ def resume(self, precompute_data: PrecomputeData) -> RoadDetector: if not isinstance(entity, Building): continue for entity_id in entity.get_neighbours(): - neighbour = self._world_info.get_entity(entity_id) - if isinstance(neighbour, Road): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): self._target_areas.add(entity_id) self._priority_roads = set() @@ -70,8 +70,8 @@ def resume(self, precompute_data: PrecomputeData) -> RoadDetector: if not isinstance(entity, Building): continue for entity_id in entity.get_neighbours(): - neighbour = self._world_info.get_entity(entity_id) - if isinstance(neighbour, Road): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): self._priority_roads.add(entity_id) return self @@ -88,16 +88,16 @@ def prepare(self) -> RoadDetector: for entity in entities: building: Building = cast(Building, entity) for entity_id in building.get_neighbours(): - neighbour = self._world_info.get_entity(entity_id) - if isinstance(neighbour, Road): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): self._target_areas.add(entity_id) self._priority_roads = set() for entity in self._world_info.get_entities_of_types([Refuge]): refuge: Refuge = cast(Refuge, entity) for entity_id in refuge.get_neighbours(): - neighbour = self._world_info.get_entity(entity_id) - if isinstance(neighbour, Road): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): self._priority_roads.add(entity_id) return self From 1b5300c827a25f9c46b595e2a9962f25530daffc Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 9 Oct 2024 13:11:27 +0900 Subject: [PATCH 089/249] refactor: Update action method in DefaultTacticsPoliceForce to use set_target_entity_id from action_ext_move --- .../implement/tactics/default_tactics_police_force.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py index 3253aeb..0a78597 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -116,7 +116,7 @@ def think( target_entity_id = self._road_detector.calculate().get_target_entity_id() if target_entity_id is not None: action = ( - self._action_ext_clear.set_target_entity_id(target_entity_id) + self._action_ext_move.set_target_entity_id(target_entity_id) .calc() .get_action() ) From fd95fce18685b3a01862be89ca692363be13ccae Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 9 Oct 2024 13:34:21 +0900 Subject: [PATCH 090/249] refactor: Simplify CI workflow by removing redundant virtual environment caching steps --- .github/workflows/ci.yaml | 80 +++++++-------------------------------- 1 file changed, 14 insertions(+), 66 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b0da406..bb98450 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,32 +11,19 @@ on: - develop jobs: + # これ以降のジョブでキャッシュが使えるようにする setup: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Python + - &setup-python + name: Set up Python uses: actions/setup-python@v5 with: python-version: 3.12 - - - name: Cache virtual environment - uses: actions/cache@v4 - with: - path: .venv - key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} - restore-keys: | - ${{ runner.os }}-venv- - - - name: Install dependencies - run: | - python -m venv .venv - source .venv/bin/activate - pip install --upgrade pip - pip install poetry - poetry install + cache: 'poetry' ruff-format: needs: setup @@ -45,23 +32,13 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Restore virtual environment cache - uses: actions/cache@v4 - with: - path: .venv - key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} - restore-keys: | - ${{ runner.os }}-venv- + - *setup-python - name: Check ruff version - run: | - source .venv/bin/activate - poetry run ruff --version + run: poetry run ruff --version - name: Run ruff format check - run: | - source .venv/bin/activate - poetry run ruff format --check . + run: poetry run ruff format --check . ruff-lint: needs: setup @@ -70,26 +47,13 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Restore virtual environment cache - uses: actions/cache@v4 - with: - path: .venv - key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} - restore-keys: | - ${{ runner.os }}-venv- - - - name: Set up Python environment - run: source .venv/bin/activate + - *setup-python - name: Check ruff version - run: | - source .venv/bin/activate - poetry run ruff --version + run: poetry run ruff --version - name: Run ruff lint - run: | - source .venv/bin/activate - poetry run ruff check --output-format=github . + run: poetry run ruff check --output-format=github . mypy-type-check: needs: setup @@ -98,23 +62,13 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Restore virtual environment cache - uses: actions/cache@v4 - with: - path: .venv - key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} - restore-keys: | - ${{ runner.os }}-venv- + - *setup-python - name: Check mypy version - run: | - source .venv/bin/activate - poetry run mypy --version + run: poetry run mypy --version - name: Run mypy type check - run: | - source .venv/bin/activate - poetry run mypy . + run: poetry run mypy . pytest: needs: setup @@ -123,13 +77,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Restore virtual environment cache - uses: actions/cache@v4 - with: - path: .venv - key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} - restore-keys: | - ${{ runner.os }}-venv- + - *setup-python - name: Run pytest run: | From a4ac073db97eb100ba3a2ab9e89fbf2468e21ded Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 9 Oct 2024 13:47:10 +0900 Subject: [PATCH 091/249] refactor: Use environment variables for Python version and cache type in CI workflow --- .github/workflows/ci.yaml | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bb98450..793e68a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,6 +10,10 @@ on: - main - develop +env: + PYTHON_VERSION: 3.12 + PYTHON_CACHE_TYPE: 'poetry' + jobs: # これ以降のジョブでキャッシュが使えるようにする setup: @@ -18,12 +22,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - &setup-python - name: Set up Python + - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.12 - cache: 'poetry' + python-version: ${{ env.PYTHON_VERSION }} + cache: ${{ env.PYTHON_CACHE_TYPE }} ruff-format: needs: setup @@ -32,7 +35,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - *setup-python + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: ${{ env.PYTHON_CACHE_TYPE }} - name: Check ruff version run: poetry run ruff --version @@ -47,7 +54,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - *setup-python + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: ${{ env.PYTHON_CACHE_TYPE }} - name: Check ruff version run: poetry run ruff --version @@ -62,7 +73,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - *setup-python + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: ${{ env.PYTHON_CACHE_TYPE }} - name: Check mypy version run: poetry run mypy --version @@ -77,9 +92,12 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - *setup-python + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: ${{ env.PYTHON_CACHE_TYPE }} - name: Run pytest run: | - source .venv/bin/activate poetry run pytest tests/ From df4d626a28e8eda006fac52ca00cfd6b11e59cc9 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 9 Oct 2024 15:35:00 +0900 Subject: [PATCH 092/249] refactor: Streamline CI workflow by consolidating Python setup and virtual environment caching --- .github/workflows/ci.yaml | 84 +++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 793e68a..b3ee205 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,12 +10,7 @@ on: - main - develop -env: - PYTHON_VERSION: 3.12 - PYTHON_CACHE_TYPE: 'poetry' - jobs: - # これ以降のジョブでキャッシュが使えるようにする setup: runs-on: ubuntu-latest steps: @@ -25,8 +20,23 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: ${{ env.PYTHON_VERSION }} - cache: ${{ env.PYTHON_CACHE_TYPE }} + python-version: 3.12 + + - name: Cache virtual environment + id: cached-virtualenv + uses: actions/cache@v4 + with: + path: .venv + key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} + + - name: Install dependencies + if: steps.cached-virtualenv.outputs.cache-hit != 'true' + run: | + python -m venv .venv + source .venv/bin/activate + pip install --upgrade pip + pip install poetry + poetry install ruff-format: needs: setup @@ -35,17 +45,21 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Restore virtual environment cache + uses: actions/cache@v4 with: - python-version: ${{ env.PYTHON_VERSION }} - cache: ${{ env.PYTHON_CACHE_TYPE }} + path: .venv + key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} - name: Check ruff version - run: poetry run ruff --version + run: | + source .venv/bin/activate + ruff --version - name: Run ruff format check - run: poetry run ruff format --check . + run: | + source .venv/bin/activate + ruff format --check . ruff-lint: needs: setup @@ -54,17 +68,24 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Restore virtual environment cache + uses: actions/cache@v4 with: - python-version: ${{ env.PYTHON_VERSION }} - cache: ${{ env.PYTHON_CACHE_TYPE }} + path: .venv + key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} + + - name: Set up Python environment + run: source .venv/bin/activate - name: Check ruff version - run: poetry run ruff --version + run: | + source .venv/bin/activate + poetry run ruff --version - name: Run ruff lint - run: poetry run ruff check --output-format=github . + run: | + source .venv/bin/activate + poetry run ruff check --output-format=github . mypy-type-check: needs: setup @@ -73,17 +94,21 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Restore virtual environment cache + uses: actions/cache@v4 with: - python-version: ${{ env.PYTHON_VERSION }} - cache: ${{ env.PYTHON_CACHE_TYPE }} + path: .venv + key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} - name: Check mypy version - run: poetry run mypy --version + run: | + source .venv/bin/activate + poetry run mypy --version - name: Run mypy type check - run: poetry run mypy . + run: | + source .venv/bin/activate + poetry run mypy . pytest: needs: setup @@ -92,12 +117,13 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Restore virtual environment cache + uses: actions/cache@v4 with: - python-version: ${{ env.PYTHON_VERSION }} - cache: ${{ env.PYTHON_CACHE_TYPE }} + path: .venv + key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} - name: Run pytest run: | + source .venv/bin/activate poetry run pytest tests/ From e3484b85e26149d035591a892acf120527769ac6 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 9 Oct 2024 15:40:36 +0900 Subject: [PATCH 093/249] refactor: Update CI workflow to use Poetry for ruff commands and remove redundant Python environment setup --- .github/workflows/ci.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b3ee205..9d37a27 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -54,12 +54,12 @@ jobs: - name: Check ruff version run: | source .venv/bin/activate - ruff --version + poetry run ruff --version - name: Run ruff format check run: | source .venv/bin/activate - ruff format --check . + poetry run ruff format --check . ruff-lint: needs: setup @@ -74,9 +74,6 @@ jobs: path: .venv key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} - - name: Set up Python environment - run: source .venv/bin/activate - - name: Check ruff version run: | source .venv/bin/activate From aee0c252ed070a39fe2c02ec4e50d427025503f3 Mon Sep 17 00:00:00 2001 From: harrki Date: Wed, 9 Oct 2024 15:50:25 +0900 Subject: [PATCH 094/249] fix: Rename modules from ExtAction to ExtendAction --- .../core/agent/module/module_manager.py | 12 +- .../ext_action.py => action/extend_action.py} | 14 +-- .../core/component/tactics/tactics_agent.py | 8 +- adf_core_python/core/launcher/config_key.py | 2 +- .../default_extend_action_move.py | 16 +-- .../default_extend_action_transport.py | 16 +-- .../tactics/default_tactics_ambulance_team.py | 12 +- .../tactics/default_tactics_fire_brigade.py | 12 +- .../tactics/default_tactics_police_force.py | 12 +- config/module.yaml | 102 ++++++++-------- tests/core/agent/config/module.yaml | 114 +++++++++--------- tests/core/agent/config/test_module_config.py | 14 +-- tests/core/agent/module/module.yaml | 114 +++++++++--------- 13 files changed, 225 insertions(+), 223 deletions(-) rename adf_core_python/core/component/{extaction/ext_action.py => action/extend_action.py} (92%) rename adf_core_python/implement/{extend_action => action}/default_extend_action_move.py (92%) rename adf_core_python/implement/{extend_action => action}/default_extend_action_transport.py (96%) diff --git a/adf_core_python/core/agent/module/module_manager.py b/adf_core_python/core/agent/module/module_manager.py index 4f2dfbd..b15dd8e 100644 --- a/adf_core_python/core/agent/module/module_manager.py +++ b/adf_core_python/core/agent/module/module_manager.py @@ -3,7 +3,7 @@ import importlib from typing import TYPE_CHECKING, Any -from adf_core_python.core.component.extaction.ext_action import ExtAction +from adf_core_python.core.component.action.extend_action import ExtendAction from adf_core_python.core.component.module.abstract_module import AbstractModule if TYPE_CHECKING: @@ -29,7 +29,7 @@ def __init__( self._module_config = module_config self._develop_data = develop_data self._modules: dict[str, AbstractModule] = {} - self._actions: dict[str, ExtAction] = {} + self._actions: dict[str, ExtendAction] = {} self._executors: dict[str, Any] = {} self._pickers: dict[str, Any] = {} @@ -63,7 +63,9 @@ def get_module(self, module_name: str, default_module_name: str) -> AbstractModu raise RuntimeError(f"Module {class_name} is not a subclass of AbstractModule") - def get_ext_action(self, action_name: str, default_action_name: str) -> ExtAction: + def get_extend_action( + self, action_name: str, default_action_name: str + ) -> ExtendAction: class_name = self._module_config.get_value_or_default( action_name, default_action_name ) @@ -77,7 +79,7 @@ def get_ext_action(self, action_name: str, default_action_name: str) -> ExtActio if instance is not None: return instance - if issubclass(action_class, ExtAction): + if issubclass(action_class, ExtendAction): instance = action_class( self._agent_info, self._world_info, @@ -88,7 +90,7 @@ def get_ext_action(self, action_name: str, default_action_name: str) -> ExtActio self._actions[action_name] = instance return instance - raise RuntimeError(f"Action {class_name} is not a subclass of ExtAction") + raise RuntimeError(f"Action {class_name} is not a subclass of ExtendAction") def _load_module(self, class_name: str) -> type: module_name, module_class_name = class_name.rsplit(".", 1) diff --git a/adf_core_python/core/component/extaction/ext_action.py b/adf_core_python/core/component/action/extend_action.py similarity index 92% rename from adf_core_python/core/component/extaction/ext_action.py rename to adf_core_python/core/component/action/extend_action.py index e474a8f..752f64c 100644 --- a/adf_core_python/core/component/extaction/ext_action.py +++ b/adf_core_python/core/component/action/extend_action.py @@ -16,7 +16,7 @@ from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData -class ExtAction(ABC): +class ExtendAction(ABC): def __init__( self, agent_info: AgentInfo, @@ -38,29 +38,29 @@ def __init__( self.count_update_info_current_time: int = 0 @abstractmethod - def set_target_entity_id(self, target_entity_id: EntityID) -> ExtAction: + def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: raise NotImplementedError @abstractmethod - def calc(self) -> ExtAction: + def calc(self) -> ExtendAction: raise NotImplementedError def get_action(self) -> Optional[Action]: return self.result - def precompute(self, precompute_data: PrecomputeData) -> ExtAction: + def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: self.count_precompute += 1 return self - def resume(self, precompute_data: PrecomputeData) -> ExtAction: + def resume(self, precompute_data: PrecomputeData) -> ExtendAction: self.count_resume += 1 return self - def prepare(self) -> ExtAction: + def prepare(self) -> ExtendAction: self.count_prepare += 1 return self - def update_info(self, message_manager: MessageManager) -> ExtAction: + def update_info(self, message_manager: MessageManager) -> ExtendAction: self.count_update_info += 1 return self diff --git a/adf_core_python/core/component/tactics/tactics_agent.py b/adf_core_python/core/component/tactics/tactics_agent.py index fab20dc..b513441 100644 --- a/adf_core_python/core/component/tactics/tactics_agent.py +++ b/adf_core_python/core/component/tactics/tactics_agent.py @@ -14,7 +14,7 @@ from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.component.extaction.ext_action import ExtAction + from adf_core_python.core.component.action.extend_action import ExtendAction from adf_core_python.core.component.module.abstract_module import AbstractModule @@ -22,7 +22,7 @@ class TacticsAgent(ABC): def __init__(self, parent: Optional[TacticsAgent] = None) -> None: self._parent = parent self._modules: list[AbstractModule] = [] - self._actions: list[ExtAction] = [] + self._actions: list[ExtendAction] = [] self._command_executor: Any = None @abstractmethod @@ -100,10 +100,10 @@ def register_module(self, module: AbstractModule) -> None: def unregister_module(self, module: AbstractModule) -> None: self._modules.remove(module) - def register_action(self, action: ExtAction) -> None: + def register_action(self, action: ExtendAction) -> None: self._actions.append(action) - def unregister_action(self, action: ExtAction) -> None: + def unregister_action(self, action: ExtendAction) -> None: self._actions.remove(action) def register_command_executor(self, command_executor: Any) -> None: diff --git a/adf_core_python/core/launcher/config_key.py b/adf_core_python/core/launcher/config_key.py index bcb2577..69d7d35 100644 --- a/adf_core_python/core/launcher/config_key.py +++ b/adf_core_python/core/launcher/config_key.py @@ -3,7 +3,7 @@ class ConfigKey: # General - KEY_LOADER_CLASS: Final[str] = "adf.launcher.loader" + KEY_LOADER_CLASS: Final[str] = "adf_core_python.launcher.loader" KEY_KERNEL_HOST: Final[str] = "kernel.host" KEY_KERNEL_PORT: Final[str] = "kernel.port" KEY_TEAM_NAME: Final[str] = "team.name" diff --git a/adf_core_python/implement/extend_action/default_extend_action_move.py b/adf_core_python/implement/action/default_extend_action_move.py similarity index 92% rename from adf_core_python/implement/extend_action/default_extend_action_move.py rename to adf_core_python/implement/action/default_extend_action_move.py index cd8d934..9026816 100644 --- a/adf_core_python/implement/extend_action/default_extend_action_move.py +++ b/adf_core_python/implement/action/default_extend_action_move.py @@ -15,11 +15,11 @@ from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData -from adf_core_python.core.component.extaction.ext_action import ExtAction +from adf_core_python.core.component.action.extend_action import ExtendAction from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning -class DefaultExtendActionMove(ExtAction): +class DefaultExtendActionMove(ExtendAction): def __init__( self, agent_info: AgentInfo, @@ -49,35 +49,35 @@ def __init__( case Mode.PRECOMPUTED: pass - def precompute(self, precompute_data: PrecomputeData) -> ExtAction: + def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: super().precompute(precompute_data) if self.get_count_precompute() > 1: return self self._path_planning.precompute(precompute_data) return self - def resume(self, precompute_data: PrecomputeData) -> ExtAction: + def resume(self, precompute_data: PrecomputeData) -> ExtendAction: super().resume(precompute_data) if self.get_count_resume() > 1: return self self._path_planning.resume(precompute_data) return self - def prepare(self) -> ExtAction: + def prepare(self) -> ExtendAction: super().prepare() if self.get_count_prepare() > 1: return self self._path_planning.prepare() return self - def update_info(self, message_manager: MessageManager) -> ExtAction: + def update_info(self, message_manager: MessageManager) -> ExtendAction: super().update_info(message_manager) if self.get_count_update_info() > 1: return self self._path_planning.update_info(message_manager) return self - def set_target_entity_id(self, target_entity_id: EntityID) -> ExtAction: + def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: entity: Optional[Entity] = self.world_info.get_entity(target_entity_id) self._target_entity_id = None @@ -94,7 +94,7 @@ def set_target_entity_id(self, target_entity_id: EntityID) -> ExtAction: return self - def calc(self) -> ExtAction: + def calc(self) -> ExtendAction: self.result = None agent: Human = cast(Human, self.agent_info.get_myself()) diff --git a/adf_core_python/implement/extend_action/default_extend_action_transport.py b/adf_core_python/implement/action/default_extend_action_transport.py similarity index 96% rename from adf_core_python/implement/extend_action/default_extend_action_transport.py rename to adf_core_python/implement/action/default_extend_action_transport.py index cad0aeb..fec221f 100644 --- a/adf_core_python/implement/extend_action/default_extend_action_transport.py +++ b/adf_core_python/implement/action/default_extend_action_transport.py @@ -19,13 +19,13 @@ from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData -from adf_core_python.core.component.extaction.ext_action import ExtAction +from adf_core_python.core.component.action.extend_action import ExtendAction from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning from adf_core_python.core.logger.logger import get_agent_logger # TODO: refactor this class -class DefaultExtendActionTransport(ExtAction): +class DefaultExtendActionTransport(ExtendAction): def __init__( self, agent_info: AgentInfo, @@ -58,35 +58,35 @@ def __init__( case Mode.PRECOMPUTED: pass - def precompute(self, precompute_data: PrecomputeData) -> ExtAction: + def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: super().precompute(precompute_data) if self.get_count_precompute() > 1: return self self._path_planning.precompute(precompute_data) return self - def resume(self, precompute_data: PrecomputeData) -> ExtAction: + def resume(self, precompute_data: PrecomputeData) -> ExtendAction: super().resume(precompute_data) if self.get_count_resume() > 1: return self self._path_planning.resume(precompute_data) return self - def prepare(self) -> ExtAction: + def prepare(self) -> ExtendAction: super().prepare() if self.get_count_prepare() > 1: return self self._path_planning.prepare() return self - def update_info(self, message_manager: MessageManager) -> ExtAction: + def update_info(self, message_manager: MessageManager) -> ExtendAction: super().update_info(message_manager) if self.get_count_update_info() > 1: return self self._path_planning.update_info(message_manager) return self - def set_target_entity_id(self, target_entity_id: EntityID) -> ExtAction: + def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: entity: Optional[Entity] = self.world_info.get_world_model().get_entity( target_entity_id ) @@ -100,7 +100,7 @@ def set_target_entity_id(self, target_entity_id: EntityID) -> ExtAction: return self - def calc(self) -> ExtAction: + def calc(self) -> ExtendAction: self._result = None agent: AmbulanceTeamEntity = cast( AmbulanceTeamEntity, self.agent_info.get_myself() diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index ad34f68..5d7e4bb 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -55,13 +55,13 @@ def initialize( "adf_core_python.core.component.module.complex.human_detector.HumanDetector", ), ) - self._action_transport = module_manager.get_ext_action( - "DefaultTacticsAmbulanceTeam.ExtActionTransport", - "adf_core_python.implement.extend_action.default_extend_action_transport.DefaultExtendActionTransport", + self._action_transport = module_manager.get_extend_action( + "DefaultTacticsAmbulanceTeam.ExtendActionTransport", + "adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport", ) - self._action_ext_move = module_manager.get_ext_action( - "DefaultTacticsAmbulanceTeam.ExtActionMove", - "adf_core_python.implement.extend_action.default_extend_action_move.DefaultExtendActionMove", + self._action_ext_move = module_manager.get_extend_action( + "DefaultTacticsAmbulanceTeam.ExtendActionMove", + "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", ) self.register_module(self._search) self.register_module(self._human_detector) diff --git a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py index 2af3378..a61f6ff 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py @@ -46,13 +46,13 @@ def initialize( "adf_core_python.impl.module.complex.DefaultHumanDetector", ), ) - self._action_fire_rescue = module_manager.get_ext_action( - "DefaultTacticsFireBrigade.ExtActionFireRescue", - "adf_core_python.impl.extaction.DefaultExtActionFireRescue", + self._action_fire_rescue = module_manager.get_extend_action( + "DefaultTacticsFireBrigade.ExtendActionRescue", + "adf_core_python.implement.action.DefaultExtendActionRescue", ) - self._action_ext_move = module_manager.get_ext_action( - "DefaultTacticsAmbulanceTeam.ExtActionMove", - "adf_core_python.impl.extaction.DefaultExtActionMove", + self._action_ext_move = module_manager.get_extend_action( + "DefaultTacticsAmbulanceTeam.ExtendActionMove", + "adf_core_python.implement.action.DefaultExtendActionMove", ) self.register_module(self._search) self.register_module(self._human_detector) diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py index 3253aeb..a9f6e92 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -50,13 +50,13 @@ def initialize( "adf_core_python.implement.module.complex.DefaultRoadDetector", ), ) - self._action_ext_clear = module_manager.get_ext_action( - "DefaultTacticsPoliceForce.ExtActionClear", - "adf_core_python.implement.extaction.DefaultExtActionClear", + self._action_ext_clear = module_manager.get_extend_action( + "DefaultTacticsPoliceForce.ExtendActionClear", + "adf_core_python.implement.action.DefaultExtendActionClear", ) - self._action_ext_move = module_manager.get_ext_action( - "DefaultTacticsPoliceForce.ExtActionMove", - "adf_core_python.implement.extaction.DefaultExtActionMove", + self._action_ext_move = module_manager.get_extend_action( + "DefaultTacticsPoliceForce.ExtendActionMove", + "adf_core_python.implement.action.DefaultExtendActionMove", ) self.register_module(self._search) self.register_module(self._road_detector) diff --git a/config/module.yaml b/config/module.yaml index a7dff76..3047c7a 100644 --- a/config/module.yaml +++ b/config/module.yaml @@ -2,43 +2,43 @@ DefaultTacticsAmbulanceTeam: HumanDetector: adf_core_python.implement.module.complex.default_human_detector.DefaultHumanDetector Search: adf_core_python.implement.module.complex.default_search.DefaultSearch - ExtActionTransport: adf_core_python.implement.extend_action.default_extend_action_transport.DefaultExtendActionTransport - ExtActionMove: adf_core_python.implement.extend_action.default_extend_action_move.DefaultExtendActionMove - CommandExecutorAmbulance: adf.impl.centralized.DefaultCommandExecutorAmbulance - CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScout + ExtendActionTransport: adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport + ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove + CommandExecutorAmbulance: adf_core_python.implement.centralized.DefaultCommandExecutorAmbulance + CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout # ## DefaultTacticsFireBrigade # DefaultTacticsFireBrigade: # HumanDetector: sample_team.module.complex.SampleHumanDetector # Search: sample_team.module.complex.SampleSearch -# ExtActionFireRescue: adf.impl.extaction.DefaultExtActionFireRescue -# ExtActionMove: adf.impl.extaction.DefaultExtActionMove -# CommandExecutorFire: adf.impl.centralized.DefaultCommandExecutorFire -# CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScout +# ExtendActionRescue: adf_core_python.implement.action.DefaultExtendActionRescue +# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove +# CommandExecutorFire: adf_core_python.implement.centralized.DefaultCommandExecutorFire +# CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout # ## DefaultTacticsPoliceForce # DefaultTacticsPoliceForce: # RoadDetector: sample_team.module.complex.SampleRoadDetector # Search: sample_team.module.complex.SampleSearch -# ExtActionClear: adf.impl.extaction.DefaultExtActionClear -# ExtActionMove: adf.impl.extaction.DefaultExtActionMove -# CommandExecutorPolice: adf.impl.centralized.DefaultCommandExecutorPolice -# CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScoutPolice +# ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear +# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove +# CommandExecutorPolice: adf_core_python.implement.centralized.DefaultCommandExecutorPolice +# CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScoutPolice # ## DefaultTacticsAmbulanceCentre # DefaultTacticsAmbulanceCentre: # TargetAllocator: sample_team.module.complex.SampleAmbulanceTargetAllocator -# CommandPicker: adf.impl.centralized.DefaultCommandPickerAmbulance +# CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerAmbulance # ## DefaultTacticsFireStation # DefaultTacticsFireStation: # TargetAllocator: sample_team.module.complex.SampleFireTargetAllocator -# CommandPicker: adf.impl.centralized.DefaultCommandPickerFire +# CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerFire # ## DefaultTacticsPoliceOffice # DefaultTacticsPoliceOffice: # TargetAllocator: sample_team.module.complex.SamplePoliceTargetAllocator -# CommandPicker: adf.impl.centralized.DefaultCommandPickerPolice +# CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerPolice ## SampleSearch SampleSearch: @@ -47,70 +47,70 @@ SampleSearch: # ## SampleBuildDetector # SampleBuildingDetector: -# Clustering: adf.impl.module.algorithm.KMeansClustering +# Clustering: adf_core_python.implement.module.algorithm.KMeansClustering # ## SampleRoadDetector # SampleRoadDetector: -# Clustering: adf.impl.module.algorithm.KMeansClustering -# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +# Clustering: adf_core_python.implement.module.algorithm.KMeansClustering +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning # ## SampleHumanDetector # SampleHumanDetector: -# Clustering: adf.impl.module.algorithm.KMeansClustering +# Clustering: adf_core_python.implement.module.algorithm.KMeansClustering -# ## DefaultExtActionClear -# DefaultExtActionClear: -# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +# ## DefaultExtendActionClear +# DefaultExtendActionClear: +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning -# ## DefaultExtActionFireFighting -# DefaultExtActionFireFighting: -# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +# ## DefaultExtendActionFireFighting +# DefaultExtendActionFireFighting: +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning -# ## DefaultExtActionFireRescue -# DefaultExtActionFireRescue: -# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +# ## DefaultExtendActionRescue +# DefaultExtendActionRescue: +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning -# ## DefaultExtActionMove -# DefaultExtActionMove: -# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +# ## DefaultExtendActionMove +# DefaultExtendActionMove: +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning -# ## DefaultExtActionTransport -# DefaultExtActionTransport: -# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +# ## DefaultExtendActionTransport +# DefaultExtendActionTransport: +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning # ## DefaultCommandExecutorAmbulance # DefaultCommandExecutorAmbulance: -# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning -# ExtActionTransport: adf.impl.extaction.DefaultExtActionTransport -# ExtActionMove: adf.impl.extaction.DefaultExtActionMove +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# ExtendActionTransport: adf_core_python.implement.action.DefaultExtendActionTransport +# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove # ## DefaultCommandExecutorFire # DefaultCommandExecutorFire: -# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning -# EtxActionFireRescue: adf.impl.extaction.DefaultExtActionFireRescue -# EtxActionFireFighting: adf.impl.extaction.DefaultExtActionFireFighting -# ExtActionMove: adf.impl.extaction.DefaultExtActionMove +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# EtxActionFireRescue: adf_core_python.implement.action.DefaultExtendActionRescue +# EtxActionFireFighting: adf_core_python.implement.action.DefaultExtendActionFireFighting +# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove # ## DefaultCommandExecutorPolice # DefaultCommandExecutorPolice: -# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning -# ExtActionClear: adf.impl.extaction.DefaultExtActionClear -# ExtActionMove: adf.impl.extaction.DefaultExtActionMove +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear +# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove # ## DefaultCommandExecutorScout # DefaultCommandExecutorScout: -# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning # ## DefaultCommandExecutorScoutPolice # DefaultCommandExecutorScoutPolice: -# PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning -# ExtActionClear: adf.impl.extaction.DefaultExtActionClear +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear # ## MessageManager # MessageManager: -# PlatoonChannelSubscriber: adf.impl.module.comm.DefaultChannelSubscriber -# CenterChannelSubscriber: adf.impl.module.comm.DefaultChannelSubscriber -# PlatoonMessageCoordinator: adf.impl.module.comm.DefaultMessageCoordinator -# CenterMessageCoordinator: adf.impl.module.comm.DefaultMessageCoordinator +# PlatoonChannelSubscriber: adf_core_python.implement.module.comm.DefaultChannelSubscriber +# CenterChannelSubscriber: adf_core_python.implement.module.comm.DefaultChannelSubscriber +# PlatoonMessageCoordinator: adf_core_python.implement.module.comm.DefaultMessageCoordinator +# CenterMessageCoordinator: adf_core_python.implement.module.comm.DefaultMessageCoordinator # ## VisualDebug # VisualDebug: true diff --git a/tests/core/agent/config/module.yaml b/tests/core/agent/config/module.yaml index 6e27f99..54c54a9 100644 --- a/tests/core/agent/config/module.yaml +++ b/tests/core/agent/config/module.yaml @@ -2,122 +2,122 @@ DefaultTacticsAmbulanceTeam: HumanDetector: sample_team.module.complex.SampleHumanDetector Search: sample_team.module.complex.SampleSearch - ExtActionTransport: adf.impl.extaction.DefaultExtActionTransport - ExtActionMove: adf.impl.extaction.DefaultExtActionMove - CommandExecutorAmbulance: adf.impl.centralized.DefaultCommandExecutorAmbulance - CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScout + ExtendActionTransport: adf_core_python.implement.action.DefaultExtendActionTransport + ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove + CommandExecutorAmbulance: adf_core_python.implement.centralized.DefaultCommandExecutorAmbulance + CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout ## DefaultTacticsFireBrigade DefaultTacticsFireBrigade: HumanDetector: sample_team.module.complex.SampleHumanDetector Search: sample_team.module.complex.SampleSearch - ExtActionFireRescue: adf.impl.extaction.DefaultExtActionFireRescue - ExtActionMove: adf.impl.extaction.DefaultExtActionMove - CommandExecutorFire: adf.impl.centralized.DefaultCommandExecutorFire - CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScout + ExtendActionRescue: adf_core_python.implement.action.DefaultExtendActionRescue + ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove + CommandExecutorFire: adf_core_python.implement.centralized.DefaultCommandExecutorFire + CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout ## DefaultTacticsPoliceForce DefaultTacticsPoliceForce: RoadDetector: sample_team.module.complex.SampleRoadDetector Search: sample_team.module.complex.SampleSearch - ExtActionClear: adf.impl.extaction.DefaultExtActionClear - ExtActionMove: adf.impl.extaction.DefaultExtActionMove - CommandExecutorPolice: adf.impl.centralized.DefaultCommandExecutorPolice - CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScoutPolice + ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear + ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove + CommandExecutorPolice: adf_core_python.implement.centralized.DefaultCommandExecutorPolice + CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScoutPolice ## DefaultTacticsAmbulanceCentre DefaultTacticsAmbulanceCentre: TargetAllocator: sample_team.module.complex.SampleAmbulanceTargetAllocator - CommandPicker: adf.impl.centralized.DefaultCommandPickerAmbulance + CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerAmbulance ## DefaultTacticsFireStation DefaultTacticsFireStation: TargetAllocator: sample_team.module.complex.SampleFireTargetAllocator - CommandPicker: adf.impl.centralized.DefaultCommandPickerFire + CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerFire ## DefaultTacticsPoliceOffice DefaultTacticsPoliceOffice: TargetAllocator: sample_team.module.complex.SamplePoliceTargetAllocator - CommandPicker: adf.impl.centralized.DefaultCommandPickerPolice + CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerPolice ## SampleSearch SampleSearch: PathPlanning: - Ambulance: adf.impl.module.algorithm.DijkstraPathPlanning - Fire: adf.impl.module.algorithm.DijkstraPathPlanning - Police: adf.impl.module.algorithm.DijkstraPathPlanning + Ambulance: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + Fire: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + Police: adf_core_python.implement.module.algorithm.DijkstraPathPlanning Clustering: - Ambulance: adf.impl.module.algorithm.KMeansClustering - Fire: adf.impl.module.algorithm.KMeansClustering - Police: adf.impl.module.algorithm.KMeansClustering + Ambulance: adf_core_python.implement.module.algorithm.KMeansClustering + Fire: adf_core_python.implement.module.algorithm.KMeansClustering + Police: adf_core_python.implement.module.algorithm.KMeansClustering ## SampleBuildDetector SampleBuildingDetector: - Clustering: adf.impl.module.algorithm.KMeansClustering + Clustering: adf_core_python.implement.module.algorithm.KMeansClustering ## SampleRoadDetector SampleRoadDetector: - Clustering: adf.impl.module.algorithm.KMeansClustering - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + Clustering: adf_core_python.implement.module.algorithm.KMeansClustering + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning ## SampleHumanDetector SampleHumanDetector: - Clustering: adf.impl.module.algorithm.KMeansClustering + Clustering: adf_core_python.implement.module.algorithm.KMeansClustering -## DefaultExtActionClear -DefaultExtActionClear: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +## DefaultExtendActionClear +DefaultExtendActionClear: + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning -## DefaultExtActionFireFighting -DefaultExtActionFireFighting: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +## DefaultExtendActionFireFighting +DefaultExtendActionFireFighting: + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning -## DefaultExtActionFireRescue -DefaultExtActionFireRescue: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +## DefaultExtendActionRescue +DefaultExtendActionRescue: + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning -## DefaultExtActionMove -DefaultExtActionMove: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +## DefaultExtendActionMove +DefaultExtendActionMove: + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning -## DefaultExtActionTransport -DefaultExtActionTransport: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +## DefaultExtendActionTransport +DefaultExtendActionTransport: + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning ## DefaultCommandExecutorAmbulance DefaultCommandExecutorAmbulance: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning - ExtActionTransport: adf.impl.extaction.DefaultExtActionTransport - ExtActionMove: adf.impl.extaction.DefaultExtActionMove + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + ExtendActionTransport: adf_core_python.implement.action.DefaultExtendActionTransport + ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove ## DefaultCommandExecutorFire DefaultCommandExecutorFire: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning - EtxActionFireRescue: adf.impl.extaction.DefaultExtActionFireRescue - EtxActionFireFighting: adf.impl.extaction.DefaultExtActionFireFighting - ExtActionMove: adf.impl.extaction.DefaultExtActionMove + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + EtxActionFireRescue: adf_core_python.implement.action.DefaultExtendActionRescue + EtxActionFireFighting: adf_core_python.implement.action.DefaultExtendActionFireFighting + ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove ## DefaultCommandExecutorPolice DefaultCommandExecutorPolice: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning - ExtActionClear: adf.impl.extaction.DefaultExtActionClear - ExtActionMove: adf.impl.extaction.DefaultExtActionMove + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear + ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove ## DefaultCommandExecutorScout DefaultCommandExecutorScout: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning ## DefaultCommandExecutorScoutPolice DefaultCommandExecutorScoutPolice: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning - ExtActionClear: adf.impl.extaction.DefaultExtActionClear + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear ## MessageManager MessageManager: - PlatoonChannelSubscriber: adf.impl.module.comm.DefaultChannelSubscriber - CenterChannelSubscriber: adf.impl.module.comm.DefaultChannelSubscriber - PlatoonMessageCoordinator: adf.impl.module.comm.DefaultMessageCoordinator - CenterMessageCoordinator: adf.impl.module.comm.DefaultMessageCoordinator + PlatoonChannelSubscriber: adf_core_python.implement.module.comm.DefaultChannelSubscriber + CenterChannelSubscriber: adf_core_python.implement.module.comm.DefaultChannelSubscriber + PlatoonMessageCoordinator: adf_core_python.implement.module.comm.DefaultMessageCoordinator + CenterMessageCoordinator: adf_core_python.implement.module.comm.DefaultMessageCoordinator ## VisualDebug VisualDebug: true diff --git a/tests/core/agent/config/test_module_config.py b/tests/core/agent/config/test_module_config.py index fcdd099..af0aa95 100644 --- a/tests/core/agent/config/test_module_config.py +++ b/tests/core/agent/config/test_module_config.py @@ -16,31 +16,31 @@ def test_can_read_from_yaml(self) -> None: ) assert ( config.get_value("DefaultTacticsPoliceOffice.CommandPicker") - == "adf.impl.centralized.DefaultCommandPickerPolice" + == "adf_core_python.implement.centralized.DefaultCommandPickerPolice" ) assert ( config.get_value("SampleSearch.PathPlanning.Ambulance") - == "adf.impl.module.algorithm.DijkstraPathPlanning" + == "adf_core_python.implement.module.algorithm.DijkstraPathPlanning" ) assert ( config.get_value("SampleSearch.PathPlanning.Fire") - == "adf.impl.module.algorithm.DijkstraPathPlanning" + == "adf_core_python.implement.module.algorithm.DijkstraPathPlanning" ) assert ( config.get_value("SampleSearch.PathPlanning.Police") - == "adf.impl.module.algorithm.DijkstraPathPlanning" + == "adf_core_python.implement.module.algorithm.DijkstraPathPlanning" ) assert ( config.get_value("SampleSearch.Clustering.Ambulance") - == "adf.impl.module.algorithm.KMeansClustering" + == "adf_core_python.implement.module.algorithm.KMeansClustering" ) assert ( config.get_value("SampleSearch.Clustering.Fire") - == "adf.impl.module.algorithm.KMeansClustering" + == "adf_core_python.implement.module.algorithm.KMeansClustering" ) assert ( config.get_value("SampleSearch.Clustering.Police") - == "adf.impl.module.algorithm.KMeansClustering" + == "adf_core_python.implement.module.algorithm.KMeansClustering" ) def test_if_file_not_found(self) -> None: diff --git a/tests/core/agent/module/module.yaml b/tests/core/agent/module/module.yaml index 6e27f99..54c54a9 100644 --- a/tests/core/agent/module/module.yaml +++ b/tests/core/agent/module/module.yaml @@ -2,122 +2,122 @@ DefaultTacticsAmbulanceTeam: HumanDetector: sample_team.module.complex.SampleHumanDetector Search: sample_team.module.complex.SampleSearch - ExtActionTransport: adf.impl.extaction.DefaultExtActionTransport - ExtActionMove: adf.impl.extaction.DefaultExtActionMove - CommandExecutorAmbulance: adf.impl.centralized.DefaultCommandExecutorAmbulance - CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScout + ExtendActionTransport: adf_core_python.implement.action.DefaultExtendActionTransport + ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove + CommandExecutorAmbulance: adf_core_python.implement.centralized.DefaultCommandExecutorAmbulance + CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout ## DefaultTacticsFireBrigade DefaultTacticsFireBrigade: HumanDetector: sample_team.module.complex.SampleHumanDetector Search: sample_team.module.complex.SampleSearch - ExtActionFireRescue: adf.impl.extaction.DefaultExtActionFireRescue - ExtActionMove: adf.impl.extaction.DefaultExtActionMove - CommandExecutorFire: adf.impl.centralized.DefaultCommandExecutorFire - CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScout + ExtendActionRescue: adf_core_python.implement.action.DefaultExtendActionRescue + ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove + CommandExecutorFire: adf_core_python.implement.centralized.DefaultCommandExecutorFire + CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout ## DefaultTacticsPoliceForce DefaultTacticsPoliceForce: RoadDetector: sample_team.module.complex.SampleRoadDetector Search: sample_team.module.complex.SampleSearch - ExtActionClear: adf.impl.extaction.DefaultExtActionClear - ExtActionMove: adf.impl.extaction.DefaultExtActionMove - CommandExecutorPolice: adf.impl.centralized.DefaultCommandExecutorPolice - CommandExecutorScout: adf.impl.centralized.DefaultCommandExecutorScoutPolice + ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear + ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove + CommandExecutorPolice: adf_core_python.implement.centralized.DefaultCommandExecutorPolice + CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScoutPolice ## DefaultTacticsAmbulanceCentre DefaultTacticsAmbulanceCentre: TargetAllocator: sample_team.module.complex.SampleAmbulanceTargetAllocator - CommandPicker: adf.impl.centralized.DefaultCommandPickerAmbulance + CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerAmbulance ## DefaultTacticsFireStation DefaultTacticsFireStation: TargetAllocator: sample_team.module.complex.SampleFireTargetAllocator - CommandPicker: adf.impl.centralized.DefaultCommandPickerFire + CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerFire ## DefaultTacticsPoliceOffice DefaultTacticsPoliceOffice: TargetAllocator: sample_team.module.complex.SamplePoliceTargetAllocator - CommandPicker: adf.impl.centralized.DefaultCommandPickerPolice + CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerPolice ## SampleSearch SampleSearch: PathPlanning: - Ambulance: adf.impl.module.algorithm.DijkstraPathPlanning - Fire: adf.impl.module.algorithm.DijkstraPathPlanning - Police: adf.impl.module.algorithm.DijkstraPathPlanning + Ambulance: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + Fire: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + Police: adf_core_python.implement.module.algorithm.DijkstraPathPlanning Clustering: - Ambulance: adf.impl.module.algorithm.KMeansClustering - Fire: adf.impl.module.algorithm.KMeansClustering - Police: adf.impl.module.algorithm.KMeansClustering + Ambulance: adf_core_python.implement.module.algorithm.KMeansClustering + Fire: adf_core_python.implement.module.algorithm.KMeansClustering + Police: adf_core_python.implement.module.algorithm.KMeansClustering ## SampleBuildDetector SampleBuildingDetector: - Clustering: adf.impl.module.algorithm.KMeansClustering + Clustering: adf_core_python.implement.module.algorithm.KMeansClustering ## SampleRoadDetector SampleRoadDetector: - Clustering: adf.impl.module.algorithm.KMeansClustering - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + Clustering: adf_core_python.implement.module.algorithm.KMeansClustering + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning ## SampleHumanDetector SampleHumanDetector: - Clustering: adf.impl.module.algorithm.KMeansClustering + Clustering: adf_core_python.implement.module.algorithm.KMeansClustering -## DefaultExtActionClear -DefaultExtActionClear: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +## DefaultExtendActionClear +DefaultExtendActionClear: + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning -## DefaultExtActionFireFighting -DefaultExtActionFireFighting: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +## DefaultExtendActionFireFighting +DefaultExtendActionFireFighting: + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning -## DefaultExtActionFireRescue -DefaultExtActionFireRescue: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +## DefaultExtendActionRescue +DefaultExtendActionRescue: + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning -## DefaultExtActionMove -DefaultExtActionMove: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +## DefaultExtendActionMove +DefaultExtendActionMove: + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning -## DefaultExtActionTransport -DefaultExtActionTransport: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning +## DefaultExtendActionTransport +DefaultExtendActionTransport: + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning ## DefaultCommandExecutorAmbulance DefaultCommandExecutorAmbulance: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning - ExtActionTransport: adf.impl.extaction.DefaultExtActionTransport - ExtActionMove: adf.impl.extaction.DefaultExtActionMove + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + ExtendActionTransport: adf_core_python.implement.action.DefaultExtendActionTransport + ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove ## DefaultCommandExecutorFire DefaultCommandExecutorFire: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning - EtxActionFireRescue: adf.impl.extaction.DefaultExtActionFireRescue - EtxActionFireFighting: adf.impl.extaction.DefaultExtActionFireFighting - ExtActionMove: adf.impl.extaction.DefaultExtActionMove + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + EtxActionFireRescue: adf_core_python.implement.action.DefaultExtendActionRescue + EtxActionFireFighting: adf_core_python.implement.action.DefaultExtendActionFireFighting + ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove ## DefaultCommandExecutorPolice DefaultCommandExecutorPolice: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning - ExtActionClear: adf.impl.extaction.DefaultExtActionClear - ExtActionMove: adf.impl.extaction.DefaultExtActionMove + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear + ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove ## DefaultCommandExecutorScout DefaultCommandExecutorScout: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning ## DefaultCommandExecutorScoutPolice DefaultCommandExecutorScoutPolice: - PathPlanning: adf.impl.module.algorithm.DijkstraPathPlanning - ExtActionClear: adf.impl.extaction.DefaultExtActionClear + PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear ## MessageManager MessageManager: - PlatoonChannelSubscriber: adf.impl.module.comm.DefaultChannelSubscriber - CenterChannelSubscriber: adf.impl.module.comm.DefaultChannelSubscriber - PlatoonMessageCoordinator: adf.impl.module.comm.DefaultMessageCoordinator - CenterMessageCoordinator: adf.impl.module.comm.DefaultMessageCoordinator + PlatoonChannelSubscriber: adf_core_python.implement.module.comm.DefaultChannelSubscriber + CenterChannelSubscriber: adf_core_python.implement.module.comm.DefaultChannelSubscriber + PlatoonMessageCoordinator: adf_core_python.implement.module.comm.DefaultMessageCoordinator + CenterMessageCoordinator: adf_core_python.implement.module.comm.DefaultMessageCoordinator ## VisualDebug VisualDebug: true From d8a08857667ce909e0dfda55467b4ebce5279c64 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 9 Oct 2024 15:52:10 +0900 Subject: [PATCH 095/249] refactor: Swap action_ext_move and action_ext_clear in DefaultTacticsPoliceForce --- .../implement/tactics/default_tactics_police_force.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py index 0a78597..ac649aa 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -116,7 +116,7 @@ def think( target_entity_id = self._road_detector.calculate().get_target_entity_id() if target_entity_id is not None: action = ( - self._action_ext_move.set_target_entity_id(target_entity_id) + self._action_ext_clear.set_target_entity_id(target_entity_id) .calc() .get_action() ) @@ -126,7 +126,7 @@ def think( target_entity_id = self._search.calculate().get_target_entity_id() if target_entity_id is not None: action = ( - self._action_ext_clear.set_target_entity_id(target_entity_id) + self._action_ext_move.set_target_entity_id(target_entity_id) .calc() .get_action() ) From f9004de25b0901cb0e73798c651dc212cc6b243e Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 9 Oct 2024 16:16:11 +0900 Subject: [PATCH 096/249] ci: Simplify CI workflow by removing poetry activation and setting path directly --- .github/workflows/ci.yaml | 40 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9d37a27..9854a70 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -51,15 +51,14 @@ jobs: path: .venv key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} + - name: Set path + run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH + - name: Check ruff version - run: | - source .venv/bin/activate - poetry run ruff --version + run: ruff --version - name: Run ruff format check - run: | - source .venv/bin/activate - poetry run ruff format --check . + run: ruff format --check . ruff-lint: needs: setup @@ -74,15 +73,14 @@ jobs: path: .venv key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} + - name: Set path + run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH + - name: Check ruff version - run: | - source .venv/bin/activate - poetry run ruff --version + run: ruff --version - name: Run ruff lint - run: | - source .venv/bin/activate - poetry run ruff check --output-format=github . + run: ruff check --output-format=github . mypy-type-check: needs: setup @@ -97,15 +95,14 @@ jobs: path: .venv key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} + - name: Set path + run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH + - name: Check mypy version - run: | - source .venv/bin/activate - poetry run mypy --version + run: mypy --version - name: Run mypy type check - run: | - source .venv/bin/activate - poetry run mypy . + run: mypy . pytest: needs: setup @@ -120,7 +117,8 @@ jobs: path: .venv key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} + - name: Set path + run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH + - name: Run pytest - run: | - source .venv/bin/activate - poetry run pytest tests/ + run: pytest tests/ From f3beb56129a0cf86ff60d2393426b45cd7e1dbac Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 11 Oct 2024 11:25:26 +0900 Subject: [PATCH 097/249] fix: add count reset --- .../implement/tactics/default_tactics_fire_brigade.py | 1 + .../implement/tactics/default_tactics_police_force.py | 1 + 2 files changed, 2 insertions(+) diff --git a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py index a61f6ff..f9eabd0 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py @@ -104,6 +104,7 @@ def think( message_manager: MessageManager, develop_data: DevelopData, ) -> Action: + self.reset_count() self.module_update_info(message_manager) agent: FireBrigadeEntity = cast(FireBrigadeEntity, agent_info.get_myself()) # noqa: F841 diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py index 82bfae7..3cb08cc 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -108,6 +108,7 @@ def think( message_manager: MessageManager, develop_data: DevelopData, ) -> Action: + self.reset_count() self.module_update_info(message_manager) agent: PoliceForceEntity = cast(PoliceForceEntity, agent_info.get_myself()) # noqa: F841 From 301124737888cffe69f1a5f98bdc874229b8de23 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 17 Oct 2024 17:08:15 +0900 Subject: [PATCH 098/249] fix: update protobuf to version 5.28.2 and adjust Python version compatibility --- poetry.lock | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/poetry.lock b/poetry.lock index d96029f..7ae97d6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -191,33 +191,22 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "protobuf" -version = "3.20.3" -description = "Protocol Buffers" +version = "5.28.2" +description = "" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99"}, - {file = "protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e"}, - {file = "protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c"}, - {file = "protobuf-3.20.3-cp310-cp310-win_amd64.whl", hash = "sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7"}, - {file = "protobuf-3.20.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469"}, - {file = "protobuf-3.20.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4"}, - {file = "protobuf-3.20.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4"}, - {file = "protobuf-3.20.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454"}, - {file = "protobuf-3.20.3-cp37-cp37m-win32.whl", hash = "sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905"}, - {file = "protobuf-3.20.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c"}, - {file = "protobuf-3.20.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7"}, - {file = "protobuf-3.20.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee"}, - {file = "protobuf-3.20.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050"}, - {file = "protobuf-3.20.3-cp38-cp38-win32.whl", hash = "sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86"}, - {file = "protobuf-3.20.3-cp38-cp38-win_amd64.whl", hash = "sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9"}, - {file = "protobuf-3.20.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b"}, - {file = "protobuf-3.20.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b"}, - {file = "protobuf-3.20.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402"}, - {file = "protobuf-3.20.3-cp39-cp39-win32.whl", hash = "sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480"}, - {file = "protobuf-3.20.3-cp39-cp39-win_amd64.whl", hash = "sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7"}, - {file = "protobuf-3.20.3-py2.py3-none-any.whl", hash = "sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db"}, - {file = "protobuf-3.20.3.tar.gz", hash = "sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2"}, + {file = "protobuf-5.28.2-cp310-abi3-win32.whl", hash = "sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d"}, + {file = "protobuf-5.28.2-cp310-abi3-win_amd64.whl", hash = "sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132"}, + {file = "protobuf-5.28.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7"}, + {file = "protobuf-5.28.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f"}, + {file = "protobuf-5.28.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f"}, + {file = "protobuf-5.28.2-cp38-cp38-win32.whl", hash = "sha256:87317e9bcda04a32f2ee82089a204d3a2f0d3c8aeed16568c7daf4756e4f1fe0"}, + {file = "protobuf-5.28.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0ea0123dac3399a2eeb1a1443d82b7afc9ff40241433296769f7da42d142ec3"}, + {file = "protobuf-5.28.2-cp39-cp39-win32.whl", hash = "sha256:ca53faf29896c526863366a52a8f4d88e69cd04ec9571ed6082fa117fac3ab36"}, + {file = "protobuf-5.28.2-cp39-cp39-win_amd64.whl", hash = "sha256:8ddc60bf374785fb7cb12510b267f59067fa10087325b8e1855b898a0d81d276"}, + {file = "protobuf-5.28.2-py3-none-any.whl", hash = "sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece"}, + {file = "protobuf-5.28.2.tar.gz", hash = "sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0"}, ] [[package]] @@ -340,14 +329,14 @@ files = [] develop = false [package.dependencies] -protobuf = "==3.20.*" +protobuf = ">=5.28" rtree = "*" [package.source] type = "git" url = "https://github.com/adf-python/rcrs-core-python" reference = "HEAD" -resolved_reference = "3ceb2db8ed14842db1394c8c92900720eb334600" +resolved_reference = "bfcdcff39e79c71ef953d5f282a4fedd507a5084" [[package]] name = "rtree" From eb8340d5ecc3a4042da9fe4eaf3dfc3d4aa4c71b Mon Sep 17 00:00:00 2001 From: harrki Date: Wed, 23 Oct 2024 11:08:00 +0900 Subject: [PATCH 099/249] feat: Add PoliceForce and FireBrigade modules --- .../core/agent/action/common/action_move.py | 9 + .../core/agent/action/police/action_clear.py | 4 +- .../agent/action/police/action_clear_area.py | 8 + adf_core_python/core/agent/info/world_info.py | 20 +- .../core/agent/platoon/platoon_fire.py | 34 + .../core/agent/platoon/platoon_police.py | 34 + .../core/component/action/extend_action.py | 2 +- .../core/launcher/agent_launcher.py | 21 +- .../connect/connector_ambulance_team.py | 2 +- .../connect/connector_fire_brigade.py | 30 +- .../connect/connector_fire_station.py | 2 +- .../connect/connector_police_force.py | 32 +- .../connect/connector_police_office.py | 4 +- .../action/default_extend_action_clear.py | 753 ++++++++++++++++++ .../action/default_extend_action_move.py | 2 +- .../action/default_extend_action_rescue.py | 149 ++++ .../action/default_extend_action_transport.py | 2 +- .../module/complex/default_road_detector.py | 6 +- .../tactics/default_tactics_ambulance_team.py | 4 +- .../tactics/default_tactics_fire_brigade.py | 37 +- .../tactics/default_tactics_police_force.py | 34 +- adf_core_python/main.py | 8 +- config/module.yaml | 28 +- poetry.lock | 161 ++-- pyproject.toml | 1 + 25 files changed, 1259 insertions(+), 128 deletions(-) create mode 100644 adf_core_python/core/agent/platoon/platoon_fire.py create mode 100644 adf_core_python/core/agent/platoon/platoon_police.py create mode 100644 adf_core_python/implement/action/default_extend_action_clear.py create mode 100644 adf_core_python/implement/action/default_extend_action_rescue.py diff --git a/adf_core_python/core/agent/action/common/action_move.py b/adf_core_python/core/agent/action/common/action_move.py index d5f586b..2baaba4 100644 --- a/adf_core_python/core/agent/action/common/action_move.py +++ b/adf_core_python/core/agent/action/common/action_move.py @@ -22,6 +22,15 @@ def __init__( self.destination_x = destination_x self.destination_y = destination_y + def is_destination_defined(self) -> bool: + return self.destination_x is not None and self.destination_y is not None + + def get_destination_x(self) -> Optional[int]: + return self.destination_x + + def get_destination_y(self) -> Optional[int]: + return self.destination_y + def get_command(self, agent_id: EntityID, time: int) -> Command: path: list[int] = [p.get_value() for p in self.path] if self.destination_x is not None and self.destination_y is not None: diff --git a/adf_core_python/core/agent/action/police/action_clear.py b/adf_core_python/core/agent/action/police/action_clear.py index dcee14a..b343336 100644 --- a/adf_core_python/core/agent/action/police/action_clear.py +++ b/adf_core_python/core/agent/action/police/action_clear.py @@ -1,12 +1,14 @@ from typing import TYPE_CHECKING from rcrs_core.commands.AKClear import AKClear +from rcrs_core.commands.Command import Command +from rcrs_core.entities.blockade import Blockade +from rcrs_core.worldmodel.entityID import EntityID from adf_core_python.core.agent.action.action import Action if TYPE_CHECKING: from rcrs_core.commands.Command import Command - from rcrs_core.entities.blockade import Blockade from rcrs_core.worldmodel.entityID import EntityID diff --git a/adf_core_python/core/agent/action/police/action_clear_area.py b/adf_core_python/core/agent/action/police/action_clear_area.py index f87726f..2531dd5 100644 --- a/adf_core_python/core/agent/action/police/action_clear_area.py +++ b/adf_core_python/core/agent/action/police/action_clear_area.py @@ -1,6 +1,8 @@ from typing import TYPE_CHECKING from rcrs_core.commands.AKClearArea import AKClearArea +from rcrs_core.commands.Command import Command +from rcrs_core.worldmodel.entityID import EntityID from adf_core_python.core.agent.action.action import Action @@ -14,6 +16,12 @@ def __init__(self, position_x: int, position_y: int) -> None: self.position_x = position_x self.position_y = position_y + def get_position_x(self) -> int: + return self.position_x + + def get_position_y(self) -> int: + return self.position_y + def get_command(self, agent_id: EntityID, time: int) -> Command: return AKClearArea(agent_id, time, self.position_x, self.position_y) diff --git a/adf_core_python/core/agent/info/world_info.py b/adf_core_python/core/agent/info/world_info.py index f4818f9..223bfb1 100644 --- a/adf_core_python/core/agent/info/world_info.py +++ b/adf_core_python/core/agent/info/world_info.py @@ -1,5 +1,7 @@ -from typing import Any, Optional +from typing import Any, Optional, cast +from rcrs_core.entities.area import Area +from rcrs_core.entities.blockade import Blockade from rcrs_core.entities.entity import Entity from rcrs_core.worldmodel.changeSet import ChangeSet from rcrs_core.worldmodel.entityID import EntityID @@ -153,3 +155,19 @@ def get_change_set(self) -> ChangeSet: Change set """ return self._change_set + + def get_bloackades(self, area: Area) -> set[Blockade]: + """ + Get the blockades in the area + + Returns + ------- + ChangeSet + Blockade + """ + bloakcades = set() + for blockade_entity_id in area.get_blockades(): + bloackde_entity = self.get_entity(blockade_entity_id) + if isinstance(bloackde_entity, Blockade): + bloakcades.add(cast(Blockade, bloackde_entity)) + return bloakcades diff --git a/adf_core_python/core/agent/platoon/platoon_fire.py b/adf_core_python/core/agent/platoon/platoon_fire.py new file mode 100644 index 0000000..1935795 --- /dev/null +++ b/adf_core_python/core/agent/platoon/platoon_fire.py @@ -0,0 +1,34 @@ +from rcrs_core.connection.URN import Entity as EntityURN + +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.platoon.platoon import Platoon +from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent + + +class PlatoonFire(Platoon): + def __init__( + self, + tactics_agent: TacticsAgent, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + ): + super().__init__( + tactics_agent, + team_name, + is_precompute, + is_debug, + data_storage_name, + module_config, + develop_data, + ) + + def precompute(self) -> None: + pass + + def get_requested_entities(self) -> list[EntityURN]: + return [EntityURN.FIRE_BRIGADE] diff --git a/adf_core_python/core/agent/platoon/platoon_police.py b/adf_core_python/core/agent/platoon/platoon_police.py new file mode 100644 index 0000000..129c663 --- /dev/null +++ b/adf_core_python/core/agent/platoon/platoon_police.py @@ -0,0 +1,34 @@ +from rcrs_core.connection.URN import Entity as EntityURN + +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.platoon.platoon import Platoon +from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent + + +class PlatoonPolice(Platoon): + def __init__( + self, + tactics_agent: TacticsAgent, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + ): + super().__init__( + tactics_agent, + team_name, + is_precompute, + is_debug, + data_storage_name, + module_config, + develop_data, + ) + + def precompute(self) -> None: + pass + + def get_requested_entities(self) -> list[EntityURN]: + return [EntityURN.POLICE_FORCE] diff --git a/adf_core_python/core/component/action/extend_action.py b/adf_core_python/core/component/action/extend_action.py index 752f64c..fece605 100644 --- a/adf_core_python/core/component/action/extend_action.py +++ b/adf_core_python/core/component/action/extend_action.py @@ -42,7 +42,7 @@ def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: raise NotImplementedError @abstractmethod - def calc(self) -> ExtendAction: + def calculate(self) -> ExtendAction: raise NotImplementedError def get_action(self) -> Optional[Action]: diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index 3b66c1b..edc4148 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -14,21 +14,22 @@ from adf_core_python.core.launcher.connect.connector_ambulance_team import ( ConnectorAmbulanceTeam, ) -from adf_core_python.core.logger.logger import get_logger +from adf_core_python.core.launcher.connect.connector_fire_brigade import ( + ConnectorFireBrigade, +) -# from adf_core_python.core.launcher.connect.connector_fire_brigade import ( -# ConnectorFireBrigade, -# ) # from adf_core_python.core.launcher.connect.connector_fire_station import ( # ConnectorFireStation, # ) -# from adf_core_python.core.launcher.connect.connector_police_force import ( -# ConnectorPoliceForce, -# ) +from adf_core_python.core.launcher.connect.connector_police_force import ( + ConnectorPoliceForce, +) # from adf_core_python.core.launcher.connect.connector_police_office import ( # ConnectorPoliceOffice, # ) +from adf_core_python.core.logger.logger import get_logger + class AgentLauncher: def __init__(self, config: Config): @@ -50,11 +51,11 @@ def init_connector(self) -> None: self.config.get_value(ConfigKey.KEY_TEAM_NAME), ) - # self.connectors.append(ConnectorAmbulanceCentre()) self.connectors.append(ConnectorAmbulanceTeam()) - # self.connectors.append(ConnectorFireBrigade()) + # self.connectors.append(ConnectorAmbulanceCentre()) + self.connectors.append(ConnectorFireBrigade()) # self.connectors.append(ConnectorFireStation()) - # self.connectors.append(ConnectorPoliceForce()) + self.connectors.append(ConnectorPoliceForce()) # self.connectors.append(ConnectorPoliceOffice()) def launch(self) -> None: diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index 1d457b3..8bb3af5 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -26,7 +26,7 @@ def connect( config: Config, loader: AbstractLoader, ) -> list[threading.Thread]: - count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) + count: int = config.get_value(ConfigKey.KEY_AMBULANCE_TEAM_COUNT, 0) if count == 0: return [] diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/adf_core_python/core/launcher/connect/connector_fire_brigade.py index f210a63..576978e 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_brigade.py +++ b/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -1,21 +1,24 @@ import threading -from logging import Logger, getLogger -from rcrs_core.agents.fireBrigadeAgent import FireBrigadeAgent from rcrs_core.connection.componentLauncher import ComponentLauncher from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.platoon.platoon_fire import PlatoonFire from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.component.tactics.tactics_fire_brigade import ( + TacticsFireBrigade, +) from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.connector import Connector +from adf_core_python.core.logger.logger import get_logger class ConnectorFireBrigade(Connector): def __init__(self) -> None: super().__init__() - self.logger: Logger = getLogger(__name__) + self.logger = get_logger(__name__) def connect( self, @@ -23,20 +26,19 @@ def connect( config: Config, loader: AbstractLoader, ) -> list[threading.Thread]: - count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) + count: int = config.get_value(ConfigKey.KEY_FIRE_BRIGADE_COUNT, 0) if count == 0: return [] threads: list[threading.Thread] = [] for _ in range(count): - # tactics_fire_brigade: TacticsFireBrigade - if loader.get_tactics_fire_brigade() is not None: + if loader.get_tactics_fire_brigade() is None: self.logger.error("Cannot load fire brigade tactics") - # tactics_fire_brigade = loader.get_tactics_fire_brigade() - else: - # tactics_fire_brigade = DummyTacticsFireBrigade() - pass + + tactics_fire_brigade: TacticsFireBrigade = ( # noqa: F841 + loader.get_tactics_fire_brigade() + ) module_config: ModuleConfig = ModuleConfig( # noqa: F841 config.get_value( @@ -56,8 +58,14 @@ def connect( thread = threading.Thread( target=component_launcher.connect, args=( - FireBrigadeAgent( + PlatoonFire( + tactics_fire_brigade, + "fire_brigade", config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + "test", + module_config, + develop_data, ), component_launcher.generate_request_ID(), ), diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/adf_core_python/core/launcher/connect/connector_fire_station.py index 86ac1dc..99d50dc 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_station.py +++ b/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -23,7 +23,7 @@ def connect( config: Config, loader: AbstractLoader, ) -> list[threading.Thread]: - count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) + count: int = config.get_value(ConfigKey.KEY_FIRE_STATION_COUNT, 0) if count == 0: return [] diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py index 440a75a..8d1502f 100644 --- a/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -1,21 +1,24 @@ import threading -from logging import Logger, getLogger -from rcrs_core.agents.policeForceAgent import PoliceForceAgent from rcrs_core.connection.componentLauncher import ComponentLauncher from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.platoon.platoon_police import PlatoonPolice from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.component.tactics.tactics_police_force import ( + TacticsPoliceForce, +) from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.connector import Connector +from adf_core_python.core.logger.logger import get_logger class ConnectorPoliceForce(Connector): def __init__(self) -> None: super().__init__() - self.logger: Logger = getLogger(__name__) + self.logger = get_logger(__name__) def connect( self, @@ -23,20 +26,19 @@ def connect( config: Config, loader: AbstractLoader, ) -> list[threading.Thread]: - count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) + count: int = config.get_value(ConfigKey.KEY_POLICE_FORCE_COUNT, 0) if count == 0: return [] threads: list[threading.Thread] = [] for _ in range(count): - # tactics_police_force: TacticsPoliceForce - if loader.get_tactics_police_force() is not None: + if loader.get_tactics_police_force() is None: self.logger.error("Cannot load police force tactics") - # tactics_police_force = loader.get_tactics_police_force() - else: - # tactics_police_force = DummyTacticsPoliceForce() - pass + + tactics_police_force: TacticsPoliceForce = ( # noqa: F841 + loader.get_tactics_police_force() + ) module_config: ModuleConfig = ModuleConfig( # noqa: F841 config.get_value( @@ -56,13 +58,19 @@ def connect( thread = threading.Thread( target=component_launcher.connect, args=( - PoliceForceAgent( + PlatoonPolice( + tactics_police_force, + "police_force", config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + "test", + module_config, + develop_data, ), component_launcher.generate_request_ID(), ), ) threads.append(thread) - self.logger.info("Connected ambulance centre (count: %d)" % count) + self.logger.info("Connected police force (count: %d)" % count) return threads diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py index 789e962..7627fc4 100644 --- a/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -23,7 +23,7 @@ def connect( config: Config, loader: AbstractLoader, ) -> list[threading.Thread]: - count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) + count: int = config.get_value(ConfigKey.KEY_POLICE_OFFICE_COUNT, 0) if count == 0: return [] @@ -64,5 +64,5 @@ def connect( ) threads.append(thread) - self.logger.info("Connected ambulance centre (count: %d)" % count) + self.logger.info("Connected police office (count: %d)" % count) return threads diff --git a/adf_core_python/implement/action/default_extend_action_clear.py b/adf_core_python/implement/action/default_extend_action_clear.py new file mode 100644 index 0000000..7b6731b --- /dev/null +++ b/adf_core_python/implement/action/default_extend_action_clear.py @@ -0,0 +1,753 @@ +import math +import sys +from typing import Optional, cast + +from rcrs_core.entities.ambulanceTeam import AmbulanceTeamEntity +from rcrs_core.entities.area import Area +from rcrs_core.entities.blockade import Blockade +from rcrs_core.entities.building import Building +from rcrs_core.entities.fireBrigade import FireBrigadeEntity +from rcrs_core.entities.human import Human +from rcrs_core.entities.policeForce import PoliceForceEntity +from rcrs_core.entities.refuge import Refuge +from rcrs_core.entities.road import Road +from rcrs_core.worldmodel.entityID import EntityID +from shapely import LineString, Point, Polygon + +from adf_core_python.core.agent.action.action import Action +from adf_core_python.core.agent.action.common.action_move import ActionMove +from adf_core_python.core.agent.action.common.action_rest import ActionRest +from adf_core_python.core.agent.action.police.action_clear import ActionClear +from adf_core_python.core.agent.action.police.action_clear_area import ActionClearArea +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.action.extend_action import ExtendAction +from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning + + +class DefaultExtendActionClear(ExtendAction): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._clear_distance = float( + self.scenario_info.get_value("clear.repair.distance", 0.0) + ) + self._forced_move = float( + develop_data.get_value( + "adf_core_python.implement.action.DefaultExtendActionClear.forced_move", + 3, + ) + ) + self._threshold_rest = float( + develop_data.get_value( + "adf_core_python.implement.action.DefaultExtendActionClear.rest", 100 + ) + ) + + self._target_entity_id = None + self._move_point_cache: dict[EntityID, Optional[set[tuple[float, float]]]] = {} + self._old_clear_x = 0 + self._old_clear_y = 0 + self.count = 0 + + match self.scenario_info.get_mode(): + case Mode.NON_PRECOMPUTE: + self._path_planning = cast( + PathPlanning, + self.module_manager.get_module( + "DefaultExtendActionClear.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + case Mode.PRECOMPUTATION: + pass + case Mode.PRECOMPUTED: + pass + + def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: + super().precompute(precompute_data) + if self.get_count_precompute() >= 2: + return self + self._path_planning.precompute(precompute_data) + self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + return self + + def resume(self, precompute_data: PrecomputeData) -> ExtendAction: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + self._path_planning.resume(precompute_data) + self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + return self + + def prepare(self) -> ExtendAction: + super().prepare() + if self.get_count_prepare() >= 2: + return self + self._path_planning.prepare() + self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + return self + + def update_info(self, message_manager: MessageManager) -> ExtendAction: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self + self._path_planning.update_info(message_manager) + return self + + def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: + self._target_entity_id = None + target_entity = self.world_info.get_entity(target_entity_id) + if target_entity is not None: + if isinstance(target_entity, Road): + self._target_entity_id = target_entity_id + elif isinstance(target_entity, Blockade): + self._target_entity_id = target_entity.get_position() + elif isinstance(target_entity, Building): + self._target_entity_id = target_entity_id + return self + + def calculate(self) -> ExtendAction: + self.result = None + police_force = cast(PoliceForceEntity, self.agent_info.get_myself()) + + if self._need_rest(police_force): + target_entity_ids: list[EntityID] = [] + if self._target_entity_id is not None: + target_entity_ids.append(self._target_entity_id) + + self.result = self._calc_rest( + police_force, self._path_planning, target_entity_ids + ) + if self.result is not None: + return self + + if self._target_entity_id is None: + return self + + agent_position_entity_id = police_force.get_position() + target_entity = self.world_info.get_entity(self._target_entity_id) + position_entity = self.world_info.get_entity(agent_position_entity_id) + if target_entity is None or isinstance(target_entity, Area) is False: + return self + if isinstance(position_entity, Road): + self.result = self._get_rescue_action( + police_force, cast(Road, position_entity) + ) + if self.result is not None: + return self + + if agent_position_entity_id == self._target_entity_id: + self.result = self._get_area_clear_action( + police_force, cast(Road, position_entity) + ) + if self.result is not None: + return self + elif ( + cast(Area, target_entity).get_edge_to(agent_position_entity_id) is not None + ): + self.result = self._get_neighbour_position_action( + police_force, cast(Area, target_entity) + ) + else: + path = self._path_planning.get_path( + agent_position_entity_id, self._target_entity_id + ) + if path is not None and len(path) > 0: + index = self._index_of(path, agent_position_entity_id) + if index == -1: + area = cast(Area, position_entity) + for i in range(0, len(path), 1): + if area.get_edge_to(path[i]) is not None: + index = i + break + + elif index >= 0: + index += 1 + + if index >= 0 and index < len(path): + entity = self.world_info.get_entity(path[index]) + self.result = self._get_neighbour_position_action( + police_force, cast(Area, entity) + ) + if self.result is not None and isinstance(self.result, ActionMove): + action_move = cast(ActionMove, self.result) + if action_move.is_destination_defined(): + self.result = None + + if self.result is None: + self.result = ActionMove(path) + + return self + + def _need_rest(self, police_force: PoliceForceEntity) -> bool: + hp = police_force.get_hp() + damage = police_force.get_damage() + + if hp == 0 or damage == 0: + return False + + active_time = (hp / damage) + (1 if (hp % damage) != 0 else 0) + if self._kernel_time == -1: + self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + + return damage >= self._threshold_rest or ( + active_time + self.agent_info.get_time() < self._kernel_time + ) + + def _calc_rest( + self, + police_force: PoliceForceEntity, + path_planning: PathPlanning, + target_entity_ids: list[EntityID], + ) -> Optional[Action]: + position_entity_id = police_force.get_position() + refuges = self.world_info.get_entity_ids_of_types([Refuge]) + current_size = len(refuges) + if position_entity_id in refuges: + return ActionRest() + + first_result: list[EntityID] = [] + while len(refuges) > 0: + path = path_planning.get_path(position_entity_id, refuges[0]) + if path is not None and len(path) > 0: + if first_result == []: + first_result = path.copy() + if target_entity_ids == []: + break + + refuge_entity_id = path[-1] + from_refuge_to_target_path = path_planning.get_path( + refuge_entity_id, target_entity_ids[0] + ) + if from_refuge_to_target_path != []: + return ActionMove(path) + + refuges.remove(refuge_entity_id) + if current_size == len(refuges): + break + current_size = len(refuges) + else: + break + + return ActionMove(first_result) if first_result != [] else None + + def _get_rescue_action( + self, police_entity: PoliceForceEntity, road: Road + ) -> Optional[Action]: + blockades = set( + [] + if road.get_blockades() is None + else [ + cast(Blockade, self.world_info.get_entity(blockade_entity_id)) + for blockade_entity_id in road.get_blockades() + ] + ) + agent_entities = set( + self.world_info.get_entities_of_types( + [AmbulanceTeamEntity, FireBrigadeEntity] + ) + ) + + police_x = police_entity.get_x() + police_y = police_entity.get_y() + min_distance = sys.float_info.max + move_action: Optional[ActionMove] = None + + for agent_entity in agent_entities: + human = cast(Human, agent_entity) + if human.get_position().get_value() != road.get_id().get_value(): + continue + + human_x = human.get_x() + human_y = human.get_y() + action_clear: Optional[ActionClear | ActionClearArea] = None + clear_blockade: Optional[Blockade] = None + for blockade in blockades: + if not self._is_inside(human_x, human_y, blockade.get_apexes()): + continue + + distance = self._get_distance(police_x, police_y, human_x, human_y) + if self._is_intersecting_area( + police_x, police_y, human_x, human_y, road + ): + action = self._get_intersect_edge_action( + police_x, police_y, human_x, human_y, road + ) + if action is None: + continue + if isinstance(action, ActionClear): + if action_clear is None: + action_clear = action + clear_blockade = blockade + continue + + if clear_blockade is not None: + if self._is_intersecting_blockades( + blockade, clear_blockade + ): + return ActionClear(clear_blockade) + + another_distance = self.world_info.get_distance( + police_entity.get_id(), clear_blockade.get_id() + ) + blockade_distance = self.world_info.get_distance( + police_entity.get_id(), blockade.get_id() + ) + if blockade_distance < another_distance: + return action + + return action_clear + elif isinstance(action, ActionMove) and distance < min_distance: + min_distance = distance + move_action = action + + elif self._is_intersecting_blockade( + police_x, police_y, human_x, human_y, blockade + ): + vector = self._scale_clear( + self._get_vector(police_x, police_y, human_x, human_y) + ) + clear_x = int(police_x + vector[0]) + clear_y = int(police_y + vector[1]) + + vector = self._scale_back_clear(vector) + start_x = int(police_x + vector[0]) + start_y = int(police_y + vector[1]) + + if self._is_intersecting_blockade( + start_x, start_y, clear_x, clear_y, blockade + ): + if action_clear is None: + action_clear = ActionClearArea(clear_x, clear_y) + clear_blockade = blockade + else: + if clear_blockade is not None: + if self._is_intersecting_blockades( + blockade, clear_blockade + ): + return ActionClear(clear_blockade) + + distance1 = self.world_info.get_distance( + police_entity.get_id(), clear_blockade.get_id() + ) + distance2 = self.world_info.get_distance( + police_entity.get_id(), blockade.get_id() + ) + if distance1 > distance2: + return ActionClearArea(clear_x, clear_y) + + return action_clear + + elif distance < min_distance: + min_distance = distance + move_action = ActionMove([road.get_id()], human_x, human_y) + + if action_clear is not None: + return action_clear + + return move_action + + def _is_inside(self, x: float, y: float, apexes: list[int]) -> bool: + point = Point(x, y) + polygon = Polygon( + [(apexes[i], apexes[i + 1]) for i in range(0, len(apexes), 2)] + ) + return polygon.contains(point) + + def _get_distance(self, x1: float, y1: float, x2: float, y2: float) -> float: + return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5 + + def _is_intersecting_area( + self, agent_x: float, agent_y: float, point_x: float, point_y: float, area: Area + ) -> bool: + for edge in area.get_edges(): + start_x = edge.get_start_x() + start_y = edge.get_start_y() + end_x = edge.get_end_x() + end_y = edge.get_end_y() + + line1 = LineString([(agent_x, agent_y), (point_x, point_y)]) + line2 = LineString([(start_x, start_y), (end_x, end_y)]) + if line1.intersects(line2): + mid_x = (start_x + end_x) / 2.0 + mid_y = (start_y + end_y) / 2.0 + if not self._equals_point( + agent_x, agent_y, mid_x, mid_y, 1000 + ) and not self._equals_point(point_x, point_y, mid_x, mid_y, 1000): + return True + + return False + + def _equals_point( + self, x1: float, y1: float, x2: float, y2: float, range: float + ) -> bool: + return (x2 - range < x1 and x1 < x2 + range) and ( + y2 - range < y1 and y1 < y2 + range + ) + + def _get_intersect_edge_action( + self, agent_x: float, agent_y: float, point_x: float, point_y: float, road: Road + ) -> Action: + move_points = self._get_move_points(road) + best_point: Optional[tuple[float, float]] = None + best_distance = sys.float_info.max + for point in move_points: + if not self._is_intersecting_area( + agent_x, agent_y, point[0], point[1], road + ): + if not self._is_intersecting_area( + point_x, point_y, point[0], point[1], road + ): + distance = self._get_distance(point_x, point_y, point[0], point[1]) + if distance < best_distance: + best_point = point + best_distance = distance + + if best_point is not None: + bp_x, bp_y = best_point + if road.get_blockades() is None: + return ActionMove([road.get_id()], int(bp_x), int(bp_y)) + + action_clear: Optional[ActionClearArea] = None + clear_blockade: Optional[Blockade] = None + action_move: Optional[ActionMove] = None + + vector = self._scale_clear(self._get_vector(agent_x, agent_y, bp_x, bp_y)) + clear_x = int(agent_x + vector[0]) + clear_y = int(agent_x + vector[1]) + + vector = self._scale_back_clear(vector) + start_x = int(agent_x + vector[0]) + start_y = int(agent_y + vector[1]) + + for blockade in self.world_info.get_bloackades(road): + if self._is_intersecting_blockade( + start_x, start_y, bp_x, bp_y, blockade + ): + if self._is_intersecting_blockade( + start_x, start_y, clear_x, clear_y, blockade + ): + if action_clear is None: + action_clear = ActionClearArea(clear_x, clear_y) + clear_blockade = blockade + else: + if ( + clear_blockade is not None + and self._is_intersecting_blockades( + blockade, clear_blockade + ) + ): + return ActionClear(clear_blockade) + return action_clear + elif action_move is None: + action_move = ActionMove([road.get_id()], int(bp_x), int(bp_y)) + + if action_clear is not None: + return action_clear + if action_move is not None: + return action_move + + action = self._get_area_clear_action( + cast(PoliceForceEntity, self.agent_info.get_myself()), road + ) + if action is None: + action = ActionMove([road.get_id()], int(point_x), int(point_y)) + return action + + def _get_move_points(self, road: Road) -> set[tuple[float, float]]: + points: Optional[set[tuple[float, float]]] = self._move_point_cache.get( + road.get_id() + ) + if points is None: + points = set() + apex = road.get_apexes() + for i in range(0, len(apex), 2): + for j in range(i + 2, len(apex), 2): + mid_x = (apex[i] + apex[j]) / 2.0 + mid_y = (apex[i + 1] + apex[j + 1]) / 2.0 + if self._is_inside(mid_x, mid_y, apex): + points.add((mid_x, mid_y)) + + for edge in road.get_edges(): + mid_x = (edge.get_start_x() + edge.get_end_x()) / 2.0 + mid_y = (edge.get_start_y() + edge.get_end_y()) / 2.0 + points.remove((mid_x, mid_y)) + + self._move_point_cache[road.get_id()] = points + + return points + + def _get_vector( + self, from_x: float, from_y: float, to_x: float, to_y: float + ) -> tuple[float, float]: + return (to_x - from_x, to_y - from_y) + + def _scale_clear(self, vector: tuple[float, float]) -> tuple[float, float]: + length = 1.0 / math.hypot(vector[0], vector[1]) + return ( + vector[0] * length * self._clear_distance, + vector[1] * length * self._clear_distance, + ) + + def _scale_back_clear(self, vector: tuple[float, float]) -> tuple[float, float]: + length = 1.0 / math.hypot(vector[0], vector[1]) + return (vector[0] * length * -510, vector[1] * length * -510) + + def _is_intersecting_blockade( + self, + agent_x: float, + agent_y: float, + point_x: float, + point_y: float, + blockade: Blockade, + ) -> bool: + apexes = blockade.get_apexes() + for i in range(0, len(apexes) - 3, 2): + line1 = LineString( + [(apexes[i], apexes[i + 1]), (apexes[i + 2], apexes[i + 3])] + ) + line2 = LineString([(agent_x, agent_y), (point_x, point_y)]) + if line1.intersects(line2): + return True + return False + + def _is_intersecting_blockades( + self, blockade1: Blockade, blockade2: Blockade + ) -> bool: + apexes1 = blockade1.get_apexes() + apexes2 = blockade2.get_apexes() + for i in range(0, len(apexes1) - 2, 2): + for j in range(0, len(apexes2) - 2, 2): + line1 = LineString( + [(apexes1[i], apexes1[i + 1]), (apexes1[i + 2], apexes1[i + 3])] + ) + line2 = LineString( + [(apexes2[j], apexes2[j + 1]), (apexes2[j + 2], apexes2[j + 3])] + ) + if line1.intersects(line2): + return True + + for i in range(0, len(apexes1) - 2, 2): + line1 = LineString( + [(apexes1[i], apexes1[i + 1]), (apexes1[i + 2], apexes1[i + 3])] + ) + line2 = LineString([(apexes2[-2], apexes2[-1]), (apexes2[0], apexes2[1])]) + if line1.intersects(line2): + return True + + for i in range(0, len(apexes1) - 2, 2): + line1 = LineString([(apexes1[-2], apexes1[-1]), (apexes1[0], apexes1[1])]) + line2 = LineString( + [(apexes2[i], apexes2[i + 1]), (apexes2[i + 2], apexes2[i + 3])] + ) + if line1.intersects(line2): + return True + + return False + + def _get_area_clear_action( + self, police_entity: PoliceForceEntity, road: Road + ) -> Optional[Action]: + if road.get_blockades() == []: + return None + + blockades = set(self.world_info.get_bloackades(road)) + min_distance = sys.float_info.max + clear_blockade: Optional[Blockade] = None + for blockade in blockades: + for another in blockades: + if blockade == another: + continue + + if self._is_intersecting_blockades(blockade, another): + distance1 = self.world_info.get_distance( + police_entity.get_id(), blockade.get_id() + ) + distance2 = self.world_info.get_distance( + police_entity.get_id(), another.get_id() + ) + if distance1 <= distance2 and distance1 < min_distance: + min_distance = distance1 + clear_blockade = blockade + elif distance2 < min_distance: + min_distance = distance2 + clear_blockade = another + + if clear_blockade is not None: + if min_distance < self._clear_distance: + return ActionClear(clear_blockade) + else: + return ActionMove( + [police_entity.get_position()], + clear_blockade.get_x(), + clear_blockade.get_y(), + ) + + agent_x = police_entity.get_x() + agent_y = police_entity.get_y() + clear_blockade = None + min_point_distance = sys.float_info.max + clear_x = 0 + clear_y = 0 + for blockade in blockades: + apexes = blockade.get_apexes() + for i in range(0, len(apexes) - 2, 2): + distance = self._get_distance( + agent_x, agent_y, apexes[i], apexes[i + 1] + ) + if distance < min_point_distance: + clear_blockade = blockade + min_point_distance = distance + clear_x = apexes[i] + clear_y = apexes[i + 1] + + if clear_blockade is not None: + if min_point_distance < self._clear_distance: + vector = self._scale_clear( + self._get_vector(agent_x, agent_y, clear_x, clear_y) + ) + clear_x = int(agent_x + vector[0]) + clear_y = int(agent_y + vector[1]) + return ActionClearArea(clear_x, clear_y) + return ActionMove([police_entity.get_position()], clear_x, clear_y) + + return None + + def _index_of(self, list: list[EntityID], x: EntityID) -> int: + return list.index(x) if x in list else -1 + + def _get_neighbour_position_action( + self, police_entity: PoliceForceEntity, target: Area + ) -> Optional[Action]: + agent_x = police_entity.get_x() + agent_y = police_entity.get_y() + position = police_entity.get_position() + edge = target.get_edge_to(position) + if edge is None: + return None + + if isinstance(position, Road): + road = cast(Road, position) + if road.get_blockades() != []: + mid_x = (edge.get_start_x() + edge.get_end_x()) / 2.0 + mid_y = (edge.get_start_y() + edge.get_end_y()) / 2.0 + if self._is_intersecting_area(agent_x, agent_y, mid_x, mid_y, road): + return self._get_intersect_edge_action( + agent_x, agent_y, mid_x, mid_y, road + ) + + action_clear: Optional[ActionClear | ActionClearArea] = None + clear_blockade: Optional[Blockade] = None + action_move: Optional[ActionMove] = None + + vector = self._scale_clear( + self._get_vector(agent_x, agent_y, mid_x, mid_y) + ) + clear_x = int(agent_x + vector[0]) + clear_y = int(agent_y + vector[1]) + + vector = self._scale_back_clear(vector) + start_x = int(agent_x + vector[0]) + start_y = int(agent_y + vector[1]) + + for blockade in self.world_info.get_bloackades(road): + if self._is_intersecting_blockade( + start_x, start_y, mid_x, mid_y, blockade + ): + if self._is_intersecting_blockade( + start_x, start_y, clear_x, clear_y, blockade + ): + if action_clear is None: + action_clear = ActionClearArea(clear_x, clear_y) + clear_blockade = blockade + if self._equals_point( + self._old_clear_x, + self._old_clear_y, + clear_x, + clear_y, + 1000, + ): + if self.count >= self._forced_move: + self.count = 0 + return ActionMove( + [road.get_id()], int(clear_x), int(clear_y) + ) + self.count += 1 + + self._old_clear_x = clear_x + self._old_clear_y = clear_y + else: + if clear_blockade is not None: + if self._is_intersecting_blockades( + blockade, clear_blockade + ): + return ActionClear(clear_blockade) + + return action_clear + elif action_move is None: + action_move = ActionMove( + [road.get_id()], int(mid_x), int(mid_y) + ) + + if action_clear is not None: + return action_clear + if action_move is not None: + return action_move + + if isinstance(target, Road): + road = cast(Road, target) + if road.get_blockades() == []: + return ActionMove([position, target.get_id()]) + + target_blockade: Optional[Blockade] = None + min_point_distance = sys.float_info.max + clear_x = 0 + clear_y = 0 + for blockade in self.world_info.get_bloackades(road): + apexes = blockade.get_apexes() + for i in range(0, len(apexes) - 2, 2): + distance = self._get_distance( + agent_x, agent_y, apexes[i], apexes[i + 1] + ) + if distance < min_point_distance: + target_blockade = blockade + min_point_distance = distance + clear_x = apexes[i] + clear_y = apexes[i + 1] + + if ( + target_blockade is not None + and min_point_distance < self._clear_distance + ): + vector = self._scale_clear( + self._get_vector(agent_x, agent_y, clear_x, clear_y) + ) + clear_x = int(agent_x + vector[0]) + clear_y = int(agent_y + vector[1]) + if self._equals_point( + self._old_clear_x, self._old_clear_y, clear_x, clear_y, 1000 + ): + if self.count >= self._forced_move: + self.count = 0 + return ActionMove([road.get_id()], clear_x, clear_y) + self.count += 1 + + self._old_clear_x = clear_x + self._old_clear_y = clear_y + return ActionClearArea(clear_x, clear_y) + + return ActionMove([position, target.get_id()]) diff --git a/adf_core_python/implement/action/default_extend_action_move.py b/adf_core_python/implement/action/default_extend_action_move.py index d888215..46a2c6c 100644 --- a/adf_core_python/implement/action/default_extend_action_move.py +++ b/adf_core_python/implement/action/default_extend_action_move.py @@ -95,7 +95,7 @@ def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: return self - def calc(self) -> ExtendAction: + def calculate(self) -> ExtendAction: self.result = None agent: Human = cast(Human, self.agent_info.get_myself()) diff --git a/adf_core_python/implement/action/default_extend_action_rescue.py b/adf_core_python/implement/action/default_extend_action_rescue.py new file mode 100644 index 0000000..845ad18 --- /dev/null +++ b/adf_core_python/implement/action/default_extend_action_rescue.py @@ -0,0 +1,149 @@ +from typing import cast, Optional + +from rcrs_core.entities.area import Area +from rcrs_core.entities.blockade import Blockade +from rcrs_core.entities.fireBrigade import FireBrigadeEntity +from rcrs_core.entities.human import Human +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.action import Action +from adf_core_python.core.agent.action.ambulance.action_rescue import ActionRescue +from adf_core_python.core.agent.action.common.action_move import ActionMove +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo, Mode +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.action.extend_action import ExtendAction +from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning + + +class DefaultExtendActionRescue(ExtendAction): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._kernel_time = None + self._target_entity_id = None + self._threshold_rest = develop_data.get_value( + "adf_core_python.implement.action.DefaultExtendActionRescue.rest", 100 + ) + + match self.scenario_info.get_mode(): + case Mode.NON_PRECOMPUTE: + self._path_planning = cast( + PathPlanning, + self.module_manager.get_module( + "DefaultExtendActionMove.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + case Mode.PRECOMPUTATION: + pass + case Mode.PRECOMPUTED: + pass + + def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: + super().precompute(precompute_data) + if self.get_count_precompute() >= 2: + return self + self._path_planning.precompute(precompute_data) + self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + return self + + def resume(self, precompute_data: PrecomputeData) -> ExtendAction: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + self._path_planning.resume(precompute_data) + self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + return self + + def prepare(self) -> ExtendAction: + super().prepare() + if self.get_count_prepare() >= 2: + return self + self._path_planning.prepare() + self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + return self + + def update_info(self, message_manager: MessageManager) -> ExtendAction: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self + self._path_planning.update_info(message_manager) + return self + + def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: + self._target_entity_id = None + if target_entity_id is not None: + entity = self.world_info.get_entity(target_entity_id) + if isinstance(entity, Human) or isinstance(entity, Area): + self._target_entity_id = target_entity_id + return self + return self + + def calculate(self) -> ExtendAction: + self.result = None + agent = cast(FireBrigadeEntity, self.agent_info.get_myself()) + + if self._target_entity_id is not None: + self.result = self._calc_rescue( + agent, self._path_planning, self._target_entity_id + ) + + return self + + def _calc_rescue( + self, + agent: FireBrigadeEntity, + path_planning: PathPlanning, + target_entity_id: EntityID, + ) -> Optional[Action]: + target_entity = self.world_info.get_entity(target_entity_id) + if target_entity is None: + return None + + agent_position_entity_id = agent.get_position() + if isinstance(target_entity, Human): + human = cast(Human, target_entity) + if human.get_hp() == 0: + return None + + target_position_entity_id = human.get_position() + if ( + agent_position_entity_id.get_value() + == target_position_entity_id.get_value() + ): + buriedness = human.get_buriedness() + if buriedness is not None and buriedness > 0: + return ActionRescue(target_entity_id) + else: + path = path_planning.get_path( + agent_position_entity_id, target_position_entity_id + ) + if path != []: + return ActionMove(path) + + return None + + if isinstance(target_entity, Blockade): + blockade = cast(Blockade, target_entity) + target_entity = self.world_info.get_entity(blockade.get_position()) + if isinstance(target_entity, Area): + path = self._path_planning.get_path( + agent_position_entity_id, target_entity.get_id() + ) + if path != []: + return ActionMove(path) + + return None diff --git a/adf_core_python/implement/action/default_extend_action_transport.py b/adf_core_python/implement/action/default_extend_action_transport.py index fec221f..a84b859 100644 --- a/adf_core_python/implement/action/default_extend_action_transport.py +++ b/adf_core_python/implement/action/default_extend_action_transport.py @@ -100,7 +100,7 @@ def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: return self - def calc(self) -> ExtendAction: + def calculate(self) -> ExtendAction: self._result = None agent: AmbulanceTeamEntity = cast( AmbulanceTeamEntity, self.agent_info.get_myself() diff --git a/adf_core_python/implement/module/complex/default_road_detector.py b/adf_core_python/implement/module/complex/default_road_detector.py index e4514ac..adad2d8 100644 --- a/adf_core_python/implement/module/complex/default_road_detector.py +++ b/adf_core_python/implement/module/complex/default_road_detector.py @@ -37,7 +37,7 @@ def __init__( PathPlanning, module_manager.get_module( "DefaultRoadDetector.PathPlanning", - "adf_core_python.implement.module.algorithm.DijkstraPathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", ), ) @@ -120,7 +120,7 @@ def update_info(self, message_manager: MessageManager) -> RoadDetector: return self - def calc(self) -> RoadDetector: + def calculate(self) -> RoadDetector: if self._result is None: position_entity_id: EntityID = self._agent_info.get_position_entity_id() if position_entity_id in self._target_areas: @@ -154,5 +154,5 @@ def calc(self) -> RoadDetector: return self - def get_target(self) -> Optional[EntityID]: + def get_target_entity_id(self) -> Optional[EntityID]: return self._result diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 5d7e4bb..3154107 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -127,7 +127,7 @@ def think( if target_entity_id is not None: action = ( self._action_transport.set_target_entity_id(target_entity_id) - .calc() + .calculate() .get_action() ) if action is not None: @@ -141,7 +141,7 @@ def think( if target_entity_id is not None: action = ( self._action_ext_move.set_target_entity_id(target_entity_id) - .calc() + .calculate() .get_action() ) if action is not None: diff --git a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py index f9eabd0..d421ac6 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py @@ -30,33 +30,43 @@ def initialize( develop_data: DevelopData, ) -> None: # world_info.index_class() + super().initialize( + agent_info, + world_info, + scenario_info, + module_manager, + precompute_data, + message_manager, + develop_data, + ) + match scenario_info.get_mode(): case Mode.NON_PRECOMPUTE: self._search: Search = cast( Search, module_manager.get_module( "DefaultTacticsFireBrigade.Search", - "adf_core_python.impl.module.complex.DefaultSearch", + "adf_core_python.core.component.module.complex.search.Search", ), ) self._human_detector: HumanDetector = cast( HumanDetector, module_manager.get_module( "DefaultTacticsFireBrigade.HumanDetector", - "adf_core_python.impl.module.complex.DefaultHumanDetector", + "adf_core_python.core.component.module.complex.human_detector.HumanDetector", ), ) - self._action_fire_rescue = module_manager.get_extend_action( + self._action_rescue = module_manager.get_extend_action( "DefaultTacticsFireBrigade.ExtendActionRescue", - "adf_core_python.implement.action.DefaultExtendActionRescue", + "adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue", ) self._action_ext_move = module_manager.get_extend_action( "DefaultTacticsAmbulanceTeam.ExtendActionMove", - "adf_core_python.implement.action.DefaultExtendActionMove", + "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", ) self.register_module(self._search) self.register_module(self._human_detector) - self.register_action(self._action_fire_rescue) + self.register_action(self._action_rescue) self.register_action(self._action_ext_move) def precompute( @@ -111,23 +121,32 @@ def think( entity_id = agent_info.get_entity_id() # noqa: F841 target_entity_id = self._human_detector.calculate().get_target_entity_id() + self._logger.debug( + f"human detector target_entity_id: {target_entity_id}", + time=agent_info.get_time(), + ) if target_entity_id is not None: action = ( - self._action_fire_rescue.set_target_entity_id(target_entity_id) - .calc() + self._action_rescue.set_target_entity_id(target_entity_id) + .calculate() .get_action() ) if action is not None: + self._logger.debug(f"action: {action}", time=agent_info.get_time()) return action target_entity_id = self._search.calculate().get_target_entity_id() + self._logger.debug( + f"search target_entity_id: {target_entity_id}", time=agent_info.get_time() + ) if target_entity_id is not None: action = ( self._action_ext_move.set_target_entity_id(target_entity_id) - .calc() + .calculate() .get_action() ) if action is not None: + self._logger.debug(f"action: {action}", time=agent_info.get_time()) return action return ActionRest() diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py index 3cb08cc..4f8bb89 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -30,9 +30,18 @@ def initialize( develop_data: DevelopData, ) -> None: # world_info.index_class() - self._clear_distance = int( - scenario_info.get_value("clear.repair.distance", "null") + super().initialize( + agent_info, + world_info, + scenario_info, + module_manager, + precompute_data, + message_manager, + develop_data, ) + # self._clear_distance = int( + # scenario_info.get_value("clear.repair.distance", "null") + # ) match scenario_info.get_mode(): case Mode.NON_PRECOMPUTE: @@ -40,23 +49,23 @@ def initialize( Search, module_manager.get_module( "DefaultTacticsPoliceForce.Search", - "adf_core_python.implement.module.complex.DefaultSearch", + "adf_core_python.core.component.module.complex.search.Search", ), ) self._road_detector: RoadDetector = cast( RoadDetector, module_manager.get_module( "DefaultTacticsPoliceForce.RoadDetector", - "adf_core_python.implement.module.complex.DefaultRoadDetector", + "adf_core_python.core.component.module.complex.road_detector.RoadDetector", ), ) self._action_ext_clear = module_manager.get_extend_action( "DefaultTacticsPoliceForce.ExtendActionClear", - "adf_core_python.implement.action.DefaultExtendActionClear", + "adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear", ) self._action_ext_move = module_manager.get_extend_action( "DefaultTacticsPoliceForce.ExtendActionMove", - "adf_core_python.implement.action.DefaultExtendActionMove", + "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", ) self.register_module(self._search) self.register_module(self._road_detector) @@ -115,23 +124,32 @@ def think( entity_id = agent_info.get_entity_id() # noqa: F841 target_entity_id = self._road_detector.calculate().get_target_entity_id() + self._logger.debug( + f"road detector target_entity_id: {target_entity_id}", + time=agent_info.get_time(), + ) if target_entity_id is not None: action = ( self._action_ext_clear.set_target_entity_id(target_entity_id) - .calc() + .calculate() .get_action() ) if action is not None: + self._logger.debug(f"action: {action}", time=agent_info.get_time()) return action target_entity_id = self._search.calculate().get_target_entity_id() + self._logger.debug( + f"search target_entity_id: {target_entity_id}", time=agent_info.get_time() + ) if target_entity_id is not None: action = ( self._action_ext_move.set_target_entity_id(target_entity_id) - .calc() + .calculate() .get_action() ) if action is not None: + self._logger.debug(f"action: {action}", time=agent_info.get_time()) return action return ActionRest() diff --git a/adf_core_python/main.py b/adf_core_python/main.py index 8ba028c..36151d1 100644 --- a/adf_core_python/main.py +++ b/adf_core_python/main.py @@ -27,7 +27,7 @@ def __init__(self) -> None: ) parser.add_argument( "-a", - "--ambulance", + "--ambulanceteam", type=int, default=-1, help="number of ambulance agents(Default: -1 means all ambulance)", @@ -65,9 +65,9 @@ def __init__(self) -> None: self.config = Config() self.config.set_value(ConfigKey.KEY_KERNEL_HOST, args.host) self.config.set_value(ConfigKey.KEY_KERNEL_PORT, args.port) - self.config.set_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, args.ambulance) - self.config.set_value(ConfigKey.KEY_FIRE_STATION_COUNT, args.firebrigade) - self.config.set_value(ConfigKey.KEY_POLICE_OFFICE_COUNT, args.policeforce) + self.config.set_value(ConfigKey.KEY_AMBULANCE_TEAM_COUNT, args.ambulanceteam) + self.config.set_value(ConfigKey.KEY_FIRE_BRIGADE_COUNT, args.firebrigade) + self.config.set_value(ConfigKey.KEY_POLICE_FORCE_COUNT, args.policeforce) self.config.set_value(ConfigKey.KEY_PRECOMPUTE, args.precompute) self.config.set_value(ConfigKey.KEY_DEBUG_FLAG, args.debug) self.logger.info(f"Config: {self.config}") diff --git a/config/module.yaml b/config/module.yaml index 3047c7a..a265bde 100644 --- a/config/module.yaml +++ b/config/module.yaml @@ -8,22 +8,22 @@ DefaultTacticsAmbulanceTeam: CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout # ## DefaultTacticsFireBrigade -# DefaultTacticsFireBrigade: -# HumanDetector: sample_team.module.complex.SampleHumanDetector -# Search: sample_team.module.complex.SampleSearch -# ExtendActionRescue: adf_core_python.implement.action.DefaultExtendActionRescue -# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove -# CommandExecutorFire: adf_core_python.implement.centralized.DefaultCommandExecutorFire -# CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout +DefaultTacticsFireBrigade: + HumanDetector: adf_core_python.implement.module.complex.default_human_detector.DefaultHumanDetector + Search: adf_core_python.implement.module.complex.default_search.DefaultSearch + ExtendActionRescue: adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue + ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove + CommandExecutorFire: adf_core_python.implement.centralized.DefaultCommandExecutorFire + CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout # ## DefaultTacticsPoliceForce -# DefaultTacticsPoliceForce: -# RoadDetector: sample_team.module.complex.SampleRoadDetector -# Search: sample_team.module.complex.SampleSearch -# ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear -# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove -# CommandExecutorPolice: adf_core_python.implement.centralized.DefaultCommandExecutorPolice -# CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScoutPolice +DefaultTacticsPoliceForce: + RoadDetector: adf_core_python.implement.module.complex.default_road_detector.DefaultRoadDetector + Search: adf_core_python.implement.module.complex.default_search.DefaultSearch + ExtendActionClear: adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear + ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove + CommandExecutorPolice: adf_core_python.implement.centralized.DefaultCommandExecutorPolice + CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScoutPolice # ## DefaultTacticsAmbulanceCentre # DefaultTacticsAmbulanceCentre: diff --git a/poetry.lock b/poetry.lock index 7ae97d6..0524cca 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "colorama" @@ -35,49 +35,54 @@ files = [ [[package]] name = "mslex" -version = "1.2.0" +version = "1.3.0" description = "shlex for windows" optional = false python-versions = ">=3.5" files = [ - {file = "mslex-1.2.0-py3-none-any.whl", hash = "sha256:c68ec637485ee3544c5847c1b4e78b02940b32708568fb1d8715491815aa2341"}, - {file = "mslex-1.2.0.tar.gz", hash = "sha256:79e2abc5a129dd71cdde58a22a2039abb7fa8afcbac498b723ba6e9b9fbacc14"}, + {file = "mslex-1.3.0-py3-none-any.whl", hash = "sha256:c7074b347201b3466fc077c5692fbce9b5f62a63a51f537a53fbbd02eff2eea4"}, + {file = "mslex-1.3.0.tar.gz", hash = "sha256:641c887d1d3db610eee2af37a8e5abda3f70b3006cdfd2d0d29dc0d1ae28a85d"}, ] [[package]] name = "mypy" -version = "1.11.2" +version = "1.13.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, - {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, - {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, - {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, - {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, - {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, - {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, - {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, - {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, - {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, - {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, - {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, - {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, - {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, - {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, - {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, - {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, - {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, - {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, - {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, - {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, - {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, - {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, - {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, - {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, - {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, - {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, ] [package.dependencies] @@ -86,6 +91,7 @@ typing-extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] @@ -191,22 +197,22 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "protobuf" -version = "5.28.2" +version = "5.28.3" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-5.28.2-cp310-abi3-win32.whl", hash = "sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d"}, - {file = "protobuf-5.28.2-cp310-abi3-win_amd64.whl", hash = "sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132"}, - {file = "protobuf-5.28.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7"}, - {file = "protobuf-5.28.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f"}, - {file = "protobuf-5.28.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f"}, - {file = "protobuf-5.28.2-cp38-cp38-win32.whl", hash = "sha256:87317e9bcda04a32f2ee82089a204d3a2f0d3c8aeed16568c7daf4756e4f1fe0"}, - {file = "protobuf-5.28.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0ea0123dac3399a2eeb1a1443d82b7afc9ff40241433296769f7da42d142ec3"}, - {file = "protobuf-5.28.2-cp39-cp39-win32.whl", hash = "sha256:ca53faf29896c526863366a52a8f4d88e69cd04ec9571ed6082fa117fac3ab36"}, - {file = "protobuf-5.28.2-cp39-cp39-win_amd64.whl", hash = "sha256:8ddc60bf374785fb7cb12510b267f59067fa10087325b8e1855b898a0d81d276"}, - {file = "protobuf-5.28.2-py3-none-any.whl", hash = "sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece"}, - {file = "protobuf-5.28.2.tar.gz", hash = "sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0"}, + {file = "protobuf-5.28.3-cp310-abi3-win32.whl", hash = "sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24"}, + {file = "protobuf-5.28.3-cp310-abi3-win_amd64.whl", hash = "sha256:91fba8f445723fcf400fdbe9ca796b19d3b1242cd873907979b9ed71e4afe868"}, + {file = "protobuf-5.28.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a3f6857551e53ce35e60b403b8a27b0295f7d6eb63d10484f12bc6879c715687"}, + {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:3fa2de6b8b29d12c61911505d893afe7320ce7ccba4df913e2971461fa36d584"}, + {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135"}, + {file = "protobuf-5.28.3-cp38-cp38-win32.whl", hash = "sha256:3e6101d095dfd119513cde7259aa703d16c6bbdfae2554dfe5cfdbe94e32d548"}, + {file = "protobuf-5.28.3-cp38-cp38-win_amd64.whl", hash = "sha256:27b246b3723692bf1068d5734ddaf2fccc2cdd6e0c9b47fe099244d80200593b"}, + {file = "protobuf-5.28.3-cp39-cp39-win32.whl", hash = "sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535"}, + {file = "protobuf-5.28.3-cp39-cp39-win_amd64.whl", hash = "sha256:70585a70fc2dd4818c51287ceef5bdba6387f88a578c86d47bb34669b5552c36"}, + {file = "protobuf-5.28.3-py3-none-any.whl", hash = "sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed"}, + {file = "protobuf-5.28.3.tar.gz", hash = "sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b"}, ] [[package]] @@ -336,7 +342,7 @@ rtree = "*" type = "git" url = "https://github.com/adf-python/rcrs-core-python" reference = "HEAD" -resolved_reference = "bfcdcff39e79c71ef953d5f282a4fedd507a5084" +resolved_reference = "9dfa01ee8d8ccc9cb5e84fdf21c8c403e4f28ed3" [[package]] name = "rtree" @@ -405,6 +411,11 @@ files = [ {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f60021ec1574e56632be2a36b946f8143bf4e5e6af4a06d85281adc22938e0dd"}, {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:394397841449853c2290a32050382edaec3da89e35b3e03d6cc966aebc6a8ae6"}, {file = "scikit_learn-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:57cc1786cfd6bd118220a92ede80270132aa353647684efa385a74244a41e3b1"}, + {file = "scikit_learn-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9a702e2de732bbb20d3bad29ebd77fc05a6b427dc49964300340e4c9328b3f5"}, + {file = "scikit_learn-1.5.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:b0768ad641981f5d3a198430a1d31c3e044ed2e8a6f22166b4d546a5116d7908"}, + {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:178ddd0a5cb0044464fc1bfc4cca5b1833bfc7bb022d70b05db8530da4bb3dd3"}, + {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7284ade780084d94505632241bf78c44ab3b6f1e8ccab3d2af58e0e950f9c12"}, + {file = "scikit_learn-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:b7b0f9a0b1040830d38c39b91b3a44e1b643f4b36e36567b80b7c6bd2202a27f"}, {file = "scikit_learn-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:757c7d514ddb00ae249832fe87100d9c73c6ea91423802872d9e74970a0e40b9"}, {file = "scikit_learn-1.5.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:52788f48b5d8bca5c0736c175fa6bdaab2ef00a8f536cda698db61bd89c551c1"}, {file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:643964678f4b5fbdc95cbf8aec638acc7aa70f5f79ee2cdad1eec3df4ba6ead8"}, @@ -478,6 +489,64 @@ dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodest doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +[[package]] +name = "shapely" +version = "2.0.6" +description = "Manipulation and analysis of geometric objects" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shapely-2.0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29a34e068da2d321e926b5073539fd2a1d4429a2c656bd63f0bd4c8f5b236d0b"}, + {file = "shapely-2.0.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c84c3f53144febf6af909d6b581bc05e8785d57e27f35ebaa5c1ab9baba13b"}, + {file = "shapely-2.0.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad2fae12dca8d2b727fa12b007e46fbc522148a584f5d6546c539f3464dccde"}, + {file = "shapely-2.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3304883bd82d44be1b27a9d17f1167fda8c7f5a02a897958d86c59ec69b705e"}, + {file = "shapely-2.0.6-cp310-cp310-win32.whl", hash = "sha256:3ec3a0eab496b5e04633a39fa3d5eb5454628228201fb24903d38174ee34565e"}, + {file = "shapely-2.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:28f87cdf5308a514763a5c38de295544cb27429cfa655d50ed8431a4796090c4"}, + {file = "shapely-2.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aeb0f51a9db176da9a30cb2f4329b6fbd1e26d359012bb0ac3d3c7781667a9e"}, + {file = "shapely-2.0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a7a78b0d51257a367ee115f4d41ca4d46edbd0dd280f697a8092dd3989867b2"}, + {file = "shapely-2.0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f32c23d2f43d54029f986479f7c1f6e09c6b3a19353a3833c2ffb226fb63a855"}, + {file = "shapely-2.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3dc9fb0eb56498912025f5eb352b5126f04801ed0e8bdbd867d21bdbfd7cbd0"}, + {file = "shapely-2.0.6-cp311-cp311-win32.whl", hash = "sha256:d93b7e0e71c9f095e09454bf18dad5ea716fb6ced5df3cb044564a00723f339d"}, + {file = "shapely-2.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:c02eb6bf4cfb9fe6568502e85bb2647921ee49171bcd2d4116c7b3109724ef9b"}, + {file = "shapely-2.0.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cec9193519940e9d1b86a3b4f5af9eb6910197d24af02f247afbfb47bcb3fab0"}, + {file = "shapely-2.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83b94a44ab04a90e88be69e7ddcc6f332da7c0a0ebb1156e1c4f568bbec983c3"}, + {file = "shapely-2.0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:537c4b2716d22c92036d00b34aac9d3775e3691f80c7aa517c2c290351f42cd8"}, + {file = "shapely-2.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fea108334be345c283ce74bf064fa00cfdd718048a8af7343c59eb40f59726"}, + {file = "shapely-2.0.6-cp312-cp312-win32.whl", hash = "sha256:42fd4cd4834747e4990227e4cbafb02242c0cffe9ce7ef9971f53ac52d80d55f"}, + {file = "shapely-2.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:665990c84aece05efb68a21b3523a6b2057e84a1afbef426ad287f0796ef8a48"}, + {file = "shapely-2.0.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:42805ef90783ce689a4dde2b6b2f261e2c52609226a0438d882e3ced40bb3013"}, + {file = "shapely-2.0.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6d2cb146191a47bd0cee8ff5f90b47547b82b6345c0d02dd8b25b88b68af62d7"}, + {file = "shapely-2.0.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3fdef0a1794a8fe70dc1f514440aa34426cc0ae98d9a1027fb299d45741c381"}, + {file = "shapely-2.0.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c665a0301c645615a107ff7f52adafa2153beab51daf34587170d85e8ba6805"}, + {file = "shapely-2.0.6-cp313-cp313-win32.whl", hash = "sha256:0334bd51828f68cd54b87d80b3e7cee93f249d82ae55a0faf3ea21c9be7b323a"}, + {file = "shapely-2.0.6-cp313-cp313-win_amd64.whl", hash = "sha256:d37d070da9e0e0f0a530a621e17c0b8c3c9d04105655132a87cfff8bd77cc4c2"}, + {file = "shapely-2.0.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fa7468e4f5b92049c0f36d63c3e309f85f2775752e076378e36c6387245c5462"}, + {file = "shapely-2.0.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed5867e598a9e8ac3291da6cc9baa62ca25706eea186117034e8ec0ea4355653"}, + {file = "shapely-2.0.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81d9dfe155f371f78c8d895a7b7f323bb241fb148d848a2bf2244f79213123fe"}, + {file = "shapely-2.0.6-cp37-cp37m-win32.whl", hash = "sha256:fbb7bf02a7542dba55129062570211cfb0defa05386409b3e306c39612e7fbcc"}, + {file = "shapely-2.0.6-cp37-cp37m-win_amd64.whl", hash = "sha256:837d395fac58aa01aa544495b97940995211e3e25f9aaf87bc3ba5b3a8cd1ac7"}, + {file = "shapely-2.0.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c6d88ade96bf02f6bfd667ddd3626913098e243e419a0325ebef2bbd481d1eb6"}, + {file = "shapely-2.0.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8b3b818c4407eaa0b4cb376fd2305e20ff6df757bf1356651589eadc14aab41b"}, + {file = "shapely-2.0.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbc783529a21f2bd50c79cef90761f72d41c45622b3e57acf78d984c50a5d13"}, + {file = "shapely-2.0.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2423f6c0903ebe5df6d32e0066b3d94029aab18425ad4b07bf98c3972a6e25a1"}, + {file = "shapely-2.0.6-cp38-cp38-win32.whl", hash = "sha256:2de00c3bfa80d6750832bde1d9487e302a6dd21d90cb2f210515cefdb616e5f5"}, + {file = "shapely-2.0.6-cp38-cp38-win_amd64.whl", hash = "sha256:3a82d58a1134d5e975f19268710e53bddd9c473743356c90d97ce04b73e101ee"}, + {file = "shapely-2.0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:392f66f458a0a2c706254f473290418236e52aa4c9b476a072539d63a2460595"}, + {file = "shapely-2.0.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eba5bae271d523c938274c61658ebc34de6c4b33fdf43ef7e938b5776388c1be"}, + {file = "shapely-2.0.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7060566bc4888b0c8ed14b5d57df8a0ead5c28f9b69fb6bed4476df31c51b0af"}, + {file = "shapely-2.0.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b02154b3e9d076a29a8513dffcb80f047a5ea63c897c0cd3d3679f29363cf7e5"}, + {file = "shapely-2.0.6-cp39-cp39-win32.whl", hash = "sha256:44246d30124a4f1a638a7d5419149959532b99dfa25b54393512e6acc9c211ac"}, + {file = "shapely-2.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:2b542d7f1dbb89192d3512c52b679c822ba916f93479fa5d4fc2fe4fa0b3c9e8"}, + {file = "shapely-2.0.6.tar.gz", hash = "sha256:997f6159b1484059ec239cacaa53467fd8b5564dabe186cd84ac2944663b0bf6"}, +] + +[package.dependencies] +numpy = ">=1.14,<3" + +[package.extras] +docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] +test = ["pytest", "pytest-cov"] + [[package]] name = "structlog" version = "24.4.0" @@ -570,4 +639,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "93caa36e663f9d18358e1aefabd4d5b3e71ad1864663ad2a5f956b65c6cf77be" +content-hash = "f5041edb49c80e926991fbd658e04175d3c35e3e95fb90bf8762d244de7498f3" diff --git a/pyproject.toml b/pyproject.toml index 04ae6e9..b768e40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ pytest = "^8.3.2" types-pyyaml = "^6.0.12.20240808" scikit-learn = "^1.5.2" structlog = "^24.4.0" +shapely = "^2.0.6" [tool.poetry.group.dev.dependencies] From f0447f8fa4a073bcb551e1edce878376fa38d20e Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 23 Oct 2024 11:10:29 +0900 Subject: [PATCH 100/249] feat: add launcher script with argument parsing and configuration setup --- adf_core_python/launcher.py | 103 ++++++++++++++++++++++ config/{develop.json => development.json} | 0 config/launcher.yaml | 4 +- 3 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 adf_core_python/launcher.py rename config/{develop.json => development.json} (100%) diff --git a/adf_core_python/launcher.py b/adf_core_python/launcher.py new file mode 100644 index 0000000..04d566b --- /dev/null +++ b/adf_core_python/launcher.py @@ -0,0 +1,103 @@ +import argparse + +from adf_core_python.core.config.config import Config +from adf_core_python.core.launcher.agent_launcher import AgentLauncher +from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.logger.logger import configure_logger, get_logger + + +class Launcher: + def __init__( + self, + launcher_config_file: str, + ) -> None: + configure_logger() + + self.logger = get_logger(__name__) + self.launcher_config = Config(launcher_config_file) + + parser = argparse.ArgumentParser(description="Agent Launcher") + + parser.add_argument( + "--host", + type=str, + help="host name(Default: localhost)", + metavar="", + ) + parser.add_argument( + "--port", + type=int, + help="port number(Default: 27931)", + metavar="", + ) + parser.add_argument( + "-a", + "--ambulance", + type=int, + help="number of ambulance agents(Default: all ambulance)", + metavar="", + ) + parser.add_argument( + "-f", + "--firebrigade", + type=int, + help="number of firebrigade agents(Default: all firebrigade)", + metavar="", + ) + parser.add_argument( + "-p", + "--policeforce", + type=int, + help="number of policeforce agents(Default: all policeforce)", + metavar="", + ) + parser.add_argument( + "--precompute", + type=bool, + help="precompute flag", + metavar="", + ) + parser.add_argument( + "--debug", type=bool, default=False, help="debug flag", metavar="" + ) + args = parser.parse_args() + self.logger.info(f"Arguments: {args}") + + config_map = { + args.host: ConfigKey.KEY_KERNEL_HOST, + args.port: ConfigKey.KEY_KERNEL_PORT, + args.ambulance: ConfigKey.KEY_AMBULANCE_TEAM_COUNT, + args.firebrigade: ConfigKey.KEY_FIRE_BRIGADE_COUNT, + args.policeforce: ConfigKey.KEY_POLICE_FORCE_COUNT, + args.precompute: ConfigKey.KEY_PRECOMPUTE, + args.debug: ConfigKey.KEY_DEBUG_FLAG, + } + + for arg, key in config_map.items(): + if arg is not None: + self.launcher_config.set_value(key, arg) + + self.logger.info(f"Config: {self.launcher_config}") + + def launch(self) -> None: + agent_launcher: AgentLauncher = AgentLauncher( + self.launcher_config, + ) + agent_launcher.init_connector() + + try: + agent_launcher.launch() + except KeyboardInterrupt: + self.logger.info("Agent launcher interrupted") + except Exception as e: + self.logger.exception("Agent launcher failed", exc_info=e) + raise e + self.logger.info("Agent launcher finished") + + +if __name__ == "__main__": + launcher = Launcher( + "config/launcher.yaml", + ) + + launcher.launch() diff --git a/config/develop.json b/config/development.json similarity index 100% rename from config/develop.json rename to config/development.json diff --git a/config/launcher.yaml b/config/launcher.yaml index 532a4cf..b75f0d1 100644 --- a/config/launcher.yaml +++ b/config/launcher.yaml @@ -16,12 +16,12 @@ adf: develop: flag: 1 - filename: config/develop.json + filename: config/development.json team: platoon: ambulance: - count: -1 + count: 5 fire: count: -1 police: From f77fcd056cdb610a4cdbf16bbd0b7dcd711e4acd Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 23 Oct 2024 11:10:53 +0900 Subject: [PATCH 101/249] fix: fix get config key --- .../connect/connector_police_force.py | 2 +- adf_core_python/main.py | 97 ------------------- 2 files changed, 1 insertion(+), 98 deletions(-) delete mode 100644 adf_core_python/main.py diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py index 8d1502f..92151bf 100644 --- a/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -26,7 +26,7 @@ def connect( config: Config, loader: AbstractLoader, ) -> list[threading.Thread]: - count: int = config.get_value(ConfigKey.KEY_POLICE_FORCE_COUNT, 0) + count: int = config.get_value(ConfigKey.KEY_POLICE_OFFICE_COUNT, 0) if count == 0: return [] diff --git a/adf_core_python/main.py b/adf_core_python/main.py deleted file mode 100644 index 36151d1..0000000 --- a/adf_core_python/main.py +++ /dev/null @@ -1,97 +0,0 @@ -import argparse - -from adf_core_python.core.config.config import Config -from adf_core_python.core.launcher.agent_launcher import AgentLauncher -from adf_core_python.core.launcher.config_key import ConfigKey -from adf_core_python.core.logger.logger import configure_logger, get_logger - - -class Main: - def __init__(self) -> None: - self.logger = get_logger(__name__) - parser = argparse.ArgumentParser(description="Agent Launcher") - - parser.add_argument( - "--host", - type=str, - default="localhost", - help="host name(Default: localhost)", - metavar="", - ) - parser.add_argument( - "--port", - type=int, - default=27931, - help="port number(Default: 27931)", - metavar="", - ) - parser.add_argument( - "-a", - "--ambulanceteam", - type=int, - default=-1, - help="number of ambulance agents(Default: -1 means all ambulance)", - metavar="", - ) - parser.add_argument( - "-f", - "--firebrigade", - type=int, - default=-1, - help="number of firebrigade agents(Default: -1 means all firebrigade)", - metavar="", - ) - parser.add_argument( - "-p", - "--policeforce", - type=int, - default=-1, - help="number of policeforce agents(Default: -1 means all policeforce)", - metavar="", - ) - parser.add_argument( - "--precompute", - type=bool, - default=False, - help="precompute flag", - metavar="", - ) - parser.add_argument( - "--debug", type=bool, default=False, help="debug flag", metavar="" - ) - args = parser.parse_args() - self.logger.info(f"Arguments: {args}") - - self.config = Config() - self.config.set_value(ConfigKey.KEY_KERNEL_HOST, args.host) - self.config.set_value(ConfigKey.KEY_KERNEL_PORT, args.port) - self.config.set_value(ConfigKey.KEY_AMBULANCE_TEAM_COUNT, args.ambulanceteam) - self.config.set_value(ConfigKey.KEY_FIRE_BRIGADE_COUNT, args.firebrigade) - self.config.set_value(ConfigKey.KEY_POLICE_FORCE_COUNT, args.policeforce) - self.config.set_value(ConfigKey.KEY_PRECOMPUTE, args.precompute) - self.config.set_value(ConfigKey.KEY_DEBUG_FLAG, args.debug) - self.logger.info(f"Config: {self.config}") - - def launch(self) -> None: - agent_launcher: AgentLauncher = AgentLauncher( - self.config, - ) - agent_launcher.init_connector() - - try: - agent_launcher.launch() - except KeyboardInterrupt: - self.logger.info("Agent launcher interrupted") - except Exception as e: - self.logger.exception("Agent launcher failed", exc_info=e) - raise e - self.logger.info("Agent launcher finished") - - -if __name__ == "__main__": - configure_logger() - logger = get_logger(__name__) - logger.info("Starting the agent launcher") - - main = Main() - main.launch() From 71ab5aa865fd77c69cda2e2940e7d1f5061cbb88 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 23 Oct 2024 11:15:54 +0900 Subject: [PATCH 102/249] docs: add instructions for running the agent and accessing help --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index d0692eb..0125315 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,15 @@ poetry install ``` +### Run Agent + +```bash +poetry run python ./adf_core_python/launcher.py + +# get help +poetry run python ./adf_core_python/launcher.py -h +``` + ### Build ```bash From 7652c059adba2d924a1d506fd0cb1302030e585b Mon Sep 17 00:00:00 2001 From: harrki Date: Wed, 23 Oct 2024 11:08:00 +0900 Subject: [PATCH 103/249] feat: Add PoliceForce and FireBrigade modules --- adf_core_python/core/launcher/connect/connector_police_force.py | 2 +- adf_core_python/launcher.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py index 92151bf..8d1502f 100644 --- a/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -26,7 +26,7 @@ def connect( config: Config, loader: AbstractLoader, ) -> list[threading.Thread]: - count: int = config.get_value(ConfigKey.KEY_POLICE_OFFICE_COUNT, 0) + count: int = config.get_value(ConfigKey.KEY_POLICE_FORCE_COUNT, 0) if count == 0: return [] diff --git a/adf_core_python/launcher.py b/adf_core_python/launcher.py index 04d566b..e67ffff 100644 --- a/adf_core_python/launcher.py +++ b/adf_core_python/launcher.py @@ -32,7 +32,7 @@ def __init__( ) parser.add_argument( "-a", - "--ambulance", + "--ambulanceteam", type=int, help="number of ambulance agents(Default: all ambulance)", metavar="", From b0da5d63493ecd926214ce52cab8b25233636435 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 23 Oct 2024 11:41:34 +0900 Subject: [PATCH 104/249] fix: update argument name for ambulance team count in launcher --- adf_core_python/launcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adf_core_python/launcher.py b/adf_core_python/launcher.py index e67ffff..fa8b33e 100644 --- a/adf_core_python/launcher.py +++ b/adf_core_python/launcher.py @@ -66,7 +66,7 @@ def __init__( config_map = { args.host: ConfigKey.KEY_KERNEL_HOST, args.port: ConfigKey.KEY_KERNEL_PORT, - args.ambulance: ConfigKey.KEY_AMBULANCE_TEAM_COUNT, + args.ambulanceteam: ConfigKey.KEY_AMBULANCE_TEAM_COUNT, args.firebrigade: ConfigKey.KEY_FIRE_BRIGADE_COUNT, args.policeforce: ConfigKey.KEY_POLICE_FORCE_COUNT, args.precompute: ConfigKey.KEY_PRECOMPUTE, From ff1ab55963dad9c290ca90c7d66e7d49c9af558a Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 23 Oct 2024 11:49:48 +0900 Subject: [PATCH 105/249] refactor: remove unnecessary comments --- .../core/launcher/connect/connector_ambulance_team.py | 8 +++----- .../core/launcher/connect/connector_fire_brigade.py | 9 +++------ .../core/launcher/connect/connector_police_force.py | 9 +++------ 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index 8bb3af5..b4d7fb4 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -36,26 +36,24 @@ def connect( if loader.get_tactics_ambulance_team() is None: self.logger.error("Cannot load ambulance team tactics") - tactics_ambulance_team: TacticsAmbulanceTeam = ( # noqa: F841 + tactics_ambulance_team: TacticsAmbulanceTeam = ( loader.get_tactics_ambulance_team() ) - module_config: ModuleConfig = ModuleConfig( # noqa: F841 + module_config: ModuleConfig = ModuleConfig( config.get_value( ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, ModuleConfig.DEFAULT_CONFIG_FILE_NAME, ) ) - develop_data: DevelopData = DevelopData( # noqa: F841 + develop_data: DevelopData = DevelopData( config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), config.get_value( ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME ), ) - # TODO: component_launcher.generate_request_ID can cause race condition - # threading thread = threading.Thread( target=component_launcher.connect, args=( diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/adf_core_python/core/launcher/connect/connector_fire_brigade.py index 576978e..81ae2af 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_brigade.py +++ b/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -36,25 +36,22 @@ def connect( if loader.get_tactics_fire_brigade() is None: self.logger.error("Cannot load fire brigade tactics") - tactics_fire_brigade: TacticsFireBrigade = ( # noqa: F841 - loader.get_tactics_fire_brigade() - ) + tactics_fire_brigade: TacticsFireBrigade = loader.get_tactics_fire_brigade() - module_config: ModuleConfig = ModuleConfig( # noqa: F841 + module_config: ModuleConfig = ModuleConfig( config.get_value( ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, ModuleConfig.DEFAULT_CONFIG_FILE_NAME, ) ) - develop_data: DevelopData = DevelopData( # noqa: F841 + develop_data: DevelopData = DevelopData( config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), config.get_value( ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME ), ) - # TODO: component_launcher.generate_request_ID can cause race condition thread = threading.Thread( target=component_launcher.connect, args=( diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py index 8d1502f..eb4e7cc 100644 --- a/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -36,25 +36,22 @@ def connect( if loader.get_tactics_police_force() is None: self.logger.error("Cannot load police force tactics") - tactics_police_force: TacticsPoliceForce = ( # noqa: F841 - loader.get_tactics_police_force() - ) + tactics_police_force: TacticsPoliceForce = loader.get_tactics_police_force() - module_config: ModuleConfig = ModuleConfig( # noqa: F841 + module_config: ModuleConfig = ModuleConfig( config.get_value( ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, ModuleConfig.DEFAULT_CONFIG_FILE_NAME, ) ) - develop_data: DevelopData = DevelopData( # noqa: F841 + develop_data: DevelopData = DevelopData( config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), config.get_value( ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME ), ) - # TODO: component_launcher.generate_request_ID can cause race condition thread = threading.Thread( target=component_launcher.connect, args=( From 23e8e191523df4090177ae7f0d9b8358e52be0ca Mon Sep 17 00:00:00 2001 From: harrki Date: Wed, 23 Oct 2024 19:13:55 +0900 Subject: [PATCH 106/249] fix: Fixed FireBrigade to exec ActionLoad --- .../implement/module/complex/default_human_detector.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/adf_core_python/implement/module/complex/default_human_detector.py b/adf_core_python/implement/module/complex/default_human_detector.py index fa55bc6..4720226 100644 --- a/adf_core_python/implement/module/complex/default_human_detector.py +++ b/adf_core_python/implement/module/complex/default_human_detector.py @@ -123,7 +123,12 @@ def _is_valid_human(self, target_entity_id: EntityID) -> bool: if hp is None or hp <= 0: return False buriedness: Optional[int] = target.get_buriedness() - if buriedness is None or buriedness > 0: + if buriedness is None: + return False + myself = self._agent_info.get_myself() + if myself.get_urn() == EntityURN.FIRE_BRIGADE and buriedness == 0: + return False + if myself.get_urn() == EntityURN.AMBULANCE_TEAM and buriedness > 0: return False damage: Optional[int] = target.get_damage() if damage is None or damage == 0: From 46ec6120e23b34ed9fb58bdc82902c04c0c4bccf Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 23 Oct 2024 16:36:07 +0900 Subject: [PATCH 107/249] wip --- .gitignore | 3 ++ adf_core_python/core/agent/agent.py | 24 ++++++++++ adf_core_python/core/agent/platoon/platoon.py | 17 ++++--- .../core/launcher/agent_launcher.py | 11 +++-- .../launcher/connect/component_launcher.py | 48 +++++++++++++++++++ .../core/launcher/connect/connection.py | 48 +++++++++++++++++++ .../core/launcher/connect/connector.py | 3 +- .../connect/connector_ambulance_centre.py | 4 +- .../connect/connector_ambulance_team.py | 5 +- .../connect/connector_fire_brigade.py | 5 +- .../connect/connector_fire_station.py | 4 +- .../connect/connector_police_force.py | 5 +- .../connect/connector_police_office.py | 4 +- adf_core_python/core/logger/logger.py | 6 ++- adf_core_python/launcher.py | 6 +-- config/launcher.yaml | 6 +-- poetry.lock | 9 +--- 17 files changed, 164 insertions(+), 44 deletions(-) create mode 100644 adf_core_python/core/agent/agent.py create mode 100644 adf_core_python/core/launcher/connect/component_launcher.py create mode 100644 adf_core_python/core/launcher/connect/connection.py diff --git a/.gitignore b/.gitignore index 675f361..7e7baef 100644 --- a/.gitignore +++ b/.gitignore @@ -169,3 +169,6 @@ cython_debug/ # MacOS .DS_Store + +# ADF +agent.log* diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py new file mode 100644 index 0000000..3cfb99c --- /dev/null +++ b/adf_core_python/core/agent/agent.py @@ -0,0 +1,24 @@ +import sys + +from rcrs_core.agents.agent import Agent as RCRSAgent +from rcrs_core.worldmodel.worldmodel import WorldModel + +from adf_core_python.core.logger.logger import get_logger + + +class Agent(RCRSAgent): + def __init__(self, is_precompute: bool, name: str) -> None: + self.name = name + self.connect_request_id = None + self.world_model = WorldModel() + self.config = None + self.random = None + self.agent_id = None + self.precompute_flag = is_precompute + self.logger = get_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}" + ) + + def handle_connect_error(self, msg): + self.logger.warning("failed {0} : {1}".format(msg.request_id, msg.reason)) + sys.exit(1) diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py index 1b7748c..6ffcd02 100644 --- a/adf_core_python/core/agent/platoon/platoon.py +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -1,9 +1,10 @@ -from rcrs_core.agents.agent import Agent +# from rcrs_core.agents.agent import Agent from rcrs_core.commands.Command import Command from rcrs_core.config.config import Config as RCRSConfig from rcrs_core.worldmodel.changeSet import ChangeSet from adf_core_python.core.agent.action.action import Action +from adf_core_python.core.agent.agent import Agent from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -28,9 +29,7 @@ def __init__( module_config: ModuleConfig, develop_data: DevelopData, ) -> None: - super().__init__( - is_precompute, - ) + super().__init__(is_precompute, self.__class__.__qualname__) self._tactics_agent = tactics_agent self._team_name = team_name self._is_precompute = is_precompute @@ -61,15 +60,15 @@ def post_connect(self) -> None: config = Config() if self.config is not None: rcrc_config: RCRSConfig = self.config - for key, value in rcrc_config.data.items(): + for key, value in rcrc_config.data.items(): # type: ignore config.set_value(key, value) - for key, value in rcrc_config.int_data.items(): + for key, value in rcrc_config.int_data.items(): # type: ignore config.set_value(key, value) - for key, value in rcrc_config.float_data.items(): + for key, value in rcrc_config.float_data.items(): # type: ignore config.set_value(key, value) - for key, value in rcrc_config.boolean_data.items(): + for key, value in rcrc_config.boolean_data.items(): # type: ignore config.set_value(key, value) - for key, value in rcrc_config.array_data.items(): + for key, value in rcrc_config.array_data.items(): # type: ignore config.set_value(key, value) self._scenario_info: ScenarioInfo = ScenarioInfo(config, self._mode) diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index edc4148..e7884fb 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -1,11 +1,12 @@ import importlib import threading -from rcrs_core.connection.componentLauncher import ComponentLauncher - from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey + +# from rcrs_core.connection.componentLauncher import ComponentLauncher +from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector # from adf_core_python.core.launcher.connect.connector_ambulance_centre import ( @@ -24,10 +25,10 @@ from adf_core_python.core.launcher.connect.connector_police_force import ( ConnectorPoliceForce, ) + # from adf_core_python.core.launcher.connect.connector_police_office import ( # ConnectorPoliceOffice, # ) - from adf_core_python.core.logger.logger import get_logger @@ -63,7 +64,9 @@ def launch(self) -> None: port: int = self.config.get_value(ConfigKey.KEY_KERNEL_PORT, 27931) self.logger.info(f"Start agent launcher (host: {host}, port: {port})") - component_launcher: ComponentLauncher = ComponentLauncher(port, host) + component_launcher: ComponentLauncher = ComponentLauncher( + host, port, self.logger + ) for connector in self.connectors: threads = connector.connect(component_launcher, self.config, self.loader) diff --git a/adf_core_python/core/launcher/connect/component_launcher.py b/adf_core_python/core/launcher/connect/component_launcher.py new file mode 100644 index 0000000..cb7fe16 --- /dev/null +++ b/adf_core_python/core/launcher/connect/component_launcher.py @@ -0,0 +1,48 @@ +import socket + +from rcrs_core.agents.agent import Agent + +from adf_core_python.core.launcher.connect.connection import Connection + + +class ComponentLauncher: + def __init__(self, host: str, port: int, logger) -> None: + self.request_id = 0 + self.port = port + self.host = host + self.logger = logger + + def make_connection(self) -> Connection: + return Connection(self.host, self.port) + + def connect(self, agent: Agent, _request_id) -> None: + self.logger.info( + f"{agent.__class__.__name__} connecting to {self.host}:{self.port} request_id: {_request_id}" + ) + connection = self.make_connection() + try: + connection.connect() + # ソケットが使用しているPORT番号を取得 + if connection.socket is not None: + self.logger.info( + f"Connected to {self.host}:{self.port} on port {connection.socket.getsockname()[1]}" + ) + except socket.timeout: + self.logger.warning(f"Connection to {self.host}:{self.port} timed out") + return + except socket.error: + self.logger.error(f"Failed to connect to {self.host}:{self.port}") + return + + connection.message_received(agent.message_received) + agent.set_send_msg(connection.send_msg) + agent.start_up(_request_id) + + try: + connection.parse_message_from_kernel() + except Exception as e: + self.logger.error(f"Failed to connect agent: {self.host}:{self.port} {e}") + + def generate_request_id(self) -> int: + self.request_id += 1 + return self.request_id diff --git a/adf_core_python/core/launcher/connect/connection.py b/adf_core_python/core/launcher/connect/connection.py new file mode 100644 index 0000000..3f9b35a --- /dev/null +++ b/adf_core_python/core/launcher/connect/connection.py @@ -0,0 +1,48 @@ +import socket +import sys + +import rcrs_core.connection.rcrs_encoding_utils as rcrs_encoding_utils + + +class Connection: + def __init__(self, host: str, port: int) -> None: + self.socket = None + self.agent = None + self.buffer_size: int = 4096 + self.data_buffer: bytes = b"" + self.host = host + self.port = port + + def connect(self) -> None: + """ + Connect to the kernel + + Raises + ------ + socket.timeout + If the connection times out + socket.error + If there is an error connecting to the socket + + """ + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.connect((self.host, self.port)) + + def parse_message_from_kernel(self) -> None: + """ + Parse messages from the kernel + + Raises + ------ + IOError + If there is an error reading from the socket + """ + while True: + msg = rcrs_encoding_utils.read_msg(self.socket) + self.agent_message_received(msg) + + def message_received(self, agent_message_received) -> None: + self.agent_message_received = agent_message_received + + def send_msg(self, msg) -> None: + rcrs_encoding_utils.write_msg(msg, self.socket) diff --git a/adf_core_python/core/launcher/connect/connector.py b/adf_core_python/core/launcher/connect/connector.py index e2e562f..850023a 100644 --- a/adf_core_python/core/launcher/connect/connector.py +++ b/adf_core_python/core/launcher/connect/connector.py @@ -1,10 +1,9 @@ import threading from abc import ABC, abstractmethod -from rcrs_core.connection.componentLauncher import ComponentLauncher - from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.config.config import Config +from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher class Connector(ABC): diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py index e7909b4..9efeee5 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py @@ -2,13 +2,13 @@ from logging import Logger, getLogger from rcrs_core.agents.ambulanceCenterAgent import AmbulanceCenterAgent -from rcrs_core.connection.componentLauncher import ComponentLauncher from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector @@ -59,7 +59,7 @@ def connect( AmbulanceCenterAgent( config.get_value(ConfigKey.KEY_PRECOMPUTE, False), ), # type: ignore - component_launcher.generate_request_ID(), + component_launcher.generate_request_id(), ), ) threads.append(thread) diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index b4d7fb4..15da390 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -1,7 +1,5 @@ import threading -from rcrs_core.connection.componentLauncher import ComponentLauncher - from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.platoon.platoon_ambulance import PlatoonAmbulance @@ -11,6 +9,7 @@ ) from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector from adf_core_python.core.logger.logger import get_logger @@ -66,7 +65,7 @@ def connect( module_config, develop_data, ), - component_launcher.generate_request_ID(), + component_launcher.generate_request_id(), ), ) threads.append(thread) diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/adf_core_python/core/launcher/connect/connector_fire_brigade.py index 81ae2af..fab7095 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_brigade.py +++ b/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -1,7 +1,5 @@ import threading -from rcrs_core.connection.componentLauncher import ComponentLauncher - from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.platoon.platoon_fire import PlatoonFire @@ -11,6 +9,7 @@ ) from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector from adf_core_python.core.logger.logger import get_logger @@ -64,7 +63,7 @@ def connect( module_config, develop_data, ), - component_launcher.generate_request_ID(), + component_launcher.generate_request_id(), ), ) threads.append(thread) diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/adf_core_python/core/launcher/connect/connector_fire_station.py index 99d50dc..0f3bc81 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_station.py +++ b/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -2,13 +2,13 @@ from logging import Logger, getLogger from rcrs_core.agents.fireStationAgent import FireStationAgent -from rcrs_core.connection.componentLauncher import ComponentLauncher from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector @@ -59,7 +59,7 @@ def connect( FireStationAgent( config.get_value(ConfigKey.KEY_PRECOMPUTE, False), ), # type: ignore - component_launcher.generate_request_ID(), + component_launcher.generate_request_id(), ), ) threads.append(thread) diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py index eb4e7cc..bbf9359 100644 --- a/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -1,7 +1,5 @@ import threading -from rcrs_core.connection.componentLauncher import ComponentLauncher - from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.platoon.platoon_police import PlatoonPolice @@ -11,6 +9,7 @@ ) from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector from adf_core_python.core.logger.logger import get_logger @@ -64,7 +63,7 @@ def connect( module_config, develop_data, ), - component_launcher.generate_request_ID(), + component_launcher.generate_request_id(), ), ) threads.append(thread) diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py index 7627fc4..bd1d405 100644 --- a/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -2,13 +2,13 @@ from logging import Logger, getLogger from rcrs_core.agents.policeOfficeAgent import PoliceOfficeAgent -from rcrs_core.connection.componentLauncher import ComponentLauncher from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector @@ -59,7 +59,7 @@ def connect( PoliceOfficeAgent( config.get_value(ConfigKey.KEY_PRECOMPUTE, False), ), # type: ignore - component_launcher.generate_request_ID(), + component_launcher.generate_request_id(), ), ) threads.append(thread) diff --git a/adf_core_python/core/logger/logger.py b/adf_core_python/core/logger/logger.py index fe29e0a..84f857a 100644 --- a/adf_core_python/core/logger/logger.py +++ b/adf_core_python/core/logger/logger.py @@ -1,5 +1,6 @@ import logging import sys +from logging.handlers import RotatingFileHandler import structlog from structlog.dev import ConsoleRenderer @@ -69,7 +70,10 @@ def configure_logger() -> None: structlog.stdlib.ProcessorFormatter(processor=ConsoleRenderer()) ) - handler_file = logging.FileHandler("agent.log") + handler_file = RotatingFileHandler( + "agent.log", maxBytes=10 * 1024 * 1024, backupCount=5 + ) + handler_file.doRollover() handler_file.setFormatter( structlog.stdlib.ProcessorFormatter(processor=JSONRenderer()) ) diff --git a/adf_core_python/launcher.py b/adf_core_python/launcher.py index fa8b33e..9c48b9b 100644 --- a/adf_core_python/launcher.py +++ b/adf_core_python/launcher.py @@ -57,9 +57,7 @@ def __init__( help="precompute flag", metavar="", ) - parser.add_argument( - "--debug", type=bool, default=False, help="debug flag", metavar="" - ) + parser.add_argument("--debug", type=bool, help="debug flag", metavar="") args = parser.parse_args() self.logger.info(f"Arguments: {args}") @@ -73,6 +71,8 @@ def __init__( args.debug: ConfigKey.KEY_DEBUG_FLAG, } + self.logger.info(f"Config map: {config_map}") + for arg, key in config_map.items(): if arg is not None: self.launcher_config.set_value(key, arg) diff --git a/config/launcher.yaml b/config/launcher.yaml index b75f0d1..3d64272 100644 --- a/config/launcher.yaml +++ b/config/launcher.yaml @@ -21,11 +21,11 @@ adf: team: platoon: ambulance: - count: 5 + count: 100 fire: - count: -1 + count: 100 police: - count: -1 + count: 100 office: ambulance: count: -1 diff --git a/poetry.lock b/poetry.lock index 0524cca..a96b272 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "colorama" @@ -342,7 +342,7 @@ rtree = "*" type = "git" url = "https://github.com/adf-python/rcrs-core-python" reference = "HEAD" -resolved_reference = "9dfa01ee8d8ccc9cb5e84fdf21c8c403e4f28ed3" +resolved_reference = "b353cd6e82005e20e099554d4f357d9c0cc2e052" [[package]] name = "rtree" @@ -411,11 +411,6 @@ files = [ {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f60021ec1574e56632be2a36b946f8143bf4e5e6af4a06d85281adc22938e0dd"}, {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:394397841449853c2290a32050382edaec3da89e35b3e03d6cc966aebc6a8ae6"}, {file = "scikit_learn-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:57cc1786cfd6bd118220a92ede80270132aa353647684efa385a74244a41e3b1"}, - {file = "scikit_learn-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9a702e2de732bbb20d3bad29ebd77fc05a6b427dc49964300340e4c9328b3f5"}, - {file = "scikit_learn-1.5.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:b0768ad641981f5d3a198430a1d31c3e044ed2e8a6f22166b4d546a5116d7908"}, - {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:178ddd0a5cb0044464fc1bfc4cca5b1833bfc7bb022d70b05db8530da4bb3dd3"}, - {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7284ade780084d94505632241bf78c44ab3b6f1e8ccab3d2af58e0e950f9c12"}, - {file = "scikit_learn-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:b7b0f9a0b1040830d38c39b91b3a44e1b643f4b36e36567b80b7c6bd2202a27f"}, {file = "scikit_learn-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:757c7d514ddb00ae249832fe87100d9c73c6ea91423802872d9e74970a0e40b9"}, {file = "scikit_learn-1.5.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:52788f48b5d8bca5c0736c175fa6bdaab2ef00a8f536cda698db61bd89c551c1"}, {file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:643964678f4b5fbdc95cbf8aec638acc7aa70f5f79ee2cdad1eec3df4ba6ead8"}, From 70c38fe6713e7f38d3fb87a4dbe0eb4706937c0e Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 25 Oct 2024 14:36:01 +0900 Subject: [PATCH 108/249] refactor: improve logging format in agent connection error handling and bind agent ID in component launcher --- adf_core_python/core/agent/agent.py | 4 +++- adf_core_python/core/launcher/connect/component_launcher.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index 3cfb99c..de93d4a 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -20,5 +20,7 @@ def __init__(self, is_precompute: bool, name: str) -> None: ) def handle_connect_error(self, msg): - self.logger.warning("failed {0} : {1}".format(msg.request_id, msg.reason)) + self.logger.warning( + "Failed to connect agent: %s(request_id: %s)", msg.reason, msg.request_id + ) sys.exit(1) diff --git a/adf_core_python/core/launcher/connect/component_launcher.py b/adf_core_python/core/launcher/connect/component_launcher.py index cb7fe16..c723c51 100644 --- a/adf_core_python/core/launcher/connect/component_launcher.py +++ b/adf_core_python/core/launcher/connect/component_launcher.py @@ -16,6 +16,8 @@ def make_connection(self) -> Connection: return Connection(self.host, self.port) def connect(self, agent: Agent, _request_id) -> None: + self.logger.bind(agent_id=agent.get_id()) + self.logger.info( f"{agent.__class__.__name__} connecting to {self.host}:{self.port} request_id: {_request_id}" ) From 0d6de7338aaac3b931d0a60e2860dcb99f40d7eb Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 25 Oct 2024 14:42:22 +0900 Subject: [PATCH 109/249] refactor: remove unused import of sys in connection.py --- adf_core_python/core/launcher/connect/connection.py | 1 - 1 file changed, 1 deletion(-) diff --git a/adf_core_python/core/launcher/connect/connection.py b/adf_core_python/core/launcher/connect/connection.py index 3f9b35a..b5fc5d4 100644 --- a/adf_core_python/core/launcher/connect/connection.py +++ b/adf_core_python/core/launcher/connect/connection.py @@ -1,5 +1,4 @@ import socket -import sys import rcrs_core.connection.rcrs_encoding_utils as rcrs_encoding_utils From 9f6b8a89f67f47c55bf18d975a4e8ecd231a0057 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 25 Oct 2024 18:27:48 +0900 Subject: [PATCH 110/249] refactor: add type hints to agent and connection classes --- adf_core_python/core/agent/agent.py | 5 +++-- .../core/launcher/connect/component_launcher.py | 5 +++-- adf_core_python/core/launcher/connect/connection.py | 7 ++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index de93d4a..297f42a 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -1,4 +1,5 @@ import sys +from typing import Any, NoReturn from rcrs_core.agents.agent import Agent as RCRSAgent from rcrs_core.worldmodel.worldmodel import WorldModel @@ -19,8 +20,8 @@ def __init__(self, is_precompute: bool, name: str) -> None: f"{self.__class__.__module__}.{self.__class__.__qualname__}" ) - def handle_connect_error(self, msg): - self.logger.warning( + def handle_connect_error(self, msg: Any) -> NoReturn: + self.logger.error( "Failed to connect agent: %s(request_id: %s)", msg.reason, msg.request_id ) sys.exit(1) diff --git a/adf_core_python/core/launcher/connect/component_launcher.py b/adf_core_python/core/launcher/connect/component_launcher.py index c723c51..027bb85 100644 --- a/adf_core_python/core/launcher/connect/component_launcher.py +++ b/adf_core_python/core/launcher/connect/component_launcher.py @@ -1,12 +1,13 @@ import socket from rcrs_core.agents.agent import Agent +from structlog import BoundLogger from adf_core_python.core.launcher.connect.connection import Connection class ComponentLauncher: - def __init__(self, host: str, port: int, logger) -> None: + def __init__(self, host: str, port: int, logger: BoundLogger) -> None: self.request_id = 0 self.port = port self.host = host @@ -15,7 +16,7 @@ def __init__(self, host: str, port: int, logger) -> None: def make_connection(self) -> Connection: return Connection(self.host, self.port) - def connect(self, agent: Agent, _request_id) -> None: + def connect(self, agent: Agent, _request_id: int) -> None: self.logger.bind(agent_id=agent.get_id()) self.logger.info( diff --git a/adf_core_python/core/launcher/connect/connection.py b/adf_core_python/core/launcher/connect/connection.py index b5fc5d4..6497231 100644 --- a/adf_core_python/core/launcher/connect/connection.py +++ b/adf_core_python/core/launcher/connect/connection.py @@ -1,11 +1,12 @@ import socket +from typing import Any, Callable import rcrs_core.connection.rcrs_encoding_utils as rcrs_encoding_utils class Connection: def __init__(self, host: str, port: int) -> None: - self.socket = None + self.socket: socket.socket self.agent = None self.buffer_size: int = 4096 self.data_buffer: bytes = b"" @@ -40,8 +41,8 @@ def parse_message_from_kernel(self) -> None: msg = rcrs_encoding_utils.read_msg(self.socket) self.agent_message_received(msg) - def message_received(self, agent_message_received) -> None: + def message_received(self, agent_message_received: Callable) -> None: self.agent_message_received = agent_message_received - def send_msg(self, msg) -> None: + def send_msg(self, msg: Any) -> None: rcrs_encoding_utils.write_msg(msg, self.socket) From 865fd7bc76087c2b88c2b667f47509538860d58c Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 25 Oct 2024 18:47:52 +0900 Subject: [PATCH 111/249] refactor: update argument parsing to improve config mapping in launcher --- adf_core_python/launcher.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/adf_core_python/launcher.py b/adf_core_python/launcher.py index 9c48b9b..a7105ca 100644 --- a/adf_core_python/launcher.py +++ b/adf_core_python/launcher.py @@ -59,23 +59,20 @@ def __init__( ) parser.add_argument("--debug", type=bool, help="debug flag", metavar="") args = parser.parse_args() - self.logger.info(f"Arguments: {args}") config_map = { - args.host: ConfigKey.KEY_KERNEL_HOST, - args.port: ConfigKey.KEY_KERNEL_PORT, - args.ambulanceteam: ConfigKey.KEY_AMBULANCE_TEAM_COUNT, - args.firebrigade: ConfigKey.KEY_FIRE_BRIGADE_COUNT, - args.policeforce: ConfigKey.KEY_POLICE_FORCE_COUNT, - args.precompute: ConfigKey.KEY_PRECOMPUTE, - args.debug: ConfigKey.KEY_DEBUG_FLAG, + ConfigKey.KEY_KERNEL_HOST: args.host, + ConfigKey.KEY_KERNEL_PORT: args.port, + ConfigKey.KEY_AMBULANCE_TEAM_COUNT: args.ambulanceteam, + ConfigKey.KEY_FIRE_BRIGADE_COUNT: args.firebrigade, + ConfigKey.KEY_POLICE_FORCE_COUNT: args.policeforce, + ConfigKey.KEY_PRECOMPUTE: args.precompute, + ConfigKey.KEY_DEBUG_FLAG: args.debug, } - self.logger.info(f"Config map: {config_map}") - - for arg, key in config_map.items(): - if arg is not None: - self.launcher_config.set_value(key, arg) + for key, value in config_map.items(): + if value is not None: + self.launcher_config.set_value(key, value) self.logger.info(f"Config: {self.launcher_config}") From bad2e4a73e8c54cde735277af1238c3f0a9c90f9 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 25 Oct 2024 18:53:06 +0900 Subject: [PATCH 112/249] refactor: replace getLogger with custom get_logger in connector classes --- .../core/launcher/connect/connector_ambulance_centre.py | 4 ++-- .../core/launcher/connect/connector_fire_station.py | 4 ++-- .../core/launcher/connect/connector_police_office.py | 4 ++-- .../implement/action/default_extend_action_move.py | 2 -- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py index 9efeee5..0194918 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py @@ -1,5 +1,4 @@ import threading -from logging import Logger, getLogger from rcrs_core.agents.ambulanceCenterAgent import AmbulanceCenterAgent @@ -10,12 +9,13 @@ from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector +from adf_core_python.core.logger.logger import get_logger class ConnectorAmbulanceCentre(Connector): def __init__(self) -> None: super().__init__() - self.logger: Logger = getLogger(__name__) + self.logger = get_logger(__name__) def connect( self, diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/adf_core_python/core/launcher/connect/connector_fire_station.py index 0f3bc81..49bd190 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_station.py +++ b/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -1,5 +1,4 @@ import threading -from logging import Logger, getLogger from rcrs_core.agents.fireStationAgent import FireStationAgent @@ -10,12 +9,13 @@ from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector +from adf_core_python.core.logger.logger import get_logger class ConnectorFireStation(Connector): def __init__(self) -> None: super().__init__() - self.logger: Logger = getLogger(__name__) + self.logger = get_logger(__name__) def connect( self, diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py index bd1d405..32e9060 100644 --- a/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -1,5 +1,4 @@ import threading -from logging import Logger, getLogger from rcrs_core.agents.policeOfficeAgent import PoliceOfficeAgent @@ -10,12 +9,13 @@ from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector +from adf_core_python.core.logger.logger import get_logger class ConnectorPoliceOffice(Connector): def __init__(self) -> None: super().__init__() - self.logger: Logger = getLogger(__name__) + self.logger = get_logger(__name__) def connect( self, diff --git a/adf_core_python/implement/action/default_extend_action_move.py b/adf_core_python/implement/action/default_extend_action_move.py index 46a2c6c..6614ffd 100644 --- a/adf_core_python/implement/action/default_extend_action_move.py +++ b/adf_core_python/implement/action/default_extend_action_move.py @@ -1,4 +1,3 @@ -from logging import Logger, getLogger from typing import Optional, cast from rcrs_core.entities.area import Area @@ -33,7 +32,6 @@ def __init__( ) self._target_entity_id: Optional[EntityID] = None self._threshold_to_rest: int = develop_data.get_value("threshold_to_rest", 100) - self._logger: Logger = getLogger(__name__) match self.scenario_info.get_mode(): case Mode.NON_PRECOMPUTE: From c800e252f93a8fb35bea2f511e4ccdbb1ffcd702 Mon Sep 17 00:00:00 2001 From: shima004 Date: Sat, 26 Oct 2024 12:10:28 +0900 Subject: [PATCH 113/249] feat: communication module --- adf_core_python/core/agent/agent.py | 38 ++- .../agent/communication/message_manager.py | 271 +++++++++++++++++- .../standard/standard_communication_module.py | 14 + .../core/agent/info/scenario_info.py | 31 ++ .../communication/channel_subscriber.py | 32 +++ .../communication/communication_message.py | 4 + .../communication/communication_module.py | 15 + .../communication/message_coordinator.py | 23 ++ 8 files changed, 425 insertions(+), 3 deletions(-) create mode 100644 adf_core_python/core/agent/communication/standard/standard_communication_module.py create mode 100644 adf_core_python/core/component/communication/channel_subscriber.py create mode 100644 adf_core_python/core/component/communication/communication_module.py create mode 100644 adf_core_python/core/component/communication/message_coordinator.py diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index 297f42a..50056da 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -1,14 +1,30 @@ import sys +from abc import abstractmethod from typing import Any, NoReturn from rcrs_core.agents.agent import Agent as RCRSAgent +from rcrs_core.connection.URN import Entity as EntityURN from rcrs_core.worldmodel.worldmodel import WorldModel +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.scenario_info import Mode +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.logger.logger import get_logger class Agent(RCRSAgent): - def __init__(self, is_precompute: bool, name: str) -> None: + def __init__( + self, + is_precompute: bool, + name: str, + is_debug: bool, + team_name: str, + deta_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + ) -> None: self.name = name self.connect_request_id = None self.world_model = WorldModel() @@ -20,8 +36,28 @@ def __init__(self, is_precompute: bool, name: str) -> None: f"{self.__class__.__module__}.{self.__class__.__qualname__}" ) + self.team_name = team_name + self.is_debug = is_debug + self.is_precompute = is_precompute + + if is_precompute: + # PrecomputeData.remove_date(deta_storage_name) + self.mode = Mode.PRECOMPUTATION + + self.module_config = module_config + self.develop_data = develop_data + self.precompute_data = PrecomputeData(deta_storage_name) + self.message_manager: MessageManager = MessageManager() + def handle_connect_error(self, msg: Any) -> NoReturn: self.logger.error( "Failed to connect agent: %s(request_id: %s)", msg.reason, msg.request_id ) sys.exit(1) + + def precompute(self): + pass + + @abstractmethod + def get_requested_entities(self) -> list[EntityURN]: + pass diff --git a/adf_core_python/core/agent/communication/message_manager.py b/adf_core_python/core/agent/communication/message_manager.py index 7883136..e873664 100644 --- a/adf_core_python/core/agent/communication/message_manager.py +++ b/adf_core_python/core/agent/communication/message_manager.py @@ -1,4 +1,271 @@ -# TODO: Implement MessageManager class +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo, ScenarioInfoKeys +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.component.communication.channel_subscriber import ( + ChannelSubscriber, +) +from adf_core_python.core.component.communication.communication_message import ( + CommunicationMessage, +) +from adf_core_python.core.component.communication.message_coordinator import ( + MessageCoordinator, +) + + class MessageManager: + MAX_MESSAGE_CLASS_COUNT = 32 + def __init__(self) -> None: - pass + """ + Initialize the MessageManager. + + Parameters + ---------- + __standard_message_class_count : int + The count of standard message classes. + __custom_message_class_count : int + The count of custom message classes. + __message_classes : dict[int, CommunicationMessage] + The message classes. + __subscribed_channels : list[int] + The subscribed channels. Default is [1]. + __is_subscribed : bool + The flag to indicate if the agent is subscribed to the channel. + """ + self.__standard_message_class_count = 0b0000_0001 + self.__custom_message_class_count = 0b0001_0000 + self.__message_classes: dict[int, CommunicationMessage] = {} + self.__send_message_list: list[CommunicationMessage] = [] + self.__received_message_list: list[CommunicationMessage] = [] + self.__channel_send_message_list: list[list[CommunicationMessage]] = [] + self.__check_duplicate_cache: set[str] = set() + self.__message_coordinator: MessageCoordinator + self.__channel_subscriber: ChannelSubscriber + self.__heard_agent_help_message_count: int = 0 + self.__subscribed_channels: list[int] = [1] + self.__is_subscribed = False + + def set_subscribed_channels(self, subscribed_channels: list[int]) -> None: + """ + Set the subscribed channels. + + Parameters + ---------- + subscribed_channels : list[int] + The subscribed channels. + + """ + self.__subscribed_channels = subscribed_channels + self.__is_subscribed = False + + def get_subscribed_channels(self) -> list[int]: + """ + Get the subscribed channels. + + Returns + ------- + list[int] + The subscribed channels. + + """ + return self.__subscribed_channels + + def set_is_subscribed(self, is_subscribed: bool) -> None: + """ + Set the flag to indicate if the agent is subscribed to the channel. + + Parameters + ---------- + is_subscribed : bool + The flag to indicate if the agent is subscribed to the channel. + + """ + self.__is_subscribed = is_subscribed + + def get_is_subscribed(self) -> bool: + """ + Get the flag to indicate if the agent is subscribed to the channel. + + Returns + ------- + bool + The flag to indicate if the agent is subscribed to the channel. + + """ + return self.__is_subscribed + + def set_channel_subscriber(self, channel_subscriber: ChannelSubscriber) -> None: + """ + Set the channel subscriber. + + Parameters + ---------- + channel_subscriber : ChannelSubscriber + The channel subscriber. + + """ + self.__channel_subscriber = channel_subscriber + + def get_channel_subscriber(self) -> ChannelSubscriber: + """ + Get the channel subscriber. + + Returns + ------- + ChannelSubscriber + The channel subscriber. + + """ + return self.__channel_subscriber + + def add_heard_agent_help_message_count(self) -> None: + """ + Add the heard agent help message count. + + """ + self.__heard_agent_help_message_count += 1 + + def get_heard_agent_help_message_count(self) -> int: + """ + Get the heard agent help message count. + + Returns + ------- + int + The heard agent help message count. + + """ + return self.__heard_agent_help_message_count + + def add_received_message(self, message: CommunicationMessage) -> None: + """ + Add the received message. + + Parameters + ---------- + message : CommunicationMessage + The received message. + + """ + self.__received_message_list.append(message) + + def get_received_message_list(self) -> list[CommunicationMessage]: + """ + Get the received message list. + + Returns + ------- + list[CommunicationMessage] + The received message list. + + """ + return self.__received_message_list + + def subscribe( + self, agent_info: AgentInfo, world_info: WorldInfo, scenario_info: ScenarioInfo + ) -> None: + """ + Subscribe to the channel. + + Parameters + ---------- + agent_info : AgentInfo + The agent info. + world_info : WorldInfo + The world info. + scenario_info : ScenarioInfo + The scenario info. + + Throws + ------ + ValueError + If the ChannelSubscriber is not set. + + """ + if self.__channel_subscriber is None: + raise ValueError("ChannelSubscriber is not set.") + + self.__channel_subscriber.subscribe(agent_info, world_info, scenario_info, self) + + def register_message_class( + self, index: int, message_class: CommunicationMessage + ) -> None: + """ + Register the message class. + + Parameters + ---------- + message_class : type[CommunicationMessage] + The message class. + + """ + if index >= self.MAX_MESSAGE_CLASS_COUNT: + raise ValueError( + f"Possible index values are 0 to {self.MAX_MESSAGE_CLASS_COUNT-1}" + ) + self.__message_classes[index] = message_class + + def add_message( + self, message: CommunicationMessage, check_duplicate: bool = True + ) -> None: + """ + Add the message. + + Parameters + ---------- + message : CommunicationMessage + The message. + + """ + check_key = message.get_check_key() + # TODO:両方同じコードになっているが、なぜなのか調査する + if check_duplicate and check_key not in self.__check_duplicate_cache: + self.__send_message_list.append(message) + self.__check_duplicate_cache.add(check_key) + else: + self.__send_message_list.append(message) + self.__check_duplicate_cache.add(check_key) + + def coordinate_message( + self, agent_info: AgentInfo, world_info: WorldInfo, scenario_info: ScenarioInfo + ) -> None: + """ + Coordinate the message. + + Parameters + ---------- + agent_info : AgentInfo + The agent info. + world_info : WorldInfo + The world info. + scenario_info : ScenarioInfo + The scenario info. + + """ + if self.__message_coordinator is None: + raise ValueError("MessageCoordinator is not set.") + + self.__channel_send_message_list = [ + [] + for _ in range( + scenario_info.get_value(ScenarioInfoKeys.COMMS_CHANNELS_COUNT, 1) + ) + ] + + self.__message_coordinator.coordinate( + agent_info, + world_info, + scenario_info, + self, + self.__send_message_list, + self.__channel_send_message_list, + ) + + def refresh(self) -> None: + """ + Refresh the message manager. + + """ + self.__send_message_list = [] + self.__check_duplicate_cache = set() + self.__heard_agent_help_message_count = 0 diff --git a/adf_core_python/core/agent/communication/standard/standard_communication_module.py b/adf_core_python/core/agent/communication/standard/standard_communication_module.py new file mode 100644 index 0000000..057930d --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/standard_communication_module.py @@ -0,0 +1,14 @@ +from rcrs_core.agents.agent import Agent + +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.component.communication.communication_module import ( + CommunicationModule, +) + + +class StandardCommunicationModule(CommunicationModule): + def receive(self, agent: Agent, message_manager: MessageManager) -> None: + raise NotImplementedError + + def send(self, agent: Agent, message_manager: MessageManager) -> None: + raise NotImplementedError diff --git a/adf_core_python/core/agent/info/scenario_info.py b/adf_core_python/core/agent/info/scenario_info.py index fe085bf..f8d35fa 100644 --- a/adf_core_python/core/agent/info/scenario_info.py +++ b/adf_core_python/core/agent/info/scenario_info.py @@ -10,6 +10,37 @@ class Mode(Enum): PRECOMPUTATION = 2 +class ScenarioInfoKeys: + KERNEL_PERCEPTION = "kernel.perception" + PERCEPTION_LOS_PRECISION_HP = "perception.los.precision.hp" + CLEAR_REPAIR_RAD = "clear.repair.rad" + FIRE_TANK_REFILL_HYDRANT_RATE = "fire.tank.refill_hydrant_rate" + SCENARIO_AGENTS_PF = "scenario.agents.pf" + SCENARIO_AGENTS_FS = "scenario.agents.fs" + VOICE_MESSAGES_SIZE = "comms.channels.0.messages.size" + FIRE_TANK_REFILL_RATE = "fire.tank.refill_rate" + KERNEL_TIMESTEPS = "kernel.timesteps" + FIRE_EXTINGUISH_MAX_SUM = "fire.extinguish.max-sum" + COMMS_CHANNELS_MAX_PLATOON = "comms.channels.max.platoon" + KERNEL_AGENTS_THINK_TIME = "kernel.agents.think-time" + FIRE_TANK_MAXIMUM = "fire.tank.maximum" + CLEAR_REPAIR_RATE = "clear.repair.rate" + KERNEL_STARTUP_CONNECT_TIME = "kernel.startup.connect-time" + KERNEL_HOST = "kernel.host" + SCENARIO_AGENTS_AT = "scenario.agents.at" + PERCEPTION_LOS_MAX_DISTANCE = "perception.los.max-distance" + SCENARIO_AGENTS_FB = "scenario.agents.fb" + SCENARIO_AGENTS_PO = "scenario.agents.po" + KERNEL_COMMUNICATION_MODEL = "kernel.communication-model" + PERCEPTION_LOS_PRECISION_DAMAGE = "perception.los.precision.damage" + SCENARIO_AGENTS_AC = "scenario.agents.ac" + COMMS_CHANNELS_MAX_OFFICE = "comms.channels.max.centre" + FIRE_EXTINGUISH_MAX_DISTANCE = "fire.extinguish.max-distance" + KERNEL_AGENTS_IGNOREUNTIL = "kernel.agents.ignoreuntil" + CLEAR_REPAIR_DISTANCE = "clear.repair.distance" + COMMS_CHANNELS_COUNT = "comms.channels.count" + + class ScenarioInfo: def __init__(self, config: Config, mode: Mode): """ diff --git a/adf_core_python/core/component/communication/channel_subscriber.py b/adf_core_python/core/component/communication/channel_subscriber.py new file mode 100644 index 0000000..7b4ee89 --- /dev/null +++ b/adf_core_python/core/component/communication/channel_subscriber.py @@ -0,0 +1,32 @@ +from abc import ABC, abstractmethod + +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo + + +class ChannelSubscriber(ABC): + @abstractmethod + def subscribe( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + message_manager: MessageManager, + ) -> None: + """ + Subscribe to the channel. + + Parameters + ---------- + agent_info : AgentInfo + The agent info. + world_info : WorldInfo + The world info. + scenario_info : ScenarioInfo + The scenario info. + message_manager : MessageManager + The message manager. + """ + pass diff --git a/adf_core_python/core/component/communication/communication_message.py b/adf_core_python/core/component/communication/communication_message.py index 2909fcf..a6cd25d 100644 --- a/adf_core_python/core/component/communication/communication_message.py +++ b/adf_core_python/core/component/communication/communication_message.py @@ -16,4 +16,8 @@ def get_byte_size(self) -> int: def to_bytes(self) -> bytes: raise NotImplementedError + @abstractmethod + def get_check_key(self) -> str: + raise NotImplementedError + # TODO: Implement the toBitOutputStream and getCheckKey methods diff --git a/adf_core_python/core/component/communication/communication_module.py b/adf_core_python/core/component/communication/communication_module.py new file mode 100644 index 0000000..c1b957e --- /dev/null +++ b/adf_core_python/core/component/communication/communication_module.py @@ -0,0 +1,15 @@ +from abc import ABC, abstractmethod + +from rcrs_core.agents.agent import Agent + +from adf_core_python.core.agent.communication.message_manager import MessageManager + + +class CommunicationModule(ABC): + @abstractmethod + def receive(self, agent: Agent, message_manager: MessageManager) -> None: + pass + + @abstractmethod + def send(self, agent: Agent, message_manager: MessageManager) -> None: + pass diff --git a/adf_core_python/core/component/communication/message_coordinator.py b/adf_core_python/core/component/communication/message_coordinator.py new file mode 100644 index 0000000..d749050 --- /dev/null +++ b/adf_core_python/core/component/communication/message_coordinator.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod + +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.component.communication.communication_message import ( + CommunicationMessage, +) + + +class MessageCoordinator(ABC): + @abstractmethod + def coordinate( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + message_manager: MessageManager, + send_message_list: list[CommunicationMessage], + channel_send_message_list: list[list[CommunicationMessage]], + ) -> None: + pass From e37bac5ad8696220027fd78ec660ead8a6171188 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 18 Oct 2024 20:31:14 +0900 Subject: [PATCH 114/249] feat: add standard message and priority classes --- adf_core_python/core/agent/agent.py | 70 +++++++- .../infomation/message_ambulance_team.py | 159 ++++++++++++++++++ .../standard/bundle/standard_message.py | 53 ++++++ .../bundle/standard_message_priority.py | 14 ++ 4 files changed, 291 insertions(+), 5 deletions(-) create mode 100644 adf_core_python/core/agent/communication/standard/bundle/infomation/message_ambulance_team.py create mode 100644 adf_core_python/core/agent/communication/standard/bundle/standard_message.py create mode 100644 adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index 50056da..805d29c 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -3,15 +3,28 @@ from typing import Any, NoReturn from rcrs_core.agents.agent import Agent as RCRSAgent +from rcrs_core.commands.Command import Command +from rcrs_core.config.config import Config as RCRSConfig from rcrs_core.connection.URN import Entity as EntityURN +from rcrs_core.worldmodel.changeSet import ChangeSet from rcrs_core.worldmodel.worldmodel import WorldModel from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.communication.standard.standard_communication_module import ( + StandardCommunicationModule, +) from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData -from adf_core_python.core.agent.info.scenario_info import Mode +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData -from adf_core_python.core.logger.logger import get_logger +from adf_core_python.core.component.communication.communication_module import ( + CommunicationModule, +) +from adf_core_python.core.config.config import Config +from adf_core_python.core.launcher.config_key import ConfigKey +from adf_core_python.core.logger.logger import get_agent_logger, get_logger class Agent(RCRSAgent): @@ -21,7 +34,7 @@ def __init__( name: str, is_debug: bool, team_name: str, - deta_storage_name: str, + data_storage_name: str, module_config: ModuleConfig, develop_data: DevelopData, ) -> None: @@ -41,13 +54,60 @@ def __init__( self.is_precompute = is_precompute if is_precompute: - # PrecomputeData.remove_date(deta_storage_name) + # PrecomputeData.remove_date(data_storage_name) self.mode = Mode.PRECOMPUTATION self.module_config = module_config self.develop_data = develop_data - self.precompute_data = PrecomputeData(deta_storage_name) + self.precompute_data = PrecomputeData(data_storage_name) self.message_manager: MessageManager = MessageManager() + self.communication_module: CommunicationModule = StandardCommunicationModule() + + def post_connect(self) -> None: + self.world_model.index_entities() + + config = Config() + if self.config is not None: + rcrc_config: RCRSConfig = self.config + for key, value in rcrc_config.data.items(): + config.set_value(key, value) + for key, value in rcrc_config.int_data.items(): + config.set_value(key, value) + for key, value in rcrc_config.float_data.items(): + config.set_value(key, value) + for key, value in rcrc_config.boolean_data.items(): + config.set_value(key, value) + for key, value in rcrc_config.array_data.items(): + config.set_value(key, value) + + if self.is_precompute: + self._mode = Mode.PRECOMPUTATION + else: + # if self._precompute_data.is_ready(): + # self._mode = Mode.PRECOMPUTED + # else: + # self._mode = Mode.NON_PRECOMPUTE + self._mode = Mode.NON_PRECOMPUTE + + config.set_value(ConfigKey.KEY_DEBUG_FLAG, self.is_debug) + config.set_value( + ConfigKey.KEY_DEVELOP_FLAG, self.develop_data.is_develop_mode() + ) + self.ignore_time = config.get_value("kernel.agents.ignoreuntil", 3) + self.scenario_info: ScenarioInfo = ScenarioInfo(config, self._mode) + self.world_info: WorldInfo = WorldInfo(self.world_model) + self.agent_info = AgentInfo(self, self.world_model) + self.logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self.agent_info, + ) + + def think(self, time: int, change_set: ChangeSet, hear: list[Command]) -> None: + self.agent_info.record_think_start_time() + self.agent_info.set_time(time) + + # if time == 1: + # self.message_manager.register_message_class() def handle_connect_error(self, msg: Any) -> NoReturn: self.logger.error( diff --git a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_ambulance_team.py b/adf_core_python/core/agent/communication/standard/bundle/infomation/message_ambulance_team.py new file mode 100644 index 0000000..3c11291 --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/bundle/infomation/message_ambulance_team.py @@ -0,0 +1,159 @@ +from typing import TYPE_CHECKING, Optional + +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) + +if TYPE_CHECKING: + from rcrs_core.entities.ambulanceTeam import AmbulanceTeamEntity + + +class MessageAmbulanceTeam(StandardMessage): + ACTION_REST: int = 0 + ACTION_MOVE: int = 1 + ACTION_RESCUE: int = 2 + ACTION_LOAD: int = 3 + ACTION_UNLOAD: int = 4 + + SIZE_AMBULANCE_TEAM_ENTITY_ID: int = 32 + SIZE_AMBULANCE_TEAM_HP: int = 14 + SIZE_AMBULANCE_TEAM_BURIEDNESS: int = 13 + SIZE_AMBULANCE_TEAM_DAMAGE: int = 14 + SIZE_AMBULANCE_TEAM_POSITION: int = 32 + SIZE_TARGET_ENTITY_ID: int = 32 + SIZE_ACTION: int = 4 + + def __init__( + self, + is_wireless_message: bool, + sender_id: int, + ttl: int, + priority: StandardMessagePriority, + ambulance_team: AmbulanceTeamEntity, + action: int, + target_entity_id: EntityID, + ): + super().__init__(is_wireless_message, sender_id, ttl, priority) + self._ambulance_team_entity_id: Optional[EntityID] = ambulance_team.get_id() + self._ambulance_team_hp: Optional[int] = ambulance_team.get_hp() or -1 + self._ambulance_team_buriedness: Optional[int] = ( + ambulance_team.get_buriedness() or -1 + ) + self._ambulance_team_damage: Optional[int] = ambulance_team.get_damage() or -1 + self._ambulance_team_position: Optional[EntityID] = ( + ambulance_team.get_position() or EntityID(-1) + ) + self._target_entity_id: Optional[EntityID] = target_entity_id + self._action: Optional[int] = action + + def get_byte_size(self) -> int: + return self.to_bytes().__len__() + + def to_bytes(self) -> bytes: + byte_array = bytearray() + self.write_with_exist_flag( + byte_array, + self._ambulance_team_entity_id.get_value() + if self._ambulance_team_entity_id + else None, + self.SIZE_AMBULANCE_TEAM_ENTITY_ID, + ) + self.write_with_exist_flag( + byte_array, self._ambulance_team_hp, self.SIZE_AMBULANCE_TEAM_HP + ) + self.write_with_exist_flag( + byte_array, + self._ambulance_team_buriedness, + self.SIZE_AMBULANCE_TEAM_BURIEDNESS, + ) + self.write_with_exist_flag( + byte_array, self._ambulance_team_damage, self.SIZE_AMBULANCE_TEAM_DAMAGE + ) + self.write_with_exist_flag( + byte_array, + self._ambulance_team_position.get_value() + if self._ambulance_team_position + else None, + self.SIZE_AMBULANCE_TEAM_POSITION, + ) + self.write_with_exist_flag( + byte_array, + self._target_entity_id.get_value() if self._target_entity_id else None, + self.SIZE_TARGET_ENTITY_ID, + ) + self.write_with_exist_flag(byte_array, self._action, self.SIZE_ACTION) + return bytes(byte_array) + + def from_bytes(self, bytes: bytes) -> None: + byte_array = bytearray(bytes) + raw_ambulance_team_entity_id = self.read_with_exist_flag( + byte_array, self.SIZE_AMBULANCE_TEAM_ENTITY_ID + ) + self._ambulance_team_entity_id = ( + EntityID(raw_ambulance_team_entity_id) + if raw_ambulance_team_entity_id + else None + ) + self._ambulance_team_hp = self.read_with_exist_flag( + byte_array, self.SIZE_AMBULANCE_TEAM_HP + ) + self._ambulance_team_buriedness = self.read_with_exist_flag( + byte_array, self.SIZE_AMBULANCE_TEAM_BURIEDNESS + ) + self._ambulance_team_damage = self.read_with_exist_flag( + byte_array, self.SIZE_AMBULANCE_TEAM_DAMAGE + ) + raw_ambulance_team_position = self.read_with_exist_flag( + byte_array, self.SIZE_AMBULANCE_TEAM_POSITION + ) + self._ambulance_team_position = ( + EntityID(raw_ambulance_team_position) + if raw_ambulance_team_position + else None + ) + raw_target_entity_id = self.read_with_exist_flag( + byte_array, self.SIZE_TARGET_ENTITY_ID + ) + self._target_entity_id = ( + EntityID(raw_target_entity_id) if raw_target_entity_id else None + ) + self._action = self.read_with_exist_flag(byte_array, self.SIZE_ACTION) + + def get_check_key(self) -> str: + target_id_value: str = ( + str(self._target_entity_id.get_value()) + if self._target_entity_id + else "None" + ) + ambulance_team_id_value: str = ( + str(self._ambulance_team_entity_id.get_value()) + if self._ambulance_team_entity_id + else "None" + ) + return f"{self.__class__.__name__} > agent: {ambulance_team_id_value}, target: {target_id_value}, action: {self._action}" + + def get_ambulance_team_entity_id(self) -> Optional[EntityID]: + return self._ambulance_team_entity_id + + def get_ambulance_team_hp(self) -> Optional[int]: + return self._ambulance_team_hp + + def get_ambulance_team_buriedness(self) -> Optional[int]: + return self._ambulance_team_buriedness + + def get_ambulance_team_damage(self) -> Optional[int]: + return self._ambulance_team_damage + + def get_ambulance_team_position(self) -> Optional[EntityID]: + return self._ambulance_team_position + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._target_entity_id + + def get_action(self) -> Optional[int]: + return self._action diff --git a/adf_core_python/core/agent/communication/standard/bundle/standard_message.py b/adf_core_python/core/agent/communication/standard/bundle/standard_message.py new file mode 100644 index 0000000..db6c8dd --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/bundle/standard_message.py @@ -0,0 +1,53 @@ +from typing import Optional + +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) +from adf_core_python.core.component.communication.communication_message import ( + CommunicationMessage, +) + + +class StandardMessage(CommunicationMessage): + def __init__( + self, + is_wireless_message: bool, + sender_id: int, + ttl: int, + priority: StandardMessagePriority, + ): + super().__init__(is_wireless_message) + self._sender_id = sender_id + self._ttl = ttl + self._priority = priority + + def get_sender_entity_id(self) -> EntityID: + return EntityID(self._sender_id) + + def get_ttl(self) -> int: + return self._ttl + + def get_priority(self) -> StandardMessagePriority: + return self._priority + + def write_with_exist_flag( + self, byte_array: bytearray, value: Optional[int], size: int + ) -> None: + if value is None: + byte_array.extend(b"\b0") + else: + byte_array.extend(b"\b1") + byte_array.extend(value.to_bytes(size, "big")) + + def read_with_exist_flag(self, byte_array: bytearray, size: int) -> Optional[int]: + exist_flag = byte_array.pop(0) + if exist_flag == 0: + return None + elif exist_flag == 1: + value = int.from_bytes(byte_array[:size], "big") + del byte_array[:size] + return value + else: + raise ValueError("Invalid exist flag") diff --git a/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py b/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py new file mode 100644 index 0000000..1c6b17f --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py @@ -0,0 +1,14 @@ +from enum import Enum + + +class StandardMessagePriority(Enum): + """ + Standard message priorities. + """ + + LOW = 0 + NORMAL = 1 + HIGH = 2 + + def __str__(self) -> str: + return self.name.lower() From 48fa3b3ab3551bd17c504a52ecd0fdfd287ad664 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 21 Oct 2024 02:52:07 +0900 Subject: [PATCH 115/249] feat: add info message --- adf_core_python/core/agent/agent.py | 2 +- .../infomation/message_ambulance_team.py | 101 +++++--- .../bundle/infomation/message_building.py | 110 ++++++++ .../bundle/infomation/message_civilian.py | 123 +++++++++ .../bundle/infomation/message_fire_brigade.py | 185 ++++++++++++++ .../bundle/infomation/message_police_force.py | 169 ++++++++++++ .../bundle/infomation/message_road.py | 167 ++++++++++++ .../standard/bundle/standard_message.py | 25 +- poetry.lock | 240 ++++++++++++++---- pyproject.toml | 2 +- 10 files changed, 1023 insertions(+), 101 deletions(-) create mode 100644 adf_core_python/core/agent/communication/standard/bundle/infomation/message_building.py create mode 100644 adf_core_python/core/agent/communication/standard/bundle/infomation/message_civilian.py create mode 100644 adf_core_python/core/agent/communication/standard/bundle/infomation/message_fire_brigade.py create mode 100644 adf_core_python/core/agent/communication/standard/bundle/infomation/message_police_force.py create mode 100644 adf_core_python/core/agent/communication/standard/bundle/infomation/message_road.py diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index 805d29c..2e86e21 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -115,7 +115,7 @@ def handle_connect_error(self, msg: Any) -> NoReturn: ) sys.exit(1) - def precompute(self): + def precompute(self) -> None: pass @abstractmethod diff --git a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_ambulance_team.py b/adf_core_python/core/agent/communication/standard/bundle/infomation/message_ambulance_team.py index 3c11291..6a462a2 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_ambulance_team.py +++ b/adf_core_python/core/agent/communication/standard/bundle/infomation/message_ambulance_team.py @@ -1,5 +1,9 @@ -from typing import TYPE_CHECKING, Optional +from __future__ import annotations +from typing import Optional + +from bitarray import bitarray +from rcrs_core.entities.ambulanceTeam import AmbulanceTeamEntity from rcrs_core.worldmodel.entityID import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( @@ -9,9 +13,6 @@ StandardMessagePriority, ) -if TYPE_CHECKING: - from rcrs_core.entities.ambulanceTeam import AmbulanceTeamEntity - class MessageAmbulanceTeam(StandardMessage): ACTION_REST: int = 0 @@ -31,22 +32,22 @@ class MessageAmbulanceTeam(StandardMessage): def __init__( self, is_wireless_message: bool, - sender_id: int, - ttl: int, priority: StandardMessagePriority, ambulance_team: AmbulanceTeamEntity, action: int, target_entity_id: EntityID, + sender_id: int = -1, + ttl: int = -1, ): - super().__init__(is_wireless_message, sender_id, ttl, priority) + super().__init__(is_wireless_message, priority, sender_id, ttl) self._ambulance_team_entity_id: Optional[EntityID] = ambulance_team.get_id() - self._ambulance_team_hp: Optional[int] = ambulance_team.get_hp() or -1 + self._ambulance_team_hp: Optional[int] = ambulance_team.get_hp() or None self._ambulance_team_buriedness: Optional[int] = ( - ambulance_team.get_buriedness() or -1 + ambulance_team.get_buriedness() or None ) - self._ambulance_team_damage: Optional[int] = ambulance_team.get_damage() or -1 + self._ambulance_team_damage: Optional[int] = ambulance_team.get_damage() or None self._ambulance_team_position: Optional[EntityID] = ( - ambulance_team.get_position() or EntityID(-1) + ambulance_team.get_position() or None ) self._target_entity_id: Optional[EntityID] = target_entity_id self._action: Optional[int] = action @@ -55,74 +56,92 @@ def get_byte_size(self) -> int: return self.to_bytes().__len__() def to_bytes(self) -> bytes: - byte_array = bytearray() + bit_array = bitarray() self.write_with_exist_flag( - byte_array, + bit_array, self._ambulance_team_entity_id.get_value() if self._ambulance_team_entity_id else None, self.SIZE_AMBULANCE_TEAM_ENTITY_ID, ) self.write_with_exist_flag( - byte_array, self._ambulance_team_hp, self.SIZE_AMBULANCE_TEAM_HP + bit_array, self._ambulance_team_hp, self.SIZE_AMBULANCE_TEAM_HP ) self.write_with_exist_flag( - byte_array, + bit_array, self._ambulance_team_buriedness, self.SIZE_AMBULANCE_TEAM_BURIEDNESS, ) self.write_with_exist_flag( - byte_array, self._ambulance_team_damage, self.SIZE_AMBULANCE_TEAM_DAMAGE + bit_array, self._ambulance_team_damage, self.SIZE_AMBULANCE_TEAM_DAMAGE ) self.write_with_exist_flag( - byte_array, + bit_array, self._ambulance_team_position.get_value() if self._ambulance_team_position else None, self.SIZE_AMBULANCE_TEAM_POSITION, ) self.write_with_exist_flag( - byte_array, + bit_array, self._target_entity_id.get_value() if self._target_entity_id else None, self.SIZE_TARGET_ENTITY_ID, ) - self.write_with_exist_flag(byte_array, self._action, self.SIZE_ACTION) - return bytes(byte_array) + self.write_with_exist_flag(bit_array, self._action, self.SIZE_ACTION) + return bit_array.tobytes() - def from_bytes(self, bytes: bytes) -> None: - byte_array = bytearray(bytes) - raw_ambulance_team_entity_id = self.read_with_exist_flag( - byte_array, self.SIZE_AMBULANCE_TEAM_ENTITY_ID + @classmethod + def from_bytes(cls, bytes: bytes) -> MessageAmbulanceTeam: + bit_array = bitarray() + bit_array.frombytes(bytes) + raw_ambulance_team_entity_id = cls.read_with_exist_flag( + bit_array, cls.SIZE_AMBULANCE_TEAM_ENTITY_ID ) - self._ambulance_team_entity_id = ( + ambulance_team_entity_id = ( EntityID(raw_ambulance_team_entity_id) - if raw_ambulance_team_entity_id + if raw_ambulance_team_entity_id is not None else None ) - self._ambulance_team_hp = self.read_with_exist_flag( - byte_array, self.SIZE_AMBULANCE_TEAM_HP + ambulance_team_hp = cls.read_with_exist_flag( + bit_array, cls.SIZE_AMBULANCE_TEAM_HP ) - self._ambulance_team_buriedness = self.read_with_exist_flag( - byte_array, self.SIZE_AMBULANCE_TEAM_BURIEDNESS + ambulance_team_buriedness = cls.read_with_exist_flag( + bit_array, cls.SIZE_AMBULANCE_TEAM_BURIEDNESS ) - self._ambulance_team_damage = self.read_with_exist_flag( - byte_array, self.SIZE_AMBULANCE_TEAM_DAMAGE + ambulance_team_damage = cls.read_with_exist_flag( + bit_array, cls.SIZE_AMBULANCE_TEAM_DAMAGE ) - raw_ambulance_team_position = self.read_with_exist_flag( - byte_array, self.SIZE_AMBULANCE_TEAM_POSITION + + raw_ambulance_team_position = cls.read_with_exist_flag( + bit_array, cls.SIZE_AMBULANCE_TEAM_POSITION ) - self._ambulance_team_position = ( + ambulance_team_position = ( EntityID(raw_ambulance_team_position) - if raw_ambulance_team_position + if raw_ambulance_team_position is not None else None ) - raw_target_entity_id = self.read_with_exist_flag( - byte_array, self.SIZE_TARGET_ENTITY_ID + + raw_target_entity_id = cls.read_with_exist_flag( + bit_array, cls.SIZE_TARGET_ENTITY_ID + ) + target_entity_id = ( + EntityID(raw_target_entity_id) if raw_target_entity_id is not None else None + ) + action = cls.read_with_exist_flag(bit_array, cls.SIZE_ACTION) + ambulance_team = AmbulanceTeamEntity( + ambulance_team_entity_id, ) - self._target_entity_id = ( - EntityID(raw_target_entity_id) if raw_target_entity_id else None + ambulance_team.set_hp(ambulance_team_hp) + ambulance_team.set_buriedness(ambulance_team_buriedness) + ambulance_team.set_damage(ambulance_team_damage) + ambulance_team.set_position(ambulance_team_position) + return MessageAmbulanceTeam( + False, + StandardMessagePriority.NORMAL, + ambulance_team, + action or -1, + target_entity_id or EntityID(-1), ) - self._action = self.read_with_exist_flag(byte_array, self.SIZE_ACTION) def get_check_key(self) -> str: target_id_value: str = ( diff --git a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_building.py b/adf_core_python/core/agent/communication/standard/bundle/infomation/message_building.py new file mode 100644 index 0000000..e26d00c --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/bundle/infomation/message_building.py @@ -0,0 +1,110 @@ +from __future__ import annotations + +from typing import Optional + +from bitarray import bitarray +from rcrs_core.entities.building import Building +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) + + +class MessageBuilding(StandardMessage): + SIZE_BUILDING_ENTITY_ID: int = 32 + SIZE_BUILDING_BROKENNESS: int = 32 + SIZE_BUILDING_FIREYNESS: int = 32 + SIZE_BUILDING_TEMPERATURE: int = 32 + + def __init__( + self, + is_wireless_message: bool, + building: Building, + priority: StandardMessagePriority = StandardMessagePriority.NORMAL, + sender_id: int = -1, + ttl: int = -1, + ): + super().__init__(is_wireless_message, priority, sender_id, ttl) + self._building_entity_id: Optional[EntityID] = building.get_id() + self._building_brokenness: Optional[int] = building.get_brokenness() or None + self._building_fireyness: Optional[int] = building.get_fieryness() or None + self._building_temperature: Optional[int] = building.get_temperature() or None + + def get_byte_size(self) -> int: + return self.to_bytes().__len__() + + def to_bytes(self) -> bytes: + bit_array = bitarray() + self.write_with_exist_flag( + bit_array, + self._building_entity_id.get_value() if self._building_entity_id else None, + self.SIZE_BUILDING_ENTITY_ID, + ) + self.write_with_exist_flag( + bit_array, + self._building_brokenness, + self.SIZE_BUILDING_BROKENNESS, + ) + self.write_with_exist_flag( + bit_array, + self._building_fireyness, + self.SIZE_BUILDING_FIREYNESS, + ) + self.write_with_exist_flag( + bit_array, + self._building_temperature, + self.SIZE_BUILDING_TEMPERATURE, + ) + return bit_array.tobytes() + + @classmethod + def from_bytes(cls, bytes: bytes) -> MessageBuilding: + bit_array = bitarray() + bit_array.frombytes(bytes) + raw_building_entity_id = cls.read_with_exist_flag( + bit_array, cls.SIZE_BUILDING_ENTITY_ID + ) + building_entity_id = ( + EntityID(raw_building_entity_id) if raw_building_entity_id else None + ) + building_brokenness = cls.read_with_exist_flag( + bit_array, cls.SIZE_BUILDING_BROKENNESS + ) + building_fireyness = cls.read_with_exist_flag( + bit_array, cls.SIZE_BUILDING_FIREYNESS + ) + building_temperature = cls.read_with_exist_flag( + bit_array, cls.SIZE_BUILDING_TEMPERATURE + ) + building = Building( + building_entity_id.get_value() if building_entity_id else None + ) + building.set_brokenness(building_brokenness) + building.set_fieryness(building_fireyness) + building.set_temperature(building_temperature) + return MessageBuilding( + False, + building, + ) + + def get_check_key(self) -> str: + building_entity_id_value = ( + self._building_entity_id.get_value() if self._building_entity_id else None + ) + return f"{self.__class__.__name__} > building: {building_entity_id_value}" + + def get_building_entity_id(self) -> Optional[EntityID]: + return self._building_entity_id + + def get_building_brokenness(self) -> Optional[int]: + return self._building_brokenness + + def get_building_fireyness(self) -> Optional[int]: + return self._building_fireyness + + def get_building_temperature(self) -> Optional[int]: + return self._building_temperature diff --git a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_civilian.py b/adf_core_python/core/agent/communication/standard/bundle/infomation/message_civilian.py new file mode 100644 index 0000000..bc3c73b --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/bundle/infomation/message_civilian.py @@ -0,0 +1,123 @@ +from __future__ import annotations + +from typing import Optional + +from bitarray import bitarray +from rcrs_core.entities.civilian import Civilian +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) + + +class MessageCivilian(StandardMessage): + SIZE_CIVILIAN_ENTITY_ID: int = 32 + SIZE_CIVILIAN_HP: int = 14 + SIZE_CIVILIAN_BURIEDNESS: int = 13 + SIZE_CIVILIAN_DAMAGE: int = 14 + SIZE_CIVILIAN_POSITION: int = 32 + + def __init__( + self, + is_wireless_message: bool, + civilian: Civilian, + priority: StandardMessagePriority = StandardMessagePriority.NORMAL, + sender_id: int = -1, + ttl: int = -1, + ): + super().__init__(is_wireless_message, priority, sender_id, ttl) + self._civilian_entity_id: Optional[EntityID] = civilian.get_id() + self._civilian_hp: Optional[int] = civilian.get_hp() or None + self._civilian_buriedness: Optional[int] = civilian.get_buriedness() or None + self._civilian_damage: Optional[int] = civilian.get_damage() or None + self._civilian_position: Optional[EntityID] = civilian.get_position() or None + + def get_byte_size(self) -> int: + return self.to_bytes().__len__() + + def to_bytes(self) -> bytes: + bit_array = bitarray() + self.write_with_exist_flag( + bit_array, + self._civilian_entity_id.get_value() if self._civilian_entity_id else None, + self.SIZE_CIVILIAN_ENTITY_ID, + ) + self.write_with_exist_flag( + bit_array, + self._civilian_hp, + self.SIZE_CIVILIAN_HP, + ) + self.write_with_exist_flag( + bit_array, + self._civilian_buriedness, + self.SIZE_CIVILIAN_BURIEDNESS, + ) + self.write_with_exist_flag( + bit_array, + self._civilian_damage, + self.SIZE_CIVILIAN_DAMAGE, + ) + self.write_with_exist_flag( + bit_array, + self._civilian_position.get_value() if self._civilian_position else None, + self.SIZE_CIVILIAN_POSITION, + ) + return bit_array.tobytes() + + @classmethod + def from_bytes(cls, bytes: bytes) -> MessageCivilian: + bit_array = bitarray() + bit_array.frombytes(bytes) + raw_civilian_entity_id = cls.read_with_exist_flag( + bit_array, cls.SIZE_CIVILIAN_ENTITY_ID + ) + civilian_entity_id = ( + EntityID(raw_civilian_entity_id) if raw_civilian_entity_id else None + ) + civilian_hp = cls.read_with_exist_flag(bit_array, cls.SIZE_CIVILIAN_HP) + civilian_buriedness = cls.read_with_exist_flag( + bit_array, cls.SIZE_CIVILIAN_BURIEDNESS + ) + civilian_damage = cls.read_with_exist_flag(bit_array, cls.SIZE_CIVILIAN_DAMAGE) + raw_civilian_position = cls.read_with_exist_flag( + bit_array, cls.SIZE_CIVILIAN_POSITION + ) + civilian_position = ( + EntityID(raw_civilian_position) if raw_civilian_position else None + ) + civilian = Civilian( + civilian_entity_id.get_value() if civilian_entity_id else None + ) + civilian.set_hp(civilian_hp) + civilian.set_buriedness(civilian_buriedness) + civilian.set_damage(civilian_damage) + civilian.set_position(civilian_position) + return MessageCivilian( + False, + civilian, + ) + + def get_check_key(self) -> str: + civilian_entity_id_value = ( + self._civilian_entity_id.get_value() if self._civilian_entity_id else None + ) + return f"{self.__class__.__name__} > civilian: {civilian_entity_id_value}" + + def get_civilian_entity_id(self) -> Optional[EntityID]: + return self._civilian_entity_id + + def get_civilian_hp(self) -> Optional[int]: + return self._civilian_hp + + def get_civilian_buriedness(self) -> Optional[int]: + return self._civilian_buriedness + + def get_civilian_damage(self) -> Optional[int]: + return self._civilian_damage + + def get_civilian_position(self) -> Optional[EntityID]: + return self._civilian_position diff --git a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_fire_brigade.py b/adf_core_python/core/agent/communication/standard/bundle/infomation/message_fire_brigade.py new file mode 100644 index 0000000..b2d9298 --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/bundle/infomation/message_fire_brigade.py @@ -0,0 +1,185 @@ +from __future__ import annotations + +from typing import Optional + +from bitarray import bitarray +from rcrs_core.entities.fireBrigade import FireBrigadeEntity +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) + + +class MessageFireBrigade(StandardMessage): + CTION_REST: int = 0 + ACTION_MOVE: int = 1 + ACTION_EXTINGUISH: int = 2 + ACTION_REFILL: int = 3 + ACTION_RESCUE: int = 4 + + SIZE_FIRE_BRIGADE_ENTITY_ID: int = 32 + SIZE_FIRE_BRIGADE_HP: int = 14 + SIZE_FIRE_BRIGADE_BURIEDNESS: int = 13 + SIZE_FIRE_BRIGADE_DAMAGE: int = 14 + SIZE_FIRE_BRIGADE_POSITION: int = 32 + SIZE_FIRE_BRIGADE_WATER: int = 14 + SIZE_TARGET_ENTITY_ID: int = 32 + SIZE_ACTION: int = 4 + + def __init__( + self, + is_wireless_message: bool, + fire_brigade: FireBrigadeEntity, + action: int, + target_entity_id: EntityID, + priority: StandardMessagePriority = StandardMessagePriority.NORMAL, + sender_id: int = -1, + ttl: int = -1, + ): + super().__init__(is_wireless_message, priority, sender_id, ttl) + self._fire_brigade_entity_id: Optional[EntityID] = fire_brigade.get_id() + self._fire_brigade_hp: Optional[int] = fire_brigade.get_hp() or None + self._fire_brigade_buriedness: Optional[int] = ( + fire_brigade.get_buriedness() or None + ) + self._fire_brigade_damage: Optional[int] = fire_brigade.get_damage() or None + self._fire_brigade_position: Optional[EntityID] = ( + fire_brigade.get_position() or None + ) + self._fire_brigade_water: Optional[int] = fire_brigade.get_water() or None + self._target_entity_id: Optional[EntityID] = target_entity_id + self._action: Optional[int] = action + + def get_byte_size(self) -> int: + return self.to_bytes().__len__() + + def to_bytes(self) -> bytes: + bit_array = bitarray() + self.write_with_exist_flag( + bit_array, + self._fire_brigade_entity_id.get_value() + if self._fire_brigade_entity_id + else None, + self.SIZE_FIRE_BRIGADE_ENTITY_ID, + ) + self.write_with_exist_flag( + bit_array, + self._fire_brigade_hp, + self.SIZE_FIRE_BRIGADE_HP, + ) + self.write_with_exist_flag( + bit_array, + self._fire_brigade_buriedness, + self.SIZE_FIRE_BRIGADE_BURIEDNESS, + ) + self.write_with_exist_flag( + bit_array, + self._fire_brigade_damage, + self.SIZE_FIRE_BRIGADE_DAMAGE, + ) + self.write_with_exist_flag( + bit_array, + self._fire_brigade_position.get_value() + if self._fire_brigade_position + else None, + self.SIZE_FIRE_BRIGADE_POSITION, + ) + self.write_with_exist_flag( + bit_array, + self._fire_brigade_water, + self.SIZE_FIRE_BRIGADE_WATER, + ) + self.write_with_exist_flag( + bit_array, + self._target_entity_id.get_value() if self._target_entity_id else None, + self.SIZE_TARGET_ENTITY_ID, + ) + self.write_with_exist_flag(bit_array, self._action, self.SIZE_ACTION) + return bit_array.tobytes() + + @classmethod + def from_bytes(cls, bytes: bytes) -> MessageFireBrigade: + bit_array = bitarray() + bit_array.frombytes(bytes) + raw_fire_brigade_entity_id = cls.read_with_exist_flag( + bit_array, cls.SIZE_FIRE_BRIGADE_ENTITY_ID + ) + fire_brigade_entity_id = ( + EntityID(raw_fire_brigade_entity_id) if raw_fire_brigade_entity_id else None + ) + fire_brigade_hp = cls.read_with_exist_flag(bit_array, cls.SIZE_FIRE_BRIGADE_HP) + fire_brigade_buriedness = cls.read_with_exist_flag( + bit_array, cls.SIZE_FIRE_BRIGADE_BURIEDNESS + ) + fire_brigade_damage = cls.read_with_exist_flag( + bit_array, cls.SIZE_FIRE_BRIGADE_DAMAGE + ) + raw_fire_brigade_position = cls.read_with_exist_flag( + bit_array, cls.SIZE_FIRE_BRIGADE_POSITION + ) + fire_brigade_position = ( + EntityID(raw_fire_brigade_position) if raw_fire_brigade_position else None + ) + fire_brigade_water = cls.read_with_exist_flag( + bit_array, cls.SIZE_FIRE_BRIGADE_WATER + ) + raw_target_entity_id = cls.read_with_exist_flag( + bit_array, cls.SIZE_TARGET_ENTITY_ID + ) + target_entity_id = ( + EntityID(raw_target_entity_id) if raw_target_entity_id else None + ) + action = cls.read_with_exist_flag(bit_array, cls.SIZE_ACTION) + fire_brigade = FireBrigadeEntity( + fire_brigade_entity_id.get_value() if fire_brigade_entity_id else None + ) + fire_brigade.set_hp(fire_brigade_hp) + fire_brigade.set_buriedness(fire_brigade_buriedness) + fire_brigade.set_damage(fire_brigade_damage) + fire_brigade.set_position(fire_brigade_position) + fire_brigade.set_water(fire_brigade_water) + return MessageFireBrigade( + False, + fire_brigade, + action or -1, + target_entity_id or EntityID(-1), + ) + + def get_check_key(self) -> str: + fire_brigade_entity_id_value = ( + self._fire_brigade_entity_id.get_value() + if self._fire_brigade_entity_id + else None + ) + target_entity_id_value = ( + self._target_entity_id.get_value() if self._target_entity_id else None + ) + return f"{self.__class__.__name__} > fire brigade: {fire_brigade_entity_id_value} > target: {target_entity_id_value} > action: {self._action}" + + def get_fire_brigade_entity_id(self) -> Optional[EntityID]: + return self._fire_brigade_entity_id + + def get_fire_brigade_hp(self) -> Optional[int]: + return self._fire_brigade_hp + + def get_fire_brigade_buriedness(self) -> Optional[int]: + return self._fire_brigade_buriedness + + def get_fire_brigade_damage(self) -> Optional[int]: + return self._fire_brigade_damage + + def get_fire_brigade_position(self) -> Optional[EntityID]: + return self._fire_brigade_position + + def get_fire_brigade_water(self) -> Optional[int]: + return self._fire_brigade_water + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._target_entity_id + + def get_action(self) -> Optional[int]: + return self._action diff --git a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_police_force.py b/adf_core_python/core/agent/communication/standard/bundle/infomation/message_police_force.py new file mode 100644 index 0000000..6f3c43c --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/bundle/infomation/message_police_force.py @@ -0,0 +1,169 @@ +from __future__ import annotations + +from typing import Optional + +from bitarray import bitarray +from rcrs_core.entities.policeForce import PoliceForceEntity +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) + + +class MessagePoliceForce(StandardMessage): + CTION_REST: int = 0 + ACTION_MOVE: int = 1 + ACTION_CLEAR: int = 2 + + SIZE_POLICE_FORCE_ENTITY_ID: int = 32 + SIZE_POLICE_FORCE_HP: int = 14 + SIZE_POLICE_FORCE_BURIEDNESS: int = 13 + SIZE_POLICE_FORCE_DAMAGE: int = 14 + SIZE_POLICE_FORCE_POSITION: int = 32 + SIZE_TARGET_ENTITY_ID: int = 32 + SIZE_ACTION: int = 4 + + def __init__( + self, + is_wireless_message: bool, + police_force: PoliceForceEntity, + action: int, + target_entity_id: EntityID, + priority: StandardMessagePriority = StandardMessagePriority.NORMAL, + sender_id: int = -1, + ttl: int = -1, + ): + super().__init__(is_wireless_message, priority, sender_id, ttl) + self._police_force_entity_id: Optional[EntityID] = police_force.get_id() + self._police_force_hp: Optional[int] = police_force.get_hp() or None + self._police_force_buriedness: Optional[int] = ( + police_force.get_buriedness() or None + ) + self._police_force_damage: Optional[int] = police_force.get_damage() or None + self._police_force_position: Optional[EntityID] = ( + police_force.get_position() or None + ) + self._target_entity_id: Optional[EntityID] = target_entity_id + self._action: Optional[int] = action + + def get_byte_size(self) -> int: + return self.to_bytes().__len__() + + def to_bytes(self) -> bytes: + bit_array = bitarray() + self.write_with_exist_flag( + bit_array, + self._police_force_entity_id.get_value() + if self._police_force_entity_id + else None, + self.SIZE_POLICE_FORCE_ENTITY_ID, + ) + self.write_with_exist_flag( + bit_array, + self._police_force_hp, + self.SIZE_POLICE_FORCE_HP, + ) + self.write_with_exist_flag( + bit_array, + self._police_force_buriedness, + self.SIZE_POLICE_FORCE_BURIEDNESS, + ) + self.write_with_exist_flag( + bit_array, + self._police_force_damage, + self.SIZE_POLICE_FORCE_DAMAGE, + ) + self.write_with_exist_flag( + bit_array, + self._police_force_position.get_value() + if self._police_force_position + else None, + self.SIZE_POLICE_FORCE_POSITION, + ) + self.write_with_exist_flag( + bit_array, + self._target_entity_id.get_value() if self._target_entity_id else None, + self.SIZE_TARGET_ENTITY_ID, + ) + self.write_with_exist_flag(bit_array, self._action, self.SIZE_ACTION) + return bit_array.tobytes() + + @classmethod + def from_bytes(cls, bytes: bytes) -> MessagePoliceForce: + bit_array = bitarray() + bit_array.frombytes(bytes) + raw_police_force_entity_id = cls.read_with_exist_flag( + bit_array, cls.SIZE_POLICE_FORCE_ENTITY_ID + ) + police_force_entity_id = ( + EntityID(raw_police_force_entity_id) if raw_police_force_entity_id else None + ) + police_force_hp = cls.read_with_exist_flag(bit_array, cls.SIZE_POLICE_FORCE_HP) + police_force_buriedness = cls.read_with_exist_flag( + bit_array, cls.SIZE_POLICE_FORCE_BURIEDNESS + ) + police_force_damage = cls.read_with_exist_flag( + bit_array, cls.SIZE_POLICE_FORCE_DAMAGE + ) + raw_police_force_position = cls.read_with_exist_flag( + bit_array, cls.SIZE_POLICE_FORCE_POSITION + ) + police_force_position = ( + EntityID(raw_police_force_position) if raw_police_force_position else None + ) + raw_target_entity_id = cls.read_with_exist_flag( + bit_array, cls.SIZE_TARGET_ENTITY_ID + ) + target_entity_id = ( + EntityID(raw_target_entity_id) if raw_target_entity_id else None + ) + action = cls.read_with_exist_flag(bit_array, cls.SIZE_ACTION) + police_force = PoliceForceEntity( + police_force_entity_id.get_value() if police_force_entity_id else None + ) + police_force.set_hp(police_force_hp) + police_force.set_buriedness(police_force_buriedness) + police_force.set_damage(police_force_damage) + police_force.set_position(police_force_position) + return MessagePoliceForce( + False, + police_force, + action or -1, + target_entity_id or EntityID(-1), + ) + + def get_check_key(self) -> str: + police_force_entity_id_value = ( + self._police_force_entity_id.get_value() + if self._police_force_entity_id + else None + ) + target_entity_id_value = ( + self._target_entity_id.get_value() if self._target_entity_id else None + ) + return f"{self.__class__.__name__} > police force: {police_force_entity_id_value} > target: {target_entity_id_value} > action: {self._action}" + + def get_police_force_entity_id(self) -> Optional[EntityID]: + return self._police_force_entity_id + + def get_police_force_hp(self) -> Optional[int]: + return self._police_force_hp + + def get_police_force_buriedness(self) -> Optional[int]: + return self._police_force_buriedness + + def get_police_force_damage(self) -> Optional[int]: + return self._police_force_damage + + def get_police_force_position(self) -> Optional[EntityID]: + return self._police_force_position + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._target_entity_id + + def get_action(self) -> Optional[int]: + return self._action diff --git a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_road.py b/adf_core_python/core/agent/communication/standard/bundle/infomation/message_road.py new file mode 100644 index 0000000..89a2863 --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/bundle/infomation/message_road.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +from typing import Optional + +from bitarray import bitarray +from rcrs_core.entities.blockade import Blockade +from rcrs_core.entities.road import Road +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) + + +class MessageRoad(StandardMessage): + CTION_REST: int = 0 + ACTION_MOVE: int = 1 + ACTION_CLEAR: int = 2 + + SIZE_ROAD_ENTITY_ID: int = 32 + SIZE_ROAD_BLOCKADE_ENTITY_ID: int = 32 + SIZE_ROAD_BLOCKADE_REPAIR_COST: int = 32 + SIZE_ROAD_BLOCKADE_X: int = 32 + SIZE_ROAD_BLOCKADE_Y: int = 32 + SIZE_PASSABLE: int = 1 + + def __init__( + self, + is_wireless_message: bool, + road: Road, + is_send_blockade_location: bool, + is_passable: Optional[bool], + blockade: Optional[Blockade], + priority: StandardMessagePriority = StandardMessagePriority.NORMAL, + sender_id: int = -1, + ttl: int = -1, + ): + super().__init__(is_wireless_message, priority, sender_id, ttl) + self._road_entity_id: Optional[EntityID] = road.get_id() + self._road_blockade_entity_id: Optional[EntityID] = None + self._road_blockade_repair_cost: Optional[int] = None + self._road_blockade_x: Optional[int] = None + self._road_blockade_y: Optional[int] = None + + if blockade: + self._road_blockade_entity_id = blockade.get_id() + self._road_blockade_repair_cost = blockade.get_repaire_cost() + if is_send_blockade_location: + self._road_blockade_x = blockade.get_x() or None + self._road_blockade_y = blockade.get_y() or None + + self._is_passable: Optional[bool] = is_passable + self._is_send_blockade_location: bool = is_send_blockade_location + + def get_byte_size(self) -> int: + return self.to_bytes().__len__() + + def to_bytes(self) -> bytes: + bit_array = bitarray() + self.write_with_exist_flag( + bit_array, + self._road_entity_id.get_value() if self._road_entity_id else None, + self.SIZE_ROAD_ENTITY_ID, + ) + self.write_with_exist_flag( + bit_array, + self._road_blockade_entity_id.get_value() + if self._road_blockade_entity_id + else None, + self.SIZE_ROAD_BLOCKADE_ENTITY_ID, + ) + self.write_with_exist_flag( + bit_array, + self._road_blockade_repair_cost + if self._road_blockade_repair_cost + else None, + self.SIZE_ROAD_BLOCKADE_REPAIR_COST, + ) + if self._is_send_blockade_location: + self.write_with_exist_flag( + bit_array, + self._road_blockade_x if self._road_blockade_x else None, + self.SIZE_ROAD_BLOCKADE_X, + ) + self.write_with_exist_flag( + bit_array, + self._road_blockade_y if self._road_blockade_y else None, + self.SIZE_ROAD_BLOCKADE_Y, + ) + else: + self.write_with_exist_flag(bit_array, None, self.SIZE_ROAD_BLOCKADE_X) + self.write_with_exist_flag(bit_array, None, self.SIZE_ROAD_BLOCKADE_Y) + self.write_with_exist_flag( + bit_array, + self._is_passable if self._is_passable else None, + self.SIZE_PASSABLE, + ) + return bit_array.tobytes() + + @classmethod + def from_bytes(cls, bytes: bytes) -> MessageRoad: + bit_array = bitarray() + bit_array.frombytes(bytes) + raw_road_entity_id = cls.read_with_exist_flag( + bit_array, cls.SIZE_ROAD_ENTITY_ID + ) + road_entity_id = EntityID(raw_road_entity_id) if raw_road_entity_id else None + raw_road_blockade_entity_id = cls.read_with_exist_flag( + bit_array, cls.SIZE_ROAD_BLOCKADE_ENTITY_ID + ) + road_blockade_entity_id = ( + EntityID(raw_road_blockade_entity_id) + if raw_road_blockade_entity_id + else None + ) + road_blockade_repair_cost = cls.read_with_exist_flag( + bit_array, cls.SIZE_ROAD_BLOCKADE_REPAIR_COST + ) + road_blockade_x = cls.read_with_exist_flag(bit_array, cls.SIZE_ROAD_BLOCKADE_X) + road_blockade_y = cls.read_with_exist_flag(bit_array, cls.SIZE_ROAD_BLOCKADE_Y) + is_passable = ( + True if cls.read_with_exist_flag(bit_array, cls.SIZE_PASSABLE) else False + ) + road = Road(road_entity_id.get_value() if road_entity_id else None) + blockade = Blockade( + road_blockade_entity_id.get_value() if road_blockade_entity_id else None + ) + blockade.set_repaire_cost(road_blockade_repair_cost) + blockade.set_x(road_blockade_x) + blockade.set_y(road_blockade_y) + return MessageRoad( + False, + road, + False, + is_passable, + blockade, + ) + + def get_check_key(self) -> str: + road_entity_id_value = ( + self._road_entity_id.get_value() if self._road_entity_id else None + ) + return f"{self.__class__.__name__} > road: {road_entity_id_value}" + + def get_road_entity_id(self) -> Optional[EntityID]: + return self._road_entity_id + + def get_road_blockade_entity_id(self) -> Optional[EntityID]: + return self._road_blockade_entity_id + + def get_road_blockade_repair_cost(self) -> Optional[int]: + return self._road_blockade_repair_cost + + def get_road_blockade_x(self) -> Optional[int]: + return self._road_blockade_x + + def get_road_blockade_y(self) -> Optional[int]: + return self._road_blockade_y + + def get_is_passable(self) -> Optional[bool]: + return self._is_passable + + def get_is_send_blockade_location(self) -> bool: + return self._is_send_blockade_location diff --git a/adf_core_python/core/agent/communication/standard/bundle/standard_message.py b/adf_core_python/core/agent/communication/standard/bundle/standard_message.py index db6c8dd..c66f66a 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/standard_message.py +++ b/adf_core_python/core/agent/communication/standard/bundle/standard_message.py @@ -1,5 +1,6 @@ from typing import Optional +from bitarray import bitarray from rcrs_core.worldmodel.entityID import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( @@ -14,14 +15,14 @@ class StandardMessage(CommunicationMessage): def __init__( self, is_wireless_message: bool, - sender_id: int, - ttl: int, priority: StandardMessagePriority, + sender_id: int = -1, + ttl: int = -1, ): super().__init__(is_wireless_message) + self._priority = priority self._sender_id = sender_id self._ttl = ttl - self._priority = priority def get_sender_entity_id(self) -> EntityID: return EntityID(self._sender_id) @@ -32,22 +33,24 @@ def get_ttl(self) -> int: def get_priority(self) -> StandardMessagePriority: return self._priority + @staticmethod def write_with_exist_flag( - self, byte_array: bytearray, value: Optional[int], size: int + bit_array: bitarray, value: Optional[int], size: int ) -> None: if value is None: - byte_array.extend(b"\b0") + bit_array.extend([False]) else: - byte_array.extend(b"\b1") - byte_array.extend(value.to_bytes(size, "big")) + bit_array.extend([True]) + bit_array.frombytes(value.to_bytes(size, "big")) - def read_with_exist_flag(self, byte_array: bytearray, size: int) -> Optional[int]: - exist_flag = byte_array.pop(0) + @staticmethod + def read_with_exist_flag(bit_array: bitarray, size: int) -> Optional[int]: + exist_flag = bit_array.pop(0) if exist_flag == 0: return None elif exist_flag == 1: - value = int.from_bytes(byte_array[:size], "big") - del byte_array[:size] + value = int.from_bytes(bit_array.tobytes()[:size], "big") + del bit_array[:size] return value else: raise ValueError("Invalid exist flag") diff --git a/poetry.lock b/poetry.lock index a96b272..50ac978 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,151 @@ # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +[[package]] +name = "bitarray" +version = "3.0.0" +description = "efficient arrays of booleans -- C extension" +optional = false +python-versions = "*" +files = [ + {file = "bitarray-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5ddbf71a97ad1d6252e6e93d2d703b624d0a5b77c153b12f9ea87d83e1250e0c"}, + {file = "bitarray-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0e7f24a0b01e6e6a0191c50b06ca8edfdec1988d9d2b264d669d2487f4f4680"}, + {file = "bitarray-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:150b7b29c36d9f1a24779aea723fdfc73d1c1c161dc0ea14990da27d4e947092"}, + {file = "bitarray-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8330912be6cb8e2fbfe8eb69f82dee139d605730cadf8d50882103af9ac83bb4"}, + {file = "bitarray-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e56ba8be5f17dee0ffa6d6ce85251e062ded2faa3cbd2558659c671e6c3bf96d"}, + {file = "bitarray-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffd94b4803811c738e504a4b499fb2f848b2f7412d71e6b517508217c1d7929d"}, + {file = "bitarray-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0255bd05ec7165e512c115423a5255a3f301417973d20a80fc5bfc3f3640bcb"}, + {file = "bitarray-3.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe606e728842389943a939258809dc5db2de831b1d2e0118515059e87f7bbc1a"}, + {file = "bitarray-3.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e89ea59a3ed86a6eb150d016ed28b1bedf892802d0ed32b5659d3199440f3ced"}, + {file = "bitarray-3.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cf0cc2e91dd38122dec2e6541efa99aafb0a62e118179218181eff720b4b8153"}, + {file = "bitarray-3.0.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:2d9fe3ee51afeb909b68f97e14c6539ace3f4faa99b21012e610bbe7315c388d"}, + {file = "bitarray-3.0.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:37be5482b9df3105bad00fdf7dc65244e449b130867c3879c9db1db7d72e508b"}, + {file = "bitarray-3.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0027b8f3bb2bba914c79115e96a59b9924aafa1a578223a7c4f0a7242d349842"}, + {file = "bitarray-3.0.0-cp310-cp310-win32.whl", hash = "sha256:628f93e9c2c23930bd1cfe21c634d6c84ec30f45f23e69aefe1fcd262186d7bb"}, + {file = "bitarray-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:0b655c3110e315219e266b2732609fddb0857bc69593de29f3c2ba74b7d3f51a"}, + {file = "bitarray-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:44c3e78b60070389b824d5a654afa1c893df723153c81904088d4922c3cfb6ac"}, + {file = "bitarray-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:545d36332de81e4742a845a80df89530ff193213a50b4cbef937ed5a44c0e5e5"}, + {file = "bitarray-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a9eb510cde3fa78c2e302bece510bf5ed494ec40e6b082dec753d6e22d5d1b1"}, + {file = "bitarray-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e3727ab63dfb6bde00b281934e2212bb7529ea3006c0031a556a84d2268bea5"}, + {file = "bitarray-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2055206ed653bee0b56628f6a4d248d53e5660228d355bbec0014bdfa27050ae"}, + {file = "bitarray-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:147542299f458bdb177f798726e5f7d39ab8491de4182c3c6d9885ed275a3c2b"}, + {file = "bitarray-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f761184b93092077c7f6b7dad7bd4e671c1620404a76620da7872ceb576a94"}, + {file = "bitarray-3.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e008b7b4ce6c7f7a54b250c45c28d4243cc2a3bbfd5298fa7dac92afda229842"}, + {file = "bitarray-3.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dfea514e665af278b2e1d4deb542de1cd4f77413bee83dd15ae16175976ea8d5"}, + {file = "bitarray-3.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:66d6134b7bb737b88f1d16478ad0927c571387f6054f4afa5557825a4c1b78e2"}, + {file = "bitarray-3.0.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3cd565253889940b4ec4768d24f101d9fe111cad4606fdb203ea16f9797cf9ed"}, + {file = "bitarray-3.0.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4800c91a14656789d2e67d9513359e23e8a534c8ee1482bb9b517a4cfc845200"}, + {file = "bitarray-3.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c2945e0390d1329c585c584c6b6d78be017d9c6a1288f9c92006fe907f69cc28"}, + {file = "bitarray-3.0.0-cp311-cp311-win32.whl", hash = "sha256:c23286abba0cb509733c6ce8f4013cd951672c332b2e184dbefbd7331cd234c8"}, + {file = "bitarray-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:ca79f02a98cbda1472449d440592a2fe2ad96fe55515a0447fa8864a38017cf8"}, + {file = "bitarray-3.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:184972c96e1c7e691be60c3792ca1a51dd22b7f25d96ebea502fe3c9b554f25d"}, + {file = "bitarray-3.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:787db8da5e9e29be712f7a6bce153c7bc8697ccc2c38633e347bb9c82475d5c9"}, + {file = "bitarray-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2da91ab3633c66999c2a352f0ca9ae064f553e5fc0eca231d28e7e305b83e942"}, + {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7edb83089acbf2c86c8002b96599071931dc4ea5e1513e08306f6f7df879a48b"}, + {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996d1b83eb904589f40974538223eaed1ab0f62be8a5105c280b9bd849e685c4"}, + {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4817d73d995bd2b977d9cde6050be8d407791cf1f84c8047fa0bea88c1b815bc"}, + {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d47bc4ff9b0e1624d613563c6fa7b80aebe7863c56c3df5ab238bb7134e8755"}, + {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aca0a9cd376beaccd9f504961de83e776dd209c2de5a4c78dc87a78edf61839b"}, + {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:572a61fba7e3a710a8324771322fba8488d134034d349dcd036a7aef74723a80"}, + {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a817ad70c1aff217530576b4f037dd9b539eb2926603354fcac605d824082ad1"}, + {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2ac67b658fa5426503e9581a3fb44a26a3b346c1abd17105735f07db572195b3"}, + {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:12f19ede03e685c5c588ab5ed63167999295ffab5e1126c5fe97d12c0718c18f"}, + {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcef31b062f756ba7eebcd7890c5d5de84b9d64ee877325257bcc9782288564a"}, + {file = "bitarray-3.0.0-cp312-cp312-win32.whl", hash = "sha256:656db7bdf1d81ec3b57b3cad7ec7276765964bcfd0eb81c5d1331f385298169c"}, + {file = "bitarray-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f785af6b7cb07a9b1e5db0dea9ef9e3e8bb3d74874a0a61303eab9c16acc1999"}, + {file = "bitarray-3.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7cb885c043000924554fe2124d13084c8fdae03aec52c4086915cd4cb87fe8be"}, + {file = "bitarray-3.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7814c9924a0b30ecd401f02f082d8697fc5a5be3f8d407efa6e34531ff3c306a"}, + {file = "bitarray-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bcf524a087b143ba736aebbb054bb399d49e77cf7c04ed24c728e411adc82bfa"}, + {file = "bitarray-3.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1d5abf1d6d910599ac16afdd9a0ed3e24f3b46af57f3070cf2792f236f36e0b"}, + {file = "bitarray-3.0.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9929051feeaf8d948cc0b1c9ce57748079a941a1a15c89f6014edf18adaade84"}, + {file = "bitarray-3.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96cf0898f8060b2d3ae491762ae871b071212ded97ff9e1e3a5229e9fefe544c"}, + {file = "bitarray-3.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab37da66a8736ad5a75a58034180e92c41e864da0152b84e71fcc253a2f69cd4"}, + {file = "bitarray-3.0.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beeb79e476d19b91fd6a3439853e4e5ba1b3b475920fa40d62bde719c8af786f"}, + {file = "bitarray-3.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f75fc0198c955d840b836059bd43e0993edbf119923029ca60c4fc017cefa54a"}, + {file = "bitarray-3.0.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f12cc7c7638074918cdcc7491aff897df921b092ffd877227892d2686e98f876"}, + {file = "bitarray-3.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dbe1084935b942fab206e609fa1ed3f46ad1f2612fb4833e177e9b2a5e006c96"}, + {file = "bitarray-3.0.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ac06dd72ee1e1b6e312504d06f75220b5894af1fb58f0c20643698f5122aea76"}, + {file = "bitarray-3.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00f9a88c56e373009ac3c73c55205cfbd9683fbd247e2f9a64bae3da78795252"}, + {file = "bitarray-3.0.0-cp313-cp313-win32.whl", hash = "sha256:9c6e52005e91803eb4e08c0a08a481fb55ddce97f926bae1f6fa61b3396b5b61"}, + {file = "bitarray-3.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:cb98d5b6eac4b2cf2a5a69f60a9c499844b8bea207059e9fc45c752436e6bb49"}, + {file = "bitarray-3.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:eb27c01b747649afd7e1c342961680893df6d8d81f832a6f04d8c8e03a8a54cc"}, + {file = "bitarray-3.0.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4683bff52f5a0fd523fb5d3138161ef87611e63968e1fcb6cf4b0c6a86970fe0"}, + {file = "bitarray-3.0.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb7302dbcfcb676f0b66f15891f091d0233c4fc23e1d4b9dc9b9e958156e347f"}, + {file = "bitarray-3.0.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:153d7c416a70951dcfa73487af05d2f49c632e95602f1620cd9a651fa2033695"}, + {file = "bitarray-3.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251cd5bd47f542893b2b61860eded54f34920ea47fd5bff038d85e7a2f7ae99b"}, + {file = "bitarray-3.0.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fa4b4d9fa90124b33b251ef74e44e737021f253dc7a9174e1b39f097451f7ca"}, + {file = "bitarray-3.0.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:18abdce7ab5d2104437c39670821cba0b32fdb9b2da9e6d17a4ff295362bd9dc"}, + {file = "bitarray-3.0.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:2855cc01ee370f7e6e3ec97eebe44b1453c83fb35080313145e2c8c3c5243afb"}, + {file = "bitarray-3.0.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:0cecaf2981c9cd2054547f651537b4f4939f9fe225d3fc2b77324b597c124e40"}, + {file = "bitarray-3.0.0-cp36-cp36m-musllinux_1_2_s390x.whl", hash = "sha256:22b00f65193fafb13aa644e16012c8b49e7d5cbb6bb72825105ff89aadaa01e3"}, + {file = "bitarray-3.0.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:20f30373f0af9cb583e4122348cefde93c82865dbcbccc4997108b3d575ece84"}, + {file = "bitarray-3.0.0-cp36-cp36m-win32.whl", hash = "sha256:aef404d5400d95c6ec86664df9924bde667c8865f8e33c9b7bd79823d53b3e5d"}, + {file = "bitarray-3.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:ec5b0f2d13da53e0975ac15ecbe8badb463bdb0bebaa09457f4df3320421915c"}, + {file = "bitarray-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:041c889e69c847b8a96346650e50f728b747ae176889199c49a3f31ae1de0e23"}, + {file = "bitarray-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc83ea003dd75e9ade3291ef0585577dd5524aec0c8c99305c0aaa2a7570d6db"}, + {file = "bitarray-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c33129b49196aa7965ac0f16fcde7b6ad8614b606caf01669a0277cef1afe1d"}, + {file = "bitarray-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ef5c787c8263c082a73219a69eb60a500e157a4ac69d1b8515ad836b0e71fb4"}, + {file = "bitarray-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e15c94d79810c5ab90ddf4d943f71f14332890417be896ca253f21fa3d78d2b1"}, + {file = "bitarray-3.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cd021ada988e73d649289cee00428b75564c46d55fbdcb0e3402e504b0ae5ea"}, + {file = "bitarray-3.0.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7f1c24be7519f16a47b7e2ad1a1ef73023d34d8cbe1a3a59b185fc14baabb132"}, + {file = "bitarray-3.0.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:000df24c183011b5d27c23d79970f49b6762e5bb5aacd25da9c3e9695c693222"}, + {file = "bitarray-3.0.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:42bf1b222c698b467097f58b9f59dc850dfa694dde4e08237407a6a103757aa3"}, + {file = "bitarray-3.0.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:648e7ce794928e8d11343b5da8ecc5b910af75a82ea1a4264d5d0a55c3785faa"}, + {file = "bitarray-3.0.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:f536fc4d1a683025f9caef0bebeafd60384054579ffe0825bb9bd8c59f8c55b8"}, + {file = "bitarray-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:a754c1464e7b946b1cac7300c582c6fba7d66e535cd1dab76d998ad285ac5a37"}, + {file = "bitarray-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e91d46d12781a14ccb8b284566b14933de4e3b29f8bc5e1c17de7a2001ad3b5b"}, + {file = "bitarray-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:904c1d5e3bd24f0c0d37a582d2461312033c91436a6a4f3bdeeceb4bea4a899d"}, + {file = "bitarray-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:47ccf9887bd595d4a0536f2310f0dcf89e17ab83b8befa7dc8727b8017120fda"}, + {file = "bitarray-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:71ad0139c95c9acf4fb62e203b428f9906157b15eecf3f30dc10b55919225896"}, + {file = "bitarray-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e002ac1073ac70e323a7a4bfa9ab95e7e1a85c79160799e265563f342b1557"}, + {file = "bitarray-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acc07211a59e2f245e9a06f28fa374d094fb0e71cf5366eef52abbb826ddc81e"}, + {file = "bitarray-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98a4070ddafabddaee70b2aa7cc6286cf73c37984169ab03af1782da2351059a"}, + {file = "bitarray-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7d09ef06ba57bea646144c29764bf6b870fb3c5558ca098191e07b6a1d40bf7"}, + {file = "bitarray-3.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce249ed981f428a8b61538ca82d3875847733d579dd40084ab8246549160f8a4"}, + {file = "bitarray-3.0.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ea40e98d751ed4b255db4a88fe8fb743374183f78470b9e9305aab186bf28ede"}, + {file = "bitarray-3.0.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:928b8b6dfcd015e1a81334cfdac02815da2a2407854492a80cf8a3a922b04052"}, + {file = "bitarray-3.0.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:fbb645477595ce2a0fbb678d1cfd08d3b896e5d56196d40fb9e114eeab9382b3"}, + {file = "bitarray-3.0.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:dc1937a0ff2671797d35243db4b596329842480d125a65e9fe964bcffaf16dfc"}, + {file = "bitarray-3.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a4f49ac31734fe654a68e2515c0da7f5bbdf2d52755ba09a42ac406f1f08c9d0"}, + {file = "bitarray-3.0.0-cp38-cp38-win32.whl", hash = "sha256:6d2a2ce73f9897268f58857ad6893a1a6680c5a6b28f79d21c7d33285a5ae646"}, + {file = "bitarray-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:b1047999f1797c3ea7b7c85261649249c243308dcf3632840d076d18fa72f142"}, + {file = "bitarray-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:39b38a3d45dac39d528c87b700b81dfd5e8dc8e9e1a102503336310ef837c3fd"}, + {file = "bitarray-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0e104f9399144fab6a892d379ba1bb4275e56272eb465059beef52a77b4e5ce6"}, + {file = "bitarray-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0879f839ec8f079fa60c3255966c2e1aa7196699a234d4e5b7898fbc321901b5"}, + {file = "bitarray-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9502c2230d59a4ace2fddfd770dad8e8b414cbd99517e7e56c55c20997c28b8d"}, + {file = "bitarray-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57d5ef854f8ec434f2ffd9ddcefc25a10848393fe2976e2be2c8c773cf5fef42"}, + {file = "bitarray-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3c36b2fcfebe15ad1c10a90c1d52a42bebe960adcbce340fef867203028fbe7"}, + {file = "bitarray-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66a33a537e781eac3a352397ce6b07eedf3a8380ef4a804f8844f3f45e335544"}, + {file = "bitarray-3.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa54c7e1da8cf4be0aab941ea284ec64033ede5d6de3fd47d75e77cafe986e9d"}, + {file = "bitarray-3.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a667ea05ba1ea81b722682276dbef1d36990f8908cf51e570099fd505a89f931"}, + {file = "bitarray-3.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d756bfeb62ca4fe65d2af7a39249d442c05070c047d03729ad6cd4c2e9b0f0bd"}, + {file = "bitarray-3.0.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c9e9fef0754867d88e948ce8351c9fd7e507d8514e0f242fd67c907b9cdf98b3"}, + {file = "bitarray-3.0.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:67a0b56dd02f2713f6f52cacb3f251afd67c94c5f0748026d307d87a81a8e15c"}, + {file = "bitarray-3.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d8c36ddc1923bcc4c11b9994c54eaae25034812a42400b7b8a86fe6d242166a2"}, + {file = "bitarray-3.0.0-cp39-cp39-win32.whl", hash = "sha256:1414a7102a3c4986f241480544f5c99f5d32258fb9b85c9c04e84e48c490ab35"}, + {file = "bitarray-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:8c9733d2ff9b7838ac04bf1048baea153174753e6a47312be14c83c6a395424b"}, + {file = "bitarray-3.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fef4e3b3f2084b4dae3e5316b44cda72587dcc81f68b4eb2dbda1b8d15261b61"}, + {file = "bitarray-3.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e9eee03f187cef1e54a4545124109ee0afc84398628b4b32ebb4852b4a66393"}, + {file = "bitarray-3.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cb5702dd667f4bb10fed056ffdc4ddaae8193a52cd74cb2cdb54e71f4ef2dd1"}, + {file = "bitarray-3.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:666e44b0458bb2894b64264a29f2cc7b5b2cbcc4c5e9cedfe1fdbde37a8e329a"}, + {file = "bitarray-3.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c756a92cf1c1abf01e56a4cc40cb89f0ff9147f2a0be5b557ec436a23ff464d8"}, + {file = "bitarray-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7e51e7f8289bf6bb631e1ef2a8f5e9ca287985ff518fe666abbdfdb6a848cb26"}, + {file = "bitarray-3.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fa5d8e4b28388b337face6ce4029be73585651a44866901513df44be9a491ab"}, + {file = "bitarray-3.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3963b80a68aedcd722a9978d261ae53cb9bb6a8129cc29790f0f10ce5aca287a"}, + {file = "bitarray-3.0.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b555006a7dea53f6bebc616a4d0249cecbf8f1fadf77860120a2e5dbdc2f167"}, + {file = "bitarray-3.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4ac2027ca650a7302864ed2528220d6cc6921501b383e9917afc7a2424a1e36d"}, + {file = "bitarray-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bf90aba4cff9e72e24ecdefe33bad608f147a23fa5c97790a5bab0e72fe62b6d"}, + {file = "bitarray-3.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a199e6d7c3bad5ba9d0e4dc00dde70ee7d111c9dfc521247fa646ef59fa57e"}, + {file = "bitarray-3.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43b6c7c4f4a7b80e86e24a76f4c6b9b67d03229ea16d7d403520616535c32196"}, + {file = "bitarray-3.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fc13da3518f14825b239374734fce93c1a9299ed7b558c3ec1d659ec7e4c70"}, + {file = "bitarray-3.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:369b6d457af94af901d632c7e625ca6caf0a7484110fc91c6290ce26bc4f1478"}, + {file = "bitarray-3.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ee040ad3b7dfa05e459713099f16373c1f2a6f68b43cb0575a66718e7a5daef4"}, + {file = "bitarray-3.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dad7ba2af80f9ec1dd988c3aca7992408ec0d0b4c215b65d353d95ab0070b10"}, + {file = "bitarray-3.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4839d3b64af51e4b8bb4a602563b98b9faeb34fd6c00ed23d7834e40a9d080fc"}, + {file = "bitarray-3.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f71f24b58e75a889b9915e3197865302467f13e7390efdea5b6afc7424b3a2ea"}, + {file = "bitarray-3.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:bcf0150ae0bcc4aa97bdfcb231b37bad1a59083c1b5012643b266012bf420e68"}, + {file = "bitarray-3.0.0.tar.gz", hash = "sha256:a2083dc20f0d828a7cdf7a16b20dae56aab0f43dc4f347a3b3039f6577992b03"}, +] + [[package]] name = "colorama" version = "0.4.6" @@ -46,43 +192,43 @@ files = [ [[package]] name = "mypy" -version = "1.13.0" +version = "1.12.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, - {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, - {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, - {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, - {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, - {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, - {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, - {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, - {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, - {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, - {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, - {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, - {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, - {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, - {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, - {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, - {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, - {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, - {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, - {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, - {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, - {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, + {file = "mypy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4397081e620dc4dc18e2f124d5e1d2c288194c2c08df6bdb1db31c38cd1fe1ed"}, + {file = "mypy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:684a9c508a283f324804fea3f0effeb7858eb03f85c4402a967d187f64562469"}, + {file = "mypy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cabe4cda2fa5eca7ac94854c6c37039324baaa428ecbf4de4567279e9810f9e"}, + {file = "mypy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:060a07b10e999ac9e7fa249ce2bdcfa9183ca2b70756f3bce9df7a92f78a3c0a"}, + {file = "mypy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:0eff042d7257f39ba4ca06641d110ca7d2ad98c9c1fb52200fe6b1c865d360ff"}, + {file = "mypy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b86de37a0da945f6d48cf110d5206c5ed514b1ca2614d7ad652d4bf099c7de7"}, + {file = "mypy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20c7c5ce0c1be0b0aea628374e6cf68b420bcc772d85c3c974f675b88e3e6e57"}, + {file = "mypy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a64ee25f05fc2d3d8474985c58042b6759100a475f8237da1f4faf7fcd7e6309"}, + {file = "mypy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:faca7ab947c9f457a08dcb8d9a8664fd438080e002b0fa3e41b0535335edcf7f"}, + {file = "mypy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:5bc81701d52cc8767005fdd2a08c19980de9ec61a25dbd2a937dfb1338a826f9"}, + {file = "mypy-1.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8462655b6694feb1c99e433ea905d46c478041a8b8f0c33f1dab00ae881b2164"}, + {file = "mypy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:923ea66d282d8af9e0f9c21ffc6653643abb95b658c3a8a32dca1eff09c06475"}, + {file = "mypy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ebf9e796521f99d61864ed89d1fb2926d9ab6a5fab421e457cd9c7e4dd65aa9"}, + {file = "mypy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e478601cc3e3fa9d6734d255a59c7a2e5c2934da4378f3dd1e3411ea8a248642"}, + {file = "mypy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:c72861b7139a4f738344faa0e150834467521a3fba42dc98264e5aa9507dd601"}, + {file = "mypy-1.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52b9e1492e47e1790360a43755fa04101a7ac72287b1a53ce817f35899ba0521"}, + {file = "mypy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48d3e37dd7d9403e38fa86c46191de72705166d40b8c9f91a3de77350daa0893"}, + {file = "mypy-1.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f106db5ccb60681b622ac768455743ee0e6a857724d648c9629a9bd2ac3f721"}, + {file = "mypy-1.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:233e11b3f73ee1f10efada2e6da0f555b2f3a5316e9d8a4a1224acc10e7181d3"}, + {file = "mypy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:4ae8959c21abcf9d73aa6c74a313c45c0b5a188752bf37dace564e29f06e9c1b"}, + {file = "mypy-1.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eafc1b7319b40ddabdc3db8d7d48e76cfc65bbeeafaa525a4e0fa6b76175467f"}, + {file = "mypy-1.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9b9ce1ad8daeb049c0b55fdb753d7414260bad8952645367e70ac91aec90e07e"}, + {file = "mypy-1.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfe012b50e1491d439172c43ccb50db66d23fab714d500b57ed52526a1020bb7"}, + {file = "mypy-1.12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c40658d4fa1ab27cb53d9e2f1066345596af2f8fe4827defc398a09c7c9519b"}, + {file = "mypy-1.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:dee78a8b9746c30c1e617ccb1307b351ded57f0de0d287ca6276378d770006c0"}, + {file = "mypy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b5df6c8a8224f6b86746bda716bbe4dbe0ce89fd67b1fa4661e11bfe38e8ec8"}, + {file = "mypy-1.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5feee5c74eb9749e91b77f60b30771563327329e29218d95bedbe1257e2fe4b0"}, + {file = "mypy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:77278e8c6ffe2abfba6db4125de55f1024de9a323be13d20e4f73b8ed3402bd1"}, + {file = "mypy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dcfb754dea911039ac12434d1950d69a2f05acd4d56f7935ed402be09fad145e"}, + {file = "mypy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:06de0498798527451ffb60f68db0d368bd2bae2bbfb5237eae616d4330cc87aa"}, + {file = "mypy-1.12.0-py3-none-any.whl", hash = "sha256:fd313226af375d52e1e36c383f39bf3836e1f192801116b31b090dfcd3ec5266"}, + {file = "mypy-1.12.0.tar.gz", hash = "sha256:65a22d87e757ccd95cbbf6f7e181e6caa87128255eb2b6be901bb71b26d8a99d"}, ] [package.dependencies] @@ -197,22 +343,22 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "protobuf" -version = "5.28.3" +version = "5.28.2" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-5.28.3-cp310-abi3-win32.whl", hash = "sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24"}, - {file = "protobuf-5.28.3-cp310-abi3-win_amd64.whl", hash = "sha256:91fba8f445723fcf400fdbe9ca796b19d3b1242cd873907979b9ed71e4afe868"}, - {file = "protobuf-5.28.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a3f6857551e53ce35e60b403b8a27b0295f7d6eb63d10484f12bc6879c715687"}, - {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:3fa2de6b8b29d12c61911505d893afe7320ce7ccba4df913e2971461fa36d584"}, - {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135"}, - {file = "protobuf-5.28.3-cp38-cp38-win32.whl", hash = "sha256:3e6101d095dfd119513cde7259aa703d16c6bbdfae2554dfe5cfdbe94e32d548"}, - {file = "protobuf-5.28.3-cp38-cp38-win_amd64.whl", hash = "sha256:27b246b3723692bf1068d5734ddaf2fccc2cdd6e0c9b47fe099244d80200593b"}, - {file = "protobuf-5.28.3-cp39-cp39-win32.whl", hash = "sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535"}, - {file = "protobuf-5.28.3-cp39-cp39-win_amd64.whl", hash = "sha256:70585a70fc2dd4818c51287ceef5bdba6387f88a578c86d47bb34669b5552c36"}, - {file = "protobuf-5.28.3-py3-none-any.whl", hash = "sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed"}, - {file = "protobuf-5.28.3.tar.gz", hash = "sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b"}, + {file = "protobuf-5.28.2-cp310-abi3-win32.whl", hash = "sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d"}, + {file = "protobuf-5.28.2-cp310-abi3-win_amd64.whl", hash = "sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132"}, + {file = "protobuf-5.28.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7"}, + {file = "protobuf-5.28.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f"}, + {file = "protobuf-5.28.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f"}, + {file = "protobuf-5.28.2-cp38-cp38-win32.whl", hash = "sha256:87317e9bcda04a32f2ee82089a204d3a2f0d3c8aeed16568c7daf4756e4f1fe0"}, + {file = "protobuf-5.28.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0ea0123dac3399a2eeb1a1443d82b7afc9ff40241433296769f7da42d142ec3"}, + {file = "protobuf-5.28.2-cp39-cp39-win32.whl", hash = "sha256:ca53faf29896c526863366a52a8f4d88e69cd04ec9571ed6082fa117fac3ab36"}, + {file = "protobuf-5.28.2-cp39-cp39-win_amd64.whl", hash = "sha256:8ddc60bf374785fb7cb12510b267f59067fa10087325b8e1855b898a0d81d276"}, + {file = "protobuf-5.28.2-py3-none-any.whl", hash = "sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece"}, + {file = "protobuf-5.28.2.tar.gz", hash = "sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0"}, ] [[package]] @@ -342,7 +488,7 @@ rtree = "*" type = "git" url = "https://github.com/adf-python/rcrs-core-python" reference = "HEAD" -resolved_reference = "b353cd6e82005e20e099554d4f357d9c0cc2e052" +resolved_reference = "e6d1c51c7a03122f8c2f98e88272775ee77363ce" [[package]] name = "rtree" @@ -634,4 +780,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "f5041edb49c80e926991fbd658e04175d3c35e3e95fb90bf8762d244de7498f3" +content-hash = "6fdb27af56e45fff1fa8898664fb70a3df4a4258237612e8fc3d795d4c2dc45d" diff --git a/pyproject.toml b/pyproject.toml index b768e40..d41e193 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ pytest = "^8.3.2" types-pyyaml = "^6.0.12.20240808" scikit-learn = "^1.5.2" structlog = "^24.4.0" -shapely = "^2.0.6" +bitarray = "^3.0.0" [tool.poetry.group.dev.dependencies] From 2b19002b0035e9c7e528645c4e445e08010735a3 Mon Sep 17 00:00:00 2001 From: shima004 Date: Sat, 26 Oct 2024 12:16:05 +0900 Subject: [PATCH 116/249] update: package version --- poetry.lock | 156 +++++++++++++++++----------------------------------- 1 file changed, 49 insertions(+), 107 deletions(-) diff --git a/poetry.lock b/poetry.lock index 50ac978..c0b7ea8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -192,43 +192,43 @@ files = [ [[package]] name = "mypy" -version = "1.12.0" +version = "1.13.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4397081e620dc4dc18e2f124d5e1d2c288194c2c08df6bdb1db31c38cd1fe1ed"}, - {file = "mypy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:684a9c508a283f324804fea3f0effeb7858eb03f85c4402a967d187f64562469"}, - {file = "mypy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cabe4cda2fa5eca7ac94854c6c37039324baaa428ecbf4de4567279e9810f9e"}, - {file = "mypy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:060a07b10e999ac9e7fa249ce2bdcfa9183ca2b70756f3bce9df7a92f78a3c0a"}, - {file = "mypy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:0eff042d7257f39ba4ca06641d110ca7d2ad98c9c1fb52200fe6b1c865d360ff"}, - {file = "mypy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b86de37a0da945f6d48cf110d5206c5ed514b1ca2614d7ad652d4bf099c7de7"}, - {file = "mypy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20c7c5ce0c1be0b0aea628374e6cf68b420bcc772d85c3c974f675b88e3e6e57"}, - {file = "mypy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a64ee25f05fc2d3d8474985c58042b6759100a475f8237da1f4faf7fcd7e6309"}, - {file = "mypy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:faca7ab947c9f457a08dcb8d9a8664fd438080e002b0fa3e41b0535335edcf7f"}, - {file = "mypy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:5bc81701d52cc8767005fdd2a08c19980de9ec61a25dbd2a937dfb1338a826f9"}, - {file = "mypy-1.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8462655b6694feb1c99e433ea905d46c478041a8b8f0c33f1dab00ae881b2164"}, - {file = "mypy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:923ea66d282d8af9e0f9c21ffc6653643abb95b658c3a8a32dca1eff09c06475"}, - {file = "mypy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ebf9e796521f99d61864ed89d1fb2926d9ab6a5fab421e457cd9c7e4dd65aa9"}, - {file = "mypy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e478601cc3e3fa9d6734d255a59c7a2e5c2934da4378f3dd1e3411ea8a248642"}, - {file = "mypy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:c72861b7139a4f738344faa0e150834467521a3fba42dc98264e5aa9507dd601"}, - {file = "mypy-1.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52b9e1492e47e1790360a43755fa04101a7ac72287b1a53ce817f35899ba0521"}, - {file = "mypy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48d3e37dd7d9403e38fa86c46191de72705166d40b8c9f91a3de77350daa0893"}, - {file = "mypy-1.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f106db5ccb60681b622ac768455743ee0e6a857724d648c9629a9bd2ac3f721"}, - {file = "mypy-1.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:233e11b3f73ee1f10efada2e6da0f555b2f3a5316e9d8a4a1224acc10e7181d3"}, - {file = "mypy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:4ae8959c21abcf9d73aa6c74a313c45c0b5a188752bf37dace564e29f06e9c1b"}, - {file = "mypy-1.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eafc1b7319b40ddabdc3db8d7d48e76cfc65bbeeafaa525a4e0fa6b76175467f"}, - {file = "mypy-1.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9b9ce1ad8daeb049c0b55fdb753d7414260bad8952645367e70ac91aec90e07e"}, - {file = "mypy-1.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfe012b50e1491d439172c43ccb50db66d23fab714d500b57ed52526a1020bb7"}, - {file = "mypy-1.12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c40658d4fa1ab27cb53d9e2f1066345596af2f8fe4827defc398a09c7c9519b"}, - {file = "mypy-1.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:dee78a8b9746c30c1e617ccb1307b351ded57f0de0d287ca6276378d770006c0"}, - {file = "mypy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b5df6c8a8224f6b86746bda716bbe4dbe0ce89fd67b1fa4661e11bfe38e8ec8"}, - {file = "mypy-1.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5feee5c74eb9749e91b77f60b30771563327329e29218d95bedbe1257e2fe4b0"}, - {file = "mypy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:77278e8c6ffe2abfba6db4125de55f1024de9a323be13d20e4f73b8ed3402bd1"}, - {file = "mypy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dcfb754dea911039ac12434d1950d69a2f05acd4d56f7935ed402be09fad145e"}, - {file = "mypy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:06de0498798527451ffb60f68db0d368bd2bae2bbfb5237eae616d4330cc87aa"}, - {file = "mypy-1.12.0-py3-none-any.whl", hash = "sha256:fd313226af375d52e1e36c383f39bf3836e1f192801116b31b090dfcd3ec5266"}, - {file = "mypy-1.12.0.tar.gz", hash = "sha256:65a22d87e757ccd95cbbf6f7e181e6caa87128255eb2b6be901bb71b26d8a99d"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, ] [package.dependencies] @@ -343,22 +343,22 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "protobuf" -version = "5.28.2" +version = "5.28.3" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-5.28.2-cp310-abi3-win32.whl", hash = "sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d"}, - {file = "protobuf-5.28.2-cp310-abi3-win_amd64.whl", hash = "sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132"}, - {file = "protobuf-5.28.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7"}, - {file = "protobuf-5.28.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f"}, - {file = "protobuf-5.28.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f"}, - {file = "protobuf-5.28.2-cp38-cp38-win32.whl", hash = "sha256:87317e9bcda04a32f2ee82089a204d3a2f0d3c8aeed16568c7daf4756e4f1fe0"}, - {file = "protobuf-5.28.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0ea0123dac3399a2eeb1a1443d82b7afc9ff40241433296769f7da42d142ec3"}, - {file = "protobuf-5.28.2-cp39-cp39-win32.whl", hash = "sha256:ca53faf29896c526863366a52a8f4d88e69cd04ec9571ed6082fa117fac3ab36"}, - {file = "protobuf-5.28.2-cp39-cp39-win_amd64.whl", hash = "sha256:8ddc60bf374785fb7cb12510b267f59067fa10087325b8e1855b898a0d81d276"}, - {file = "protobuf-5.28.2-py3-none-any.whl", hash = "sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece"}, - {file = "protobuf-5.28.2.tar.gz", hash = "sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0"}, + {file = "protobuf-5.28.3-cp310-abi3-win32.whl", hash = "sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24"}, + {file = "protobuf-5.28.3-cp310-abi3-win_amd64.whl", hash = "sha256:91fba8f445723fcf400fdbe9ca796b19d3b1242cd873907979b9ed71e4afe868"}, + {file = "protobuf-5.28.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a3f6857551e53ce35e60b403b8a27b0295f7d6eb63d10484f12bc6879c715687"}, + {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:3fa2de6b8b29d12c61911505d893afe7320ce7ccba4df913e2971461fa36d584"}, + {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135"}, + {file = "protobuf-5.28.3-cp38-cp38-win32.whl", hash = "sha256:3e6101d095dfd119513cde7259aa703d16c6bbdfae2554dfe5cfdbe94e32d548"}, + {file = "protobuf-5.28.3-cp38-cp38-win_amd64.whl", hash = "sha256:27b246b3723692bf1068d5734ddaf2fccc2cdd6e0c9b47fe099244d80200593b"}, + {file = "protobuf-5.28.3-cp39-cp39-win32.whl", hash = "sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535"}, + {file = "protobuf-5.28.3-cp39-cp39-win_amd64.whl", hash = "sha256:70585a70fc2dd4818c51287ceef5bdba6387f88a578c86d47bb34669b5552c36"}, + {file = "protobuf-5.28.3-py3-none-any.whl", hash = "sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed"}, + {file = "protobuf-5.28.3.tar.gz", hash = "sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b"}, ] [[package]] @@ -488,7 +488,7 @@ rtree = "*" type = "git" url = "https://github.com/adf-python/rcrs-core-python" reference = "HEAD" -resolved_reference = "e6d1c51c7a03122f8c2f98e88272775ee77363ce" +resolved_reference = "b353cd6e82005e20e099554d4f357d9c0cc2e052" [[package]] name = "rtree" @@ -630,64 +630,6 @@ dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodest doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] -[[package]] -name = "shapely" -version = "2.0.6" -description = "Manipulation and analysis of geometric objects" -optional = false -python-versions = ">=3.7" -files = [ - {file = "shapely-2.0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29a34e068da2d321e926b5073539fd2a1d4429a2c656bd63f0bd4c8f5b236d0b"}, - {file = "shapely-2.0.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c84c3f53144febf6af909d6b581bc05e8785d57e27f35ebaa5c1ab9baba13b"}, - {file = "shapely-2.0.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad2fae12dca8d2b727fa12b007e46fbc522148a584f5d6546c539f3464dccde"}, - {file = "shapely-2.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3304883bd82d44be1b27a9d17f1167fda8c7f5a02a897958d86c59ec69b705e"}, - {file = "shapely-2.0.6-cp310-cp310-win32.whl", hash = "sha256:3ec3a0eab496b5e04633a39fa3d5eb5454628228201fb24903d38174ee34565e"}, - {file = "shapely-2.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:28f87cdf5308a514763a5c38de295544cb27429cfa655d50ed8431a4796090c4"}, - {file = "shapely-2.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aeb0f51a9db176da9a30cb2f4329b6fbd1e26d359012bb0ac3d3c7781667a9e"}, - {file = "shapely-2.0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a7a78b0d51257a367ee115f4d41ca4d46edbd0dd280f697a8092dd3989867b2"}, - {file = "shapely-2.0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f32c23d2f43d54029f986479f7c1f6e09c6b3a19353a3833c2ffb226fb63a855"}, - {file = "shapely-2.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3dc9fb0eb56498912025f5eb352b5126f04801ed0e8bdbd867d21bdbfd7cbd0"}, - {file = "shapely-2.0.6-cp311-cp311-win32.whl", hash = "sha256:d93b7e0e71c9f095e09454bf18dad5ea716fb6ced5df3cb044564a00723f339d"}, - {file = "shapely-2.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:c02eb6bf4cfb9fe6568502e85bb2647921ee49171bcd2d4116c7b3109724ef9b"}, - {file = "shapely-2.0.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cec9193519940e9d1b86a3b4f5af9eb6910197d24af02f247afbfb47bcb3fab0"}, - {file = "shapely-2.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83b94a44ab04a90e88be69e7ddcc6f332da7c0a0ebb1156e1c4f568bbec983c3"}, - {file = "shapely-2.0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:537c4b2716d22c92036d00b34aac9d3775e3691f80c7aa517c2c290351f42cd8"}, - {file = "shapely-2.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fea108334be345c283ce74bf064fa00cfdd718048a8af7343c59eb40f59726"}, - {file = "shapely-2.0.6-cp312-cp312-win32.whl", hash = "sha256:42fd4cd4834747e4990227e4cbafb02242c0cffe9ce7ef9971f53ac52d80d55f"}, - {file = "shapely-2.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:665990c84aece05efb68a21b3523a6b2057e84a1afbef426ad287f0796ef8a48"}, - {file = "shapely-2.0.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:42805ef90783ce689a4dde2b6b2f261e2c52609226a0438d882e3ced40bb3013"}, - {file = "shapely-2.0.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6d2cb146191a47bd0cee8ff5f90b47547b82b6345c0d02dd8b25b88b68af62d7"}, - {file = "shapely-2.0.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3fdef0a1794a8fe70dc1f514440aa34426cc0ae98d9a1027fb299d45741c381"}, - {file = "shapely-2.0.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c665a0301c645615a107ff7f52adafa2153beab51daf34587170d85e8ba6805"}, - {file = "shapely-2.0.6-cp313-cp313-win32.whl", hash = "sha256:0334bd51828f68cd54b87d80b3e7cee93f249d82ae55a0faf3ea21c9be7b323a"}, - {file = "shapely-2.0.6-cp313-cp313-win_amd64.whl", hash = "sha256:d37d070da9e0e0f0a530a621e17c0b8c3c9d04105655132a87cfff8bd77cc4c2"}, - {file = "shapely-2.0.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fa7468e4f5b92049c0f36d63c3e309f85f2775752e076378e36c6387245c5462"}, - {file = "shapely-2.0.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed5867e598a9e8ac3291da6cc9baa62ca25706eea186117034e8ec0ea4355653"}, - {file = "shapely-2.0.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81d9dfe155f371f78c8d895a7b7f323bb241fb148d848a2bf2244f79213123fe"}, - {file = "shapely-2.0.6-cp37-cp37m-win32.whl", hash = "sha256:fbb7bf02a7542dba55129062570211cfb0defa05386409b3e306c39612e7fbcc"}, - {file = "shapely-2.0.6-cp37-cp37m-win_amd64.whl", hash = "sha256:837d395fac58aa01aa544495b97940995211e3e25f9aaf87bc3ba5b3a8cd1ac7"}, - {file = "shapely-2.0.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c6d88ade96bf02f6bfd667ddd3626913098e243e419a0325ebef2bbd481d1eb6"}, - {file = "shapely-2.0.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8b3b818c4407eaa0b4cb376fd2305e20ff6df757bf1356651589eadc14aab41b"}, - {file = "shapely-2.0.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbc783529a21f2bd50c79cef90761f72d41c45622b3e57acf78d984c50a5d13"}, - {file = "shapely-2.0.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2423f6c0903ebe5df6d32e0066b3d94029aab18425ad4b07bf98c3972a6e25a1"}, - {file = "shapely-2.0.6-cp38-cp38-win32.whl", hash = "sha256:2de00c3bfa80d6750832bde1d9487e302a6dd21d90cb2f210515cefdb616e5f5"}, - {file = "shapely-2.0.6-cp38-cp38-win_amd64.whl", hash = "sha256:3a82d58a1134d5e975f19268710e53bddd9c473743356c90d97ce04b73e101ee"}, - {file = "shapely-2.0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:392f66f458a0a2c706254f473290418236e52aa4c9b476a072539d63a2460595"}, - {file = "shapely-2.0.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eba5bae271d523c938274c61658ebc34de6c4b33fdf43ef7e938b5776388c1be"}, - {file = "shapely-2.0.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7060566bc4888b0c8ed14b5d57df8a0ead5c28f9b69fb6bed4476df31c51b0af"}, - {file = "shapely-2.0.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b02154b3e9d076a29a8513dffcb80f047a5ea63c897c0cd3d3679f29363cf7e5"}, - {file = "shapely-2.0.6-cp39-cp39-win32.whl", hash = "sha256:44246d30124a4f1a638a7d5419149959532b99dfa25b54393512e6acc9c211ac"}, - {file = "shapely-2.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:2b542d7f1dbb89192d3512c52b679c822ba916f93479fa5d4fc2fe4fa0b3c9e8"}, - {file = "shapely-2.0.6.tar.gz", hash = "sha256:997f6159b1484059ec239cacaa53467fd8b5564dabe186cd84ac2944663b0bf6"}, -] - -[package.dependencies] -numpy = ">=1.14,<3" - -[package.extras] -docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] -test = ["pytest", "pytest-cov"] - [[package]] name = "structlog" version = "24.4.0" @@ -707,13 +649,13 @@ typing = ["mypy (>=1.4)", "rich", "twisted"] [[package]] name = "taskipy" -version = "1.13.0" +version = "1.14.0" description = "tasks runner for python projects" optional = false python-versions = "<4.0,>=3.6" files = [ - {file = "taskipy-1.13.0-py3-none-any.whl", hash = "sha256:56f42b7e508d9aed2c7b6365f8d3dab62dbd0c768c1ab606c819da4fc38421f7"}, - {file = "taskipy-1.13.0.tar.gz", hash = "sha256:2b52f0257958fed151f1340f7de93fcf0848f7a358ad62ba05c31c2ca04f89fe"}, + {file = "taskipy-1.14.0-py3-none-any.whl", hash = "sha256:29040d9a8038170602feb71792bdef5203720ed30f595304aee843625892452b"}, + {file = "taskipy-1.14.0.tar.gz", hash = "sha256:5d9631c29980481d59858f0a100ed3200cf7468ca8c0540ef19388586485532d"}, ] [package.dependencies] From 56a70d59a5a8b48efc10ff2af4eef2af3ddc59f8 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 30 Oct 2024 02:19:02 +0900 Subject: [PATCH 117/249] feat: standard messages WIP WIP WIP WIP WIP WIP WIP WIP WIP feat: standard messages --- adf_core_python/core/agent/agent.py | 289 ++++++++++++--- adf_core_python/core/agent/command_factory.py | 44 +++ .../agent/communication/message_manager.py | 134 ++++++- .../agent/communication/standard/__init__.py | 0 .../communication/standard/bundle/__init__.py | 0 .../standard/bundle/centralized/__init__.py | 0 .../bundle/centralized/command_ambulance.py | 135 +++++++ .../bundle/centralized/command_fire.py | 136 +++++++ .../bundle/centralized/command_police.py | 134 +++++++ .../bundle/centralized/command_scout.py | 129 +++++++ .../bundle/centralized/message_report.py | 88 +++++ .../standard/bundle/information/__init__.py | 0 .../message_ambulance_team.py | 162 +++++---- .../message_building.py | 96 ++--- .../message_civilian.py | 107 +++--- .../message_fire_brigade.py | 159 +++++---- .../message_police_force.py | 150 ++++---- .../message_road.py | 138 ++++---- .../standard/bundle/standard_message.py | 71 ++-- .../bundle/standard_message_priority.py | 8 + .../standard/standard_communication_module.py | 168 ++++++++- .../standard/utility/__init__.py | 0 .../standard/utility/apply_to_world_info.py | 335 ++++++++++++++++++ .../utility/bitarray_with_exits_flag.py | 72 ++++ adf_core_python/core/agent/info/agent_info.py | 19 +- .../core/agent/info/scenario_info.py | 21 +- adf_core_python/core/agent/info/world_info.py | 11 + .../core/agent/module/module_manager.py | 58 +++ adf_core_python/core/agent/platoon/platoon.py | 85 ++--- .../core/component/communication/__init__.py | 0 .../communication/channel_subscriber.py | 21 +- .../communication/communication_message.py | 12 +- .../communication/communication_module.py | 9 +- .../communication/message_coordinator.py | 18 +- .../launcher/connect/component_launcher.py | 15 +- .../connect/connector_ambulance_centre.py | 4 +- .../connect/connector_ambulance_team.py | 4 +- .../connect/connector_fire_brigade.py | 4 +- .../connect/connector_fire_station.py | 5 +- .../connect/connector_police_force.py | 4 +- .../connect/connector_police_office.py | 6 +- .../default_channel_subscriber.py | 101 ++++++ .../default_message_coordinator.py | 222 ++++++++++++ .../tactics/default_tactics_ambulance_team.py | 73 ++++ config/launcher.yaml | 6 +- config/module.yaml | 11 +- poetry.lock | 178 +++++++--- pyproject.toml | 1 + pyrightconfig.json | 2 +- 49 files changed, 2820 insertions(+), 625 deletions(-) create mode 100644 adf_core_python/core/agent/command_factory.py create mode 100644 adf_core_python/core/agent/communication/standard/__init__.py create mode 100644 adf_core_python/core/agent/communication/standard/bundle/__init__.py create mode 100644 adf_core_python/core/agent/communication/standard/bundle/centralized/__init__.py create mode 100644 adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py create mode 100644 adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py create mode 100644 adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py create mode 100644 adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py create mode 100644 adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py create mode 100644 adf_core_python/core/agent/communication/standard/bundle/information/__init__.py rename adf_core_python/core/agent/communication/standard/bundle/{infomation => information}/message_ambulance_team.py (61%) rename adf_core_python/core/agent/communication/standard/bundle/{infomation => information}/message_building.py (57%) rename adf_core_python/core/agent/communication/standard/bundle/{infomation => information}/message_civilian.py (58%) rename adf_core_python/core/agent/communication/standard/bundle/{infomation => information}/message_fire_brigade.py (63%) rename adf_core_python/core/agent/communication/standard/bundle/{infomation => information}/message_police_force.py (61%) rename adf_core_python/core/agent/communication/standard/bundle/{infomation => information}/message_road.py (60%) create mode 100644 adf_core_python/core/agent/communication/standard/utility/__init__.py create mode 100644 adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py create mode 100644 adf_core_python/core/agent/communication/standard/utility/bitarray_with_exits_flag.py create mode 100644 adf_core_python/core/component/communication/__init__.py create mode 100644 adf_core_python/implement/module/communication/default_channel_subscriber.py create mode 100644 adf_core_python/implement/module/communication/default_message_coordinator.py diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index 2e86e21..51c9e98 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -2,14 +2,66 @@ from abc import abstractmethod from typing import Any, NoReturn -from rcrs_core.agents.agent import Agent as RCRSAgent +from bitarray import bitarray +from rcrs_core.commands.AKClear import AKClear +from rcrs_core.commands.AKClearArea import AKClearArea +from rcrs_core.commands.AKLoad import AKLoad +from rcrs_core.commands.AKMove import AKMove +from rcrs_core.commands.AKRescue import AKRescue +from rcrs_core.commands.AKRest import AKRest +from rcrs_core.commands.AKSay import AKSay +from rcrs_core.commands.AKSpeak import AKSpeak +from rcrs_core.commands.AKSubscribe import AKSubscribe +from rcrs_core.commands.AKTell import AKTell +from rcrs_core.commands.AKUnload import AKUnload from rcrs_core.commands.Command import Command from rcrs_core.config.config import Config as RCRSConfig from rcrs_core.connection.URN import Entity as EntityURN +from rcrs_core.messages.AKAcknowledge import AKAcknowledge +from rcrs_core.messages.AKConnect import AKConnect +from rcrs_core.messages.controlMessageFactory import ControlMessageFactory +from rcrs_core.messages.KAConnectError import KAConnectError +from rcrs_core.messages.KAConnectOK import KAConnectOK +from rcrs_core.messages.KASense import KASense from rcrs_core.worldmodel.changeSet import ChangeSet +from rcrs_core.worldmodel.entityID import EntityID from rcrs_core.worldmodel.worldmodel import WorldModel +from adf_core_python.core.agent.command_factory import CommandFactory from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( + CommandAmbulance, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_fire import ( + CommandFire, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_police import ( + CommandPolice, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( + CommandScout, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( + CommandReport, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_ambulance_team import ( + MessageAmbulanceTeam, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_building import ( + MessageBuilding, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_civilian import ( + MessageCivilian, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_fire_brigade import ( + MessageFireBrigade, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_police_force import ( + MessagePoliceForce, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_road import ( + MessageRoad, +) from adf_core_python.core.agent.communication.standard.standard_communication_module import ( StandardCommunicationModule, ) @@ -27,7 +79,7 @@ from adf_core_python.core.logger.logger import get_agent_logger, get_logger -class Agent(RCRSAgent): +class Agent: def __init__( self, is_precompute: bool, @@ -41,9 +93,9 @@ def __init__( self.name = name self.connect_request_id = None self.world_model = WorldModel() - self.config = None + self.config: Config self.random = None - self.agent_id = None + self.agent_id: EntityID self.precompute_flag = is_precompute self.logger = get_logger( f"{self.__class__.__module__}.{self.__class__.__qualname__}" @@ -57,29 +109,19 @@ def __init__( # PrecomputeData.remove_date(data_storage_name) self.mode = Mode.PRECOMPUTATION - self.module_config = module_config - self.develop_data = develop_data - self.precompute_data = PrecomputeData(data_storage_name) - self.message_manager: MessageManager = MessageManager() - self.communication_module: CommunicationModule = StandardCommunicationModule() + self._module_config = module_config + self._develop_data = develop_data + self._precompute_data = PrecomputeData(data_storage_name) + self._message_manager: MessageManager = MessageManager() + self._communication_module: CommunicationModule = StandardCommunicationModule() - def post_connect(self) -> None: - self.world_model.index_entities() - - config = Config() - if self.config is not None: - rcrc_config: RCRSConfig = self.config - for key, value in rcrc_config.data.items(): - config.set_value(key, value) - for key, value in rcrc_config.int_data.items(): - config.set_value(key, value) - for key, value in rcrc_config.float_data.items(): - config.set_value(key, value) - for key, value in rcrc_config.boolean_data.items(): - config.set_value(key, value) - for key, value in rcrc_config.array_data.items(): - config.set_value(key, value) + def get_entity_id(self) -> EntityID: + return self.agent_id + + def set_send_msg(self, connection_send_func): + self.send_msg = connection_send_func + def post_connect(self) -> None: if self.is_precompute: self._mode = Mode.PRECOMPUTATION else: @@ -89,35 +131,198 @@ def post_connect(self) -> None: # self._mode = Mode.NON_PRECOMPUTE self._mode = Mode.NON_PRECOMPUTE - config.set_value(ConfigKey.KEY_DEBUG_FLAG, self.is_debug) - config.set_value( - ConfigKey.KEY_DEVELOP_FLAG, self.develop_data.is_develop_mode() + self.config.set_value(ConfigKey.KEY_DEBUG_FLAG, self.is_debug) + self.config.set_value( + ConfigKey.KEY_DEVELOP_FLAG, self._develop_data.is_develop_mode() ) - self.ignore_time = config.get_value("kernel.agents.ignoreuntil", 3) - self.scenario_info: ScenarioInfo = ScenarioInfo(config, self._mode) - self.world_info: WorldInfo = WorldInfo(self.world_model) - self.agent_info = AgentInfo(self, self.world_model) + self._ignore_time: int = int( + self.config.get_value("kernel.agents.ignoreuntil", 3) + ) + self._scenario_info: ScenarioInfo = ScenarioInfo(self.config, self._mode) + self._world_info: WorldInfo = WorldInfo(self.world_model) + self._agent_info = AgentInfo(self, self.world_model) self.logger = get_agent_logger( f"{self.__class__.__module__}.{self.__class__.__qualname__}", - self.agent_info, + self._agent_info, ) - def think(self, time: int, change_set: ChangeSet, hear: list[Command]) -> None: - self.agent_info.record_think_start_time() - self.agent_info.set_time(time) + self.logger.info(f"config: {self.config}") - # if time == 1: - # self.message_manager.register_message_class() + def update_step_info( + self, time: int, change_set: ChangeSet, hear: list[Command] + ) -> None: + self._agent_info.record_think_start_time() + self._agent_info.set_time(time) - def handle_connect_error(self, msg: Any) -> NoReturn: - self.logger.error( - "Failed to connect agent: %s(request_id: %s)", msg.reason, msg.request_id + if time == 1: + self._message_manager.register_message_class(0, MessageAmbulanceTeam) + self._message_manager.register_message_class(1, MessageFireBrigade) + self._message_manager.register_message_class(2, MessagePoliceForce) + self._message_manager.register_message_class(3, MessageBuilding) + self._message_manager.register_message_class(4, MessageCivilian) + self._message_manager.register_message_class(5, MessageRoad) + self._message_manager.register_message_class(6, CommandAmbulance) + self._message_manager.register_message_class(7, CommandFire) + self._message_manager.register_message_class(8, CommandPolice) + self._message_manager.register_message_class(9, CommandScout) + self._message_manager.register_message_class(10, CommandReport) + + if time > self._ignore_time: + self._message_manager.subscribe( + self._agent_info, self._world_info, self._scenario_info + ) + if not self._message_manager.get_is_subscribed(): + subscribed_channels = self._message_manager.get_subscribed_channels() + if subscribed_channels: + self.logger.debug( + f"Subscribed channels: {subscribed_channels}", + message_manager=self._message_manager, + ) + self.send_subscribe(time, subscribed_channels) + self._message_manager.set_is_subscribed(True) + + self._agent_info.set_heard_commands(hear) + self._agent_info.set_change_set(change_set) + self._world_info.set_change_set(change_set) + + self._message_manager.refresh() + self._communication_module.receive(self, self._message_manager) + + self.think() + + self.logger.debug( + f"send messages: {self._message_manager.get_send_message_list()}", + message_manager=self._message_manager, ) - sys.exit(1) + self._message_manager.coordinate_message( + self._agent_info, self._world_info, self._scenario_info + ) + self._communication_module.send(self, self._message_manager) + + @abstractmethod + def think(self) -> None: + pass + + @abstractmethod def precompute(self) -> None: pass @abstractmethod def get_requested_entities(self) -> list[EntityURN]: pass + + def start_up(self, request_id): + ak_connect = AKConnect() + self.send_msg(ak_connect.write(request_id, self)) + + def message_received(self, msg): + c_msg = ControlMessageFactory().make_message(msg) + if isinstance(c_msg, KASense): + self.handler_sense(c_msg) + elif isinstance(c_msg, KAConnectOK): + self.handle_connect_ok(c_msg) + elif isinstance(c_msg, KAConnectError): + self.handle_connect_error(c_msg) + + def handle_connect_error(self, msg: Any) -> NoReturn: + self.logger.error( + "Failed to connect agent: %s(request_id: %s)", msg.reason, msg.request_id + ) + sys.exit(1) + + def handle_connect_ok(self, msg): + self.agent_id = EntityID(msg.agent_id) + self.world_model.add_entities(msg.world) + config: RCRSConfig = msg.config + self.config = Config() + if config is not None: + for key, value in config.data.items(): + self.config.set_value(key, value) + for key, value in config.int_data.items(): + self.config.set_value(key, value) + for key, value in config.float_data.items(): + self.config.set_value(key, value) + for key, value in config.boolean_data.items(): + self.config.set_value(key, value) + for key, value in config.array_data.items(): + self.config.set_value(key, value) + self.send_acknowledge(msg.request_id) + self.post_connect() + if self.precompute_flag: + print("self.precompute_flag: ", self.precompute_flag) + self.precompute() + + def handler_sense(self, msg): + _id = EntityID(msg.agent_id) + time = msg.time + change_set = msg.change_set + hear = msg.hear.commands + + if _id != self.get_entity_id(): + self.logger.error("Agent ID mismatch: %s != %s", _id, self.get_entity_id()) + return + + hear_commands = [CommandFactory.create_command(cmd) for cmd in hear] + + self.world_model.merge(change_set) + self.update_step_info(time, change_set, hear_commands) + + def send_acknowledge(self, request_id): + ak_ack = AKAcknowledge() + self.send_msg(ak_ack.write(request_id, self.agent_id)) + + def send_clear(self, time, target): + cmd = AKClear(self.get_entity_id(), time, target) + msg = cmd.prepare_cmd() + self.send_msg(msg) + + def send_clear_area(self, time, x=-1, y=-1): + cmd = AKClearArea(self.get_entity_id(), time, x, y) + msg = cmd.prepare_cmd() + self.send_msg(msg) + + def send_load(self, time, target): + cmd = AKLoad(self.get_entity_id(), time, target) + msg = cmd.prepare_cmd() + self.send_msg(msg) + + def send_move(self, time, path, x=-1, y=-1): + cmd = AKMove(self.get_entity_id(), time, path[:], x, y) + msg = cmd.prepare_cmd() + self.send_msg(msg) + + def send_rescue(self, time, target): + cmd = AKRescue(self.get_entity_id(), time, target) + msg = cmd.prepare_cmd() + self.send_msg(msg) + + def send_rest(self, time_step): + cmd = AKRest(self.get_entity_id(), time_step) + msg = cmd.prepare_cmd() + self.send_msg(msg) + + def send_say(self, time_step: int, message: str): + cmd = AKSay(self.get_entity_id(), time_step, message) + msg = cmd.prepare_cmd() + self.send_msg(msg) + + def send_speak(self, time_step: int, message: bitarray, channel: int): + cmd = AKSpeak(self.get_entity_id(), time_step, bytes(message), channel) # type: ignore + msg = cmd.prepare_cmd() + self.send_msg(msg) + + def send_subscribe(self, time, channel): + cmd = AKSubscribe(self.get_entity_id(), time, channel) + msg = cmd.prepare_cmd() + self.send_msg(msg) + + def send_tell(self, time_step: int, message: str): + cmd = AKTell(self.get_entity_id(), time_step, message) + msg = cmd.prepare_cmd() + self.send_msg(msg) + + def send_unload(self, time): + cmd = AKUnload(self.get_entity_id(), time) + msg = cmd.prepare_cmd() + self.send_msg(msg) diff --git a/adf_core_python/core/agent/command_factory.py b/adf_core_python/core/agent/command_factory.py new file mode 100644 index 0000000..33c5253 --- /dev/null +++ b/adf_core_python/core/agent/command_factory.py @@ -0,0 +1,44 @@ +from rcrs_core.commands.AKClear import AKClear +from rcrs_core.commands.AKClearArea import AKClearArea +from rcrs_core.commands.AKExtinguish import AKExtinguish +from rcrs_core.commands.AKLoad import AKLoad +from rcrs_core.commands.AKMove import AKMove +from rcrs_core.commands.AKRescue import AKRescue +from rcrs_core.commands.AKRest import AKRest +from rcrs_core.commands.AKSay import AKSay +from rcrs_core.commands.AKSpeak import AKSpeak +from rcrs_core.commands.AKSubscribe import AKSubscribe +from rcrs_core.commands.AKTell import AKTell +from rcrs_core.commands.AKUnload import AKUnload +from rcrs_core.commands.Command import Command +from rcrs_core.connection.URN import Command as CommandURN +from rcrs_core.connection.URN import ComponentCommand as ComponentCommandMessageID +from rcrs_core.connection.URN import ComponentControlMSG as ComponentControlMessageID + + +class CommandFactory: + @staticmethod + def create_command(herad_command) -> Command: + if herad_command.urn == CommandURN.AK_SPEAK: + return AKSpeak( + herad_command.components[ComponentControlMessageID.AgentID].entityID, + herad_command.components[ComponentControlMessageID.Time].intValue, + herad_command.components[ComponentCommandMessageID.Message].rawData, + herad_command.components[ComponentCommandMessageID.Channel].intValue, + ) + return herad_command + # if herad_command.urn == CommandURN.AK_CLEAR: + # return AKClear( + # herad_command.agent_id, herad_command.time, herad_command.target + # ) + # elif herad_command.urn == CommandURN.AK_CLEAR_AREA: + # return AKClearArea( + # herad_command.agent_id, + # herad_command.time, + # herad_command.x, + # herad_command.y, + # ) + # elif herad_command.urn == CommandURN.AK_EXTINGUISH: + # return AKExtinguish( + # herad_command.agent_id, herad_command.time, herad_command.target + # ) diff --git a/adf_core_python/core/agent/communication/message_manager.py b/adf_core_python/core/agent/communication/message_manager.py index e873664..53c8f43 100644 --- a/adf_core_python/core/agent/communication/message_manager.py +++ b/adf_core_python/core/agent/communication/message_manager.py @@ -1,15 +1,21 @@ -from adf_core_python.core.agent.info.agent_info import AgentInfo +from __future__ import annotations + +from typing import TYPE_CHECKING + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo, ScenarioInfoKeys from adf_core_python.core.agent.info.world_info import WorldInfo -from adf_core_python.core.component.communication.channel_subscriber import ( - ChannelSubscriber, -) -from adf_core_python.core.component.communication.communication_message import ( - CommunicationMessage, -) -from adf_core_python.core.component.communication.message_coordinator import ( - MessageCoordinator, -) + +if TYPE_CHECKING: + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.component.communication.channel_subscriber import ( + ChannelSubscriber, + ) + from adf_core_python.core.component.communication.communication_message import ( + CommunicationMessage, + ) + from adf_core_python.core.component.communication.message_coordinator import ( + MessageCoordinator, + ) class MessageManager: @@ -34,7 +40,7 @@ def __init__(self) -> None: """ self.__standard_message_class_count = 0b0000_0001 self.__custom_message_class_count = 0b0001_0000 - self.__message_classes: dict[int, CommunicationMessage] = {} + self.__message_classes: dict[int, type[CommunicationMessage]] = {} self.__send_message_list: list[CommunicationMessage] = [] self.__received_message_list: list[CommunicationMessage] = [] self.__channel_send_message_list: list[list[CommunicationMessage]] = [] @@ -106,6 +112,18 @@ def set_channel_subscriber(self, channel_subscriber: ChannelSubscriber) -> None: """ self.__channel_subscriber = channel_subscriber + def set_message_coordinator(self, message_coordinator: MessageCoordinator) -> None: + """ + Set the message coordinator. + + Parameters + ---------- + message_coordinator : MessageCoordinator + The message coordinator. + + """ + self.__message_coordinator = message_coordinator + def get_channel_subscriber(self) -> ChannelSubscriber: """ Get the channel subscriber. @@ -161,6 +179,18 @@ def get_received_message_list(self) -> list[CommunicationMessage]: """ return self.__received_message_list + def get_channel_send_message_list(self) -> list[list[CommunicationMessage]]: + """ + Get the channel send message list. + + Returns + ------- + list[list[CommunicationMessage]] + The channel send message list. + + """ + return self.__channel_send_message_list + def subscribe( self, agent_info: AgentInfo, world_info: WorldInfo, scenario_info: ScenarioInfo ) -> None: @@ -185,10 +215,13 @@ def subscribe( if self.__channel_subscriber is None: raise ValueError("ChannelSubscriber is not set.") - self.__channel_subscriber.subscribe(agent_info, world_info, scenario_info, self) + if agent_info.get_time() == 1: + self.__subscribed_channels = self.__channel_subscriber.subscribe( + agent_info, world_info, scenario_info + ) def register_message_class( - self, index: int, message_class: CommunicationMessage + self, index: int, message_class: type[CommunicationMessage] ) -> None: """ Register the message class. @@ -205,6 +238,43 @@ def register_message_class( ) self.__message_classes[index] = message_class + def get_message_class(self, index: int) -> type[CommunicationMessage]: + """ + Get the message class. + + Parameters + ---------- + index : int + The index. + + Returns + ------- + type[CommunicationMessage] + The message class. + + """ + return self.__message_classes[index] + + def get_message_class_index(self, message_class: type[CommunicationMessage]) -> int: + """ + Get the message class index. + + Parameters + ---------- + message_class : type[CommunicationMessage] + The message class. + + Returns + ------- + int + The message class index. + + """ + for index, cls in self.__message_classes.items(): + if cls == message_class: + return index + return -1 + def add_message( self, message: CommunicationMessage, check_duplicate: bool = True ) -> None: @@ -217,7 +287,7 @@ def add_message( The message. """ - check_key = message.get_check_key() + check_key = message.__hash__() # TODO:両方同じコードになっているが、なぜなのか調査する if check_duplicate and check_key not in self.__check_duplicate_cache: self.__send_message_list.append(message) @@ -226,6 +296,18 @@ def add_message( self.__send_message_list.append(message) self.__check_duplicate_cache.add(check_key) + def get_send_message_list(self) -> list[CommunicationMessage]: + """ + Get the send message list. + + Returns + ------- + list[CommunicationMessage] + The send message list. + + """ + return self.__send_message_list + def coordinate_message( self, agent_info: AgentInfo, world_info: WorldInfo, scenario_info: ScenarioInfo ) -> None: @@ -248,7 +330,11 @@ def coordinate_message( self.__channel_send_message_list = [ [] for _ in range( - scenario_info.get_value(ScenarioInfoKeys.COMMS_CHANNELS_COUNT, 1) + int( + scenario_info.get_value( + ScenarioInfoKeys.COMMUNICATION_CHANNELS_COUNT, 1 + ) + ) ) ] @@ -267,5 +353,23 @@ def refresh(self) -> None: """ self.__send_message_list = [] + self.__received_message_list = [] self.__check_duplicate_cache = set() self.__heard_agent_help_message_count = 0 + + def __str__(self) -> str: + return ( + f"MessageManager(" + f"standard_message_class_count={self.__standard_message_class_count}, " + f"custom_message_class_count={self.__custom_message_class_count}, " + f"message_classes={self.__message_classes}, " + f"send_message_list={self.__send_message_list}, " + f"received_message_list={self.__received_message_list}, " + f"channel_send_message_list={self.__channel_send_message_list}, " + f"check_duplicate_cache={self.__check_duplicate_cache}, " + f"message_coordinator={self.__message_coordinator}, " + f"channel_subscriber={self.__channel_subscriber}, " + f"heard_agent_help_message_count={self.__heard_agent_help_message_count}, " + f"subscribed_channels={self.__subscribed_channels}, " + f"is_subscribed={self.__is_subscribed})" + ) diff --git a/adf_core_python/core/agent/communication/standard/__init__.py b/adf_core_python/core/agent/communication/standard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/agent/communication/standard/bundle/__init__.py b/adf_core_python/core/agent/communication/standard/bundle/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/__init__.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py new file mode 100644 index 0000000..ff28ab8 --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py @@ -0,0 +1,135 @@ +from __future__ import annotations + +from typing import Optional + +from bitarray import bitarray +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) +from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( + read_with_exist_flag, + write_with_exist_flag, +) + + +class CommandAmbulance(StandardMessage): + ACTION_REST: int = 0 + ACTION_MOVE: int = 1 + ACTION_RESCUE: int = 2 + ACTION_LOAD: int = 3 + ACTION_UNLOAD: int = 4 + ACTION_AUTONOMY: int = 5 + + SIZE_AMBULANCE_TEAM_ENTITY_ID: int = 32 + SIZE_TARGET_ENTITY_ID: int = 32 + SIZE_ACTION: int = 4 + + def __init__( + self, + is_wireless_message: bool, + command_executor_agent_entity_id: EntityID, + sender_entity_id: EntityID, + execute_action: int, + priority: StandardMessagePriority, + command_target_entity_id: Optional[EntityID] = None, + ): + super().__init__(is_wireless_message, priority, sender_entity_id) + self._command_executor_agent_entity_id: Optional[EntityID] = ( + command_executor_agent_entity_id + ) + self._command_target_entity_id: Optional[EntityID] = command_target_entity_id + self._is_bloadcast: bool = command_target_entity_id is None + self._execute_action: Optional[int] = execute_action + + def get_command_executor_agent_entity_id(self) -> Optional[EntityID]: + return self._command_executor_agent_entity_id + + def get_command_target_entity_id(self) -> Optional[EntityID]: + return self._command_target_entity_id + + def get_execute_action(self) -> Optional[int]: + return self._execute_action + + def is_broadcast(self) -> bool: + return self._is_bloadcast + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + raw_command_executor_agent_entity_id = ( + self._command_executor_agent_entity_id.get_value() + if self._command_executor_agent_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, + raw_command_executor_agent_entity_id, + self.SIZE_AMBULANCE_TEAM_ENTITY_ID, + ) + raw_command_target_entity_id = ( + self._command_target_entity_id.get_value() + if self._command_target_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, raw_command_target_entity_id, self.SIZE_TARGET_ENTITY_ID + ) + write_with_exist_flag(bit_array, self._execute_action, self.SIZE_ACTION) + return bit_array + + @classmethod + def from_bits( + cls, + bit_array: bitarray, + is_wireless_message: bool, + sender_entity_id: EntityID, + ) -> CommandAmbulance: + std_message = super().from_bits( + bit_array, is_wireless_message, sender_entity_id + ) + raw_command_executor_agent_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_AMBULANCE_TEAM_ENTITY_ID + ) + command_executor_agent_id = ( + EntityID(raw_command_executor_agent_entity_id) + if raw_command_executor_agent_entity_id is not None + else None + ) + raw_command_target_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_TARGET_ENTITY_ID + ) + command_target_id = ( + EntityID(raw_command_target_entity_id) + if raw_command_target_entity_id is not None + else None + ) + execute_action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) + return cls( + is_wireless_message, + command_executor_agent_id or EntityID(-1), + sender_entity_id, + execute_action if execute_action is not None else -1, + std_message.get_priority(), + command_target_id, + ) + + def __hash__(self): + h = super().__hash__() + return hash( + ( + h, + self._command_executor_agent_entity_id, + self._command_target_entity_id, + self._execute_action, + ) + ) + + def __str__(self) -> str: + return f"CommandAmbulance(executor={self._command_executor_agent_entity_id}, target={self._command_target_entity_id}, action={self._execute_action})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py new file mode 100644 index 0000000..5927a2e --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py @@ -0,0 +1,136 @@ +from __future__ import annotations + +from typing import Optional + +from bitarray import bitarray +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) +from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( + read_with_exist_flag, + write_with_exist_flag, +) + + +class CommandFire(StandardMessage): + ACTION_REST: int = 0 + ACTION_MOVE: int = 1 + ACTION_EXTINGUISH: int = 2 + ACTION_REFILL: int = 3 + ACTION_RESCUE: int = 4 + ACTION_AUTONOMY: int = 5 + + SIZE_FIRE_BRIGADE_ENTITY_ID: int = 32 + SIZE_TARGET_ENTITY_ID: int = 32 + SIZE_ACTION: int = 4 + + def __init__( + self, + is_wireless_message: bool, + command_executor_agent_entity_id: EntityID, + sender_entity_id: EntityID, + execute_action: int, + priority: StandardMessagePriority, + command_target_entity_id: Optional[EntityID] = None, + ): + super().__init__(is_wireless_message, priority, sender_entity_id) + self._command_executor_agent_entity_id: Optional[EntityID] = ( + command_executor_agent_entity_id + ) + self._command_target_entity_id: Optional[EntityID] = command_target_entity_id + self._is_bloadcast: bool = command_target_entity_id is None + self._execute_action: Optional[int] = execute_action + + def get_command_executor_agent_entity_id(self) -> Optional[EntityID]: + return self._command_executor_agent_entity_id + + def get_command_target_entity_id(self) -> Optional[EntityID]: + return self._command_target_entity_id + + def get_execute_action(self) -> Optional[int]: + return self._execute_action + + def is_broadcast(self) -> bool: + return self._is_bloadcast + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + raw_command_executor_agent_entity_id = ( + self._command_executor_agent_entity_id.get_value() + if self._command_executor_agent_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, + raw_command_executor_agent_entity_id, + self.SIZE_FIRE_BRIGADE_ENTITY_ID, + ) + raw_command_target_entity_id = ( + self._command_target_entity_id.get_value() + if self._command_target_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, raw_command_target_entity_id, self.SIZE_TARGET_ENTITY_ID + ) + write_with_exist_flag(bit_array, self._execute_action, self.SIZE_ACTION) + + return bit_array + + @classmethod + def from_bits( + cls, + bit_array: bitarray, + is_wireless_message: bool, + sender_entity_id: EntityID, + ) -> CommandFire: + std_message = super().from_bits( + bit_array, is_wireless_message, sender_entity_id + ) + raw_command_executor_agent_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_FIRE_BRIGADE_ENTITY_ID + ) + command_executor_agent_id = ( + EntityID(raw_command_executor_agent_entity_id) + if raw_command_executor_agent_entity_id is not None + else None + ) + raw_command_target_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_TARGET_ENTITY_ID + ) + command_target_id = ( + EntityID(raw_command_target_entity_id) + if raw_command_target_entity_id is not None + else None + ) + execute_action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) + return cls( + is_wireless_message, + command_executor_agent_id or EntityID(-1), + sender_entity_id, + execute_action if execute_action is not None else -1, + std_message.get_priority(), + command_target_id, + ) + + def __hash__(self): + h = super().__hash__() + return hash( + ( + h, + self._command_executor_agent_entity_id, + self._command_target_entity_id, + self._execute_action, + ) + ) + + def __str__(self) -> str: + return f"CommandFire(executor={self._command_executor_agent_entity_id}, target={self._command_target_entity_id}, action={self._execute_action})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py new file mode 100644 index 0000000..cf3cc4d --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py @@ -0,0 +1,134 @@ +from __future__ import annotations + +from typing import Optional + +from bitarray import bitarray +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) +from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( + read_with_exist_flag, + write_with_exist_flag, +) + + +class CommandPolice(StandardMessage): + ACTION_REST: int = 0 + ACTION_MOVE: int = 1 + ACTION_CLEAR: int = 2 + ACTION_AUTONOMY: int = 3 + + SIZE_POLICE_FORCE_ENTITY_ID: int = 32 + SIZE_TARGET_ENTITY_ID: int = 32 + SIZE_ACTION: int = 4 + + def __init__( + self, + is_wireless_message: bool, + command_executor_agent_entity_id: EntityID, + sender_entity_id: EntityID, + execute_action: int, + priority: StandardMessagePriority, + command_target_entity_id: Optional[EntityID] = None, + ): + super().__init__(is_wireless_message, priority, sender_entity_id) + self._command_executor_agent_entity_id: Optional[EntityID] = ( + command_executor_agent_entity_id + ) + self._command_target_entity_id: Optional[EntityID] = command_target_entity_id + self._is_bloadcast: bool = command_target_entity_id is None + self._execute_action: Optional[int] = execute_action + + def get_command_executor_agent_entity_id(self) -> Optional[EntityID]: + return self._command_executor_agent_entity_id + + def get_command_target_entity_id(self) -> Optional[EntityID]: + return self._command_target_entity_id + + def get_execute_action(self) -> Optional[int]: + return self._execute_action + + def is_broadcast(self) -> bool: + return self._is_bloadcast + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + raw_command_executor_agent_entity_id = ( + self._command_executor_agent_entity_id.get_value() + if self._command_executor_agent_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, + raw_command_executor_agent_entity_id, + self.SIZE_POLICE_FORCE_ENTITY_ID, + ) + raw_command_target_entity_id = ( + self._command_target_entity_id.get_value() + if self._command_target_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, raw_command_target_entity_id, self.SIZE_TARGET_ENTITY_ID + ) + write_with_exist_flag(bit_array, self._execute_action, self.SIZE_ACTION) + + return bit_array + + @classmethod + def from_bits( + cls, + bit_array: bitarray, + is_wireless_message: bool, + sender_entity_id: EntityID, + ) -> CommandPolice: + std_message = super().from_bits( + bit_array, is_wireless_message, sender_entity_id + ) + raw_command_executor_agent_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_POLICE_FORCE_ENTITY_ID + ) + command_executor_agent_id = ( + EntityID(raw_command_executor_agent_entity_id) + if raw_command_executor_agent_entity_id is not None + else None + ) + raw_command_target_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_TARGET_ENTITY_ID + ) + command_target_id = ( + EntityID(raw_command_target_entity_id) + if raw_command_target_entity_id is not None + else None + ) + execute_action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) + return cls( + is_wireless_message, + command_executor_agent_id or EntityID(-1), + sender_entity_id, + execute_action if execute_action is not None else -1, + std_message.get_priority(), + command_target_id, + ) + + def __hash__(self): + h = super().__hash__() + return hash( + ( + h, + self._command_executor_agent_entity_id, + self._command_target_entity_id, + self._execute_action, + ) + ) + + def __str__(self) -> str: + return f"CommandPolice(executor={self._command_executor_agent_entity_id}, target={self._command_target_entity_id}, action={self._execute_action})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py new file mode 100644 index 0000000..253883b --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +from typing import Optional + +from bitarray import bitarray +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) +from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( + read_with_exist_flag, + write_with_exist_flag, +) + + +class CommandScout(StandardMessage): + SIZE_AGENT_ENTITY_ID: int = 32 + SIZE_TARGET_ENTITY_ID: int = 32 + SIZE_SCOUT_RANGE: int = 32 + + def __init__( + self, + is_wireless_message: bool, + command_executor_agent_entity_id: EntityID, + sender_entity_id: EntityID, + scout_range: int, + priority: StandardMessagePriority, + command_target_entity_id: Optional[EntityID] = None, + ): + super().__init__(is_wireless_message, priority, sender_entity_id) + self._command_executor_agent_entity_id: Optional[EntityID] = ( + command_executor_agent_entity_id + ) + self._command_target_entity_id: Optional[EntityID] = command_target_entity_id + self._is_bloadcast: bool = command_target_entity_id is None + self._scout_range: Optional[int] = scout_range + + def get_command_executor_agent_entity_id(self) -> Optional[EntityID]: + return self._command_executor_agent_entity_id + + def get_command_target_entity_id(self) -> Optional[EntityID]: + return self._command_target_entity_id + + def get_scout_range(self) -> Optional[int]: + return self._scout_range + + def is_broadcast(self) -> bool: + return self._is_bloadcast + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + raw_command_executor_agent_entity_id = ( + self._command_executor_agent_entity_id.get_value() + if self._command_executor_agent_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, + raw_command_executor_agent_entity_id, + self.SIZE_AGENT_ENTITY_ID, + ) + raw_command_target_entity_id = ( + self._command_target_entity_id.get_value() + if self._command_target_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, raw_command_target_entity_id, self.SIZE_TARGET_ENTITY_ID + ) + write_with_exist_flag(bit_array, self._scout_range, self.SIZE_SCOUT_RANGE) + + return bit_array + + @classmethod + def from_bits( + cls, + bit_array: bitarray, + is_wireless_message: bool, + sender_entity_id: EntityID, + ) -> CommandScout: + std_message = super().from_bits( + bit_array, is_wireless_message, sender_entity_id + ) + raw_command_executor_agent_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_AGENT_ENTITY_ID + ) + command_executor_agent_id = ( + EntityID(raw_command_executor_agent_entity_id) + if raw_command_executor_agent_entity_id is not None + else None + ) + raw_command_target_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_TARGET_ENTITY_ID + ) + command_target_id = ( + EntityID(raw_command_target_entity_id) + if raw_command_target_entity_id is not None + else None + ) + scout_range = read_with_exist_flag(bit_array, cls.SIZE_SCOUT_RANGE) + return cls( + is_wireless_message, + command_executor_agent_id or EntityID(-1), + sender_entity_id, + scout_range if scout_range is not None else -1, + std_message.get_priority(), + command_target_id, + ) + + def __hash__(self): + h = super().__hash__() + return hash( + ( + h, + self._command_executor_agent_entity_id, + self._command_target_entity_id, + self._scout_range, + ) + ) + + def __str__(self) -> str: + return f"CommandScout(executor={self._command_executor_agent_entity_id}, target={self._command_target_entity_id}, scout_range={self._scout_range})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py new file mode 100644 index 0000000..2971bb5 --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +from bitarray import bitarray +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) +from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( + read_with_exist_flag, + write_with_exist_flag, +) + + +class CommandReport(StandardMessage): + SIZE_DONE: int = 1 + SIZE_BLOADCAST: int = 1 + + def __init__( + self, + is_wireless_message: bool, + is_done: bool, + is_bloadcast: bool, + sender_entity_id: EntityID, + priority: StandardMessagePriority, + ): + super().__init__(is_wireless_message, priority, sender_entity_id) + self._is_done: bool = is_done + self._is_bloadcast: bool = is_bloadcast + + def is_done(self) -> bool: + return self._is_done + + def is_broadcast(self) -> bool: + return self._is_bloadcast + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + write_with_exist_flag( + bit_array, + self._is_done, + self.SIZE_DONE, + ) + write_with_exist_flag( + bit_array, + self._is_bloadcast, + self.SIZE_BLOADCAST, + ) + return bit_array + + @classmethod + def from_bits( + cls, + bit_array: bitarray, + is_wireless_message: bool, + sender_entity_id: EntityID, + ) -> CommandReport: + std_message = super().from_bits( + bit_array, is_wireless_message, sender_entity_id + ) + is_done = read_with_exist_flag(bit_array, cls.SIZE_DONE) == 1 + is_bloadcast = read_with_exist_flag(bit_array, cls.SIZE_BLOADCAST) == 1 + return cls( + is_wireless_message, + is_done, + is_bloadcast, + std_message.get_sender_entity_id(), + std_message.get_priority(), + ) + + def __hash__(self): + h = super().__hash__() + return hash( + ( + h, + self._is_done, + self._is_bloadcast, + ) + ) + + def __str__(self) -> str: + return f"CommandReport(done={self._is_done}, broadcast={self._is_bloadcast})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/__init__.py b/adf_core_python/core/agent/communication/standard/bundle/information/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_ambulance_team.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py similarity index 61% rename from adf_core_python/core/agent/communication/standard/bundle/infomation/message_ambulance_team.py rename to adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py index 6a462a2..7f495f7 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_ambulance_team.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py @@ -12,6 +12,10 @@ from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( StandardMessagePriority, ) +from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( + read_with_exist_flag, + write_with_exist_flag, +) class MessageAmbulanceTeam(StandardMessage): @@ -32,14 +36,14 @@ class MessageAmbulanceTeam(StandardMessage): def __init__( self, is_wireless_message: bool, - priority: StandardMessagePriority, ambulance_team: AmbulanceTeamEntity, action: int, target_entity_id: EntityID, - sender_id: int = -1, - ttl: int = -1, + priority: StandardMessagePriority, + sender_entity_id: EntityID, + ttl: Optional[int] = None, ): - super().__init__(is_wireless_message, priority, sender_id, ttl) + super().__init__(is_wireless_message, priority, sender_entity_id, ttl) self._ambulance_team_entity_id: Optional[EntityID] = ambulance_team.get_id() self._ambulance_team_hp: Optional[int] = ambulance_team.get_hp() or None self._ambulance_team_buriedness: Optional[int] = ( @@ -52,67 +56,86 @@ def __init__( self._target_entity_id: Optional[EntityID] = target_entity_id self._action: Optional[int] = action - def get_byte_size(self) -> int: - return self.to_bytes().__len__() + def get_ambulance_team_entity_id(self) -> Optional[EntityID]: + return self._ambulance_team_entity_id + + def get_ambulance_team_hp(self) -> Optional[int]: + return self._ambulance_team_hp + + def get_ambulance_team_buriedness(self) -> Optional[int]: + return self._ambulance_team_buriedness + + def get_ambulance_team_damage(self) -> Optional[int]: + return self._ambulance_team_damage + + def get_ambulance_team_position(self) -> Optional[EntityID]: + return self._ambulance_team_position + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._target_entity_id + + def get_action(self) -> Optional[int]: + return self._action - def to_bytes(self) -> bytes: - bit_array = bitarray() - self.write_with_exist_flag( + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + write_with_exist_flag( bit_array, self._ambulance_team_entity_id.get_value() if self._ambulance_team_entity_id else None, self.SIZE_AMBULANCE_TEAM_ENTITY_ID, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._ambulance_team_hp, self.SIZE_AMBULANCE_TEAM_HP ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._ambulance_team_buriedness, self.SIZE_AMBULANCE_TEAM_BURIEDNESS, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._ambulance_team_damage, self.SIZE_AMBULANCE_TEAM_DAMAGE ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._ambulance_team_position.get_value() if self._ambulance_team_position else None, self.SIZE_AMBULANCE_TEAM_POSITION, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._target_entity_id.get_value() if self._target_entity_id else None, self.SIZE_TARGET_ENTITY_ID, ) - self.write_with_exist_flag(bit_array, self._action, self.SIZE_ACTION) - return bit_array.tobytes() + write_with_exist_flag(bit_array, self._action, self.SIZE_ACTION) + return bit_array @classmethod - def from_bytes(cls, bytes: bytes) -> MessageAmbulanceTeam: - bit_array = bitarray() - bit_array.frombytes(bytes) - raw_ambulance_team_entity_id = cls.read_with_exist_flag( - bit_array, cls.SIZE_AMBULANCE_TEAM_ENTITY_ID - ) - ambulance_team_entity_id = ( - EntityID(raw_ambulance_team_entity_id) - if raw_ambulance_team_entity_id is not None - else None + def from_bits( + cls, + bit_array: bitarray, + is_wireless_message: bool, + sender_entity_id: EntityID, + ) -> MessageAmbulanceTeam: + std_message = super().from_bits( + bit_array, is_wireless_message, sender_entity_id ) - ambulance_team_hp = cls.read_with_exist_flag( - bit_array, cls.SIZE_AMBULANCE_TEAM_HP + ambulance_team_id = read_with_exist_flag( + bit_array, cls.SIZE_AMBULANCE_TEAM_ENTITY_ID ) - ambulance_team_buriedness = cls.read_with_exist_flag( + ambulance_team_hp = read_with_exist_flag(bit_array, cls.SIZE_AMBULANCE_TEAM_HP) + ambulance_team_buriedness = read_with_exist_flag( bit_array, cls.SIZE_AMBULANCE_TEAM_BURIEDNESS ) - ambulance_team_damage = cls.read_with_exist_flag( + ambulance_team_damage = read_with_exist_flag( bit_array, cls.SIZE_AMBULANCE_TEAM_DAMAGE ) - - raw_ambulance_team_position = cls.read_with_exist_flag( + raw_ambulance_team_position = read_with_exist_flag( bit_array, cls.SIZE_AMBULANCE_TEAM_POSITION ) ambulance_team_position = ( @@ -120,59 +143,44 @@ def from_bytes(cls, bytes: bytes) -> MessageAmbulanceTeam: if raw_ambulance_team_position is not None else None ) - - raw_target_entity_id = cls.read_with_exist_flag( + raw_target_entity_id = read_with_exist_flag( bit_array, cls.SIZE_TARGET_ENTITY_ID ) target_entity_id = ( - EntityID(raw_target_entity_id) if raw_target_entity_id is not None else None - ) - action = cls.read_with_exist_flag(bit_array, cls.SIZE_ACTION) - ambulance_team = AmbulanceTeamEntity( - ambulance_team_entity_id, + EntityID(raw_target_entity_id) + if raw_target_entity_id is not None + else EntityID(-1) ) + action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) + ambulance_team = AmbulanceTeamEntity(ambulance_team_id or -1) ambulance_team.set_hp(ambulance_team_hp) ambulance_team.set_buriedness(ambulance_team_buriedness) ambulance_team.set_damage(ambulance_team_damage) ambulance_team.set_position(ambulance_team_position) return MessageAmbulanceTeam( False, - StandardMessagePriority.NORMAL, ambulance_team, - action or -1, - target_entity_id or EntityID(-1), - ) - - def get_check_key(self) -> str: - target_id_value: str = ( - str(self._target_entity_id.get_value()) - if self._target_entity_id - else "None" - ) - ambulance_team_id_value: str = ( - str(self._ambulance_team_entity_id.get_value()) - if self._ambulance_team_entity_id - else "None" - ) - return f"{self.__class__.__name__} > agent: {ambulance_team_id_value}, target: {target_id_value}, action: {self._action}" - - def get_ambulance_team_entity_id(self) -> Optional[EntityID]: - return self._ambulance_team_entity_id - - def get_ambulance_team_hp(self) -> Optional[int]: - return self._ambulance_team_hp - - def get_ambulance_team_buriedness(self) -> Optional[int]: - return self._ambulance_team_buriedness - - def get_ambulance_team_damage(self) -> Optional[int]: - return self._ambulance_team_damage - - def get_ambulance_team_position(self) -> Optional[EntityID]: - return self._ambulance_team_position - - def get_target_entity_id(self) -> Optional[EntityID]: - return self._target_entity_id - - def get_action(self) -> Optional[int]: - return self._action + action if action is not None else -1, + target_entity_id, + StandardMessagePriority.NORMAL, + sender_entity_id, + std_message.get_ttl(), + ) + + def __hash__(self): + h = super().__hash__() + return hash( + ( + h, + self._ambulance_team_entity_id, + self._ambulance_team_hp, + self._ambulance_team_buriedness, + self._ambulance_team_damage, + self._ambulance_team_position, + self._target_entity_id, + self._action, + ) + ) + + def __str__(self) -> str: + return f"MessageAmbulanceTeam(ambulance_team_entity_id={self._ambulance_team_entity_id}, ambulance_team_hp={self._ambulance_team_hp}, ambulance_team_buriedness={self._ambulance_team_buriedness}, ambulance_team_damage={self._ambulance_team_damage}, ambulance_team_position={self._ambulance_team_position}, target_entity_id={self._target_entity_id}, action={self._action}, ttl={self._ttl})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_building.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py similarity index 57% rename from adf_core_python/core/agent/communication/standard/bundle/infomation/message_building.py rename to adf_core_python/core/agent/communication/standard/bundle/information/message_building.py index e26d00c..3363e92 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_building.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py @@ -12,6 +12,10 @@ from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( StandardMessagePriority, ) +from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( + read_with_exist_flag, + write_with_exist_flag, +) class MessageBuilding(StandardMessage): @@ -24,87 +28,95 @@ def __init__( self, is_wireless_message: bool, building: Building, - priority: StandardMessagePriority = StandardMessagePriority.NORMAL, - sender_id: int = -1, - ttl: int = -1, + priority: StandardMessagePriority, + sender_entity_id: EntityID, + ttl: Optional[int] = None, ): - super().__init__(is_wireless_message, priority, sender_id, ttl) + super().__init__(is_wireless_message, priority, sender_entity_id, ttl) self._building_entity_id: Optional[EntityID] = building.get_id() self._building_brokenness: Optional[int] = building.get_brokenness() or None self._building_fireyness: Optional[int] = building.get_fieryness() or None self._building_temperature: Optional[int] = building.get_temperature() or None - def get_byte_size(self) -> int: - return self.to_bytes().__len__() + def get_building_entity_id(self) -> Optional[EntityID]: + return self._building_entity_id + + def get_building_brokenness(self) -> Optional[int]: + return self._building_brokenness + + def get_building_fireyness(self) -> Optional[int]: + return self._building_fireyness + + def get_building_temperature(self) -> Optional[int]: + return self._building_temperature + + def get_bit_size(self) -> int: + return self.to_bits().__len__() - def to_bytes(self) -> bytes: - bit_array = bitarray() - self.write_with_exist_flag( + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + write_with_exist_flag( bit_array, self._building_entity_id.get_value() if self._building_entity_id else None, self.SIZE_BUILDING_ENTITY_ID, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._building_brokenness, self.SIZE_BUILDING_BROKENNESS, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._building_fireyness, self.SIZE_BUILDING_FIREYNESS, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._building_temperature, self.SIZE_BUILDING_TEMPERATURE, ) - return bit_array.tobytes() + return bit_array @classmethod - def from_bytes(cls, bytes: bytes) -> MessageBuilding: - bit_array = bitarray() - bit_array.frombytes(bytes) - raw_building_entity_id = cls.read_with_exist_flag( - bit_array, cls.SIZE_BUILDING_ENTITY_ID + def from_bits( + cls, bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID + ) -> MessageBuilding: + std_message = super().from_bits( + bit_array, is_wireless_message, sender_entity_id ) - building_entity_id = ( - EntityID(raw_building_entity_id) if raw_building_entity_id else None - ) - building_brokenness = cls.read_with_exist_flag( + building_id = read_with_exist_flag(bit_array, cls.SIZE_BUILDING_ENTITY_ID) + building_brokenness = read_with_exist_flag( bit_array, cls.SIZE_BUILDING_BROKENNESS ) - building_fireyness = cls.read_with_exist_flag( + building_fireyness = read_with_exist_flag( bit_array, cls.SIZE_BUILDING_FIREYNESS ) - building_temperature = cls.read_with_exist_flag( + building_temperature = read_with_exist_flag( bit_array, cls.SIZE_BUILDING_TEMPERATURE ) - building = Building( - building_entity_id.get_value() if building_entity_id else None - ) + building = Building(building_id or -1) building.set_brokenness(building_brokenness) building.set_fieryness(building_fireyness) building.set_temperature(building_temperature) return MessageBuilding( False, building, + StandardMessagePriority.NORMAL, + sender_entity_id, + std_message.get_ttl(), ) - def get_check_key(self) -> str: - building_entity_id_value = ( - self._building_entity_id.get_value() if self._building_entity_id else None + def __hash__(self): + h = super().__hash__() + return hash( + ( + h, + self._building_entity_id, + self._building_brokenness, + self._building_fireyness, + self._building_temperature, + ) ) - return f"{self.__class__.__name__} > building: {building_entity_id_value}" - def get_building_entity_id(self) -> Optional[EntityID]: - return self._building_entity_id - - def get_building_brokenness(self) -> Optional[int]: - return self._building_brokenness - - def get_building_fireyness(self) -> Optional[int]: - return self._building_fireyness - - def get_building_temperature(self) -> Optional[int]: - return self._building_temperature + def __str__(self): + return f"MessageBuilding(building_entity_id={self._building_entity_id}, building_brokenness={self._building_brokenness}, building_fireyness={self._building_fireyness}, building_temperature={self._building_temperature})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_civilian.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py similarity index 58% rename from adf_core_python/core/agent/communication/standard/bundle/infomation/message_civilian.py rename to adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py index bc3c73b..ac29847 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_civilian.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py @@ -12,6 +12,10 @@ from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( StandardMessagePriority, ) +from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( + read_with_exist_flag, + write_with_exist_flag, +) class MessageCivilian(StandardMessage): @@ -25,73 +29,84 @@ def __init__( self, is_wireless_message: bool, civilian: Civilian, - priority: StandardMessagePriority = StandardMessagePriority.NORMAL, - sender_id: int = -1, - ttl: int = -1, + priority: StandardMessagePriority, + sender_entity_id: EntityID, + ttl: Optional[int] = None, ): - super().__init__(is_wireless_message, priority, sender_id, ttl) + super().__init__(is_wireless_message, priority, sender_entity_id, ttl) self._civilian_entity_id: Optional[EntityID] = civilian.get_id() self._civilian_hp: Optional[int] = civilian.get_hp() or None self._civilian_buriedness: Optional[int] = civilian.get_buriedness() or None self._civilian_damage: Optional[int] = civilian.get_damage() or None self._civilian_position: Optional[EntityID] = civilian.get_position() or None - def get_byte_size(self) -> int: - return self.to_bytes().__len__() + def get_civilian_entity_id(self) -> Optional[EntityID]: + return self._civilian_entity_id + + def get_civilian_hp(self) -> Optional[int]: + return self._civilian_hp + + def get_civilian_buriedness(self) -> Optional[int]: + return self._civilian_buriedness + + def get_civilian_damage(self) -> Optional[int]: + return self._civilian_damage + + def get_civilian_position(self) -> Optional[EntityID]: + return self._civilian_position + + def get_bit_size(self) -> int: + return self.to_bits().__len__() - def to_bytes(self) -> bytes: - bit_array = bitarray() - self.write_with_exist_flag( + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + write_with_exist_flag( bit_array, self._civilian_entity_id.get_value() if self._civilian_entity_id else None, self.SIZE_CIVILIAN_ENTITY_ID, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._civilian_hp, self.SIZE_CIVILIAN_HP, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._civilian_buriedness, self.SIZE_CIVILIAN_BURIEDNESS, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._civilian_damage, self.SIZE_CIVILIAN_DAMAGE, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._civilian_position.get_value() if self._civilian_position else None, self.SIZE_CIVILIAN_POSITION, ) - return bit_array.tobytes() + return bit_array @classmethod - def from_bytes(cls, bytes: bytes) -> MessageCivilian: - bit_array = bitarray() - bit_array.frombytes(bytes) - raw_civilian_entity_id = cls.read_with_exist_flag( - bit_array, cls.SIZE_CIVILIAN_ENTITY_ID + def from_bits( + cls, bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID + ) -> MessageCivilian: + std_message = super().from_bits( + bit_array, is_wireless_message, sender_entity_id ) - civilian_entity_id = ( - EntityID(raw_civilian_entity_id) if raw_civilian_entity_id else None - ) - civilian_hp = cls.read_with_exist_flag(bit_array, cls.SIZE_CIVILIAN_HP) - civilian_buriedness = cls.read_with_exist_flag( + civilian_id = read_with_exist_flag(bit_array, cls.SIZE_CIVILIAN_ENTITY_ID) + civilian_hp = read_with_exist_flag(bit_array, cls.SIZE_CIVILIAN_HP) + civilian_buriedness = read_with_exist_flag( bit_array, cls.SIZE_CIVILIAN_BURIEDNESS ) - civilian_damage = cls.read_with_exist_flag(bit_array, cls.SIZE_CIVILIAN_DAMAGE) - raw_civilian_position = cls.read_with_exist_flag( + civilian_damage = read_with_exist_flag(bit_array, cls.SIZE_CIVILIAN_DAMAGE) + raw_civilian_position = read_with_exist_flag( bit_array, cls.SIZE_CIVILIAN_POSITION ) civilian_position = ( EntityID(raw_civilian_position) if raw_civilian_position else None ) - civilian = Civilian( - civilian_entity_id.get_value() if civilian_entity_id else None - ) + civilian = Civilian(civilian_id or -1) civilian.set_hp(civilian_hp) civilian.set_buriedness(civilian_buriedness) civilian.set_damage(civilian_damage) @@ -99,25 +114,23 @@ def from_bytes(cls, bytes: bytes) -> MessageCivilian: return MessageCivilian( False, civilian, + StandardMessagePriority.NORMAL, + sender_entity_id, + std_message.get_ttl(), ) - def get_check_key(self) -> str: - civilian_entity_id_value = ( - self._civilian_entity_id.get_value() if self._civilian_entity_id else None + def __hash__(self): + h = super().__hash__() + return hash( + ( + h, + self._civilian_entity_id, + self._civilian_hp, + self._civilian_buriedness, + self._civilian_damage, + self._civilian_position, + ) ) - return f"{self.__class__.__name__} > civilian: {civilian_entity_id_value}" - - def get_civilian_entity_id(self) -> Optional[EntityID]: - return self._civilian_entity_id - - def get_civilian_hp(self) -> Optional[int]: - return self._civilian_hp - def get_civilian_buriedness(self) -> Optional[int]: - return self._civilian_buriedness - - def get_civilian_damage(self) -> Optional[int]: - return self._civilian_damage - - def get_civilian_position(self) -> Optional[EntityID]: - return self._civilian_position + def __str__(self): + return f"MessageCivilian(civilian_entity_id={self._civilian_entity_id}, civilian_hp={self._civilian_hp}, civilian_buriedness={self._civilian_buriedness}, civilian_damage={self._civilian_damage}, civilian_position={self._civilian_position})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_fire_brigade.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py similarity index 63% rename from adf_core_python/core/agent/communication/standard/bundle/infomation/message_fire_brigade.py rename to adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py index b2d9298..11742d5 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_fire_brigade.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py @@ -12,10 +12,14 @@ from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( StandardMessagePriority, ) +from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( + read_with_exist_flag, + write_with_exist_flag, +) class MessageFireBrigade(StandardMessage): - CTION_REST: int = 0 + ACTION_REST: int = 0 ACTION_MOVE: int = 1 ACTION_EXTINGUISH: int = 2 ACTION_REFILL: int = 3 @@ -36,11 +40,11 @@ def __init__( fire_brigade: FireBrigadeEntity, action: int, target_entity_id: EntityID, - priority: StandardMessagePriority = StandardMessagePriority.NORMAL, - sender_id: int = -1, - ttl: int = -1, + priority: StandardMessagePriority, + sender_entity_id: EntityID, + ttl: Optional[int] = None, ): - super().__init__(is_wireless_message, priority, sender_id, ttl) + super().__init__(is_wireless_message, priority, sender_entity_id, ttl) self._fire_brigade_entity_id: Optional[EntityID] = fire_brigade.get_id() self._fire_brigade_hp: Optional[int] = fire_brigade.get_hp() or None self._fire_brigade_buriedness: Optional[int] = ( @@ -54,88 +58,112 @@ def __init__( self._target_entity_id: Optional[EntityID] = target_entity_id self._action: Optional[int] = action - def get_byte_size(self) -> int: - return self.to_bytes().__len__() + def get_fire_brigade_entity_id(self) -> Optional[EntityID]: + return self._fire_brigade_entity_id + + def get_fire_brigade_hp(self) -> Optional[int]: + return self._fire_brigade_hp + + def get_fire_brigade_buriedness(self) -> Optional[int]: + return self._fire_brigade_buriedness + + def get_fire_brigade_damage(self) -> Optional[int]: + return self._fire_brigade_damage + + def get_fire_brigade_position(self) -> Optional[EntityID]: + return self._fire_brigade_position - def to_bytes(self) -> bytes: - bit_array = bitarray() - self.write_with_exist_flag( + def get_fire_brigade_water(self) -> Optional[int]: + return self._fire_brigade_water + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._target_entity_id + + def get_action(self) -> Optional[int]: + return self._action + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + write_with_exist_flag( bit_array, self._fire_brigade_entity_id.get_value() if self._fire_brigade_entity_id else None, self.SIZE_FIRE_BRIGADE_ENTITY_ID, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._fire_brigade_hp, self.SIZE_FIRE_BRIGADE_HP, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._fire_brigade_buriedness, self.SIZE_FIRE_BRIGADE_BURIEDNESS, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._fire_brigade_damage, self.SIZE_FIRE_BRIGADE_DAMAGE, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._fire_brigade_position.get_value() if self._fire_brigade_position else None, self.SIZE_FIRE_BRIGADE_POSITION, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._fire_brigade_water, self.SIZE_FIRE_BRIGADE_WATER, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._target_entity_id.get_value() if self._target_entity_id else None, self.SIZE_TARGET_ENTITY_ID, ) - self.write_with_exist_flag(bit_array, self._action, self.SIZE_ACTION) - return bit_array.tobytes() + write_with_exist_flag(bit_array, self._action, self.SIZE_ACTION) + return bit_array @classmethod - def from_bytes(cls, bytes: bytes) -> MessageFireBrigade: - bit_array = bitarray() - bit_array.frombytes(bytes) - raw_fire_brigade_entity_id = cls.read_with_exist_flag( - bit_array, cls.SIZE_FIRE_BRIGADE_ENTITY_ID + def from_bits( + cls, bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID + ) -> MessageFireBrigade: + std_message = super().from_bits( + bit_array, is_wireless_message, sender_entity_id ) - fire_brigade_entity_id = ( - EntityID(raw_fire_brigade_entity_id) if raw_fire_brigade_entity_id else None + fire_brigade_id = read_with_exist_flag( + bit_array, cls.SIZE_FIRE_BRIGADE_ENTITY_ID ) - fire_brigade_hp = cls.read_with_exist_flag(bit_array, cls.SIZE_FIRE_BRIGADE_HP) - fire_brigade_buriedness = cls.read_with_exist_flag( + fire_brigade_hp = read_with_exist_flag(bit_array, cls.SIZE_FIRE_BRIGADE_HP) + fire_brigade_buriedness = read_with_exist_flag( bit_array, cls.SIZE_FIRE_BRIGADE_BURIEDNESS ) - fire_brigade_damage = cls.read_with_exist_flag( + fire_brigade_damage = read_with_exist_flag( bit_array, cls.SIZE_FIRE_BRIGADE_DAMAGE ) - raw_fire_brigade_position = cls.read_with_exist_flag( + raw_fire_brigade_position = read_with_exist_flag( bit_array, cls.SIZE_FIRE_BRIGADE_POSITION ) fire_brigade_position = ( EntityID(raw_fire_brigade_position) if raw_fire_brigade_position else None ) - fire_brigade_water = cls.read_with_exist_flag( + fire_brigade_water = read_with_exist_flag( bit_array, cls.SIZE_FIRE_BRIGADE_WATER ) - raw_target_entity_id = cls.read_with_exist_flag( + raw_target_entity_id = read_with_exist_flag( bit_array, cls.SIZE_TARGET_ENTITY_ID ) target_entity_id = ( - EntityID(raw_target_entity_id) if raw_target_entity_id else None + EntityID(raw_target_entity_id) if raw_target_entity_id else EntityID(-1) ) - action = cls.read_with_exist_flag(bit_array, cls.SIZE_ACTION) + action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) fire_brigade = FireBrigadeEntity( - fire_brigade_entity_id.get_value() if fire_brigade_entity_id else None + fire_brigade_id or -1, ) fire_brigade.set_hp(fire_brigade_hp) fire_brigade.set_buriedness(fire_brigade_buriedness) @@ -145,41 +173,28 @@ def from_bytes(cls, bytes: bytes) -> MessageFireBrigade: return MessageFireBrigade( False, fire_brigade, - action or -1, - target_entity_id or EntityID(-1), - ) - - def get_check_key(self) -> str: - fire_brigade_entity_id_value = ( - self._fire_brigade_entity_id.get_value() - if self._fire_brigade_entity_id - else None - ) - target_entity_id_value = ( - self._target_entity_id.get_value() if self._target_entity_id else None - ) - return f"{self.__class__.__name__} > fire brigade: {fire_brigade_entity_id_value} > target: {target_entity_id_value} > action: {self._action}" - - def get_fire_brigade_entity_id(self) -> Optional[EntityID]: - return self._fire_brigade_entity_id - - def get_fire_brigade_hp(self) -> Optional[int]: - return self._fire_brigade_hp - - def get_fire_brigade_buriedness(self) -> Optional[int]: - return self._fire_brigade_buriedness - - def get_fire_brigade_damage(self) -> Optional[int]: - return self._fire_brigade_damage - - def get_fire_brigade_position(self) -> Optional[EntityID]: - return self._fire_brigade_position - - def get_fire_brigade_water(self) -> Optional[int]: - return self._fire_brigade_water - - def get_target_entity_id(self) -> Optional[EntityID]: - return self._target_entity_id - - def get_action(self) -> Optional[int]: - return self._action + action if action is not None else -1, + target_entity_id, + StandardMessagePriority.NORMAL, + sender_entity_id, + std_message.get_ttl(), + ) + + def __hash__(self): + h = super().__hash__() + return hash( + ( + h, + self._fire_brigade_entity_id, + self._fire_brigade_hp, + self._fire_brigade_buriedness, + self._fire_brigade_damage, + self._fire_brigade_position, + self._fire_brigade_water, + self._target_entity_id, + self._action, + ) + ) + + def __str__(self): + return f"MessageFireBrigade(fire_brigade_entity_id={self._fire_brigade_entity_id}, fire_brigade_hp={self._fire_brigade_hp}, fire_brigade_buriedness={self._fire_brigade_buriedness}, fire_brigade_damage={self._fire_brigade_damage}, fire_brigade_position={self._fire_brigade_position}, fire_brigade_water={self._fire_brigade_water}, target_entity_id={self._target_entity_id}, action={self._action})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_police_force.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py similarity index 61% rename from adf_core_python/core/agent/communication/standard/bundle/infomation/message_police_force.py rename to adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py index 6f3c43c..8280184 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_police_force.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py @@ -12,10 +12,14 @@ from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( StandardMessagePriority, ) +from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( + read_with_exist_flag, + write_with_exist_flag, +) class MessagePoliceForce(StandardMessage): - CTION_REST: int = 0 + ACTION_REST: int = 0 ACTION_MOVE: int = 1 ACTION_CLEAR: int = 2 @@ -33,11 +37,11 @@ def __init__( police_force: PoliceForceEntity, action: int, target_entity_id: EntityID, - priority: StandardMessagePriority = StandardMessagePriority.NORMAL, - sender_id: int = -1, - ttl: int = -1, + priority: StandardMessagePriority, + sender_entity_id: EntityID, + ttl: Optional[int] = None, ): - super().__init__(is_wireless_message, priority, sender_id, ttl) + super().__init__(is_wireless_message, priority, sender_entity_id, ttl) self._police_force_entity_id: Optional[EntityID] = police_force.get_id() self._police_force_hp: Optional[int] = police_force.get_hp() or None self._police_force_buriedness: Optional[int] = ( @@ -50,81 +54,100 @@ def __init__( self._target_entity_id: Optional[EntityID] = target_entity_id self._action: Optional[int] = action - def get_byte_size(self) -> int: - return self.to_bytes().__len__() + def get_police_force_entity_id(self) -> Optional[EntityID]: + return self._police_force_entity_id + + def get_police_force_hp(self) -> Optional[int]: + return self._police_force_hp + + def get_police_force_buriedness(self) -> Optional[int]: + return self._police_force_buriedness + + def get_police_force_damage(self) -> Optional[int]: + return self._police_force_damage + + def get_police_force_position(self) -> Optional[EntityID]: + return self._police_force_position - def to_bytes(self) -> bytes: - bit_array = bitarray() - self.write_with_exist_flag( + def get_target_entity_id(self) -> Optional[EntityID]: + return self._target_entity_id + + def get_action(self) -> Optional[int]: + return self._action + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + write_with_exist_flag( bit_array, self._police_force_entity_id.get_value() if self._police_force_entity_id else None, self.SIZE_POLICE_FORCE_ENTITY_ID, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._police_force_hp, self.SIZE_POLICE_FORCE_HP, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._police_force_buriedness, self.SIZE_POLICE_FORCE_BURIEDNESS, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._police_force_damage, self.SIZE_POLICE_FORCE_DAMAGE, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._police_force_position.get_value() if self._police_force_position else None, self.SIZE_POLICE_FORCE_POSITION, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._target_entity_id.get_value() if self._target_entity_id else None, self.SIZE_TARGET_ENTITY_ID, ) - self.write_with_exist_flag(bit_array, self._action, self.SIZE_ACTION) - return bit_array.tobytes() + write_with_exist_flag(bit_array, self._action, self.SIZE_ACTION) + return bit_array @classmethod - def from_bytes(cls, bytes: bytes) -> MessagePoliceForce: - bit_array = bitarray() - bit_array.frombytes(bytes) - raw_police_force_entity_id = cls.read_with_exist_flag( - bit_array, cls.SIZE_POLICE_FORCE_ENTITY_ID + def from_bits( + cls, bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID + ) -> MessagePoliceForce: + std_message = super().from_bits( + bit_array, is_wireless_message, sender_entity_id ) - police_force_entity_id = ( - EntityID(raw_police_force_entity_id) if raw_police_force_entity_id else None + police_force_id = read_with_exist_flag( + bit_array, cls.SIZE_POLICE_FORCE_ENTITY_ID ) - police_force_hp = cls.read_with_exist_flag(bit_array, cls.SIZE_POLICE_FORCE_HP) - police_force_buriedness = cls.read_with_exist_flag( + police_force_hp = read_with_exist_flag(bit_array, cls.SIZE_POLICE_FORCE_HP) + police_force_buriedness = read_with_exist_flag( bit_array, cls.SIZE_POLICE_FORCE_BURIEDNESS ) - police_force_damage = cls.read_with_exist_flag( + police_force_damage = read_with_exist_flag( bit_array, cls.SIZE_POLICE_FORCE_DAMAGE ) - raw_police_force_position = cls.read_with_exist_flag( + raw_police_force_position = read_with_exist_flag( bit_array, cls.SIZE_POLICE_FORCE_POSITION ) police_force_position = ( EntityID(raw_police_force_position) if raw_police_force_position else None ) - raw_target_entity_id = cls.read_with_exist_flag( + raw_target_entity_id = read_with_exist_flag( bit_array, cls.SIZE_TARGET_ENTITY_ID ) target_entity_id = ( - EntityID(raw_target_entity_id) if raw_target_entity_id else None - ) - action = cls.read_with_exist_flag(bit_array, cls.SIZE_ACTION) - police_force = PoliceForceEntity( - police_force_entity_id.get_value() if police_force_entity_id else None + EntityID(raw_target_entity_id) if raw_target_entity_id else EntityID(-1) ) + action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) + police_force = PoliceForceEntity(police_force_id or -1) police_force.set_hp(police_force_hp) police_force.set_buriedness(police_force_buriedness) police_force.set_damage(police_force_damage) @@ -132,38 +155,27 @@ def from_bytes(cls, bytes: bytes) -> MessagePoliceForce: return MessagePoliceForce( False, police_force, - action or -1, - target_entity_id or EntityID(-1), - ) - - def get_check_key(self) -> str: - police_force_entity_id_value = ( - self._police_force_entity_id.get_value() - if self._police_force_entity_id - else None - ) - target_entity_id_value = ( - self._target_entity_id.get_value() if self._target_entity_id else None - ) - return f"{self.__class__.__name__} > police force: {police_force_entity_id_value} > target: {target_entity_id_value} > action: {self._action}" - - def get_police_force_entity_id(self) -> Optional[EntityID]: - return self._police_force_entity_id - - def get_police_force_hp(self) -> Optional[int]: - return self._police_force_hp - - def get_police_force_buriedness(self) -> Optional[int]: - return self._police_force_buriedness - - def get_police_force_damage(self) -> Optional[int]: - return self._police_force_damage - - def get_police_force_position(self) -> Optional[EntityID]: - return self._police_force_position - - def get_target_entity_id(self) -> Optional[EntityID]: - return self._target_entity_id - - def get_action(self) -> Optional[int]: - return self._action + action if action is not None else -1, + target_entity_id, + StandardMessagePriority.NORMAL, + sender_entity_id, + std_message.get_ttl(), + ) + + def __hash__(self): + h = super().__hash__() + return hash( + ( + h, + self._police_force_entity_id, + self._police_force_hp, + self._police_force_buriedness, + self._police_force_damage, + self._police_force_position, + self._target_entity_id, + self._action, + ) + ) + + def __str__(self): + return f"MessagePoliceForce(police_force_entity_id={self._police_force_entity_id}, police_force_hp={self._police_force_hp}, police_force_buriedness={self._police_force_buriedness}, police_force_damage={self._police_force_damage}, police_force_position={self._police_force_position}, target_entity_id={self._target_entity_id}, action={self._action})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_road.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py similarity index 60% rename from adf_core_python/core/agent/communication/standard/bundle/infomation/message_road.py rename to adf_core_python/core/agent/communication/standard/bundle/information/message_road.py index 89a2863..9023973 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/infomation/message_road.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py @@ -13,10 +13,14 @@ from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( StandardMessagePriority, ) +from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( + read_with_exist_flag, + write_with_exist_flag, +) class MessageRoad(StandardMessage): - CTION_REST: int = 0 + ACTION_REST: int = 0 ACTION_MOVE: int = 1 ACTION_CLEAR: int = 2 @@ -34,11 +38,11 @@ def __init__( is_send_blockade_location: bool, is_passable: Optional[bool], blockade: Optional[Blockade], - priority: StandardMessagePriority = StandardMessagePriority.NORMAL, - sender_id: int = -1, - ttl: int = -1, + priority: StandardMessagePriority, + sender_entity_id: EntityID, + ttl: Optional[int] = None, ): - super().__init__(is_wireless_message, priority, sender_id, ttl) + super().__init__(is_wireless_message, priority, sender_entity_id, ttl) self._road_entity_id: Optional[EntityID] = road.get_id() self._road_blockade_entity_id: Optional[EntityID] = None self._road_blockade_repair_cost: Optional[int] = None @@ -55,24 +59,45 @@ def __init__( self._is_passable: Optional[bool] = is_passable self._is_send_blockade_location: bool = is_send_blockade_location - def get_byte_size(self) -> int: - return self.to_bytes().__len__() + def get_road_entity_id(self) -> Optional[EntityID]: + return self._road_entity_id + + def get_road_blockade_entity_id(self) -> Optional[EntityID]: + return self._road_blockade_entity_id + + def get_road_blockade_repair_cost(self) -> Optional[int]: + return self._road_blockade_repair_cost + + def get_road_blockade_x(self) -> Optional[int]: + return self._road_blockade_x - def to_bytes(self) -> bytes: - bit_array = bitarray() - self.write_with_exist_flag( + def get_road_blockade_y(self) -> Optional[int]: + return self._road_blockade_y + + def get_is_passable(self) -> Optional[bool]: + return self._is_passable + + def get_is_send_blockade_location(self) -> bool: + return self._is_send_blockade_location + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + write_with_exist_flag( bit_array, self._road_entity_id.get_value() if self._road_entity_id else None, self.SIZE_ROAD_ENTITY_ID, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._road_blockade_entity_id.get_value() if self._road_blockade_entity_id else None, self.SIZE_ROAD_BLOCKADE_ENTITY_ID, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._road_blockade_repair_cost if self._road_blockade_repair_cost @@ -80,88 +105,75 @@ def to_bytes(self) -> bytes: self.SIZE_ROAD_BLOCKADE_REPAIR_COST, ) if self._is_send_blockade_location: - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._road_blockade_x if self._road_blockade_x else None, self.SIZE_ROAD_BLOCKADE_X, ) - self.write_with_exist_flag( + write_with_exist_flag( bit_array, self._road_blockade_y if self._road_blockade_y else None, self.SIZE_ROAD_BLOCKADE_Y, ) else: - self.write_with_exist_flag(bit_array, None, self.SIZE_ROAD_BLOCKADE_X) - self.write_with_exist_flag(bit_array, None, self.SIZE_ROAD_BLOCKADE_Y) - self.write_with_exist_flag( + write_with_exist_flag(bit_array, None, self.SIZE_ROAD_BLOCKADE_X) + write_with_exist_flag(bit_array, None, self.SIZE_ROAD_BLOCKADE_Y) + write_with_exist_flag( bit_array, self._is_passable if self._is_passable else None, self.SIZE_PASSABLE, ) - return bit_array.tobytes() + return bit_array @classmethod - def from_bytes(cls, bytes: bytes) -> MessageRoad: - bit_array = bitarray() - bit_array.frombytes(bytes) - raw_road_entity_id = cls.read_with_exist_flag( - bit_array, cls.SIZE_ROAD_ENTITY_ID + def from_bits( + cls, bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID + ) -> MessageRoad: + std_message = super().from_bits( + bit_array, is_wireless_message, sender_entity_id ) - road_entity_id = EntityID(raw_road_entity_id) if raw_road_entity_id else None - raw_road_blockade_entity_id = cls.read_with_exist_flag( + road_id = read_with_exist_flag(bit_array, cls.SIZE_ROAD_ENTITY_ID) + road_blockade_id = read_with_exist_flag( bit_array, cls.SIZE_ROAD_BLOCKADE_ENTITY_ID ) - road_blockade_entity_id = ( - EntityID(raw_road_blockade_entity_id) - if raw_road_blockade_entity_id - else None - ) - road_blockade_repair_cost = cls.read_with_exist_flag( + road_blockade_repair_cost = read_with_exist_flag( bit_array, cls.SIZE_ROAD_BLOCKADE_REPAIR_COST ) - road_blockade_x = cls.read_with_exist_flag(bit_array, cls.SIZE_ROAD_BLOCKADE_X) - road_blockade_y = cls.read_with_exist_flag(bit_array, cls.SIZE_ROAD_BLOCKADE_Y) + road_blockade_x = read_with_exist_flag(bit_array, cls.SIZE_ROAD_BLOCKADE_X) + road_blockade_y = read_with_exist_flag(bit_array, cls.SIZE_ROAD_BLOCKADE_Y) is_passable = ( - True if cls.read_with_exist_flag(bit_array, cls.SIZE_PASSABLE) else False - ) - road = Road(road_entity_id.get_value() if road_entity_id else None) - blockade = Blockade( - road_blockade_entity_id.get_value() if road_blockade_entity_id else None + True if read_with_exist_flag(bit_array, cls.SIZE_PASSABLE) else False ) + road = Road(road_id or -1) + blockade = Blockade(road_blockade_id or -1) blockade.set_repaire_cost(road_blockade_repair_cost) blockade.set_x(road_blockade_x) blockade.set_y(road_blockade_y) return MessageRoad( - False, + is_wireless_message, road, False, is_passable, blockade, + StandardMessagePriority.NORMAL, + sender_entity_id, + std_message.get_ttl(), ) - def get_check_key(self) -> str: - road_entity_id_value = ( - self._road_entity_id.get_value() if self._road_entity_id else None + def __hash__(self): + h = super().__hash__() + return hash( + ( + h, + self._road_entity_id, + self._road_blockade_entity_id, + self._road_blockade_repair_cost, + self._road_blockade_x, + self._road_blockade_y, + self._is_passable, + self._is_send_blockade_location, + ) ) - return f"{self.__class__.__name__} > road: {road_entity_id_value}" - - def get_road_entity_id(self) -> Optional[EntityID]: - return self._road_entity_id - - def get_road_blockade_entity_id(self) -> Optional[EntityID]: - return self._road_blockade_entity_id - - def get_road_blockade_repair_cost(self) -> Optional[int]: - return self._road_blockade_repair_cost - def get_road_blockade_x(self) -> Optional[int]: - return self._road_blockade_x - - def get_road_blockade_y(self) -> Optional[int]: - return self._road_blockade_y - - def get_is_passable(self) -> Optional[bool]: - return self._is_passable - - def get_is_send_blockade_location(self) -> bool: - return self._is_send_blockade_location + def __str__(self): + return f"MessageRoad(road_entity_id={self._road_entity_id}, road_blockade_entity_id={self._road_blockade_entity_id}, road_blockade_repair_cost={self._road_blockade_repair_cost}, road_blockade_x={self._road_blockade_x}, road_blockade_y={self._road_blockade_y}, is_passable={self._is_passable}, is_send_blockade_location={self._is_send_blockade_location})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/standard_message.py b/adf_core_python/core/agent/communication/standard/bundle/standard_message.py index c66f66a..490f367 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/standard_message.py +++ b/adf_core_python/core/agent/communication/standard/bundle/standard_message.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Optional from bitarray import bitarray @@ -6,51 +8,64 @@ from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( StandardMessagePriority, ) +from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( + read_with_exist_flag, + write_with_exist_flag, +) from adf_core_python.core.component.communication.communication_message import ( CommunicationMessage, ) class StandardMessage(CommunicationMessage): + SIZE_TTL: int = 3 + def __init__( self, is_wireless_message: bool, priority: StandardMessagePriority, - sender_id: int = -1, - ttl: int = -1, + sender_entity_id: EntityID, + ttl: Optional[int] = None, ): super().__init__(is_wireless_message) self._priority = priority - self._sender_id = sender_id + self._sender_entity_id = sender_entity_id self._ttl = ttl def get_sender_entity_id(self) -> EntityID: - return EntityID(self._sender_id) - - def get_ttl(self) -> int: - return self._ttl + return self._sender_entity_id def get_priority(self) -> StandardMessagePriority: return self._priority - @staticmethod - def write_with_exist_flag( - bit_array: bitarray, value: Optional[int], size: int - ) -> None: - if value is None: - bit_array.extend([False]) - else: - bit_array.extend([True]) - bit_array.frombytes(value.to_bytes(size, "big")) - - @staticmethod - def read_with_exist_flag(bit_array: bitarray, size: int) -> Optional[int]: - exist_flag = bit_array.pop(0) - if exist_flag == 0: - return None - elif exist_flag == 1: - value = int.from_bytes(bit_array.tobytes()[:size], "big") - del bit_array[:size] - return value - else: - raise ValueError("Invalid exist flag") + def get_ttl(self) -> Optional[int]: + return self._ttl + + @classmethod + def from_bits( + cls, + bit_array: bitarray, + is_wireless_message: bool, + sender_entity_id: EntityID, + ) -> StandardMessage: + ttl = read_with_exist_flag(bit_array, cls.SIZE_TTL) + return StandardMessage( + is_wireless_message, + StandardMessagePriority.NORMAL, + sender_entity_id, + ttl, + ) + + def to_bits(self) -> bitarray: + bit_array = bitarray() + write_with_exist_flag(bit_array, self._ttl, self.SIZE_TTL) + return bit_array + + def get_bit_size(self) -> int: + raise NotImplementedError + + def __hash__(self): + return hash((self._sender_entity_id, self._priority, self._ttl)) + + def __str__(self) -> str: + return f"StandardMessage(sender_entity_id={self._sender_entity_id}, priority={self._priority}, ttl={self._ttl})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py b/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py index 1c6b17f..e8e2e25 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py +++ b/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py @@ -12,3 +12,11 @@ class StandardMessagePriority(Enum): def __str__(self) -> str: return self.name.lower() + + def __lt__(self, other): + if isinstance(other, StandardMessagePriority): + return self.value < other.value + + def __le__(self, other): + if isinstance(other, StandardMessagePriority): + return self.value <= other.value diff --git a/adf_core_python/core/agent/communication/standard/standard_communication_module.py b/adf_core_python/core/agent/communication/standard/standard_communication_module.py index 057930d..3fdffb3 100644 --- a/adf_core_python/core/agent/communication/standard/standard_communication_module.py +++ b/adf_core_python/core/agent/communication/standard/standard_communication_module.py @@ -1,14 +1,176 @@ -from rcrs_core.agents.agent import Agent +from __future__ import annotations + +from typing import TYPE_CHECKING + +from bitarray import bitarray +from rcrs_core.commands.AKSpeak import AKSpeak +from rcrs_core.worldmodel.entityID import EntityID from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) +from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( + read_with_exist_flag, + write_with_exist_flag, +) from adf_core_python.core.component.communication.communication_module import ( CommunicationModule, ) +from adf_core_python.core.logger.logger import get_logger + +if TYPE_CHECKING: + from adf_core_python.core.agent.agent import Agent class StandardCommunicationModule(CommunicationModule): + ESCAPE_CHAR = bitarray("11111111") + SIZE_ID: int = 5 + SIZE_TTL: int = 3 + def receive(self, agent: Agent, message_manager: MessageManager) -> None: - raise NotImplementedError + heard_commands = agent._agent_info.get_heard_commands() + for command in heard_commands: + if isinstance(command, AKSpeak): + sender_entity_id = command.agent_id + if sender_entity_id == agent.get_entity_id(): + continue + data = command.message + is_wireless_message = command.channel != 0 + + if len(data) == 0: + continue + + if is_wireless_message: + bit_array = bitarray() + bit_array.frombytes(data) + self.add_received_message( + message_manager, + is_wireless_message, + sender_entity_id, + bit_array, + ) + else: + try: + voice_message = data.decode("utf-8") + if voice_message.startswith("Help") or voice_message.startswith( + "Ouch" + ): + message_manager.add_heard_agent_help_message_count() + continue + except UnicodeDecodeError: + pass + + escape_char = self.ESCAPE_CHAR.tobytes()[0] + i = 0 + bit_array = bitarray() + a = bitarray() + a.frombytes(data) + while i < len(data): + if data[i] == escape_char: + if (i + 1) >= len(data): + self.add_received_message( + message_manager, + False, + sender_entity_id, + bit_array, + ) + break + elif data[i + 1] != escape_char: + self.add_received_message( + message_manager, + False, + sender_entity_id, + bit_array, + ) + bit_array.clear() + i += 1 # Skip the next character + continue + i += 1 # Skip the escaped character + bits = bitarray() + bits.frombytes(data[i].to_bytes(1, "big")) + bit_array.extend(bits) + i += 1 def send(self, agent: Agent, message_manager: MessageManager) -> None: - raise NotImplementedError + voice_message_limit_bytes = agent._scenario_info.get_value( + "comms.channels.0.messages.size", 256 + ) + left_voice_message_limit_bits = voice_message_limit_bytes * 8 + voice_message_bit_array = bitarray() + + send_messages = message_manager.get_channel_send_message_list() + + for channel in range(len(send_messages)): + for message in send_messages[channel]: + message_class_index = message_manager.get_message_class_index( + type(message) + ) + bit_array = bitarray() + + write_with_exist_flag(bit_array, message_class_index, self.SIZE_ID) + + bit_array.extend(message.to_bits()) + + if channel > 0: + agent.send_speak( + agent._agent_info.get_time(), + bit_array, + channel, + ) + else: + # bit_arrayを8bitごとに区切れるようにして、エスケープ処理を行う + if len(bit_array) % 8 != 0: + bit_array.extend([False] * (8 - len(bit_array) % 8)) + message_bit_size = len(bit_array) + if message_bit_size <= left_voice_message_limit_bits: + esceped_message_data = bitarray() + for i in range(len(bit_array) // 8): + if bit_array[(i * 8) : ((i + 1) * 8)] == self.ESCAPE_CHAR: + esceped_message_data.extend(self.ESCAPE_CHAR) + esceped_message_data.extend( + bit_array[(i * 8) : ((i + 1) * 8)] + ) + esceped_message_data.extend(self.ESCAPE_CHAR) + if len(esceped_message_data) <= voice_message_limit_bytes: + left_voice_message_limit_bits -= len(esceped_message_data) + voice_message_bit_array.extend(esceped_message_data) + + if len(voice_message_bit_array) > 0: + agent.send_speak( + agent._agent_info.get_time(), + voice_message_bit_array, + 0, + ) + + def add_received_message( + self, + message_manager: MessageManager, + is_wireless_message: bool, + sender_entity_id: EntityID, + data: bitarray, + ) -> None: + message_class_index = read_with_exist_flag(data, self.SIZE_ID) + if message_class_index is None or message_class_index < 0: + get_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}" + ).warning(f"Invalid message class index: {message_class_index}") + return + + message = message_manager.get_message_class(message_class_index) + if message is None: + get_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}" + ).warning(f"Invalid message class index: {message_class_index}") + return + + if issubclass(message, StandardMessage): + message_instance = message.from_bits( + data, is_wireless_message, sender_entity_id + ) + message_manager.add_received_message(message_instance) + else: + get_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}" + ).warning(f"Invalid message class: {message}") + return diff --git a/adf_core_python/core/agent/communication/standard/utility/__init__.py b/adf_core_python/core/agent/communication/standard/utility/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py b/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py new file mode 100644 index 0000000..811c152 --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py @@ -0,0 +1,335 @@ +from rcrs_core.entities.ambulanceTeam import AmbulanceTeamEntity +from rcrs_core.entities.blockade import Blockade +from rcrs_core.entities.building import Building +from rcrs_core.entities.civilian import Civilian +from rcrs_core.entities.fireBrigade import FireBrigadeEntity +from rcrs_core.entities.policeForce import PoliceForceEntity +from rcrs_core.entities.road import Road + +from adf_core_python.core.agent.communication.standard.bundle.information.message_ambulance_team import ( + MessageAmbulanceTeam, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_building import ( + MessageBuilding, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_civilian import ( + MessageCivilian, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_fire_brigade import ( + MessageFireBrigade, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_police_force import ( + MessagePoliceForce, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_road import ( + MessageRoad, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) +from adf_core_python.core.agent.info.world_info import WorldInfo + + +def apply_to_world_info( + world_info: WorldInfo, + standard_message: StandardMessage, +) -> None: + """ + Apply to world info. + + PARAMETERS + ---------- + world_info: WorldInfo + The world info to apply to. + standard_message: StandardMessage + The standard message to apply to world info. + """ + + if isinstance(standard_message, MessageAmbulanceTeam): + _apply_to_world_info_ambulance_team(world_info, standard_message) + elif isinstance(standard_message, MessageFireBrigade): + _apply_to_world_info_fire_brigade(world_info, standard_message) + elif isinstance(standard_message, MessagePoliceForce): + _apply_to_world_info_police_force(world_info, standard_message) + elif isinstance(standard_message, MessageCivilian): + _apply_to_world_info_civilian(world_info, standard_message) + elif isinstance(standard_message, MessageBuilding): + _apply_to_world_info_building(world_info, standard_message) + elif isinstance(standard_message, MessageRoad): + _apply_to_world_info_road(world_info, standard_message) + else: + return + + +def _apply_to_world_info_ambulance_team( + world_info: WorldInfo, + message_ambulance_team: MessageAmbulanceTeam, +) -> None: + """ + Apply to world info for ambulance team. + + PARAMETERS + ---------- + world_info: WorldInfo + The world info to apply to. + standard_message: StandardMessage + The standard message to apply to world info. + """ + entity_id = message_ambulance_team.get_ambulance_team_entity_id() + if entity_id is None: + return + entity = world_info.get_entity(entity_id) + if entity is None: + ambulance = AmbulanceTeamEntity(entity_id.get_value()) + if (hp := message_ambulance_team.get_ambulance_team_hp()) is not None: + ambulance.set_hp(hp) + if (damege := message_ambulance_team.get_ambulance_team_damage()) is not None: + ambulance.set_damage(damege) + if ( + buriedness := message_ambulance_team.get_ambulance_team_buriedness() + ) is not None: + ambulance.set_buriedness(buriedness) + if ( + position := message_ambulance_team.get_ambulance_team_position() + ) is not None: + ambulance.set_position(position) + world_info.add_entity(ambulance) + else: + if isinstance(entity, AmbulanceTeamEntity): + if (hp := message_ambulance_team.get_ambulance_team_hp()) is not None: + entity.set_hp(hp) + if ( + damege := message_ambulance_team.get_ambulance_team_damage() + ) is not None: + entity.set_damage(damege) + if ( + buriedness := message_ambulance_team.get_ambulance_team_buriedness() + ) is not None: + entity.set_buriedness(buriedness) + if ( + position := message_ambulance_team.get_ambulance_team_position() + ) is not None: + entity.set_position(position) + + +def _apply_to_world_info_fire_brigade( + world_info: WorldInfo, + message_fire_brigade: MessageFireBrigade, +) -> None: + """ + Apply to world info for fire brigade. + + PARAMETERS + ---------- + world_info: WorldInfo + The world info to apply to. + standard_message: StandardMessage + The standard message to apply to world info. + """ + entity_id = message_fire_brigade.get_fire_brigade_entity_id() + if entity_id is None: + return + entity = world_info.get_entity(entity_id) + if entity is None: + fire_brigade = FireBrigadeEntity(entity_id.get_value()) + if (hp := message_fire_brigade.get_fire_brigade_hp()) is not None: + fire_brigade.set_hp(hp) + if (damage := message_fire_brigade.get_fire_brigade_damage()) is not None: + fire_brigade.set_damage(damage) + if ( + buriedness := message_fire_brigade.get_fire_brigade_buriedness() + ) is not None: + fire_brigade.set_buriedness(buriedness) + if (position := message_fire_brigade.get_fire_brigade_position()) is not None: + fire_brigade.set_position(position) + if (water := message_fire_brigade.get_fire_brigade_water()) is not None: + fire_brigade.set_water(water) + world_info.add_entity(fire_brigade) + else: + if isinstance(entity, FireBrigadeEntity): + if (hp := message_fire_brigade.get_fire_brigade_hp()) is not None: + entity.set_hp(hp) + if (damage := message_fire_brigade.get_fire_brigade_damage()) is not None: + entity.set_damage(damage) + if ( + buriedness := message_fire_brigade.get_fire_brigade_buriedness() + ) is not None: + entity.set_buriedness(buriedness) + if ( + position := message_fire_brigade.get_fire_brigade_position() + ) is not None: + entity.set_position(position) + if (water := message_fire_brigade.get_fire_brigade_water()) is not None: + entity.set_water(water) + + +def _apply_to_world_info_police_force( + world_info: WorldInfo, + message_police_force: MessagePoliceForce, +) -> None: + """ + Apply to world info for police force. + + PARAMETERS + ---------- + world_info: WorldInfo + The world info to apply to. + standard_message: StandardMessage + The standard message to apply to world info. + """ + entity_id = message_police_force.get_police_force_entity_id() + if entity_id is None: + return + entity = world_info.get_entity(entity_id) + if entity is None: + police_force = PoliceForceEntity(entity_id.get_value()) + if (hp := message_police_force.get_police_force_hp()) is not None: + police_force.set_hp(hp) + if (damage := message_police_force.get_police_force_damage()) is not None: + police_force.set_damage(damage) + if ( + buriedness := message_police_force.get_police_force_buriedness() + ) is not None: + police_force.set_buriedness(buriedness) + if (position := message_police_force.get_police_force_position()) is not None: + police_force.set_position(position) + world_info.add_entity(police_force) + else: + if isinstance(entity, PoliceForceEntity): + if (hp := message_police_force.get_police_force_hp()) is not None: + entity.set_hp(hp) + if (damage := message_police_force.get_police_force_damage()) is not None: + entity.set_damage(damage) + if ( + buriedness := message_police_force.get_police_force_buriedness() + ) is not None: + entity.set_buriedness(buriedness) + if ( + position := message_police_force.get_police_force_position() + ) is not None: + entity.set_position(position) + + +def _apply_to_world_info_civilian( + world_info: WorldInfo, + message_civilian: MessageCivilian, +) -> None: + """ + Apply to world info for civilian. + + PARAMETERS + ---------- + world_info: WorldInfo + The world info to apply to. + standard_message: StandardMessage + The standard message to apply to world info. + """ + entity_id = message_civilian.get_civilian_entity_id() + if entity_id is None: + return + entity = world_info.get_entity(entity_id) + if entity is None: + civilian = Civilian(entity_id.get_value()) + if (hp := message_civilian.get_civilian_hp()) is not None: + civilian.set_hp(hp) + if (damage := message_civilian.get_civilian_damage()) is not None: + civilian.set_damage(damage) + if (buriedness := message_civilian.get_civilian_buriedness()) is not None: + civilian.set_buriedness(buriedness) + if (position := message_civilian.get_civilian_position()) is not None: + civilian.set_position(position) + world_info.add_entity(civilian) + else: + if isinstance(entity, Civilian): + if (hp := message_civilian.get_civilian_hp()) is not None: + entity.set_hp(hp) + if (damage := message_civilian.get_civilian_damage()) is not None: + entity.set_damage(damage) + if (buriedness := message_civilian.get_civilian_buriedness()) is not None: + entity.set_buriedness(buriedness) + if (position := message_civilian.get_civilian_position()) is not None: + entity.set_position(position) + + +def _apply_to_world_info_building( + world_info: WorldInfo, + message_building: MessageBuilding, +) -> None: + """ + Apply to world info for building. + + PARAMETERS + ---------- + world_info: WorldInfo + The world info to apply to. + standard_message: StandardMessage + The standard message to apply to world info. + """ + entity_id = message_building.get_building_entity_id() + if entity_id is None: + return + entity = world_info.get_entity(entity_id) + if entity is None: + building = Building(entity_id.get_value()) + if (fieryness := message_building.get_building_fireyness()) is not None: + building.set_fieryness(fieryness) + if (brokenness := message_building.get_building_brokenness()) is not None: + building.set_brokenness(brokenness) + if (temperature := message_building.get_building_temperature()) is not None: + building.set_temperature(temperature) + world_info.add_entity(building) + else: + if isinstance(entity, Building): + if (fieryness := message_building.get_building_fireyness()) is not None: + entity.set_fieryness(fieryness) + if (brokenness := message_building.get_building_brokenness()) is not None: + entity.set_brokenness(brokenness) + if (temperature := message_building.get_building_temperature()) is not None: + entity.set_temperature(temperature) + + +def _apply_to_world_info_road( + world_info: WorldInfo, + message_road: MessageRoad, +) -> None: + """ + Apply to world info for road. + + PARAMETERS + ---------- + world_info: WorldInfo + The world info to apply to. + standard_message: StandardMessage + The standard message to apply to world info. + """ + entity_id = message_road.get_road_entity_id() + if entity_id is None: + return + entity = world_info.get_entity(entity_id) + if not isinstance(entity, Road): + return + + blockade_entity_id = message_road.get_road_blockade_entity_id() + if blockade_entity_id is None: + return + + blockade = world_info.get_entity(blockade_entity_id) + if blockade is None: + road_blockade = Blockade(blockade_entity_id.get_value()) + if (repair_cost := message_road.get_road_blockade_repair_cost()) is not None: + road_blockade.set_repaire_cost(repair_cost) + if (x := message_road.get_road_blockade_x()) is not None: + road_blockade.set_x(x) + if (y := message_road.get_road_blockade_y()) is not None: + road_blockade.set_y(y) + world_info.add_entity(road_blockade) + else: + if isinstance(blockade, Blockade): + if ( + repair_cost := message_road.get_road_blockade_repair_cost() + ) is not None: + blockade.set_repaire_cost(repair_cost) + if (x := message_road.get_road_blockade_x()) is not None: + blockade.set_x(x) + if (y := message_road.get_road_blockade_y()) is not None: + blockade.set_y(y) diff --git a/adf_core_python/core/agent/communication/standard/utility/bitarray_with_exits_flag.py b/adf_core_python/core/agent/communication/standard/utility/bitarray_with_exits_flag.py new file mode 100644 index 0000000..64ab01b --- /dev/null +++ b/adf_core_python/core/agent/communication/standard/utility/bitarray_with_exits_flag.py @@ -0,0 +1,72 @@ +from typing import Optional + +from bitarray import bitarray + +IS_EXIST_FLAG = 1 +IS_NOT_EXIST_FLAG = 0 + + +def write_with_exist_flag( + bit_array: bitarray, value: Optional[int], bit_size: int +) -> None: + """ + Write value to bit_array with an exist flag. + If value is None, write IS_NOT_EXIST_FLAG to bit_array. + If value is not None, write IS_EXIST_FLAG to bit_array and then write value to bit_array. + + PARAMETERS + ---------- + bit_array: bitarray + The bitarray to write to. + value: Optional[int] + The value to write. + bit_size: int + The number of bits to use to write value. + + RAISES + ------ + ValueError + If value is too large to fit into bit_size bits. + """ + if value is None: + bit_array.extend([IS_NOT_EXIST_FLAG]) + else: + bit_array.extend([IS_EXIST_FLAG]) + bit_value = bitarray(f"{value:0{bit_size}b}") + if len(bit_value) > bit_size: + raise ValueError(f"Value {value} is too large to fit into {bit_size} bits") + bit_array.extend(bit_value) + + +def read_with_exist_flag(bit_array: bitarray, size: int) -> Optional[int]: + """ + Read value from bit_array with an exist flag. + If the first bit is IS_NOT_EXIST_FLAG, return None. + If the first bit is IS_EXIST_FLAG, read and return value from bit_array. + + PARAMETERS + ---------- + bit_array: bitarray + The bitarray to read from. + size: int + The number of bits to read to get value. + + RETURNS + ------- + Optional[int] + The value read from bit_array. + + RAISES + ------ + ValueError + If the first bit is not IS_EXIST_FLAG or IS_NOT_EXIST_FLAG. + """ + exist_flag = bit_array.pop(0) + if exist_flag == IS_NOT_EXIST_FLAG: + return None + elif exist_flag == IS_EXIST_FLAG: + value = int(bit_array[:size].to01(), 2) + del bit_array[:size] + return value + else: + raise ValueError("Invalid exist flag") diff --git a/adf_core_python/core/agent/info/agent_info.py b/adf_core_python/core/agent/info/agent_info.py index 79e8cf2..b3e78ee 100644 --- a/adf_core_python/core/agent/info/agent_info.py +++ b/adf_core_python/core/agent/info/agent_info.py @@ -1,7 +1,9 @@ +from __future__ import annotations + from time import time -from typing import Any +from typing import TYPE_CHECKING, Any -from rcrs_core.agents.agent import Agent +from rcrs_core.commands.Command import Command from rcrs_core.entities.civilian import Civilian from rcrs_core.entities.entity import Entity from rcrs_core.entities.human import Human @@ -11,6 +13,9 @@ from adf_core_python.core.agent.action.action import Action +if TYPE_CHECKING: + from adf_core_python.core.agent.agent import Agent + class AgentInfo: # TODO: Replace Any with the actual type @@ -45,24 +50,24 @@ def get_time(self) -> int: """ return self._time - def set_heard_commands(self, heard_commands: list[Any]) -> None: + def set_heard_commands(self, heard_commands: list[Command]) -> None: """ Set the heard commands Parameters ---------- - heard_commands : list[Any] + heard_commands : list[Command] Heard commands """ self._heard_commands = heard_commands - def get_heard_commands(self) -> list[Any]: + def get_heard_commands(self) -> list[Command]: """ Get the heard commands Returns ------- - list[Any] + list[Command] Heard commands """ return self._heard_commands @@ -77,7 +82,7 @@ def get_entity_id(self) -> EntityID: Entity ID of the agent """ # TODO: Agent class should return EntityID instead of EntityID | None - return self._agent.get_id() # type: ignore + return self._agent.get_entity_id() def get_myself(self) -> Entity: """ diff --git a/adf_core_python/core/agent/info/scenario_info.py b/adf_core_python/core/agent/info/scenario_info.py index f8d35fa..85bf7da 100644 --- a/adf_core_python/core/agent/info/scenario_info.py +++ b/adf_core_python/core/agent/info/scenario_info.py @@ -1,8 +1,10 @@ from enum import Enum -from typing import Any +from typing import TypeVar from adf_core_python.core.config.config import Config +T = TypeVar("T") + class Mode(Enum): NON_PRECOMPUTE = 0 @@ -21,7 +23,7 @@ class ScenarioInfoKeys: FIRE_TANK_REFILL_RATE = "fire.tank.refill_rate" KERNEL_TIMESTEPS = "kernel.timesteps" FIRE_EXTINGUISH_MAX_SUM = "fire.extinguish.max-sum" - COMMS_CHANNELS_MAX_PLATOON = "comms.channels.max.platoon" + COMMUNICATION_CHANNELS_MAX_PLATOON = "comms.channels.max.platoon" KERNEL_AGENTS_THINK_TIME = "kernel.agents.think-time" FIRE_TANK_MAXIMUM = "fire.tank.maximum" CLEAR_REPAIR_RATE = "clear.repair.rate" @@ -34,11 +36,11 @@ class ScenarioInfoKeys: KERNEL_COMMUNICATION_MODEL = "kernel.communication-model" PERCEPTION_LOS_PRECISION_DAMAGE = "perception.los.precision.damage" SCENARIO_AGENTS_AC = "scenario.agents.ac" - COMMS_CHANNELS_MAX_OFFICE = "comms.channels.max.centre" + COMMUNICATION_CHANNELS_MAX_OFFICE = "comms.channels.max.centre" FIRE_EXTINGUISH_MAX_DISTANCE = "fire.extinguish.max-distance" KERNEL_AGENTS_IGNOREUNTIL = "kernel.agents.ignoreuntil" CLEAR_REPAIR_DISTANCE = "clear.repair.distance" - COMMS_CHANNELS_COUNT = "comms.channels.count" + COMMUNICATION_CHANNELS_COUNT = "comms.channels.count" class ScenarioInfo: @@ -89,7 +91,7 @@ def get_mode(self) -> Mode: """ return self._mode - def get_value(self, key: str, default: Any) -> Any: + def get_value(self, key: str, default: T) -> T: """ Get the value of the configuration @@ -105,4 +107,11 @@ def get_value(self, key: str, default: Any) -> Any: Any Value of the configuration """ - return self._config.get_value(key, default) + value = self._config.get_value(key, default) + if not isinstance(value, type(default)): + try: + return type(default)(value) # type: ignore + except (ValueError, TypeError): + # 型変換に失敗した場合はそのままデフォルト値を返す + return default + return value diff --git a/adf_core_python/core/agent/info/world_info.py b/adf_core_python/core/agent/info/world_info.py index 223bfb1..5bd6676 100644 --- a/adf_core_python/core/agent/info/world_info.py +++ b/adf_core_python/core/agent/info/world_info.py @@ -171,3 +171,14 @@ def get_bloackades(self, area: Area) -> set[Blockade]: if isinstance(bloackde_entity, Blockade): bloakcades.add(cast(Blockade, bloackde_entity)) return bloakcades + + def add_entity(self, entity: Entity) -> None: + """ + Add the entity + + Parameters + ---------- + entity : Entity + Entity + """ + self._world_model.add_entity(entity) diff --git a/adf_core_python/core/agent/module/module_manager.py b/adf_core_python/core/agent/module/module_manager.py index b15dd8e..7609e62 100644 --- a/adf_core_python/core/agent/module/module_manager.py +++ b/adf_core_python/core/agent/module/module_manager.py @@ -4,6 +4,12 @@ from typing import TYPE_CHECKING, Any from adf_core_python.core.component.action.extend_action import ExtendAction +from adf_core_python.core.component.communication.channel_subscriber import ( + ChannelSubscriber, +) +from adf_core_python.core.component.communication.message_coordinator import ( + MessageCoordinator, +) from adf_core_python.core.component.module.abstract_module import AbstractModule if TYPE_CHECKING: @@ -92,6 +98,58 @@ def get_extend_action( raise RuntimeError(f"Action {class_name} is not a subclass of ExtendAction") + def get_channel_subscriber( + self, channel_subscriber_name: str, default_channel_subscriber_name: str + ) -> ChannelSubscriber: + class_name = self._module_config.get_value_or_default( + channel_subscriber_name, default_channel_subscriber_name + ) + + try: + channel_subscriber_class: type = self._load_module(class_name) + except (ImportError, AttributeError) as e: + raise RuntimeError(f"Failed to load channel subscriber {class_name}") from e + + instance = self._channel_subscribers.get(channel_subscriber_name) + if instance is not None: + return instance + + if issubclass(channel_subscriber_class, ChannelSubscriber): + instance = channel_subscriber_class() + self._channel_subscribers[channel_subscriber_name] = instance + return instance + + raise RuntimeError( + f"Channel subscriber {class_name} is not a subclass of ChannelSubscriber" + ) + + def get_message_coordinator( + self, message_coordinator_name: str, default_message_coordinator_name: str + ) -> MessageCoordinator: + class_name = self._module_config.get_value_or_default( + message_coordinator_name, default_message_coordinator_name + ) + + try: + message_coordinator_class: type = self._load_module(class_name) + except (ImportError, AttributeError) as e: + raise RuntimeError( + f"Failed to load message coordinator {class_name}" + ) from e + + instance = self._message_coordinators.get(message_coordinator_name) + if instance is not None: + return instance + + if issubclass(message_coordinator_class, MessageCoordinator): + instance = message_coordinator_class() + self._message_coordinators[message_coordinator_name] = instance + return instance + + raise RuntimeError( + f"Message coordinator {class_name} is not a subclass of MessageCoordinator" + ) + def _load_module(self, class_name: str) -> type: module_name, module_class_name = class_name.rsplit(".", 1) module = importlib.import_module(module_name) diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py index 6ffcd02..098945f 100644 --- a/adf_core_python/core/agent/platoon/platoon.py +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -1,20 +1,12 @@ -# from rcrs_core.agents.agent import Agent -from rcrs_core.commands.Command import Command -from rcrs_core.config.config import Config as RCRSConfig -from rcrs_core.worldmodel.changeSet import ChangeSet - from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.agent import Agent from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData -from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo -from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.info.scenario_info import Mode from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent -from adf_core_python.core.config.config import Config from adf_core_python.core.logger.logger import get_agent_logger @@ -29,7 +21,15 @@ def __init__( module_config: ModuleConfig, develop_data: DevelopData, ) -> None: - super().__init__(is_precompute, self.__class__.__qualname__) + super().__init__( + is_precompute, + self.__class__.__qualname__, + is_debug, + team_name, + data_storage_name, + module_config, + develop_data, + ) self._tactics_agent = tactics_agent self._team_name = team_name self._is_precompute = is_precompute @@ -39,39 +39,14 @@ def __init__( self._develop_data = develop_data def post_connect(self) -> None: - self._agent_info: AgentInfo = AgentInfo(self, self.world_model) - self._world_info: WorldInfo = WorldInfo(self.world_model) - self._precompute_data: PrecomputeData = PrecomputeData(self._data_storage_name) + super().post_connect() + self.precompute_data: PrecomputeData = PrecomputeData(self._data_storage_name) self._logger = get_agent_logger( f"{self.__class__.__module__}.{self.__class__.__qualname__}", self._agent_info, ) - if self._is_precompute: - self._mode = Mode.PRECOMPUTATION - else: - # if self._precompute_data.is_ready(): - # self._mode = Mode.PRECOMPUTED - # else: - # self._mode = Mode.NON_PRECOMPUTE - self._mode = Mode.NON_PRECOMPUTE - - config = Config() - if self.config is not None: - rcrc_config: RCRSConfig = self.config - for key, value in rcrc_config.data.items(): # type: ignore - config.set_value(key, value) - for key, value in rcrc_config.int_data.items(): # type: ignore - config.set_value(key, value) - for key, value in rcrc_config.float_data.items(): # type: ignore - config.set_value(key, value) - for key, value in rcrc_config.boolean_data.items(): # type: ignore - config.set_value(key, value) - for key, value in rcrc_config.array_data.items(): # type: ignore - config.set_value(key, value) - - self._scenario_info: ScenarioInfo = ScenarioInfo(config, self._mode) self._module_manager: ModuleManager = ModuleManager( self._agent_info, self._world_info, @@ -80,17 +55,30 @@ def post_connect(self) -> None: self._develop_data, ) + self._message_manager.set_channel_subscriber( + self._module_manager.get_channel_subscriber( + "MessageManager.PlatoonChannelSubscriber", + "adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber", + ) + ) + self._message_manager.set_message_coordinator( + self._module_manager.get_message_coordinator( + "MessageManager.PlatoonMessageCoordinator", + "adf_core_python.implement.module.communication.default_message_coordinator.DefaultMessageCoordinator", + ) + ) + self._tactics_agent.initialize( self._agent_info, self._world_info, self._scenario_info, self._module_manager, self._precompute_data, - MessageManager(), + self._message_manager, self._develop_data, ) - match self._mode: + match self._scenario_info.get_mode(): case Mode.PRECOMPUTATION: pass case Mode.PRECOMPUTED: @@ -101,25 +89,24 @@ def post_connect(self) -> None: self._world_info, self._scenario_info, self._module_manager, - self._precompute_data, + self.precompute_data, self._develop_data, ) - def think(self, time: int, change_set: ChangeSet, hear: list[Command]) -> None: - self._agent_info.set_change_set(change_set) - self._world_info.set_change_set(change_set) - self._agent_info.set_time(time) - self._agent_info.set_heard_commands(hear) - + def think(self) -> None: action: Action = self._tactics_agent.think( self._agent_info, self._world_info, self._scenario_info, self._module_manager, self._precompute_data, - MessageManager(), + self._message_manager, self._develop_data, ) if action is not None and self.agent_id is not None: - self._agent_info.set_executed_action(time, action) - self.send_msg(action.get_command(self.agent_id, time).prepare_cmd()) + self._agent_info.set_executed_action(self._agent_info.get_time(), action) + self.send_msg( + action.get_command( + self.agent_id, self._agent_info.get_time() + ).prepare_cmd() + ) diff --git a/adf_core_python/core/component/communication/__init__.py b/adf_core_python/core/component/communication/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/component/communication/channel_subscriber.py b/adf_core_python/core/component/communication/channel_subscriber.py index 7b4ee89..d2da83f 100644 --- a/adf_core_python/core/component/communication/channel_subscriber.py +++ b/adf_core_python/core/component/communication/channel_subscriber.py @@ -1,9 +1,12 @@ +from __future__ import annotations + from abc import ABC, abstractmethod +from typing import TYPE_CHECKING -from adf_core_python.core.agent.communication.message_manager import MessageManager -from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import ScenarioInfo -from adf_core_python.core.agent.info.world_info import WorldInfo +if TYPE_CHECKING: + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo class ChannelSubscriber(ABC): @@ -13,8 +16,7 @@ def subscribe( agent_info: AgentInfo, world_info: WorldInfo, scenario_info: ScenarioInfo, - message_manager: MessageManager, - ) -> None: + ) -> list[int]: """ Subscribe to the channel. @@ -26,7 +28,10 @@ def subscribe( The world info. scenario_info : ScenarioInfo The scenario info. - message_manager : MessageManager - The message manager. + + Returns + ------- + list[int] + The list of subscribed channels. """ pass diff --git a/adf_core_python/core/component/communication/communication_message.py b/adf_core_python/core/component/communication/communication_message.py index a6cd25d..0760ac0 100644 --- a/adf_core_python/core/component/communication/communication_message.py +++ b/adf_core_python/core/component/communication/communication_message.py @@ -1,5 +1,9 @@ +from __future__ import annotations + from abc import ABC, abstractmethod +from bitarray import bitarray + class CommunicationMessage(ABC): def __init__(self, is_wireless_message: bool) -> None: @@ -9,15 +13,13 @@ def is_wireless_message(self) -> bool: return self._is_wireless_message @abstractmethod - def get_byte_size(self) -> int: + def get_bit_size(self) -> int: raise NotImplementedError @abstractmethod - def to_bytes(self) -> bytes: + def to_bits(self) -> bitarray: raise NotImplementedError @abstractmethod - def get_check_key(self) -> str: + def __hash__(self): raise NotImplementedError - - # TODO: Implement the toBitOutputStream and getCheckKey methods diff --git a/adf_core_python/core/component/communication/communication_module.py b/adf_core_python/core/component/communication/communication_module.py index c1b957e..2e87e31 100644 --- a/adf_core_python/core/component/communication/communication_module.py +++ b/adf_core_python/core/component/communication/communication_module.py @@ -1,8 +1,11 @@ -from abc import ABC, abstractmethod +from __future__ import annotations -from rcrs_core.agents.agent import Agent +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING -from adf_core_python.core.agent.communication.message_manager import MessageManager +if TYPE_CHECKING: + from adf_core_python.core.agent.agent import Agent + from adf_core_python.core.agent.communication.message_manager import MessageManager class CommunicationModule(ABC): diff --git a/adf_core_python/core/component/communication/message_coordinator.py b/adf_core_python/core/component/communication/message_coordinator.py index d749050..9d8cb1b 100644 --- a/adf_core_python/core/component/communication/message_coordinator.py +++ b/adf_core_python/core/component/communication/message_coordinator.py @@ -1,12 +1,16 @@ +from __future__ import annotations + from abc import ABC, abstractmethod +from typing import TYPE_CHECKING -from adf_core_python.core.agent.communication.message_manager import MessageManager -from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import ScenarioInfo -from adf_core_python.core.agent.info.world_info import WorldInfo -from adf_core_python.core.component.communication.communication_message import ( - CommunicationMessage, -) +if TYPE_CHECKING: + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.component.communication.communication_message import ( + CommunicationMessage, + ) class MessageCoordinator(ABC): diff --git a/adf_core_python/core/launcher/connect/component_launcher.py b/adf_core_python/core/launcher/connect/component_launcher.py index 027bb85..56a142c 100644 --- a/adf_core_python/core/launcher/connect/component_launcher.py +++ b/adf_core_python/core/launcher/connect/component_launcher.py @@ -1,8 +1,8 @@ import socket -from rcrs_core.agents.agent import Agent from structlog import BoundLogger +from adf_core_python.core.agent.agent import Agent from adf_core_python.core.launcher.connect.connection import Connection @@ -17,7 +17,7 @@ def make_connection(self) -> Connection: return Connection(self.host, self.port) def connect(self, agent: Agent, _request_id: int) -> None: - self.logger.bind(agent_id=agent.get_id()) + # self.logger.bind(agent_id=agent.get_id()) self.logger.info( f"{agent.__class__.__name__} connecting to {self.host}:{self.port} request_id: {_request_id}" @@ -33,8 +33,10 @@ def connect(self, agent: Agent, _request_id: int) -> None: except socket.timeout: self.logger.warning(f"Connection to {self.host}:{self.port} timed out") return - except socket.error: - self.logger.error(f"Failed to connect to {self.host}:{self.port}") + except socket.error as e: + self.logger.exception( + f"Failed to connect to {self.host}:{self.port}", exception=str(e) + ) return connection.message_received(agent.message_received) @@ -44,7 +46,10 @@ def connect(self, agent: Agent, _request_id: int) -> None: try: connection.parse_message_from_kernel() except Exception as e: - self.logger.error(f"Failed to connect agent: {self.host}:{self.port} {e}") + self.logger.exception( + f"Failed to connect agent: {self.host}:{self.port} {e}", + exception=str(e), + ) def generate_request_id(self) -> int: self.request_id += 1 diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py index 0194918..8004fb9 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_centre.py @@ -52,6 +52,7 @@ def connect( ), ) + request_id: int = component_launcher.generate_request_id() # TODO: component_launcher.generate_request_ID can cause race condition thread = threading.Thread( target=component_launcher.connect, @@ -59,8 +60,9 @@ def connect( AmbulanceCenterAgent( config.get_value(ConfigKey.KEY_PRECOMPUTE, False), ), # type: ignore - component_launcher.generate_request_id(), + request_id, ), + name=f"AmbulanceCentreAgent-{request_id}", ) threads.append(thread) diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index 15da390..350d6bd 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -53,6 +53,7 @@ def connect( ), ) + request_id: int = component_launcher.generate_request_id() thread = threading.Thread( target=component_launcher.connect, args=( @@ -65,8 +66,9 @@ def connect( module_config, develop_data, ), - component_launcher.generate_request_id(), + request_id, ), + name=f"AmbulanceTeam-{request_id}", ) threads.append(thread) diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/adf_core_python/core/launcher/connect/connector_fire_brigade.py index fab7095..9fc24aa 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_brigade.py +++ b/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -51,6 +51,7 @@ def connect( ), ) + request_id: int = component_launcher.generate_request_id() thread = threading.Thread( target=component_launcher.connect, args=( @@ -63,8 +64,9 @@ def connect( module_config, develop_data, ), - component_launcher.generate_request_id(), + request_id, ), + name=f"FireBrigadeAgent-{request_id}", ) threads.append(thread) diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/adf_core_python/core/launcher/connect/connector_fire_station.py index 49bd190..cd5632e 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_station.py +++ b/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -52,15 +52,16 @@ def connect( ), ) - # TODO: component_launcher.generate_request_ID can cause race condition + request_id: int = component_launcher.generate_request_id() thread = threading.Thread( target=component_launcher.connect, args=( FireStationAgent( config.get_value(ConfigKey.KEY_PRECOMPUTE, False), ), # type: ignore - component_launcher.generate_request_id(), + request_id, ), + name=f"FireStationAgent-{request_id}", ) threads.append(thread) diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py index bbf9359..2b3a905 100644 --- a/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -51,6 +51,7 @@ def connect( ), ) + request_id: int = component_launcher.generate_request_id() thread = threading.Thread( target=component_launcher.connect, args=( @@ -63,8 +64,9 @@ def connect( module_config, develop_data, ), - component_launcher.generate_request_id(), + request_id, ), + name=f"PoliceForceAgent-{request_id}", ) threads.append(thread) diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py index 32e9060..561b987 100644 --- a/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -1,3 +1,4 @@ +import re import threading from rcrs_core.agents.policeOfficeAgent import PoliceOfficeAgent @@ -52,15 +53,16 @@ def connect( ), ) - # TODO: component_launcher.generate_request_ID can cause race condition + request_id: int = component_launcher.generate_request_id() thread = threading.Thread( target=component_launcher.connect, args=( PoliceOfficeAgent( config.get_value(ConfigKey.KEY_PRECOMPUTE, False), ), # type: ignore - component_launcher.generate_request_id(), + request_id, ), + name=f"PoliceOfficeAgent-{request_id}", ) threads.append(thread) diff --git a/adf_core_python/implement/module/communication/default_channel_subscriber.py b/adf_core_python/implement/module/communication/default_channel_subscriber.py new file mode 100644 index 0000000..d195c9a --- /dev/null +++ b/adf_core_python/implement/module/communication/default_channel_subscriber.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from rcrs_core.connection.URN import Entity as EntityURN + +from adf_core_python.core.agent.info.scenario_info import ScenarioInfoKeys +from adf_core_python.core.component.communication.channel_subscriber import ( + ChannelSubscriber, +) + +if TYPE_CHECKING: + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + + +class DefaultChannelSubscriber(ChannelSubscriber): + def subscribe( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + ) -> list[int]: + agent = world_info.get_entity(agent_info.get_entity_id()) + if agent is None: + return [] + + agent_type = agent.get_urn() + + number_of_channels: int = ( + scenario_info.get_value(ScenarioInfoKeys.COMMUNICATION_CHANNELS_COUNT, 1) + - 1 + ) + + is_platoon: bool = ( + agent_type == EntityURN.FIRE_BRIGADE + or agent_type == EntityURN.POLICE_FORCE + or agent_type == EntityURN.AMBULANCE_TEAM + ) + + max_channel_count: int = ( + scenario_info.get_value( + ScenarioInfoKeys.COMMUNICATION_CHANNELS_MAX_PLATOON, 1 + ) + if is_platoon + else scenario_info.get_value( + ScenarioInfoKeys.COMMUNICATION_CHANNELS_MAX_OFFICE, 1 + ) + ) + + channels = [ + self.get_channel_number(agent_type, i, number_of_channels) + for i in range(max_channel_count) + ] + return channels + + @staticmethod + def get_channel_number( + agent_type: EntityURN, channel_index: int, number_of_channels: int + ) -> int: + agent_index = 0 + if agent_type == EntityURN.FIRE_BRIGADE or agent_type == EntityURN.FIRE_STATION: + agent_index = 1 + elif ( + agent_type == EntityURN.POLICE_FORCE + or agent_type == EntityURN.POLICE_OFFICE + ): + agent_index = 2 + elif ( + agent_type == EntityURN.AMBULANCE_TEAM + or agent_type == EntityURN.AMBULANCE_CENTRE + ): + agent_index = 3 + + index = (3 * channel_index) + agent_index + if (index % number_of_channels) == 0: + index = number_of_channels + else: + index = index % number_of_channels + return index + + +if __name__ == "__main__": + num_channels = 1 + max_channels = 2 + + for i in range(max_channels): + print( + f"FIREBRIGADE-{i}: {DefaultChannelSubscriber.get_channel_number(EntityURN.FIRE_BRIGADE, i, num_channels)}" + ) + + for i in range(max_channels): + print( + f"POLICE-{i}: {DefaultChannelSubscriber.get_channel_number(EntityURN.POLICE_OFFICE, i, num_channels)}" + ) + + for i in range(max_channels): + print( + f"AMB-{i}: {DefaultChannelSubscriber.get_channel_number(EntityURN.AMBULANCE_CENTRE, i, num_channels)}" + ) diff --git a/adf_core_python/implement/module/communication/default_message_coordinator.py b/adf_core_python/implement/module/communication/default_message_coordinator.py new file mode 100644 index 0000000..2e61264 --- /dev/null +++ b/adf_core_python/implement/module/communication/default_message_coordinator.py @@ -0,0 +1,222 @@ +from rcrs_core.connection.URN import Entity as EntityURN + +from adf_core_python.core.agent.communication.standard.bundle.information.message_ambulance_team import ( + MessageAmbulanceTeam, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_building import ( + MessageBuilding, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_civilian import ( + MessageCivilian, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_fire_brigade import ( + MessageFireBrigade, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_police_force import ( + MessagePoliceForce, +) +from adf_core_python.core.agent.communication.standard.bundle.information.message_road import ( + MessageRoad, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo, ScenarioInfoKeys +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.component.communication.message_coordinator import ( + MessageCoordinator, +) +from adf_core_python.implement.module.communication.default_channel_subscriber import ( + DefaultChannelSubscriber, +) + + +class DefaultMessageCoordinator(MessageCoordinator): + def coordinate( + self, + agent_info, + world_info, + scenario_info, + message_manager, + send_message_list, + channel_send_message_list, + ): + police_messages = [] + ambulance_messages = [] + fire_brigade_messages = [] + voice_messages = [] + + agent_type = self.get_agent_type(agent_info, world_info) + + for msg in send_message_list: + if isinstance(msg, StandardMessage) and not msg.is_wireless_message(): + voice_messages.append(msg) + else: + if isinstance(msg, MessageBuilding): + fire_brigade_messages.append(msg) + elif isinstance(msg, MessageCivilian): + ambulance_messages.append(msg) + elif isinstance(msg, MessageRoad): + fire_brigade_messages.append(msg) + ambulance_messages.append(msg) + police_messages.append(msg) + # elif isinstance(msg, CommandAmbulance): + # ambulance_messages.append(msg) + # elif isinstance(msg, CommandFire): + # fire_brigade_messages.append(msg) + # elif isinstance(msg, CommandPolice): + # police_messages.append(msg) + # elif isinstance(msg, CommandScout): + # if agent_type == EntityURN.FIRE_STATION: + # fire_brigade_messages.append(msg) + # elif agent_type == EntityURN.POLICE_OFFICE: + # police_messages.append(msg) + # elif agent_type == EntityURN.AMBULANCE_CENTRE: + # ambulance_messages.append(msg) + # elif isinstance(msg, MessageReport): + # if agent_type == EntityURN.FIRE_BRIGADE: + # fire_brigade_messages.append(msg) + # elif agent_type == EntityURN.POLICE_FORCE: + # police_messages.append(msg) + # elif agent_type == EntityURN.AMBULANCE_TEAM: + # ambulance_messages.append(msg) + elif isinstance(msg, MessageFireBrigade): + fire_brigade_messages.append(msg) + ambulance_messages.append(msg) + police_messages.append(msg) + elif isinstance(msg, MessagePoliceForce): + ambulance_messages.append(msg) + police_messages.append(msg) + elif isinstance(msg, MessageAmbulanceTeam): + ambulance_messages.append(msg) + police_messages.append(msg) + + if int(scenario_info.get_value("comms.channels.count", 1)) > 1: + channel_size = [0] * ( + int(scenario_info.get_value("comms.channels.count", 1)) + ) + self.set_send_messages( + scenario_info, + EntityURN.POLICE_FORCE, + agent_info, + world_info, + police_messages, + channel_send_message_list, + channel_size, + ) + self.set_send_messages( + scenario_info, + EntityURN.AMBULANCE_TEAM, + agent_info, + world_info, + ambulance_messages, + channel_send_message_list, + channel_size, + ) + self.set_send_messages( + scenario_info, + EntityURN.FIRE_BRIGADE, + agent_info, + world_info, + fire_brigade_messages, + channel_send_message_list, + channel_size, + ) + + voice_message_low_list = [] + voice_message_normal_list = [] + voice_message_high_list = [] + + for msg in voice_messages: + if isinstance(msg, StandardMessage): + if msg.get_priority() == StandardMessagePriority.LOW: + voice_message_low_list.append(msg) + elif msg.get_priority() == StandardMessagePriority.NORMAL: + voice_message_normal_list.append(msg) + elif msg.get_priority() == StandardMessagePriority.HIGH: + voice_message_high_list.append(msg) + + channel_send_message_list[0].extend(voice_message_high_list) + channel_send_message_list[0].extend(voice_message_normal_list) + channel_send_message_list[0].extend(voice_message_low_list) + + def get_channels_by_agent_type( + self, + agent_type, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + ): + num_channels = ( + scenario_info.get_value(ScenarioInfoKeys.COMMUNICATION_CHANNELS_COUNT, 1) + - 1 + ) + max_channel_count = int( + # scenario_info.get_comms_channels_max_platoon() + scenario_info.get_value( + ScenarioInfoKeys.COMMUNICATION_CHANNELS_MAX_PLATOON, 1 + ) + if self.is_platoon_agent(agent_info, world_info) + else scenario_info.get_value( + ScenarioInfoKeys.COMMUNICATION_CHANNELS_MAX_OFFICE, 1 + ) + ) + channels = [ + DefaultChannelSubscriber.get_channel_number(agent_type, i, num_channels) + for i in range(max_channel_count) + ] + return channels + + def is_platoon_agent(self, agent_info, world_info): + agent_type = self.get_agent_type(agent_info, world_info) + return agent_type in [ + EntityURN.FIRE_BRIGADE, + EntityURN.POLICE_FORCE, + EntityURN.AMBULANCE_TEAM, + ] + + def get_agent_type(self, agent_info: AgentInfo, world_info: WorldInfo): + entity = world_info.get_entity(agent_info.get_entity_id()) + if entity is None: + return None + return entity.get_urn() + + def set_send_messages( + self, + scenario_info: ScenarioInfo, + agent_type: EntityURN, + agent_info: AgentInfo, + world_info: WorldInfo, + messages: list[StandardMessage], + channel_send_message_list, + channel_size, + ): + channels = self.get_channels_by_agent_type( + agent_type, agent_info, world_info, scenario_info + ) + channel_capacities = [ + scenario_info.get_value("comms.channels." + str(channel) + ".bandwidth", 0) + for channel in range( + scenario_info.get_value( + ScenarioInfoKeys.COMMUNICATION_CHANNELS_COUNT, 1 + ) + ) + ] + + sorted_messages = sorted( + messages, key=lambda x: x.get_priority().value, reverse=True + ) + + for message in sorted_messages: + for channel in channels: + if message not in channel_send_message_list[channel] and ( + (channel_size[channel] + message.get_bit_size()) + <= channel_capacities[channel] + ): + channel_size[channel] += message.get_bit_size() + channel_send_message_list[channel].append(message) + break diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 3154107..7a30dee 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -5,6 +5,24 @@ from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.action.common.action_rest import ActionRest from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( + CommandAmbulance, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_fire import ( + CommandFire, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_police import ( + CommandPolice, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( + CommandScout, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( + CommandReport, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo @@ -119,6 +137,61 @@ def think( agent: AmbulanceTeamEntity = cast(AmbulanceTeamEntity, agent_info.get_myself()) # noqa: F841 entity_id = agent_info.get_entity_id() # noqa: F841 + message_manager.add_message( + CommandAmbulance( + False, + entity_id, + entity_id, + CommandAmbulance.ACTION_REST, + StandardMessagePriority.NORMAL, + entity_id, + ) + ) + message_manager.add_message( + CommandFire( + False, + entity_id, + entity_id, + CommandFire.ACTION_REST, + StandardMessagePriority.NORMAL, + entity_id, + ) + ) + message_manager.add_message( + CommandPolice( + False, + entity_id, + entity_id, + CommandPolice.ACTION_REST, + StandardMessagePriority.NORMAL, + entity_id, + ) + ) + message_manager.add_message( + CommandScout( + False, + entity_id, + entity_id, + 20000, + StandardMessagePriority.NORMAL, + entity_id, + ) + ) + message_manager.add_message( + CommandReport( + False, + True, + True, + entity_id, + StandardMessagePriority.NORMAL, + ) + ) + + self._logger.debug( + f"received messages: {[str(message) for message in message_manager.get_received_message_list()]}, help: {message_manager.get_heard_agent_help_message_count()}", + message_manager=message_manager, + ) + target_entity_id = self._human_detector.calculate().get_target_entity_id() self._logger.debug( f"human detector target_entity_id: {target_entity_id}", diff --git a/config/launcher.yaml b/config/launcher.yaml index 3d64272..d69a51b 100644 --- a/config/launcher.yaml +++ b/config/launcher.yaml @@ -21,11 +21,11 @@ adf: team: platoon: ambulance: - count: 100 + count: 1 fire: - count: 100 + count: 0 police: - count: 100 + count: 0 office: ambulance: count: -1 diff --git a/config/module.yaml b/config/module.yaml index a265bde..b4fbba1 100644 --- a/config/module.yaml +++ b/config/module.yaml @@ -106,11 +106,10 @@ SampleSearch: # ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear # ## MessageManager -# MessageManager: -# PlatoonChannelSubscriber: adf_core_python.implement.module.comm.DefaultChannelSubscriber -# CenterChannelSubscriber: adf_core_python.implement.module.comm.DefaultChannelSubscriber -# PlatoonMessageCoordinator: adf_core_python.implement.module.comm.DefaultMessageCoordinator -# CenterMessageCoordinator: adf_core_python.implement.module.comm.DefaultMessageCoordinator - +MessageManager: + PlatoonChannelSubscriber: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber + CenterChannelSubscriber: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber + PlatoonMessageCoordinator: adf_core_python.implement.module.communication.default_message_coordinator.DefaultMessageCoordinator + CenterMessageCoordinator: adf_core_python.implement.module.communication.default_message_coordinator.DefaultMessageCoordinator # ## VisualDebug # VisualDebug: true diff --git a/poetry.lock b/poetry.lock index c0b7ea8..249bcc8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -255,75 +255,77 @@ files = [ [[package]] name = "numpy" -version = "2.1.2" +version = "2.1.3" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.10" files = [ - {file = "numpy-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30d53720b726ec36a7f88dc873f0eec8447fbc93d93a8f079dfac2629598d6ee"}, - {file = "numpy-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d3ca0a72dd8846eb6f7dfe8f19088060fcb76931ed592d29128e0219652884"}, - {file = "numpy-2.1.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:fc44e3c68ff00fd991b59092a54350e6e4911152682b4782f68070985aa9e648"}, - {file = "numpy-2.1.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7c1c60328bd964b53f8b835df69ae8198659e2b9302ff9ebb7de4e5a5994db3d"}, - {file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdb606a7478f9ad91c6283e238544451e3a95f30fb5467fbf715964341a8a86"}, - {file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d666cb72687559689e9906197e3bec7b736764df6a2e58ee265e360663e9baf7"}, - {file = "numpy-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6eef7a2dbd0abfb0d9eaf78b73017dbfd0b54051102ff4e6a7b2980d5ac1a03"}, - {file = "numpy-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:12edb90831ff481f7ef5f6bc6431a9d74dc0e5ff401559a71e5e4611d4f2d466"}, - {file = "numpy-2.1.2-cp310-cp310-win32.whl", hash = "sha256:a65acfdb9c6ebb8368490dbafe83c03c7e277b37e6857f0caeadbbc56e12f4fb"}, - {file = "numpy-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:860ec6e63e2c5c2ee5e9121808145c7bf86c96cca9ad396c0bd3e0f2798ccbe2"}, - {file = "numpy-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe"}, - {file = "numpy-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1"}, - {file = "numpy-2.1.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f"}, - {file = "numpy-2.1.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:13602b3174432a35b16c4cfb5de9a12d229727c3dd47a6ce35111f2ebdf66ff4"}, - {file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebec5fd716c5a5b3d8dfcc439be82a8407b7b24b230d0ad28a81b61c2f4659a"}, - {file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2b49c3c0804e8ecb05d59af8386ec2f74877f7ca8fd9c1e00be2672e4d399b1"}, - {file = "numpy-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cbba4b30bf31ddbe97f1c7205ef976909a93a66bb1583e983adbd155ba72ac2"}, - {file = "numpy-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8e00ea6fc82e8a804433d3e9cedaa1051a1422cb6e443011590c14d2dea59146"}, - {file = "numpy-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5006b13a06e0b38d561fab5ccc37581f23c9511879be7693bd33c7cd15ca227c"}, - {file = "numpy-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:f1eb068ead09f4994dec71c24b2844f1e4e4e013b9629f812f292f04bd1510d9"}, - {file = "numpy-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b"}, - {file = "numpy-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db"}, - {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1"}, - {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426"}, - {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0"}, - {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df"}, - {file = "numpy-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366"}, - {file = "numpy-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142"}, - {file = "numpy-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550"}, - {file = "numpy-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e"}, - {file = "numpy-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d"}, - {file = "numpy-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf"}, - {file = "numpy-2.1.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e"}, - {file = "numpy-2.1.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3"}, - {file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8"}, - {file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a"}, - {file = "numpy-2.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98"}, - {file = "numpy-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe"}, - {file = "numpy-2.1.2-cp313-cp313-win32.whl", hash = "sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a"}, - {file = "numpy-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445"}, - {file = "numpy-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5"}, - {file = "numpy-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0"}, - {file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17"}, - {file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6"}, - {file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8"}, - {file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35"}, - {file = "numpy-2.1.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62"}, - {file = "numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a"}, - {file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bdd407c40483463898b84490770199d5714dcc9dd9b792f6c6caccc523c00952"}, - {file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:da65fb46d4cbb75cb417cddf6ba5e7582eb7bb0b47db4b99c9fe5787ce5d91f5"}, - {file = "numpy-2.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c193d0b0238638e6fc5f10f1b074a6993cb13b0b431f64079a509d63d3aa8b7"}, - {file = "numpy-2.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a7d80b2e904faa63068ead63107189164ca443b42dd1930299e0d1cb041cec2e"}, - {file = "numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c"}, + {file = "numpy-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff"}, + {file = "numpy-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5"}, + {file = "numpy-2.1.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1"}, + {file = "numpy-2.1.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd"}, + {file = "numpy-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3"}, + {file = "numpy-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098"}, + {file = "numpy-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c"}, + {file = "numpy-2.1.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4"}, + {file = "numpy-2.1.3-cp310-cp310-win32.whl", hash = "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23"}, + {file = "numpy-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0"}, + {file = "numpy-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d"}, + {file = "numpy-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41"}, + {file = "numpy-2.1.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9"}, + {file = "numpy-2.1.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09"}, + {file = "numpy-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a"}, + {file = "numpy-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b"}, + {file = "numpy-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee"}, + {file = "numpy-2.1.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0"}, + {file = "numpy-2.1.3-cp311-cp311-win32.whl", hash = "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9"}, + {file = "numpy-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2"}, + {file = "numpy-2.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e"}, + {file = "numpy-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958"}, + {file = "numpy-2.1.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8"}, + {file = "numpy-2.1.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564"}, + {file = "numpy-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512"}, + {file = "numpy-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b"}, + {file = "numpy-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc"}, + {file = "numpy-2.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0"}, + {file = "numpy-2.1.3-cp312-cp312-win32.whl", hash = "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9"}, + {file = "numpy-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a"}, + {file = "numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f"}, + {file = "numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598"}, + {file = "numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57"}, + {file = "numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe"}, + {file = "numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43"}, + {file = "numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56"}, + {file = "numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a"}, + {file = "numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef"}, + {file = "numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f"}, + {file = "numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed"}, + {file = "numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f"}, + {file = "numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4"}, + {file = "numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e"}, + {file = "numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0"}, + {file = "numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408"}, + {file = "numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6"}, + {file = "numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f"}, + {file = "numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17"}, + {file = "numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48"}, + {file = "numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4"}, + {file = "numpy-2.1.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f"}, + {file = "numpy-2.1.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4"}, + {file = "numpy-2.1.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d"}, + {file = "numpy-2.1.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb"}, + {file = "numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761"}, ] [[package]] name = "packaging" -version = "24.1" +version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] @@ -488,7 +490,7 @@ rtree = "*" type = "git" url = "https://github.com/adf-python/rcrs-core-python" reference = "HEAD" -resolved_reference = "b353cd6e82005e20e099554d4f357d9c0cc2e052" +resolved_reference = "c4ec7b2d32badb2f10fc1d03b962b465624a1ed5" [[package]] name = "rtree" @@ -630,6 +632,64 @@ dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodest doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +[[package]] +name = "shapely" +version = "2.0.6" +description = "Manipulation and analysis of geometric objects" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shapely-2.0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29a34e068da2d321e926b5073539fd2a1d4429a2c656bd63f0bd4c8f5b236d0b"}, + {file = "shapely-2.0.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c84c3f53144febf6af909d6b581bc05e8785d57e27f35ebaa5c1ab9baba13b"}, + {file = "shapely-2.0.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad2fae12dca8d2b727fa12b007e46fbc522148a584f5d6546c539f3464dccde"}, + {file = "shapely-2.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3304883bd82d44be1b27a9d17f1167fda8c7f5a02a897958d86c59ec69b705e"}, + {file = "shapely-2.0.6-cp310-cp310-win32.whl", hash = "sha256:3ec3a0eab496b5e04633a39fa3d5eb5454628228201fb24903d38174ee34565e"}, + {file = "shapely-2.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:28f87cdf5308a514763a5c38de295544cb27429cfa655d50ed8431a4796090c4"}, + {file = "shapely-2.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aeb0f51a9db176da9a30cb2f4329b6fbd1e26d359012bb0ac3d3c7781667a9e"}, + {file = "shapely-2.0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a7a78b0d51257a367ee115f4d41ca4d46edbd0dd280f697a8092dd3989867b2"}, + {file = "shapely-2.0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f32c23d2f43d54029f986479f7c1f6e09c6b3a19353a3833c2ffb226fb63a855"}, + {file = "shapely-2.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3dc9fb0eb56498912025f5eb352b5126f04801ed0e8bdbd867d21bdbfd7cbd0"}, + {file = "shapely-2.0.6-cp311-cp311-win32.whl", hash = "sha256:d93b7e0e71c9f095e09454bf18dad5ea716fb6ced5df3cb044564a00723f339d"}, + {file = "shapely-2.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:c02eb6bf4cfb9fe6568502e85bb2647921ee49171bcd2d4116c7b3109724ef9b"}, + {file = "shapely-2.0.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cec9193519940e9d1b86a3b4f5af9eb6910197d24af02f247afbfb47bcb3fab0"}, + {file = "shapely-2.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83b94a44ab04a90e88be69e7ddcc6f332da7c0a0ebb1156e1c4f568bbec983c3"}, + {file = "shapely-2.0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:537c4b2716d22c92036d00b34aac9d3775e3691f80c7aa517c2c290351f42cd8"}, + {file = "shapely-2.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fea108334be345c283ce74bf064fa00cfdd718048a8af7343c59eb40f59726"}, + {file = "shapely-2.0.6-cp312-cp312-win32.whl", hash = "sha256:42fd4cd4834747e4990227e4cbafb02242c0cffe9ce7ef9971f53ac52d80d55f"}, + {file = "shapely-2.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:665990c84aece05efb68a21b3523a6b2057e84a1afbef426ad287f0796ef8a48"}, + {file = "shapely-2.0.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:42805ef90783ce689a4dde2b6b2f261e2c52609226a0438d882e3ced40bb3013"}, + {file = "shapely-2.0.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6d2cb146191a47bd0cee8ff5f90b47547b82b6345c0d02dd8b25b88b68af62d7"}, + {file = "shapely-2.0.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3fdef0a1794a8fe70dc1f514440aa34426cc0ae98d9a1027fb299d45741c381"}, + {file = "shapely-2.0.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c665a0301c645615a107ff7f52adafa2153beab51daf34587170d85e8ba6805"}, + {file = "shapely-2.0.6-cp313-cp313-win32.whl", hash = "sha256:0334bd51828f68cd54b87d80b3e7cee93f249d82ae55a0faf3ea21c9be7b323a"}, + {file = "shapely-2.0.6-cp313-cp313-win_amd64.whl", hash = "sha256:d37d070da9e0e0f0a530a621e17c0b8c3c9d04105655132a87cfff8bd77cc4c2"}, + {file = "shapely-2.0.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fa7468e4f5b92049c0f36d63c3e309f85f2775752e076378e36c6387245c5462"}, + {file = "shapely-2.0.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed5867e598a9e8ac3291da6cc9baa62ca25706eea186117034e8ec0ea4355653"}, + {file = "shapely-2.0.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81d9dfe155f371f78c8d895a7b7f323bb241fb148d848a2bf2244f79213123fe"}, + {file = "shapely-2.0.6-cp37-cp37m-win32.whl", hash = "sha256:fbb7bf02a7542dba55129062570211cfb0defa05386409b3e306c39612e7fbcc"}, + {file = "shapely-2.0.6-cp37-cp37m-win_amd64.whl", hash = "sha256:837d395fac58aa01aa544495b97940995211e3e25f9aaf87bc3ba5b3a8cd1ac7"}, + {file = "shapely-2.0.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c6d88ade96bf02f6bfd667ddd3626913098e243e419a0325ebef2bbd481d1eb6"}, + {file = "shapely-2.0.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8b3b818c4407eaa0b4cb376fd2305e20ff6df757bf1356651589eadc14aab41b"}, + {file = "shapely-2.0.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbc783529a21f2bd50c79cef90761f72d41c45622b3e57acf78d984c50a5d13"}, + {file = "shapely-2.0.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2423f6c0903ebe5df6d32e0066b3d94029aab18425ad4b07bf98c3972a6e25a1"}, + {file = "shapely-2.0.6-cp38-cp38-win32.whl", hash = "sha256:2de00c3bfa80d6750832bde1d9487e302a6dd21d90cb2f210515cefdb616e5f5"}, + {file = "shapely-2.0.6-cp38-cp38-win_amd64.whl", hash = "sha256:3a82d58a1134d5e975f19268710e53bddd9c473743356c90d97ce04b73e101ee"}, + {file = "shapely-2.0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:392f66f458a0a2c706254f473290418236e52aa4c9b476a072539d63a2460595"}, + {file = "shapely-2.0.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eba5bae271d523c938274c61658ebc34de6c4b33fdf43ef7e938b5776388c1be"}, + {file = "shapely-2.0.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7060566bc4888b0c8ed14b5d57df8a0ead5c28f9b69fb6bed4476df31c51b0af"}, + {file = "shapely-2.0.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b02154b3e9d076a29a8513dffcb80f047a5ea63c897c0cd3d3679f29363cf7e5"}, + {file = "shapely-2.0.6-cp39-cp39-win32.whl", hash = "sha256:44246d30124a4f1a638a7d5419149959532b99dfa25b54393512e6acc9c211ac"}, + {file = "shapely-2.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:2b542d7f1dbb89192d3512c52b679c822ba916f93479fa5d4fc2fe4fa0b3c9e8"}, + {file = "shapely-2.0.6.tar.gz", hash = "sha256:997f6159b1484059ec239cacaa53467fd8b5564dabe186cd84ac2944663b0bf6"}, +] + +[package.dependencies] +numpy = ">=1.14,<3" + +[package.extras] +docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] +test = ["pytest", "pytest-cov"] + [[package]] name = "structlog" version = "24.4.0" @@ -722,4 +782,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "6fdb27af56e45fff1fa8898664fb70a3df4a4258237612e8fc3d795d4c2dc45d" +content-hash = "6e8244db0f75c542b50338580672c5e11b1cdfe38b0a62d01b757b31d4eddaf0" diff --git a/pyproject.toml b/pyproject.toml index d41e193..35dee4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ types-pyyaml = "^6.0.12.20240808" scikit-learn = "^1.5.2" structlog = "^24.4.0" bitarray = "^3.0.0" +shapely = "^2.0.6" [tool.poetry.group.dev.dependencies] diff --git a/pyrightconfig.json b/pyrightconfig.json index 8a1eb70..8c19fca 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -1,4 +1,4 @@ { "typeCheckingMode": "standard", - "exclude": ["**/site-packages"] + "exclude": [".venv", ".git"] } From d24537638ab923eaaefc4ad203016a44083a5ed8 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 14 Nov 2024 02:32:45 +0900 Subject: [PATCH 118/249] refactor: update hash methods to include return type annotations and clean up imports --- adf_core_python/core/agent/agent.py | 74 +++++++++----- adf_core_python/core/agent/command_factory.py | 44 --------- .../agent/communication/message_manager.py | 2 +- .../bundle/centralized/command_ambulance.py | 2 +- .../bundle/centralized/command_fire.py | 2 +- .../bundle/centralized/command_police.py | 2 +- .../bundle/centralized/command_scout.py | 2 +- .../bundle/centralized/message_report.py | 6 +- .../information/message_ambulance_team.py | 2 +- .../bundle/information/message_building.py | 4 +- .../bundle/information/message_civilian.py | 4 +- .../information/message_fire_brigade.py | 4 +- .../information/message_police_force.py | 4 +- .../bundle/information/message_road.py | 4 +- .../standard/bundle/standard_message.py | 2 +- .../bundle/standard_message_priority.py | 6 +- adf_core_python/core/agent/platoon/platoon.py | 1 - .../communication/communication_message.py | 2 +- .../connect/connector_police_office.py | 1 - .../action/default_extend_action_rescue.py | 24 +++-- .../default_message_coordinator.py | 99 ++++++++++++------- .../tactics/default_tactics_ambulance_team.py | 4 +- 22 files changed, 152 insertions(+), 143 deletions(-) delete mode 100644 adf_core_python/core/agent/command_factory.py diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index 51c9e98..4d41918 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -1,6 +1,6 @@ import sys from abc import abstractmethod -from typing import Any, NoReturn +from typing import Any, Callable, NoReturn from bitarray import bitarray from rcrs_core.commands.AKClear import AKClear @@ -16,6 +16,9 @@ from rcrs_core.commands.AKUnload import AKUnload from rcrs_core.commands.Command import Command from rcrs_core.config.config import Config as RCRSConfig +from rcrs_core.connection.URN import Command as CommandURN +from rcrs_core.connection.URN import ComponentCommand as ComponentCommandMessageID +from rcrs_core.connection.URN import ComponentControlMSG as ComponentControlMessageID from rcrs_core.connection.URN import Entity as EntityURN from rcrs_core.messages.AKAcknowledge import AKAcknowledge from rcrs_core.messages.AKConnect import AKConnect @@ -27,7 +30,6 @@ from rcrs_core.worldmodel.entityID import EntityID from rcrs_core.worldmodel.worldmodel import WorldModel -from adf_core_python.core.agent.command_factory import CommandFactory from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( CommandAmbulance, @@ -42,7 +44,7 @@ CommandScout, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( - CommandReport, + MessageReport, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_ambulance_team import ( MessageAmbulanceTeam, @@ -118,7 +120,7 @@ def __init__( def get_entity_id(self) -> EntityID: return self.agent_id - def set_send_msg(self, connection_send_func): + def set_send_msg(self, connection_send_func: Callable) -> None: self.send_msg = connection_send_func def post_connect(self) -> None: @@ -165,7 +167,7 @@ def update_step_info( self._message_manager.register_message_class(7, CommandFire) self._message_manager.register_message_class(8, CommandPolice) self._message_manager.register_message_class(9, CommandScout) - self._message_manager.register_message_class(10, CommandReport) + self._message_manager.register_message_class(10, MessageReport) if time > self._ignore_time: self._message_manager.subscribe( @@ -212,11 +214,11 @@ def precompute(self) -> None: def get_requested_entities(self) -> list[EntityURN]: pass - def start_up(self, request_id): + def start_up(self, request_id: int) -> None: ak_connect = AKConnect() self.send_msg(ak_connect.write(request_id, self)) - def message_received(self, msg): + def message_received(self, msg: Any) -> None: c_msg = ControlMessageFactory().make_message(msg) if isinstance(c_msg, KASense): self.handler_sense(c_msg) @@ -231,7 +233,7 @@ def handle_connect_error(self, msg: Any) -> NoReturn: ) sys.exit(1) - def handle_connect_ok(self, msg): + def handle_connect_ok(self, msg: Any) -> None: self.agent_id = EntityID(msg.agent_id) self.world_model.add_entities(msg.world) config: RCRSConfig = msg.config @@ -253,76 +255,94 @@ def handle_connect_ok(self, msg): print("self.precompute_flag: ", self.precompute_flag) self.precompute() - def handler_sense(self, msg): + def handler_sense(self, msg: Any) -> None: _id = EntityID(msg.agent_id) time = msg.time change_set = msg.change_set - hear = msg.hear.commands + heard = msg.hear.commands if _id != self.get_entity_id(): self.logger.error("Agent ID mismatch: %s != %s", _id, self.get_entity_id()) return - hear_commands = [CommandFactory.create_command(cmd) for cmd in hear] + heard_commands: list[Command] = [] + for herad_command in heard: + if herad_command.urn == CommandURN.AK_SPEAK: + heard_commands.append( + AKSpeak( + herad_command.components[ + ComponentControlMessageID.AgentID + ].entityID, + herad_command.components[ + ComponentControlMessageID.Time + ].intValue, + herad_command.components[ + ComponentCommandMessageID.Message + ].rawData, + herad_command.components[ + ComponentCommandMessageID.Channel + ].intValue, + ) + ) self.world_model.merge(change_set) - self.update_step_info(time, change_set, hear_commands) + self.update_step_info(time, change_set, heard_commands) - def send_acknowledge(self, request_id): + def send_acknowledge(self, request_id: int) -> None: ak_ack = AKAcknowledge() self.send_msg(ak_ack.write(request_id, self.agent_id)) - def send_clear(self, time, target): + def send_clear(self, time: int, target: EntityID) -> None: cmd = AKClear(self.get_entity_id(), time, target) msg = cmd.prepare_cmd() self.send_msg(msg) - def send_clear_area(self, time, x=-1, y=-1): + def send_clear_area(self, time: int, x: int = -1, y: int = -1) -> None: cmd = AKClearArea(self.get_entity_id(), time, x, y) msg = cmd.prepare_cmd() self.send_msg(msg) - def send_load(self, time, target): + def send_load(self, time: int, target: EntityID) -> None: cmd = AKLoad(self.get_entity_id(), time, target) msg = cmd.prepare_cmd() self.send_msg(msg) - def send_move(self, time, path, x=-1, y=-1): + def send_move(self, time: int, path: list[int], x: int = -1, y: int = -1) -> None: cmd = AKMove(self.get_entity_id(), time, path[:], x, y) msg = cmd.prepare_cmd() self.send_msg(msg) - def send_rescue(self, time, target): + def send_rescue(self, time: int, target: EntityID) -> None: cmd = AKRescue(self.get_entity_id(), time, target) msg = cmd.prepare_cmd() self.send_msg(msg) - def send_rest(self, time_step): - cmd = AKRest(self.get_entity_id(), time_step) + def send_rest(self, time: int) -> None: + cmd = AKRest(self.get_entity_id(), time) msg = cmd.prepare_cmd() self.send_msg(msg) - def send_say(self, time_step: int, message: str): + def send_say(self, time_step: int, message: str) -> None: cmd = AKSay(self.get_entity_id(), time_step, message) msg = cmd.prepare_cmd() self.send_msg(msg) - def send_speak(self, time_step: int, message: bitarray, channel: int): + def send_speak(self, time_step: int, message: bitarray, channel: int) -> None: cmd = AKSpeak(self.get_entity_id(), time_step, bytes(message), channel) # type: ignore msg = cmd.prepare_cmd() self.send_msg(msg) - def send_subscribe(self, time, channel): - cmd = AKSubscribe(self.get_entity_id(), time, channel) + def send_subscribe(self, time: int, channels: list[int]) -> None: + cmd = AKSubscribe(self.get_entity_id(), time, channels) msg = cmd.prepare_cmd() self.send_msg(msg) - def send_tell(self, time_step: int, message: str): - cmd = AKTell(self.get_entity_id(), time_step, message) + def send_tell(self, time: int, message: str) -> None: + cmd = AKTell(self.get_entity_id(), time, message) msg = cmd.prepare_cmd() self.send_msg(msg) - def send_unload(self, time): + def send_unload(self, time: int) -> None: cmd = AKUnload(self.get_entity_id(), time) msg = cmd.prepare_cmd() self.send_msg(msg) diff --git a/adf_core_python/core/agent/command_factory.py b/adf_core_python/core/agent/command_factory.py deleted file mode 100644 index 33c5253..0000000 --- a/adf_core_python/core/agent/command_factory.py +++ /dev/null @@ -1,44 +0,0 @@ -from rcrs_core.commands.AKClear import AKClear -from rcrs_core.commands.AKClearArea import AKClearArea -from rcrs_core.commands.AKExtinguish import AKExtinguish -from rcrs_core.commands.AKLoad import AKLoad -from rcrs_core.commands.AKMove import AKMove -from rcrs_core.commands.AKRescue import AKRescue -from rcrs_core.commands.AKRest import AKRest -from rcrs_core.commands.AKSay import AKSay -from rcrs_core.commands.AKSpeak import AKSpeak -from rcrs_core.commands.AKSubscribe import AKSubscribe -from rcrs_core.commands.AKTell import AKTell -from rcrs_core.commands.AKUnload import AKUnload -from rcrs_core.commands.Command import Command -from rcrs_core.connection.URN import Command as CommandURN -from rcrs_core.connection.URN import ComponentCommand as ComponentCommandMessageID -from rcrs_core.connection.URN import ComponentControlMSG as ComponentControlMessageID - - -class CommandFactory: - @staticmethod - def create_command(herad_command) -> Command: - if herad_command.urn == CommandURN.AK_SPEAK: - return AKSpeak( - herad_command.components[ComponentControlMessageID.AgentID].entityID, - herad_command.components[ComponentControlMessageID.Time].intValue, - herad_command.components[ComponentCommandMessageID.Message].rawData, - herad_command.components[ComponentCommandMessageID.Channel].intValue, - ) - return herad_command - # if herad_command.urn == CommandURN.AK_CLEAR: - # return AKClear( - # herad_command.agent_id, herad_command.time, herad_command.target - # ) - # elif herad_command.urn == CommandURN.AK_CLEAR_AREA: - # return AKClearArea( - # herad_command.agent_id, - # herad_command.time, - # herad_command.x, - # herad_command.y, - # ) - # elif herad_command.urn == CommandURN.AK_EXTINGUISH: - # return AKExtinguish( - # herad_command.agent_id, herad_command.time, herad_command.target - # ) diff --git a/adf_core_python/core/agent/communication/message_manager.py b/adf_core_python/core/agent/communication/message_manager.py index 53c8f43..f318b1a 100644 --- a/adf_core_python/core/agent/communication/message_manager.py +++ b/adf_core_python/core/agent/communication/message_manager.py @@ -44,7 +44,7 @@ def __init__(self) -> None: self.__send_message_list: list[CommunicationMessage] = [] self.__received_message_list: list[CommunicationMessage] = [] self.__channel_send_message_list: list[list[CommunicationMessage]] = [] - self.__check_duplicate_cache: set[str] = set() + self.__check_duplicate_cache: set[int] = set() self.__message_coordinator: MessageCoordinator self.__channel_subscriber: ChannelSubscriber self.__heard_agent_help_message_count: int = 0 diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py index ff28ab8..a5d6984 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py +++ b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py @@ -120,7 +120,7 @@ def from_bits( command_target_id, ) - def __hash__(self): + def __hash__(self) -> int: h = super().__hash__() return hash( ( diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py index 5927a2e..cf61131 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py +++ b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py @@ -121,7 +121,7 @@ def from_bits( command_target_id, ) - def __hash__(self): + def __hash__(self) -> int: h = super().__hash__() return hash( ( diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py index cf3cc4d..64b8e34 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py +++ b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py @@ -119,7 +119,7 @@ def from_bits( command_target_id, ) - def __hash__(self): + def __hash__(self) -> int: h = super().__hash__() return hash( ( diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py index 253883b..138a1dd 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py +++ b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py @@ -114,7 +114,7 @@ def from_bits( command_target_id, ) - def __hash__(self): + def __hash__(self) -> int: h = super().__hash__() return hash( ( diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py index 2971bb5..55f8907 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py +++ b/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py @@ -15,7 +15,7 @@ ) -class CommandReport(StandardMessage): +class MessageReport(StandardMessage): SIZE_DONE: int = 1 SIZE_BLOADCAST: int = 1 @@ -60,7 +60,7 @@ def from_bits( bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID, - ) -> CommandReport: + ) -> MessageReport: std_message = super().from_bits( bit_array, is_wireless_message, sender_entity_id ) @@ -74,7 +74,7 @@ def from_bits( std_message.get_priority(), ) - def __hash__(self): + def __hash__(self) -> int: h = super().__hash__() return hash( ( diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py index 7f495f7..6c73a95 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py @@ -167,7 +167,7 @@ def from_bits( std_message.get_ttl(), ) - def __hash__(self): + def __hash__(self) -> int: h = super().__hash__() return hash( ( diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py index 3363e92..be9f911 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py @@ -106,7 +106,7 @@ def from_bits( std_message.get_ttl(), ) - def __hash__(self): + def __hash__(self) -> int: h = super().__hash__() return hash( ( @@ -118,5 +118,5 @@ def __hash__(self): ) ) - def __str__(self): + def __str__(self) -> str: return f"MessageBuilding(building_entity_id={self._building_entity_id}, building_brokenness={self._building_brokenness}, building_fireyness={self._building_fireyness}, building_temperature={self._building_temperature})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py index ac29847..a5a62b4 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py @@ -119,7 +119,7 @@ def from_bits( std_message.get_ttl(), ) - def __hash__(self): + def __hash__(self) -> int: h = super().__hash__() return hash( ( @@ -132,5 +132,5 @@ def __hash__(self): ) ) - def __str__(self): + def __str__(self) -> str: return f"MessageCivilian(civilian_entity_id={self._civilian_entity_id}, civilian_hp={self._civilian_hp}, civilian_buriedness={self._civilian_buriedness}, civilian_damage={self._civilian_damage}, civilian_position={self._civilian_position})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py index 11742d5..a7c4cee 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py @@ -180,7 +180,7 @@ def from_bits( std_message.get_ttl(), ) - def __hash__(self): + def __hash__(self) -> int: h = super().__hash__() return hash( ( @@ -196,5 +196,5 @@ def __hash__(self): ) ) - def __str__(self): + def __str__(self) -> str: return f"MessageFireBrigade(fire_brigade_entity_id={self._fire_brigade_entity_id}, fire_brigade_hp={self._fire_brigade_hp}, fire_brigade_buriedness={self._fire_brigade_buriedness}, fire_brigade_damage={self._fire_brigade_damage}, fire_brigade_position={self._fire_brigade_position}, fire_brigade_water={self._fire_brigade_water}, target_entity_id={self._target_entity_id}, action={self._action})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py index 8280184..45116f3 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py @@ -162,7 +162,7 @@ def from_bits( std_message.get_ttl(), ) - def __hash__(self): + def __hash__(self) -> int: h = super().__hash__() return hash( ( @@ -177,5 +177,5 @@ def __hash__(self): ) ) - def __str__(self): + def __str__(self) -> str: return f"MessagePoliceForce(police_force_entity_id={self._police_force_entity_id}, police_force_hp={self._police_force_hp}, police_force_buriedness={self._police_force_buriedness}, police_force_damage={self._police_force_damage}, police_force_position={self._police_force_position}, target_entity_id={self._target_entity_id}, action={self._action})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py index 9023973..474421c 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py @@ -160,7 +160,7 @@ def from_bits( std_message.get_ttl(), ) - def __hash__(self): + def __hash__(self) -> int: h = super().__hash__() return hash( ( @@ -175,5 +175,5 @@ def __hash__(self): ) ) - def __str__(self): + def __str__(self) -> str: return f"MessageRoad(road_entity_id={self._road_entity_id}, road_blockade_entity_id={self._road_blockade_entity_id}, road_blockade_repair_cost={self._road_blockade_repair_cost}, road_blockade_x={self._road_blockade_x}, road_blockade_y={self._road_blockade_y}, is_passable={self._is_passable}, is_send_blockade_location={self._is_send_blockade_location})" diff --git a/adf_core_python/core/agent/communication/standard/bundle/standard_message.py b/adf_core_python/core/agent/communication/standard/bundle/standard_message.py index 490f367..bdec11b 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/standard_message.py +++ b/adf_core_python/core/agent/communication/standard/bundle/standard_message.py @@ -64,7 +64,7 @@ def to_bits(self) -> bitarray: def get_bit_size(self) -> int: raise NotImplementedError - def __hash__(self): + def __hash__(self) -> int: return hash((self._sender_entity_id, self._priority, self._ttl)) def __str__(self) -> str: diff --git a/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py b/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py index e8e2e25..9e45154 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py +++ b/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py @@ -13,10 +13,12 @@ class StandardMessagePriority(Enum): def __str__(self) -> str: return self.name.lower() - def __lt__(self, other): + def __lt__(self, other: object) -> bool: if isinstance(other, StandardMessagePriority): return self.value < other.value + return NotImplemented - def __le__(self, other): + def __le__(self, other: object) -> bool: if isinstance(other, StandardMessagePriority): return self.value <= other.value + return NotImplemented diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py index 098945f..01babaa 100644 --- a/adf_core_python/core/agent/platoon/platoon.py +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -1,6 +1,5 @@ from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.agent import Agent -from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.scenario_info import Mode diff --git a/adf_core_python/core/component/communication/communication_message.py b/adf_core_python/core/component/communication/communication_message.py index 0760ac0..60972ff 100644 --- a/adf_core_python/core/component/communication/communication_message.py +++ b/adf_core_python/core/component/communication/communication_message.py @@ -21,5 +21,5 @@ def to_bits(self) -> bitarray: raise NotImplementedError @abstractmethod - def __hash__(self): + def __hash__(self) -> int: raise NotImplementedError diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py index 561b987..b20da2e 100644 --- a/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -1,4 +1,3 @@ -import re import threading from rcrs_core.agents.policeOfficeAgent import PoliceOfficeAgent diff --git a/adf_core_python/implement/action/default_extend_action_rescue.py b/adf_core_python/implement/action/default_extend_action_rescue.py index 845ad18..67f4ff3 100644 --- a/adf_core_python/implement/action/default_extend_action_rescue.py +++ b/adf_core_python/implement/action/default_extend_action_rescue.py @@ -1,4 +1,4 @@ -from typing import cast, Optional +from typing import Optional, cast from rcrs_core.entities.area import Area from rcrs_core.entities.blockade import Blockade @@ -12,7 +12,11 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import ScenarioInfo, Mode +from adf_core_python.core.agent.info.scenario_info import ( + Mode, + ScenarioInfo, + ScenarioInfoKeys, +) from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData @@ -32,8 +36,8 @@ def __init__( super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - self._kernel_time = None - self._target_entity_id = None + self._kernel_time: int = -1 + self._target_entity_id: Optional[EntityID] = None self._threshold_rest = develop_data.get_value( "adf_core_python.implement.action.DefaultExtendActionRescue.rest", 100 ) @@ -57,7 +61,9 @@ def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: if self.get_count_precompute() >= 2: return self self._path_planning.precompute(precompute_data) - self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + self._kernel_time = self.scenario_info.get_value( + ScenarioInfoKeys.KERNEL_TIMESTEPS, -1 + ) return self def resume(self, precompute_data: PrecomputeData) -> ExtendAction: @@ -65,7 +71,9 @@ def resume(self, precompute_data: PrecomputeData) -> ExtendAction: if self.get_count_resume() >= 2: return self self._path_planning.resume(precompute_data) - self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + self._kernel_time = self.scenario_info.get_value( + ScenarioInfoKeys.KERNEL_TIMESTEPS, -1 + ) return self def prepare(self) -> ExtendAction: @@ -73,7 +81,9 @@ def prepare(self) -> ExtendAction: if self.get_count_prepare() >= 2: return self self._path_planning.prepare() - self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + self._kernel_time = self.scenario_info.get_value( + ScenarioInfoKeys.KERNEL_TIMESTEPS, -1 + ) return self def update_info(self, message_manager: MessageManager) -> ExtendAction: diff --git a/adf_core_python/implement/module/communication/default_message_coordinator.py b/adf_core_python/implement/module/communication/default_message_coordinator.py index 2e61264..92e1555 100644 --- a/adf_core_python/implement/module/communication/default_message_coordinator.py +++ b/adf_core_python/implement/module/communication/default_message_coordinator.py @@ -1,5 +1,23 @@ +from typing import Optional + from rcrs_core.connection.URN import Entity as EntityURN +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( + CommandAmbulance, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_fire import ( + CommandFire, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_police import ( + CommandPolice, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( + CommandScout, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( + MessageReport, +) from adf_core_python.core.agent.communication.standard.bundle.information.message_ambulance_team import ( MessageAmbulanceTeam, ) @@ -27,6 +45,9 @@ from adf_core_python.core.agent.info.agent_info import AgentInfo from adf_core_python.core.agent.info.scenario_info import ScenarioInfo, ScenarioInfoKeys from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.component.communication.communication_message import ( + CommunicationMessage, +) from adf_core_python.core.component.communication.message_coordinator import ( MessageCoordinator, ) @@ -38,17 +59,17 @@ class DefaultMessageCoordinator(MessageCoordinator): def coordinate( self, - agent_info, - world_info, - scenario_info, - message_manager, - send_message_list, - channel_send_message_list, - ): - police_messages = [] - ambulance_messages = [] - fire_brigade_messages = [] - voice_messages = [] + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + message_manager: MessageManager, + send_message_list: list[CommunicationMessage], + channel_send_message_list: list[list[CommunicationMessage]], + ) -> None: + police_messages: list[StandardMessage] = [] + ambulance_messages: list[StandardMessage] = [] + fire_brigade_messages: list[StandardMessage] = [] + voice_messages: list[StandardMessage] = [] agent_type = self.get_agent_type(agent_info, world_info) @@ -64,26 +85,26 @@ def coordinate( fire_brigade_messages.append(msg) ambulance_messages.append(msg) police_messages.append(msg) - # elif isinstance(msg, CommandAmbulance): - # ambulance_messages.append(msg) - # elif isinstance(msg, CommandFire): - # fire_brigade_messages.append(msg) - # elif isinstance(msg, CommandPolice): - # police_messages.append(msg) - # elif isinstance(msg, CommandScout): - # if agent_type == EntityURN.FIRE_STATION: - # fire_brigade_messages.append(msg) - # elif agent_type == EntityURN.POLICE_OFFICE: - # police_messages.append(msg) - # elif agent_type == EntityURN.AMBULANCE_CENTRE: - # ambulance_messages.append(msg) - # elif isinstance(msg, MessageReport): - # if agent_type == EntityURN.FIRE_BRIGADE: - # fire_brigade_messages.append(msg) - # elif agent_type == EntityURN.POLICE_FORCE: - # police_messages.append(msg) - # elif agent_type == EntityURN.AMBULANCE_TEAM: - # ambulance_messages.append(msg) + elif isinstance(msg, CommandAmbulance): + ambulance_messages.append(msg) + elif isinstance(msg, CommandFire): + fire_brigade_messages.append(msg) + elif isinstance(msg, CommandPolice): + police_messages.append(msg) + elif isinstance(msg, CommandScout): + if agent_type == EntityURN.FIRE_STATION: + fire_brigade_messages.append(msg) + elif agent_type == EntityURN.POLICE_OFFICE: + police_messages.append(msg) + elif agent_type == EntityURN.AMBULANCE_CENTRE: + ambulance_messages.append(msg) + elif isinstance(msg, MessageReport): + if agent_type == EntityURN.FIRE_BRIGADE: + fire_brigade_messages.append(msg) + elif agent_type == EntityURN.POLICE_FORCE: + police_messages.append(msg) + elif agent_type == EntityURN.AMBULANCE_TEAM: + ambulance_messages.append(msg) elif isinstance(msg, MessageFireBrigade): fire_brigade_messages.append(msg) ambulance_messages.append(msg) @@ -146,11 +167,11 @@ def coordinate( def get_channels_by_agent_type( self, - agent_type, + agent_type: EntityURN, agent_info: AgentInfo, world_info: WorldInfo, scenario_info: ScenarioInfo, - ): + ) -> list[int]: num_channels = ( scenario_info.get_value(ScenarioInfoKeys.COMMUNICATION_CHANNELS_COUNT, 1) - 1 @@ -171,7 +192,7 @@ def get_channels_by_agent_type( ] return channels - def is_platoon_agent(self, agent_info, world_info): + def is_platoon_agent(self, agent_info: AgentInfo, world_info: WorldInfo) -> bool: agent_type = self.get_agent_type(agent_info, world_info) return agent_type in [ EntityURN.FIRE_BRIGADE, @@ -179,7 +200,9 @@ def is_platoon_agent(self, agent_info, world_info): EntityURN.AMBULANCE_TEAM, ] - def get_agent_type(self, agent_info: AgentInfo, world_info: WorldInfo): + def get_agent_type( + self, agent_info: AgentInfo, world_info: WorldInfo + ) -> Optional[EntityURN]: entity = world_info.get_entity(agent_info.get_entity_id()) if entity is None: return None @@ -192,9 +215,9 @@ def set_send_messages( agent_info: AgentInfo, world_info: WorldInfo, messages: list[StandardMessage], - channel_send_message_list, - channel_size, - ): + channel_send_message_list: list[list[CommunicationMessage]], + channel_size: list[int], + ) -> None: channels = self.get_channels_by_agent_type( agent_type, agent_info, world_info, scenario_info ) diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 7a30dee..f2ebf21 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -18,7 +18,7 @@ CommandScout, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( - CommandReport, + MessageReport, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( StandardMessagePriority, @@ -178,7 +178,7 @@ def think( ) ) message_manager.add_message( - CommandReport( + MessageReport( False, True, True, From ae093ef703ce23db6e0b566368732b6657ed34d6 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 14 Nov 2024 02:46:32 +0900 Subject: [PATCH 119/249] add: __init__ file --- adf_core_python/core/agent/__init__.py | 0 adf_core_python/core/agent/action/__init__.py | 0 adf_core_python/core/agent/action/ambulance/__init__.py | 0 adf_core_python/core/agent/action/common/__init__.py | 0 adf_core_python/core/agent/action/fire/__init__.py | 0 adf_core_python/core/agent/action/police/__init__.py | 0 adf_core_python/core/agent/info/__init__.py | 0 adf_core_python/core/agent/platoon/__init__.py | 0 adf_core_python/core/agent/precompute/__init__.py | 0 adf_core_python/core/component/action/__init__.py | 0 adf_core_python/core/component/module/__init__.py | 0 adf_core_python/core/component/module/algorithm/__init__.py | 0 adf_core_python/core/component/module/complex/__init__.py | 0 adf_core_python/core/component/tactics/__init__.py | 0 adf_core_python/core/config/__init__.py | 0 adf_core_python/core/launcher/__init__.py | 0 adf_core_python/core/launcher/connect/__init__.py | 0 adf_core_python/core/logger/__init__.py | 0 adf_core_python/implement/__init__.py | 0 adf_core_python/implement/action/__init__.py | 0 adf_core_python/implement/module/communication/__init__.py | 0 adf_core_python/implement/tactics/__init__.py | 0 22 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 adf_core_python/core/agent/__init__.py create mode 100644 adf_core_python/core/agent/action/__init__.py create mode 100644 adf_core_python/core/agent/action/ambulance/__init__.py create mode 100644 adf_core_python/core/agent/action/common/__init__.py create mode 100644 adf_core_python/core/agent/action/fire/__init__.py create mode 100644 adf_core_python/core/agent/action/police/__init__.py create mode 100644 adf_core_python/core/agent/info/__init__.py create mode 100644 adf_core_python/core/agent/platoon/__init__.py create mode 100644 adf_core_python/core/agent/precompute/__init__.py create mode 100644 adf_core_python/core/component/action/__init__.py create mode 100644 adf_core_python/core/component/module/__init__.py create mode 100644 adf_core_python/core/component/module/algorithm/__init__.py create mode 100644 adf_core_python/core/component/module/complex/__init__.py create mode 100644 adf_core_python/core/component/tactics/__init__.py create mode 100644 adf_core_python/core/config/__init__.py create mode 100644 adf_core_python/core/launcher/__init__.py create mode 100644 adf_core_python/core/launcher/connect/__init__.py create mode 100644 adf_core_python/core/logger/__init__.py create mode 100644 adf_core_python/implement/__init__.py create mode 100644 adf_core_python/implement/action/__init__.py create mode 100644 adf_core_python/implement/module/communication/__init__.py create mode 100644 adf_core_python/implement/tactics/__init__.py diff --git a/adf_core_python/core/agent/__init__.py b/adf_core_python/core/agent/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/agent/action/__init__.py b/adf_core_python/core/agent/action/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/agent/action/ambulance/__init__.py b/adf_core_python/core/agent/action/ambulance/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/agent/action/common/__init__.py b/adf_core_python/core/agent/action/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/agent/action/fire/__init__.py b/adf_core_python/core/agent/action/fire/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/agent/action/police/__init__.py b/adf_core_python/core/agent/action/police/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/agent/info/__init__.py b/adf_core_python/core/agent/info/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/agent/platoon/__init__.py b/adf_core_python/core/agent/platoon/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/agent/precompute/__init__.py b/adf_core_python/core/agent/precompute/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/component/action/__init__.py b/adf_core_python/core/component/action/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/component/module/__init__.py b/adf_core_python/core/component/module/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/component/module/algorithm/__init__.py b/adf_core_python/core/component/module/algorithm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/component/module/complex/__init__.py b/adf_core_python/core/component/module/complex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/component/tactics/__init__.py b/adf_core_python/core/component/tactics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/config/__init__.py b/adf_core_python/core/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/launcher/__init__.py b/adf_core_python/core/launcher/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/launcher/connect/__init__.py b/adf_core_python/core/launcher/connect/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/logger/__init__.py b/adf_core_python/core/logger/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/implement/__init__.py b/adf_core_python/implement/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/implement/action/__init__.py b/adf_core_python/implement/action/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/implement/module/communication/__init__.py b/adf_core_python/implement/module/communication/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/implement/tactics/__init__.py b/adf_core_python/implement/tactics/__init__.py new file mode 100644 index 0000000..e69de29 From 86f1c2770fa805ef1cb9a0d3360e4f1525e5580a Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 14 Nov 2024 02:48:15 +0900 Subject: [PATCH 120/249] refactor: remove unused command imports and message additions from DefaultTacticsAmbulanceTeam --- .../tactics/default_tactics_ambulance_team.py | 68 ------------------- 1 file changed, 68 deletions(-) diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index f2ebf21..857d724 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -5,24 +5,6 @@ from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.action.common.action_rest import ActionRest from adf_core_python.core.agent.communication.message_manager import MessageManager -from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( - CommandAmbulance, -) -from adf_core_python.core.agent.communication.standard.bundle.centralized.command_fire import ( - CommandFire, -) -from adf_core_python.core.agent.communication.standard.bundle.centralized.command_police import ( - CommandPolice, -) -from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( - CommandScout, -) -from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( - MessageReport, -) -from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, -) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo @@ -137,56 +119,6 @@ def think( agent: AmbulanceTeamEntity = cast(AmbulanceTeamEntity, agent_info.get_myself()) # noqa: F841 entity_id = agent_info.get_entity_id() # noqa: F841 - message_manager.add_message( - CommandAmbulance( - False, - entity_id, - entity_id, - CommandAmbulance.ACTION_REST, - StandardMessagePriority.NORMAL, - entity_id, - ) - ) - message_manager.add_message( - CommandFire( - False, - entity_id, - entity_id, - CommandFire.ACTION_REST, - StandardMessagePriority.NORMAL, - entity_id, - ) - ) - message_manager.add_message( - CommandPolice( - False, - entity_id, - entity_id, - CommandPolice.ACTION_REST, - StandardMessagePriority.NORMAL, - entity_id, - ) - ) - message_manager.add_message( - CommandScout( - False, - entity_id, - entity_id, - 20000, - StandardMessagePriority.NORMAL, - entity_id, - ) - ) - message_manager.add_message( - MessageReport( - False, - True, - True, - entity_id, - StandardMessagePriority.NORMAL, - ) - ) - self._logger.debug( f"received messages: {[str(message) for message in message_manager.get_received_message_list()]}, help: {message_manager.get_heard_agent_help_message_count()}", message_manager=message_manager, From 31abf3ad78169da9dca3faa2d0a45a24dfe1d752 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 20 Nov 2024 23:33:32 +0900 Subject: [PATCH 121/249] chore: update tomli version to 2.1.0 and resolve reference in poetry.lock --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 249bcc8..c1d9ac7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -490,7 +490,7 @@ rtree = "*" type = "git" url = "https://github.com/adf-python/rcrs-core-python" reference = "HEAD" -resolved_reference = "c4ec7b2d32badb2f10fc1d03b962b465624a1ed5" +resolved_reference = "aa661b19c98f82d8d648317f386c2f9d2c26a8fc" [[package]] name = "rtree" @@ -737,13 +737,13 @@ files = [ [[package]] name = "tomli" -version = "2.0.2" +version = "2.1.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, - {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, + {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, + {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, ] [[package]] From 1581d1bad07c2a4a74ba7a5d6f23c8ab6d1cd8e2 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 20 Nov 2024 23:33:52 +0900 Subject: [PATCH 122/249] refactor: replace entity references with updated class names for ambulance team, police force, and fire brigade --- .../information/message_ambulance_team.py | 6 ++--- .../information/message_fire_brigade.py | 6 ++--- .../information/message_police_force.py | 6 ++--- .../standard/utility/apply_to_world_info.py | 18 +++++++------- .../action/default_extend_action_clear.py | 24 +++++++++---------- .../action/default_extend_action_rescue.py | 6 ++--- .../action/default_extend_action_transport.py | 10 ++++---- .../module/algorithm/k_means_clustering.py | 14 +++++------ .../default_ambulance_target_allocator.py | 14 ++++------- .../complex/default_fire_target_allocator.py | 10 ++++---- .../default_police_target_allocator.py | 10 ++++---- .../tactics/default_tactics_ambulance_team.py | 4 ++-- .../tactics/default_tactics_fire_brigade.py | 4 ++-- .../tactics/default_tactics_police_force.py | 4 ++-- 14 files changed, 64 insertions(+), 72 deletions(-) diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py index 6c73a95..3a74aba 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py @@ -3,7 +3,7 @@ from typing import Optional from bitarray import bitarray -from rcrs_core.entities.ambulanceTeam import AmbulanceTeamEntity +from rcrs_core.entities.ambulanceTeam import AmbulanceTeam from rcrs_core.worldmodel.entityID import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( @@ -36,7 +36,7 @@ class MessageAmbulanceTeam(StandardMessage): def __init__( self, is_wireless_message: bool, - ambulance_team: AmbulanceTeamEntity, + ambulance_team: AmbulanceTeam, action: int, target_entity_id: EntityID, priority: StandardMessagePriority, @@ -152,7 +152,7 @@ def from_bits( else EntityID(-1) ) action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) - ambulance_team = AmbulanceTeamEntity(ambulance_team_id or -1) + ambulance_team = AmbulanceTeam(ambulance_team_id or -1) ambulance_team.set_hp(ambulance_team_hp) ambulance_team.set_buriedness(ambulance_team_buriedness) ambulance_team.set_damage(ambulance_team_damage) diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py index a7c4cee..feefd51 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py @@ -3,7 +3,7 @@ from typing import Optional from bitarray import bitarray -from rcrs_core.entities.fireBrigade import FireBrigadeEntity +from rcrs_core.entities.fireBrigade import FireBrigade from rcrs_core.worldmodel.entityID import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( @@ -37,7 +37,7 @@ class MessageFireBrigade(StandardMessage): def __init__( self, is_wireless_message: bool, - fire_brigade: FireBrigadeEntity, + fire_brigade: FireBrigade, action: int, target_entity_id: EntityID, priority: StandardMessagePriority, @@ -162,7 +162,7 @@ def from_bits( EntityID(raw_target_entity_id) if raw_target_entity_id else EntityID(-1) ) action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) - fire_brigade = FireBrigadeEntity( + fire_brigade = FireBrigade( fire_brigade_id or -1, ) fire_brigade.set_hp(fire_brigade_hp) diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py index 45116f3..f37432d 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py @@ -3,7 +3,7 @@ from typing import Optional from bitarray import bitarray -from rcrs_core.entities.policeForce import PoliceForceEntity +from rcrs_core.entities.policeForce import PoliceForce from rcrs_core.worldmodel.entityID import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( @@ -34,7 +34,7 @@ class MessagePoliceForce(StandardMessage): def __init__( self, is_wireless_message: bool, - police_force: PoliceForceEntity, + police_force: PoliceForce, action: int, target_entity_id: EntityID, priority: StandardMessagePriority, @@ -147,7 +147,7 @@ def from_bits( EntityID(raw_target_entity_id) if raw_target_entity_id else EntityID(-1) ) action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) - police_force = PoliceForceEntity(police_force_id or -1) + police_force = PoliceForce(police_force_id or -1) police_force.set_hp(police_force_hp) police_force.set_buriedness(police_force_buriedness) police_force.set_damage(police_force_damage) diff --git a/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py b/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py index 811c152..c1e7020 100644 --- a/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py +++ b/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py @@ -1,9 +1,9 @@ -from rcrs_core.entities.ambulanceTeam import AmbulanceTeamEntity +from rcrs_core.entities.ambulanceTeam import AmbulanceTeam from rcrs_core.entities.blockade import Blockade from rcrs_core.entities.building import Building from rcrs_core.entities.civilian import Civilian -from rcrs_core.entities.fireBrigade import FireBrigadeEntity -from rcrs_core.entities.policeForce import PoliceForceEntity +from rcrs_core.entities.fireBrigade import FireBrigade +from rcrs_core.entities.policeForce import PoliceForce from rcrs_core.entities.road import Road from adf_core_python.core.agent.communication.standard.bundle.information.message_ambulance_team import ( @@ -80,7 +80,7 @@ def _apply_to_world_info_ambulance_team( return entity = world_info.get_entity(entity_id) if entity is None: - ambulance = AmbulanceTeamEntity(entity_id.get_value()) + ambulance = AmbulanceTeam(entity_id.get_value()) if (hp := message_ambulance_team.get_ambulance_team_hp()) is not None: ambulance.set_hp(hp) if (damege := message_ambulance_team.get_ambulance_team_damage()) is not None: @@ -95,7 +95,7 @@ def _apply_to_world_info_ambulance_team( ambulance.set_position(position) world_info.add_entity(ambulance) else: - if isinstance(entity, AmbulanceTeamEntity): + if isinstance(entity, AmbulanceTeam): if (hp := message_ambulance_team.get_ambulance_team_hp()) is not None: entity.set_hp(hp) if ( @@ -131,7 +131,7 @@ def _apply_to_world_info_fire_brigade( return entity = world_info.get_entity(entity_id) if entity is None: - fire_brigade = FireBrigadeEntity(entity_id.get_value()) + fire_brigade = FireBrigade(entity_id.get_value()) if (hp := message_fire_brigade.get_fire_brigade_hp()) is not None: fire_brigade.set_hp(hp) if (damage := message_fire_brigade.get_fire_brigade_damage()) is not None: @@ -146,7 +146,7 @@ def _apply_to_world_info_fire_brigade( fire_brigade.set_water(water) world_info.add_entity(fire_brigade) else: - if isinstance(entity, FireBrigadeEntity): + if isinstance(entity, FireBrigade): if (hp := message_fire_brigade.get_fire_brigade_hp()) is not None: entity.set_hp(hp) if (damage := message_fire_brigade.get_fire_brigade_damage()) is not None: @@ -182,7 +182,7 @@ def _apply_to_world_info_police_force( return entity = world_info.get_entity(entity_id) if entity is None: - police_force = PoliceForceEntity(entity_id.get_value()) + police_force = PoliceForce(entity_id.get_value()) if (hp := message_police_force.get_police_force_hp()) is not None: police_force.set_hp(hp) if (damage := message_police_force.get_police_force_damage()) is not None: @@ -195,7 +195,7 @@ def _apply_to_world_info_police_force( police_force.set_position(position) world_info.add_entity(police_force) else: - if isinstance(entity, PoliceForceEntity): + if isinstance(entity, PoliceForce): if (hp := message_police_force.get_police_force_hp()) is not None: entity.set_hp(hp) if (damage := message_police_force.get_police_force_damage()) is not None: diff --git a/adf_core_python/implement/action/default_extend_action_clear.py b/adf_core_python/implement/action/default_extend_action_clear.py index 7b6731b..d24c408 100644 --- a/adf_core_python/implement/action/default_extend_action_clear.py +++ b/adf_core_python/implement/action/default_extend_action_clear.py @@ -2,13 +2,13 @@ import sys from typing import Optional, cast -from rcrs_core.entities.ambulanceTeam import AmbulanceTeamEntity +from rcrs_core.entities.ambulanceTeam import AmbulanceTeam from rcrs_core.entities.area import Area from rcrs_core.entities.blockade import Blockade from rcrs_core.entities.building import Building -from rcrs_core.entities.fireBrigade import FireBrigadeEntity +from rcrs_core.entities.fireBrigade import FireBrigade from rcrs_core.entities.human import Human -from rcrs_core.entities.policeForce import PoliceForceEntity +from rcrs_core.entities.policeForce import PoliceForce from rcrs_core.entities.refuge import Refuge from rcrs_core.entities.road import Road from rcrs_core.worldmodel.entityID import EntityID @@ -122,7 +122,7 @@ def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: def calculate(self) -> ExtendAction: self.result = None - police_force = cast(PoliceForceEntity, self.agent_info.get_myself()) + police_force = cast(PoliceForce, self.agent_info.get_myself()) if self._need_rest(police_force): target_entity_ids: list[EntityID] = [] @@ -193,7 +193,7 @@ def calculate(self) -> ExtendAction: return self - def _need_rest(self, police_force: PoliceForceEntity) -> bool: + def _need_rest(self, police_force: PoliceForce) -> bool: hp = police_force.get_hp() damage = police_force.get_damage() @@ -210,7 +210,7 @@ def _need_rest(self, police_force: PoliceForceEntity) -> bool: def _calc_rest( self, - police_force: PoliceForceEntity, + police_force: PoliceForce, path_planning: PathPlanning, target_entity_ids: list[EntityID], ) -> Optional[Action]: @@ -246,7 +246,7 @@ def _calc_rest( return ActionMove(first_result) if first_result != [] else None def _get_rescue_action( - self, police_entity: PoliceForceEntity, road: Road + self, police_entity: PoliceForce, road: Road ) -> Optional[Action]: blockades = set( [] @@ -257,9 +257,7 @@ def _get_rescue_action( ] ) agent_entities = set( - self.world_info.get_entities_of_types( - [AmbulanceTeamEntity, FireBrigadeEntity] - ) + self.world_info.get_entities_of_types([AmbulanceTeam, FireBrigade]) ) police_x = police_entity.get_x() @@ -462,7 +460,7 @@ def _get_intersect_edge_action( return action_move action = self._get_area_clear_action( - cast(PoliceForceEntity, self.agent_info.get_myself()), road + cast(PoliceForce, self.agent_info.get_myself()), road ) if action is None: action = ActionMove([road.get_id()], int(point_x), int(point_y)) @@ -560,7 +558,7 @@ def _is_intersecting_blockades( return False def _get_area_clear_action( - self, police_entity: PoliceForceEntity, road: Road + self, police_entity: PoliceForce, road: Road ) -> Optional[Action]: if road.get_blockades() == []: return None @@ -631,7 +629,7 @@ def _index_of(self, list: list[EntityID], x: EntityID) -> int: return list.index(x) if x in list else -1 def _get_neighbour_position_action( - self, police_entity: PoliceForceEntity, target: Area + self, police_entity: PoliceForce, target: Area ) -> Optional[Action]: agent_x = police_entity.get_x() agent_y = police_entity.get_y() diff --git a/adf_core_python/implement/action/default_extend_action_rescue.py b/adf_core_python/implement/action/default_extend_action_rescue.py index 67f4ff3..eb96543 100644 --- a/adf_core_python/implement/action/default_extend_action_rescue.py +++ b/adf_core_python/implement/action/default_extend_action_rescue.py @@ -2,7 +2,7 @@ from rcrs_core.entities.area import Area from rcrs_core.entities.blockade import Blockade -from rcrs_core.entities.fireBrigade import FireBrigadeEntity +from rcrs_core.entities.fireBrigade import FireBrigade from rcrs_core.entities.human import Human from rcrs_core.worldmodel.entityID import EntityID @@ -104,7 +104,7 @@ def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: def calculate(self) -> ExtendAction: self.result = None - agent = cast(FireBrigadeEntity, self.agent_info.get_myself()) + agent = cast(FireBrigade, self.agent_info.get_myself()) if self._target_entity_id is not None: self.result = self._calc_rescue( @@ -115,7 +115,7 @@ def calculate(self) -> ExtendAction: def _calc_rescue( self, - agent: FireBrigadeEntity, + agent: FireBrigade, path_planning: PathPlanning, target_entity_id: EntityID, ) -> Optional[Action]: diff --git a/adf_core_python/implement/action/default_extend_action_transport.py b/adf_core_python/implement/action/default_extend_action_transport.py index a84b859..1ee2fd4 100644 --- a/adf_core_python/implement/action/default_extend_action_transport.py +++ b/adf_core_python/implement/action/default_extend_action_transport.py @@ -1,6 +1,6 @@ from typing import Optional, Union, cast -from rcrs_core.entities.ambulanceTeam import AmbulanceTeamEntity +from rcrs_core.entities.ambulanceTeam import AmbulanceTeam from rcrs_core.entities.area import Area from rcrs_core.entities.civilian import Civilian from rcrs_core.entities.entity import Entity @@ -102,9 +102,7 @@ def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: def calculate(self) -> ExtendAction: self._result = None - agent: AmbulanceTeamEntity = cast( - AmbulanceTeamEntity, self.agent_info.get_myself() - ) + agent: AmbulanceTeam = cast(AmbulanceTeam, self.agent_info.get_myself()) transport_human: Optional[Human] = self.agent_info.some_one_on_board() if transport_human is not None: self._logger.debug(f"transport_human: {transport_human.get_id()}") @@ -123,7 +121,7 @@ def calculate(self) -> ExtendAction: def calc_rescue( self, - agent: AmbulanceTeamEntity, + agent: AmbulanceTeam, path_planning: PathPlanning, target_id: EntityID, ) -> Optional[Union[ActionMove, ActionLoad]]: @@ -158,7 +156,7 @@ def calc_rescue( def calc_unload( self, - agent: AmbulanceTeamEntity, + agent: AmbulanceTeam, path_planning: PathPlanning, transport_human: Optional[Human], target_id: Optional[EntityID], diff --git a/adf_core_python/implement/module/algorithm/k_means_clustering.py b/adf_core_python/implement/module/algorithm/k_means_clustering.py index 0a432cb..b40f7c4 100644 --- a/adf_core_python/implement/module/algorithm/k_means_clustering.py +++ b/adf_core_python/implement/module/algorithm/k_means_clustering.py @@ -1,12 +1,12 @@ import numpy as np from rcrs_core.connection.URN import Entity as EntityURN -from rcrs_core.entities.ambulanceCenter import AmbulanceCentreEntity +from rcrs_core.entities.ambulanceCenter import AmbulanceCentre from rcrs_core.entities.building import Building from rcrs_core.entities.entity import Entity -from rcrs_core.entities.fireStation import FireStationEntity -from rcrs_core.entities.gassStation import GasStation +from rcrs_core.entities.fireStation import FireStation +from rcrs_core.entities.gasStation import GasStation from rcrs_core.entities.hydrant import Hydrant -from rcrs_core.entities.policeOffice import PoliceOfficeEntity +from rcrs_core.entities.policeOffice import PoliceOffice from rcrs_core.entities.refuge import Refuge from rcrs_core.entities.road import Road from rcrs_core.worldmodel.entityID import EntityID @@ -72,11 +72,11 @@ def __init__( self.cluster_entities: list[list[Entity]] = [] self.entities: list[Entity] = world_info.get_entities_of_types( [ - AmbulanceCentreEntity, - FireStationEntity, + AmbulanceCentre, + FireStation, GasStation, Hydrant, - PoliceOfficeEntity, + PoliceOffice, Refuge, Road, Building, diff --git a/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py b/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py index 1fc40f6..912638b 100644 --- a/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py +++ b/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py @@ -1,7 +1,7 @@ from functools import cmp_to_key from typing import Callable, Optional -from rcrs_core.entities.ambulanceTeam import AmbulanceTeamEntity +from rcrs_core.entities.ambulanceTeam import AmbulanceTeam from rcrs_core.entities.entity import Entity from rcrs_core.entities.human import Human from rcrs_core.worldmodel.entityID import EntityID @@ -40,9 +40,7 @@ def resume(self, precompute_data: PrecomputeData) -> AmbulanceTargetAllocator: super().resume(precompute_data) if self.get_count_resume() >= 2: return self - for entity_id in self._world_info.get_entity_ids_of_types( - [AmbulanceTeamEntity] - ): + for entity_id in self._world_info.get_entity_ids_of_types([AmbulanceTeam]): self._ambulance_team_info_map[entity_id] = self.AmbulanceTeamInfo(entity_id) return self @@ -50,9 +48,7 @@ def prepare(self) -> AmbulanceTargetAllocator: super().prepare() if self.get_count_prepare() >= 2: return self - for entity_id in self._world_info.get_entity_ids_of_types( - [AmbulanceTeamEntity] - ): + for entity_id in self._world_info.get_entity_ids_of_types([AmbulanceTeam]): self._ambulance_team_info_map[entity_id] = self.AmbulanceTeamInfo(entity_id) return self @@ -113,9 +109,9 @@ def get_result(self) -> dict[EntityID, EntityID]: def _get_action_agents( self, info_map: dict[EntityID, "DefaultAmbulanceTargetAllocator.AmbulanceTeamInfo"], - ) -> list[AmbulanceTeamEntity]: + ) -> list[AmbulanceTeam]: result = [] - for entity in self._world_info.get_entities_of_types([AmbulanceTeamEntity]): + for entity in self._world_info.get_entities_of_types([AmbulanceTeam]): info = info_map[entity.get_id()] if info is not None and info._can_new_action: result.append(entity) diff --git a/adf_core_python/implement/module/complex/default_fire_target_allocator.py b/adf_core_python/implement/module/complex/default_fire_target_allocator.py index aaf96b3..56711c1 100644 --- a/adf_core_python/implement/module/complex/default_fire_target_allocator.py +++ b/adf_core_python/implement/module/complex/default_fire_target_allocator.py @@ -2,7 +2,7 @@ from typing import Callable, Optional from rcrs_core.entities.entity import Entity -from rcrs_core.entities.fireBrigade import FireBrigadeEntity +from rcrs_core.entities.fireBrigade import FireBrigade from rcrs_core.entities.human import Human from rcrs_core.worldmodel.entityID import EntityID @@ -40,7 +40,7 @@ def resume(self, precompute_data: PrecomputeData) -> FireTargetAllocator: super().resume(precompute_data) if self.get_count_resume() >= 2: return self - for entity_id in self._world_info.get_entity_ids_of_types([FireBrigadeEntity]): + for entity_id in self._world_info.get_entity_ids_of_types([FireBrigade]): self._fire_brigade_info_map[entity_id] = self.FireBrigadeInfo(entity_id) return self @@ -48,7 +48,7 @@ def prepare(self) -> FireTargetAllocator: super().prepare() if self.get_count_prepare() >= 2: return self - for entity_id in self._world_info.get_entity_ids_of_types([FireBrigadeEntity]): + for entity_id in self._world_info.get_entity_ids_of_types([FireBrigade]): self._fire_brigade_info_map[entity_id] = self.FireBrigadeInfo(entity_id) return self @@ -108,9 +108,9 @@ def get_result(self) -> dict[EntityID, EntityID]: def _get_action_agents( self, info_map: dict[EntityID, "DefaultFireTargetAllocator.FireBrigadeInfo"] - ) -> list[FireBrigadeEntity]: + ) -> list[FireBrigade]: result = [] - for entity in self._world_info.get_entities_of_types([FireBrigadeEntity]): + for entity in self._world_info.get_entities_of_types([FireBrigade]): info = info_map[entity.get_id()] if info is not None and info._can_new_action: result.append(entity) diff --git a/adf_core_python/implement/module/complex/default_police_target_allocator.py b/adf_core_python/implement/module/complex/default_police_target_allocator.py index 72e65e1..d88e545 100644 --- a/adf_core_python/implement/module/complex/default_police_target_allocator.py +++ b/adf_core_python/implement/module/complex/default_police_target_allocator.py @@ -4,7 +4,7 @@ from rcrs_core.entities.building import Building from rcrs_core.entities.entity import Entity from rcrs_core.entities.gassStation import GasStation -from rcrs_core.entities.policeForce import PoliceForceEntity +from rcrs_core.entities.policeForce import PoliceForce from rcrs_core.entities.refuge import Refuge from rcrs_core.entities.road import Road from rcrs_core.worldmodel.entityID import EntityID @@ -44,7 +44,7 @@ def resume(self, precompute_data: PrecomputeData) -> PoliceTargetAllocator: if self.get_count_resume() >= 2: return self - for entity_id in self._world_info.get_entity_ids_of_types([PoliceForceEntity]): + for entity_id in self._world_info.get_entity_ids_of_types([PoliceForce]): self._agent_info_map[entity_id] = self.PoliceForceInfo(entity_id) for entity in self._world_info.get_entities_of_types( [Refuge, Building, GasStation] @@ -68,7 +68,7 @@ def prepare(self) -> PoliceTargetAllocator: if self.get_count_prepare() >= 2: return self - for entity_id in self._world_info.get_entity_ids_of_types([PoliceForceEntity]): + for entity_id in self._world_info.get_entity_ids_of_types([PoliceForce]): self._agent_info_map[entity_id] = self.PoliceForceInfo(entity_id) for entity in self._world_info.get_entities_of_types( @@ -143,9 +143,9 @@ def get_result(self) -> dict[EntityID, EntityID]: def _get_action_agents( self, info_map: dict[EntityID, "DefaultPoliceTargetAllocator.PoliceForceInfo"] - ) -> list[PoliceForceEntity]: + ) -> list[PoliceForce]: result = [] - for entity in self._world_info.get_entities_of_types([PoliceForceEntity]): + for entity in self._world_info.get_entities_of_types([PoliceForce]): info = info_map[entity.get_id()] if info is not None and info._can_new_action: result.append(entity) diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 857d724..888caf2 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -1,6 +1,6 @@ from typing import cast -from rcrs_core.entities.ambulanceTeam import AmbulanceTeamEntity +from rcrs_core.entities.ambulanceTeam import AmbulanceTeam from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.action.common.action_rest import ActionRest @@ -116,7 +116,7 @@ def think( self.reset_count() self.module_update_info(message_manager) - agent: AmbulanceTeamEntity = cast(AmbulanceTeamEntity, agent_info.get_myself()) # noqa: F841 + agent: AmbulanceTeam = cast(AmbulanceTeam, agent_info.get_myself()) # noqa: F841 entity_id = agent_info.get_entity_id() # noqa: F841 self._logger.debug( diff --git a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py index d421ac6..4247b48 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py @@ -1,6 +1,6 @@ from typing import cast -from rcrs_core.entities.fireBrigade import FireBrigadeEntity +from rcrs_core.entities.fireBrigade import FireBrigade from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.action.common.action_rest import ActionRest @@ -117,7 +117,7 @@ def think( self.reset_count() self.module_update_info(message_manager) - agent: FireBrigadeEntity = cast(FireBrigadeEntity, agent_info.get_myself()) # noqa: F841 + agent: FireBrigade = cast(FireBrigade, agent_info.get_myself()) # noqa: F841 entity_id = agent_info.get_entity_id() # noqa: F841 target_entity_id = self._human_detector.calculate().get_target_entity_id() diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py index 4f8bb89..d605cba 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -1,6 +1,6 @@ from typing import cast -from rcrs_core.entities.policeForce import PoliceForceEntity +from rcrs_core.entities.policeForce import PoliceForce from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.action.common.action_rest import ActionRest @@ -120,7 +120,7 @@ def think( self.reset_count() self.module_update_info(message_manager) - agent: PoliceForceEntity = cast(PoliceForceEntity, agent_info.get_myself()) # noqa: F841 + agent: PoliceForce = cast(PoliceForce, agent_info.get_myself()) # noqa: F841 entity_id = agent_info.get_entity_id() # noqa: F841 target_entity_id = self._road_detector.calculate().get_target_entity_id() From da037c0ce7dcd4b855b5af7fd3eab467cc62e3f6 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 15 Nov 2024 18:09:34 +0900 Subject: [PATCH 123/249] feat: add CLI template and configuration files for agent team setup --- adf_core_python/cli/__init__.py | 0 adf_core_python/cli/cli.py | 53 ++++++ .../cli/template/config/development.json | 3 + .../cli/template/config/launcher.yaml | 35 ++++ .../cli/template/config/module.yaml | 113 +++++++++++++ .../cli/template/src//__init__.py | 0 .../src//module/__init__.py | 0 .../complex/_human_detector.py | 149 +++++++++++++++++ .../complex/_road_detector.py | 158 ++++++++++++++++++ .../module/complex/_search.py | 106 ++++++++++++ ...//module/complex/__init__.py | 0 config/module.yaml | 2 - poetry.lock | 16 +- pyproject.toml | 18 +- 14 files changed, 643 insertions(+), 10 deletions(-) create mode 100644 adf_core_python/cli/__init__.py create mode 100644 adf_core_python/cli/cli.py create mode 100644 adf_core_python/cli/template/config/development.json create mode 100644 adf_core_python/cli/template/config/launcher.yaml create mode 100644 adf_core_python/cli/template/config/module.yaml create mode 100644 adf_core_python/cli/template/src//__init__.py create mode 100644 adf_core_python/cli/template/src//module/__init__.py create mode 100644 adf_core_python/cli/template/src//module/complex/_human_detector.py create mode 100644 adf_core_python/cli/template/src//module/complex/_road_detector.py create mode 100644 adf_core_python/cli/template/src//module/complex/_search.py create mode 100644 adf_core_python/cli/template/src//module/complex/__init__.py diff --git a/adf_core_python/cli/__init__.py b/adf_core_python/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/cli/cli.py b/adf_core_python/cli/cli.py new file mode 100644 index 0000000..acf3c48 --- /dev/null +++ b/adf_core_python/cli/cli.py @@ -0,0 +1,53 @@ +import os + +import click + +LOWER_NAME_PLACEHOLDER = "" +UPPER_NAME_PLACEHOLDER = "" + + +@click.command() +@click.option( + "--name", prompt="Your agent team name", help="The name of your agent team" +) +def main(name): + # load template dir and create a new agent team + click.echo(f"Creating a new agent team with name: {name}") + lower_name = name.lower() + upper_name = name.upper() + # 自身がいるディレクトリを取得 + template_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "template") + click.echo(f"Current file path: {template_dir}") + # コマンドラインのカレントディレクトリを取得 + current_dir = os.getcwd() + click.echo(f"Current directory: {current_dir}") + + _copy_template(template_dir, current_dir, lower_name, upper_name) + + +def _copy_template(src, dest, lower_name, upper_name): + if os.path.isdir(src): + if not os.path.exists(dest): + os.makedirs(dest) + for item in os.listdir(src): + s = os.path.join(src, item) + d = os.path.join( + dest, + item.replace(LOWER_NAME_PLACEHOLDER, lower_name).replace( + UPPER_NAME_PLACEHOLDER, upper_name + ), + ) + _copy_template(s, d, lower_name, upper_name) + else: + with open(src, "r") as f: + content = f.read() + with open(dest, "w") as f: + f.write( + content.replace(LOWER_NAME_PLACEHOLDER, lower_name).replace( + UPPER_NAME_PLACEHOLDER, upper_name + ) + ) + + +if __name__ == "__main__": + main() diff --git a/adf_core_python/cli/template/config/development.json b/adf_core_python/cli/template/config/development.json new file mode 100644 index 0000000..d0ae716 --- /dev/null +++ b/adf_core_python/cli/template/config/development.json @@ -0,0 +1,3 @@ +{ + "test": "test" +} diff --git a/adf_core_python/cli/template/config/launcher.yaml b/adf_core_python/cli/template/config/launcher.yaml new file mode 100644 index 0000000..35fbb75 --- /dev/null +++ b/adf_core_python/cli/template/config/launcher.yaml @@ -0,0 +1,35 @@ +kernel: + host: localhost + port: 27931 + +team: + name: + +adf: + launcher: + precompute: 0 + debug: + flag: 0 + agent: + moduleconfig: + filename: config/module.yaml + + develop: + flag: 1 + filename: config/development.json + + team: + platoon: + ambulance: + count: 1 + fire: + count: 0 + police: + count: 0 + office: + ambulance: + count: -1 + fire: + count: -1 + police: + count: -1 diff --git a/adf_core_python/cli/template/config/module.yaml b/adf_core_python/cli/template/config/module.yaml new file mode 100644 index 0000000..dd85c56 --- /dev/null +++ b/adf_core_python/cli/template/config/module.yaml @@ -0,0 +1,113 @@ +## DefaultTacticsAmbulanceTeam +DefaultTacticsAmbulanceTeam: + HumanDetector: .module.complex._human_detector.HumanDetector + Search: .module.complex._search.Search + ExtendActionTransport: adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport + ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove + CommandExecutorAmbulance: adf_core_python.implement.centralized.DefaultCommandExecutorAmbulance + CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout + +# ## DefaultTacticsFireBrigade +DefaultTacticsFireBrigade: + HumanDetector: .module.complex._human_detector.HumanDetector + Search: .module.complex._search.Search + ExtendActionRescue: adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue + ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove + CommandExecutorFire: adf_core_python.implement.centralized.DefaultCommandExecutorFire + CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout + +# ## DefaultTacticsPoliceForce +DefaultTacticsPoliceForce: + RoadDetector: .module.complex._road_detector.RoadDetector + Search: .module.complex._search.Search + ExtendActionClear: adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear + ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove + CommandExecutorPolice: adf_core_python.implement.centralized.DefaultCommandExecutorPolice + CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScoutPolice + +# ## DefaultTacticsAmbulanceCentre +# DefaultTacticsAmbulanceCentre: +# TargetAllocator: sample_team.module.complex.SampleAmbulanceTargetAllocator +# CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerAmbulance + +# ## DefaultTacticsFireStation +# DefaultTacticsFireStation: +# TargetAllocator: sample_team.module.complex.SampleFireTargetAllocator +# CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerFire + +# ## DefaultTacticsPoliceOffice +# DefaultTacticsPoliceOffice: +# TargetAllocator: sample_team.module.complex.SamplePoliceTargetAllocator +# CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerPolice + +## SampleSearch +SampleSearch: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning + Clustering: adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering + +# ## SampleBuildDetector +# SampleBuildingDetector: +# Clustering: adf_core_python.implement.module.algorithm.KMeansClustering + +# ## SampleRoadDetector +# SampleRoadDetector: +# Clustering: adf_core_python.implement.module.algorithm.KMeansClustering +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + +# ## SampleHumanDetector +# SampleHumanDetector: +# Clustering: adf_core_python.implement.module.algorithm.KMeansClustering + +# ## DefaultExtendActionClear +# DefaultExtendActionClear: +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + +# ## DefaultExtendActionFireFighting +# DefaultExtendActionFireFighting: +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + +# ## DefaultExtendActionRescue +# DefaultExtendActionRescue: +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + +# ## DefaultExtendActionMove +# DefaultExtendActionMove: +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + +# ## DefaultExtendActionTransport +# DefaultExtendActionTransport: +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# ## DefaultCommandExecutorAmbulance +# DefaultCommandExecutorAmbulance: +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# ExtendActionTransport: adf_core_python.implement.action.DefaultExtendActionTransport +# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove + +# ## DefaultCommandExecutorFire +# DefaultCommandExecutorFire: +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# EtxActionFireRescue: adf_core_python.implement.action.DefaultExtendActionRescue +# EtxActionFireFighting: adf_core_python.implement.action.DefaultExtendActionFireFighting +# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove + +# ## DefaultCommandExecutorPolice +# DefaultCommandExecutorPolice: +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear +# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove + +# ## DefaultCommandExecutorScout +# DefaultCommandExecutorScout: +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning + +# ## DefaultCommandExecutorScoutPolice +# DefaultCommandExecutorScoutPolice: +# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear + +# ## MessageManager +MessageManager: + PlatoonChannelSubscriber: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber + CenterChannelSubscriber: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber + PlatoonMessageCoordinator: adf_core_python.implement.module.communication.default_message_coordinator.DefaultMessageCoordinator + CenterMessageCoordinator: adf_core_python.implement.module.communication.default_message_coordinator.DefaultMessageCoordinator diff --git a/adf_core_python/cli/template/src//__init__.py b/adf_core_python/cli/template/src//__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/cli/template/src//module/__init__.py b/adf_core_python/cli/template/src//module/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/cli/template/src//module/complex/_human_detector.py b/adf_core_python/cli/template/src//module/complex/_human_detector.py new file mode 100644 index 0000000..4720226 --- /dev/null +++ b/adf_core_python/cli/template/src//module/complex/_human_detector.py @@ -0,0 +1,149 @@ +from typing import Optional, cast + +from rcrs_core.connection.URN import Entity as EntityURN +from rcrs_core.entities.civilian import Civilian +from rcrs_core.entities.entity import Entity +from rcrs_core.entities.human import Human +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.module.algorithm.clustering import Clustering +from adf_core_python.core.component.module.complex.human_detector import HumanDetector +from adf_core_python.core.logger.logger import get_agent_logger + + +class DefaultHumanDetector(HumanDetector): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._clustering: Clustering = cast( + Clustering, + module_manager.get_module( + "DefaultHumanDetector.Clustering", + "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", + ), + ) + self.register_sub_module(self._clustering) + + self._result: Optional[EntityID] = None + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + + def calculate(self) -> HumanDetector: + transport_human: Optional[Human] = self._agent_info.some_one_on_board() + if transport_human is not None: + self._result = transport_human.get_id() + return self + + if self._result is not None: + if not self._is_valid_human(self._result): + self._result = None + + if self._result is None: + self._result = self._select_target() + + return self + + def _select_target(self) -> Optional[EntityID]: + if self._result is not None and self._is_valid_human(self._result): + return self._result + + cluster_index: int = self._clustering.get_cluster_index( + self._agent_info.get_entity_id() + ) + cluster_entities: list[Entity] = self._clustering.get_cluster_entities( + cluster_index + ) + + cluster_valid_human_entities: list[Entity] = [ + entity + for entity in cluster_entities + if self._is_valid_human(entity.get_id()) and isinstance(entity, Civilian) + ] + if len(cluster_valid_human_entities) != 0: + nearest_human_entity = cluster_valid_human_entities[0] + nearest_distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), + nearest_human_entity.get_id(), + ) + for entity in cluster_valid_human_entities: + distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), + entity.get_id(), + ) + if distance < nearest_distance: + nearest_distance = distance + nearest_human_entity = entity + return nearest_human_entity.get_id() + + world_valid_human_entities: list[Entity] = [ + entity + for entity in self._world_info.get_entities_of_types([Civilian]) + if self._is_valid_human(entity.get_id()) + ] + if len(world_valid_human_entities) != 0: + nearest_human_entity = world_valid_human_entities[0] + nearest_distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), + nearest_human_entity.get_id(), + ) + for entity in world_valid_human_entities: + distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), + entity.get_id(), + ) + if distance < nearest_distance: + nearest_distance = distance + nearest_human_entity = entity + return nearest_human_entity.get_id() + + return None + + def _is_valid_human(self, target_entity_id: EntityID) -> bool: + target: Optional[Entity] = self._world_info.get_entity(target_entity_id) + if target is None: + return False + if not isinstance(target, Human): + return False + hp: Optional[int] = target.get_hp() + if hp is None or hp <= 0: + return False + buriedness: Optional[int] = target.get_buriedness() + if buriedness is None: + return False + myself = self._agent_info.get_myself() + if myself.get_urn() == EntityURN.FIRE_BRIGADE and buriedness == 0: + return False + if myself.get_urn() == EntityURN.AMBULANCE_TEAM and buriedness > 0: + return False + damage: Optional[int] = target.get_damage() + if damage is None or damage == 0: + return False + position_entity_id: Optional[EntityID] = target.get_position() + if position_entity_id is None: + return False + position: Optional[Entity] = self._world_info.get_entity(position_entity_id) + if position is None: + return False + urn: EntityURN = position.get_urn() + if urn == EntityURN.REFUGE or urn == EntityURN.AMBULANCE_TEAM: + return False + + return True + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._result diff --git a/adf_core_python/cli/template/src//module/complex/_road_detector.py b/adf_core_python/cli/template/src//module/complex/_road_detector.py new file mode 100644 index 0000000..adad2d8 --- /dev/null +++ b/adf_core_python/cli/template/src//module/complex/_road_detector.py @@ -0,0 +1,158 @@ +from typing import Optional, cast + +from rcrs_core.entities.building import Building +from rcrs_core.entities.gassStation import GasStation +from rcrs_core.entities.refuge import Refuge +from rcrs_core.entities.road import Road +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.module.algorithm.path_planning import ( + PathPlanning, +) +from adf_core_python.core.component.module.complex.road_detector import RoadDetector + + +class DefaultRoadDetector(RoadDetector): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + match scenario_info.get_mode(): + case Mode.NON_PRECOMPUTE: + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultRoadDetector.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + + self.register_sub_module(self._path_planning) + self._result = None + + def precompute(self, precompute_data: PrecomputeData) -> RoadDetector: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> RoadDetector: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + + self._target_areas: set[EntityID] = set() + entities = self._world_info.get_entities_of_types( + [Refuge, Building, GasStation] + ) + for entity in entities: + if not isinstance(entity, Building): + continue + for entity_id in entity.get_neighbours(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._target_areas.add(entity_id) + + self._priority_roads = set() + for entity in self._world_info.get_entities_of_types([Refuge]): + if not isinstance(entity, Building): + continue + for entity_id in entity.get_neighbours(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._priority_roads.add(entity_id) + + return self + + def prepare(self) -> RoadDetector: + super().prepare() + if self.get_count_prepare() >= 2: + return self + + self._target_areas = set() + entities = self._world_info.get_entities_of_types( + [Refuge, Building, GasStation] + ) + for entity in entities: + building: Building = cast(Building, entity) + for entity_id in building.get_neighbours(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._target_areas.add(entity_id) + + self._priority_roads = set() + for entity in self._world_info.get_entities_of_types([Refuge]): + refuge: Refuge = cast(Refuge, entity) + for entity_id in refuge.get_neighbours(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._priority_roads.add(entity_id) + + return self + + def update_info(self, message_manager: MessageManager) -> RoadDetector: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self + + if self._result is not None: + if self._agent_info.get_position_entity_id == self._result: + entity = self._world_info.get_entity(self._result) + if isinstance(entity, Building): + self._result = None + elif isinstance(entity, Road): + road: Road = cast(Road, entity) + if road.get_blockades() == []: + self._target_areas.remove(self._result) + self._result = None + + return self + + def calculate(self) -> RoadDetector: + if self._result is None: + position_entity_id: EntityID = self._agent_info.get_position_entity_id() + if position_entity_id in self._target_areas: + self._result = position_entity_id + return self + remove_list = [] + for entity_id in self._priority_roads: + if entity_id not in self._target_areas: + remove_list.append(entity_id) + + self._priority_roads = self._priority_roads - set(remove_list) + if len(self._priority_roads) > 0: + _nearest_target_area = self._agent_info.get_position_entity_id() + _nearest_distance = float("inf") + for target_area in self._target_areas: + if ( + self._world_info.get_distance( + self._agent_info.get_position_entity_id(), target_area + ) + < _nearest_distance + ): + _nearest_target_area = target_area + _nearest_distance = self._world_info.get_distance( + self._agent_info.get_position_entity_id(), target_area + ) + path: list[EntityID] = self._path_planning.get_path( + self._agent_info.get_position_entity_id(), _nearest_target_area + ) + if path is not None and len(path) > 0: + self._result = path[-1] + + return self + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._result diff --git a/adf_core_python/cli/template/src//module/complex/_search.py b/adf_core_python/cli/template/src//module/complex/_search.py new file mode 100644 index 0000000..05d7c88 --- /dev/null +++ b/adf_core_python/cli/template/src//module/complex/_search.py @@ -0,0 +1,106 @@ +from typing import Optional, cast + +from rcrs_core.entities.building import Building +from rcrs_core.entities.entity import Entity +from rcrs_core.entities.refuge import Refuge +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.module.algorithm.clustering import Clustering +from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning +from adf_core_python.core.component.module.complex.search import Search +from adf_core_python.core.logger.logger import get_agent_logger + + +class DefaultSearch(Search): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + self._unreached_building_ids: set[EntityID] = set() + self._result: Optional[EntityID] = None + + self._clustering: Clustering = cast( + Clustering, + module_manager.get_module( + "DefaultSearch.Clustering", + "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", + ), + ) + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultSearch.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + + self.register_sub_module(self._clustering) + self.register_sub_module(self._path_planning) + + def update_info(self, message_manager: MessageManager) -> Search: + super().update_info(message_manager) + if self.get_count_update_info() > 1: + return self + + self._logger.debug( + f"unreached_building_ids: {[str(id) for id in self._unreached_building_ids]}" + ) + + searched_building_id = self._agent_info.get_position_entity_id() + self._unreached_building_ids.discard(searched_building_id) + + if len(self._unreached_building_ids) == 0: + self._unreached_building_ids = self._get_search_targets() + + return self + + def calculate(self) -> Search: + nearest_building_id: Optional[EntityID] = None + nearest_distance: Optional[float] = None + for building_id in self._unreached_building_ids: + distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), building_id + ) + if nearest_distance is None or distance < nearest_distance: + nearest_building_id = building_id + nearest_distance = distance + self._result = nearest_building_id + return self + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._result + + def _get_search_targets(self) -> set[EntityID]: + cluster_index: int = self._clustering.get_cluster_index( + self._agent_info.get_entity_id() + ) + cluster_entities: list[Entity] = self._clustering.get_cluster_entities( + cluster_index + ) + building_entity_ids: list[EntityID] = [ + entity.get_id() + for entity in cluster_entities + if isinstance(entity, Building) and not isinstance(entity, Refuge) + ] + + return set(building_entity_ids) diff --git a/adf_core_python/cli/template/src//module/complex/__init__.py b/adf_core_python/cli/template/src//module/complex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/module.yaml b/config/module.yaml index b4fbba1..958fbc7 100644 --- a/config/module.yaml +++ b/config/module.yaml @@ -111,5 +111,3 @@ MessageManager: CenterChannelSubscriber: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber PlatoonMessageCoordinator: adf_core_python.implement.module.communication.default_message_coordinator.DefaultMessageCoordinator CenterMessageCoordinator: adf_core_python.implement.module.communication.default_message_coordinator.DefaultMessageCoordinator -# ## VisualDebug -# VisualDebug: true diff --git a/poetry.lock b/poetry.lock index c1d9ac7..d30279b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -146,6 +146,20 @@ files = [ {file = "bitarray-3.0.0.tar.gz", hash = "sha256:a2083dc20f0d828a7cdf7a16b20dae56aab0f43dc4f347a3b3039f6577992b03"}, ] +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.6" @@ -782,4 +796,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "6e8244db0f75c542b50338580672c5e11b1cdfe38b0a62d01b757b31d4eddaf0" +content-hash = "62d5fde0d4a35f29aa7f21a31f5c1f4679c7a576c0f57c0363ec0b0cd6dc9e91" diff --git a/pyproject.toml b/pyproject.toml index 35dee4b..ba652af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,13 +5,13 @@ description = "Agent Development Framework for Python" authors = [ "Haruki Uehara ", "Yuki Shimada ", - ] +] readme = "README.md" package-mode = true [tool.poetry.dependencies] python = "^3.12" -rcrs_core = {git = "https://github.com/adf-python/rcrs-core-python"} +rcrs_core = { git = "https://github.com/adf-python/rcrs-core-python" } pyyaml = "^6.0.2" pytest = "^8.3.2" types-pyyaml = "^6.0.12.20240808" @@ -19,6 +19,7 @@ scikit-learn = "^1.5.2" structlog = "^24.4.0" bitarray = "^3.0.0" shapely = "^2.0.6" +click = "^8.1.7" [tool.poetry.group.dev.dependencies] @@ -31,6 +32,9 @@ ruff = "^0.4.4" requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" +[tool.script] +adf-core-python = "adf_core_python.cli.cli:main" + [tool.taskipy.tasks] format = "ruff format ." lint = "ruff check ." @@ -120,9 +124,9 @@ docstring-code-line-length = "dynamic" [tool.mypy] ignore_missing_imports = true -show_error_context = true # エラー時のメッセージを詳細表示 -show_column_numbers = true # エラー発生箇所の行数/列数を表示 +show_error_context = true # エラー時のメッセージを詳細表示 +show_column_numbers = true # エラー発生箇所の行数/列数を表示 disallow_untyped_defs = true # 関数定義の引数/戻り値に型アノテーション必須 -no_implicit_optional = true # デフォルト引数に None を取る場合型アノテーションに Optional 必須 -check_untyped_defs = true # 型注釈がない関数やメソッドに対して型チェックを行う -warn_redundant_casts = true # 冗長なキャストに警告 +no_implicit_optional = true # デフォルト引数に None を取る場合型アノテーションに Optional 必須 +check_untyped_defs = true # 型注釈がない関数やメソッドに対して型チェックを行う +warn_redundant_casts = true # 冗長なキャストに警告 From 68f2be064d32f2ded650454a5b9844d2f4e7414e Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 15 Nov 2024 18:20:16 +0900 Subject: [PATCH 124/249] refactor: rename main function to cli and update script entry point in pyproject.toml --- adf_core_python/cli/cli.py | 6 +----- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/adf_core_python/cli/cli.py b/adf_core_python/cli/cli.py index acf3c48..23a5146 100644 --- a/adf_core_python/cli/cli.py +++ b/adf_core_python/cli/cli.py @@ -10,7 +10,7 @@ @click.option( "--name", prompt="Your agent team name", help="The name of your agent team" ) -def main(name): +def cli(name): # load template dir and create a new agent team click.echo(f"Creating a new agent team with name: {name}") lower_name = name.lower() @@ -47,7 +47,3 @@ def _copy_template(src, dest, lower_name, upper_name): UPPER_NAME_PLACEHOLDER, upper_name ) ) - - -if __name__ == "__main__": - main() diff --git a/pyproject.toml b/pyproject.toml index ba652af..71f60d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,8 +32,8 @@ ruff = "^0.4.4" requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" -[tool.script] -adf-core-python = "adf_core_python.cli.cli:main" +[tool.poetry.scripts] +adf-core-python = "adf_core_python.cli.cli:cli" [tool.taskipy.tasks] format = "ruff format ." From 89b9fbbd91753f8e38db05831db41a0cf03b9bcf Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 15 Nov 2024 18:27:00 +0900 Subject: [PATCH 125/249] refactor: update placeholder names and file structure for team templates --- adf_core_python/cli/cli.py | 9 +++++++-- adf_core_python/cli/template/config/launcher.yaml | 2 +- adf_core_python/cli/template/config/module.yaml | 12 ++++++------ .../src/{ => team_name}/__init__.py | 0 .../{ => team_name}/module/__init__.py | 0 .../module/complex/__init__.py | 0 .../module/complex/team_name_human_detector.py} | 2 +- .../module/complex/team_name_road_detector.py} | 2 +- .../module/complex/team_name_search.py} | 2 +- 9 files changed, 17 insertions(+), 12 deletions(-) rename adf_core_python/cli/template/src/{ => team_name}/__init__.py (100%) rename adf_core_python/cli/template/src/{ => team_name}/module/__init__.py (100%) rename adf_core_python/cli/template/src/{ => team_name}/module/complex/__init__.py (100%) rename adf_core_python/cli/template/src/{/module/complex/_human_detector.py => team_name/module/complex/team_name_human_detector.py} (99%) rename adf_core_python/cli/template/src/{/module/complex/_road_detector.py => team_name/module/complex/team_name_road_detector.py} (99%) rename adf_core_python/cli/template/src/{/module/complex/_search.py => team_name/module/complex/team_name_search.py} (99%) diff --git a/adf_core_python/cli/cli.py b/adf_core_python/cli/cli.py index 23a5146..1031fa2 100644 --- a/adf_core_python/cli/cli.py +++ b/adf_core_python/cli/cli.py @@ -2,8 +2,8 @@ import click -LOWER_NAME_PLACEHOLDER = "" -UPPER_NAME_PLACEHOLDER = "" +LOWER_NAME_PLACEHOLDER = "team_name" +UPPER_NAME_PLACEHOLDER = "TeamName" @click.command() @@ -47,3 +47,8 @@ def _copy_template(src, dest, lower_name, upper_name): UPPER_NAME_PLACEHOLDER, upper_name ) ) + new_dest = dest.replace(LOWER_NAME_PLACEHOLDER, lower_name).replace( + UPPER_NAME_PLACEHOLDER, upper_name + ) + if new_dest != dest: + os.rename(dest, new_dest) diff --git a/adf_core_python/cli/template/config/launcher.yaml b/adf_core_python/cli/template/config/launcher.yaml index 35fbb75..4252585 100644 --- a/adf_core_python/cli/template/config/launcher.yaml +++ b/adf_core_python/cli/template/config/launcher.yaml @@ -3,7 +3,7 @@ kernel: port: 27931 team: - name: + name: team_name adf: launcher: diff --git a/adf_core_python/cli/template/config/module.yaml b/adf_core_python/cli/template/config/module.yaml index dd85c56..3f7467a 100644 --- a/adf_core_python/cli/template/config/module.yaml +++ b/adf_core_python/cli/template/config/module.yaml @@ -1,7 +1,7 @@ ## DefaultTacticsAmbulanceTeam DefaultTacticsAmbulanceTeam: - HumanDetector: .module.complex._human_detector.HumanDetector - Search: .module.complex._search.Search + HumanDetector: team_name.module.complex.team_name_human_detector.TeamNameHumanDetector + Search: team_name.module.complex.team_name_search.TeamNameSearch ExtendActionTransport: adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove CommandExecutorAmbulance: adf_core_python.implement.centralized.DefaultCommandExecutorAmbulance @@ -9,8 +9,8 @@ DefaultTacticsAmbulanceTeam: # ## DefaultTacticsFireBrigade DefaultTacticsFireBrigade: - HumanDetector: .module.complex._human_detector.HumanDetector - Search: .module.complex._search.Search + HumanDetector: team_name.module.complex.team_name_human_detector.TeamNameHumanDetector + Search: team_name.module.complex.team_name_search.TeamNameSearch ExtendActionRescue: adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove CommandExecutorFire: adf_core_python.implement.centralized.DefaultCommandExecutorFire @@ -18,8 +18,8 @@ DefaultTacticsFireBrigade: # ## DefaultTacticsPoliceForce DefaultTacticsPoliceForce: - RoadDetector: .module.complex._road_detector.RoadDetector - Search: .module.complex._search.Search + RoadDetector: team_name.module.complex.team_name_road_detector.TeamNameRoadDetector + Search: team_name.module.complex.team_name_search.TeamNameSearch ExtendActionClear: adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove CommandExecutorPolice: adf_core_python.implement.centralized.DefaultCommandExecutorPolice diff --git a/adf_core_python/cli/template/src//__init__.py b/adf_core_python/cli/template/src/team_name/__init__.py similarity index 100% rename from adf_core_python/cli/template/src//__init__.py rename to adf_core_python/cli/template/src/team_name/__init__.py diff --git a/adf_core_python/cli/template/src//module/__init__.py b/adf_core_python/cli/template/src/team_name/module/__init__.py similarity index 100% rename from adf_core_python/cli/template/src//module/__init__.py rename to adf_core_python/cli/template/src/team_name/module/__init__.py diff --git a/adf_core_python/cli/template/src//module/complex/__init__.py b/adf_core_python/cli/template/src/team_name/module/complex/__init__.py similarity index 100% rename from adf_core_python/cli/template/src//module/complex/__init__.py rename to adf_core_python/cli/template/src/team_name/module/complex/__init__.py diff --git a/adf_core_python/cli/template/src//module/complex/_human_detector.py b/adf_core_python/cli/template/src/team_name/module/complex/team_name_human_detector.py similarity index 99% rename from adf_core_python/cli/template/src//module/complex/_human_detector.py rename to adf_core_python/cli/template/src/team_name/module/complex/team_name_human_detector.py index 4720226..4cacc73 100644 --- a/adf_core_python/cli/template/src//module/complex/_human_detector.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/team_name_human_detector.py @@ -16,7 +16,7 @@ from adf_core_python.core.logger.logger import get_agent_logger -class DefaultHumanDetector(HumanDetector): +class TeamNameHumanDetector(HumanDetector): def __init__( self, agent_info: AgentInfo, diff --git a/adf_core_python/cli/template/src//module/complex/_road_detector.py b/adf_core_python/cli/template/src/team_name/module/complex/team_name_road_detector.py similarity index 99% rename from adf_core_python/cli/template/src//module/complex/_road_detector.py rename to adf_core_python/cli/template/src/team_name/module/complex/team_name_road_detector.py index adad2d8..222d717 100644 --- a/adf_core_python/cli/template/src//module/complex/_road_detector.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/team_name_road_detector.py @@ -19,7 +19,7 @@ from adf_core_python.core.component.module.complex.road_detector import RoadDetector -class DefaultRoadDetector(RoadDetector): +class TeamNameRoadDetector(RoadDetector): def __init__( self, agent_info: AgentInfo, diff --git a/adf_core_python/cli/template/src//module/complex/_search.py b/adf_core_python/cli/template/src/team_name/module/complex/team_name_search.py similarity index 99% rename from adf_core_python/cli/template/src//module/complex/_search.py rename to adf_core_python/cli/template/src/team_name/module/complex/team_name_search.py index 05d7c88..286cde1 100644 --- a/adf_core_python/cli/template/src//module/complex/_search.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/team_name_search.py @@ -17,7 +17,7 @@ from adf_core_python.core.logger.logger import get_agent_logger -class DefaultSearch(Search): +class TeamNameSearch(Search): def __init__( self, agent_info: AgentInfo, From 04565526f0e1e3c2169e3e3844a4b1542ace9969 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 20 Nov 2024 23:58:13 +0900 Subject: [PATCH 126/249] refactor: update placeholder names and class references in CLI templates --- adf_core_python/cli/cli.py | 37 ++++++++++--------- .../cli/template/config/module.yaml | 12 +++--- ...n_detector.py => sample_human_detector.py} | 2 +- ...ad_detector.py => sample_road_detector.py} | 2 +- .../{team_name_search.py => sample_search.py} | 2 +- 5 files changed, 28 insertions(+), 27 deletions(-) rename adf_core_python/cli/template/src/team_name/module/complex/{team_name_human_detector.py => sample_human_detector.py} (99%) rename adf_core_python/cli/template/src/team_name/module/complex/{team_name_road_detector.py => sample_road_detector.py} (99%) rename adf_core_python/cli/template/src/team_name/module/complex/{team_name_search.py => sample_search.py} (99%) diff --git a/adf_core_python/cli/cli.py b/adf_core_python/cli/cli.py index 1031fa2..853e360 100644 --- a/adf_core_python/cli/cli.py +++ b/adf_core_python/cli/cli.py @@ -2,8 +2,7 @@ import click -LOWER_NAME_PLACEHOLDER = "team_name" -UPPER_NAME_PLACEHOLDER = "TeamName" +NAME_PLACEHOLDER = "team_name" @click.command() @@ -13,8 +12,6 @@ def cli(name): # load template dir and create a new agent team click.echo(f"Creating a new agent team with name: {name}") - lower_name = name.lower() - upper_name = name.upper() # 自身がいるディレクトリを取得 template_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "template") click.echo(f"Current file path: {template_dir}") @@ -22,10 +19,18 @@ def cli(name): current_dir = os.getcwd() click.echo(f"Current directory: {current_dir}") - _copy_template(template_dir, current_dir, lower_name, upper_name) + _copy_template( + template_dir, + current_dir, + name, + ) -def _copy_template(src, dest, lower_name, upper_name): +def _copy_template( + src, + dest, + name, +): if os.path.isdir(src): if not os.path.exists(dest): os.makedirs(dest) @@ -33,22 +38,18 @@ def _copy_template(src, dest, lower_name, upper_name): s = os.path.join(src, item) d = os.path.join( dest, - item.replace(LOWER_NAME_PLACEHOLDER, lower_name).replace( - UPPER_NAME_PLACEHOLDER, upper_name - ), + item.replace(NAME_PLACEHOLDER, name), ) - _copy_template(s, d, lower_name, upper_name) + _copy_template(s, d, name) else: with open(src, "r") as f: content = f.read() with open(dest, "w") as f: - f.write( - content.replace(LOWER_NAME_PLACEHOLDER, lower_name).replace( - UPPER_NAME_PLACEHOLDER, upper_name - ) - ) - new_dest = dest.replace(LOWER_NAME_PLACEHOLDER, lower_name).replace( - UPPER_NAME_PLACEHOLDER, upper_name - ) + f.write(content.replace(NAME_PLACEHOLDER, name)) + new_dest = dest.replace(NAME_PLACEHOLDER, name) if new_dest != dest: os.rename(dest, new_dest) + + +if __name__ == "__main__": + cli() diff --git a/adf_core_python/cli/template/config/module.yaml b/adf_core_python/cli/template/config/module.yaml index 3f7467a..80a7b35 100644 --- a/adf_core_python/cli/template/config/module.yaml +++ b/adf_core_python/cli/template/config/module.yaml @@ -1,7 +1,7 @@ ## DefaultTacticsAmbulanceTeam DefaultTacticsAmbulanceTeam: - HumanDetector: team_name.module.complex.team_name_human_detector.TeamNameHumanDetector - Search: team_name.module.complex.team_name_search.TeamNameSearch + HumanDetector: team_name.module.complex.sample_human_detector.SampleHumanDetector + Search: team_name.module.complex.sample_search.SampleSearch ExtendActionTransport: adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove CommandExecutorAmbulance: adf_core_python.implement.centralized.DefaultCommandExecutorAmbulance @@ -9,8 +9,8 @@ DefaultTacticsAmbulanceTeam: # ## DefaultTacticsFireBrigade DefaultTacticsFireBrigade: - HumanDetector: team_name.module.complex.team_name_human_detector.TeamNameHumanDetector - Search: team_name.module.complex.team_name_search.TeamNameSearch + HumanDetector: team_name.module.complex.sample_human_detector.SampleHumanDetector + Search: team_name.module.complex.sample_search.SampleSearch ExtendActionRescue: adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove CommandExecutorFire: adf_core_python.implement.centralized.DefaultCommandExecutorFire @@ -18,8 +18,8 @@ DefaultTacticsFireBrigade: # ## DefaultTacticsPoliceForce DefaultTacticsPoliceForce: - RoadDetector: team_name.module.complex.team_name_road_detector.TeamNameRoadDetector - Search: team_name.module.complex.team_name_search.TeamNameSearch + RoadDetector: team_name.module.complex.sample_road_detector.SampleRoadDetector + Search: team_name.module.complex.sample_search.SampleSearch ExtendActionClear: adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove CommandExecutorPolice: adf_core_python.implement.centralized.DefaultCommandExecutorPolice diff --git a/adf_core_python/cli/template/src/team_name/module/complex/team_name_human_detector.py b/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py similarity index 99% rename from adf_core_python/cli/template/src/team_name/module/complex/team_name_human_detector.py rename to adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py index 4cacc73..c3b9587 100644 --- a/adf_core_python/cli/template/src/team_name/module/complex/team_name_human_detector.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py @@ -16,7 +16,7 @@ from adf_core_python.core.logger.logger import get_agent_logger -class TeamNameHumanDetector(HumanDetector): +class SampleHumanDetector(HumanDetector): def __init__( self, agent_info: AgentInfo, diff --git a/adf_core_python/cli/template/src/team_name/module/complex/team_name_road_detector.py b/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py similarity index 99% rename from adf_core_python/cli/template/src/team_name/module/complex/team_name_road_detector.py rename to adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py index 222d717..a46e67e 100644 --- a/adf_core_python/cli/template/src/team_name/module/complex/team_name_road_detector.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py @@ -19,7 +19,7 @@ from adf_core_python.core.component.module.complex.road_detector import RoadDetector -class TeamNameRoadDetector(RoadDetector): +class SampleRoadDetector(RoadDetector): def __init__( self, agent_info: AgentInfo, diff --git a/adf_core_python/cli/template/src/team_name/module/complex/team_name_search.py b/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py similarity index 99% rename from adf_core_python/cli/template/src/team_name/module/complex/team_name_search.py rename to adf_core_python/cli/template/src/team_name/module/complex/sample_search.py index 286cde1..d1bbe04 100644 --- a/adf_core_python/cli/template/src/team_name/module/complex/team_name_search.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py @@ -17,7 +17,7 @@ from adf_core_python.core.logger.logger import get_agent_logger -class TeamNameSearch(Search): +class SampleSearch(Search): def __init__( self, agent_info: AgentInfo, From a17f233a2d9f0ea99c70e9cbf507328eaec08a5f Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 21 Nov 2024 11:47:27 +0900 Subject: [PATCH 127/249] feat: update team configuration and add main launcher script for CLI --- adf_core_python/cli/template/config/launcher.yaml | 6 +++--- adf_core_python/cli/template/config/module.yaml | 12 ++++++------ adf_core_python/cli/template/main.py | 5 +++++ 3 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 adf_core_python/cli/template/main.py diff --git a/adf_core_python/cli/template/config/launcher.yaml b/adf_core_python/cli/template/config/launcher.yaml index 4252585..6782672 100644 --- a/adf_core_python/cli/template/config/launcher.yaml +++ b/adf_core_python/cli/template/config/launcher.yaml @@ -21,11 +21,11 @@ adf: team: platoon: ambulance: - count: 1 + count: 100 fire: - count: 0 + count: 100 police: - count: 0 + count: 100 office: ambulance: count: -1 diff --git a/adf_core_python/cli/template/config/module.yaml b/adf_core_python/cli/template/config/module.yaml index 80a7b35..dbf215b 100644 --- a/adf_core_python/cli/template/config/module.yaml +++ b/adf_core_python/cli/template/config/module.yaml @@ -1,7 +1,7 @@ ## DefaultTacticsAmbulanceTeam DefaultTacticsAmbulanceTeam: - HumanDetector: team_name.module.complex.sample_human_detector.SampleHumanDetector - Search: team_name.module.complex.sample_search.SampleSearch + HumanDetector: src.team_name.module.complex.sample_human_detector.SampleHumanDetector + Search: src.team_name.module.complex.sample_search.SampleSearch ExtendActionTransport: adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove CommandExecutorAmbulance: adf_core_python.implement.centralized.DefaultCommandExecutorAmbulance @@ -9,8 +9,8 @@ DefaultTacticsAmbulanceTeam: # ## DefaultTacticsFireBrigade DefaultTacticsFireBrigade: - HumanDetector: team_name.module.complex.sample_human_detector.SampleHumanDetector - Search: team_name.module.complex.sample_search.SampleSearch + HumanDetector: src.team_name.module.complex.sample_human_detector.SampleHumanDetector + Search: src.team_name.module.complex.sample_search.SampleSearch ExtendActionRescue: adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove CommandExecutorFire: adf_core_python.implement.centralized.DefaultCommandExecutorFire @@ -18,8 +18,8 @@ DefaultTacticsFireBrigade: # ## DefaultTacticsPoliceForce DefaultTacticsPoliceForce: - RoadDetector: team_name.module.complex.sample_road_detector.SampleRoadDetector - Search: team_name.module.complex.sample_search.SampleSearch + RoadDetector: src.team_name.module.complex.sample_road_detector.SampleRoadDetector + Search: src.team_name.module.complex.sample_search.SampleSearch ExtendActionClear: adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove CommandExecutorPolice: adf_core_python.implement.centralized.DefaultCommandExecutorPolice diff --git a/adf_core_python/cli/template/main.py b/adf_core_python/cli/template/main.py new file mode 100644 index 0000000..82ce430 --- /dev/null +++ b/adf_core_python/cli/template/main.py @@ -0,0 +1,5 @@ +from adf_core_python.launcher import Launcher + +if __name__ == "__main__": + launcher = Launcher("./config/launcher.yaml") + launcher.launch() From b80f8ce0ffcca6bd3267bdb9ad90d2f52d9ca2a1 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 21 Nov 2024 12:20:32 +0900 Subject: [PATCH 128/249] refactor: add type hints to CLI function and update parameter definitions --- adf_core_python/cli/cli.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/adf_core_python/cli/cli.py b/adf_core_python/cli/cli.py index 853e360..fe2b343 100644 --- a/adf_core_python/cli/cli.py +++ b/adf_core_python/cli/cli.py @@ -9,15 +9,13 @@ @click.option( "--name", prompt="Your agent team name", help="The name of your agent team" ) -def cli(name): +def cli(name: str) -> None: # load template dir and create a new agent team click.echo(f"Creating a new agent team with name: {name}") # 自身がいるディレクトリを取得 template_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "template") - click.echo(f"Current file path: {template_dir}") # コマンドラインのカレントディレクトリを取得 current_dir = os.getcwd() - click.echo(f"Current directory: {current_dir}") _copy_template( template_dir, @@ -27,10 +25,10 @@ def cli(name): def _copy_template( - src, - dest, - name, -): + src: str, + dest: str, + name: str, +) -> None: if os.path.isdir(src): if not os.path.exists(dest): os.makedirs(dest) From c3e4a537855540c114dace737207f13daf3a6fe0 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 21 Nov 2024 19:00:45 +0900 Subject: [PATCH 129/249] docs: add documentation section with link to ADF Core Python documentation --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 0125315..da12692 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # adf-core-python +## Documentation + +[ADF Core Python Documentation](https://adf-python.github.io/adf-core-python/) + ## Development Environment ### Prerequisites From 331326073de859e1acc9c54a48f1ecd72d270279 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 22 Nov 2024 15:31:59 +0900 Subject: [PATCH 130/249] feat: add logging for missing module keys in ModuleManager --- adf_core_python/core/agent/module/module_manager.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/adf_core_python/core/agent/module/module_manager.py b/adf_core_python/core/agent/module/module_manager.py index 7609e62..9a6d35d 100644 --- a/adf_core_python/core/agent/module/module_manager.py +++ b/adf_core_python/core/agent/module/module_manager.py @@ -11,6 +11,7 @@ MessageCoordinator, ) from adf_core_python.core.component.module.abstract_module import AbstractModule +from adf_core_python.core.logger.logger import get_logger if TYPE_CHECKING: from adf_core_python.core.agent.config.module_config import ModuleConfig @@ -43,9 +44,12 @@ def __init__( self._message_coordinators: dict[str, Any] = {} def get_module(self, module_name: str, default_module_name: str) -> AbstractModule: - class_name = self._module_config.get_value_or_default( - module_name, default_module_name - ) + class_name = self._module_config.get_value(module_name) + if class_name is None: + get_logger("ModuleManager").warning( + f"Module key {module_name} not found in config, using default module {default_module_name}" + ) + class_name = default_module_name try: module_class: type = self._load_module(class_name) From 0c902cd0b166a5b348ca0b17cb28857364d3d86a Mon Sep 17 00:00:00 2001 From: shima004 Date: Sat, 23 Nov 2024 19:42:13 +0900 Subject: [PATCH 131/249] feat: update launcher configuration and refactor module references for human and road detectors --- .../cli/template/config/module.yaml | 61 ++++++------------- .../module/complex/sample_human_detector.py | 2 +- .../module/complex/sample_road_detector.py | 2 +- .../team_name/module/complex/sample_search.py | 4 +- .../action/default_extend_action_rescue.py | 2 +- .../module/complex/default_human_detector.py | 2 +- .../module/complex/default_road_detector.py | 2 +- .../module/complex/default_search.py | 4 +- config/launcher.yaml | 12 ++-- config/module.yaml | 61 ++++++------------- 10 files changed, 49 insertions(+), 103 deletions(-) diff --git a/adf_core_python/cli/template/config/module.yaml b/adf_core_python/cli/template/config/module.yaml index dbf215b..280129b 100644 --- a/adf_core_python/cli/template/config/module.yaml +++ b/adf_core_python/cli/template/config/module.yaml @@ -1,4 +1,3 @@ -## DefaultTacticsAmbulanceTeam DefaultTacticsAmbulanceTeam: HumanDetector: src.team_name.module.complex.sample_human_detector.SampleHumanDetector Search: src.team_name.module.complex.sample_search.SampleSearch @@ -7,7 +6,6 @@ DefaultTacticsAmbulanceTeam: CommandExecutorAmbulance: adf_core_python.implement.centralized.DefaultCommandExecutorAmbulance CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout -# ## DefaultTacticsFireBrigade DefaultTacticsFireBrigade: HumanDetector: src.team_name.module.complex.sample_human_detector.SampleHumanDetector Search: src.team_name.module.complex.sample_search.SampleSearch @@ -16,7 +14,6 @@ DefaultTacticsFireBrigade: CommandExecutorFire: adf_core_python.implement.centralized.DefaultCommandExecutorFire CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout -# ## DefaultTacticsPoliceForce DefaultTacticsPoliceForce: RoadDetector: src.team_name.module.complex.sample_road_detector.SampleRoadDetector Search: src.team_name.module.complex.sample_search.SampleSearch @@ -25,87 +22,63 @@ DefaultTacticsPoliceForce: CommandExecutorPolice: adf_core_python.implement.centralized.DefaultCommandExecutorPolice CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScoutPolice -# ## DefaultTacticsAmbulanceCentre # DefaultTacticsAmbulanceCentre: # TargetAllocator: sample_team.module.complex.SampleAmbulanceTargetAllocator # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerAmbulance -# ## DefaultTacticsFireStation # DefaultTacticsFireStation: # TargetAllocator: sample_team.module.complex.SampleFireTargetAllocator # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerFire -# ## DefaultTacticsPoliceOffice # DefaultTacticsPoliceOffice: # TargetAllocator: sample_team.module.complex.SamplePoliceTargetAllocator # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerPolice -## SampleSearch SampleSearch: PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning Clustering: adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering -# ## SampleBuildDetector -# SampleBuildingDetector: -# Clustering: adf_core_python.implement.module.algorithm.KMeansClustering - -# ## SampleRoadDetector -# SampleRoadDetector: -# Clustering: adf_core_python.implement.module.algorithm.KMeansClustering -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +SampleRoadDetector: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ## SampleHumanDetector -# SampleHumanDetector: -# Clustering: adf_core_python.implement.module.algorithm.KMeansClustering +SampleHumanDetector: + Clustering: adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering -# ## DefaultExtendActionClear -# DefaultExtendActionClear: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +DefaultExtendActionClear: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ## DefaultExtendActionFireFighting -# DefaultExtendActionFireFighting: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +DefaultExtendActionRescue: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ## DefaultExtendActionRescue -# DefaultExtendActionRescue: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +DefaultExtendActionMove: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ## DefaultExtendActionMove -# DefaultExtendActionMove: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +DefaultExtendActionTransport: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ## DefaultExtendActionTransport -# DefaultExtendActionTransport: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning -# ## DefaultCommandExecutorAmbulance # DefaultCommandExecutorAmbulance: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning # ExtendActionTransport: adf_core_python.implement.action.DefaultExtendActionTransport # ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove -# ## DefaultCommandExecutorFire # DefaultCommandExecutorFire: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning # EtxActionFireRescue: adf_core_python.implement.action.DefaultExtendActionRescue # EtxActionFireFighting: adf_core_python.implement.action.DefaultExtendActionFireFighting # ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove -# ## DefaultCommandExecutorPolice # DefaultCommandExecutorPolice: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning # ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear # ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove -# ## DefaultCommandExecutorScout # DefaultCommandExecutorScout: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ## DefaultCommandExecutorScoutPolice # DefaultCommandExecutorScoutPolice: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning # ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear -# ## MessageManager MessageManager: PlatoonChannelSubscriber: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber CenterChannelSubscriber: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber diff --git a/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py b/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py index c3b9587..8b44545 100644 --- a/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py @@ -31,7 +31,7 @@ def __init__( self._clustering: Clustering = cast( Clustering, module_manager.get_module( - "DefaultHumanDetector.Clustering", + "SampleHumanDetector.Clustering", "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", ), ) diff --git a/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py b/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py index a46e67e..7a560fc 100644 --- a/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py @@ -36,7 +36,7 @@ def __init__( self._path_planning: PathPlanning = cast( PathPlanning, module_manager.get_module( - "DefaultRoadDetector.PathPlanning", + "SampleRoadDetector.PathPlanning", "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", ), ) diff --git a/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py b/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py index d1bbe04..d495c49 100644 --- a/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py @@ -36,7 +36,7 @@ def __init__( self._clustering: Clustering = cast( Clustering, module_manager.get_module( - "DefaultSearch.Clustering", + "SampleSearch.Clustering", "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", ), ) @@ -44,7 +44,7 @@ def __init__( self._path_planning: PathPlanning = cast( PathPlanning, module_manager.get_module( - "DefaultSearch.PathPlanning", + "SampleSearch.PathPlanning", "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", ), ) diff --git a/adf_core_python/implement/action/default_extend_action_rescue.py b/adf_core_python/implement/action/default_extend_action_rescue.py index eb96543..640d088 100644 --- a/adf_core_python/implement/action/default_extend_action_rescue.py +++ b/adf_core_python/implement/action/default_extend_action_rescue.py @@ -47,7 +47,7 @@ def __init__( self._path_planning = cast( PathPlanning, self.module_manager.get_module( - "DefaultExtendActionMove.PathPlanning", + "DefaultExtendActionRescue.PathPlanning", "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", ), ) diff --git a/adf_core_python/implement/module/complex/default_human_detector.py b/adf_core_python/implement/module/complex/default_human_detector.py index 4720226..f607fa1 100644 --- a/adf_core_python/implement/module/complex/default_human_detector.py +++ b/adf_core_python/implement/module/complex/default_human_detector.py @@ -31,7 +31,7 @@ def __init__( self._clustering: Clustering = cast( Clustering, module_manager.get_module( - "DefaultHumanDetector.Clustering", + "SampleHumanDetector.Clustering", "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", ), ) diff --git a/adf_core_python/implement/module/complex/default_road_detector.py b/adf_core_python/implement/module/complex/default_road_detector.py index adad2d8..a2b9b1a 100644 --- a/adf_core_python/implement/module/complex/default_road_detector.py +++ b/adf_core_python/implement/module/complex/default_road_detector.py @@ -36,7 +36,7 @@ def __init__( self._path_planning: PathPlanning = cast( PathPlanning, module_manager.get_module( - "DefaultRoadDetector.PathPlanning", + "SampleRoadDetector.PathPlanning", "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", ), ) diff --git a/adf_core_python/implement/module/complex/default_search.py b/adf_core_python/implement/module/complex/default_search.py index 05d7c88..972b8ae 100644 --- a/adf_core_python/implement/module/complex/default_search.py +++ b/adf_core_python/implement/module/complex/default_search.py @@ -36,7 +36,7 @@ def __init__( self._clustering: Clustering = cast( Clustering, module_manager.get_module( - "DefaultSearch.Clustering", + "SampleSearch.Clustering", "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", ), ) @@ -44,7 +44,7 @@ def __init__( self._path_planning: PathPlanning = cast( PathPlanning, module_manager.get_module( - "DefaultSearch.PathPlanning", + "SampleSearch.PathPlanning", "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", ), ) diff --git a/config/launcher.yaml b/config/launcher.yaml index d69a51b..07de1ca 100644 --- a/config/launcher.yaml +++ b/config/launcher.yaml @@ -7,25 +7,25 @@ team: adf: launcher: - precompute: 0 + precompute: false debug: - flag: 0 + flag: false agent: moduleconfig: filename: config/module.yaml develop: - flag: 1 + flag: true filename: config/development.json team: platoon: ambulance: - count: 1 + count: 100 fire: - count: 0 + count: 100 police: - count: 0 + count: 100 office: ambulance: count: -1 diff --git a/config/module.yaml b/config/module.yaml index 958fbc7..da21aa5 100644 --- a/config/module.yaml +++ b/config/module.yaml @@ -1,4 +1,3 @@ -## DefaultTacticsAmbulanceTeam DefaultTacticsAmbulanceTeam: HumanDetector: adf_core_python.implement.module.complex.default_human_detector.DefaultHumanDetector Search: adf_core_python.implement.module.complex.default_search.DefaultSearch @@ -7,7 +6,6 @@ DefaultTacticsAmbulanceTeam: CommandExecutorAmbulance: adf_core_python.implement.centralized.DefaultCommandExecutorAmbulance CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout -# ## DefaultTacticsFireBrigade DefaultTacticsFireBrigade: HumanDetector: adf_core_python.implement.module.complex.default_human_detector.DefaultHumanDetector Search: adf_core_python.implement.module.complex.default_search.DefaultSearch @@ -16,7 +14,6 @@ DefaultTacticsFireBrigade: CommandExecutorFire: adf_core_python.implement.centralized.DefaultCommandExecutorFire CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout -# ## DefaultTacticsPoliceForce DefaultTacticsPoliceForce: RoadDetector: adf_core_python.implement.module.complex.default_road_detector.DefaultRoadDetector Search: adf_core_python.implement.module.complex.default_search.DefaultSearch @@ -25,87 +22,63 @@ DefaultTacticsPoliceForce: CommandExecutorPolice: adf_core_python.implement.centralized.DefaultCommandExecutorPolice CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScoutPolice -# ## DefaultTacticsAmbulanceCentre # DefaultTacticsAmbulanceCentre: # TargetAllocator: sample_team.module.complex.SampleAmbulanceTargetAllocator # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerAmbulance -# ## DefaultTacticsFireStation # DefaultTacticsFireStation: # TargetAllocator: sample_team.module.complex.SampleFireTargetAllocator # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerFire -# ## DefaultTacticsPoliceOffice # DefaultTacticsPoliceOffice: # TargetAllocator: sample_team.module.complex.SamplePoliceTargetAllocator # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerPolice -## SampleSearch SampleSearch: PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning Clustering: adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering -# ## SampleBuildDetector -# SampleBuildingDetector: -# Clustering: adf_core_python.implement.module.algorithm.KMeansClustering - -# ## SampleRoadDetector -# SampleRoadDetector: -# Clustering: adf_core_python.implement.module.algorithm.KMeansClustering -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +SampleRoadDetector: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ## SampleHumanDetector -# SampleHumanDetector: -# Clustering: adf_core_python.implement.module.algorithm.KMeansClustering +SampleHumanDetector: + Clustering: adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering -# ## DefaultExtendActionClear -# DefaultExtendActionClear: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +DefaultExtendActionClear: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ## DefaultExtendActionFireFighting -# DefaultExtendActionFireFighting: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +DefaultExtendActionRescue: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ## DefaultExtendActionRescue -# DefaultExtendActionRescue: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +DefaultExtendActionMove: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ## DefaultExtendActionMove -# DefaultExtendActionMove: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +DefaultExtendActionTransport: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ## DefaultExtendActionTransport -# DefaultExtendActionTransport: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning -# ## DefaultCommandExecutorAmbulance # DefaultCommandExecutorAmbulance: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning # ExtendActionTransport: adf_core_python.implement.action.DefaultExtendActionTransport # ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove -# ## DefaultCommandExecutorFire # DefaultCommandExecutorFire: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning # EtxActionFireRescue: adf_core_python.implement.action.DefaultExtendActionRescue # EtxActionFireFighting: adf_core_python.implement.action.DefaultExtendActionFireFighting # ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove -# ## DefaultCommandExecutorPolice # DefaultCommandExecutorPolice: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning # ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear # ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove -# ## DefaultCommandExecutorScout # DefaultCommandExecutorScout: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ## DefaultCommandExecutorScoutPolice # DefaultCommandExecutorScoutPolice: -# PathPlanning: adf_core_python.implement.module.algorithm.DijkstraPathPlanning +# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning # ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear -# ## MessageManager MessageManager: PlatoonChannelSubscriber: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber CenterChannelSubscriber: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber From 78490e34015f59b3467a731ddba5b8854e39fe7a Mon Sep 17 00:00:00 2001 From: shima004 Date: Sat, 23 Nov 2024 20:33:04 +0900 Subject: [PATCH 132/249] fix: improve error handling in agent connection and update logging messages --- adf_core_python/core/agent/agent.py | 17 ++++++++++++++--- .../core/launcher/connect/component_launcher.py | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index 4d41918..6ed82cf 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -197,6 +197,8 @@ def update_step_info( message_manager=self._message_manager, ) + a = 1 / 0 + self._message_manager.coordinate_message( self._agent_info, self._world_info, self._scenario_info ) @@ -228,9 +230,18 @@ def message_received(self, msg: Any) -> None: self.handle_connect_error(c_msg) def handle_connect_error(self, msg: Any) -> NoReturn: - self.logger.error( - "Failed to connect agent: %s(request_id: %s)", msg.reason, msg.request_id - ) + if msg.reason.startswith("No more agents"): + self.logger.debug( + "Agent already connected: %s(request_id: %s)", + msg.reason, + msg.request_id, + ) + else: + self.logger.error( + "Failed to connect agent: %s(request_id: %s)", + msg.reason, + msg.request_id, + ) sys.exit(1) def handle_connect_ok(self, msg: Any) -> None: diff --git a/adf_core_python/core/launcher/connect/component_launcher.py b/adf_core_python/core/launcher/connect/component_launcher.py index 56a142c..d4f6ffd 100644 --- a/adf_core_python/core/launcher/connect/component_launcher.py +++ b/adf_core_python/core/launcher/connect/component_launcher.py @@ -47,7 +47,7 @@ def connect(self, agent: Agent, _request_id: int) -> None: connection.parse_message_from_kernel() except Exception as e: self.logger.exception( - f"Failed to connect agent: {self.host}:{self.port} {e}", + f"Agent threw an exception {e}", exception=str(e), ) From 70c1f0f980abb7b2c022c26b527c96dc802009b7 Mon Sep 17 00:00:00 2001 From: shima004 Date: Sat, 23 Nov 2024 20:33:25 +0900 Subject: [PATCH 133/249] fix: remove intentional division by zero in agent.py --- adf_core_python/core/agent/agent.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index 6ed82cf..a5bbee6 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -197,8 +197,6 @@ def update_step_info( message_manager=self._message_manager, ) - a = 1 / 0 - self._message_manager.coordinate_message( self._agent_info, self._world_info, self._scenario_info ) From c10472ee07e65e9c3772d1ffdd57415eb5c5ae37 Mon Sep 17 00:00:00 2001 From: shima004 Date: Sun, 24 Nov 2024 14:44:55 +0900 Subject: [PATCH 134/249] fix: set logging levels for stdout and file handlers in logger configuration --- adf_core_python/core/logger/logger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/adf_core_python/core/logger/logger.py b/adf_core_python/core/logger/logger.py index 84f857a..440cef0 100644 --- a/adf_core_python/core/logger/logger.py +++ b/adf_core_python/core/logger/logger.py @@ -69,6 +69,7 @@ def configure_logger() -> None: handler_stdout.setFormatter( structlog.stdlib.ProcessorFormatter(processor=ConsoleRenderer()) ) + handler_stdout.setLevel(logging.INFO) handler_file = RotatingFileHandler( "agent.log", maxBytes=10 * 1024 * 1024, backupCount=5 @@ -77,6 +78,7 @@ def configure_logger() -> None: handler_file.setFormatter( structlog.stdlib.ProcessorFormatter(processor=JSONRenderer()) ) + handler_file.setLevel(logging.DEBUG) root_logger = logging.getLogger() root_logger.addHandler(handler_stdout) From 4a2e00e8fa6f01fccb3e72b9cf882d4ace640de3 Mon Sep 17 00:00:00 2001 From: shima004 Date: Sun, 24 Nov 2024 14:57:59 +0900 Subject: [PATCH 135/249] fix: update module references from Sample to Default for human and road detectors --- .../implement/module/complex/default_human_detector.py | 2 +- .../implement/module/complex/default_road_detector.py | 2 +- adf_core_python/implement/module/complex/default_search.py | 4 ++-- config/module.yaml | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/adf_core_python/implement/module/complex/default_human_detector.py b/adf_core_python/implement/module/complex/default_human_detector.py index f607fa1..4720226 100644 --- a/adf_core_python/implement/module/complex/default_human_detector.py +++ b/adf_core_python/implement/module/complex/default_human_detector.py @@ -31,7 +31,7 @@ def __init__( self._clustering: Clustering = cast( Clustering, module_manager.get_module( - "SampleHumanDetector.Clustering", + "DefaultHumanDetector.Clustering", "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", ), ) diff --git a/adf_core_python/implement/module/complex/default_road_detector.py b/adf_core_python/implement/module/complex/default_road_detector.py index a2b9b1a..adad2d8 100644 --- a/adf_core_python/implement/module/complex/default_road_detector.py +++ b/adf_core_python/implement/module/complex/default_road_detector.py @@ -36,7 +36,7 @@ def __init__( self._path_planning: PathPlanning = cast( PathPlanning, module_manager.get_module( - "SampleRoadDetector.PathPlanning", + "DefaultRoadDetector.PathPlanning", "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", ), ) diff --git a/adf_core_python/implement/module/complex/default_search.py b/adf_core_python/implement/module/complex/default_search.py index 972b8ae..05d7c88 100644 --- a/adf_core_python/implement/module/complex/default_search.py +++ b/adf_core_python/implement/module/complex/default_search.py @@ -36,7 +36,7 @@ def __init__( self._clustering: Clustering = cast( Clustering, module_manager.get_module( - "SampleSearch.Clustering", + "DefaultSearch.Clustering", "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", ), ) @@ -44,7 +44,7 @@ def __init__( self._path_planning: PathPlanning = cast( PathPlanning, module_manager.get_module( - "SampleSearch.PathPlanning", + "DefaultSearch.PathPlanning", "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", ), ) diff --git a/config/module.yaml b/config/module.yaml index da21aa5..2697130 100644 --- a/config/module.yaml +++ b/config/module.yaml @@ -34,14 +34,14 @@ DefaultTacticsPoliceForce: # TargetAllocator: sample_team.module.complex.SamplePoliceTargetAllocator # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerPolice -SampleSearch: +DefaultSearch: PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning Clustering: adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering -SampleRoadDetector: +DefaultRoadDetector: PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -SampleHumanDetector: +DefaultHumanDetector: Clustering: adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering DefaultExtendActionClear: From 2f0943a69f20834bd38c4fd1307b101abe3417f1 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 28 Nov 2024 12:23:08 +0900 Subject: [PATCH 136/249] fix: refactor template copying to use shutil and replace placeholders in files and directories --- adf_core_python/cli/cli.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/adf_core_python/cli/cli.py b/adf_core_python/cli/cli.py index fe2b343..74ca0de 100644 --- a/adf_core_python/cli/cli.py +++ b/adf_core_python/cli/cli.py @@ -1,4 +1,5 @@ import os +import shutil import click @@ -29,25 +30,24 @@ def _copy_template( dest: str, name: str, ) -> None: - if os.path.isdir(src): - if not os.path.exists(dest): - os.makedirs(dest) - for item in os.listdir(src): - s = os.path.join(src, item) - d = os.path.join( - dest, - item.replace(NAME_PLACEHOLDER, name), - ) - _copy_template(s, d, name) - else: - with open(src, "r") as f: - content = f.read() - with open(dest, "w") as f: - f.write(content.replace(NAME_PLACEHOLDER, name)) - new_dest = dest.replace(NAME_PLACEHOLDER, name) - if new_dest != dest: - os.rename(dest, new_dest) - + dest = os.path.join(dest, name) + shutil.copytree(src, dest) + + # dest以下のファイル内のNAME_PLACEHOLDERをnameに置換 + for root, dirs, files in os.walk(dest): + for file in files: + file_path = os.path.join(root, file) + with open(file_path, "r") as f: + content = f.read() + with open(file_path, "w") as f: + f.write(content.replace(NAME_PLACEHOLDER, name)) + + # ディレクトリ名のNAME_PLACEHOLDERをnameに置換 + for root, dirs, files in os.walk(dest): + for dir in dirs: + dir_path = os.path.join(root, dir) + new_dir_path = dir_path.replace(NAME_PLACEHOLDER, name) + os.rename(dir_path, new_dir_path) if __name__ == "__main__": cli() From 30dc90469ef612bd6f4fea90e0637880cfdf76b7 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 28 Nov 2024 12:38:38 +0900 Subject: [PATCH 137/249] fix: remove unnecessary blank lines in template copying function --- adf_core_python/cli/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/adf_core_python/cli/cli.py b/adf_core_python/cli/cli.py index 74ca0de..232bd1b 100644 --- a/adf_core_python/cli/cli.py +++ b/adf_core_python/cli/cli.py @@ -32,7 +32,7 @@ def _copy_template( ) -> None: dest = os.path.join(dest, name) shutil.copytree(src, dest) - + # dest以下のファイル内のNAME_PLACEHOLDERをnameに置換 for root, dirs, files in os.walk(dest): for file in files: @@ -49,5 +49,6 @@ def _copy_template( new_dir_path = dir_path.replace(NAME_PLACEHOLDER, name) os.rename(dir_path, new_dir_path) + if __name__ == "__main__": cli() From 02a029bd9718a97023f4e78487d9e15a55c3c28e Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 14 Nov 2024 17:42:27 +0900 Subject: [PATCH 138/249] feat: add Sphinx documentation structure and configuration --- .github/workflows/document.yaml | 56 ++ docs/Makefile | 20 + docs/README.md | 9 + docs/make.bat | 35 ++ docs/source/conf.py | 37 ++ docs/source/examples/example_index.rst | 14 + docs/source/index.rst | 28 + docs/source/installation/installation.rst | 43 ++ docs/source/introduction/introduction.rst | 44 ++ docs/source/usage/usage_index.rst | 37 ++ poetry.lock | 645 +++++++++++++++++++++- pyproject.toml | 3 + 12 files changed, 962 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/document.yaml create mode 100644 docs/Makefile create mode 100644 docs/README.md create mode 100644 docs/make.bat create mode 100644 docs/source/conf.py create mode 100644 docs/source/examples/example_index.rst create mode 100644 docs/source/index.rst create mode 100644 docs/source/installation/installation.rst create mode 100644 docs/source/introduction/introduction.rst create mode 100644 docs/source/usage/usage_index.rst diff --git a/.github/workflows/document.yaml b/.github/workflows/document.yaml new file mode 100644 index 0000000..a413184 --- /dev/null +++ b/.github/workflows/document.yaml @@ -0,0 +1,56 @@ +name: document + +on: + workflow_dispatch: + push: + branches: + - main + - develop + paths: + - 'docs/**' + +concurrency: + group: 'pages' + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + + - name: Cache virtual environment + id: cached-virtualenv + uses: actions/cache@v4 + with: + path: .venv + key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} + + - name: Install dependencies + if: steps.cached-virtualenv.outputs.cache-hit != 'true' + run: | + python -m venv .venv + source .venv/bin/activate + pip install --upgrade pip + pip install poetry + poetry install + + - name: Build documentation + run: | + source .venv/bin/activate + sphinx-apidoc -f -o ./docs/source ./adf_core_python + cd docs + make html + ls build/html + + - name: Deploy documentation + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/build/html diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..32c5368 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,9 @@ +# sqhinx-documentation + +## Generate documentation + +```bash +sphinx-apidoc -f -o ./docs/source ./adf_core_python +cd docs +make html +``` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..747ffb7 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..9e1cff1 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,37 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +import os +import sys + +sys.path.insert(0, os.path.abspath("../../")) + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "adf-core-python" +copyright = "2024, Haruki Uehara, Yuki Shimada" +author = "Haruki Uehara, Yuki Shimada" +release = "0.1.0" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.autosummary", + "myst_parser", +] + +templates_path = ["_templates"] +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "sphinx_book_theme" +html_static_path = ["_static"] diff --git a/docs/source/examples/example_index.rst b/docs/source/examples/example_index.rst new file mode 100644 index 0000000..cbb5995 --- /dev/null +++ b/docs/source/examples/example_index.rst @@ -0,0 +1,14 @@ +.. _example_index: + +Example Index +============= + +Welcome to the example index for the ADF Core Python documentation. This section provides various examples to help you understand how to use the library effectively. + +.. toctree:: + :maxdepth: 2 + :caption: Examples + + example1 + example2 + example3 diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..4e2bb29 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,28 @@ +.. adf-core-python documentation master file, created by + sphinx-quickstart on Mon Oct 14 00:15:07 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to adf-core-python's documentation! +============================================ + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + introduction/introduction + installation/installation + usage/usage_index + examples/example_index + +.. automodule:: adf_core_python + :members: + :undoc-members: + :show-inheritance: + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/installation/installation.rst b/docs/source/installation/installation.rst new file mode 100644 index 0000000..26c731c --- /dev/null +++ b/docs/source/installation/installation.rst @@ -0,0 +1,43 @@ +.. _installation: + +Installation +============ + +This section provides instructions on how to install the package. + +.. contents:: Table of Contents + :depth: 2 + :local: + +Prerequisites +------------- + +Before installing, ensure you have the following prerequisites: + +- Python 3.6 or higher +- pip + +Installing the Package +---------------------- + +To install the package, run the following command: + +.. code-block:: bash + + pip install your-package-name + +Verifying the Installation +-------------------------- + +To verify the installation, you can run: + +.. code-block:: bash + + python -c "import your_package_name; print(your_package_name.__version__)" + +If the package is installed correctly, this should print the version number of the package. + +Troubleshooting +--------------- + +If you encounter any issues during installation, please refer to the troubleshooting section or contact support. diff --git a/docs/source/introduction/introduction.rst b/docs/source/introduction/introduction.rst new file mode 100644 index 0000000..9293e19 --- /dev/null +++ b/docs/source/introduction/introduction.rst @@ -0,0 +1,44 @@ +.. _introduction: + +Introduction +============ + +Welcome to the ADF Core Python Documentation + +.. contents:: Table of Contents + :depth: 2 + :local: + +-------------------------------------------- + +This documentation is built using the `Sphinx `_ documentation generator and the `sphinx_book_theme `_. + +Features +-------- + +- **Easy to navigate**: The theme provides a clean and modern interface for your documentation. +- **Responsive design**: Works well on both desktop and mobile devices. +- **Search functionality**: Quickly find the information you need. + +Getting Started +--------------- + +To get started with ADF Core Python, follow the installation instructions and explore the examples provided in this documentation. + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + installation + usage + examples + +Contributing +------------ + +We welcome contributions! Please read our `contributing guide <../contributing.rst>`_ to learn how you can help. + +Contact +------- + +If you have any questions or feedback, please reach out to us at `support@example.com `_. diff --git a/docs/source/usage/usage_index.rst b/docs/source/usage/usage_index.rst new file mode 100644 index 0000000..ee39535 --- /dev/null +++ b/docs/source/usage/usage_index.rst @@ -0,0 +1,37 @@ +.. _usage_index: + +Usage +===== + +This section provides an overview of how to use the `adf-core-python` library. + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + installation + quickstart + examples + +.. note:: + This documentation is built using the `sphinx_book_theme`. + +Getting Started +--------------- + +To get started with `adf-core-python`, follow the instructions in the sections below. + +Installation +------------ + +.. include:: installation.rst + +Quickstart +---------- + +.. include:: quickstart.rst + +Examples +-------- + +.. include:: examples.rst diff --git a/poetry.lock b/poetry.lock index d30279b..58faa1d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,69 @@ # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +[[package]] +name = "accessible-pygments" +version = "0.0.5" +description = "A collection of accessible pygments styles" +optional = false +python-versions = ">=3.9" +files = [ + {file = "accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7"}, + {file = "accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872"}, +] + +[package.dependencies] +pygments = ">=1.5" + +[package.extras] +dev = ["pillow", "pkginfo (>=1.10)", "playwright", "pre-commit", "setuptools", "twine (>=5.0)"] +tests = ["hypothesis", "pytest"] + +[[package]] +name = "alabaster" +version = "1.0.0" +description = "A light, configurable Sphinx theme" +optional = false +python-versions = ">=3.10" +files = [ + {file = "alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"}, + {file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"}, +] + +[[package]] +name = "babel" +version = "2.16.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, + {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "bitarray" version = "3.0.0" @@ -147,18 +211,129 @@ files = [ ] [[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} +[[package]] +name = "charset-normalizer" +version = "3.4.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] [[package]] name = "colorama" @@ -171,6 +346,42 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "docutils" +version = "0.21.2" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.9" +files = [ + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, +] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + [[package]] name = "iniconfig" version = "2.0.0" @@ -182,6 +393,23 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + [[package]] name = "joblib" version = "1.4.2" @@ -193,6 +421,130 @@ files = [ {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, ] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.4.2" +description = "Collection of plugins for markdown-it-py" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, + {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, +] + +[package.dependencies] +markdown-it-py = ">=1.0.0,<4.0.0" + +[package.extras] +code-style = ["pre-commit"] +rtd = ["myst-parser", "sphinx-book-theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "mslex" version = "1.3.0" @@ -267,6 +619,32 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "myst-parser" +version = "4.0.0" +description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," +optional = false +python-versions = ">=3.10" +files = [ + {file = "myst_parser-4.0.0-py3-none-any.whl", hash = "sha256:b9317997552424448c6096c2558872fdb6f81d3ecb3a40ce84a7518798f3f28d"}, + {file = "myst_parser-4.0.0.tar.gz", hash = "sha256:851c9dfb44e36e56d15d05e72f02b80da21a9e0d07cba96baf5e2d476bb91531"}, +] + +[package.dependencies] +docutils = ">=0.19,<0.22" +jinja2 = "*" +markdown-it-py = ">=3.0,<4.0" +mdit-py-plugins = ">=0.4.1,<1.0" +pyyaml = "*" +sphinx = ">=7,<9" + +[package.extras] +code-style = ["pre-commit (>=3.0,<4.0)"] +linkify = ["linkify-it-py (>=2.0,<3.0)"] +rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] +testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] +testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] + [[package]] name = "numpy" version = "2.1.3" @@ -405,6 +783,47 @@ files = [ [package.extras] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +[[package]] +name = "pydata-sphinx-theme" +version = "0.16.0" +description = "Bootstrap-based Sphinx theme from the PyData community" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pydata_sphinx_theme-0.16.0-py3-none-any.whl", hash = "sha256:18c810ee4e67e05281e371e156c1fb5bb0fa1f2747240461b225272f7d8d57d8"}, + {file = "pydata_sphinx_theme-0.16.0.tar.gz", hash = "sha256:721dd26e05fa8b992d66ef545536e6cbe0110afb9865820a08894af1ad6f7707"}, +] + +[package.dependencies] +accessible-pygments = "*" +Babel = "*" +beautifulsoup4 = "*" +docutils = "!=0.17.0" +pygments = ">=2.7" +sphinx = ">=6.1" +typing-extensions = "*" + +[package.extras] +a11y = ["pytest-playwright"] +dev = ["pandoc", "pre-commit", "pydata-sphinx-theme[doc,test]", "pyyaml", "sphinx-theme-builder[cli]", "tox"] +doc = ["ablog (>=0.11.8)", "colorama", "graphviz", "ipykernel", "ipyleaflet", "ipywidgets", "jupyter_sphinx", "jupyterlite-sphinx", "linkify-it-py", "matplotlib", "myst-parser", "nbsphinx", "numpy", "numpydoc", "pandas", "plotly", "rich", "sphinx-autoapi (>=3.0.0)", "sphinx-copybutton", "sphinx-design", "sphinx-favicon (>=1.0.1)", "sphinx-sitemap", "sphinx-togglebutton", "sphinxcontrib-youtube (>=1.4.1)", "sphinxext-rediraffe", "xarray"] +i18n = ["Babel", "jinja2"] +test = ["pytest", "pytest-cov", "pytest-regressions", "sphinx[test]"] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + [[package]] name = "pytest" version = "8.3.3" @@ -506,6 +925,27 @@ url = "https://github.com/adf-python/rcrs-core-python" reference = "HEAD" resolved_reference = "aa661b19c98f82d8d648317f386c2f9d2c26a8fc" +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + [[package]] name = "rtree" version = "1.3.0" @@ -704,6 +1144,176 @@ numpy = ">=1.14,<3" docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] test = ["pytest", "pytest-cov"] +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "soupsieve" +version = "2.6" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +description = "Python documentation generator" +optional = false +python-versions = ">=3.10" +files = [ + {file = "sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2"}, + {file = "sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927"}, +] + +[package.dependencies] +alabaster = ">=0.7.14" +babel = ">=2.13" +colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} +docutils = ">=0.20,<0.22" +imagesize = ">=1.3" +Jinja2 = ">=3.1" +packaging = ">=23.0" +Pygments = ">=2.17" +requests = ">=2.30.0" +snowballstemmer = ">=2.2" +sphinxcontrib-applehelp = ">=1.0.7" +sphinxcontrib-devhelp = ">=1.0.6" +sphinxcontrib-htmlhelp = ">=2.0.6" +sphinxcontrib-jsmath = ">=1.0.1" +sphinxcontrib-qthelp = ">=1.0.6" +sphinxcontrib-serializinghtml = ">=1.1.9" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["flake8 (>=6.0)", "mypy (==1.11.1)", "pyright (==1.1.384)", "pytest (>=6.0)", "ruff (==0.6.9)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241005)", "types-requests (==2.32.0.20240914)", "types-urllib3 (==1.26.25.14)"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] + +[[package]] +name = "sphinx-book-theme" +version = "1.1.3" +description = "A clean book theme for scientific explanations and documentation with Sphinx" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinx_book_theme-1.1.3-py3-none-any.whl", hash = "sha256:a554a9a7ac3881979a87a2b10f633aa2a5706e72218a10f71be38b3c9e831ae9"}, + {file = "sphinx_book_theme-1.1.3.tar.gz", hash = "sha256:1f25483b1846cb3d353a6bc61b3b45b031f4acf845665d7da90e01ae0aef5b4d"}, +] + +[package.dependencies] +pydata-sphinx-theme = ">=0.15.2" +sphinx = ">=5" + +[package.extras] +code-style = ["pre-commit"] +doc = ["ablog", "folium", "ipywidgets", "matplotlib", "myst-nb", "nbclient", "numpy", "numpydoc", "pandas", "plotly", "sphinx-copybutton", "sphinx-design", "sphinx-examples", "sphinx-tabs", "sphinx-thebe", "sphinx-togglebutton", "sphinxcontrib-bibtex", "sphinxcontrib-youtube", "sphinxext-opengraph"] +test = ["beautifulsoup4", "coverage", "defusedxml", "myst-nb", "pytest", "pytest-cov", "pytest-regressions", "sphinx_thebe"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, + {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, + {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, + {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, + {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["defusedxml (>=0.7.1)", "pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, + {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + [[package]] name = "structlog" version = "24.4.0" @@ -793,7 +1403,24 @@ files = [ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +[[package]] +name = "urllib3" +version = "2.2.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "62d5fde0d4a35f29aa7f21a31f5c1f4679c7a576c0f57c0363ec0b0cd6dc9e91" +content-hash = "dede3d08af33fe3c124b63b82aad441e9fb9a8eba7f069ffb30945006c60efb1" diff --git a/pyproject.toml b/pyproject.toml index 71f60d0..04bdb98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,9 @@ taskipy = "^1.12.2" mypy = "^1.9.0" types-protobuf = "^4.25.0.20240410" ruff = "^0.4.4" +sphinx = "^8.1.3" +myst-parser = "^4.0.0" +sphinx-book-theme = "^1.1.3" [build-system] requires = ["poetry-core"] From ea1e4287c8389e449c0c40ef98ce907091d2277c Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 14 Nov 2024 17:49:08 +0900 Subject: [PATCH 139/249] feat: update GitHub Actions workflow and configuration files to include documentation paths and adjust type checking settings --- .github/workflows/document.yaml | 6 ++++++ pyproject.toml | 2 ++ pyrightconfig.json | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/document.yaml b/.github/workflows/document.yaml index a413184..ae46f13 100644 --- a/.github/workflows/document.yaml +++ b/.github/workflows/document.yaml @@ -8,6 +8,12 @@ on: - develop paths: - 'docs/**' + pull_request: + branches: + - main + - develop + paths: + - 'docs/**' concurrency: group: 'pages' diff --git a/pyproject.toml b/pyproject.toml index 04bdb98..2d244cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,6 +75,7 @@ exclude = [ "site-packages", "venv", "pb2", + "docs", ] # Same as Black. @@ -133,3 +134,4 @@ disallow_untyped_defs = true # 関数定義の引数/戻り値に型アノテ no_implicit_optional = true # デフォルト引数に None を取る場合型アノテーションに Optional 必須 check_untyped_defs = true # 型注釈がない関数やメソッドに対して型チェックを行う warn_redundant_casts = true # 冗長なキャストに警告 +exclude = ["docs/*"] diff --git a/pyrightconfig.json b/pyrightconfig.json index 8c19fca..972999c 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -1,4 +1,4 @@ { "typeCheckingMode": "standard", - "exclude": [".venv", ".git"] + "include": ["adf_core_python"] } From d5f84b82e796fcaa92ecf13d261cb2929a9dc4f7 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 14 Nov 2024 18:04:45 +0900 Subject: [PATCH 140/249] feat: update GitHub Actions workflow to add permissions for contents and pull requests --- .github/workflows/document.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/document.yaml b/.github/workflows/document.yaml index ae46f13..bccc46d 100644 --- a/.github/workflows/document.yaml +++ b/.github/workflows/document.yaml @@ -19,6 +19,10 @@ concurrency: group: 'pages' cancel-in-progress: true +permissions: + contents: write + pull-requests: write + jobs: deploy: runs-on: ubuntu-latest From a5379f325ba8dce77a8f0c3eac02cd8f7d2b602e Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 15 Nov 2024 16:37:36 +0900 Subject: [PATCH 141/249] feat: add Japanese localization and update documentation structure --- docs/.gitignore | 1 + docs/source/conf.py | 1 + docs/source/examples/example_index.rst | 7 ++-- docs/source/index.rst | 2 +- docs/source/installation/installation.rst | 38 ++++++++++---------- docs/source/introduction/introduction.rst | 42 +++++++---------------- docs/source/modules.rst | 7 ++++ docs/source/usage/usage_index.rst | 27 +++++++-------- 8 files changed, 58 insertions(+), 67 deletions(-) create mode 100644 docs/.gitignore create mode 100644 docs/source/modules.rst diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..b9913b1 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +source/adf_core_python.* diff --git a/docs/source/conf.py b/docs/source/conf.py index 9e1cff1..b3427dc 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -29,6 +29,7 @@ templates_path = ["_templates"] exclude_patterns = [] +language = "ja" # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output diff --git a/docs/source/examples/example_index.rst b/docs/source/examples/example_index.rst index cbb5995..06f21bb 100644 --- a/docs/source/examples/example_index.rst +++ b/docs/source/examples/example_index.rst @@ -1,14 +1,15 @@ .. _example_index: -Example Index +例 ============= -Welcome to the example index for the ADF Core Python documentation. This section provides various examples to help you understand how to use the library effectively. +ADF Core Python ドキュメントの例のインデックスへようこそ。このセクションでは、ライブラリの効果的な使用方法を理解するためのさまざまな例を提供します。 .. toctree:: :maxdepth: 2 - :caption: Examples + :caption: 例 example1 example2 example3 + diff --git a/docs/source/index.rst b/docs/source/index.rst index 4e2bb29..bffe1f4 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -7,7 +7,7 @@ Welcome to adf-core-python's documentation! ============================================ .. toctree:: - :maxdepth: 2 + :maxdepth: 3 :caption: Contents: introduction/introduction diff --git a/docs/source/installation/installation.rst b/docs/source/installation/installation.rst index 26c731c..06e4870 100644 --- a/docs/source/installation/installation.rst +++ b/docs/source/installation/installation.rst @@ -1,43 +1,43 @@ .. _installation: -Installation +インストール ============ -This section provides instructions on how to install the package. +このセクションでは、パッケージのインストール方法について説明します。 -.. contents:: Table of Contents +.. contents:: 目次 :depth: 2 :local: -Prerequisites -------------- +前提条件 +-------- -Before installing, ensure you have the following prerequisites: +インストールする前に、以下の前提条件を確認してください: -- Python 3.6 or higher +- Python 3.12 以上 - pip -Installing the Package ----------------------- +パッケージのインストール +-------------------------- -To install the package, run the following command: +パッケージをインストールするには、次のコマンドを実行します: .. code-block:: bash - pip install your-package-name + pip install adf_core_python -Verifying the Installation --------------------------- +インストールの確認 +------------------ -To verify the installation, you can run: +インストールを確認するには、次のコマンドを実行します: .. code-block:: bash - python -c "import your_package_name; print(your_package_name.__version__)" + python -c "import adf_core_python; print(adf_core_python.__version__)" -If the package is installed correctly, this should print the version number of the package. +パッケージが正しくインストールされている場合、パッケージのバージョン番号が表示されます。 -Troubleshooting ---------------- +トラブルシューティング +----------------------- -If you encounter any issues during installation, please refer to the troubleshooting section or contact support. +インストール中に問題が発生した場合は、トラブルシューティングセクションを参照するか、サポートに連絡してください。 diff --git a/docs/source/introduction/introduction.rst b/docs/source/introduction/introduction.rst index 9293e19..455fd61 100644 --- a/docs/source/introduction/introduction.rst +++ b/docs/source/introduction/introduction.rst @@ -1,44 +1,26 @@ .. _introduction: -Introduction -============ +イントロダクション +================== -Welcome to the ADF Core Python Documentation +ADF Core Python ドキュメントへようこそ -.. contents:: Table of Contents +.. contents:: 目次 :depth: 2 :local: -------------------------------------------- -This documentation is built using the `Sphinx `_ documentation generator and the `sphinx_book_theme `_. +特徴 +---- -Features --------- - -- **Easy to navigate**: The theme provides a clean and modern interface for your documentation. -- **Responsive design**: Works well on both desktop and mobile devices. -- **Search functionality**: Quickly find the information you need. - -Getting Started ---------------- - -To get started with ADF Core Python, follow the installation instructions and explore the examples provided in this documentation. +- **モジュール単位での開発**: モジュール単位でエージェント開発を行い、モジュールの入れ替えが容易です。 +- **モジュールの再利用**: 他のエージェントで使用されているモジュールを再利用することができます。 +- **エージェントの開発に集中**: シミュレーションサーバーとの通信やログ出力などの共通処理をライブラリが提供します。 -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - installation - usage - examples - -Contributing ------------- +はじめに +-------- -We welcome contributions! Please read our `contributing guide <../contributing.rst>`_ to learn how you can help. +ADF Core Python を始めるには、インストール手順に従い、このドキュメントに記載されている例を参照してください。 -Contact -------- -If you have any questions or feedback, please reach out to us at `support@example.com `_. diff --git a/docs/source/modules.rst b/docs/source/modules.rst new file mode 100644 index 0000000..d0160b5 --- /dev/null +++ b/docs/source/modules.rst @@ -0,0 +1,7 @@ +adf_core_python +=============== + +.. toctree:: + :maxdepth: 4 + + adf_core_python diff --git a/docs/source/usage/usage_index.rst b/docs/source/usage/usage_index.rst index ee39535..195d806 100644 --- a/docs/source/usage/usage_index.rst +++ b/docs/source/usage/usage_index.rst @@ -1,37 +1,36 @@ .. _usage_index: -Usage -===== +使用方法 +======== -This section provides an overview of how to use the `adf-core-python` library. +このセクションでは、`adf-core-python` ライブラリの使用方法の概要を提供します。 .. toctree:: :maxdepth: 2 - :caption: Contents: + :caption: 目次: installation quickstart examples -.. note:: - This documentation is built using the `sphinx_book_theme`. -Getting Started ---------------- +はじめに +-------- -To get started with `adf-core-python`, follow the instructions in the sections below. +`adf-core-python` を始めるには、以下のセクションの指示に従ってください。 -Installation +インストール ------------ .. include:: installation.rst -Quickstart ----------- +クイックスタート +---------------- .. include:: quickstart.rst -Examples --------- +例 +-- .. include:: examples.rst + From d40a902c968a960cb4a716a1cb224e0f52eccbd3 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 18 Nov 2024 19:49:59 +0900 Subject: [PATCH 142/249] fix: update README to correct Sphinx build command --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 32c5368..7f41356 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,6 +4,6 @@ ```bash sphinx-apidoc -f -o ./docs/source ./adf_core_python -cd docs +sphinx-build -M html ./docs/source ./docs/build make html ``` From 72ea86b69b55f3cfc2773bb63cab90ba91278616 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 21 Nov 2024 14:50:05 +0900 Subject: [PATCH 143/249] feat: add click package and update dependencies in poetry.lock --- poetry.lock | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 58faa1d..93141fb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -335,6 +335,20 @@ files = [ {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, ] +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.6" @@ -1423,4 +1437,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "dede3d08af33fe3c124b63b82aad441e9fb9a8eba7f069ffb30945006c60efb1" +content-hash = "48245d641990d81a38f747cc5e2846dd7722e3189a99b3f5081e800bf253b142" From ba09c8ef49eb98fea73f0726e3443aba52185dbe Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 21 Nov 2024 18:44:30 +0900 Subject: [PATCH 144/249] fix: remove obsolete make command from README --- docs/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 7f41356..8095940 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,5 +5,4 @@ ```bash sphinx-apidoc -f -o ./docs/source ./adf_core_python sphinx-build -M html ./docs/source ./docs/build -make html ``` From 0a3970dcdd721d3f0365114cb19731d51e38ce48 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 21 Nov 2024 18:44:43 +0900 Subject: [PATCH 145/249] feat: add Japanese documentation for clustering module and quickstart guide --- docs/source/hands-on/clustering.rst | 43 +++++++++++ docs/source/images/clustering_image.jpg | Bin 0 -> 550610 bytes docs/source/index.rst | 51 ++++++++++--- docs/source/quickstart/quickstart.rst | 70 +++++++++++++++++ docs/source/usage/agent/agent.rst | 59 +++++++++++++++ docs/source/usage/config/config.rst | 24 ++++++ docs/source/usage/module/clustering.rst | 0 docs/source/usage/module/module.rst | 95 ++++++++++++++++++++++++ docs/source/usage/module/search.rst | 3 + docs/source/usage/usage_index.rst | 36 --------- 10 files changed, 336 insertions(+), 45 deletions(-) create mode 100644 docs/source/hands-on/clustering.rst create mode 100644 docs/source/images/clustering_image.jpg create mode 100644 docs/source/quickstart/quickstart.rst create mode 100644 docs/source/usage/agent/agent.rst create mode 100644 docs/source/usage/config/config.rst create mode 100644 docs/source/usage/module/clustering.rst create mode 100644 docs/source/usage/module/module.rst create mode 100644 docs/source/usage/module/search.rst delete mode 100644 docs/source/usage/usage_index.rst diff --git a/docs/source/hands-on/clustering.rst b/docs/source/hands-on/clustering.rst new file mode 100644 index 0000000..e2f6f34 --- /dev/null +++ b/docs/source/hands-on/clustering.rst @@ -0,0 +1,43 @@ +.. _clustering: + +クラスタリングモジュール +======================= + +.. contents:: 目次 + :depth: 2 + :local: + +クラスタリングモジュールの目的 +-------------------------------- + +複数のエージェントを動かす場合は、それらのエージェントにどのように協調させるかが重要になります。 RRSでは多くのチームが、エージェントに各々の担当地域を持たせ役割分担をおこなう協調を取り入れています(他の手段による協調も取り入れています)。 担当地域を割り振るためには、地図上のオブジェクトをいくつかのグループに分ける必要があります。 このようなグループ分けをしてそれらを管理する場合には、クラスタリングモジュールと呼ばれるモジュールを用います。 + +本資料では、多くの世界大会参加チームが使用しているアルゴリズムを用いた クラスタリングモジュールの実装をおこないます。 + +開発するクラスタリングモジュールの概要 +---------------------------------------- + +本資料で開発するモジュールは下の画像のように、 + +#. k-means++アルゴリズムによって地図上のオブジェクトをエージェント数分の区画に分けます。 +#. Hungarianアルゴリズムによってそれらの区画とエージェントを (間の距離の総和が最も小さくなるように)1対1で結びつけます。 + +.. image:: ./../images/clustering_image.jpg + :width: 600px + :align: center + +クラスタリングモジュールの実装 +-------------------------------- + +.. note:: + 以降の作業では、カレントディレクトリがプロジェクトのルートディレクトリであることを前提としています。 + +まず、クラスタリングモジュールを記述するためのファイルを作成します。 + +.. code-block:: bash + + mkdir -p src//module/algorithm + touch src//module/algorithm/k_means_pp_clustering.py + +次に、クラスタリングモジュールの実装を行います。 以下のコードを `k_means_pp_clustering.py` に記述してください。 + diff --git a/docs/source/images/clustering_image.jpg b/docs/source/images/clustering_image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6fb37ebd113b568de3b21e4bfaa985dca7b0d91c GIT binary patch literal 550610 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

>mPN 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 z z;fBT3(^th4`GjR`bg5OcBLhHC-HQy2qjXdh6^2RI=u^5ThJcHQJNb%<8+@M@(cp=R ze$_Rq+RD9w%z5pun4-X?>fM7<+JL>u%H;H__-;>=5izjgL~~)npa;YtNd}Glz@nDA zFH}&v+9A&y*$#S=Bt5-DN=t~}VxO?|Nq7)t{g?5RIjNdK2@5da_*b}{pr_$Mt`_Xz6 zJLhfgyJ#P+Bh>F^VPW@mPPA^4o4_wk8deaOFB^PmNbsX;vUu6-=OsW=FMNjs{6N{{8LJ1DxH!gb1aR&o>B?=?#WqyQxSr917GVh_l;*to+&NF3?2Tg|_j2{zo$&*< z|Nq|01(1gLa{6pZIPrS(QO?unv2MoD#9lmOt&?Hykr@EWTuC1-%I(fLF`?3jckDAl zDc#TQdfHc-G$mVm&W9?{!VQaVIWZ|BFf=}AK|qUJ;c){_jY?|{xIraIPsT!fMmGH( z9aO8LY|c8i~MwN3Btxqp|q*PUCM>Zsns z(OtJ3A0nWB*ew~IONXqHO;I&q!>^FCw)tniM{y(jcrv}|stg`Fx2g%xY4rn%@Hwai z+KmAoBascFZpP&AgI^*I>?Oz zBpSp6QTf1v^Y6$Q;*)E=f4sZ4r!+V9l*FkmNV>;+g@C_B`ZXK>FdKNX-(coK@>qvQT#2`BB^76qWX6z76!LF`OTI;F zR5Z-S@vxVhoqle**q||t$qWJyi^S5XH9ON7qh;)Ro>3;en>mxxpWTH5qk9c zjp3dpYRJ)t9oXWK0{gSF>g3|5qDQN@CB;J9Dya;rKkxeeG~ugk@jvX8!m^9nSTwa6 zaxM~&X}c&=>!{R~n!hi66oVY(M#0o?fy6t)N0&YN5it^cmtojg8Qfy4SMM$^)H$o3 zWJ3bMWs(p%lyp~^tZ8f?MdS?QdeGn)E`GBCCHHF+%KB0kZz$q;nxpaFM@U?jH69V{ zHQL!em)B59%eXPG*ds9IZC?;xO$ALD@%yc7mx+~3{n|7yBh1Ddg`1in#r`yAU2R3K zIe#hgCIaC<0@-yoG3omhM``MS8{MlX2Pfg*MHG|+jy4mE2MCUFX)^*y#bue7-`Li6 z7E_Nm>9z2H-zqD1SSawjyOKRDqv$u4s)sPP!afj0?;Ud&&?CUmI79rFdkh=N+}%w< zPuy3{S^_6i8VV=vBc#nNxIbJ$0Djlxt;sx0E_`mm7mV2*BNp(NQ^I;S*4fMwayIt+ z9|7S$PviwS!fyM$6%aXXV3I~G$UQe(TMKrQt!*;C^7F8jTT|uIk`DneHK50CQ5py? z&oQo*E7hVdn`IX{Gib@BD*Q6c3C=o z-B7T6C=IXxVBgs`CO*1)#rn(gI3&rUwIwt6H=5fAf=t)X$E})7PE3aJ<=+VuxGHk9 zgoR~S#7r3P!-$q>xL0*BiU_o zQr|P(Di|l5TZ5%CzU{lQ*Ag~Q4R*1CZ5L0UgQTJ3Q z0)C|kr5Hz$MUg&NpBKuF=LcuZ^v!rm6z-wQf(*!4b+$n+Oo=++VO#3;p5}_hMiRW? zak05NMAKC%H?Ci}1ey%v>`zaI4yCBteSf14p4%+HxaMigR0SJyMji8^f{O6i;Bfu*+Z;J4lx$_5I)?b_{V&CWn4i`)CVF$#resf^tg)ljdaalG z-*xm;u0qhdFu}IAy|qnd=2sfnHZ3;^(y`KbZ;Vx8XwPVK6wP`uI>}jjQ1Tb>X& z9*Hyv59Sr`1?My3(Klv)7I^pREHLZOIH$)5H8E_OOjbdXe;Waw-C)yd+iI~0Ds2&lpgJqq&=K`Xgpak4ZKOS8`y}%n;(_P)0;wOGCBcR*$_vb1zI=6)2ZE%?PV>8*go83D7(Bn<_Pn#&qVMzUNtk9itVWor&Wd&FUPz+!7E^ zMEd-|5_sc=C#Bh{w_M=`eKKNioO(c`7W3w+K*%oxQ|SV_c`{BcuOEL|o{$OhWR&No zIaNXJQs%qlO(QF2`-Yso#DXe|&Vehy*-8Cwdg)yE;}0y}bGtq#htxjxQR3I_66b$Q zRy%*o-Ps6QZ^Vu|vdpNn?Eb)l(rpo4-!#C-F8Kq;nK#e>+s#AiOPluha;Ls*A`q-s zdJ_32(R@=!_vjBX2k~}~fgqvVaYuvL*G&NXS$O&WjSI$cPj=Pc z+apcDN#O_tAa2XBbyNK%ukJ!l{(QI?0g*SI^nRfTk0z$(#MHewHLug^WR1%;^$Q^Z(yk3jtPF*St5VZ{X`ax)0W0igIa&VXaiHYQSSrHLJHYKvy$h)o( z+_GOermOp?HN}9FGcun`fGW`S8tZKo-QJYRTj(zv>U_O8rrs_q!?q$&6?>O$;r!$q zO=FR4LYsX=2HYnKEq=`+@3oxi$@~fxtKpTD6P<3qHjUyLdBNhHJVXhl zz(TrLV(M9}m}?Zab#cA@X=mC(S0Q-LRq0^hoTfHZ3D$iSVj@x~bYN+`*!@HX=Eg0! zv*B}|i`WOxu$iopn8>z}5pFRx8(l&q=|rkIUE8cY1f_3jozoU~Q>DpYr9W^qN&I?< z-m$TfTdg&3qT1hQMz(ofwo489*`j`mS35vgA=>WgqHs_4Gud7et((3NaIFLQ3@vGs z;B&lOgmkncI_UVjwq2;>crylme^;Td!Fdr1cuGOz2m`Sr0>h#gmAe*c%E@-cfQxPK z2>Roxa9T4C(SFj7ABfbQ4xIc*WwI$NfXW+EOO7Wc-5FkK~JW9PqIa%sJ0m9!~7Oo4(#7( zMHDMAy<(ia&CDk%;*LB1B&Yu#o9gr|iqcw4nMpCYt61ApV~dw#JWv)G&DW2<6Y8c{ z=i*xBF00=W^}H(ck}_JFnKf~j;|_aTPjNjHPV{ug$<(r;fqf~vi%a>YI3VDmH634QB zh3Kbgyq_-1gOIUQGXeOeEMtvP&+4b02jy~$k3AVwoYPwCNniEpH++a$E6%6u2fE0~ z^^+K`53*!ax@$Y}Gx`<*4FQhsO5Ubifi%h;As%N9#PI^5toshtGS0>F#-Aru~!M73S0LXgbmM4y?gRd*kE22#qD4eKgMAVxiBXJ>b=>5q$PX`E8 zs+qZ-NAOt*YDbJk7)-Tx44$?Q_P+qt_okT}DW!^Klu4sq@i`4Pw8G99yVHR*BHt(> z`jv4Z^qOtb&~u6}G0~F&Y6vO2(PI2l=9x*!h$+;vhjSM`MakSSxFqF=A&4Cg5%eo- z$Y)~42Sjfy&V_nmF#Q-T(o^v4jSNNzTL|RYKvdQ>Q>HIkIxp}p;)NR#qWs5KXHX9nLEji0*py- z78F$LFzw(0ai0Q#jZ=wd+guX6nq*sc>$eZ&?5jfL>a0#d@vGRE-kt?~+wK_@%;s!Z zVHbZ(>;HG}{`{`t{-B5dJ^ut;pN^r8Sxc3P&Q{o;7wvIo8*H6NsCq7pprVVW;7UD* z$}Y8`z59&Qd5(-nCw4IdVCEA8X(E|i&iG=r`9_sHM{3Hw+e8~X0Ubr|$z<}x=^G1F zfCi%4?%)+xT+<(y!eRkWf8i-{Q*_HP1wdngf4E`C%;Vsu^RJUztc@{ML!`i$zUs>x z?Df7|MSQ=NGh*LZ^oI3Fo+Q}*EmOxuOAs*k-#bY8r?sVRR(9R9r_7aeXe#iMYi(x>nMda+AiMTuGBFs>~*)FJFaFW$nLT5 zR?aKwTdnop?qNYy)vUW#v5~`i1)y`;%Bo?q)m+eamF<0f@6?66wtCIBl~qV`Yi!cC zYMk_#Sr_~l#^-dJd#qPaQ$3s71U@_3ti8FCM{BFVa;6vMPM!|~Dmz|qv!i0qv(kzb zC1aCsL9u{YeEB1ES@Mn7A6Pm7$=p66k6%wzE;{Ij$L}Z%n1RA}z!{|hJ0Tud9gcji z7vpmZEOn(YKDfk?cPlrwGL3K5l@rh-`JY}kDutDca00-vHnV$lxJP+&#e%(Z)e?_rF3BEsrh!BGXPwK1o0l5keBwcRrj|>cOHN#4f4d( ztxL^yZ`wAHZe`s9o8G{!{s9oN+085NTdm#6yEWb09pg}V%rLWi>4*eE61cv`ym2i< ze1x%c-AxQ-Rd7>FI8@P}BhQ>sk?xj}n!x%3piHc-ek#+!esU*!v`;PXqymgSISx6y zDLvo()9#+~@j8EFSoke9M9z-s`&)TKTU}dKX5ir`TC^vuI^CU96raJeV-1&>LHJc~ zZ;|am;P?VtSGSsWJ~67JLg#sDMUopLb?BdWHbM9!+NulxmFMV>fp3e}1OT)K(5$}R zy7WKYp1!l&aJoezW^_~|=A-S(0sP;*XsClff+lEdJkS1*Krs)IH=?)Y-BL-Vw#Lan zsE>c%DvIW5#+~bZwm~jG-T4*G)Y5^IFAWJ zo;Hfuc-Pg*Dl@(!*NnYa0XZLI3(LrLdPeqg?!9uwFzNwroS=duU{;3o8~Nl1JX+y2 zZdk)ekdadTiP2Vfw79gRM5Bkrpv_~wIdILdLzGxIU64!4E9>f3B~r1ZJsfhQTL6QW zt{rCRo8E6SHQ8HrSxH?gUTsdVtoeI=wWTlxd}L%~f#GhiA2iECQBlu}&QUPu>Bz;- zfo_P2(6JI(m7iuU6kE+1eA#F*=bHPMhAG7+A6N$4Px2eaUo2hDl14bZ>Uga0PxNNs z-Ic?1XiBOx`HnLT-PF8(PmO5Igx18ebf0Y&%r#wvPhf)sp6V-t@Qy46PYm>f`BZILQ$YwTA3`SLJ=NL6>J64YQiwFsTX;98f7d&J8C4V7fS~ zRe|;jPG@d#5X@SH9~O-%v~v_aKhB)qf^GvaJM-INJaB5hYK1r zFiLi5HaV!8flSpm1vwqy@*UEN?Hs(FgK&D+t*=3`Sk>{=_o;<&n>VR$h)BU|ASwfH z5H6~@X97e8@Nb-Zv;vuuG@en?EFnDZrPU$-oW?J2<}2SFoj}w58WUc%=aG65y1TbH zGUJs^+H|5p8EHTbw{cRs_$K8A5aJZmCQ03E_1Hg#M6;Nk_Xskd05xfOMwRk|EYp`8 z$E#aQTb)*|c%~ggv1mfQ>HRCs360$Qt`YgM#)qUMQ*Nm(&iSiemgxdlyI}gPV}q?# zN`kB3$6R)l1M5Fav!b9WzHrwxfzIl#LT;YAJZexfGryvqj!zeGj?HcIk}oE;h7)G= za5;S*k6RzdK580B7sweH#Do)1G|dbzwxakU$|?r8l%CMKVH1z1nL0E#2iZ zkIN&rK_{;kx32Q?jWMHPPWS6C^P5gWV`J>~@O`|XD;T$|CdDMV_ChaAM&wKJGiJPx zSK{QUjr>GBK_*s0B@&(r>AW;L5QHTjKoVBZ#x0FFh=Js#z0>P7LK!jjsPy#u0VVT6 zvA8IURjHT$0@}rzwL=iwR8>=MTNk;zcABZR1{<*f-{qw&%Qkx(8|7OT2c94)BL`VE zY^zguGr7wNQn-b=^A8a1`{#wym-eFv&?M7}KCq|e&F+vc%ox@9ju*1;e1JjMFsQC5 z53JHh)R`d$WTX~{#_B3K%}g$;(AFN?j+S#(#2vj3e{@+t)h8g8Dg4!xV0%1A8CkRP zMIUXd3qSS$vG?9#O=bJuIF=d50uF{EO=fTe0YO6uHQ>xhu_SPigdP+aLJvK3vC@l$ z61q|XBoIVu2t~S5g%CmsT{?tbr1@^dnK|d4x$k?Qd+zV|Jm-Dh{IS{Wz1G^_z4zK{ zt#A8$h&CZ&27@KWk|_(4`7tPrh(ugx#cfm&8Q>i3)}fh)*o0VI_v~*?Zh`m|=bydh zMpsX8&9SSMHU`Vm!7!&Si87D%naoBIhh!lY67fEt_wqmqD1b~EWN3p(1z^QSb^VwK zJzA}$ij=B%+FNA~h5bqHaO)f@Q6-y#8<(XCx{~xm`(RRdg1xpz+!*a`1hr77UfjCC zW%88#Eq!M>`wo;llOTfc_SCtg)nOrR8lR*8VQRBXt;pl@02PhWcgc>=G#o+Z3BjEj z?26ajU589Q#+VnsIF4=Gs%vdpo5RT3ECH%d6&ixcF5nadpW{i?&Es~dg2mDg1*1Ah}6gE?2(ib*ezuO!CRICa4^A2)2(XDykSe;EBh#2`UB$ zH~q|acmEjxzi%HYzc=4O1=Z4oKoMC>C8NqQ(-f`w8xCk9-MEWVgap{^0KNX)H)qOc ze+uoQ2i5BAi3uSapKuf72-$?JPKyXZ<8C0V^7cYc6=KHq$yZp^gVz7`?cra4`T5(Q z|EV2e9jxZ!ofo?svnGhAN$>VfF8f#R+6BLP;(I$^>pr_7+p&hYs(bmL*$gP3G9I_K zZm&gd)wj1^68lwF?>~n~NB$+P**{(U|9s%T7HuuqZ_gukrFSdGObUftPzxTUHKwYA zi1G48E)ERt#%BpZ?rAB$asKffg{})PF0WiGjmWe|uhu$DJFyOq{L&raw`{p*?eYD5 zXeC}gqqeN0y%Jv4ddx_1<9+O>SIle0(pz^2V>xCuRX@pmW|I}No*WXrGZE;_(L<5QCx2A@^eJzoc<0c*gUCk+xjH5l(bANn|rJB%>!t}Xdf zN*W5!N-Ol7Uz0VzSlgj`(rp^Np5_(f6*sY}t8tYNw+panBHZmbFAc=>CM4Vnr(hB~ zFrjV>M6w04se4@RyjEx&N>$}z$L&ys(5?kdUlqTdd+&Qolq6EciHZPDO$53kbF@0$ z^HuNb{)CS>AVHssbE6p%O{lNEz9I42g-Pjx;VpLhXAF87na?v!8FO1!WfCdr{{2ri ztRe}rVHlC~p<2Ng3}ckCI&&~07Sk70lj55SM{4U_U~5IuHkr3(*9mh8ds#aq&7y7P zE!XwPZC4^OekPt2N6a3J8;O>U1f?~!So-=M*(%p)9`~6}ex$E#`o5~|Ok=UZX~Z3C z08Ix*z{GXO;Zp>~m9!sxAlpY6x`>*`Po>1Ci^;y#GRB+KB8;ap1H>9n^n`IUVOFLQ znG(&A2=((jLgIPb_gY&t?poPtIr96*!wz zNh5s_Z^S-*X8X(rI4IL~4|LzW{xROZ2Jt;Wdrw%L+du$WH*>fJc}`!3WYp( zFpBs@Q2juq!`!qgs4Ts9?}Cvwq}z%z=pJi^ zHJ2^I4U~50j+=nlH>53-Z)bPER_>>EO|EKMKpPu*WH$0H`x&)(US0F>ygGCD*IfC` zcxF$L5=|K6YvmOUt`4QNwN^XeoFCL1*BRH%qCq*1X?o$mN)lmxKuDWjp=O^j9P3xi zJ&)CKVTpuT*Y%QoOmyp2#qEomPoE8stq0;qI<)qnOSN%Wp?em5_@0qYqIp%qgYJSE zOnIOXT(>f=K=0{{nPlQLX>`%Y>a;qEA=SaBacAUhz3nj33+W?&{~J;d>8zg;wC^G# z6j0>FtQU(f1Gv>#HBC$SqAYoy%lW3!CB~go@{;mPdRMyCJ;*e5h?1R5@SVO(mNRy# zqCo^Q$9E%kl2y2mHs~f|WHL&!QCIK^V4y_-23lia$s~R5qpstQHGVAfEzMjmlFwz_ zk0Xn~T+-MG_%V}fw%#PK{lcuLGSbUx#fJzePUfU&xb{QKVeSBm?13+2q1%D2wKj}%NzirJd1*6?{8 z3Mo)yxU?c^=lCo#0X7Az_dKufU~71mylY;o5T$A!9whe`QkI2d^R1% za|KT_XyzIyLc)O+V)-prIUH`qAy`*8{==!)U&rhtza;(k%p1U+_x7)g;3L4KHY3Il z(pK~j(pFFqgf2oSy;1ODv{kQqSuP{AXu*~MCBn{xl$PWoh$WUo`Q8a#3qK$J>Fr<7 z1OJ_)hyHI3O*~;&bF?i`P~s3F1SNPg8}d@(uOH9R#hgK{L;x~viuAGGJvv?L@5 zL{Cy$cr)(NZ}}7r8Jhyk{47Xd#+H!e7MawCB*xpNS`(oJiyM)G(j(4{OCENDal!9b zvZg6}lf~E$f9_Z&S-Sm{QK$UtN#4bhMY@eQ9k*jVgMoU(R3!#B+k$U^;@_S8J9(6( zCz9GD_F<}m&i%d@DgFmiwm9@_*~2#R)A1G&7Mk+)SoiqB`avq9*|={uJ}t0OI@+-y z;MWFWZr9Tg%0!EnmbP*NrsFdk)haQ(bp?PT87OL-1=Xc%grZ47n|^VFu@^VwTFZ0E zuFQV9$T9n_~Fwo{s-k$zKE36eWb`)_;h}SwDv){$pewf z-r~sTN7m*4{}FuYds@Bo&7~XpEKo!K3UK2k^<0>GBuD&1$Q>=jgKZA5Mm(y<(xc-AwGK zl?RW(+yPf($uQ8m2U4GQeXcolFyA zok+S(&JMpA@s0N@kI~!JgA2YeiCNeUkBqQaDfE+x{4~(H3)nGINL39D0lRcI;vB?q zar)cUgxBw9o_rb}zJ?3=$ZWMGXK&Epwo$X`J5z)YK**V1)bxVIyelq!(qd-Kw6VeX zSO2|&J^s?MK#kflR)_h3l5V;#hpQln=w+Kltf$Z-@cO-wD8{OLMR8<@)#TNYBvUJ# zh%6zx|7OKHT2c})Unqnaa`^I^fwtek5g4?0k@h57^q}*%VrZ~gTC68MI z41z={19mQKFqy&%X#TAj0xFEf%)28Jxj`BFvLmF4K(nSdu#z8Z)N`cbF9wZ~vqn;i zUIgR^G%a!&6sXJ18)sf8z$CehC=oN)lukms-1?tYssgd;<$&*OB z$`WKKMZRSv2VU3k;@ojvO=N#XV2Mks7eU|7$0b>yI2%w`Jx7`q%-!&}DQX>xw|ed_ z#Vp}ArnVZzw3xb_vJTRs4FwhCy0#R1J!NaB9$zhyE?p!um6!@MDl|xX)nG1#sJNIa zR;w+*t3a@BbB<~kG$r-bcmQh|GSo}y3N9-NVnPj~`fG){5LZX(_S_S$bU+W-x74t) z-B{+>XwSG!N4lC0B|?=9^|ih~XZ|-_Ag3Pf%i^4{!sV>dLQQ;XX2ncG)`DlTg^OeF64= zUxZNWFj1p{)9X?V+Yov@lnQeRV%K7OOKkZUwl#?_Y-{YZvICjo2Ko0$P%D1L(g7<& zsKL#NHGF)owxCU2XK9HP9h=*iG^;O8(OUa~{meo0{}bE)JEN2j!EVCg?>S#`5?`{h ziGzuGP|rjENrd-4Ys_u2zafFQmEB(Ke=@~PiG9Hft@7kuI3}jYLET*kiGq)x4|Nc2 zGIzP8uH3pi%e|;;x)I>@tT#Bg)APbAlE$qOt)?w;tWY3DCz$HP_kdbXv@j^B7)#do zI9?>;k6mOofMR~J@Wd<4_{y8e|70`jO3Rnb9Q}O z;WxH)ZH|yf5^!5`xzY)^?THZh)bGBqjh$OcdAzocHVK;!@d*2ZalaPDJ^3(r%o2M1=JCV5Ea- zkLC7-CnY|7+zeDNJ~iZgfSd1Uw!%*X6|eeE=~>!_WR>7@Z3twA$3q@`q)eHf&Ro_y zS@_t4W(QoUR}RV*LjAZES>Sog%{X+a9(i zh|rntDBl<~NPLra^6QY6@4&%*^aXxEs0;Wua)7m#Ju0#;%KQB{>P zlKgF4PcDIJoNC$+lfNaC@|C8M@J&zs>9AxEoEHoM)qXOQ%yE1%o4an&F*`DD!Zu`6 zA4M9_G~#D#(^{drYBs2MDMdG^nhlwWZR|}Hl}!ap-oCQ{5Jz2|bLaC_ zV`LqiNjN_lbw{Pns46Vt3E(pb7qw`ZoCjF^WZGM2R9=+0$OWv=jYmz;TNZ75XhrgX z=|03>cfmr7&(0Q@{vMC^P|@WR!S7kPWR3<2@zAcx=bCo5tX8-BSuRKDNW9e(i2__g zlrcrqLhl8KRU^L=4@&!&w`RF?6NC=jxoq>~hBWZDCKmTP?C0e8LnQOHbRjW_V!wrA zf9_RpN)kG3SxL)3s6azKh=A3pB!r)^Zwqs^PrKz#+}+{?MIuZq@rs2tLG}e9T4R2AJR&>dO}c2k&z;*^0GqK zEH!A)#?m~KkRR0(oHP%!>zTs}OEk00ow?O{9CY(8x8y&_^t-p_ubjxrOhmZ~SV$JR zcng(zY=|fjtb$$UeWE6odrti{npdB9`Ra3(BIP@weS*jZTVnT_xXw@=CA*a-IdSRV z=uj<}E%pRi!_UbBj5x)GxQJ?t?vG3p;8 z;C%P7zzB@O&`ng2_W23FRuL=gn|bKm&S`>JezHa>mMi%KKo2LH@?va0Bh#yp60Rz_ z=4nKjv=bGxg8ODo?zu5vK(coh1Qf&nEWXz&i>YrS+VR4rFuu&;0)VXTg;WV3Yy) zorDEpGUT4_AE+2CV~ zmUdF(?LL+JnOExCBrj92ttdDh4b?olY(3#wE;Yv^F>!7eglH1G5$t{GiwYjM@NrNK z`jp4;K(_y~sa_A50KxO?PVazBF-w%F+k@y|74F}*(9M7Hvxqxa#5d;d|9rEDINIz{ zR>Ejqt@ZeA;Uutiap*}@GtyinYp}mtR4DgRP^dyF!Hq=Siv~wI8nr){J2E9LY5Ckr zei_z~hz7>$heWH8ESyk$`Z5=2mDEaJL6EW$zak*(CF^=predj@3Ai zGOEnHpO|Kft6h=th1hw_;Y3w=KLk>%&?GG)wyk$v-|<2_*vp;t!X=b@!907hGRwEQ zxXgV#9sz*>xGV2Zi-i$ng8h_xjf-0Q=3Dmg;i|c(;EkswJFvdqltK0H;(QDVVg^1_ zC8{bWlWKX9{(9xP5rzf5$_HnRtA+;`y3xt$YZGA1;^JL6ZYKiire^ zJC7J@d1LgPof*fO$Q5nnrhE{DlQXBC3!GRokommgOjWqPVijpjTiG(Smq|MU03qZn z80DH_+oDjwJhq$YV5r!TYfT(*PU6#kZ@6z}|J&IcF~4^8q*~RK@wGsSfGN&1R90;^ zb=8D?wO#*M+ae_KnL7C68mfsnMg*oZxiua&9wS=49Zqm9RK#9(aMUFg;+#R9T80gGVv6sIO^Cp-KK=1WPJJfXI$v@5wH>oxDVdMG~2rCfAftg?4V7~lz$)4V9~V@`U*~db=AM#~S*fK(-^I^t$-$fcvG2BRuu=}m4(hTCB4E&huL+|foxS8iVODzr=OQ@Rl3&H_FtbS*iJ?b#5YwoCc z10A(hLeu9qJ$_ddp93ui(kmI9wSu5gb#SR9Ub4Sw)1n$Y;+${bRifOHUh(ZoUPPaV zSecoJR10~0qoiwA7QQ|6yJub6h-9$1(}vr(=TiLU*B}tJ_R5SfIZ=nUc_YYI$g@R* z8uNLJOX~f))iCGLJMN?A779)ie@rf1!Cl<26L5Ib%XBTmMj+~E4Y|DHkqSN_b&fP& z>Zqa92;v^Q_2F*$iJ!T<@Bj07@ZUP}@98SPiBsnD`Nn0f8KwQmfbkFwy4<5Ks!75*{+y8I+S3> z>aQ?onot^X;^7<0)^Y&ZU*dFPx^vjJh4ZHi*>xyclYA&#V;O_NFWQDz23DDOi?-@H z_7ld-N+p10$c69v93E!!ncQizF0!3Gil}Tzf?-)8TlG7H_459D6K45uW3SJKQF&e2 zgVKClf;)Y6-!*e|dgiE}Ymm8Yn#ydU0g;(>g_-1I zZF5`gl7-?#XQ-tx>-r2m3hbTvV1=rhPlugO>RpT4kofo?kA*i;PQ3~}I3JNc?8|D; z!J^(-o-#eych^2LiSx5?Az!;_zIL!r^b}Sum=%b2;k(9rurH+h_R~O$0h9E!rKGp^ z$J{i?+XVncQQxZj&J5v5`=zahag#X9FfHO;*m8Rg9phsaW$7{6+F!&Z65YG$%2N*N z3DIpxn&=I-nx>UYa&`~XxC8S1)bAZ1i_o*XnH_H>-oh=zi?VI?R(uF@uUJH$h9V=}|Zy(5c@umH$I zVZ{1v|4-1y_0Md0^?u(H%YyR35W8wi2Dt-LVJ{p za~Rbw1J1eJwcWj=#deT_z!n#8OqpsbEG7iKA-F*k@GplDHSkiPfeYMJIsaXf~>Rd_3| z*8XA}#%U{rIq?|=g9^yxOE7byOAvm#*vp{RrBu~ zYL%;XVRb2-SM%w&9d~}zME4jgoTP*)+(pe?Vw*EPmkH6K5 zohKEgeT{Le(5@OK1&`MV9>s_$IJ1li;~;gN#9OXqJTtK??@5>56R2mUC1xW|kWxTq zl!oyx3?G`=u2_PmRA}LCtsVlN7Lpp|o7kRe4AR0p1dCGl*(`B^X5L8_O1Lm^?U45X zRWUrc)+C7Lr)HDQM8Xb}m?MAzVV-i)6OS9?LolaftelisrLseLL zN8^QURqyKGdSlE?H%>mPaPd{rO?8iEA++QV`HD~RMR&o;Ffl{l`Uq@?Esu}Y%!1TVGEY-OdUW)Iso zl>5TxBh4j;&FomnRjN{i_gRA=h=H(@(Xfj%e{dxTNzxaeBHc;=w*y?-$pr? zE@w5|x8;O5Aw(qJ0$`67`F^m1yWwwBbGgy3$6c`c;30NGoBoIO&_hVAjawZxTTRLQ z=btU#0~i|4jBnAn{lf*%Y}F(;D4sEMi#~0=bIV`cx1y{ltElYTt2cs&w6-IW;_?HS zk0t|01UMbNDmnq52Wj$Gp?C$ek276|3cidWUzOG?FFpz_q}!5vfsWCusDIpdk@5Gx*w2e!(8qytpaX^X@HhSP{RsmeiRD)(P0LEIkDHg%|d(a=j z<>_s!YY}{AC{e4oj;JG1K_39|51s9+>m4UI|i z?7YO$*-I5*A`xX1ZkgBVAa2%3?m)M47uZ<-bnpUMCfS*k2KYhRYD!7ZO_|##BkRpX zz1e#SqVD)H`uXa4snGZaQ6(TIw2>6+bHpWL%V%VK$>+>&3SDz1GN1~x=~;pcEv)MR zSmr`ZyAo}JBc{2CKw|uj`+uxK{{GDzx>*{nYbtceVD#^f9wN5le56~a$vGPQ%poH1 zL1GkaT;^6RAcCov?}R#=o(bM4st2-)&lkW<3miEyRDGL}EV3*<<&^YDKI4*>QKP82 zi!VzF*Ez?YP%N^pjk!pHAobOz@ge4XEy1sS%;}xl~9w3fAPfZ=GvF0_EbzhwzLbj zEH~X(x;FuW#F($8><-NzAA8R7imdBvpC8VmfCH(O#4_2jBq5ve;%JbT2*!821Aq~E z1T6cmWPO|ZfNlX5#OgyQvEus)UYxr)vQP zNxy2_iHMrRomcEF*Ry$Cg=p@U049-!BDiOBtYn3fNcCN(eo-Lts2m}nD3QSa>(wb<#9JPU6$D^Y0zgPyuc z;uE?S-4usVCY#XMv!Dj)@;6Szb&KQj9xI6y8E`2TKj$!qp6(v+o5;3!SkO_Y4vsqI zixDwk5VFN+Tjes-*>~E$e_FE1WFRjhk%u2kBR^Zw&uql~U*SnN52Ih>1kO_4f=&cj9mfJr(l#E_=xrw!ma7cM>U?J6BUEo9Z@FrK_S+Wry5UX z8fT4JoPJz$*Fgut>0>n8dA@phk8BlCi*7K_HOtX~4p5Og>fn($M~!pw?hCi;=bIU+ z;ze8MmwU`sTGYAyLo?i37B@R#QYiB|UGG+Iy1I!jSAiVO1vX@{EGXxI?sMjmBq+gj z1my_6{xS^{hPiqxH7&kRU8nkl=V(Y$G)uT;`6KJI4cM^3)(w#W^ytD^|q!~<4u`qZ&s zS`Ma%|7V@}yJ_+tcCX{C@~hT=`fjgfU4F0bfz|?7Mcca#CDp3ZBbwKN|8tpDQ~lNpa)87#8(EZVde$Pit; zN1@NBd~!TCo22!bjeuy1OFQAZn-UZ^GYDdsuXQ}z$qrnLrw3GM6*hz54Y{RV?!d9k zz_o}nnX6HYdj(nnlZ1OMmQw%mME(cnYI{$Uv2)gJ+WAk~r)P!Se)AU}*|m5@nNG=% zDjw5eXlO1c3wJ9`z1!2R-D7d^aqkg#W7``M)ohzMD(Gg-7q2L;o4n#KO}-*pdQLACB7)dm0=$H>z=KwNZC zn(e;PCuzn?T+l+fhuJJyfurJVsZUbnX*h*f0)8Mg7dq9Nj3)vfC$XA`4jhx4wy%rs zG)vG52*QO}22m^*4^plFh{l^AL#Ycl04Ag9L?gA!?6L z6tf8Fgn}>LdQg0E7unsJu_%an2_#3o;IDZpE_h-#i!ssr#8B_sa-`X871uhlV>nVk zmIl?DW555z3a6WYbVKXyr=+fwpW3Y3Wu_VoOJsGS2qo>7 z2o}#8(SwwEQ3mgD~#N%E4)Ugypkm|4noO|83w6U&|a%rxvS(r z`}32yCR)^*a6cqZ_XtDEzFcs|^i}XGKqnp~R)#y)!a6wPeoFi0K-AnXD*IL4zQ{n! zGWYv-F4O$@7XdTB6ix!9l($s=UfXT%blku};hD23zfi@Q4f*M>D3#|11%y#N+4#)x z@GsSJF%Bi3Fh5y0n3NkGJ_}LhwJ@jopA2d%!-yxLoN*EqO*fB%+w*PC#ZKGmA7;rS zq%H*4;!<X1I#G`stKm}q38u%Upr$Im<&}g)<)@boHtEHm)=3Y~NTL#)Z zDBM6_9@ZOLgo4EMXZkI9%IYKvnciM^+tv?AmJn5KPZYQmd$r}wDfm!xKFF3>IHVC@ zZ}NJ6bGLaN$2zgA;0x7TKu|(}m29UkC~Z8fUm)S*C$iX~kcdEBE5J2?0$z3_iULZ! zWxJd4)`S2swBl19`}Y^>lvT>bW3ro^(BRB(rZNNX2%q6iusS=pBBTcq_Qnn*TSvwV zuDkBk0P7GQ#RS1nP@)y38+5702Hn3Qf)C299*LnFROq`kRSG-U=cwqSNUose&um>m z9y`+G#>yHT1H5x4#8q8^t<*_D5=*B5g4F)Cs(N_pIgO}CIoEZUf&wgTjP|jrwvXG; zoc3RTg0`=3J3-nyb>c7eQ#SkN9A=;>#^dcj?ZTMb#s*M^`QJJf*)Q4taOt|gm>HEzIY6QAUD2d3p3*HYJZ6_n%b9}Hs;P}lM)IGM`OW*`r4}+Qp}Yf|JrZ&wm8>$?oH5QPeeA-!*9NdVzmUgC$PRRWydB!e zsr{t%6$-2a!m~*285=z&7B5MZ4Z)m$ni@~QB+bnVLvq>#E{1r}98Yq>mlmDGd0c=@ z#kAwOy6M`-zEjm1Fp|tvu2I(k3!QM=3m2VSQaAR0u8UTlN<<6tPmj2#?1{)E%HJCI zNJb7<-d!3jX7Zl>Yn6Xk`q(%pG z(KvIO)%_jT?bt!`kg!WO!$Sp}ke5u0NjMdx1P6$%^?A>ah563nM>BVAH$Pdf#`u)Q zG>fBob;A8-veJOQ6t;t}>89*}Q|~fLXn&*RXWO3LFr&XK z^^WFVYxN#^upz0YW#3sXIgenlr(Dqavgyv*ipcQqWQNO|$oWQVe%Tlm%qT^FX1nF` zcP$FpwrBmz?a>@gyBe*g&jz$V&wdm)fm`a%N(~VOI!jF;XHXF;J1BWAOqyqnH=9WG ziGM8^=sq)KpBdeeyI$t?W1GWzlNXwQ$S(7_kWk7_n;vcN4(S8Oi>QOiGR)zQL#?+A z0I#Os?=%>e79lFx5c{&)lk2`@?m>xveS7rr!~3W1|5H1vsm#dXDzWW_?$U%Gm7neK ze*Dblx5WTip4iMVPh#VL!*)6xa&OmSFW+_L*71adjalKf(3E%st;g0mUtB!@)Z+eu zEWlm$Kk|Dz*KXlIcb#+hi_h(F(xA9c(leHupTE<$5l{E8z;5b4_rT}Fgf?fVa``wDy?tYmFfT; zvu8uD88!>ry=p1lRqL8kfSz($`irv`MOT=UbB@aoOZP{896=y)H~jyj+nZ}CxKuY$+T_$@=Z-=ywP@tSJ;yuFyV}38D$5t9 ztzTc3N~-=%4ihF<#LyRNDqUR?p%yu)gE}_eSFo!#&mqcF#|;wg>zSI|S9#8F+!~cd zI^~&?*No}yJ~tA(x}hB?QIQ)-KYpL3HuI023u@Tie%EytsS`@biWf5=2X}f2Qm^*x z;80=2E9=rjwQU6mB#peT>VdgU1d#NAw9(|_Pg>-Z#v`T{BH(o&KXtk70HBGpyjfKt z&9mgobiFO9wgdnbAi^=YdekY}CMe%!=v1BKG@d@SxJmLDQ@ZezmL1!r`+v%ztXJp% z-EdVx-zYhW5+gFt81hOXTjV?TlZ;W8K zXgn<&u7j{>{5F5=zKvY%<{iGNQeWq5RTFpt!(_C_aU6vZ`YgQ_W*tbW1Uh!)^tRIW zRX*E#3Zc>=8y|Q@=qhsv=%t$bz0?=z=D=HZQ`SA*cCTgO8lEGCes%w|m^hNToiLv49<={i{N8}A*{A)_;;%0F<$Azp zaesPm;uG*$Tm?|d9k9(KE~n(qO`KZ;SYCDE3#-G!H|(qps3tn_jbOJQ=# zypM!GjmsoV! zIkV6{iPwsQX7W-)nYY~0$r9yS3KYy*i^|qzBccrjpv!IXypW4jYk=45p4mpU;kI1_ zGaZ~&R1m$@6Z|&vHsR)3pGMC1&NH(M9+g-qRqML|#?HYaaP3Nqyk{=+jQ~y}5eTTq zO(d@;OLH3*>HpOKVZv)-xF_t9R2@bEh%7#R)()I!n`HZ`^ux|0;gH@$ltI{=)&44& z5U-Zd8N{3Zop$@n>he|9HM%{$sh+A`H&m!PoP!a>l@3UzJA#~fXU__`TvNy*6?N4$ zB8f@9)&?(Jk`W#m3MFNO3}b@$^o%X776p@8V^NFxr;-!X9#vP2{c_DES!17?OjrBGv+=nah5*B7(makhq}CW-*4tWL94k}b zOOhoFo;(J%GifzqWS*ZsflCu1wyTFRj+fI+`@f6m!j?R@H1z9fc5Hy~fh$tuJyU&M zF9OKBXW(_`1q_QnR7 z{h2MBx?p9nvGVq{9Cy9GE1xaNgT+8YmWXlsV$5xoJ^_95xSVyJdfdipy*KKd^s%Tw zi}eD&H;S!inXpzp;&6psHTojicrY^5*0#yiCASXOC{w4a&b)BeShrUnS9v*qHKxuU zbHx0Or01y%N(1=rwcasu-brDx0WnO0y%euaV+ZDAV`YiQLz@`PRcx`NbP=lgQ*Llj zgoMP9_9|*1`9{H$f&5m7kNuL74f=ZPHbr3Vc9~D-%4W!AL}GSa93wzCx1;1|YEfxV z?Fh!Y*+IKJLTYDo{D@JJ#+=71a?Zy?l0P4ey#hIT7Yib4R*j<>pqz%Mg9Bw`6!oTZ ziA|B#nvra(cK4t_6LT-n*wazcb?cJ5Br}@&YoqMl8(aOgUdAaOW*ynbvHsVxO+VtE zL|DIlu~rhWTypi**IKtL-p!vB?06vdwODSsd^Ba3Bg5xeACF?%;eTvg=;p@x!b`3i zM^nBQD_$Jbn_N|jW@%a*OoSc3dLV<4!$^5<`xGEa#E1OWvuEyoEp{N!j6Ox^l|}rO zpLP$50oD#w&NuOrC1?~vH}j;?V#!5dR^;2SQ=1!IVOChm~Umn)~^R50lU;o42q5rLK^@shk{J zb8k?ZB_{BYDrc+j@FiGha zxVEp{9|$3bAj}o{ypstHXavzVja#2x7Y0uMF}(Zz*rj2)*keI;sEQJV5q!BJWRoaD zh3bM+oRyS=GN9_ndYyV8jDBhA{+BxvoOR17;KWf4pMZr916g6ZD>IYQjAFcU&wPBH zQNGqz7M^S|5y9ttBV5$n_cY=S%dw})_qpy%Jj*}MY|${hpO;`>u0})fdw!TOH4ICv zlBpdX4d3Y_OAO8uNcd2+6ftX}r2)&58ey&as!+>pst97*XT|_r3+Cgm4;A=x)WTu8 z4E*WRAaNYoCSU|ma&+QSY_A4_lOOs*brCZd#RC4)9Oo07ToUWzKmSqh5ZVd>9sc4a z**|ugBlGMZ=h&j{7ksMXb&w=0pMjlD%7Y~&F7nZu!&{u-_kR}RM`W<=U)tgfKZxC|i?rRw z3Uov{*VTJ=uAaSo_~|$9FaNySzqa>pi5q`L=s;n-TA&jCnJxbZU26Sh*EcF{k$Eh( zj?VoDa){Kv-K!cU7k;2X776|>GjhW?MO^L0Knpcfc-3wDZ$E9gN>4ZL3aQepw~Ov9 zBuoOd9B=<4EytIKJIZE4s1&;39I=Klx_o6aqj&dff|u53sIle_9sG1OF*|6fE#i1Yjp^Gl?8p+A zqcjb6WsAL`c=n`s8_isrnQD{DdZE@H_@JkkSB6YfEY9FN|IqxGDFLcR^F&9&$5nKw zuH&orYn@hTw+OlEb`OO{Q)0Q+35*gun7ht*c}n|ycu~h4BEExLC+fb$>o213Z)CT1 zb`--_1p>Z~3&!W9t||b4z}H8gTpxWf*LnYsl7F>xT(50|`YD6^X0b=bP^%6m|69o#Sf+TU1zY{b;iuQgou+P{ottDk;ficL#Z|sG4*W`ANAk2t)_4S?}+!M z(K6EewcAP4n~{24N{_daRB4ggI%_D@8xU$4s@+zCwnrO{9ME7JSms@EO^L3AWeq)*fN z#^Jb6iH_hFFkiBCxrPXZNX&dF>K^p*#>Vxy#PzmH4V5_b$9qL&W{w5=f(nFn= zKlFxYRy`f9SY?>dQh|jMlX3p-M5$eIo>T44F{?_?>=mGyj#%hc~hKVU9Zo@fDGe~#rC-&6oQi-B)Sf@+wQ-R!^g{1b$qy= zl;CM-PP@|@(Yi3#oJ~-1f~^y;t5j0GSxzh1gqByC%)uc%m;ad3F-Ra-=(8Un4|CByx@4pZ?)LcM98U}UlB0EegrO?k-c6lX>OiW6UG&Cd!1*$#6qM+SPnilB{Bpulw z9G7zX$fIH@H5B6AijDE5W85#lb`CbS6_){ChgM$kknCgyeD{T^~{_Hb*6mU#Zk>7zY_MTCWTS+avSecybrD) zF8lCj3;){Qzh#!;agKwC4B@X~n&Jbav6S9(lbVbTHtXpx59F)rpz(DKiMXiSxSiV` zm3o6Ufu==nDfwlc@u9@JjqOFG*cAHpp4R!@xZ2fIgLCyw77tk) zhGh3oiGrpr#G#p@A>o)8+G0QR%mJXd1?saXb)-IjUCr1F@E_!02cp|SV1d%D*+gdr zJ9l)vN4|^ANJcZHBCN$F1bb&ty+sz40YMniGut^@mT+|5Tsn3fjN;aKafTAmXp*m} z>Fa_K6KfmGa*(SwtTIoasDwBSWIlALo96c2k#@yf?#FuS3G>=oQ{dC8XD0#BqW=)> z$;Kv^pd4NhJGlu%eZm)EKO&4Y-mr>i3HwCsJ#_xs$q;4Bb5TBbe!eS7DE}rnHH#?! ztQJIiQhYINIyV>oR8Zso{;f!jUQEE-6;IC*Q;8NaW=)6|OA|FNLv|R(2CwZ<$aH-F|-cY)QWVD@mJL7XGk{a4X?(pZ7mrSh? zCjcHxHd#)F{k`X*>J`;3^U9b! z`+Q^AXSPE@Z>@Nz>U6m1Xm@>nt#Xi*MPENYUc~Im5vPMU@Yma;2YO0KhOzSC!bEh- z>5#NSUttpV$m`+zf7U1oYWZL6y$4)V+13Xd1{FsI1`(9%45$>P8H!XLrHs-<2)zgl zAieh@V*^4{=_Q~b0TKuzHH2Q3YJku?gkF{2d?yLWIQ`x4-tW!#?wsF`u+KhwuU*bL z`>egne_guEqYBwTu%!>R3h9~)N1-z|??87PWzYy(!^yxXqnK8$5(kq{AjS!V^Sh{R z%LK$3QJBXjDPofi<;tzH?Jj1p)SILF*%%-e?qBa{P zphBYN5Cr;1Ep~7RzD)h=YO#cQlm088K*>PBdJasCtJ2lUsns}Ay!yebNI6+DJk6I) z?VpiH*@O=EUcJHEeM6N8yR~IDY23?c*)ilj5c*v6y?XQ@gk}_W%A=t7-ttT9$X?Oz zS$$PV5fJ?;$3X~ijTTM}=+D&3^Q184?S&UY*%g;9hps#JKa8CpsK&3DE5+8JL}MKI zF?V899TuEETGBZ>F3r!yMe+h6Pv}(iz^|(7P8EwSiROb21)l3Y(ZGeMK?Ms9b0#pU z0)CaKlyGZaop@UwU~ka0k?ajAJ%g?6zQXLn&Rc^GsM07#OV6B~7|Hj>^e?X|Zy{+J zN*{c~64N_vJ7atWBSW{?s2ANTxWsYAOD0|q%GZTfY=G3};*?JnuK-a3g-mn>qu)6l zI4$AB!^Ngc*&Q z*Srn3mkL1J1m-Hzwb7|c*mLq?XQ>#A&%1uWIIH?Y;pxfVsI75-SN^y=+&iaB0kD7p z?(uQ*i!4z_AA0*K7>qC?f}OWsLT3qaW4g$mx=^-oBTAwI5a|dHAziT8db~Gw?Q$bu zUn<>Ch_p%iWmI&VpVsKAXzv{$Ix3h|0g{{q`gpXQIEHWf-#hpu)Q_@YMM^FzG_TcF zjy10&3~@Jp&{aPfn-qhVUThJ6q?i1%9~A&|%}K^u#l>~N17J*Tk}W4P^0y6hm?e9V zv7B%`SgkHlol8O~qt-q=-?fqVLI;>qW{YlNu9LA%BiQ|A_X)iq*_N&(T)ict-CS30 z!=%)$A=gGj@PmwG`Oz+7ZwNmQu=n@<%bwE+<~W@l;TwCTIJ4-pAouRlJE$s4ihWXh3jN~_cYK0?t8plfed zZ1Yv?Cp!rHy9wbf6A4tXZC=|us*sjlvx|W979U_ad$XQ2JO7P)J2IZGepvZLxDm%` z?A59SO1w6kk{>$Nveuz8L8>S%TS!$@o;KNh-t+nW!tEPe1iSt*#AzfrsX8~x0iONW}I4G~3 z^(S4Vxk!cfUwIdwlrVwj*b1%QyBOi8TkSE{tfK7L7q(M3!b-Y$s|sZ#>0rj0ik`o@ zJGw+ox_I8OQ-Bo>4p-gltsm*?$=5T{p}H~uhr28n*h}^tYg#m#JaekCZau)Eg9DA) zyxeFkz_9c#zVLj#QCwadvx-`^V{ysAd|)X|ztb->_@wV;(&K$joap>?=)S2Z(D z#DetwWh_WigTODoH+gNf<$YcL?e+#IpWW`wGpqHCD}`rWlgAZSPd3pl%*_A80amxCuvQ zi}1AU;sq)Is1OKp-SP_3n`)=XMg>)*R`R&6FI!aEn@}N`AFlz#k7TX?r3{ zqr%dwrkCnc*i7N^E+?MX`9UfWmp}rdl0_+z891>`Zx+-r-AU<#VE3j|6p%PM6+<;> zXx~px8QeN>0o`g5a?EgI$x|3}A2I0V({v&HJ)5(C^vJUY;^z7Dd{L@9=5(F-fD<&D zL~6oZ+m*ta$C++qulHrKxTaX?l{{|qrfXwdFyFv;pF%-xR3LQK1Vn$@VZbCq7#~nC z4s0JprJSQ%omk}WxlkPU&kLx%Yg%LvUPu}!U7G+<9O)j^+(kg`g?Y7OMh4pikl5iR z^@Uoe2DkXGDVuWBjyJO-$kUv$sdR$9aN`QjF!P)O&b(aB<5ApDPWTE~2qGqJmtT#g z>p{efHDU^1K@{!jR0RZEPAIrR+{UgEM?x*sR$2g&5;JAyyY~i(mbG=Hb}2-puWi;f z&o^rn$?CauSlhCIS*Yd&6a|JmuI|pm-(fHw_r`l*w|!Zk6}h4Z%xs!w&|Ym|y>4id z7fo_qA=TCJyawiK(I6JFYikWdW=@+~Ec_FH0^R0E6Gy0(r>Yb&`$W` z0wJH}@0i^XZZ&WO;hYdKBP`#6Icc|A!ry3=<&ly;V@bA~n&F)1`w67lVIxZz0Jy`} zBPDglmRzAm1uGP~8Fw$#lZKTdoe66bd&T&ib02Er)c7mA>#<73wgAYdg5@DusPD&D zGqo!c*Qf;+-0*rUQditCHOFr-yb)<%9~x90eR8E)*2=4nuWEEU$)-uQB%san5-V}$ znqEVZ^waUC4igrFignv#PNKY&!XQ6r}`)Ri@Ew| zPX1jF1ZT5t#PFh~g^l*0Ej;sznff!%9;xBvHkrN*y;E(D+=e|B8W5KpgyYl0Ca=F( zP=0apH}j>YxY5^^H!5n_!b0t+)ER_-Oqnu|6HpIWr@rL>i=KZz)vjI>8kE&g&z5X* z#YUR9+v)UUUE7d7i))@myqnIR`m8;I3j5Zs|DL_KXKY{600&DMD%LqELA0&Xn%V`q z^U-TJljU2TsX$ik!AuGT%?}+r_uO?;P7W2c(M#l&X;VdK<1;tko3Q;?Ww6H_NFFG$ zmPy4Q8nF-|x$i^Hz`QQ|m6(#modDKne&{V$eIh%i9jP~Zyf44x=qhifp=V_0OV*lB zf27uRP`Thne)C%NMPsHHS-YqAzPVxl7p13Xt$S(qVxa&zSD>kP*ka^_?R2A z5>TaG(}k+pWWzYuhAFJy4o>Q_2~YzR6Rl=H#A(~fH7^(nrwHxF^|!UWI|pfMIXtV* zI8RNh7uen;!U?-TuSbY+#B{8>Q9;4J@*$f}POo)PL$->N@Yq0ArvROzu2Ws(6k68x zVe{gOex*i+^ps%BS@44#eg-)BB3jQ)yv+hJdNX>lDh}G+uD_$^syrN!Dsumu%*5Wl z3m2(vnx#DG*)@{{VXlJ`@Mi)7fSV2NuwO!n5k|~8a3Pe7VfTM$uU5+#SFpHJSAyW`a9*5A5_(mmtza&|mjpx< zx%r6wY9F8<3PO9Het;H6*-Fku!vzs24M!q`>jK6N8LJbqmUSFZp)=fTt{SM-eP@FW zL8N0Wma&X=r=+hdj2a|6;MH=U9=b+&1&@~Y;puajBa3AkB2?Asp-4D;ReiVsSmKna zs15tq5JzdfB=bRO`}5fj)_w3a*io-t-Vtk=AV{x@SPh+`*m_4oJ;#-%i~Yzj)49`Z zLa`UP{ayO}__GAx%^CsNd~_Pj0?M?0MG57m&c>G!i#ax2LBh}Iq?e}gbC#YRNyWLA z>H%)lSBPf0gbtA%>*pw5H-PUp|2TztwU&5%d9b@NA<<+j*ZWS}3-Y8t6iECvm9Iyq z(Ho46TAN%cf<5x4sgN$K7*83OvMAPi^yR#H&r+ZWO2hgF0mVWW+WAJ}7&cwdnkPR+M9s z#?thJ`w73FU%c_%3^7}bP{Q@;TMuHLWjaoE?nZN*TbHf0zsjb4sSkE>-+oo53$gUVLX@nOkn zkyzVO(~5G3#!k9$qhy*#=n@p0@wgK#t~Wy%e4IXqdjz-1{sf(HY+~F_Kt~v3K-l|g zD{`f!(5dR3>QtCfRLQu7(z*S`QB*t!-jK1cNS*HdHZwK+SG3Z4yw|o(89B;NBC~Ou^h+|{16^K z37Y;xH8L4@_rThH!K8GOeTyUcNZQ<4is@e8*(=`?8zTM)#@z&;vD8*d*wo1nGxv-I zVCV((Ddn$RWevByuTh)eNceH4_wJegZ{J zFO$E*19W-%M;@p20;^!7(9?sq{ycRg?%vjuP{q@?ghLDOEn{7om_{&LSId15%e?=i z%Kxypf7RpatC{lOpGZK0jh(vDfGh2lOF8kw!Wn&h-lSZ%v*jL_RxG1k|IHC%bw;mI z(?wC7TBm=qe0X&=MAgu(syGNwkOkJjnQ>oABTA$|Yh6^T{C!85xoSy3Tk*YO$PynsMl_nK-KKD{i z)M3sMt21%U)bOj;h4SD^Cq1VITDtHftJbIQLrSMC1;-bz^E|cArw~C~9#_+_r4?Lk zQlU}6jXX-3nD0U1bn<|v*j<*$=BjLsi$k6GW;`+WnF< zy1*w*M*YRHeSU~sCzoZY)|YfLLE$5$wSlMiW^PH7S!?*2tRpG$XG3pYCV#YStA3#Ea6C|g zOwN7+jXUHge%R6Xb&7xS4_ov%W{ZCMu3HaQkbBzxlx~UeCY^vls-Kn?#vx8k(b=3D z6iO93Z~f$B{RZInD3YMjnA+oJbN`9R%Vr9>HQ_3EycghN$<%UjTwIy5WzqIV{4a{T zBrqio4ftJQIosDuIT7)Zb-c?~A}e}MLgZ>i36-4~5ao$bTe9i(fV+VJf%ZcW<{>8_ z4ilj<&p5;452ePri@<*7$g*hpvcsRri)LV7b^Fb;#~|=DFYlbNlmju?_(pu}N-5s**9im@Y;{PHaEM zr?C--5kCd~G{PYE>+`o0DH^K=fE+&|>Qx8b3Prw_WZr z?Kmqi*t~hQasYstl{3`^5ZDr(@>4a0$)#4kDo>c~At5n}D27-C$a0VHw`%>*v5 zPax?9OSp5}O_%L3{_JSHi>={wY#P)kCMNib5i?J=g(G|dn_}1HJ0iH(yCbZYPtZa2 zOJ_W_ZRb^ASxQi`1XOiCs_w7qYrAig4$qXZTR<_u*49{-SGM#@LUkt~tcsI&LGuOs z!~3oO`}|&J0{N9Ja=%HnUTCtOvu0%f7sK5N9OM=eYG(sSUz1(v^bKmpZuAX zS)Ed3<+tStYc*zxlW32w}{7&;bnTt~#-!zj$iybZZP z5I|-EX4RrUjK-`sj(pISjq5>a0^wPnRb`RO2?&TOAHv`p)aPrXIN<=+B0}hU$DK!$ z1@>o(4^3s*CN@6~GxwtQwF++U9t=Ljk|34oe>!!|7I$HkL=;Arqa}K&v5Oi>_!6g1sCzuw zoBEu-stc&1*BWba>#at{nhr}kmjlZXgHF4QO}ro2&_{ZPz$bW$1Y9R`pfOO_oL%#! z6kRXnt1dVfL_g}X`O~lXN#nm`Y6Q6hteb#{P4SAe&@kZ8FLYSuf)iGspE2`g!(#Un zWuJJ(G&KDrqR2Uc3!CSLu?SqX1T4ss3S%uoV|7&2+SDMdHzwRinIvCUYL!n!#|yK< z<`CSi(J{CT&szYxBQ&{*b6Pyn*lz8zdqtam!Cdnu!fC{h_m&HjU@Bkpg~s04A|p2c z#FOy%d99k!p}ecyLBk!_4wU~_OkD^2T-!S5Tt!YaZ*7HadhDQ@>qe1d15ZFyTXvuC z^!3r|XdwChqoPt;aQ8uAL?Y;`_fJTxaRkwSM8wU=?4d@Qvi3@A@w-i_x>i*Vk{ zAd$Pia7A3Hg-b^I3;M67PrFz2xVYcqKCee?@YOUg#XIFpj@y`Ks7=6 zTi)hkj|c2rUc;@U@_(6 z^3meqszOS7?|6*obWxme4pDPoHNAh9OAy&@(th?go|Dg6|16ikt)=-qK|rG2Oy}yO zrq?1oiimp{z=KVhqI9FLMY21{^risBUc{6xPq$g_{=^#{AG{hLHS^Zlv- zy}kOq5+fj-IAHe(0oN7|$4FsEKLkNAkqe6IQ^5;2kWU1i6#^~!8Rnd|^CSVI5!O}o zF!I@iy^a(FT$K*~Tq??Pli8Eo2h*v33J0?Ay1}~QQc5Exj zq|1xm)?y_Zaj{xeNF!sW3c)yyNy5AEnW#iVgqAI(R-*%kT6NQ>^PQW+sLxW3g-?PV z;!UocXszWgGDTS9GOGDQxbzSJOMX7eyUHL)i4nJH`~wgzil)9OWY*Ng^YYtC)UqnA z?SSm6#5d0*OlBNy#;{U9z%PSj%ZYsgjm6nVb(Nciy=P0C?CDg`$vRQg8)XwXh%u0z zvO>W5@Qc<;X?D~KPf5jp`}a`Xck|Ta;U*W}dOqAp0py4~r-Y+^TCoPX=zAU1IFfkJ zGqy8@r%E|jvdOYF!b{`fosQ#mDJ*1_9va94ZcOxmNpo_!|4~#&dBZ2`nwS#s#rdj# zfKyu!FxXJ2(mEjg7gFZ=L~)^0GD&)9pIv&fENA;DNKNk#EC1cRW}PIP#Pz|E%uDar z1n1nEHE;Lm&#BVgOZ5Hl*7tV?ayUcTQE%DA*^;)e)0@E*CU>}`|Fkz?Jwxq^Nm@wN z$QzzZmfrKY0{u_CU&hB_ZK2Ctt1ry=Q$Mpvm-z#W^!eby2BJlJ7z-A&L9|GB+_y;o z`BwIEy2${4NrUD4P^0ONy*;fPG%x&H();##{6eBA07ppYv^m;>3#02&@78OWM~9PM zAhKr74Wn-?*yeg%R{_7Z=*}NDdPF>{4d;w9Af_ucdPT`M;a;3d*x9RcXqj15G~Da~ z(&y+#|Li_vws8*N%-&-Hs`M%i5KYsju~V=M7wBPx7;sF-3%z7!c_vGY#(L4rkn8Ui z%=+2x=ZJf^7WzasU9g0D9)DH`&U%b}T^Og?D0zBTL(vKFvZf0b)7mX6QGi{pnm1drNgu5 z8Txl^c@?b;;N#1xV=s;)zaF31zzLhqf3@S1*$s7F(u0_T#g;g{)ym!H&n|seNvT^b z+nBWSF3a;$Pm?I748qUq>9_rZSi0}WzN;fQ9$8rUWv8#~p6PS-0phf*QLjIBRwbJ# zykG5iw7q@S!u$S1`~FQ*;qP$@6bZvX6b5E?RaHVI$~spmn?Jh-hlbs@-!e`#lj75@$>_7&>TZ9P`2;$G_Pc0w|oh2JErwX^}~_OlGP^`8z)+t^5kF#;Sx97eBz~CU6;e9Vu|RWZmwI2UQroU(Q?VhZj&vyJRbSIj8tl>@+UrzyM4*P&|UnoleMBPTb^;P_431~|68VGdU)$OIZ%fcxhFy)U2 zl>a) ztN0Z5cK7&)`=vXU0dF3A-N{z+rPKmZ)W($gWPbu_qBl|=H8pLmgl^WfG+pAjE~x$) zeBu{0K_IoylBi!@+Rq@{TkRa{_N;<#n0w|aK$3jO8-9h!v0sOtXay93Bdvb1&dl^{ zPI}AJtHqN0=E(WMw4i^7IdWVq;D7TT@We^oPg$Scw*opv5iY&oqc8tc85D`!zqE70 z(}YUFRdxZN_%hV$WsA;FYaUWJjlU;qVf~_r#xV3LQEq#ZJEZ2G!exZJ8TJJk?Xthl9Ua*x+oqjH_jmMOBSXkL41Ey=F$uAP<5GuTjG>D%@H< zeb%8GpFt4g3B1n_1$@9x4b%f*;%*lPYd792=%3s@P6G*&p_QpO0|FRI5Ca$n*wV(9 zA3uRITBZHBwg@`~(@pBb z!P(J%P4s0Y1Jf|J(4M6W&VJOE)L9ZUBAeZfn^owP zDFZ^Xw<+pfqN*0A< z!eiPz6&CZ4cJ$vrF|hnnnAIOOCy0C1Ab`}Ek9zp=_&_e}l-pJQl9?NE)H2=CHU=>_ zF;ro{yhLGYi1zuh-|yy{e8tA&1#N1l;_K6l@&|O6Z}S)3k$~{ad%9S>`6RgGKg|=P!<+0 zTE0ip1FgP`clNS?-$%X`JF)lFesyCHn?-Jwh-iPz)h%V-MGlQ!*(qlfyGD4l_dyo0 zdsr>W&{IphGmd!9=kDlKr_r5b-f4{csh@dr{7jfbpz^Z=zm@Fzf#}IGR_A21t3~kS zVE%vO$w4$^`58fCvVD9@+_%{@Hk*EDM{G)9r`NFLfNN|teuL~xec99~Nt-3@KZH$o zZOBh60YKTfplQgE3qp@h-xFr+zhW63kI9!&g{Ub%k+=<56u#2f`9_dB6&U zn#Ka@JcWw5RJ0@@5HEb%Y6MUQKr{7F0C4*NP5=RLjcHTvM<&(hRsAYW>8Ali?GnUl zc8UKY$1;xKb0}6_o6QHi!;;ujY^odto$m+4gsL=pLJ%ONh{fp0sxD+RV@>%&jxvV@ zqgS9gzs~m-MT@=D+;FEj=cTDp8)oiF1hl{+DCNaRg?q~%Q4#VglOQr`Sd-QwfWTc%Z!_7CWLqt4;G8xL0 zA#a^)EK!LEb;zRp=I&e45Pwhhz%;HI7Dd?8R2^$M1Ul3V1wJXEV$Fy~OAsP%p5sxjk&CJ{P7O#6~;0N|b+r6YB{)XjY_-jyTY z2uj7l9PSD85~gZuP=TlnJYQ)rd#lkxp_}jZ*lRKPs0V)DeDiULL3k9Ysu(_J?Bc|u zdt&%`v)kDXAAV7uLbs%GnqJiBjy~FIV*gcLSM@p~9Pb@~QKVIno_h5rHW;?xtsiGI zqlK_2ppy^uPvXQt1gAh>f8+c4IU`rh^{$eG2c|H|^$B|1!vqH}>Fp#AUHd9&@^hKr zNc`QPoyqrXGbcj!oG_oXu31DQrMD>}f5y{(B~)NgB~rq9Qzf%#m!V|pdM#~T@_3UN z`Wx!I;lD9T(EkjiPol4_$bR>+R6FH<>Sw54FMbo%i_i&&oqfrIgYjp++oO*fgZ&`S zc&tslC&^0{@t~JCGe<}3aU(zMIIXT$i4D8n+972&6-5;QuNR!5d_vy0y3t;tQu*ne z516l}?M%ZOpW!$gj2q^f{qc%hW<;h-Y14%SrPo3m`pyrUc@!JZzl#nSK?o+8R#YRP zX|SGTu6v;0TGNgsi*@(0y2=Rnz~<~FVFFce$7ALJ9a4sMJx?zJA5tHiKHQoJ>q@ZE zNt!E40|b14sI$6`na|LE?UJh{a~xn;`ORd^YNZZhfc>;DjMqi2X9Uh(>8oF*UxiDV zfv8AP67=u{|GGBVNg(|A3wj2FDr12w+~dVOTagygt)@We2q(#e{v*|fX;tI)j>)!3 zK%jb|fKcX!Sm@ef{z5IJLLhE3jsPEYu|N$XqbndVtZw|c=8Yn%(IG>oWMwPLZn<{t zB>+uAl}zyCPf&Ck=7p3cv1?lf&$h5P8@(2}Tj4C_( zP}-5k9}O*mG(;#gZg0!q=pAyg4q~~Q1V$#tg?a3_nw4FnkO6M~SHI21$Axx8>6n^p zV6QPx>`6_2>v{b$?}!hzAm(At`$_l(q0Yy?q)@z(5xwv+8yrKGHEvrj?A7!x`@7cm z0FS1@JFKb3ZIlHXORP#^$n{VT`8zD83u)6XlKj2S_i4ta#LE0XIOToUVY3sx%adZ$Bh2G>RcMw9c z<}r)4C}+TctAUE>x_-RtVnk=c^7w@DEY{Z5;j+3h9*|EovOh`Hha?dU``HF zf5E+4guC1@=aQ4*l1tKD=4|t-m(_(qvkUA(q1SH@#soqXxUA1Qp+7_pW&ly0ggaMu zx}#5o^3+eGGU(|8!Mf(^Yx?x%cHAWnY4r%W@%YBLmHB7{U4p0z(?Ku3Nc|zt4$}D% z#NfusGgn9!?7XWFGv!@Xw2*29>Dn`gbr3{BE%3p&y*}QZEOA)n;Db#RcU-F5K)Bk) z-H9zzoSzzh&OJVHeWj6?4OgFrYO;rbU)8Saiya<3?}&>60&Xw9D>u#-_2G_N*U8gH zGc#7UT*Hi^Jl?Fx#9=n?SEB2deuQ<;Ki6YKcG@%QVRUhNDzsp3iQYLU4}n<^4{md) zxj+1}Ut~ZO9N#-!J z$JIjxYnd|6ws4_vcW$BudFI_IC<@P?5{fX-fq|nuInvKii@chaC2yKCqbiWGZKL8) zcEw$Fp}xb07Top80s4qXSt%Se?__fA+dF5LWP`%Nd;36Q!&$t7z$$RE(3~DO%hiu?jz3w zo;`|4pJ-D)U7(I%yDp|FeK%FbVL>=`GpCl&9qTs2m7m&mORpGHD!p!o3&Po}GYBAS zl<7&aPER5tk*xMw9fK{M(-s`dRv`7r)Ta{OsC(l8VG;RwanB zj>EDT8jdl^_alrlX%|_wzS%DW%;cU3=KS8Gc>IhqYiOL{^w>4Y2fz6hKjgQEF%e1% zLUL+;NxpA3)G>Kx%U96Jmq6J%-$AG;`F&(ce)qd*AQ65h^jOyONbH(?B9|h_y|d*^ zTOIdB35OTugwnr0ME}UNhydUQ0XzkMex{UsFD6Pg*U;aHN&ayq5dT{z@8Xt z-fH;%&1@ZY%+TD3l`b4%5ppJAB$4)Uj-4n9%v)lE55!5UsBdz{tQ9xwUo_Q0GxW|! z<|x^WvAGNP-Oa`{TtTE~J8*UYapkyTZdnZ!_~1mWH56T@gN+bono)@uBYb*&;k#GW zRb5A;e?0)u;kOlrrb6*T=`o8<{8>K?OLVlPA8%jfM_Nyf*VmEGsfC7KKa-w%l2fzv z-SZUh$J*;LP(;g}+s80sC0r`grQHT8Qk_w_sa95-YyW|j2jB}_Ki=Xd0W)jxOL2!_ z7kx4dsj40vjOVKrRE>#Nb$1n()?-dky5-2GlaVPKIMO^Irk;VE7U6bQEN_NIwR{7Y zrImV9P7uc8l@e8JFTioRLivzUrsdS-^kMk`{#lX7@=YV#7NvK;jwt`!>;Ebx4H;u^)^OZF__?&b_t^OBQS3)kn65v?>sz0v z@9^GZck;gJiOhLu^A9{vM}7=N!dE-Q`@HI5MJFQPFXbcU>9ZC_LKF66T{ z;f$RW3F0b$&!hC&n5RK-J&&qiYha`Z7F-$-(vc;ysF@ws?wBN-RTb2dw z!!ga9)>PQ7$92hLJ!;+oK5&q)JDRO)=5Vz>L|P zl=dn0J6x-2nci>8Zh0f5vX!2Gd=BdNC5%_6tZz+{!7+Un2z_O{CfOpZJMGDZceLMZ zov|WlK6o{A)Y(>q(`FGPR$`9>m+qv+BU2C&Ln_-x~m6&qUa0;->QFWSiGYvlI(lUk}jni3BboHheE= z98~*n@(w;^X;b#Z*5mf`WF6;iuxIPzAdXPErdVf(Z&@oi52oZG^}njB6sI6+%%PdE_rBu!Q-qGd(Z+EZVmMdyUeYo3%g);(}&-lJzPzOp-*lp{7;u_GVir3WfCN)X1LRn?V@I0Xm;grzFwM^AS% zT1Y&va39vub0JGnmsTR+6<}K{AXdFtctR;GLqt@)ZCn%raT3qb)p(_#0Ty!*J(C$h z=Yy0SJOU_SO&N9L%=tx8thG@_fotqakJT1uDBB+E6=mWuY9^c?KwmuYm#1b8)yOUL z3ogHN)N!-Age{KKnd*=b%VnKJ4~BGtUs{?DprtjGwB+x>_1wqNtxz;`X#Z*PrJiwi zCy8k^)YN)Pg88Pgi4pk2g&14pFdEp9;Z6auXQL5r)4?Vh*krlbr$;miV?T`9X>r6x zi6sE$XY@c&zrJOY=2XBPO5|)NZ+>HIRx%&=LNH3L@3&K5osU~@OFSPRf-n$B=j+kQdD2<*}K-Ow^-FIf6l^h~tqG(%tHV~@@D4|k=?K??R6N;b13p?BB7>Ip+MO(%%ihQVuiy_45xni1mn#>E|tpi>*~$qLCujBQ~{?70}&T`t{v_6;d@)Q<+{$ z%_WQSdjWv*`UypijltsUJzVhlSXHrF|E!U@P8;(_U0#&Eu0};~>d)L24Z-}!LwxQ+0~6KPh(FzmI7VHQGiz8-?Uzx8@i zyV&$)yTs)q5zwh&XgcfytyiMcu{i~uMr$0T=pNKOmR&1mx6Sq z-Pf`l)atn!j32xjqi7l&i(RzzN~5ZE;W(iMcpqfJ!YMw^>dkh;%WkwHas`n@3mc*`jO$w!gQW zWbfq+?A%8c4{1(-&?V=jMwrJqd3qy4WhFzEg|{HAL7uD$P(gR|cCttUEi#**twb^h zJJ6=O8Trf{LF8luX*H6*j9Tx(%eMmP0aQK~TdLKePruswEveQ)2MMVF(sHELUFJ2v z6LdPEWCZfj1IbEvl@laO&{Q7^W2-b=7;^2j!s_Wv4M)qHw)Z1lz8Nf`zc&~yTRoWG zbyc`v8m3gSkyi$>%$XTa>2dNfIZEr3dUv~L5aBFNG2XGG3go-MSYxgq`l7tr?B)h6 zRStgpCy{9v>ZZ)iRMpo$*0wltsZuj@>{R%-ikq;9A1!^-+h@8vQVWX1gZi6d1K zhpjZLdg9d6lT6b)j2Lw*a5garGwI0eW2vTiFEu|0bK=KcP=(9+&ZbY$I@j#TAJL*Q9?1iPJ|$QnjzA-RWN)yn&Rdv~t`%or!-k>dv6vvy10nZsW7=kY$|X+q<% zy=U)%?QW}2%5`!2kO}4uwartRtjnGnAGAouh8nkFWkY$yd@q+1DDfOvrA-!mR-&N` zcg&shO(VP++=-j9$tRFOCf#%oy~N>`CIbt%XCFWYj(Z*X{eLu!%# z3L=A1XZCVf?wOTw^iBAgQFWC}J@nBv&OEU;wiUnV{4!xX(J_G~UfgI6PPt(pk_#RT zn|G|}c;flNyV=dfZZ2>jS_EG!_P)hO8QnrbYG=5+Dxc5e?!TSh;iUTsN!W zcAN@Af*kwm=LNq=lNG#kd3ZY9m~Bpk8__pKhm{bkx((>`EmK|6nkkIlx+H&{prR{X zgKYl3rL`wg?orfq zZ{T1=b9}6#1ni2@&_`xgLq&+80R4Q8grmaJ<#=B;!1O$Xitfnu0Ao<2W z<$$wVStmi&6FDiFd$Ut`ZBsqgy=AMZ6epL=T9=v>%&P4SGVvvl`4b`j5GwN5972Jnz7p7rVfJj!z`Dxga>$kFEJpo@ zhllQfP`Q6Fh^J%G+rmtTA*WF_bhde|I=sjsiIH%A-DD=cQ#ArKeaVhwHh4$f#x=ON zM!9sJL0lpkiUckI%X%H&Y$TM~!`BGcjP{E#u>-@g& zSMI%NvZusKf58luegUi4ASB~dT^oO=@S|g%#yfWHkqa>a*Ua7|s4eU-M>hc-nfRVn zYDe#le#9q`*Pt-+rV{`~a`emof%aLbzr5Squm7%cbV-vtFWUpbu4l^Tb{_jivarfn z>3g#p0W+}I`<6$EpcH)&`4Vp&X^*Y2p3xE?+g#rnbv0SGqd#YKsE4sw?|ueB<`I4Y0qW*XpAcCev? z>J@P;9nN|YYL&!iu;-|z4dB|UsS`GMa1-p@Dnjzisw7~Hq((GG;>6-pZ~u)1PJfJZ z$q&}epEovwTH|O0TOKErSY}%~_@iRrI=!fS2<}u(8IR7IjayWCOFnlV{uP=T}_ORRiB=+3a0Z;&pT8%cxki?sBe^GS*l#Jx2 zu1CUh*Gg5Xu-!xV?Vq2-OyTC-Jnv!o_w0=RfNX4*KgmQ-4=P(|7fEBBEvYnx4C3%c7mZ9>7zx+?;P_q?nxvZZDL`|j5P;jHKy#Stn_TIt!Jy#68!`KFhJ{Bx?9t;+`W&5yJ zt8UnOa@u08Raq;c>aRTu%SSD_uIT>nz?Wp-hSHk z2c7Tb@>Nb}m2Z`tJ(v9=lLaP$xF~0)%-wc*a&=EIWldVlBv0M@(3VDii7>7|GbE3S zaINj+*7r9d>grOucbT_wtEmBJzdgLh|I2y#3Ya&BQn6`e3UUp zc9EMbh~-p0&4+?G!Df3Ni=^0O?quOQWb^_TbDf63z#Y-pVGcz{91kJNkg%j~o%;{t zV(5?$&l)=^fNTzpys0N&lpdNwdAyniVik3*II`f`qo>Ek>*c_@EJ7`6KIC?q^*|S__jB9ia4>2Y-+hpg|DR~EoQW9T3P;@|2 zVPfESl8=3Yau{=zUQf94=Zo>&AL^Y=-G2SCweUW>E$;CaEWhlQzJAgL2k*-~*vO}O zr2K~|cDu5J$m1{LbFkQY`XUYLG3(9OPN=hQdshsdz+uVB;XI_XyHmPXpA}43PMUGZ z$#H6v&MNk(zi(Cw3Az0%nlKjTmo|ZF+6dQhRi$DN*+8geRZ{XjfB$+AiOF+E_aMij zQsZEV(wmI9S1-T-)Pe2O-Xy$h!Lq9QGiH0F3gALeATHU<=fPj#_;^HEO?s3wV12i# zMZWukn#XIWlEm|J$m2;Uht+g~V21bd=A@KXV3 z2gEr04}(fYH_92m`n>GD{QF9id1Xv-jYsl&;pv0QCu-ALRdRL~cFlDvWL-VmU4k05 z=Eu91Ged!g^I?jWJCjcaZ%FN&L_EtTI@9Tk>?d;)!GDv?$6HqvB(PORgGlO2fszkt z6(wTeMLD>M6#jwhgGh>7hQ*J|)K5PVF%I*dMrSBdQx9bW@7`oE`*pJ)*qomju?ZTP znnA9SWM%)VUDB5ypFcbM|G@?glu%hFD{~RSS)iv{a|!^!5S%$m+1zW8!guL-%=lNl zoh_~<;!$rDskG1Gm z-M=HX*cI%m!OyDhttE!`{g5;L4z0nSWmuMe*3C*5L!t!JEqB*i`LPfAtn>9?duXvR zyB@BQA|g>;iA2%{@^+@nc3N(Li3T-2PF~-FL zSm8>cZtH?3LeULs%K#GBf{JK4sE=?1DTphe2XQ%kmZ^P<{9x5<1CU@cn_FBO27-9{ zhTMX~7Q(&no7?(AJU?%O$VS%g0hlqpHS<_U?6XX-%a3v0xCZWv8fF@o(uy~Vr$K+Z z6*zc`hC7tWUi_xMD$!zQ~juzd5qPO zGNk5N;KstV%Qv)EOtO?Mag>{R3ko}NWLeW46ohG4e=SA0k)HZ#V*jZNmu4xq&TMic zaE<~BQxYfIT%f6(k$IYE1v3bk`UZQ-XsuJRTdQfCi{|LwNn=yDaZpH9C`0SgC|JZK zMAE2;m6jSZ6Jf3>;YDdn>a#hdhbtZtAJ=AzSAhvO=93%XGN=iU7-=p#Q}d>u^lPud zeVTkbS1ieaM>uS6HYBsuScn(sNfPmh=z(OK5VS+zjKb8bY+dmjTHQuR4|xFro9F~! zNB1polf$h(fjmEfVpW`y&9(-&ThTu)`9PWE%Qy4O#iu?vDFY zko8z2y<20z#$6e;GGRC@{QObOSQ3*42xFp3oH*GbL zB#3}bpMXryslZN6a4qKF!{|eB;iAh{9gac}nyA5!D?4LICUAVqCy;H~w0!PRj@J=G z8Ry}wHi%$zn1sB@s^7&b03@#ua;j6AS+pe8!;GhSO+M!mJ^`@v2(E210}FftH7qQC z0%6b14mc{RvM7~Rj3ntjfTg~hhOskQX9wvsT03c$$bM9G?krbo!zks_CCZ{j-_o>szPL0A5Qvr|V3lT_^MYMj7AH1JNL4M3*)l;-U zOe11sG_pMt7iQ`N5yVA}m?I=;Z47ie<9^J=(ddgDriJKq1)!Brtj9R_RMyZsU|ft~ zLb34ZHfe8(4>z$@qn6Q^AZZ=rtaCJ|c)`qNrW^o`MFTu5Rut?agnWevb{-x`w>%!E zm*r{Ne5`0&tp?e9rXa_)Sn7Oo5J!6_+xb5ihUkL$mj{0D4vBnsWlIw9A&2D&#zoJOM7 zflpaP9OCs{2zisBc~GA)!wB`(>xld z{e5YjGJfY3NI_RicNtzsfcK5%NmauA4R@hm^8V$Y13L;w!yR$|PyZIB<9?Hrm5OOd zYxoN7Gn*q2=&sBv{ex5Q{t5OVvsh-rB6QkKe?ecdM$s5B1DkCPO5%}>n6RNB%b)}a z7Qw%z|GP=!+4vVbB%>HkeNQsapU{06oNHEY8NEvgtRb&@!56+4{TS5Yan)Ut+oSt; zk^zp+mLnD8y7>`pxxKPiTw{g{hl_xi*KXzv)j6@s>lDa4>~k}o&_auBeB!LrBN3~^ zs*|rLlqZ7kU1YvJg3&KOU=zaI@R_ZQNwMjCs46IO)Ri0M)YE34q8$435;gvo>Xw6H zRX>qxxg-+_;(=gAv6SaOq1$FGh9;L@O!~-t@>*}r{pb>KlB&b3J_52VcApiq$`v!C z_(YQcRPpXrfpql1qr;>xCvv-~@0kGZw@bkBmcsd;9x}h2_)_46lI9`%&edP;km0i2 z+;fCw(6XK-9I?XE;YZ*Y;tw7*;G)g82@r*q$DB~bH z?SI+##Fflih+JkNkxE4&pwrW3?CV)`dh-+6YRIWT zdDV{_au$XxN26kjzJxzYwfm;8uy30OdySY-a2kfJG3?n?#x>_Juz!ClJ?zc(Dmco1 ztN>!h@-4JV?nymRG12I|u_9h)R|-G-`g)0D;tyZz%o>i5n9PV8)H8F$Zt;!gkqa#!}JHq`7JmpZ(if(=m^k(M7{#qb18EnQbd$b3`!nRdK^R%seQAi$(I! zv?=e?5f-x)zj8C0tEbR-#cFOf^GOl-&`BltZ+zTgSp?B@;t0g}#O)!7P{U=i0ZiQ-;)NXKz;q3OhTC@4b&^ zLL#=Z``WPSRjkouwF5!SS79kj-@Kh21nGWV%MmIlbfC2(DXGKp>9$fD?c|C_%lC!; zAcF&#JZ;_kp{Y|fG8LMssUA{r-!OzwGDS&KbAL4jF)cO&IO===9KjscDGgAZXf>{eqvM+V(R9{WnWH2 z)>p=N|8jEp7moj9Zg6cyO{9BleY=lc;M<)J6m;<>teqJj8FeH*)hTp1dkg@>>z-P1 zqK#ETe_etWd)ymPG$=P*+R7;32cP+2C+E&~D2$ahnl9_gLxnW!=oHS?@$9ZRb1iV@ zuhJGq7P8o0@22~Q$S(@9vseBta(pznXJsp^)Npso?{@MDTl8a3>brf-A|}GmX6ljx zJ%X>pU^{aP2(fGV*7w|~O@QA${cjKvwnl%|oNIPfUpK4@%6enAPMe!m>~KC+V-1d@ zaBo;$S|LEvn`jsRft6Q%{Y@pXR87rsrmc5Yq>d<3L`at{*D2Z8xP4b!LCo6hSg zD2{BEl9-&c#S3rF_nN$Wm3*cIbI#ZvpD>s(;6B;emL4Q(VX6q}7)W?TONdZJ%dYbp zPBZd79<@8^>6CWoVSQDuNg02*L9tDp&tFA*(s`@p%+Z-5Ni&IsYcf7$?w?<6np;#T ziQ}eV>ZWBQ!qZ@y%EYq2Rl9R>&a(`1q~Rg)G@TL4`*K1zbJz# zH%R-G74m-0T=g^C1B3i{(h-h$m*D93`j>4a&+8PIk1!($2%m#3zVvRTw8}DZ-c@Yb zOL(iSBvpg*N{((m?C3Q#>i@h2%4ZXXMcLhc(R^O&@ia=a=T>S@P5(&VBXgZ)$-$uL zjEVV5oP|*%?A*$ADUHr^i!dXfstX?ZixB?{cDMCQVc>T)otN?64^EdS;v`!IUr8C+ z-pVb5w_~&MqN3l~DNyKZxJ@`G4L@vJWx&VRQrciWvlL47p^BE;PJA5qj>2a7jr(O* z9;wEpAyGCFd5cm|?CASNjU3T~ZIxYAWRfw-G9Q9aimp|aTSn}UM-F^uGm`G0aN?I` zRGL7Jb453ebC@k&X(5@APVeGFyvyyWiS%%|%B-tT`E3uRjS^nCVK`L`6`pA)Jy#=i znD^%DA86+mr@uDLl|#8WfcjdFPf`zM3d~MWgFBBM6wXzZZ!y~RZ7CQKCz+Bj7{9CT z#5*Q|IX`*JJniTzHTdxjBlBxF;Vv_~eZk=hCSHoj$WsXg56nB0i>d%EI)RX#`QS3r z*l)USKp~02&M@X|CLZB05GNKijyg|Qb>wo1nk=Fm|RnmqDxD|#!U!m-BI zoybH+&5cHSK8)v#pK3e5O~m5pR6MH0=_H>Lq2_p)k+lQyVp_;bsp=Wf{VLl<>pUo7n&Bdj74;RBAGk^B zY#>mkmp(O~r|`kzYNF|}o85Mo3>3xohekLzcfx=OeW>&5sfYDNq~5gWyAu`(&b7y? zUM*{hyf)7RJ5K59w}2r^>T129Q-f}lG5t(uj-ZfSW#j2b@+}n+i>?I` ze4bAC9r=i@q>6`|%NzPYBK#D~OVOE1) zZ}4!0kVDDPE*^V|_B>pB@;IW@d-Sx|6p1gT&z{hu7!_zCr@2hx^;bHdoMAkC{Hw1z z|7wK!7f$>q`s`mspXJzH(Jp>5sw;>&2%t;2c>5PwE?VOOOB^#?%5*&Klo{pqJfB$a z+4b$#UN*wYw}0&U{ky`|_~P9uPsDW9_9&4|j59LMYsxb*ytQ!aBiqk^O#0jVUE{SK zo7$K&{~iN2g~VC^?T;2`oBey2(`FRas@F9RHqL2g%hUxfX(2C1IAwljgDrngY&ST_ zzv&ufC`WkmLPF2+RhCS^0?s=ui{Id3^Y-QMlgghZi{Hu~_( z4b@T^x>1AAm9az0EDyD+;SNoqLqQZcrHV02zf4aDBX;SuDe7bpmIEaU^0AQ!ZZdQLDeX7W4s5SInlm zuqZ@-BQ$Sm<7s^}*ZbiI1vO{5U#?1!3@cI$3=40+C?=B@`J#=6L3ySn+~FjlP@GiX zEaJWPD-k=gUttI!xzwSBRkXOU%8#7ISvm4veq8X)tndwXCW-3)z!hWYP0L+)0SmI&OEoVtRxdw%=!*0oNJ;;LJnnX@t`9x^qiQ*%t4H7}b6J^g52 zJ>N5J>t|@nj`*trIb>9iGq(u_DVZkflmCX00=9!-l*WllXrw>XxTVU=KW@cNY9Vy! z1D01+@k)GOcHPSbMQNxP3Bh)S93i;m4k8EGYT9Z^o^E+Tc^=^LB>cXEqKc!LF_avR2@xdogy^K4!!Iyc5?j z!qtQfr50^1Lgmu-BWz^6#n_DnOYqr^<3+}d!h$Jimz!M7u$+{5bky4ywTG+u*-sf= zQ@FhB|MP<0rv#e04j;$-E>Fo6CX_)097xnW=5nor;Yg0jb5RzsZG|d>9!@r0L^uV+ne_?L4twW z>YCkY;?+%-ZYNfoh6Rof8yT#jtcRaO6@CEoL-T}gc@unnSI_&ZBWRYHrR_*EKcNN4BG)4Aky>HhyN4 z_GpX7yRM;A3Md*8niaL%1>HWhEg?Z5?!0boChmer-N4Sr^W z*82Ch%#V*MEe81A>IUuQ$AJUu&6i$GIV^2!E&ivF-9G(a&?jDHP*2ao-arEzO`dPY zleSGqbH(&}KQT2NP>SkR^E>atEn3&iI$(OWuPdnU*xlW4?H`DQ^&jEoZI?fx&?WB3XvYTqb%r-Q!raP7PUBOz7Xpd~hxur%fnBaO= z%4t1?{?_lBJcu}uaus}l2wC^KRa2aX0M8Oq5VXLFSPDV?NKJIL`r!k>Abb#8&#Esp z@0lI75P=#OhNK94GVd!KK+30RevNhzw&8J+@9UZS{%ORtAWg~9D=}j9D*!G2*1Cq+ ztJKeIVg_}As95DURW(PNqPH*pNY^{?F1fsBy(G$?-_cAW(85aW`EqMwvPSl5^Hd#x zc}F*4xHnE@QFLMrggA~gFrcR6aIG7(7X4_D%+Qrl7=H(ap*EGuO^ZDJyul7*VQ_w> zLsR5p=x4Sgw|;!aiLJI#0}(d*9Ah&Z3dPC-&j{z1YOf3wOi831q42tPQdr`p zc&$UYPNa>D)l8&rs?)Zb-F!6gKz+#V<{^Cq-pm%2kxP%7-MbRhp>maipJ>VsseJcP z6x+@8^R)X_+ST3@2~2(Ec|s4lrP({`%B!Me8*vt?v}AMh8m^86!BO8%n!~tZMmJY| zg>+eXG}%I~R=z>gx_f)bu&Bce)=&Av`Xf!=K)sUk5A--;;K^xpnydF0#mf~cxZ>rw zC$-QHlwk{@wPG`*LqKw$6THRc<_%pp#fB(p+BbkwxWyXpZsnnIX|k)BR+e_P&&FTW<)1LPtY zNE500NH0*nPAu$hRaaGCst!u*Cwmy=r%;2Jzoq&ro$pD2eWB@5uG*~?n7_pK_y^ zYS62BI%ue(Q6qsH3-6yvhD=EG!n|oV!LMQ##H$(M^-e5O5KSHc=J_TqyDub$DBE#_@5t-%gWkm) z9{W5vkc}R2qgfw@oA5DbwdEOx{?O>t=2uBtpa*1+92hmZ#tOv+eBzTEaeX2jPkL&g zyoftAr5@!-S@dH#VAM^Uf(UTjIKLVa$>-M;GMA6L^?W&Kh#tg>QnBkMxT!oe+`fE@ zYEW>}+vh9^WX0Y|ni=wLRJZKBjLMMgn1|@cP20(uVl}KwrsU`$1SZp)M97Wkn6DfNUr<@|?vjfdvmd zLm@@a$A2&rPhRq|n_Df-+`Jw!-F%soo#7rr;s|PVrdR4MsznR6Ou9Qk;6Kl|-y+_} z4rE20Feq9UuX4JhsVr)r9>}}d^zbv=dpi|hj7|15v{Sx$UIvm6pH-}oPF)(0npn^H zlv2xpsF}qg$ZGX-pJE-qK;!;#c=*@$zc(AFf2b8F1QMrQw?e%luGG#}F`90eB>D}gE3}xl%z$>>m^Bnb278JR$F)U=&Y1YSWIrPiT_bil_ zcWv}ZD^@0hu`rR`A zy8t&2k~L-nJHs+g?@D!RYJDqopLLWUVw~C2SQ_TWd}f=tQx)rfxUVubVt#_nWJhT! z=#>!vY~KjVdNO>sHV4G4*gy-wY?EKLdknZp0eW1--f}2k70gRvSi}I%RXb#XbnQ0~ zJGt@MfKzf}Lc&0T*X@Sejr2d3itj|es?@iYY0WQTDT|}Z) zN3;g=yAKn1>^RmjBq2pPzA5QNi%U=Ez*wP6l4rI29E?{NG*;V0#p^Y7+bATY9Pv#z z*k6lW6@o4!dL}-zt$$`q&}Q>U**swpUiZprP>%YbF<+rQZpAMYV-WkxB>GRcys-HjT|WpTlmJJSqFQ{gGxA!tXIutmTA(X%m0ybCt}_G zmUC=axT44%8}*IY$E_&&l*4mRUXN(rMQ6$7uj(1g9)*{k;gO{9t91&aP<*h{O&3Ys zW4R0usZZf7A9p7O!-uuK2{;LYusU>k?0QzEQ;;YQB|25jFQgXhVpnQ(L!kzmMCa<8 zAoj2@x0krl+c)Ap8=sM*s6nrPo`rcga<)hkLfb^`Lj6dqQ$zDbqHY_PWwd?lQd>C% z5{b78I&OSsqZF?_Uy4*-1Jn!j0ISP8Fq+d;JO`3O_p3Yjat zJX+wnP4$?IuiqfRXe!j;XBn3O+j!`@>GJE4R6wO1gdp z>^*JPDe|_AF@5OPOaaA+uFW_sZOF~!YR1y6>dPuVvxVcn?4R#XWahaCTaj};VtR>d zKJkF8Ao|Nb=3CVz0Hwt%&pl}=Q0CKTw(av?U-l0VSyJkbHx)Kn>_3`dJx?90?Cy2l z@gvbaq3|l%H}c{oi=#ZE!zBdsGDudjyTjeD-|fB?dzL6A#eq9sXrV0$j3xqLzYB2$ zh%V0W$zB+pw7&;Xp&B;~hqWr#AxnB)m#_inhVx=6dQBTb{Jai=41x61{S%fQX?RGU66<(`$i=l0bLM`K!2L^ zE9qx7Q%f7nmul~*-Vr0~S#1t${n-|kaL6gDg87tqULr9Xelan$Nic7kt2@mh4;-og z{PB#PkJ_6hEBpMFT&92TtWQf`ib|qI?Tld@@@9PAkV&yF)kV}#a2uOuSN%HLx#y@5 zZpQjf(*<`*6&x9wCJleV;Kb*H-i@dQPtCO1Zd~%`2`da_kB3n3Q&uTQMyHa=` z;9=~T;69P#zN0Z|rCE8{u|!OoS7_AN*$7+F@~@lBCm7QQ-Eh@aESB$d~ zTNBnctk!q@Y!<=2sDGnc!kYsD-#vQmKkzxkTniij7=}s2;$y_4~|RA z9C8nU!zZI^S|EawZf{eD{3Tc^H|;KFuF6gg`~E`wwI0x?nskKOct;-ToXF!q&Y((1U(UVbTi$Sd+H< ziCIjo_6yca^^ou5ljvaM(s6LryG(AEEiXMw%*0$%nPi9zi%>ouY-Tq_Rdy`BEYaj% z-}kOQn~+n_&NZ$8t@8{WzU_Q36woP4M2JemNZ_HeQu&6t(&p`HO=eq(W`E_DfM!*R zm6BSg!FB~{HDHD6#m6G2<&Gpx%2f^ycqmAvgaWYg#FdN#AS4Ul~ZZ}5dP^(z*ofXyMMep$FAk1WzVkU zD+1jgBeQExvgac_+Bo`8_z1J@`vn&1YP{|j0EC4vK$BzXx^aILU7no%CANyE*)NFP z=G}J+O1r?9%frj|pxG|$fpN<;&y9|d3e_*JvB^??srPRE+w5qw8-O3#?oy2gFI zAYG6ER&53nQL0PAKR-@1)BSx@zMn*7Z8RKLN_{-sVR=-zPMqvFo!Dzx1*=s36zxbrqVfhJ zvQg7@&h1+;)!|{GlY$K{Vc4l2!JGMd$d@hcT@-}PduhB~>9TXg8$@uWwX?Njo_x|1 z<8{xq68 zeI3xNb|chT`aMw&6Tn4(jjiUia62*N++g8rE&y*~fmuSv_~Fi3ghDsLkq`iu=%9IU zc*~d)0DrV|y;GvPIxW*FW^ooFNlGrY+M#pwes7H~CC&G*Xpi%;w#r9+(FvyG#a0(0 zls+~eI!cVOjUMq|f|#wXEO@As1x{@WC@B*okED;Hs?cxxV-P5rMrCT;5>6sRB9k16 z5MAPgc>H1g&bu0T*c-Y#%`-LKTL0aMl5@42VgWHoW8tHdw~ZPJrHi)s~_G3&l<8&2u#$EZbVlL$Km4Dp0l|{J0acqMq z(dXqdsR~jjoU2@jN+R<Lyk7k*r50W6Ib%{@Pytf5CDTaKw+-OycoMog*&UHrCeOmMLmX2qBKmL%hrg z5^0_Uwl^+vm-crUB!f!Q-wMA4;yq3+^39#wztVZtsk8{;X9y_19c zYUL@35$#v*?qW846sLjt#1Pma(u=$0>hINpW~HJe{pnFtO7$(r$Q=^5zFVx=540iT z(hcC|NpWjGjT#`!3UznLmD_v8?p4th(FCmzcz=UYeNJO?9ADA^qDzwqc6Nh#YtV(0M%mNPHy*{sG#M44*&rI*{%TJql? zI%EBip?$VjcSao`&ssUy?hW!i{7@F*m;oCM@=Pdp5mOtXS4B5Mg9J|&+n-0P zEJFcnRc#0NRDUBt+Y#Ubd!?3t{f({SIh7_3P9-Ti7i7|N5+tOV?ZuZnMMF5Gxw7!0 zYU0JJKRq%=w_1C%w(8PK?Zkt@4NB)b^|&aZI9_vA9Y{!g2>N_h~qalEk2gJ~FXKko`nL9h*p zJdbrqT>(N!kJOFEWn+o;a-4eF4NVIe-orK4*?A6xB~By2@I&2zL;!3r9S90|n@gbq zPfB}U4=md_kOrkpC>}D^OeK2?RGmw2u0cQf;yO;9URxMnHeK6e>Cmjou4dvXgtiP< z^k1$WM`QA#lc(uxInODYGQxn2E|FwC(GNB8i$6!n9WbA+u_ zUDGc1BpTlY*j#>Yytem|-gx4sm0Y!p<}6nANuu_+${k5dO$K7> z!L-iFWj=+J2*+UxAt3=Xp0Hd_PnWNAVban;!Gx?i^zjSS?OH};-OH{Y?-hcKPbkG_ zrzwx6a^#;WljwzGkx^~3D%F8|Z`C<>5okjUqj64Rsml_! zKgY%25|nP(lCNanV+g90169h#W)lb!nCwIh;N&D0bXvKgULavnxdu5qpemr&sVePZ z{A3^kR>B@vZ$j}0W1YiQfg zc6@7X$KjH)y}h}%d8(yUv$+Mr$<0b(m&b?!CIGrwEHS%*?9jaBC;dIZ`{?$*vIn0# zZR`TE6vun%D{^@7Poh1paCg7y&X0rmu`1k^^5yv51O<%w#T+^|GdT-PG{WiobxP3l zxgf;X4T=JGBjlXLvr!`t(*+WSsoYQd&wVsWAZtUY-eYT6f{$L^h}iAm3PG7~3|!g0 zht)pPlfNZ_99d?C@R-8esF6~%V%i<&MM=KX*y@gmVYObtbsi6ac6ak4@vsteJ%$ll zPg{3}=}*1k|1RZ8^=xsco&&%6v?JL)vCf*tW1pv#oieJ(BHPU;!js9IA$VcT1R*2= z=j+L+cSkSwlL7bV$sR+OEp9jhBb>$P@G)W?745P}7?+X(aq0^*+}v4SRSx{>nnIu; z5iAO4R#i2&am5K-#vRclM+w$5{NU2BP;9)ev7G-VeJD5ljYWfEt;E|7oFoOfSpf9* z=HU72pPhdu4d(NlE218SnVJTjs4xowGf8({;rRrrHEP;5^weG*i6=VWc0$CaUw#@}qy9{`V?E*)PI=pBCC* zY(QgPN(K2mdM|C-PgPqRjAWO0fV*AZ=CQM?TA`oTRdG1iF1ebW5^V6b+XSoM-ORYe zrCA$@+Yu?+ibW>03V&zBZIS3_E>n|lS6;!;ntd8W=K_Ab+7jQe?8EN@ka4E8;KGn} ze|G?JmEpM;H0^XHBn()lG1`eHiGAt%qG{7faV~I(X9HCI1XkLzva8ArD+2?ahyxhd zk_1|?}9zdY(N+KlUd-M@+%32K^(;g6v(-e6I#>@7*}@b>No}-Pt^D*ZZd@$)uDg+CK#v#OZ-XH3 z6tlbiiratq{}u)577pdhhO>KOAyR$hF8;~DgNyS&r60?F;rpyr{c6?`2f?%QQCt#V zVRbR=f0E*v_`jUs1^ob+fDeCpM0+A@x1zh+lcEUA4?Zaql05=e-?s;zOvRqqe}C~V z5SK)z&TT;DJ0SczEQ<5}VaYL<2WyO{xqT!7tH%$iNT{7WG3qAx9d+4s*hRg`H*c6_Bb(sVwd4QqEfz4inn z!Vxclcfg$~X%{wZug8s{_qQH$S*yF?h~ZluOA_2%eu=cc=~qAHz_H+78_ z7`i8>KW7yqj_Ic;aX7oHe`Y)V1erD?NWqHUUZSKNc{+HLrXkSzw)`mf2So3S7b-P< z{<4WSF36ipy>Fp)ctTd17hYjr_g>8BuMS|>L6;u4O|YKKcf{UYguekB2qx!g@F{LK!A`4jJ43%K?KA2_y?`iHXjJTgC^L!_gj2h~;B?a!*ouf`NrP~YjZo;Y?kAH{OYRu{C zF{|yxr*aw9RNLfV?_60;T9IK45{$${BV}(yyW;%M`FWUeUhV*I#Lt9zk zf%$?TrVJtSFfRHFqxqoV#SyF^SM*z+?}?c#rfpoZQTV8eEqxZCTxdl5-aWi2rAbp zYZ?EYVS2t)vHa#Kp7U zqlG5)z-CdNy}n5~GFKeiqWzGi174v)tyu9i`LqPQy5W&$gsJ-0enpKiEkXrD0tS_@ ziK){49+(P!!|(pF?FWeM{yt+sSiVq9$*4Z}2f)?I$kxb?{iLi2{k8=r-JkP~L6GUr z>_+r=PsIB!LQj&cr>*eFQem$M1^OdtC6t1jod0n~GXuTY+<%O(%orj!`*KMe#+g$! zUvKE$SP$lO55?Ps87(ro?H7ELxu?{J-BU?V+u8TD;9qL;kDF_u`_r=KYaVd_DcHoD zwh>WZKk0Ho<4Vc8ygo;Y0uy33)+%q3SBgqbvot{U$tmU=F)cuxLW@m@`<_@#{_w{h z`1fN_h=ZSrEBL0wg#5K&Yo}6zC}pqUEJ38`zA<61ofD4B56V`|bV&AU&0gHI$4O6} zw89J;5AS>9_X7|BFy2;qp8qirPglG)WawdRCjd3kXA^L1&thvgJRzXzs2kXw;^M?~ zY!IqM0K%JlhX{I20B$MNUd@=a_xjBMrhvHl>(vT~KziSn*-e4quXk>~w;hajP{T4a zq`NNd&IU23NCz{Lg`zIY?|-=HYP?~q{{J+N@*ivQkL`bla#%q8x-X=Pk-_EgCXf7@ z)qfssTtJwDcx1AJtMhEh}#bE%hk?Mvj5tXF8MG+B#R+&i`3weZ}h5Ahv9u;bN#dfaI!5 z*a3bcoDkkAWeiaV*4&!67YkOTx(hY8Lo;I)OsT=d6`GiGDv_?U!z-hZr6_rvf&tXcI7%htjb&vM0#M*(NE>zqjFyi}TReCEI-cehbWw8OZR{#m+f?TrV*yOO}PqVv60@8q3v%&6= zu9djy=@KU*HwJ^p$oI_8We1?KISBKNLF ztluiy1A<^gWSXtchUwfVF+ z;g7Zp=7vU$@_BMBIsr=#CUq>M^Mmj#zt{WWyOYo12#CbMfuEG`+BEZL<;>UpI+*_3 zUHlJ-BH7uMz5Z+<{nO{ZZ$#wMT1qg^Gez)QKj6aeX(SOk9cP%(iN{W2eaNJ*Bo`aK zM-mKz^3DRNmoL5@fVbsF$r|nuSnS7{Pu_k@k@mE5z$iD}Y})hF1qh!k60i+fGC$D^ zKN97xCgI%__^%J z58Q2c|Foz63;%z2+ysvk0z&=BaF5uM>*IrQf;quYM}+o=R5dKBiam(3+|lWD6ju6d zLTxYsN88nF+5NB%p?J*b1Hb0b&;cBrNHNB0xHNZ^T*S+0&o*xi17g>jg!X zuALKfy&{T{2xVIEu#ZQ{YZ%w2X?}P+p|ihn+#>&8r8OSaNN`9Tym4@G!C9ZT5sZ`9 zHmKw3q0ieS?2tsmhm@a^I`2q%@a+(ZgIBeIVeyBZ7&}L9q8AAGlnwX~dvz4`EPD}15BRO_Xb%9-pj{4tdL6*tIOZDl?H4&e+99OfLu!BDw(Bh2@zOa5gdwrdv+lAdZt?J zjShx|Jb=6`$I0&@#Y=7w6#}`M`YF)7i2l+CMT7!D#_Oevee}z|--Tn7)O&n*iqXWy z_V9G9ZL+f^BJe<2-JH_R)rdv??b70wbPGENVQCnTb0d7bQm$V&Xp39r#;93m0zpif zq?PJ-i78DIG29{76a~1&{q0UqY<`b(@BE}7utP+wTdsLGff=iMxu{i%X*wK{jYk3g znfrU;H#I$e;!+s1Mk#KsMxNTl)8Mby8V~KA7C7c%w|f6G8(vGCb~^6(up8tQMig*M zvPBCjd#pQRQKE@J-%{zXlcHZ(p1G&HIaOBK3$S&piri z6q{!k4Bg0-Q)S>u(34FTc|(@HOa+HbKEEJ>kDVC$a7Z{Lfyx>8^D(KrJ01R_=`pdf ziHZ0=TyOX^5d`ADnOC8DSedxqyvmqg?FtWCs*mmIWx`Fh-RewpU21NXv|Et%g%?dR z=NqXhDpxtFKkO8R=+pSgw zl%uj#W-06f1NoI}@jWx_93Og=bSi|FViPJ^MRv$v=Ji_vkwI4NmO(pj|)NNk`~cx;pKgRvqz`ynH zP|B&4L(dNG3Q=D5hoZd!nTnz1+{Bt_Vq0GOWcxkcoNJve?) zsrK;~kB#2|`Y=!D@S9ef^d=J2-FR~|__P2;?nq3#zo9p+BQ^lOMs~d;BJclVUh}6> zqS&Q@fJe>7FgJhl_>u}M8R)?#bG>bPYIO&MG4kz0_{_1Vk?z8(> zqa**V2mg1^{Z6%^&2LATa|?X4fqFVm^y=A=NHL=#*KV@$5~c21nW&u&H!ag*`h5gS zU9TOtGt>xKC?~G!wJq~H;BRl0+CV3Gb3Xa;i?X)O=9i+@FY6o!f%8K#;!J;c(kF5W zU?k+Yc#>pB(_f-Av7NfS+dCO>Is^=dy|{n~ZJIVs07SI__Sig_t6_{3$c?J9bI(b- zOu*Qf;zeSK)po|YI&nxzg4m_dxMRTiXBcX!-W4^nYyT!KAt^dXTNl)8t`ZoN za^tM|BvbQ<01iEXU8;;%|p3g@AIyT<}IhqtfYdi)GXe8|pp z6;Inejw}I~RdMxqSG^`v_mgu2V3vC)YQL1Ny3~BHqpe(=w0mr8V}E`vAu?3`?8UwG z9`U+iZ6m_SVRbE)7U#9gx4K7fBWeJ+F@Si|0yhl3?N;sx@TGNqidn#&?2XC9HTf(B zIE*Uo5CCa+d0W3>d$uy&)9lW*6p!6xl%{P`1ORFVkWs7c-uE4;tW{5!FxNU*({3ss zm0retX7VnX{LI$1rUu@qI3BTk_om%sqiXBr@(W2j1Q)mwD2593>2E2Ot`Il)V;y&@ zw_8UK5)hDOXsUT;Q&kC4JS;w{x|2*Td3Iuz-sHMN07UTVO zzzqZP2(Z+Uuqe`NPac7apX-F>F!W2C%Fgeyw}*=>;x=zvi=R?5#CtGR)p$g;RwU8*2252lVrlfpHl3fRQ%D&{j!atGegec3cOO) z`_chWE~ln4qe*(X?v@2_FFNxn&&_xuC1sJaI>}|q>W;9y`@dQ!|0mtf#>*X!qSJ?)7&#$;S3@S#eD>nJXZ67Z!X!h!Xm*R@}Xh!%DehGzeociJoU ze-;PzXFZ{~rek6!5P(@eOiHJ8VF^Dj2YxL3Ry+rexTb5=xidt7zBV}6r2o6L_ z=A_Tzd7>)oZGdCfJuTzHam1w0);0PP_2DW5=hCmK;QZPP4a6$xUAkh>HUkB<| zl86@;d!(T4F|!UIdy7Bc$Ys`)@ZM+to#A>4TxCV!u1aJH+xOHxn*!dw!6>f{^d@*e z-{8Bi>YnX9#NUE@`Lh1C_%a&h{TyKRf7p8uxF)i$4;Xc=h%5%Aio3AVTL__qqN@~z z1QtUm(j+XQ_ug4iq@xQ$5}F1GkWfTGqy$i!QiT|hE+D-tz4;B`)_0$G_uYNJ_uKDz zHu?Q#?#$dd=iGbe-nnz`IsZcl=~z(8eRQfOXqUkL?~q23KVV@NKly3&IjI+Xt&_eX zgY&?1A2qW7;}kvB5kFl z`@gLZvfkAcf&I4ouW}y*AX9%ld*G|?yz%l$ncOq`^y)r?U86jBIZDI zg~C>;bwX>|AL&9ea+tNW)MXEa{A0KC>&NEL9RE#0Ee{p|xUZ3%c4-XlqQ(<(wkk$U zA`$CQ{A6h1JiiG$?S4>!teuD<$vs6{Jr~uw;&>{$A z%wfIg#j#oXRZAn0=A->K%#qbP*(h-n1ab^szN{%}c_2ItI{|H0yQ8W=m1?f7t!?1A zf=v)A*Arc&N<283_VZ>qFxQ`$KpN;J*rYNuWD{+Y2;4_%7~=*hAZTqbb;Qd6>lYvH z+0X4cAj+>A>w+~cd|aUCg3T02vHwcBjah?Fat5UhI%9eDO0JnmPYE+p8Lk#{+c%7B z;N5ihicb=-rZm&h{nGNfP^sxru8y`e(Hg@}>yiczEqW0(C{2u#oETDgju^VJO29U` z_0;tdLOZZ2heMiV3lSAMD9th&N7MB}_&Ad|OH^??S~zX^2Y1!K)u81QK24M&mJyV! zy-f>)YX{Q;ZCc!^BcVWWC|@iI&ATkRWSDX-&y~>k{=VyD7%u9AZBhdgTxsu$s~l~; zMolkiW!pY89!E}^C{f5Tnu#Ww`}K^2UpG4@8jZ<_xy1gPWk^;hlkys~Qw+WZfI7#z zOGEqtcV%Xvu zdjgx5eGYWE1noW5|FKJMKkw7Onf~MEzyGIbjpZT5G6l@q@JWa;f{3_79PKWLz}giU zY?9KTIV@pS0f~O26rgMI)@BJQ-2#j*57srGPb@;g+nFjyup_3&lJI&e(0Y`rJz^_9 zYJ3d}N9C_)-1Zf$!VGLE0x?O|Rb?XXU&i^Gn>kp5*|na1UTQO%9a^ePg^b*}RW=%- zhv6&Z$&TUV*wI7pB!N^@S-vufk>Oh{T;-K3(Y1QjGw{+u<>ds*R;{&*7D55SQJ<(} z#C?SVrQP#A9MC*!ELj8sfzlVQ?0M01XAuB>jUYaAECdx<$P^Gp5*(O>`Hlg8Y6zRU z-opE37=25wiM}^4p+p}=Bq;yLa;oJ4H0^1oHw)a$GK#C8DCIU|r+{b$fD_@o1-EK7 zQ{Q$qw-tZihtI}OMa_rsMp1^DTDlX^toqQ}qUS04IMYsO=(7%Amvnh-MbH zo>J=yft7VGtmLZdAU~($10D(64?coPEGW3@2A{0<>k=kqqnbXVb=N@KK|x~(!j(n2 zb}d5@H}~?L$96VxSw^nr#qp&5C+V|TOnJ(D`aqE(v~Sg)Z@KUJnpDM{OheWIvmaH1 zzj)?8ee@WGj`uH-+ixp)-d;BCu9RudIndyF@h@s#eg_k6Uk<56jlDTq4PMB17CjeVArbdS3kzA65ciOmphxwd3-N4-vu6Czc3h3 zT-$xAQcyqh;*p%7LnH9PDqwv)$7rkLIc98oqhT98J{Avp$RYeI+vJ~77W$vWd;Hmb z|0VhN^szhe#mgC$3UTGIPTLf|kUr%;B7ZpMbg%Sev4a3Xil}hRq0K{cr@DaRK`z{? z8lCx!(dfsL)t3g<^o3W|gyI&8yB;HEIePMJ$R>ggA??QBqy<`<No zE+cdNnyrdmED@tdFMaTry(mf!7HyWZ@NpGard2D^Z&#=CQo`n~QshD`D&lFZJlUlv zZSLdI(ywe~gzopspF8fj?z*0Py~dzWcLNCXo)@g94!D&_5+#EUJo+Tju~usk+h%O$ zXX!1f?wGRS!~*UW>`?qlrcjc93C6+9HAb){{ebmw&|d!22>hIHWvoxTU6*VBQeu}Y z{o9TYPj{gZZ~x{e-~pz#@=3k4<1W0Zo*ntB%!>o8v_Q<-pz5QeXZ!!M&jZ=gw|}c6 zJi}0aV15i(X$b!5Ymu02OzC4BPaARHc;Aada0e}pP)AaQBi?u}1II^Xa2kAn8LthO0X@FdTyua>@b5;y7D>Ry)huG~R==*EKYRQ)bR+uxqWpDr#InP3FKh`m zm1&}Q-h+Do3CqN)t*>p{1w-$+%?UU4 zEk`HUdHbdmylR82-RvSDnNICzJS!jmepS)@VT$GA;e2Hx&splzd~w%Km?z8VoKM4L zg|=aOlQ6ile5SLB$#~8uQX$@{wPz@&vA^Vb_T+~Pp`t$L!Jp6V^ZG=JeJt?v>r3kB zeb;xUeZHW`+GcB+IqON_E_A+?C>-vf-m)7KRw#tEXt#&k3enNBo z{f0x=?*TX~4nxFu&C|+GykzNdm+RcvUbzCoe9D`p4G9st9z67wm{_FJy#KMI;*~>ehw*pXJv}OPHeYUm zF`0tlXmjG)xtiC&SKl#90CQ_<3_uIx*ORoQ#4x*ZzFAZQQ~el+ru+kMK>#E#ch@}u zaFBt9aJi@%uQcpr<50 z0;j^@)cArLRZzw4f#OfY3<7AyruU~4j+{7}U zOOtB4o+kU!^0-f;Om?Y}<>v{BQhH) z+Lz`lxc1~`OAaFOwc|vd)cP7L8lBb2$A2#^4dK?sPL(6k_5HE~d^$SJN3u@l%ZfH& z!-dkc!(1hw_vBfT#!osgNaR~;v(OM_GOyQ!?3qzY>UvM2IbH{lMkWWk(e_kNMq?Ju zd*J$T6bA~-ef(RahG7S)6wL#JTL8!Q60B(e;wo%FjATPddOSvVuW^^(-%3xw|3a)c zO}v9N88)c(S))V_yn(sdKikfDf;8~*2u~YAbj{X7qRrvUrC#NAEuL#5dN3&zt=7Nunsli?sTFj&(rsD1AIb6p=iOO+a@9_5do3D+^Q-Bw7#FuJOdV+L*m5D6#9q5=j`ofZT~iz;MMdoJ6p~Eok(} z*x(6St#n7Ke~@W!4+3-9`5`vr(K7Ki`T8RZ9kmLgZEBnUmb^Tb;)7VJr;}JpMsz^Ht)$j+C&! zqMkR#`YdDIc^6yz#Diam{psM~zW(L(J2j>G43kK~kqU9rPRn9t)QwZ}Wq%P?DKe4m zq$c0M4lSFM?AE0#seRY8YCS`V;uVyy= zMw@5lD;|D6r6m7COW~|qgZJ_ATNUz3gL3kk^8(7SnCSPQwximM1DCvq#80)Av}#hz z8<|d}ja1zF@a(mp=iL-9jhe#Q53xma?{8iH1^ESl`n&&i;>(&>u_%suZto${Z}n%C zI{6PcCKYmO&a7WzeJ#?^GM;kkAx6njVIe8fo!)OWrMzP$X~E)@c|2tcq4#(oiVj2$+H0hLl)%6B;O?ILb4p+xU0`~x?f=1B-NlDQ z79-f;aO>T(Vk%*IS!6~qXOFplD!p5#k%bOrtfib)q6qHNG($)8`fVYYMA|A@z4aq2 z_I43*lZ?s36+SyIblpGZUsfpt0=F}_Ah`wDLvcwDN9X!Ub2Lf`oTemTx6lnmX_kzu zW1vy8y|%Kpx`LquY$q?mPk>a;lvaG}O+wxdIhA-pRFjpTlsoG=hQ>v6co8{mWes%& zh>6zGuUOA~XRnyj? z{atEUUax9wtMl%8c3{P*kiBztg_)zj_)s9x~BKggcZ-Vw2AG}&<+~(Qg zcwKZ~K|E*X$6?~hIr5tB5$RVl?jL@#=@I+1h?64!d?SBmkaMDwZlwIdUv-K9_Ut?W z{@)Q93j11SX9ACwmJ0p?azdHQSin;$3rE$W)^LbiF(E6kiN)No6~wr^TDS~ z|4b-;R$ISF{<#SSiUj~b)G_rNFjV+{`AmT;!pOb;?)L;ayIRZ{j90P7Q(C^-l+SFi zi%m@nWb}(;$<@DL`s0Ceq0*Z1C|dHv-VO*5qvRRBIKBSj(6@V84$;m`mFlQSHBZ9; zbeh(0-tTrzEeg@{drr7C0%asa!ejWu6+|gXiex4LJ}=6v9R=WSEZgsYH8(`e(w5W$FGw6AKlo9mpCt}~KndNoNLc+! zb}}Z*;ugFmi6pemd{zQg`oX-k-yZC6=)g6uYyM`?Q%lRiMqad6;<6E!r zi4_CBtdG#HWX20uRgVM+3zwVwqqsNv)Hsiz(Qa5Q0t8BH9jNIq(4BqJsB<9hi(TG- zD{V9TQQwbrcpxOm7xRH1)Pd+58v^#?ho*XhW}g16p1?r(x-nx+cy2qwaB|^sk1=7+ z*5rFG6(k}XtN)zJVnW0h8BUX#-idyA*`>?-!f-r3$a$k>5Lr$W@ws-wBaI2At#z@a zMoCUxAlZuO=S)uO{n3O&{Kj0Di7z!VHKtI7(*NS+#Nlz-J>Cb{J}H~8tn6dTaKZJW z#J|3|AV{Zo{b$nsrR0B5Y&b$85OO7AKJg`&bv&q4WaIOY3^|pl`5V^w@etWB87AgD zpB#P>LDOz!e=AJ6@!PffJKqJC7HjhQq!E8tb%K`_HJWo%)>q#Lljt@HdLDHnzx5md zwFy&hjlCeLt>V^45F)yV4d3@I9a}D^e@N;Zg*(=~I+haj+no2#qR~!goS9H4)%(rX zEM7RflT^?vi4Ur=5F@y9?IJ|edsS$J@3jGmT#$50(OAB5D%034nfq(!58Yi)>2Cbp z6uy$DKKYD%z3aC8lLJ=qfo`XtRf4rgL}{)Mf~rwu3|MXJ0k#XGH!6IztGW2o|FK>8 zSv~#f8IrEn9P?O0Z>(M@ zd_+@`U-v$hLcb})B5S9N8ddDLe*&qa2S>rDK|?6iJlZw#E#3Z`c9#rGC~o-nln8u* zR$D_42I)+GMg`{|1xez0uy1d>#W1tu9s!u{=v<8Kc?=nf`>d`eOIq{U3Z1 zBv5Y_HkkycanymIrI)Z1g+$E8V*FY3xIo9oIc#;-D=G)-#=&=p0g`mdV?n@ef3G!` zAG#d3T45DAq|_NYGJ+iIm(Qoy54t2JJHMx96^ud4Jj7{gJMD)c(Tg28C`??&4k} z|1*`|tA^j${KcuXs_LQgnGLunNto2{Yz#QH*NJHP$rS63y3v%bCDOCOrt**4kTS0m zvAFxog8pa1OD4n9n(6F*LlQc!i(wf51z?%M0)qs_l!IaQIjCQc8gvJ~Xap;Q7++0w z0JB#mkC!aZ29uP8!iS+l?SequXJ}Yh4DR}qtHf4zm?^lyuQTywqKki>=ODrEm+)T$J!NwB8uBZks!@7r`D)!A}LeIT)Mam z!IX@QJ!w~VJ9;f{qv4x^!PBV*RbP8j1vXkzb^LqUA5Wc;A4X1}z0}~R?r;8}d@?qt zds(eNms9MadF6Sfg4ZIRwV9nT`br$^Z*oK7IhF*pgc}kyh32um(&3J_>mx$2@zzx~ z*^T%#X@O%M|CoI5Kj?pH`{zu)Ny3UiH2KPPz<}et9AYQtyUbw3IK{GPonhJ;ZSR;y zu!Uqc_X&2fqs09S^W3mDLbz5JI-9cJ5cLp%guC?Q45_HoGFTObQekdEY2IVXX%xhw zr)!6WRyU`3Zzgm0X8IVlWJg6plhVT7(C}$kW6+a@x1Y#;#jeClFdOSzY*%nqu-e!; z^^s0v*eX8+%^#NGHh*cSrOq9g3ccFw)&GFEsQJG5+rS)M&IfS`^HxL|CfkP>9D7?y?J&>_y z8XK0v@*Ub-CV?>OG42!AcnvOQiP@H-q6~XuVOA}>prPMP;KBe%e2iHk=bHbl{H#@2 zC;A(jZEX9H^h(??@APqrXEm63@rFv0011ejV$f5&sLmc1WfIb|>6txHB3Dg^1!MtG zW~}j<;k*W_gE+`F-*gCA1%nf_yt^RvqTMeE(+wei<4X zEe=d;2v|HRWvpDbfrd``PT6-VEhOMucVCc?;~=RNhsC|6SvZ7ynhk;1$_iwN+0+*Y z%0ho2dMGe<721)G5P% z9d#5xr|pou?5+YH!KqF+*Q&ZKm3T7QG%Ew6Qdfk5&0=XqyNgvpdaV<@N!w+%sc?P@ zH+mJ3oou9l=`FFrL}*hkgmi(Dniuq=fLK}>4st@CF%Cb4>vZ=50THreAYt_bwVIwn zAL$+(0X)XPl9vlD)#)pMISNLZf5+{xVq50uBN07+vdoY#JJOh4Gh5R0cyh?s^AqL|^D_dr9IUsk}Il`H}o^X@wquf4D-emUWk7`X1tw0sL|X7)jzbvcYA zfQ|W}A3| z%9LF%Y19OvWD%OADg=1h5>rlK*lv_C#eKUzJ6&vCDkIc+kzIMejs8}O)<5c+51_j> z0Ej!sG0A##W={ooLEA10eR}5pS$O=R^Zk1N7d1F^Y`(=V_Qu3_s)g{Wgds6n;Bkjp z?Q|L!Op4t>+eCh_3!;+G9j403sCkY-1)Gezv&u&K!G)c&_VdC4=WoHkDsCP`(mkg|cl*C>?aH{c0GV6mQB zng-CpI)X$LelQ^b=rPQpqKIUO!)Tj77mOzQc_+y;?R@hY$%^x>-=}<}3-%3b1^ihG z0W~{JvjU;Ah3Zpo-N_zko~A5_L?V^1^|BO+4_hv`d46$Z>Wf|ZWPG9hG88szP6~c5 zaUfEvv_>qX35aYJIK+1VF;@+I3h_ebQ+TOy{hwwl&?t-X?FS21>wrhQ0KK@tE|EhJ zMx1F)8~2VBRlVXQI4m^zS`WpAX4p9{cvYf1IdKCCVWtra^FNbXzrS7;L5@)O;xP+> z68n&H`RFGZeF$u^=|{RpN9JzqQUiWpl>^2S-iEcO>&14s`9zKIXs;#XDWXi%>xu4q zq(dmttf?v}Z}epzzs{RWKG{+4m(S|duls!OahvPzibbna;xR20xY8tzgdG&u?qPP3 zmmHTx+~VImq`@5%vSv@noyEB(@jVCa9qy3^ZjNp)Z3USf-#c_ia*j^PUy4@47>yr) zT&5NNO$y1}k8%}b5HQ+Y*5Xcs=um4!ZF_W(?4!a@#VzO2(|AD=uh@!i>~QSWMfLKH z3d~MEhf-(BWP-*Tgk@VjP{>+IP%4#~Tt%*ToLWrIb)@nyV1zf~c62)Yt^wEd0`(#X zX(RI22Z>s5oAs6jx3bgM(t&GvJNfLuO&-Nn@YHA6{3+zR_bb&86&`%8O4cPF)|r}o zCA($2rr#q4w~TvQvH}Fx7Ak?Nx!8TFxLYyNyOP6KZgbu9_^Y+^?Y=z5{mA%ss9;VrOCh52v zo*icqbl;GJDJHrm@fSWjp*(#|lsyk-OXS&> zXbOlO!+R>=s=E5Zx(d4moX5GN^3!gc+fq0Sa+il?8F%KP0;}SUdCxX+{zMPJrw=a> zB}kMC_4!O}Mp@b71l*QGy+vo`>?tpb*KJ`_#Amt5^^A^VcL#>ZX;=@lfuY#P4 zk90-c4CgK^)E%(+<;=TuW|&*)S7fzE;WBbC1x$lwolPzJH~RcOAtA#wk!O8{)b zprJ3$9cOp+_$2pFA5{Nnq z+wKtIoFP@~a{0A01Bqlq)1$vx9Fi&QL`MU(&bSU}d^u~<(4#VIh>PX$8el8c&eFRw zL#4R5g!ngR6Z%nlFeForsNWbH!q;73KwP3Kq8x?N(?NoXEBxPu?t*3etPEnw!aA#d z*LJeZ{EGQ?Br31RU9^07>q0Fng=t-5d~*(npTAuyRykQjeCgHma*d>R5bE*Wdb^E&9W( zD{7#{6EUyKVKkI**CuYKVwlPgMnnmF<5BZ=md;%$KUjr=!{4p4> zG31*li?Qto<^3vEBw(Ug{hLg7PaY>C2P!TQZWD1@J_|eHoIRH*Gau<<^qRH`FY5Zd z+RdW8vNuj3@?Lp1`k%>miDvU+csQ@8bc5k__3riE-(QQKewr(m{Z7Hn0i#l8T4l;{ zwOdadT1X`i88*Q_Q7_>e8Nu(DlCM2m_xa|TZISTk3F8DunqaEVqX3gwlKus8b+)q) zO$Fq|21!Vz>>FDY{@p8a67rWSzwt1x(jofq-m1BKMc{_VZNCRI)mg+KezpN^~1cDcU~vu3tibDI&PahJnwY?V7q}A1U~zIX-_u{kr58uL+xZ<(&y18wF9*%7UNQsE7$eL>!R@0rl_c1>6zA9Al`8Z^;}9HrCrou z^~v~6{_yJ#68c1JrrP~lw8O9v5LX~b`r;pKgWvriV_VyKTQ#B1Fr?|c{VecR9Ov@_ z^Q+#bRtndZE*>B7`j>1zqYBQOoNK>Cg=bQ{0#z7hzZ_TzY#@*B7(5VGwA#Im$M@?& z!KLfOE-U#_g{W(zToMbMmk09vsyVZRejxkTfC+z~0_>&P9j@ZWSz6=*POR{1%9X&I zFG7be%QF*mw8<#^4W;Il)gq{TzrhDt;n)udhGysq%0rSTUw#l&MYyUzPE z7IjYJE&1x2Yi*x7U>GLGECkl>y2ljtC@-_J0@$df-pS!T>NZ5&%K{%7Ol{4@Q4Jz! zHT&ZlI1~0w6p>Jd$w_1-ZfbS`_{vGGFb1f*JX5-A@~tv9j>)K*(WV|Llzge0bdjCS z3y2N>)uu(R>no=}$cb4Zz>uI?Wv%Vg6H9Tfcf`~3;~pKcFwivDvbb5r)bK{5#lS-v z*O7C6A~hx6b8@VH)Cfyh_A*jof*9i`sSWCbm-C0UAaXVA8ZUCFU>F>(9!z&K^jC46 zhaHb%WD4d<(Hs>b zC3^JcgtRIHr^Tl!DV4Ll%1GH7+(o2q#cA)tZ%uzXk$d$W(}51Qc^tCBBTdOb!C&nj6?;H{&D4NETHj zdkuXy+c}x^y|8MS$ayU(#cMysS%cjR`ru>mkfP9g3|9ya6MO#FK>+@xeOae!GR@9asj-Au9BY>YA) zcus2bRJ{6VOYr4O)-7-j#5xA!vb$xwLN z$2<8SL4UhOPmZ!N$ftZS8F?zXO#|j-c4hd-7Et;dw1A%IjYn$+_?(__KCh*3f!4Lx zEt%^UVC8_rh;`3suO@4nPQ6a2@@35D3{vq_;$_9j?~M z9PW0_+J{X&z4j{eL>41Nz3kEhLFUP?s3(Hz$`ghhyQ<1D_^HLFhW!!4(#QJ&nE{jC zY893m_p1R$`tQ)Nw=4KSjJNS+3{TVi8`2<}3~XBwOQ_0>-tL4|f_+=;b6Xg%$_ zu(}%~3Ef3z1+i^&qyE1rRF3Ls=W0QonMq|N_h{!*%npz`2Ox|NFb=I)lFXGZc=CTL zB{HkjGZDFMeBDzl=7^a>8_S%r$wuy&lGK9j$>oZ3H8TyUdcS}C=Y4WygAS`PKz-aV|zoAdD;XyrM>XNLypoq9UVL_AAt4Ko z4p8h#FVqxkafX~7JbTtuywzo^gEVq9ATd_iR`*`*hsZX`zRE3wI7^m%`& zrGLkL1UR#D!Zq_?|7L7J?($U0vf7d2);uvC$I-aab&tY)SN*ny$;D#1uUeB8tqTi7 z3quPKn<%PrS(-kH3oTGET(ZrsyQoigLpsnl?I69(SM)Nm_ z7`EPCA4d!xlgeD2=_i>gZX&}^_|x>JX&EcEDqvq%Mt%YbDyP)qV9cI`T<nP&mfKaiBb$(hdfpP?HUi=c;o7-XK8B>5yw8FGhgogF-^rr$bcV zx9F(&_PT1~=Zm7GoJlk7rd;rb^zNS4LSZ(mLY0j&F4YymR1nl5P^5`@9i+5d?hw(#;VB?-KeA;zPDK^>b zbYMe*I})&d=}diZIS2=A02isw21-cJLA28oSuZF_$~o%cbF&4xF=56K5+4oPx_Xt? zzIr*Yvc$fRK*~x913iOjfxZW#ssaDz=Edf+Ua{~JpR44FclPR&0~5okq##(UI=I-s zgkhp_K_q;c|OzR{8}iW3+`aCs+l)Qyf11}=P)$f z0&A^uzX%wz$mMZ>WrYYfRPXtF%!451u8>^_sTP>>mwEsK{-}-~2MRtZ@ox5n3rWb+ z1L56{g1{7l#$nYz`;__9$pinM2!Dc*nrhpc>a0IhF9&05k+r0IwI_mp%znbUhD5mc z;pPK6znW&-EXAL2ZGO!1JBd|?tgsG~&j9@r`JR!tkoiK~@WS#Na_a>p_`R{PPOOg) zNhln!s%=>$uhNbK-k4~900?W358W%0F5pHV%17jjT{+E5*~F$U`VOwRN~qtT&gj>H987t4YlvN1q5c zwG`8MX>m71!n0vpm-MNVg-LumA*TDuFF5 zh$9D?B;T_R3zVb4>F{D-wX$e>%R-ZT<-z4Hp7(S&6s}QyOWw**KhibjO}-tvy9Hia}V3ZUtD!saAI2YlpDWP4KR_YMxpSu z7v(c6r$#|qewK?KqIdB&HDhs$G-)1mKxY+n&c@I8-3Z1>_WH_+PFN>*gnlB{AAll2 zxW)4|TD-BVIqMQ>j2G(-=R@Pup3iDZ;uo0$cuU_3Rnu%*^sQ6Ya4}8E%CryZ>w)IM zB=bAL!Lx2_-4*dEB9#p>1(#eS&e+*A=;kVGI}((UOm)Y>*d8!*om9QSm(SjkYG_cD z+}Ldls}T8|4+#yJri@=PX*JHYtvJAO%-@n;ojd76=tbl78F_v|u$SaCkP3@-J2W)6q~1c#XlM>0YouTl&hIP#=IvXx+x6O*zILf`JY4rwc^A)9 zvdG=!IAR2D6z`zTf&|0rv~!%Hy$>2RWa4s2yfX*KmAX`N1kX44ZkCB!j)s)(u}a0x=0HsN?EfYee(G*ZpT;?m5g?Bv#lu6jhk*rd-!-BDdiU6JTN}1 z0afwmkB_oa=+b?kY;4Kr8$Ykd;pivgZi6Ga-{-9THeY94E=aQa&R9ITB&Hln1+$N`tR=;)%ABv^*i>) z2M}Ivw{=2avHUKfy$lDg4&9UudwVnG3!R^CV8*pyGymo=G|Ei}NTOuVfAiC|KbSg7 z62g+lOwFC0#oKi^5*#t6t+-+(tyEv*!V)o6vCz4c$#n|dbcH_M39Zv4<>@cfPCuZd z6Vop=;n^;i9=SeE;`!3Pv|9t|YyC*7R(aBe&)wQ3w}&qeF-xTwAzNO#^tVZtON*i+ z!sm^t*9n%vI(HcFTIgTrNz~`msrBo5V)B`u<4VX}>kO@F&6~>P;Tb<(rz8_w9$u=0 zK+(j_ZdD^)Mx}F+>}@U@xsm6yqO6J?lwRBtcQYCktt#-o9icy?*i>w|T7-1rv#14o zuh>Q>JIijV1h#?CwddoBc^pK``%U@mNpNMwde&}-@G{%HaX(G}SI;Nhw+!?i>Sdyv zL-CpFXpG-Ct7jY*-C)a07RUgT1adn^Q_f5!b@Yt1>tXbcScINHntB$wy0^9K9awdf zbY{tEF4*&B*VM~b4&JbBR93wnQ@qv%*Y2eAjbo*yiXRrP$XsfE)LrlCFn45D#vW|&Q6)|eL3ewv=6EBylzFv z_XiJuYcIw(V4iGLYc}q0gXZB!u(=|1fa!7532=v|fy|W0SY@oz)?+k+G+yn-nZl=E zzm-1qI@5rU+VlL|ClU$ZVq`_;)r|YL)=s)pxZ=nG%_NwU+?xWnGJ@pGn$ns2Go!9X zG`+yUgl(9f@l)^UHCnz%g<;FWn{qrgoLE-D(MF$yZ+5C}hC9QhBP~pvLaHnz)-`8c zQ`@PTur$UTy_j&%3JB-U8AXGgQ?nd>4ZxJKh6!3~;f_K9ao{1428g&q7(a_boZyPf z;0L`6mZ->&bca>I2-zvY7-rp7xs`K|r=k?NxkKL#b=tgZTNuqZ+BvJ4w0hWqvMf98 ztXChNFk-dvx<2_D5&DrX_vH~-i&4x_uTtq2thjmputy%3ds$`!R8n;I%=^%AR~Q=a zRaAQFlC5N$K?+=}FusDETKK5HN3PZ|Q4b0Mxalxr06#HtitTc-^I9edJg7tbhJg9g7wN)1mE zY{H_2iWVPm#>^_gb{t&=>(IjG7_#A%%!j-cvmSBnc9AYs{Cqans0J-WM3>2@I9l87 zgp?MzQa%)GQ%Gp9VYm46J!7Pzh074B=-{X-8(BUvt>S!Nb@>sQ@5<#gCw8Z4P~5z4 zW=BYiIjchi*6^M&7|cYHe?4!A)j(64iU2RF+b7yA6i5mqU4O9a|M%Jc4K>-$*$tnm z-u+B9Jm|kK`j1yns4*!>C|lG0d{Ph0)3X&MNcTWlKtSOKKW6>I3B}%I{;0Pan3bkY_q-}noteA* zP8;7UX_wAqoF~W>NJgD6Pti{-MKEn@RKyY0h(gqXy> zb+`3BRQP21BVBuo$$gjc&BS&VdlU%f+rwvk|4Cb+itZ{YavYj(vl7DBDJj~MXC3vX z!)2_~?1?A;b7hsJ!=cm~soEwX$t`Bhg5UzslGDo(Yw2Kxq&&Rnb%GGZl#!A7at5Nz zE!1mck(Hb@m1j9E%E!|ZTIIPV?Bkdj>{I+oWcnE<21U-7s5LItp_!g+tvCuFeE)rM zvclbQQ|S(gPT8@RDqZGDxB@NbO|e2zjWHX!%;P0(sshzGL4jbIm2Gh@N17aiG~ zg_(zMp06@qx~|P26`)>($a}yBJQyczIb| zwxTM(!8K7PwiBv+lG9W;sd3{;mJ4z2;1|-qha#E;YS99uvguTA{3UMuw_SO?jX5gN zX4PnumJ$_D%K*rn&%7;I=Fu7q(}IMzXb+wfwCBaRWPPNYG*2EIYaT_ZCW~f%Re_4A zl3Fk#o9L0fnqE&1a^%Ga&;3bK|QZ-SD#&hkKt|lM;(mH+U@d zb1Jb6V1HoLh3cv&#-Oi4EV*rp4S5<)Tl3M6Mm zJq|tahl&ZpEZh@6^~SG;5?{`ORgVak_WU~C1?ll!EWEl? zO*gA=NE&@*@Vtl6k`?orQ=3p78D#1=F>HUfK=q7|ymPW?sHP}C?N#W!w;A?7<-uX@mQ(CLu z6X#-7%8uz3ze%jNs&O>sHpK;-*YB#vctk%XMt_b zWYy(l$JqB}Hq- z4_UbLa=w1HCk^l%VhWYaDCd?uf>`Y)a0SO!6q;RQJ9O`}75rbWME{kwEHp=;<+cFcP{S7#=(~o2x~XkyqQ5Se*)P)eI4M>LNUrq7-Mrr%c-G0KdbW^0Bjs z+cj7)0jF9pgGdg7BQ3Wbq z>($RuiSY1Y;cT=fSY92XjXI<1$L13!DqGAPP~j2l0ob+`yAl~9t_P37KqFiwW-1ws z3|-46GHpfXNuf9*IyJ>O!`9O40_vRYup(BnV_<VD(gf=S+msr#Y@Td^NL(lM&dD6B1hqyt{BBJ{~g?;pSSdot#jXttnR+&Sw5Z@8%- zqP}UN8CI%kjA-@{mLT7Gh^!#;DL`t-i8jeogoHq=VHhQRZoOe{QZ9gzG1kK4(UUX{ zU2Cf8$=u!&U8Q$@r8$Pt{5j8ogerIH$%V(_rUeN%$P|yKgcg!J$$~Z)>NkcVO||-t z=}-dc6U>OS({TocT4zz3cSFbIiSO3rV>LM~MmXWSa; za>=!qR%Av?=|W`{XWjQRwayJu=QnsrKI%FiN@8Q&TSH=#7s5W$y*`Q|Pxxvi(DLvm znYWjt&fZptx%b$n1lM{xd%-wknUdYv0sldO-RA^VHpO-1MO0T|Je^ zGvXj)w>l|D_e>M^cOjNWvf|5%sOytBl8Y|`F0!WgIThhvtF`l_2HACrep^dt`p)5< zOcK>4Pow|g?VWW0UbF1|Q|>i4Khhy-GD7d#tcC)!&w0GD@fms+m_3#3^tS5tGNQ5c zY}&uL!6TY=qbiUKBGSJy&hT9DYgD594SwoOs9M@_hdu5a zv)u?fyRdS#!lyp+=}<@^yG^0PsEptM!8Pa_pZhSM+jnJzd$5WwllM51`AuH!u%wiF zl1K1i5O|0G=(fpkB;! zi7)fc^)hJfSI2&pmFn>pIaR%mDYt_v?p3$uUX>9?jiTHCQyKKMF&F3;q`oC#i7>vx zXKx-qeFMOHAS{_|R{YOu%h5>yuIWGgjvy+#PGS_B$LT}z?2QKJvqw7iEzvt?>X%3vrEBVR?Mnpk<;%p(<2!kTO*Axp|J%CjM1!*FRKXM9Ghf%gmLylf(peO?J= za%vfhkS~+rMRbWB+I>|e8~|QbZTiJiA(ZQ@M8kW;{WI=OLA!4%SY5u(+5hG|QZcr! z=b1hC>E&6TQ1VsWg8Y3b(ny)A1J?>U|Gv*&`XImlL<%``PP+(d$<~L$)|%$^lX%r_ zhwlhmwL83Xi=z#mBDRud^Vg<`E~(N@9`c^TZ~CnWHsKCbDBMfdh?Vh;#4~FK8hwJc@tNt8DlEyp!+k-aUMy_bU${HcSZ>)mdS~5w z#THT$XSrr)-{bk(5Z9^HaH-aw=IK7cD$)*L^s;za|J2SxR}hbexO-F;t~|Nl=s+`~ zg4sP`qO%Vvfln}?%og29Sizb%u8T=?!w(`LQQT9!VfJ$8M8USf<|RkACXPBtK$H%d z^A-t{5~IeQWOeP1t={BK#qff0kAPQ+s^Z5Tph+r_{8kj!%ui-w{F18t^(wgZ4t)(^ z&6HonN6~&(2W#2j)k@B2gV3{)?0X93XJ@9NQa6P+RY!aH!a_su_ovQOavlyyTr_H~ z$Gwx7o|&RyKS)oK+_Bn37az{fv8Qup&&>*HZ)z;!T0E_HF;hA$!#E?{yaexLM6jiV zP}U;*avv6qoV9$ISCOW$nJi3N<77KNIBYej!lhk5w%ocgit-w>cxTBHNe0C)S==jF zIiH)rGKYd93~U$7`l7@%3LVTb5x&ChJc`Rrg*orxPg`2M#o9{mPdV5nOCe!2hj+DG zeUgLp_3t)SQc+2iZ|-SxZgpfYAdfjrd8fZ=Tv&H6BMMP;Hz@qij_0ztDKuu0WQB%Z zwrM5XPG-Ztr-C^|(fq_d2Y6Bfw?-#yM>z3Cxl5b0l&x{Ia^9P)> zBWh=_^!FIF=Q+KVLqT+{Z- ze)!G}dZ7n&bc|4HWeE!004A_U6zFoY7?%OjM*WVH_>-tQ7#SffHjH}Jb>%i#N<}m8 zWghw+wUuLm>(0M+Ao$@${QLWZm%hGuLFxe|==rQdoqnB6~|P}X$8?LzB^i4{||fL0oPQP z{U3)>$ASz7M5!Yx2ndQ0niOZGSOUm|08%ALkQPel)iMI1>WGqrrl1J~2qGnhP{aff z2q4DLdq;Zj_OSAM)I{oRf zucYvc6_b*N;e*A~v}$}k_y%RYGdX$5`pV;(?>;5_?I8fDsP5jo%`)zm8;~9nKOBKfO zW>OuzEoovL;ag8%IQd*43d6K37^nndBq_;(+;Y2uM1*+iCL{~($sRrcvKKG}q?nkPQg6U^ zQ4jWc0ELrhQz$y5zfgF7W_EH)QI8UJ>huA|VC7FW@k_nuG3M14vAewQ^EmlIt!`atT zv>U|0%r^&~Z0QrmqctrvX!ZwSv7T zXK`pv<#jh9ZQ7l%`fxC|djb7W)FuT%>SQ=}1I5r0?hvq+71)oTo7fTfYBb-`q0qDO z)lxp9xpil~I^8UxV%?a?1vR4>F2%{z4N${;E#koul`xBzI?vUk&E2J_;L#Yz(E!q< zcC~AqJS)%v$Fd0Ex|IgNRMTItV>5ztl|4nI%V3D_-ACNtosI z4mHgjnRToFYIrphP_k!~!2vL;BNasxu8VuO)pMQekJmoVXaqfZ0E|C*Gc7376dTR8 zlaeXendshD#9w#wI#_R>bl+~P<-m$9_okuF&$rut`w;(S%0DJI{$(BB-kmP6dsJG( zx%z#s+Kmkpmsb__KhIWq9es%mxOXv?LJ0bM)Hv$l;%{ra_IPcQg>`YloBO&HDk$r zE~$=9%R9U@w5kl@nH(ix0TW(^`qhd-?i>NE2Vjt$#9c zYAcc?v2c%0`|jmdwHNNReR@s3V?DhR`Id2_*eQo$tfaR}v*{$#*RVMO{u~=dF#Q#Y8_ZakKx0RvgR3*O5$CYp}2pp=E| z`$syXer_+x^s)*e&w_?|*@iyS>6dN)$N-Hlu}QM13eVUJg%=@(;~R-4 zNlMd4e<*>0V~aQQvMgjR|Z(4OyDtQLZyersBP<$BbEluxa zl&_35rcL=AVill0s%O?9MZ(rM2sDLW2||Ve_XBt3{&E8qT{)(U!xT(z;UHn$V?>g# zAZYiT9^kBGXmAoMl>gkV0j#YaE}1;jRCcVjA-X1;1%YAcP2kEC!vS3{M&IT*ugf=* zC_(6V>}9JWNVB1$rFKrtjfmSK$t!_;mX3FNMjG9)eg_6g+>w1AMZ`@dl%K+Lylv?E zHW^WDhcV4sL*oZ-%x7xR^-}o3NX5;v8}?HL5sOk2gyY|P+jc*4D-9z%Ea~8#^G@hv zoe!C{JG&|QB--b4PueWnJGZ5A7U^b{aov%&cH^~7QQ`C( z6&VPG<>t3V#%xB$$FeQ<*oUWxhwQdaEiqI}A>mTmHJ59WQQ6ss$AVU`-(OIj@EDHI zs8##1ta7 z&e0ViIocv37JKGdF*kmig#*s?WRF^-o{+;xs!#bT|2#YS>nHw*o(=xG4nX^fwj=>+ z(j$uFTbK)c%hshZTKQ4*W3{@N>`hs(|Gj{n5HUWk&wW!L9^VJu6`eZ9Bo<+E6dc)RYiNxIS2nz8dQ92ZZK(j^Wa?-HKn?dwzNC;z&=h4POGL-#(L5iIWDJLEruW= zl@Lbl-t7FztNU9l;u49);Q&~@R(zxLLSL=7rrz%v zR6-ugdp41cHPGUYta;><5nJg@iU~Yi*Wj#YoA)FvTL8T#DZX~Xa0+(Hceoeu;H{0% zS(Jh48kTeO*ZB@jMNbUg{2K80^UVgUSCYWa;HkT1%z5T3>AaP>0rD=+A^re)J9jy^ zz#41TM6u3N z^|XR#EL0Y?s&~kSC0~-&p(m|3h#S%(h+zS3iI1n1d_ZEZ2`P=i4#h3zSL9U3E}hSe zDJi&p8G}y|h2YS#Zg=X2UaaG2aGxWjjSE=)8JZVSip~UF%-Q#JBj*GRgvimldtBu` zj&O#H*_aUaajSD$HxM+Ss$ydOY(FYyqAu98Fw>Oe<7_qvyJt!aXLAciG(aay+-+_Y zoc1OSQeI^FI2$mt6vjuhPo?54h4+RvoLiK>o@|8^zO$d+SKeDhkmZ&7`@82D@#=iw!z4 zR{T;q)GQ|P^3PzZS<&I1o z(}@V;x`)L3%ta$-ZI4KE;>12D{L>0YFVK~CgP$$Uw+g+>mZ^ztMYI%G84wK`j$txw>?007laq5oo z8cX6Z6#j~^jL@i5ud#Z?2Q5vCW18+DL#y99O#gnmKN;+o&0s-7-qwniIk);39v5|% zx5ZxQrg%l~W0k8A4JRreqvbVwQQE69rvAdlwO>i(LhGD+si6(nV!#unB6+}sG{rIw zA&1IB0&}=bb_x$JPpCBVZQdheMN0P9g24Gp+?8z;^r^q>V)A*eJ&&xCyr@<44@}Dp%XCl=gE}3w++8wTp0&n$a&UCRT1oBV&}zq5ud!nsLXB zuGy&=?AS=mfRVy=5mkW|&pB9BX1hQ4<0tkE04(*8*HSuhZ9Xu}NZ<&8ZD3K=1sCSx z@H>{rUdys`>^txVRFF-hIwhUUQS|cPYUztJO{f?U7|r3648u$-uyWhtjSX3P&QPd+ zY3kIWm<7xcK`OU-H91eRl`5g@(8_$P-wrOa_r z)R>r!mQ~ug0QaLf85SyXP^?5b8dEXU_81iy($Y@jzzYix^~=_a2V-cEn+30&=fZbkyYT zR99dyEOOjsVA;`!UM|#oWd=1m6)US=yiC`9&H|gmGvTwRjPv|soGs5_E%HNZvTYtU z7~P$`K(v^1m$ntS>3rAOLSv0o1feF1(KZ(K6GfiJ@De6{F3rOP)95;nUP*I|ZPF=)kJ39YT8z<>mkAjX&?qi(iM1*^LdAse>8<1K( zU(2%=Cw1$aL%z1PcU!Z>cbHF3H8~`z2$`2~aoE$f3`%7?a~v=C9%)c;Zjyykxs1?i z{RZrvdJ9UaDd#$h+7wHJ!lnCi;O1lm9wDRmcpPvUw1lQx6*nAHUYjd6mmfumAykEV zGVBzkT7<4qwC(W-z$P=xf4DdZAwdXoFwKb9K-CS!tAQ46GEO1;XK?E{7bKK3t@dC`3h*P$-f{ z5dHdM@)ROlNQWsIBp8JzSBy<~{G_|-5ix2gdp+T$!)v(NQg>MAj{|_%X4|u@-%&g6 zq_4E9=$|>hOE>R?06cQn_6*~3bHW4w^T=JsKe}sExeVQ_gD(q?>UT*a$zzkZbH+3? zUGyg1R}kZcL!GSbodI=0C+s|Y;Yhg{MV(D~g%|KHda(X@(+-x9-NljTeVci0QeHJD z=)%g1lQ=!5hp#!Q!~NCUoN~A6%9%F>Ns)st_u%ZhDUxlfp2HNEgQc3#ky0u@tZs$N zy|`xF$!NP9b}41T*-xI7))V2@8@j*6!{LZ21*)Ym5*9XfDrs=_VN)E^Bta-ah_{?H zh$SI?U_$rmT$jb*-r%4taoxj2`tjvea8Rb6*uct=X$Hw5&J5WjQJiReK*?=^a6IEg zUR%OL#nQqgM|TXNk-wHY1t%BoS1N+Hqa+7K*Ud)b!~+d8qX#n8GR(PeU-briEn0S(Yj{{6Y@H~zBe4|!7cZ!34uOs?5#AY}dChsDHeZ=Z(~UOLo<`Sqv#DnE2= z3{i)xd*5BEI30Upca_wUFBq7<)9NqQ4xRtiy$1jMwzRqX$D=EhziqQ9j5!NFJZ5vt z@(t)VYgLVo9@&6z;&}O4QQm9v?}$>f15h)BTt<)c8<00~Xk+iiBSm4KrTZ6x9JY7t zmdR(_9c$d09?hz{X_;z1fEALPbf2V>JpC+rjv(wYjr;1X<6@Z8)Q8KZQH=IdcRLHm z45ROkKLzq@8(U}?JORNOMAl3KE?g=$!jiW4l}n#wR5;P}1iTAV>adyd!RSh@+5^om zhs^Saly5{795eCvQIW!gk(3<52GE-xn5L-Wy5eXQloidQEuU>sJ!DEEdE(-tzdOx- z^32&N@G9lSR0~s{j9dz@?CTP_}xznD2`pXiWO9pLoHkKaDr#!L9e!q?<!s^(J|W>z*&FsFyzB&^c{Z+PtdP;_4Q`fyg@?p7A6~MLe^A^Y@gr%N zgv0b;Om{M|thgRA&tV?4kp45@VnOCjU=?1NpA5iiMZH=Rk8falYHS;vLmz(&tYuoC zqW+@dbr86Acu*zzr&&K`+7t1}s-4lu(*O=w z#)Y>yU>)yrtAtG@0g!eF-{MeZt{fQW)N{N}Z+&WPK{EtwU|bmo(zVf@t;;s85m^@! zMtr5ITJi{lL?o?rY?It)@bp|>ANM{4K5mFebCDpB

fCoYJd0(yk`~tVlOnAOC~Qh|dr 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 None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + # クラスタリングモジュールの取得 + self._clustering: Clustering = cast( + # モジュールの型を指定 + Clustering, + # モジュールの取得 + module_manager.get_module( + # モジュールの名前 + "DefaultSearch.Clustering", + # 上記のモジュールの名前が指定されていない場合のデフォルトのモジュールのパス + "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", + ), + ) + + # パスプランニングモジュールの取得 + self._path_planning: PathPlanning = cast( + # モジュールの型を指定 + PathPlanning, + # モジュールの取得 + module_manager.get_module( + # モジュールの名前 + "DefaultSearch.PathPlanning", + # 上記のモジュールの名前が指定されていない場合のデフォルトのモジュールのパス + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + + # モジュールの登録(これをしないと、モジュール内のシュミレーション環境の情報が更新されません) + self.register_sub_module(self._clustering) + self.register_sub_module(self._path_planning) + +モジュールの種類について +-------------------------------- + +adf-core-pythonでは、以下のモジュールが提供されています。 + +==================== =========================== +クラス 役割 +==================== =========================== +TacticsFireBrigade 消防隊用のモジュール呼び出し (競技では変更不可) +TacticsPoliceForce 土木隊用のモジュール呼び出し (競技では変更不可) +TacticsAmbulanceTeam 救急隊用のモジュール呼び出し (競技では変更不可) +BuildingDetector 消防隊の活動対象の選択 +RoadDetector 土木隊の活動対象の選択 +HumanDetector 救急隊の活動対象の選択 +Search 情報探索に向けた活動対象の選択 +PathPlanning 経路探索 +DynamicClustering シミュレーションの進行によってクラスタが変化するクラスタリング +StaticClustering シミュレーション開始時にクラスタが定まるクラスタリング +ExtAction 活動対象決定後のエージェントの動作決定 +MessageCoordinator 通信でメッセージ(情報)を送信する経路である,チャンネルへのメッセージの分配 +ChannelSubscriber 通信によってメッセージを受け取るチャンネルの選択 +==================== =========================== + +自分で上記の役割以外のモジュールを作成する際は、`AbstractModule` を継承してください。 diff --git a/docs/source/usage/module/search.rst b/docs/source/usage/module/search.rst new file mode 100644 index 0000000..e10ee49 --- /dev/null +++ b/docs/source/usage/module/search.rst @@ -0,0 +1,3 @@ +.. _search: + + diff --git a/docs/source/usage/usage_index.rst b/docs/source/usage/usage_index.rst deleted file mode 100644 index 195d806..0000000 --- a/docs/source/usage/usage_index.rst +++ /dev/null @@ -1,36 +0,0 @@ -.. _usage_index: - -使用方法 -======== - -このセクションでは、`adf-core-python` ライブラリの使用方法の概要を提供します。 - -.. toctree:: - :maxdepth: 2 - :caption: 目次: - - installation - quickstart - examples - - -はじめに --------- - -`adf-core-python` を始めるには、以下のセクションの指示に従ってください。 - -インストール ------------- - -.. include:: installation.rst - -クイックスタート ----------------- - -.. include:: quickstart.rst - -例 --- - -.. include:: examples.rst - From c355188f703e62fbed393993822cfaf11a2461c6 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 22 Nov 2024 12:12:06 +0900 Subject: [PATCH 146/249] fix: update Sphinx build command to include the '-a' option for all files --- docs/README.md | 2 +- docs/source/_static/.gitkeep | 0 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/source/_static/.gitkeep diff --git a/docs/README.md b/docs/README.md index 8095940..fa006f8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,5 +4,5 @@ ```bash sphinx-apidoc -f -o ./docs/source ./adf_core_python -sphinx-build -M html ./docs/source ./docs/build +sphinx-build -M html ./docs/source ./docs/build -a ``` diff --git a/docs/source/_static/.gitkeep b/docs/source/_static/.gitkeep new file mode 100644 index 0000000..e69de29 From 5c68d2122c7f122fe9a7e8220c0da4f0da0f97e0 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 22 Nov 2024 15:15:16 +0900 Subject: [PATCH 147/249] feat: add sphinx-copybutton package to poetry.lock and pyproject.toml --- poetry.lock | 20 +++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 93141fb..b74b187 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1234,6 +1234,24 @@ code-style = ["pre-commit"] doc = ["ablog", "folium", "ipywidgets", "matplotlib", "myst-nb", "nbclient", "numpy", "numpydoc", "pandas", "plotly", "sphinx-copybutton", "sphinx-design", "sphinx-examples", "sphinx-tabs", "sphinx-thebe", "sphinx-togglebutton", "sphinxcontrib-bibtex", "sphinxcontrib-youtube", "sphinxext-opengraph"] test = ["beautifulsoup4", "coverage", "defusedxml", "myst-nb", "pytest", "pytest-cov", "pytest-regressions", "sphinx_thebe"] +[[package]] +name = "sphinx-copybutton" +version = "0.5.2" +description = "Add a copy button to each of your code cells." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"}, + {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"}, +] + +[package.dependencies] +sphinx = ">=1.8" + +[package.extras] +code-style = ["pre-commit (==2.12.1)"] +rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] + [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" @@ -1437,4 +1455,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "48245d641990d81a38f747cc5e2846dd7722e3189a99b3f5081e800bf253b142" +content-hash = "5cebed177aa550b84d919cc51f4f2512e26a9399959395f81696b0397071a0ab" diff --git a/pyproject.toml b/pyproject.toml index 2d244cb..b313439 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ ruff = "^0.4.4" sphinx = "^8.1.3" myst-parser = "^4.0.0" sphinx-book-theme = "^1.1.3" +sphinx-copybutton = "^0.5.2" [build-system] requires = ["poetry-core"] From 0a2186d50205a80bc76e66440cdbaf7c5af94eb2 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 22 Nov 2024 15:15:29 +0900 Subject: [PATCH 148/249] feat: rst to markdown --- docs/source/conf.py | 1 + docs/source/installation/installation.md | 29 ++++++ docs/source/installation/installation.rst | 43 --------- docs/source/quickstart/quickstart.md | 62 ++++++++++++ docs/source/quickstart/quickstart.rst | 70 -------------- docs/source/usage/agent/agent.md | 49 ++++++++++ docs/source/usage/agent/agent.rst | 59 ------------ .../usage/config/{config.rst => config.md} | 16 ++-- .../module/{clustering.rst => clustering.md} | 0 docs/source/usage/module/module.md | 88 +++++++++++++++++ docs/source/usage/module/module.rst | 95 ------------------- .../usage/module/{search.rst => search.md} | 0 12 files changed, 235 insertions(+), 277 deletions(-) create mode 100644 docs/source/installation/installation.md delete mode 100644 docs/source/installation/installation.rst create mode 100644 docs/source/quickstart/quickstart.md delete mode 100644 docs/source/quickstart/quickstart.rst create mode 100644 docs/source/usage/agent/agent.md delete mode 100644 docs/source/usage/agent/agent.rst rename docs/source/usage/config/{config.rst => config.md} (80%) rename docs/source/usage/module/{clustering.rst => clustering.md} (100%) create mode 100644 docs/source/usage/module/module.md delete mode 100644 docs/source/usage/module/module.rst rename docs/source/usage/module/{search.rst => search.md} (100%) diff --git a/docs/source/conf.py b/docs/source/conf.py index b3427dc..380ea3b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -23,6 +23,7 @@ "sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx.ext.autosummary", + "sphinx_copybutton", "myst_parser", ] diff --git a/docs/source/installation/installation.md b/docs/source/installation/installation.md new file mode 100644 index 0000000..54a055f --- /dev/null +++ b/docs/source/installation/installation.md @@ -0,0 +1,29 @@ +# インストール + +このセクションでは、パッケージのインストール方法について説明します。 + + +## 前提条件 + +インストールする前に、以下の前提条件を確認してください: + +- Python 3.12 以上 +- pip + +## パッケージのインストール + +パッケージをインストールするには、次のコマンドを実行します: + +```bash +pip install adf_core_python +``` + +## インストールの確認 + +インストールを確認するには、次のコマンドを実行します: + +```bash +python -c "import adf_core_python; print(adf_core_python.__version__)" +``` + +パッケージが正しくインストールされている場合、パッケージのバージョン番号が表示されます。 diff --git a/docs/source/installation/installation.rst b/docs/source/installation/installation.rst deleted file mode 100644 index 06e4870..0000000 --- a/docs/source/installation/installation.rst +++ /dev/null @@ -1,43 +0,0 @@ -.. _installation: - -インストール -============ - -このセクションでは、パッケージのインストール方法について説明します。 - -.. contents:: 目次 - :depth: 2 - :local: - -前提条件 --------- - -インストールする前に、以下の前提条件を確認してください: - -- Python 3.12 以上 -- pip - -パッケージのインストール --------------------------- - -パッケージをインストールするには、次のコマンドを実行します: - -.. code-block:: bash - - pip install adf_core_python - -インストールの確認 ------------------- - -インストールを確認するには、次のコマンドを実行します: - -.. code-block:: bash - - python -c "import adf_core_python; print(adf_core_python.__version__)" - -パッケージが正しくインストールされている場合、パッケージのバージョン番号が表示されます。 - -トラブルシューティング ------------------------ - -インストール中に問題が発生した場合は、トラブルシューティングセクションを参照するか、サポートに連絡してください。 diff --git a/docs/source/quickstart/quickstart.md b/docs/source/quickstart/quickstart.md new file mode 100644 index 0000000..4142999 --- /dev/null +++ b/docs/source/quickstart/quickstart.md @@ -0,0 +1,62 @@ +# クイックスタート + +## 前提条件 + +インストールする前に、以下の前提条件を確認してください: + +- Python 3.12 以上 +- pip + +## パッケージのインストール + +パッケージをインストールするには、次のコマンドを実行します: + +```bash +pip install adf_core_python +``` + +## 新規エージェントの作成 + +新規エージェントを作成するには、次のコマンドを実行します: + +```bash +adf-core-python +``` + +実行すると、下記のような対話形式のプロンプトが表示されます: + +```bash +Your agent team name: my-agent +Creating a new agent team with name: my-agent +``` + +入力後、下記のようなエージェントのテンプレートがカレントディレクトリに作成されます。 + +```bash +. +├── config +│ ├── development.json +│ ├── launcher.yaml +│ └── module.yaml +├── main.py +└── src + └── my-agent + ├── __init__.py + └── module + ├── __init__.py + └── complex + ├── __init__.py + ├── sample_human_detector.py + ├── sample_road_detector.py + └── sample_search.py +``` + +## エージェントを実行する + +エージェントを実行するには、シミュレーションサーバーを起動し、次のコマンドを実行します: + +```bash +python main.py +``` + +エージェントの実行が開始され、シミュレーションサーバーとの通信が開始されます。 diff --git a/docs/source/quickstart/quickstart.rst b/docs/source/quickstart/quickstart.rst deleted file mode 100644 index 6f6aec9..0000000 --- a/docs/source/quickstart/quickstart.rst +++ /dev/null @@ -1,70 +0,0 @@ -.. _quickstart: - -クイックスタート -============= - -前提条件 --------- - -インストールする前に、以下の前提条件を確認してください: - -- Python 3.12 以上 -- pip - -パッケージのインストール --------------------------- - -パッケージをインストールするには、次のコマンドを実行します: - -.. code-block:: bash - - pip install adf_core_python - -新規エージェントの作成 ----------------- - -新規エージェントを作成するには、次のコマンドを実行します: - -.. code-block:: bash - - adf-core-python - -実行すると、下記のような対話形式のプロンプトが表示されます: - -.. code-block:: bash - - Your agent team name: my-agent - Creating a new agent team with name: my-agent - - -入力後、下記のようなエージェントのテンプレートがカレントディレクトリに作成されます。 - -.. code-block:: bash - - . - ├── config - │ ├── development.json - │ ├── launcher.yaml - │ └── module.yaml - ├── main.py - └── src - └── my-agent - ├── __init__.py - └── module - ├── __init__.py - └── complex - ├── __init__.py - ├── sample_human_detector.py - ├── sample_road_detector.py - └── sample_search.py - -エージェントを実行する ----------------- - -エージェントを実行するには、シミュレーションサーバーを起動し、次のコマンドを実行します: - -.. code-block:: bash - - python main.py - -エージェントの実行が開始され、シミュレーションサーバーとの通信が開始されます。 diff --git a/docs/source/usage/agent/agent.md b/docs/source/usage/agent/agent.md new file mode 100644 index 0000000..50e4467 --- /dev/null +++ b/docs/source/usage/agent/agent.md @@ -0,0 +1,49 @@ +# エージェントの作成 + +このセクションでは、`adf-core-python` ライブラリの使用方法の概要を提供します。 + +## 新規エージェントの作成 + +新規エージェントを作成するには、次のコマンドを実行します: + +```bash +adf-core-python +``` + +実行すると、下記のような対話形式のプロンプトが表示されます: + +```bash +Your agent team name: my-agent +Creating a new agent team with name: my-agent +``` + +入力後、下記のようなエージェントのテンプレートがカレントディレクトリに作成されます。 + +```bash +. +├── config +│ ├── development.json +│ ├── launcher.yaml +│ └── module.yaml +├── main.py +└── src + └── my-agent + ├── __init__.py + └── module + ├── __init__.py + └── complex + ├── __init__.py + ├── sample_human_detector.py + ├── sample_road_detector.py + └── sample_search.py +``` + +## エージェントを実行する + +エージェントを実行するには、シミュレーションサーバーを起動し、次のコマンドを実行します: + +```bash +python main.py +``` + +エージェントの実行が開始され、シミュレーションサーバーとの通信が開始されます。 diff --git a/docs/source/usage/agent/agent.rst b/docs/source/usage/agent/agent.rst deleted file mode 100644 index 0acf6a6..0000000 --- a/docs/source/usage/agent/agent.rst +++ /dev/null @@ -1,59 +0,0 @@ -.. _agent: - -エージェントの作成 -======== - -このセクションでは、`adf-core-python` ライブラリの使用方法の概要を提供します。 - -.. contents:: 目次 - :depth: 2 - :local: - -新規エージェントの作成 ----------------- - -新規エージェントを作成するには、次のコマンドを実行します: - -.. code-block:: bash - - adf-core-python - -実行すると、下記のような対話形式のプロンプトが表示されます: - -.. code-block:: bash - - Your agent team name: my-agent - Creating a new agent team with name: my-agent - -入力後、下記のようなエージェントのテンプレートがカレントディレクトリに作成されます。 - -.. code-block:: bash - - . - ├── config - │ ├── development.json - │ ├── launcher.yaml - │ └── module.yaml - ├── main.py - └── src - └── my-agent - ├── __init__.py - └── module - ├── __init__.py - └── complex - ├── __init__.py - ├── sample_human_detector.py - ├── sample_road_detector.py - └── sample_search.py - -エージェントを実行する ----------------- - -エージェントを実行するには、シミュレーションサーバーを起動し、次のコマンドを実行します: - -.. code-block:: bash - - python main.py - -エージェントの実行が開始され、シミュレーションサーバーとの通信が開始されます。 - diff --git a/docs/source/usage/config/config.rst b/docs/source/usage/config/config.md similarity index 80% rename from docs/source/usage/config/config.rst rename to docs/source/usage/config/config.md index 36eb65f..827ba24 100644 --- a/docs/source/usage/config/config.rst +++ b/docs/source/usage/config/config.md @@ -1,24 +1,20 @@ -コンフィグについて -================= +# コンフィグについて -コンフィグについての説明 ----------------- +## コンフィグについての説明 + +### launcher.yaml -launcher.yaml -^^^^^^^^^^^^^ launcher.yaml は、エージェントの起動時に読み込まれる設定ファイルです。 シミュレーションサーバーの指定や読み込むモジュールが記述されたファイルの指定などが記述されています。 +### module.yaml -module.yaml -^^^^^^^^^^^ module.yaml は、エージェントが読み込むモジュールの設定ファイルです。 読み込むモジュールのパスなどが記述されています。 パスを指定する際は、プロジェクトのmain.pyからの相対パスで指定してください。 +### development.json -development.json -^^^^^^^^^^^^^^^^^ development.json は、開発時に使用する設定ファイルです。 エージェントの開発時に外部から値を取得する際に使用します。 そのため、競技時には使用することができません。 diff --git a/docs/source/usage/module/clustering.rst b/docs/source/usage/module/clustering.md similarity index 100% rename from docs/source/usage/module/clustering.rst rename to docs/source/usage/module/clustering.md diff --git a/docs/source/usage/module/module.md b/docs/source/usage/module/module.md new file mode 100644 index 0000000..6614d27 --- /dev/null +++ b/docs/source/usage/module/module.md @@ -0,0 +1,88 @@ +# モジュールについて + +## モジュールの指定方法 + +モジュールを指定するには、module.yamlにモジュールの名前とパスを記述します。 + +例えば、以下のように記述します: + +```yaml +SampleSearch: + PathPlanning: src.my-agent.module.complex.sample_search.SampleSearch + Clustering: src.my-agent.module.complex.sample_search.SampleClustering +``` + +この場合、`SampleSearch` というモジュールで使用される、`PathPlanning` と `Clustering` というモジュールを指定しています。 + +## モジュールの読み込み方法 + +モジュール内で、他のモジュールを読み込む場合は、次のように記述します: + +```python +from adf_core_python.core.agent.module.module_manager import ModuleManager + +class SampleSearch(Search): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + # クラスタリングモジュールの取得 + self._clustering: Clustering = cast( + # モジュールの型を指定 + Clustering, + # モジュールの取得 + module_manager.get_module( + # モジュールの名前 + "DefaultSearch.Clustering", + # 上記のモジュールの名前が指定されていない場合のデフォルトのモジュールのパス + "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", + ), + ) + + # パスプランニングモジュールの取得 + self._path_planning: PathPlanning = cast( + # モジュールの型を指定 + PathPlanning, + # モジュールの取得 + module_manager.get_module( + # モジュールの名前 + "DefaultSearch.PathPlanning", + # 上記のモジュールの名前が指定されていない場合のデフォルトのモジュールのパス + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + + # モジュールの登録(これをしないと、モジュール内のシュミレーション環境の情報が更新されません) + self.register_sub_module(self._clustering) + self.register_sub_module(self._path_planning) +``` + +## モジュールの種類について + +adf-core-pythonでは、以下のモジュールが提供されています。 + +| クラス | 役割 | +| -------------------- | -------------------------------------------------------------------------- | +| TacticsFireBrigade | 消防隊用のモジュール呼び出し (競技では変更不可) | +| TacticsPoliceForce | 土木隊用のモジュール呼び出し (競技では変更不可) | +| TacticsAmbulanceTeam | 救急隊用のモジュール呼び出し (競技では変更不可) | +| BuildingDetector | 消防隊の活動対象の選択 | +| RoadDetector | 土木隊の活動対象の選択 | +| HumanDetector | 救急隊の活動対象の選択 | +| Search | 情報探索に向けた活動対象の選択 | +| PathPlanning | 経路探索 | +| DynamicClustering | シミュレーションの進行によってクラスタが変化するクラスタリング | +| StaticClustering | シミュレーション開始時にクラスタが定まるクラスタリング | +| ExtAction | 活動対象決定後のエージェントの動作決定 | +| MessageCoordinator | 通信でメッセージ(情報)を送信する経路である,チャンネルへのメッセージの分配 | +| ChannelSubscriber | 通信によってメッセージを受け取るチャンネルの選択 | + +自分で上記の役割以外のモジュールを作成する際は、`AbstractModule` を継承してください。 diff --git a/docs/source/usage/module/module.rst b/docs/source/usage/module/module.rst deleted file mode 100644 index 3d14783..0000000 --- a/docs/source/usage/module/module.rst +++ /dev/null @@ -1,95 +0,0 @@ -.. _module: - -モジュールについて -==================== - -モジュールの指定方法 --------------------- -モジュールを指定するには、module.yamlにモジュールの名前とパスを記述します。 - -例えば、以下のように記述します: - -.. code-block:: yaml - - SampleSearch: - PathPlanning: src.my-agent.module.complex.sample_search.SampleSearch - Clustering: src.my-agent.module.complex.sample_search.SampleClustering - -この場合、`SampleSearch` というモジュールで使用される、`PathPlanning` と `Clustering` というモジュールを指定しています。 - - -モジュールの読み込み方法 ----------------------------- -モジュール内で、他のモジュールを読み込む場合は、次のように記述します: - -.. code-block:: python - - from adf_core_python.core.agent.module.module_manager import ModuleManager - - class SampleSearch(Search): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - # クラスタリングモジュールの取得 - self._clustering: Clustering = cast( - # モジュールの型を指定 - Clustering, - # モジュールの取得 - module_manager.get_module( - # モジュールの名前 - "DefaultSearch.Clustering", - # 上記のモジュールの名前が指定されていない場合のデフォルトのモジュールのパス - "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", - ), - ) - - # パスプランニングモジュールの取得 - self._path_planning: PathPlanning = cast( - # モジュールの型を指定 - PathPlanning, - # モジュールの取得 - module_manager.get_module( - # モジュールの名前 - "DefaultSearch.PathPlanning", - # 上記のモジュールの名前が指定されていない場合のデフォルトのモジュールのパス - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - - # モジュールの登録(これをしないと、モジュール内のシュミレーション環境の情報が更新されません) - self.register_sub_module(self._clustering) - self.register_sub_module(self._path_planning) - -モジュールの種類について --------------------------------- - -adf-core-pythonでは、以下のモジュールが提供されています。 - -==================== =========================== -クラス 役割 -==================== =========================== -TacticsFireBrigade 消防隊用のモジュール呼び出し (競技では変更不可) -TacticsPoliceForce 土木隊用のモジュール呼び出し (競技では変更不可) -TacticsAmbulanceTeam 救急隊用のモジュール呼び出し (競技では変更不可) -BuildingDetector 消防隊の活動対象の選択 -RoadDetector 土木隊の活動対象の選択 -HumanDetector 救急隊の活動対象の選択 -Search 情報探索に向けた活動対象の選択 -PathPlanning 経路探索 -DynamicClustering シミュレーションの進行によってクラスタが変化するクラスタリング -StaticClustering シミュレーション開始時にクラスタが定まるクラスタリング -ExtAction 活動対象決定後のエージェントの動作決定 -MessageCoordinator 通信でメッセージ(情報)を送信する経路である,チャンネルへのメッセージの分配 -ChannelSubscriber 通信によってメッセージを受け取るチャンネルの選択 -==================== =========================== - -自分で上記の役割以外のモジュールを作成する際は、`AbstractModule` を継承してください。 diff --git a/docs/source/usage/module/search.rst b/docs/source/usage/module/search.md similarity index 100% rename from docs/source/usage/module/search.rst rename to docs/source/usage/module/search.md From d7d24294cc0227f25d18299fb675ecb99098c038 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 22 Nov 2024 15:27:32 +0900 Subject: [PATCH 149/249] fix: remove obsolete example and search documentation files --- docs/source/examples/example_index.rst | 15 --------------- docs/source/hands-on/clustering.rst | 4 ++-- docs/source/usage/module/clustering.md | 0 docs/source/usage/module/search.md | 3 --- 4 files changed, 2 insertions(+), 20 deletions(-) delete mode 100644 docs/source/examples/example_index.rst delete mode 100644 docs/source/usage/module/clustering.md delete mode 100644 docs/source/usage/module/search.md diff --git a/docs/source/examples/example_index.rst b/docs/source/examples/example_index.rst deleted file mode 100644 index 06f21bb..0000000 --- a/docs/source/examples/example_index.rst +++ /dev/null @@ -1,15 +0,0 @@ -.. _example_index: - -例 -============= - -ADF Core Python ドキュメントの例のインデックスへようこそ。このセクションでは、ライブラリの効果的な使用方法を理解するためのさまざまな例を提供します。 - -.. toctree:: - :maxdepth: 2 - :caption: 例 - - example1 - example2 - example3 - diff --git a/docs/source/hands-on/clustering.rst b/docs/source/hands-on/clustering.rst index e2f6f34..197fe97 100644 --- a/docs/source/hands-on/clustering.rst +++ b/docs/source/hands-on/clustering.rst @@ -10,9 +10,9 @@ クラスタリングモジュールの目的 -------------------------------- -複数のエージェントを動かす場合は、それらのエージェントにどのように協調させるかが重要になります。 RRSでは多くのチームが、エージェントに各々の担当地域を持たせ役割分担をおこなう協調を取り入れています(他の手段による協調も取り入れています)。 担当地域を割り振るためには、地図上のオブジェクトをいくつかのグループに分ける必要があります。 このようなグループ分けをしてそれらを管理する場合には、クラスタリングモジュールと呼ばれるモジュールを用います。 +複数のエージェントを動かす場合は、それらのエージェントにどのように協調させるかが重要になります。RRSでは多くのチームが、エージェントに各々の担当地域を持たせ役割分担をおこなう協調を取り入れています(他の手段による協調も取り入れています)。担当地域を割り振るためには、地図上のオブジェクトをいくつかのグループに分ける必要があります。このようなグループ分けをしてそれらを管理する場合には、クラスタリングモジュールと呼ばれるモジュールを用います。 -本資料では、多くの世界大会参加チームが使用しているアルゴリズムを用いた クラスタリングモジュールの実装をおこないます。 +本資料では、多くの世界大会参加チームが使用しているアルゴリズムを用いたクラスタリングモジュールの実装をおこないます。 開発するクラスタリングモジュールの概要 ---------------------------------------- diff --git a/docs/source/usage/module/clustering.md b/docs/source/usage/module/clustering.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/source/usage/module/search.md b/docs/source/usage/module/search.md deleted file mode 100644 index e10ee49..0000000 --- a/docs/source/usage/module/search.md +++ /dev/null @@ -1,3 +0,0 @@ -.. _search: - - From 9d717fc8f140aaabe8dd2ec6ddb8ba0caa2690ad Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 25 Nov 2024 13:24:39 +0900 Subject: [PATCH 150/249] feat: add Japanese documentation for clustering module and remove obsolete rst file --- docs/source/hands-on/clustering.md | 303 ++++++++++++++++++++++++++++ docs/source/hands-on/clustering.rst | 43 ---- 2 files changed, 303 insertions(+), 43 deletions(-) create mode 100644 docs/source/hands-on/clustering.md delete mode 100644 docs/source/hands-on/clustering.rst diff --git a/docs/source/hands-on/clustering.md b/docs/source/hands-on/clustering.md new file mode 100644 index 0000000..6e34228 --- /dev/null +++ b/docs/source/hands-on/clustering.md @@ -0,0 +1,303 @@ +# クラスタリングモジュール + + +## クラスタリングモジュールの目的 + +複数のエージェントを動かす場合は、それらのエージェントにどのように協調させるかが重要になります。RRSでは多くのチームが、エージェントに各々の担当地域を持たせ役割分担をおこなう協調を取り入れています(他の手段による協調も取り入れています)。担当地域を割り振るためには、地図上のオブジェクトをいくつかのグループに分ける必要があります。このようなグループ分けをしてそれらを管理する場合には、クラスタリングモジュールと呼ばれるモジュールを用います。 + +本資料では、多くの世界大会参加チームが使用しているアルゴリズムを用いたクラスタリングモジュールの実装をおこないます。 + +## 開発するクラスタリングモジュールの概要 + +本資料で開発するモジュールは下の画像のように、 + +1. k-means++アルゴリズムによって地図上のオブジェクトをエージェント数分の区画に分けます。 +1. Hungarianアルゴリズムによってそれらの区画とエージェントを (間の距離の総和が最も小さくなるように)1対1で結びつけます。 + + +![クラスタリングの画像](./../images/clustering_image.jpg) + +## クラスタリングモジュールの実装 + +:::{note} +以降の作業では、カレントディレクトリがプロジェクトのルートディレクトリであることを前提としています。 +::: + +まず、クラスタリングモジュールを記述するためのファイルを作成します。 + +```bash +mkdir -p src//module/algorithm +touch src//module/algorithm/k_means_pp_clustering.py +``` + +次に、クラスタリングモジュールの実装を行います。 以下のコードを `k_means_pp_clustering.py` に記述してください。 + +```python +import numpy as np +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo, ScenarioInfoKeys +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.module.algorithm.clustering import Clustering +from adf_core_python.core.logger.logger import get_logger +from rcrs_core.connection.URN import Entity as EntityURN +from rcrs_core.entities.ambulanceCenter import AmbulanceCentre +from rcrs_core.entities.building import Building +from rcrs_core.entities.entity import Entity +from rcrs_core.entities.fireStation import FireStation +from rcrs_core.entities.gasStation import GasStation +from rcrs_core.entities.hydrant import Hydrant +from rcrs_core.entities.policeOffice import PoliceOffice +from rcrs_core.entities.refuge import Refuge +from rcrs_core.entities.road import Road +from rcrs_core.worldmodel.entityID import EntityID +from scipy.optimize import linear_sum_assignment +from sklearn.cluster import KMeans + +# クラスタリングのシード値 +SEED = 42 + + +class KMeansPPClustering(Clustering): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._logger = get_logger(f"{self.__class__.__name__}") + + # クラスター数の設定 + self._cluster_number: int = 1 + match agent_info.get_myself().get_urn(): + case EntityURN.AMBULANCE_TEAM: + self._cluster_number = scenario_info.get_value( + ScenarioInfoKeys.SCENARIO_AGENTS_AT, + 1, + ) + case EntityURN.POLICE_FORCE: + self._cluster_number = scenario_info.get_value( + ScenarioInfoKeys.SCENARIO_AGENTS_PF, + 1, + ) + case EntityURN.FIRE_BRIGADE: + self._cluster_number = scenario_info.get_value( + ScenarioInfoKeys.SCENARIO_AGENTS_FB, + 1, + ) + + # 自分と同じクラスのエージェントのリストを取得 + self._agents: list[Entity] = world_info.get_entities_of_types( + [ + agent_info.get_myself().__class__, + ] + ) + + # クラスタリング結果を保持する変数 + self._cluster_entities: list[list[Entity]] = [] + + # クラスタリング対象のエンティティのリストを取得 + self._entities: list[Entity] = world_info.get_entities_of_types( + [ + AmbulanceCentre, + FireStation, + GasStation, + Hydrant, + PoliceOffice, + Refuge, + Road, + Building, + ] + ) + + def calculate(self) -> Clustering: + return self + + def get_cluster_number(self) -> int: + """ + クラスター数を取得する + + Returns + ------- + int + クラスター数 + """ + return self._cluster_number + + def get_cluster_index(self, entity_id: EntityID) -> int: + """ + エージェントに割り当てられたクラスターのインデックスを取得する + + Parameters + ---------- + entity_id : EntityID + エージェントのID + + Returns + ------- + int + クラスターのインデックス + """ + return self._agent_cluster_indices.get(entity_id, 0) + + def get_cluster_entities(self, cluster_index: int) -> list[Entity]: + """ + クラスターのエンティティのリストを取得する + + Parameters + ---------- + cluster_index : int + クラスターのインデックス + + Returns + ------- + list[Entity] + クラスターのエンティティのリスト + """ + if cluster_index >= len(self._cluster_entities): + return [] + return self._cluster_entities[cluster_index] + + def get_cluster_entity_ids(self, cluster_index: int) -> list[EntityID]: + """ + クラスターのエンティティのIDのリストを取得する + + Parameters + ---------- + cluster_index : int + クラスターのインデックス + + Returns + ------- + list[EntityID] + クラスターのエンティティのIDのリスト + """ + if cluster_index >= len(self._cluster_entities): + return [] + return [entity.get_id() for entity in self._cluster_entities[cluster_index]] + + def prepare(self) -> Clustering: + """ + エージェントの起動時に一回のみ実行される処理 + """ + super().prepare() + if self.get_count_prepare() > 1: + return self + + # クラスタリングを実行 + kmeans_pp = self._perform_kmeans_pp(self._entities, self._cluster_number) + + # クラスタリング結果を保持 + self._cluster_entities = [[] for _ in range(self._cluster_number)] + for entity, cluster_index in zip(self._entities, kmeans_pp.labels_): + self._cluster_entities[cluster_index].append(entity) + + # エージェントとクラスターのエンティティの距離を計算し、最も全体の合計の距離が短くなるようにエージェントとクラスターを対応付ける + agent_cluster_indices = self._agent_cluster_assignment( + self._agents, kmeans_pp.cluster_centers_ + ) + + # エージェントとクラスターの対応付け結果を保持 + self._agent_cluster_indices = { + entity.get_id(): cluster_index + for entity, cluster_index in zip(self._agents, agent_cluster_indices) + } + + # デバッグ用のログ出力 + self._logger.info( + f"Clustered entities: {[[entity.get_id().get_value() for entity in cluster] for cluster in self._cluster_entities]}" + ) + + self._logger.info( + f"Agent cluster indices: {[([self._world_info.get_entity(entity_id).get_x(), self._world_info.get_entity(entity_id).get_y()], int(cluster_index)) for entity_id, cluster_index in self._agent_cluster_indices.items()]}" + ) + + return self + + def _perform_kmeans_pp(self, entities: list[Entity], n_clusters: int = 1) -> KMeans: + """ + K-means++法によるクラスタリングを実行する + + Parameters + ---------- + entities : list[Entity] + クラスタリング対象のエンティティのリスト + + n_clusters : int, optional + クラスター数, by default 1 + + Returns + ------- + KMeans + クラスタリング結果 + """ + entity_positions: np.ndarray = np.array( + [ + [entity.get_x(), entity.get_y()] + for entity in entities + if entity.get_x() is not None and entity.get_y() is not None + ] + ) + + entity_positions = entity_positions.reshape(-1, 2) + kmeans_pp = KMeans( + n_clusters=n_clusters, + init="k-means++", + random_state=SEED, + ) + kmeans_pp.fit(entity_positions) + return kmeans_pp + + def _agent_cluster_assignment( + self, agents: list[Entity], cluster_positions: np.ndarray + ) -> np.ndarray: + """ + エージェントとクラスターの対応付けを行う + + Parameters + ---------- + agents : list[Entity] + エージェントのリスト + + cluster_positions : np.ndarray + クラスターの位置のリスト + + Returns + ------- + np.ndarray + エージェントとクラスターの対応付け結果 + """ + agent_positions = np.array( + [ + [agent.get_x(), agent.get_y()] + for agent in agents + if agent.get_x() is not None and agent.get_y() is not None + ] + ) + + agent_positions = agent_positions.reshape(-1, 2) + cost_matrix = np.linalg.norm( + agent_positions[:, np.newaxis] - cluster_positions, axis=2 + ) + _, col_ind = linear_sum_assignment(cost_matrix) + return col_ind +``` + +次に、作成したモジュールを登録します。`config/module.yaml` を以下のように編集してください。 + +```yaml +SampleSearch: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning + Clustering: src.test-agent.module.algorithm.k_means_pp_clustering.KMeansPPClustering + +SampleHumanDetector: + Clustering: src.test-agent.module.algorithm.k_means_pp_clustering.KMeansPPClustering +``` + + diff --git a/docs/source/hands-on/clustering.rst b/docs/source/hands-on/clustering.rst deleted file mode 100644 index 197fe97..0000000 --- a/docs/source/hands-on/clustering.rst +++ /dev/null @@ -1,43 +0,0 @@ -.. _clustering: - -クラスタリングモジュール -======================= - -.. contents:: 目次 - :depth: 2 - :local: - -クラスタリングモジュールの目的 --------------------------------- - -複数のエージェントを動かす場合は、それらのエージェントにどのように協調させるかが重要になります。RRSでは多くのチームが、エージェントに各々の担当地域を持たせ役割分担をおこなう協調を取り入れています(他の手段による協調も取り入れています)。担当地域を割り振るためには、地図上のオブジェクトをいくつかのグループに分ける必要があります。このようなグループ分けをしてそれらを管理する場合には、クラスタリングモジュールと呼ばれるモジュールを用います。 - -本資料では、多くの世界大会参加チームが使用しているアルゴリズムを用いたクラスタリングモジュールの実装をおこないます。 - -開発するクラスタリングモジュールの概要 ----------------------------------------- - -本資料で開発するモジュールは下の画像のように、 - -#. k-means++アルゴリズムによって地図上のオブジェクトをエージェント数分の区画に分けます。 -#. Hungarianアルゴリズムによってそれらの区画とエージェントを (間の距離の総和が最も小さくなるように)1対1で結びつけます。 - -.. image:: ./../images/clustering_image.jpg - :width: 600px - :align: center - -クラスタリングモジュールの実装 --------------------------------- - -.. note:: - 以降の作業では、カレントディレクトリがプロジェクトのルートディレクトリであることを前提としています。 - -まず、クラスタリングモジュールを記述するためのファイルを作成します。 - -.. code-block:: bash - - mkdir -p src//module/algorithm - touch src//module/algorithm/k_means_pp_clustering.py - -次に、クラスタリングモジュールの実装を行います。 以下のコードを `k_means_pp_clustering.py` に記述してください。 - From fb72dac95326926bc7fdc3b123ff145a8a992ed6 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 25 Nov 2024 13:56:55 +0900 Subject: [PATCH 151/249] docs: add warning about package availability in index.rst --- docs/source/index.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/source/index.rst b/docs/source/index.rst index 3b6d6f3..14f3e68 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -10,6 +10,11 @@ adf-core-pythonのドキュメント 現在このパッケージは開発中です。破壊的な変更が行われる可能性があります。 +.. warning:: + + パッケージとしてまだ公開していないので、pip でインストールすることはできません。 + + .. contents:: 目次 :depth: 2 :local: From 83b77d8c222d7aca52d7b7edda892017910a803a Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 27 Nov 2024 01:04:24 +0900 Subject: [PATCH 152/249] feat: update Japanese documentation for clustering module and add new tutorial sections --- docs/source/hands-on/clustering.md | 4 +- docs/source/index.rst | 9 +-- docs/source/introduction/introduction.rst | 26 -------- docs/source/tutorial/agent/agent.md | 63 +++++++++++++++++++ .../{usage => tutorial}/config/config.md | 0 .../tutorial/environment/environment.md | 37 +++++++++++ .../tutorial/environment/windows/install.md | 20 ++++++ .../install/install.md} | 0 .../{usage => tutorial}/module/module.md | 0 docs/source/usage/agent/agent.md | 49 --------------- 10 files changed, 127 insertions(+), 81 deletions(-) delete mode 100644 docs/source/introduction/introduction.rst create mode 100644 docs/source/tutorial/agent/agent.md rename docs/source/{usage => tutorial}/config/config.md (100%) create mode 100644 docs/source/tutorial/environment/environment.md create mode 100644 docs/source/tutorial/environment/windows/install.md rename docs/source/{installation/installation.md => tutorial/install/install.md} (100%) rename docs/source/{usage => tutorial}/module/module.md (100%) delete mode 100644 docs/source/usage/agent/agent.md diff --git a/docs/source/hands-on/clustering.md b/docs/source/hands-on/clustering.md index 6e34228..af1affb 100644 --- a/docs/source/hands-on/clustering.md +++ b/docs/source/hands-on/clustering.md @@ -19,9 +19,9 @@ ## クラスタリングモジュールの実装 -:::{note} +```{note} 以降の作業では、カレントディレクトリがプロジェクトのルートディレクトリであることを前提としています。 -::: +``` まず、クラスタリングモジュールを記述するためのファイルを作成します。 diff --git a/docs/source/index.rst b/docs/source/index.rst index 14f3e68..8195724 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -35,10 +35,11 @@ ADF Core Python を始めるには、インストール手順に従い、この :maxdepth: 1 :caption: チュートリアル: - installation/installation - usage/agent/agent - usage/config/config - usage/module/module + tutorial/environment/environment + tutorial/install/install + tutorial/agent/agent + tutorial/config/config + tutorial/module/module .. toctree:: :maxdepth: 1 diff --git a/docs/source/introduction/introduction.rst b/docs/source/introduction/introduction.rst deleted file mode 100644 index 455fd61..0000000 --- a/docs/source/introduction/introduction.rst +++ /dev/null @@ -1,26 +0,0 @@ -.. _introduction: - -イントロダクション -================== - -ADF Core Python ドキュメントへようこそ - -.. contents:: 目次 - :depth: 2 - :local: - --------------------------------------------- - -特徴 ----- - -- **モジュール単位での開発**: モジュール単位でエージェント開発を行い、モジュールの入れ替えが容易です。 -- **モジュールの再利用**: 他のエージェントで使用されているモジュールを再利用することができます。 -- **エージェントの開発に集中**: シミュレーションサーバーとの通信やログ出力などの共通処理をライブラリが提供します。 - -はじめに --------- - -ADF Core Python を始めるには、インストール手順に従い、このドキュメントに記載されている例を参照してください。 - - diff --git a/docs/source/tutorial/agent/agent.md b/docs/source/tutorial/agent/agent.md new file mode 100644 index 0000000..1beb851 --- /dev/null +++ b/docs/source/tutorial/agent/agent.md @@ -0,0 +1,63 @@ +# エージェントの作成 + +このセクションでは、`adf-core-python` ライブラリの使用方法の概要を提供します。 + +## 新規エージェントの作成 + +新規エージェントを作成するには、次のコマンドを実行します: + +```bash +adf-core-python +``` + +実行すると、下記のような対話形式のプロンプトが表示されます: + +```bash +Your agent team name: my-agent +Creating a new agent team with name: my-agent +``` + +入力後、下記のようなエージェントのテンプレートがカレントディレクトリに作成されます。 + +```bash +. +└── my-agent + ├── config + │ ├── development.json + │ ├── launcher.yaml + │ └── module.yaml + ├── main.py + └── src + └── my-agent + ├── __init__.py + └── module + ├── __init__.py + └── complex + ├── __init__.py + ├── sample_human_detector.py + ├── sample_road_detector.py + └── sample_search.py +``` + +## シミュレーションを実行する + +シミュレーションサーバーを以下のコマンドで起動します: + +```bash +cd WORKING_DIR/rcrs-server/scripts +./start-comprun.sh -m ../maps/test/map -c ../maps/test/config +``` + +その後、エージェントを起動します: + +```bash +cd WORKING_DIR/my-agent +python main.py +``` + +エージェントが正常に起動すると、シミュレーションサーバーに接続され、エージェントがシミュレーションに参加し、エージェントが動き出します。 +途中で止めたい場合は、それぞれのコマンドラインで `Ctrl + C` を押してください。 + +```{warning} +シミュレーションサーバーを停止させたあとは、プロセスが残ってしまう場合があるので`./kill.sh` を実行してください。 +``` diff --git a/docs/source/usage/config/config.md b/docs/source/tutorial/config/config.md similarity index 100% rename from docs/source/usage/config/config.md rename to docs/source/tutorial/config/config.md diff --git a/docs/source/tutorial/environment/environment.md b/docs/source/tutorial/environment/environment.md new file mode 100644 index 0000000..db0cd93 --- /dev/null +++ b/docs/source/tutorial/environment/environment.md @@ -0,0 +1,37 @@ +# 環境構築 + +## 必要なもの + +- Git +- Python 3.12 以上 +- OpenJDK 17 + +[Windowsでの必要なもののインストール方法](./windows/install.md) + +## シミュレーションサーバーのインストール + +```{note} +WORKING_DIR は任意のディレクトリを指定してください。 +``` + +```bash +cd WORKING_DIR +git clone https://github.com/roborescue/rcrs-server.git +cd rcrs-server +./gradlew completeBuild +``` +ビルドした際に以下のようなメッセージが表示されたら成功です。 + +```bash +BUILD SUCCESSFUL in ... +``` + +## シュミレーションサーバーの動作確認 + +```bash +cd WORKING_DIR/rcrs-server/scripts +./start-comprun.sh -m ../maps/test/map -c ../maps/test/config +``` + +何個かのウィンドウが表示されたら成功です。 +コマンドラインで `Ctrl + C` を押すとシミュレーションサーバーが終了します。 diff --git a/docs/source/tutorial/environment/windows/install.md b/docs/source/tutorial/environment/windows/install.md new file mode 100644 index 0000000..ce36a29 --- /dev/null +++ b/docs/source/tutorial/environment/windows/install.md @@ -0,0 +1,20 @@ +# Windowsでの環境構築 + +## 1. Gitのインストール + +1. [Git for Windows](https://gitforwindows.org/)の公式サイトにアクセスします。 +2. ダウンロードページから最新のバージョンをダウンロードします。 +3. ダウンロードしたファイルを開き、インストールを開始します。 + +## 2. Pythonのインストール + +1. [Python](https://www.python.org/downloads/)の公式サイトにアクセスします。 +2. ダウンロードページから最新のバージョンをダウンロードします。 +3. ダウンロードしたファイルを開き、インストールを開始します。 +4. インストール時に「Add python.exe to PATH」にチェックを入れてください。 + +## 3. OpenJDKのインストール + +1. [OpenJDK](https://jdk.java.net/archive/)の公式サイトにアクセスします。 +2. ダウンロードページから17.0.2のバージョンをダウンロードします。 +3. ダウンロードしたファイルを開き、インストールを開始します。 diff --git a/docs/source/installation/installation.md b/docs/source/tutorial/install/install.md similarity index 100% rename from docs/source/installation/installation.md rename to docs/source/tutorial/install/install.md diff --git a/docs/source/usage/module/module.md b/docs/source/tutorial/module/module.md similarity index 100% rename from docs/source/usage/module/module.md rename to docs/source/tutorial/module/module.md diff --git a/docs/source/usage/agent/agent.md b/docs/source/usage/agent/agent.md deleted file mode 100644 index 50e4467..0000000 --- a/docs/source/usage/agent/agent.md +++ /dev/null @@ -1,49 +0,0 @@ -# エージェントの作成 - -このセクションでは、`adf-core-python` ライブラリの使用方法の概要を提供します。 - -## 新規エージェントの作成 - -新規エージェントを作成するには、次のコマンドを実行します: - -```bash -adf-core-python -``` - -実行すると、下記のような対話形式のプロンプトが表示されます: - -```bash -Your agent team name: my-agent -Creating a new agent team with name: my-agent -``` - -入力後、下記のようなエージェントのテンプレートがカレントディレクトリに作成されます。 - -```bash -. -├── config -│ ├── development.json -│ ├── launcher.yaml -│ └── module.yaml -├── main.py -└── src - └── my-agent - ├── __init__.py - └── module - ├── __init__.py - └── complex - ├── __init__.py - ├── sample_human_detector.py - ├── sample_road_detector.py - └── sample_search.py -``` - -## エージェントを実行する - -エージェントを実行するには、シミュレーションサーバーを起動し、次のコマンドを実行します: - -```bash -python main.py -``` - -エージェントの実行が開始され、シミュレーションサーバーとの通信が開始されます。 From d900ab5594f72ad4689633506bd5207bc4a48ad8 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 27 Nov 2024 13:10:24 +0900 Subject: [PATCH 153/249] feat: update dependencies and add sphinxcontrib-mermaid package --- poetry.lock | 68 ++++++++++++++++++++++++++++++++------------------ pyproject.toml | 1 + 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/poetry.lock b/poetry.lock index b74b187..357088b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -771,31 +771,33 @@ files = [ [[package]] name = "psutil" -version = "5.9.8" +version = "6.1.0" description = "Cross-platform lib for process and system monitoring in Python." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, - {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, - {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, - {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, - {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, - {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, - {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, - {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, - {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, - {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, - {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, - {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, - {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, - {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, - {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, - {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, + {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, + {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, + {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:000d1d1ebd634b4efb383f4034437384e44a6d455260aaee2eca1e9c1b55f047"}, + {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5cd2bcdc75b452ba2e10f0e8ecc0b57b827dd5d7aaffbc6821b2a9a242823a76"}, + {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:045f00a43c737f960d273a83973b2511430d61f283a44c96bf13a6e829ba8fdc"}, + {file = "psutil-6.1.0-cp27-none-win32.whl", hash = "sha256:9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e"}, + {file = "psutil-6.1.0-cp27-none-win_amd64.whl", hash = "sha256:a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85"}, + {file = "psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"}, + {file = "psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"}, + {file = "psutil-6.1.0-cp36-cp36m-win32.whl", hash = "sha256:6d3fbbc8d23fcdcb500d2c9f94e07b1342df8ed71b948a2649b5cb060a7c94ca"}, + {file = "psutil-6.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1209036fbd0421afde505a4879dee3b2fd7b1e14fee81c0069807adcbbcca747"}, + {file = "psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"}, + {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, + {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, ] [package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] [[package]] name = "pydata-sphinx-theme" @@ -1314,6 +1316,24 @@ files = [ [package.extras] test = ["flake8", "mypy", "pytest"] +[[package]] +name = "sphinxcontrib-mermaid" +version = "1.0.0" +description = "Mermaid diagrams in yours Sphinx powered docs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib_mermaid-1.0.0-py3-none-any.whl", hash = "sha256:60b72710ea02087f212028feb09711225fbc2e343a10d34822fe787510e1caa3"}, + {file = "sphinxcontrib_mermaid-1.0.0.tar.gz", hash = "sha256:2e8ab67d3e1e2816663f9347d026a8dee4a858acdd4ad32dd1c808893db88146"}, +] + +[package.dependencies] +pyyaml = "*" +sphinx = "*" + +[package.extras] +test = ["defusedxml", "myst-parser", "pytest", "ruff", "sphinx"] + [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" @@ -1365,19 +1385,19 @@ typing = ["mypy (>=1.4)", "rich", "twisted"] [[package]] name = "taskipy" -version = "1.14.0" +version = "1.14.1" description = "tasks runner for python projects" optional = false python-versions = "<4.0,>=3.6" files = [ - {file = "taskipy-1.14.0-py3-none-any.whl", hash = "sha256:29040d9a8038170602feb71792bdef5203720ed30f595304aee843625892452b"}, - {file = "taskipy-1.14.0.tar.gz", hash = "sha256:5d9631c29980481d59858f0a100ed3200cf7468ca8c0540ef19388586485532d"}, + {file = "taskipy-1.14.1-py3-none-any.whl", hash = "sha256:6e361520f29a0fd2159848e953599f9c75b1d0b047461e4965069caeb94908f1"}, + {file = "taskipy-1.14.1.tar.gz", hash = "sha256:410fbcf89692dfd4b9f39c2b49e1750b0a7b81affd0e2d7ea8c35f9d6a4774ed"}, ] [package.dependencies] colorama = ">=0.4.4,<0.5.0" mslex = {version = ">=1.1.0,<2.0.0", markers = "sys_platform == \"win32\""} -psutil = ">=5.7.2,<6.0.0" +psutil = ">=5.7.2,<7" tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} [[package]] @@ -1455,4 +1475,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "5cebed177aa550b84d919cc51f4f2512e26a9399959395f81696b0397071a0ab" +content-hash = "29d6b40d3dcb183f4218df24f042f514858d0b01517ecc2956beca72bc4e598e" diff --git a/pyproject.toml b/pyproject.toml index b313439..0a60a76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ sphinx = "^8.1.3" myst-parser = "^4.0.0" sphinx-book-theme = "^1.1.3" sphinx-copybutton = "^0.5.2" +sphinxcontrib-mermaid = "^1.0.0" [build-system] requires = ["poetry-core"] From ae6adaad483d1baec7240f48a325d49aa1678de7 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 27 Nov 2024 13:10:34 +0900 Subject: [PATCH 154/249] feat: add agent control tutorial and integrate sphinxcontrib-mermaid extension --- docs/source/conf.py | 1 + docs/source/images/agent_control.jpg | Bin 0 -> 39621 bytes docs/source/images/agent_flow.png | Bin 0 -> 642060 bytes docs/source/images/entity.png | Bin 0 -> 40652 bytes docs/source/images/human_detector_flow.png | Bin 0 -> 77097 bytes docs/source/index.rst | 1 + docs/source/tutorial/agent/agent_control.md | 285 ++++++++++++++++++++ 7 files changed, 287 insertions(+) create mode 100644 docs/source/images/agent_control.jpg create mode 100644 docs/source/images/agent_flow.png create mode 100644 docs/source/images/entity.png create mode 100644 docs/source/images/human_detector_flow.png create mode 100644 docs/source/tutorial/agent/agent_control.md diff --git a/docs/source/conf.py b/docs/source/conf.py index 380ea3b..d0b0168 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -25,6 +25,7 @@ "sphinx.ext.autosummary", "sphinx_copybutton", "myst_parser", + "sphinxcontrib.mermaid", ] templates_path = ["_templates"] diff --git a/docs/source/images/agent_control.jpg b/docs/source/images/agent_control.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a641c8d0650496d3f0e1309a23d608f3d05c8df9 GIT binary patch literal 39621 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


g@)397qqD(7QV}#j34!mk) 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%0AYHU)Zuh%2lB+BGZ*YUA2ErlF19(VcpY7 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 WbifOEBZ55sZw280gbDWl_26&t=Wo*h literal 0 HcmV?d00001 diff --git a/docs/source/images/agent_flow.png b/docs/source/images/agent_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..fb5720f5f0776976c38270865296eb2fcfdcf261 GIT binary patch literal 642060 zcmeEtX;e~c*mjdyQF%I*X6Ddn` znS!$@h*Xxd;!q+WkU5|-l>>;Ve4Mque_!v<@88Q>EZCbLd+qzV_w`)E&6A7nE?YM3 z-n44fsx4<-|GKnll}62~RqAgxtXG}6`B%?~>SJ}}B^Rev#GXAm8V@jHXJ#DmQe3~ zoqKW7+0|ep_2>{<>&(XoPv^L3$L<^ig63hQ*7|caq}MLhh5z^UzZLl33jA*c{y$Y^?{Sq)DsMF0F4q;~d&YCqX2p?SEk5RqoFjdv0ZK9lX34kh{1nOKpt)^|n*X)Q__? z(`lAm8&k6zs}RX9co9$|U2digZz@d-SAKj+6vb{Q>;h%M1Z>}=J@S=DR5Zxoj+p-XWt86oUY_X6c0-!@R83`-deatGa;=S$jD4zp zJ@+j2AF90ek@e8X+ny3J+7i%6zCE%?*EGJWlvf4Qy}Djj&Ly5r(`RoSh)7xRx(p|MlbfxavEn?ne8B7NpyEh0mZ_&HS!u|E!Vz}Ix3m~zKFxZ_jric|K9&?ei#h8sP(J7L2@W*zvw%rqnP zvQ}p}5W;vX$VFgQmcE(0w5}V4_GDXRg_$S{#Y))~9KDBWG1288$_Gz1(1vz_=()^J z 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 Pl(|!l-%Zy%xnz{w|EA=ZI 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%0AsDB*f;*4<|%TSCkFk40;o7%+eaSEiMcp>bk` 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

0R3UeoJE`hNCVsf%GKI5YIq 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

wu*2Ib3O(Z&tDGsjxAvmp&8qo567Px=c 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 z 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/source/images/entity.png b/docs/source/images/entity.png new file mode 100644 index 0000000000000000000000000000000000000000..503c34cf7747f4720563d4ee1962240fceaa9556 GIT binary patch literal 40652 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 literal 0 HcmV?d00001 diff --git a/docs/source/images/human_detector_flow.png b/docs/source/images/human_detector_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..13856ce93b72e6c11830e8fb6370939279743c03 GIT binary patch literal 77097 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 +touch src//module/complex/fire_brigade_human_detector.py +``` + +次に、`Human Detector` モジュールの雛形の実装を行います. 以下のコードを`fire_brigade_human_detector.py` に記述してください. + +```python +from typing import Optional + +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.module.complex.human_detector import HumanDetector +from adf_core_python.core.logger.logger import get_agent_logger + + +class FireBrigadeHumanDetector(HumanDetector): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + # 計算結果を格納する変数 + self._result: Optional[EntityID] = None + # ロガーの取得 + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + + def calculate(self) -> HumanDetector: + """ + 行動対象を決定する + + Returns + ------- + HumanDetector: 自身のインスタンス + """ + self._logger.info("Calculate FireBrigadeHumanDetector") + return self + + def get_target_entity_id(self) -> Optional[EntityID]: + """ + 行動対象のEntityIDを取得する + + Returns + ------- + Optional[EntityID]: 行動対象のEntityID + """ + return self._result +``` + +## モジュールの登錍 + +次に、作成したモジュールを登録します. + +`WORKING_DIR//config/module.yaml` ファイルを開き,以下の部分を +```yaml +DefaultTacticsFireBrigade: + HumanDetector: src..module.complex.sample_human_detector.SampleHumanDetector +``` + +以下のように変更してください. + +```yaml +DefaultTacticsFireBrigade: + HumanDetector: src..module.complex.fire_brigade_human_detector.FireBrigadeHumanDetector +``` + +シミュレーションサーバーを起動し、エージェントを実行してみましょう. + +```bash +cd WORKING_DIR/ +python main.py +``` + +標準出力に `Calculate FireBrigadeHumanDetector` と表示されれば成功です. + +## `Human Detector` モジュールの設計 + +救助対象選択プログラムの中身を書き,消防隊エージェントが救助活動をおこなえるように修正します. + +消防隊エージェントの救助対象を最も簡単に選択する方法は,埋没している市民の中で最も自身に近い市民を選択する方法です. 今回は,この方法を採用した消防隊エージェントの行動対象決定モジュールを書いてみましょう. + +埋没している市民の中で最も自身に近い市民を救助対象として選択する方法は,フローチャートで表すと下図のようになります. + +![救助対象選択フローチャート](./../../images/human_detector_flow.png) + +このフローチャート中の各処理を,次小節で紹介する各クラス・メソッド等で置き換えたものを,`fire_brigade_human_detector.py` に記述していくことで,救助対象選択プログラムを完成させます. + + +## `Human Detector` モジュールの実装で使用するクラス・メソッド + +### WorldInfo + +`WorldInfo` クラスは,エージェントが把握している情報とそれに関する操作をおこなうメソッドをもったクラスです. エージェントはこのクラスのインスタンスを通して,他のエージェントやオブジェクトの状態を確認します. + +モジュール内では,`WorldInfo` クラスのインスタンスを `self._world_info` として保持しています. + +### AgentInfo + +`AgentInfo` クラスは,エージェント自身の情報とそれに関する操作をおこなうメソッドをもったクラスです. エージェントはこのクラスのインスタンスを通して,エージェント自身の状態を取得します. + +モジュール内では,`AgentInfo` クラスのインスタンスを `self._agent_info` として保持しています. + +### EntityID + +`EntityID` クラスは,全てのエージェント/オブジェクトを一意に識別するためのID(識別子)を表すクラスです. RRSではエージェントとオブジェクトをまとめて,エンティティと呼んでいます. + +- 自分自身のエンティティIDを取得する +```python +self._agent_info.get_entity_id() +``` + +### Entity + +`Entity` クラスは,エンティティの基底クラスです. このクラスは,エンティティの基本情報を保持します. + +- エンティティIDからエンティティを取得する +```python +self._world_info.get_entity(entity_id) +``` + +- 同じクラスのエンティティを全て取得する +```python +self._world_info.get_entities_by_type([Building, Road]) +``` + +### Civilian + +`Civilian` クラスは,市民を表すクラスです.このクラスからは,エージェントの位置や負傷の進行状況を取得することができます. + +- `entity` が市民であるかどうかを判定する +```python +isinstance(entity, Civilian) +``` + +- 市民が生きているかどうかを判定する +```python +hp: Optional[int] = entity.get_hp() +if hp is None or hp <= 0: + return False +``` + +- 市民が埋まっているかどうかを判定する +```python +buriedness: Optional[int] = entity.get_buriedness() +if buriedness is None or buriedness <= 0: + return False +``` + +### entityの継承 + +RRS上のエンティティは下図のように Entity を継承したクラスで表現されています. 赤枠で囲まれたクラスは,クラスの意味がそのままRRSの直接的な構成要素を表しています. + +例: Road クラスのインスタンスの中には, Hydrant クラスを継承してない通常の道路を表すものも存在しています. + +![エンティティの継承関係](./../../images/entity.png) + + +## `Human Detector` モジュールの実装 + +`Human Detector` モジュールの実装を行います. + +```python +from typing import Optional + +from rcrs_core.worldmodel.entityID import EntityID +from rcrs_core.entities.civilian import Civilian +from rcrs_core.entities.entity import Entity + +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.module.complex.human_detector import HumanDetector +from adf_core_python.core.logger.logger import get_agent_logger + + +class SampleHumanDetector(HumanDetector): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + # 計算結果を格納する変数 + self._result: Optional[EntityID] = None + # ロガーの取得 + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + + def calculate(self) -> HumanDetector: + """ + 行動対象を決定する + + Returns + ------- + HumanDetector: 自身のインスタンス + """ + me: EntityID = self._agent_info.get_entity_id() + civilians: list[Entity] = self._world_info.get_entities_of_types( + [ + Civilian, + ] + ) + + nearest_civilian: Optional[EntityID] = None + nearest_distance: Optional[float] = None + for civilian in civilians: + if not isinstance(civilian, Civilian): + continue + + if civilian.get_hp() <= 0: + continue + + if civilian.get_buriedness() <= 0: + continue + + distance: float = self._world_info.get_distance(me, civilian.get_id()) + + if nearest_distance is None or distance < nearest_distance: + nearest_civilian = civilian.get_id() + nearest_distance = distance + + self._result = nearest_civilian + + return self + + def get_target_entity_id(self) -> Optional[EntityID]: + """ + 行動対象のEntityIDを取得する + + Returns + ------- + Optional[EntityID]: 行動対象のEntityID + """ + return self._result +``` From 6e0160f888c62fd6a125a1837fcda9fe2a3834b6 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 27 Nov 2024 17:09:58 +0900 Subject: [PATCH 155/249] docs: fix lint --- docs/source/hands-on/clustering.md | 10 +++------- docs/source/tutorial/agent/agent_control.md | 14 ++++++++++---- docs/source/tutorial/environment/environment.md | 1 + docs/source/tutorial/install/install.md | 1 - 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/source/hands-on/clustering.md b/docs/source/hands-on/clustering.md index af1affb..5c24b98 100644 --- a/docs/source/hands-on/clustering.md +++ b/docs/source/hands-on/clustering.md @@ -1,20 +1,18 @@ # クラスタリングモジュール - ## クラスタリングモジュールの目的 複数のエージェントを動かす場合は、それらのエージェントにどのように協調させるかが重要になります。RRSでは多くのチームが、エージェントに各々の担当地域を持たせ役割分担をおこなう協調を取り入れています(他の手段による協調も取り入れています)。担当地域を割り振るためには、地図上のオブジェクトをいくつかのグループに分ける必要があります。このようなグループ分けをしてそれらを管理する場合には、クラスタリングモジュールと呼ばれるモジュールを用います。 -本資料では、多くの世界大会参加チームが使用しているアルゴリズムを用いたクラスタリングモジュールの実装をおこないます。 +本資料では、多くの世界大会参加チームが使用しているアルゴリズムを用いたクラスタリングモジュールの実装をおこないます。 ## 開発するクラスタリングモジュールの概要 本資料で開発するモジュールは下の画像のように、 -1. k-means++アルゴリズムによって地図上のオブジェクトをエージェント数分の区画に分けます。 +1. k-means++アルゴリズムによって地図上のオブジェクトをエージェント数分の区画に分けます。 1. Hungarianアルゴリズムによってそれらの区画とエージェントを (間の距離の総和が最も小さくなるように)1対1で結びつけます。 - ![クラスタリングの画像](./../images/clustering_image.jpg) ## クラスタリングモジュールの実装 @@ -23,7 +21,7 @@ 以降の作業では、カレントディレクトリがプロジェクトのルートディレクトリであることを前提としています。 ``` -まず、クラスタリングモジュールを記述するためのファイルを作成します。 +まず、クラスタリングモジュールを記述するためのファイルを作成します。 ```bash mkdir -p src//module/algorithm @@ -299,5 +297,3 @@ SampleSearch: SampleHumanDetector: Clustering: src.test-agent.module.algorithm.k_means_pp_clustering.KMeansPPClustering ``` - - diff --git a/docs/source/tutorial/agent/agent_control.md b/docs/source/tutorial/agent/agent_control.md index 87a04a8..fe19f30 100644 --- a/docs/source/tutorial/agent/agent_control.md +++ b/docs/source/tutorial/agent/agent_control.md @@ -1,7 +1,8 @@ # エージェントの制御 + このセクションでは、エージェントの制御用のプログラムを作成する方法について説明します。 -## エージェントの制御 +## エージェントの制御について RRSの災害救助エージェントは3種類あり、種類毎にそれぞれ異なるプログラムを書く必要があります。しかし、初めから全てのプログラムを書くことは困難です。ここではまず初めに、消防隊エージェントを操作するプログラムの一部を書いてみましょう. @@ -88,11 +89,12 @@ class FireBrigadeHumanDetector(HumanDetector): return self._result ``` -## モジュールの登錍 +## モジュールの登録 次に、作成したモジュールを登録します. `WORKING_DIR//config/module.yaml` ファイルを開き,以下の部分を + ```yaml DefaultTacticsFireBrigade: HumanDetector: src..module.complex.sample_human_detector.SampleHumanDetector @@ -126,7 +128,6 @@ python main.py このフローチャート中の各処理を,次小節で紹介する各クラス・メソッド等で置き換えたものを,`fire_brigade_human_detector.py` に記述していくことで,救助対象選択プログラムを完成させます. - ## `Human Detector` モジュールの実装で使用するクラス・メソッド ### WorldInfo @@ -146,6 +147,7 @@ python main.py `EntityID` クラスは,全てのエージェント/オブジェクトを一意に識別するためのID(識別子)を表すクラスです. RRSではエージェントとオブジェクトをまとめて,エンティティと呼んでいます. - 自分自身のエンティティIDを取得する + ```python self._agent_info.get_entity_id() ``` @@ -155,11 +157,13 @@ self._agent_info.get_entity_id() `Entity` クラスは,エンティティの基底クラスです. このクラスは,エンティティの基本情報を保持します. - エンティティIDからエンティティを取得する + ```python self._world_info.get_entity(entity_id) ``` - 同じクラスのエンティティを全て取得する + ```python self._world_info.get_entities_by_type([Building, Road]) ``` @@ -169,11 +173,13 @@ self._world_info.get_entities_by_type([Building, Road]) `Civilian` クラスは,市民を表すクラスです.このクラスからは,エージェントの位置や負傷の進行状況を取得することができます. - `entity` が市民であるかどうかを判定する + ```python isinstance(entity, Civilian) ``` - 市民が生きているかどうかを判定する + ```python hp: Optional[int] = entity.get_hp() if hp is None or hp <= 0: @@ -181,6 +187,7 @@ if hp is None or hp <= 0: ``` - 市民が埋まっているかどうかを判定する + ```python buriedness: Optional[int] = entity.get_buriedness() if buriedness is None or buriedness <= 0: @@ -195,7 +202,6 @@ RRS上のエンティティは下図のように Entity を継承したクラス ![エンティティの継承関係](./../../images/entity.png) - ## `Human Detector` モジュールの実装 `Human Detector` モジュールの実装を行います. diff --git a/docs/source/tutorial/environment/environment.md b/docs/source/tutorial/environment/environment.md index db0cd93..ec566ec 100644 --- a/docs/source/tutorial/environment/environment.md +++ b/docs/source/tutorial/environment/environment.md @@ -20,6 +20,7 @@ git clone https://github.com/roborescue/rcrs-server.git cd rcrs-server ./gradlew completeBuild ``` + ビルドした際に以下のようなメッセージが表示されたら成功です。 ```bash diff --git a/docs/source/tutorial/install/install.md b/docs/source/tutorial/install/install.md index 54a055f..c27a88f 100644 --- a/docs/source/tutorial/install/install.md +++ b/docs/source/tutorial/install/install.md @@ -2,7 +2,6 @@ このセクションでは、パッケージのインストール方法について説明します。 - ## 前提条件 インストールする前に、以下の前提条件を確認してください: From 74df50817405f72204d2f17ffdb5bba423318c76 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 27 Nov 2024 18:00:38 +0900 Subject: [PATCH 156/249] docs: correct project name in README.md --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index fa006f8..f9d0012 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -# sqhinx-documentation +# sphinx-documentation ## Generate documentation From a677515c30cc72a3ffbde0a63fc2241865c28370 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 28 Nov 2024 00:37:25 +0900 Subject: [PATCH 157/249] docs: update agent control tutorial with entity class details and examples --- docs/source/tutorial/agent/agent_control.md | 86 +++++++++++++-------- 1 file changed, 55 insertions(+), 31 deletions(-) diff --git a/docs/source/tutorial/agent/agent_control.md b/docs/source/tutorial/agent/agent_control.md index fe19f30..1f28f5a 100644 --- a/docs/source/tutorial/agent/agent_control.md +++ b/docs/source/tutorial/agent/agent_control.md @@ -130,81 +130,96 @@ python main.py ## `Human Detector` モジュールの実装で使用するクラス・メソッド -### WorldInfo - -`WorldInfo` クラスは,エージェントが把握している情報とそれに関する操作をおこなうメソッドをもったクラスです. エージェントはこのクラスのインスタンスを通して,他のエージェントやオブジェクトの状態を確認します. +### Entity -モジュール内では,`WorldInfo` クラスのインスタンスを `self._world_info` として保持しています. +`Entity` クラスは,エンティティの基底クラスです. このクラスは,エンティティの基本情報を保持します. -### AgentInfo +RRS上のエンティティは下図のように `Entity` を継承したクラスで表現されています. 赤枠で囲まれたクラスは,クラスの意味がそのままRRSの直接的な構成要素を表しています. -`AgentInfo` クラスは,エージェント自身の情報とそれに関する操作をおこなうメソッドをもったクラスです. エージェントはこのクラスのインスタンスを通して,エージェント自身の状態を取得します. +例: Road クラスのインスタンスの中には, Hydrant クラスを継承してない通常の道路を表すものも存在しています. -モジュール内では,`AgentInfo` クラスのインスタンスを `self._agent_info` として保持しています. +![エンティティの継承関係](./../../images/entity.png) ### EntityID `EntityID` クラスは,全てのエージェント/オブジェクトを一意に識別するためのID(識別子)を表すクラスです. RRSではエージェントとオブジェクトをまとめて,エンティティと呼んでいます. -- 自分自身のエンティティIDを取得する +### Civilian + +`Civilian` クラスは,市民を表すクラスです.このクラスからは,エージェントの位置や負傷の進行状況を取得することができます. + +- `entity` が市民であるかどうかを判定する ```python -self._agent_info.get_entity_id() +isinstance(entity, Civilian) ``` -### Entity +- エンティティIDを取得する -`Entity` クラスは,エンティティの基底クラスです. このクラスは,エンティティの基本情報を保持します. +```python +entity.get_id() +``` -- エンティティIDからエンティティを取得する +- 市民が生きているかどうかを判定する ```python -self._world_info.get_entity(entity_id) +hp: Optional[int] = entity.get_hp() +if hp is None or hp <= 0: + return False ``` -- 同じクラスのエンティティを全て取得する +- 市民が埋まっているかどうかを判定する ```python -self._world_info.get_entities_by_type([Building, Road]) +buriedness: Optional[int] = entity.get_buriedness() +if buriedness is None or buriedness <= 0: + return False ``` -### Civilian +### WorldInfo -`Civilian` クラスは,市民を表すクラスです.このクラスからは,エージェントの位置や負傷の進行状況を取得することができます. +`WorldInfo` クラスは,エージェントが把握している情報とそれに関する操作をおこなうメソッドをもったクラスです. エージェントはこのクラスのインスタンスを通して,他のエージェントやオブジェクトの状態を確認します. -- `entity` が市民であるかどうかを判定する +モジュール内では,`WorldInfo` クラスのインスタンスを `self._world_info` として保持しています. + +- エンティティIDからエンティティを取得する ```python -isinstance(entity, Civilian) +self._world_info.get_entity(entity_id) ``` -- 市民が生きているかどうかを判定する +- 指定したクラスのエンティティを全て取得する ```python -hp: Optional[int] = entity.get_hp() -if hp is None or hp <= 0: - return False +self._world_info.get_entities_by_type([Building, Road]) ``` -- 市民が埋まっているかどうかを判定する +- エージェントの位置から指定したエンティティまでの距離を取得する ```python -buriedness: Optional[int] = entity.get_buriedness() -if buriedness is None or buriedness <= 0: - return False +self._world_info.get_distance(me, civilian.get_id()) ``` -### entityの継承 +[詳細はこちら](../../adf_core_python.core.agent.info.rst) + +### AgentInfo -RRS上のエンティティは下図のように Entity を継承したクラスで表現されています. 赤枠で囲まれたクラスは,クラスの意味がそのままRRSの直接的な構成要素を表しています. +`AgentInfo` クラスは,エージェント自身の情報とそれに関する操作をおこなうメソッドをもったクラスです. エージェントはこのクラスのインスタンスを通して,エージェント自身の状態を取得します. -例: Road クラスのインスタンスの中には, Hydrant クラスを継承してない通常の道路を表すものも存在しています. +モジュール内では,`AgentInfo` クラスのインスタンスを `self._agent_info` として保持しています. -![エンティティの継承関係](./../../images/entity.png) +- 自分自身のエンティティIDを取得する + +```python +self._agent_info.get_entity_id() +``` + +[詳細はこちら](../../adf_core_python.core.agent.info.rst) ## `Human Detector` モジュールの実装 `Human Detector` モジュールの実装を行います. +以下のコードを`fire_brigade_human_detector.py` に記述してください。 ```python from typing import Optional @@ -289,3 +304,12 @@ class SampleHumanDetector(HumanDetector): """ return self._result ``` + +シミュレーションサーバーを起動し,エージェントを実行してみましょう. + +```bash +cd WORKING_DIR/ +python main.py +``` + +埋没している市民を消防隊エージェントが救出できるようになっていれば成功です. From 0b731358801659b6a44cbbeb347897d1640a9a60 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 28 Nov 2024 10:46:39 +0900 Subject: [PATCH 158/249] fix --- docs/source/tutorial/agent/agent_control.md | 56 ++++++++++----------- docs/source/tutorial/module/module.md | 2 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/source/tutorial/agent/agent_control.md b/docs/source/tutorial/agent/agent_control.md index 1f28f5a..afc3457 100644 --- a/docs/source/tutorial/agent/agent_control.md +++ b/docs/source/tutorial/agent/agent_control.md @@ -4,34 +4,34 @@ ## エージェントの制御について -RRSの災害救助エージェントは3種類あり、種類毎にそれぞれ異なるプログラムを書く必要があります。しかし、初めから全てのプログラムを書くことは困難です。ここではまず初めに、消防隊エージェントを操作するプログラムの一部を書いてみましょう. +RRSの災害救助エージェントは3種類あり、種類毎にそれぞれ異なるプログラムを書く必要があります。しかし、初めから全てのプログラムを書くことは困難です。ここではまず初めに、消防隊エージェントを操作するプログラムの一部を書いてみましょう。 ```{note} -エージェントを操作するプログラムは,エージェントの種類毎に同一です. プログラムは各エージェントに配られ,そのエージェントのみの操作を担います. 消防隊エージェントを操作するプログラムを書けば,それがすべての消防隊エージェント上でそれぞれ動作します. +エージェントを操作するプログラムは、エージェントの種類毎に同一です。 プログラムは各エージェントに配られ、そのエージェントのみの操作を担います。 消防隊エージェントを操作するプログラムを書けば、それがすべての消防隊エージェント上でそれぞれ動作します。 ``` ![消防隊エージェント](./../../images/agent_control.jpg) ## エージェントの動作フロー -エージェントの動作を決めているのはTacticsというプログラムです. +エージェントの動作を決めているのはTacticsというプログラムです。 -消防隊の思考ルーチンは下の図の通りにおおよそおこなわれます. 消防隊の動作としては,まず救助対象の市民を捜索し(`Human Detector`),見つかった市民を救助します(`Action Rescue`). 救助対象の市民が見つからない場合は,探索場所を変更して市民を捜索するため,次の捜索場所を決定して(`Search`)移動します(`Action Ext move`). なお,エージェントは1ステップ内で,移動と救助活動を同時におこなうことが出来ません.つまり,ステップごとに更新される自身の周辺情報を確認して,動作の対象と動作内容を決定していくということになります. これらそれぞれの機能が,モジュールと呼ばれるプログラムとして分割して表現されています. +消防隊の思考ルーチンは下の図の通りにおおよそおこなわれます。 消防隊の動作としては、まず救助対象の市民を捜索し(`Human Detector`)、見つかった市民を救助します(`Action Rescue`)。 救助対象の市民が見つからない場合は、探索場所を変更して市民を捜索するため、次の捜索場所を決定して(`Search`)移動します(`Action Ext move`)。 なお、エージェントは1ステップ内で、移動と救助活動を同時におこなうことが出来ません。つまり、ステップごとに更新される自身の周辺情報を確認して、動作の対象と動作内容を決定していくということになります。 これらそれぞれの機能が、モジュールと呼ばれるプログラムとして分割して表現されています。 -今回はこの中で,救助(掘り起こし)対象を決定する `Human Detector` モジュールを開発します. +今回はこの中で、救助(掘り起こし)対象を決定する `Human Detector` モジュールを開発します。 ![消防隊エージェントの動作フロー](./../../images/agent_flow.png) ## `Human Detector` モジュールの実装の準備 -まず、`Human Detector` モジュールを記述するためのファイルを作成します. +まず、`Human Detector` モジュールを記述するためのファイルを作成します。 ```bash cd WORKING_DIR/ touch src//module/complex/fire_brigade_human_detector.py ``` -次に、`Human Detector` モジュールの雛形の実装を行います. 以下のコードを`fire_brigade_human_detector.py` に記述してください. +次に、`Human Detector` モジュールの雛形の実装を行います。 以下のコードを`fire_brigade_human_detector.py` に記述してください。 ```python from typing import Optional @@ -91,62 +91,62 @@ class FireBrigadeHumanDetector(HumanDetector): ## モジュールの登録 -次に、作成したモジュールを登録します. +次に、作成したモジュールを登録します。 -`WORKING_DIR//config/module.yaml` ファイルを開き,以下の部分を +`WORKING_DIR//config/module.yaml` ファイルを開き、以下の部分を ```yaml DefaultTacticsFireBrigade: HumanDetector: src..module.complex.sample_human_detector.SampleHumanDetector ``` -以下のように変更してください. +以下のように変更してください。 ```yaml DefaultTacticsFireBrigade: HumanDetector: src..module.complex.fire_brigade_human_detector.FireBrigadeHumanDetector ``` -シミュレーションサーバーを起動し、エージェントを実行してみましょう. +シミュレーションサーバーを起動し、エージェントを実行してみましょう。 ```bash cd WORKING_DIR/ python main.py ``` -標準出力に `Calculate FireBrigadeHumanDetector` と表示されれば成功です. +標準出力に `Calculate FireBrigadeHumanDetector` と表示されれば成功です。 ## `Human Detector` モジュールの設計 -救助対象選択プログラムの中身を書き,消防隊エージェントが救助活動をおこなえるように修正します. +救助対象選択プログラムの中身を書き、消防隊エージェントが救助活動をおこなえるように修正します。 -消防隊エージェントの救助対象を最も簡単に選択する方法は,埋没している市民の中で最も自身に近い市民を選択する方法です. 今回は,この方法を採用した消防隊エージェントの行動対象決定モジュールを書いてみましょう. +消防隊エージェントの救助対象を最も簡単に選択する方法は、埋没している市民の中で最も自身に近い市民を選択する方法です。 今回は、この方法を採用した消防隊エージェントの行動対象決定モジュールを書いてみましょう。 -埋没している市民の中で最も自身に近い市民を救助対象として選択する方法は,フローチャートで表すと下図のようになります. +埋没している市民の中で最も自身に近い市民を救助対象として選択する方法は、フローチャートで表すと下図のようになります。 ![救助対象選択フローチャート](./../../images/human_detector_flow.png) -このフローチャート中の各処理を,次小節で紹介する各クラス・メソッド等で置き換えたものを,`fire_brigade_human_detector.py` に記述していくことで,救助対象選択プログラムを完成させます. +このフローチャート中の各処理を、次小節で紹介する各クラス・メソッド等で置き換えたものを、`fire_brigade_human_detector.py` に記述していくことで、救助対象選択プログラムを完成させます。 ## `Human Detector` モジュールの実装で使用するクラス・メソッド ### Entity -`Entity` クラスは,エンティティの基底クラスです. このクラスは,エンティティの基本情報を保持します. +`Entity` クラスは、エンティティの基底クラスです。 このクラスは、エンティティの基本情報を保持します。 -RRS上のエンティティは下図のように `Entity` を継承したクラスで表現されています. 赤枠で囲まれたクラスは,クラスの意味がそのままRRSの直接的な構成要素を表しています. +RRS上のエンティティは下図のように `Entity` を継承したクラスで表現されています。 赤枠で囲まれたクラスは、クラスの意味がそのままRRSの直接的な構成要素を表しています。 -例: Road クラスのインスタンスの中には, Hydrant クラスを継承してない通常の道路を表すものも存在しています. +例: Road クラスのインスタンスの中には、 Hydrant クラスを継承してない通常の道路を表すものも存在しています。 ![エンティティの継承関係](./../../images/entity.png) ### EntityID -`EntityID` クラスは,全てのエージェント/オブジェクトを一意に識別するためのID(識別子)を表すクラスです. RRSではエージェントとオブジェクトをまとめて,エンティティと呼んでいます. +`EntityID` クラスは、全てのエージェント/オブジェクトを一意に識別するためのID(識別子)を表すクラスです。 RRSではエージェントとオブジェクトをまとめて、エンティティと呼んでいます。 ### Civilian -`Civilian` クラスは,市民を表すクラスです.このクラスからは,エージェントの位置や負傷の進行状況を取得することができます. +`Civilian` クラスは、市民を表すクラスです。このクラスからは、エージェントの位置や負傷の進行状況を取得することができます。 - `entity` が市民であるかどうかを判定する @@ -178,9 +178,9 @@ if buriedness is None or buriedness <= 0: ### WorldInfo -`WorldInfo` クラスは,エージェントが把握している情報とそれに関する操作をおこなうメソッドをもったクラスです. エージェントはこのクラスのインスタンスを通して,他のエージェントやオブジェクトの状態を確認します. +`WorldInfo` クラスは、エージェントが把握している情報とそれに関する操作をおこなうメソッドをもったクラスです。 エージェントはこのクラスのインスタンスを通して、他のエージェントやオブジェクトの状態を確認します。 -モジュール内では,`WorldInfo` クラスのインスタンスを `self._world_info` として保持しています. +モジュール内では、`WorldInfo` クラスのインスタンスを `self._world_info` として保持しています。 - エンティティIDからエンティティを取得する @@ -204,9 +204,9 @@ self._world_info.get_distance(me, civilian.get_id()) ### AgentInfo -`AgentInfo` クラスは,エージェント自身の情報とそれに関する操作をおこなうメソッドをもったクラスです. エージェントはこのクラスのインスタンスを通して,エージェント自身の状態を取得します. +`AgentInfo` クラスは、エージェント自身の情報とそれに関する操作をおこなうメソッドをもったクラスです。 エージェントはこのクラスのインスタンスを通して、エージェント自身の状態を取得します。 -モジュール内では,`AgentInfo` クラスのインスタンスを `self._agent_info` として保持しています. +モジュール内では、`AgentInfo` クラスのインスタンスを `self._agent_info` として保持しています。 - 自分自身のエンティティIDを取得する @@ -218,7 +218,7 @@ self._agent_info.get_entity_id() ## `Human Detector` モジュールの実装 -`Human Detector` モジュールの実装を行います. +`Human Detector` モジュールの実装を行います。 以下のコードを`fire_brigade_human_detector.py` に記述してください。 ```python @@ -305,11 +305,11 @@ class SampleHumanDetector(HumanDetector): return self._result ``` -シミュレーションサーバーを起動し,エージェントを実行してみましょう. +シミュレーションサーバーを起動し、エージェントを実行してみましょう。 ```bash cd WORKING_DIR/ python main.py ``` -埋没している市民を消防隊エージェントが救出できるようになっていれば成功です. +埋没している市民を消防隊エージェントが救出できるようになっていれば成功です。 diff --git a/docs/source/tutorial/module/module.md b/docs/source/tutorial/module/module.md index 6614d27..ce8c9de 100644 --- a/docs/source/tutorial/module/module.md +++ b/docs/source/tutorial/module/module.md @@ -82,7 +82,7 @@ adf-core-pythonでは、以下のモジュールが提供されています。 | DynamicClustering | シミュレーションの進行によってクラスタが変化するクラスタリング | | StaticClustering | シミュレーション開始時にクラスタが定まるクラスタリング | | ExtAction | 活動対象決定後のエージェントの動作決定 | -| MessageCoordinator | 通信でメッセージ(情報)を送信する経路である,チャンネルへのメッセージの分配 | +| MessageCoordinator | 通信でメッセージ(情報)を送信する経路である、チャンネルへのメッセージの分配 | | ChannelSubscriber | 通信によってメッセージを受け取るチャンネルの選択 | 自分で上記の役割以外のモジュールを作成する際は、`AbstractModule` を継承してください。 From f7e59923765e299b145ec20da749bc8c8192678e Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 28 Nov 2024 12:08:38 +0900 Subject: [PATCH 159/249] docs: add search module documentation and update index --- docs/source/hands-on/search.md | 173 +++++++++++++++++++++++++++++++++ docs/source/index.rst | 1 + 2 files changed, 174 insertions(+) create mode 100644 docs/source/hands-on/search.md diff --git a/docs/source/hands-on/search.md b/docs/source/hands-on/search.md new file mode 100644 index 0000000..cc48fce --- /dev/null +++ b/docs/source/hands-on/search.md @@ -0,0 +1,173 @@ +# サーチモジュール + +## サーチモジュールの概要 + +今回開発するモジュールは、`KMeansPPClustering` モジュールを用いた情報探索対象決定 (`Search`) モジュールです。 クラスタリングモジュールによってエージェント間で担当地域の分割をおこない、 担当地域内からランダムに探索対象として選択します。 + +## サーチモジュールの実装の準備 + +```{note} +以降の作業では、カレントディレクトリがプロジェクトのルートディレクトリであることを前提としています。 +``` + +まず、サーチモジュールを記述するためのファイルを作成します。 + +```bash +mkdir -p src//module/search +touch src//module/complex/k_means_pp_search.py +``` + +次に、サーチモジュールの実装を行います。 以下のコードを `k_means_pp_search.py` に記述してください。 +これが今回実装するサーチモジュールの雛形になります。 + +```python +import random +from typing import Optional, cast + +from rcrs_core.entities.building import Building +from rcrs_core.entities.entity import Entity +from rcrs_core.entities.refuge import Refuge +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.module.algorithm.clustering import Clustering +from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning +from adf_core_python.core.component.module.complex.search import Search +from adf_core_python.core.logger.logger import get_agent_logger + + +class KMeansPPSearch(Search): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._result: Optional[EntityID] = None + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + + def calculate(self) -> Search: + return self + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._result +``` + +## モジュールの登録 + +次に、作成したモジュールを登録します。 +以下のように`config/module.yaml`の該当箇所を変更してください + +```yaml +DefaultTacticsAmbulanceTeam: + Search: src..module.complex.k_means_pp_search.KMeansPPSearch + +DefaultTacticsFireBrigade: + Search: src..module.complex.k_means_pp_search.KMeansPPSearch + +DefaultTacticsPoliceForce: + Search: src..module.complex.k_means_pp_search.KMeansPPSearch +``` + +## モジュールの実装 + +まず、`KMeansPPClustering` モジュールを呼び出せるようにします。 + +以下のコードを`config/module.yaml`に追記してください。 + +```yaml +KMeansPPSearch: + Clustering: src..module.algorithm.k_means_pp_clustering.KMeansPPClustering +``` + +次に、`KMeansPPSearch` モジュールで `KMeansPPClustering` モジュールを呼び出せるようにします。 + +以下のコードを `k_means_pp_search.py` に追記してください。 + +```python +class KMeansPPSearch(Search): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._result: Optional[EntityID] = None + + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + + self._clustering: Clustering = cast( + Clustering, + module_manager.get_module( + "KMeansPPSearch.Clustering", + "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", + ), + ) + + self.register_sub_module(self._clustering) +``` + +そして、`calculate` メソッドでクラスタリングモジュールを呼び出し、探索対象を決定します。 + +```python + def calculate(self) -> Search: + # 自エージェントのエンティティIDを取得 + me: EntityID = self._agent_info.get_entity_id() + # 自エージェントが所属するクラスターのインデックスを取得 + allocated_cluster_index: int = self._clustering.get_cluster_index(me) + # クラスター内のエンティティIDを取得 + cluster_entity_ids: list[EntityID] = self._clustering.get_cluster_entity_ids( + allocated_cluster_index + ) + # 乱数で選択 + index = random.randint(0, len(cluster_entity_ids) - 1) + # 選択したエンティティIDを結果として設定 + self._result = cluster_entity_ids[index] + + return self +``` + +以上で、`KMeansPPClustering` モジュールを用いた `KMeansPPSearch` モジュールの実装が完了しました。 + +実行すると、各エージェントが担当地域内からランダムに探索対象を選択し、探索を行います。 + +## モジュールの改善 + +`KMeansPPSearch` モジュールは、クラスタリングモジュールを用いて担当地域内からランダムに探索対象を選択しています。 +そのため、以下のような問題があります。 + +- 探索対象がステップごとに変わってしまう + - 目標にたどり着く前に探索対象が変わってしまうため、なかなか目標にたどり着けない + - 色んなところにランダムに探索対象を選択することで、効率的な探索ができない +- すでに探索したエンティティを再度探索対象として選択してしまうため、効率的な探索ができない + +などの問題があります。 + +## 課題 + +`KMeansPPSearch` モジュールを改善し、より効率的な探索を行うモジュールを実装して見てください。 + +### 探索対象がステップごとに変わってしまう問題 + +### すでに探索したエンティティを再度探索対象として選択してしまう問題 diff --git a/docs/source/index.rst b/docs/source/index.rst index 5920345..5779f93 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -47,6 +47,7 @@ ADF Core Python を始めるには、インストール手順に従い、この :caption: ハンズオン: hands-on/clustering + hands-on/search .. toctree:: :maxdepth: 1 From 4917ee6d20438c2180e74fcc7ad7740bf1bc7af5 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 28 Nov 2024 12:27:58 +0900 Subject: [PATCH 160/249] docs: update config tutorial with directory structure and file descriptions --- docs/source/tutorial/config/config.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/source/tutorial/config/config.md b/docs/source/tutorial/config/config.md index 827ba24..3257086 100644 --- a/docs/source/tutorial/config/config.md +++ b/docs/source/tutorial/config/config.md @@ -2,19 +2,30 @@ ## コンフィグについての説明 +```bash +. +└── + └── config + ├── development.json + ├── launcher.yaml + └── module.yaml +``` + +`config` ディレクトリには、エージェントの設定ファイルが格納されています。 + ### launcher.yaml -launcher.yaml は、エージェントの起動時に読み込まれる設定ファイルです。 +`launcher.yaml` は、エージェントの起動時に読み込まれる設定ファイルです。 シミュレーションサーバーの指定や読み込むモジュールが記述されたファイルの指定などが記述されています。 ### module.yaml -module.yaml は、エージェントが読み込むモジュールの設定ファイルです。 +`module.yaml` は、エージェントが読み込むモジュールの設定ファイルです。 読み込むモジュールのパスなどが記述されています。 -パスを指定する際は、プロジェクトのmain.pyからの相対パスで指定してください。 +パスを指定する際は、プロジェクトの`main.py`からの相対パスで指定してください。 ### development.json -development.json は、開発時に使用する設定ファイルです。 +`development.json` は、開発時に使用する設定ファイルです。 エージェントの開発時に外部から値を取得する際に使用します。 そのため、競技時には使用することができません。 From 16760a92281ef381aa46670175f20cc96d3af9db Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 28 Nov 2024 15:32:06 +0900 Subject: [PATCH 161/249] docs: update clustering and search module documentation with team-specific paths and simulation instructions --- docs/source/hands-on/clustering.md | 6 ++++-- docs/source/hands-on/search.md | 3 --- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/source/hands-on/clustering.md b/docs/source/hands-on/clustering.md index 5c24b98..9e70f9a 100644 --- a/docs/source/hands-on/clustering.md +++ b/docs/source/hands-on/clustering.md @@ -292,8 +292,10 @@ class KMeansPPClustering(Clustering): ```yaml SampleSearch: PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning - Clustering: src.test-agent.module.algorithm.k_means_pp_clustering.KMeansPPClustering + Clustering: src..module.algorithm.k_means_pp_clustering.KMeansPPClustering SampleHumanDetector: - Clustering: src.test-agent.module.algorithm.k_means_pp_clustering.KMeansPPClustering + Clustering: src..module.algorithm.k_means_pp_clustering.KMeansPPClustering ``` + +シミュレーションサーバーを起動して、エージェントを起動してください。エージェントが起動すると、標準出力にクラスタリング結果が表示されます。 diff --git a/docs/source/hands-on/search.md b/docs/source/hands-on/search.md index cc48fce..a8e18d2 100644 --- a/docs/source/hands-on/search.md +++ b/docs/source/hands-on/search.md @@ -13,7 +13,6 @@ まず、サーチモジュールを記述するためのファイルを作成します。 ```bash -mkdir -p src//module/search touch src//module/complex/k_means_pp_search.py ``` @@ -29,14 +28,12 @@ from rcrs_core.entities.entity import Entity from rcrs_core.entities.refuge import Refuge from rcrs_core.worldmodel.entityID import EntityID -from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo from adf_core_python.core.agent.info.scenario_info import ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.component.module.algorithm.clustering import Clustering -from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning from adf_core_python.core.component.module.complex.search import Search from adf_core_python.core.logger.logger import get_agent_logger From 16733f647d1dd55b0537b26f5321df88561582e4 Mon Sep 17 00:00:00 2001 From: harrki Date: Thu, 28 Nov 2024 16:00:59 +0900 Subject: [PATCH 162/249] fix: Fixed to copy only necessary files --- adf_core_python/cli/cli.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/adf_core_python/cli/cli.py b/adf_core_python/cli/cli.py index 232bd1b..e86c980 100644 --- a/adf_core_python/cli/cli.py +++ b/adf_core_python/cli/cli.py @@ -38,6 +38,8 @@ def _copy_template( for file in files: file_path = os.path.join(root, file) with open(file_path, "r") as f: + if not file_path.endswith((".py", ".yaml")): + continue content = f.read() with open(file_path, "w") as f: f.write(content.replace(NAME_PLACEHOLDER, name)) From 92d4da8c2e32fdd355fb72d1fece05960dfc610c Mon Sep 17 00:00:00 2001 From: harrki Date: Thu, 28 Nov 2024 16:04:33 +0900 Subject: [PATCH 163/249] fix: Add necessary file --- adf_core_python/cli/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adf_core_python/cli/cli.py b/adf_core_python/cli/cli.py index e86c980..b14a1c7 100644 --- a/adf_core_python/cli/cli.py +++ b/adf_core_python/cli/cli.py @@ -38,7 +38,7 @@ def _copy_template( for file in files: file_path = os.path.join(root, file) with open(file_path, "r") as f: - if not file_path.endswith((".py", ".yaml")): + if not file_path.endswith((".py", ".yaml", ".json")): continue content = f.read() with open(file_path, "w") as f: From 98faf84ffd79361a5b0d1bd550ac5bb76a2e142a Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 28 Nov 2024 17:20:59 +0900 Subject: [PATCH 164/249] docs: add sphinx-togglebutton extension and improve search module entity selection logic --- docs/source/conf.py | 1 + docs/source/hands-on/search.md | 172 ++++++++++++++++++++++++++++++++- poetry.lock | 56 ++++++++++- pyproject.toml | 1 + 4 files changed, 226 insertions(+), 4 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index d0b0168..42b8350 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -24,6 +24,7 @@ "sphinx.ext.napoleon", "sphinx.ext.autosummary", "sphinx_copybutton", + "sphinx_togglebutton", "myst_parser", "sphinxcontrib.mermaid", ] diff --git a/docs/source/hands-on/search.md b/docs/source/hands-on/search.md index a8e18d2..8b6ac94 100644 --- a/docs/source/hands-on/search.md +++ b/docs/source/hands-on/search.md @@ -138,9 +138,8 @@ class KMeansPPSearch(Search): allocated_cluster_index ) # 乱数で選択 - index = random.randint(0, len(cluster_entity_ids) - 1) - # 選択したエンティティIDを結果として設定 - self._result = cluster_entity_ids[index] + if cluster_entity_ids: + self._result = random.choice(cluster_entity_ids) return self ``` @@ -158,6 +157,7 @@ class KMeansPPSearch(Search): - 目標にたどり着く前に探索対象が変わってしまうため、なかなか目標にたどり着けない - 色んなところにランダムに探索対象を選択することで、効率的な探索ができない - すでに探索したエンティティを再度探索対象として選択してしまうため、効率的な探索ができない +- 近くに未探索のエンティティがあるのに、遠くのエンティティを探索対象として選択してしまう などの問題があります。 @@ -165,6 +165,172 @@ class KMeansPPSearch(Search): `KMeansPPSearch` モジュールを改善し、より効率的な探索を行うモジュールを実装して見てください。 +```{warning} +ここに上げた問題以外にも、改善すべき点が存在すると思うので、それを改善していただいても構いません。 +``` + ### 探索対象がステップごとに変わってしまう問題 +```{admonition} 方針のヒント +:class: tip dropdown + +一度選択した探索対象に到達するまで、探索対象を変更しないようにする +``` + +```{admonition} プログラム例 +:class: tip dropdown + +````python + def calculate(self) -> Search: + # 自エージェントのエンティティIDを取得 + me: EntityID = self._agent_info.get_entity_id() + # 自エージェントが所属するクラスターのインデックスを取得 + allocated_cluster_index: int = self._clustering.get_cluster_index(me) + # クラスター内のエンティティIDを取得 + cluster_entity_ids: list[EntityID] = self._clustering.get_cluster_entity_ids( + allocated_cluster_index + ) + + # 探索対象をすでに選んでいる場合 + if self._result: + # 自エージェントのいる場所のエンティティIDを取得 + my_position = self._agent_info.get_position_entity_id() + # 探索対象の場所のエンティティIDを取得 + target_position = self._world_info.get_entity_position(self._result) + # 自エージェントのいる場所と探索対象の場所が一致している場合、探索対象をリセット + if my_position == target_position: + # 探索対象をリセット + self._result = None + + # 探索対象が未選択の場合 + if not self._result and cluster_entity_ids: + self._result = random.choice(cluster_entity_ids) + + return self +``` + ### すでに探索したエンティティを再度探索対象として選択してしまう問題 + +```{admonition} 方針のヒント +:class: tip dropdown + +すでに探索したエンティティを何かしらの方法で記録し、再度探索対象として選択しないようにする +``` + +```{admonition} プログラム例 +:class: tip dropdown + +````python + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._result: Optional[EntityID] = None + + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + + self._clustering: Clustering = cast( + Clustering, + module_manager.get_module( + "KMeansPPSearch.Clustering", + "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", + ), + ) + + self.register_sub_module(self._clustering) + + # 探索したいエンティティIDのリスト(追加) + self._search_entity_ids: list[EntityID] = [] + + def calculate(self) -> Search: + # 探索したいエンティティIDのリストが空の場合 + if not self._search_entity_ids: + # 自エージェントのエンティティIDを取得 + me: EntityID = self._agent_info.get_entity_id() + # 自エージェントが所属するクラスターのインデックスを取得 + allocated_cluster_index: int = self._clustering.get_cluster_index(me) + # クラスター内のエンティティIDを取得(変更) + self._search_entity_ids: list[EntityID] = ( + self._clustering.get_cluster_entity_ids(allocated_cluster_index) + ) + + # 探索対象をすでに選んでいる場合 + if self._result: + # 自エージェントのいる場所のエンティティIDを取得 + my_position = self._agent_info.get_position_entity_id() + # 探索対象の場所のエンティティIDを取得 + target_position = self._world_info.get_entity_position(self._result) + # 自エージェントのいる場所と探索対象の場所が一致している場合、探索対象をリセット + if my_position == target_position: + # 探索したいエンティティIDのリストから探索対象を削除 + self._search_entity_ids.remove(self._result) + # 探索対象をリセット + self._result = None + + # 探索対象が未選択の場合(変更) + if not self._result and self._search_entity_ids: + self._result = random.choice(self._search_entity_ids) + + return self +``` + +### 近くに未探索のエンティティがあるのに、遠くのエンティティを探索対象として選択してしまう + +```{admonition} 方針のヒント +:class: tip dropdown + +エンティティ間の距離を計算し、もっとも近いエンティティを探索対象として選択する +``` + +```{admonition} プログラム例 +:class: tip dropdown + +````python + def calculate(self) -> Search: + # 探索したいエンティティIDのリストが空の場合 + if not self._search_entity_ids: + # 自エージェントのエンティティIDを取得 + me: EntityID = self._agent_info.get_entity_id() + # 自エージェントが所属するクラスターのインデックスを取得 + allocated_cluster_index: int = self._clustering.get_cluster_index(me) + # クラスター内のエンティティIDを取得 + self._search_entity_ids: list[EntityID] = ( + self._clustering.get_cluster_entity_ids(allocated_cluster_index) + ) + + # 探索対象をすでに選んでいる場合 + if self._result: + # 自エージェントのいる場所のエンティティIDを取得 + my_position = self._agent_info.get_position_entity_id() + # 探索対象の場所のエンティティIDを取得 + target_position = self._world_info.get_entity_position(self._result) + # 自エージェントのいる場所と探索対象の場所が一致している場合、探索対象をリセット + if my_position == target_position: + # 探索したいエンティティIDのリストから探索対象を削除 + self._search_entity_ids.remove(self._result) + # 探索対象をリセット + self._result = None + + # 探索対象が未選択の場合 + if not self._result and self._search_entity_ids: + nearest_entity_id: Optional[EntityID] = None + nearest_distance: float = float("inf") + me: EntityID = self._agent_info.get_entity_id() + # 探索対象の中で自エージェントに最も近いエンティティIDを選択(変更) + for entity_id in self._search_entity_ids: + distance = self._world_info.get_distance(me, entity_id) + if distance < nearest_distance: + nearest_entity_id = entity_id + nearest_distance = distance + self._result = nearest_entity_id +``` diff --git a/poetry.lock b/poetry.lock index 357088b..3ab05f3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1102,6 +1102,26 @@ dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodest doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +[[package]] +name = "setuptools" +version = "75.6.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +files = [ + {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"}, + {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"] +core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"] + [[package]] name = "shapely" version = "2.0.6" @@ -1254,6 +1274,26 @@ sphinx = ">=1.8" code-style = ["pre-commit (==2.12.1)"] rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] +[[package]] +name = "sphinx-togglebutton" +version = "0.3.2" +description = "Toggle page content and collapse admonitions in Sphinx." +optional = false +python-versions = "*" +files = [ + {file = "sphinx-togglebutton-0.3.2.tar.gz", hash = "sha256:ab0c8b366427b01e4c89802d5d078472c427fa6e9d12d521c34fa0442559dc7a"}, + {file = "sphinx_togglebutton-0.3.2-py3-none-any.whl", hash = "sha256:9647ba7874b7d1e2d43413d8497153a85edc6ac95a3fea9a75ef9c1e08aaae2b"}, +] + +[package.dependencies] +docutils = "*" +setuptools = "*" +sphinx = "*" +wheel = "*" + +[package.extras] +sphinx = ["matplotlib", "myst-nb", "numpy", "sphinx-book-theme", "sphinx-design", "sphinx-examples"] + [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" @@ -1472,7 +1512,21 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "wheel" +version = "0.45.1" +description = "A built-package format for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248"}, + {file = "wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729"}, +] + +[package.extras] +test = ["pytest (>=6.0.0)", "setuptools (>=65)"] + [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "29d6b40d3dcb183f4218df24f042f514858d0b01517ecc2956beca72bc4e598e" +content-hash = "1efa77d66baa0c745a56be3ebde95805ef6ce36f1ec6f0ce86b7c472a6df34fd" diff --git a/pyproject.toml b/pyproject.toml index 0a60a76..d50216d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ myst-parser = "^4.0.0" sphinx-book-theme = "^1.1.3" sphinx-copybutton = "^0.5.2" sphinxcontrib-mermaid = "^1.0.0" +sphinx-togglebutton = "^0.3.2" [build-system] requires = ["poetry-core"] From 675c110a6126f2b416c5d1ea64e26f3dabbbcf65 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 28 Nov 2024 17:32:03 +0900 Subject: [PATCH 165/249] feat: add get_entity_position method and fix typo in get_blockades method --- adf_core_python/core/agent/info/world_info.py | 43 ++++++++++++++++--- .../action/default_extend_action_clear.py | 8 ++-- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/adf_core_python/core/agent/info/world_info.py b/adf_core_python/core/agent/info/world_info.py index 5bd6676..c447d7b 100644 --- a/adf_core_python/core/agent/info/world_info.py +++ b/adf_core_python/core/agent/info/world_info.py @@ -3,6 +3,7 @@ from rcrs_core.entities.area import Area from rcrs_core.entities.blockade import Blockade from rcrs_core.entities.entity import Entity +from rcrs_core.entities.human import Human from rcrs_core.worldmodel.changeSet import ChangeSet from rcrs_core.worldmodel.entityID import EntityID from rcrs_core.worldmodel.worldmodel import WorldModel @@ -145,6 +146,36 @@ def get_distance(self, entity_id1: EntityID, entity_id2: EntityID) -> float: return distance + def get_entity_position(self, entity_id: EntityID) -> EntityID: + """ + Get the entity position + + Parameters + ---------- + entity_id : EntityID + Entity ID + + Returns + ------- + EntityID + Entity position + + Raises + ------ + ValueError + If the entity is invalid + """ + entity = self.get_entity(entity_id) + if entity is None: + raise ValueError(f"Invalid entity: entity_id={entity_id}, entity={entity}") + if isinstance(entity, Area): + return entity.get_id() + if isinstance(entity, Human): + return entity.get_position() + if isinstance(entity, Blockade): + return entity.get_position() + raise ValueError(f"Invalid entity type: entity_id={entity_id}, entity={entity}") + def get_change_set(self) -> ChangeSet: """ Get the change set @@ -156,7 +187,7 @@ def get_change_set(self) -> ChangeSet: """ return self._change_set - def get_bloackades(self, area: Area) -> set[Blockade]: + def get_blockades(self, area: Area) -> set[Blockade]: """ Get the blockades in the area @@ -165,12 +196,12 @@ def get_bloackades(self, area: Area) -> set[Blockade]: ChangeSet Blockade """ - bloakcades = set() + blockades = set() for blockade_entity_id in area.get_blockades(): - bloackde_entity = self.get_entity(blockade_entity_id) - if isinstance(bloackde_entity, Blockade): - bloakcades.add(cast(Blockade, bloackde_entity)) - return bloakcades + blockades_entity = self.get_entity(blockade_entity_id) + if isinstance(blockades_entity, Blockade): + blockades.add(cast(Blockade, blockades_entity)) + return blockades def add_entity(self, entity: Entity) -> None: """ diff --git a/adf_core_python/implement/action/default_extend_action_clear.py b/adf_core_python/implement/action/default_extend_action_clear.py index d24c408..39aa859 100644 --- a/adf_core_python/implement/action/default_extend_action_clear.py +++ b/adf_core_python/implement/action/default_extend_action_clear.py @@ -432,7 +432,7 @@ def _get_intersect_edge_action( start_x = int(agent_x + vector[0]) start_y = int(agent_y + vector[1]) - for blockade in self.world_info.get_bloackades(road): + for blockade in self.world_info.get_blockades(road): if self._is_intersecting_blockade( start_x, start_y, bp_x, bp_y, blockade ): @@ -563,7 +563,7 @@ def _get_area_clear_action( if road.get_blockades() == []: return None - blockades = set(self.world_info.get_bloackades(road)) + blockades = set(self.world_info.get_blockades(road)) min_distance = sys.float_info.max clear_blockade: Optional[Blockade] = None for blockade in blockades: @@ -662,7 +662,7 @@ def _get_neighbour_position_action( start_x = int(agent_x + vector[0]) start_y = int(agent_y + vector[1]) - for blockade in self.world_info.get_bloackades(road): + for blockade in self.world_info.get_blockades(road): if self._is_intersecting_blockade( start_x, start_y, mid_x, mid_y, blockade ): @@ -715,7 +715,7 @@ def _get_neighbour_position_action( min_point_distance = sys.float_info.max clear_x = 0 clear_y = 0 - for blockade in self.world_info.get_bloackades(road): + for blockade in self.world_info.get_blockades(road): apexes = blockade.get_apexes() for i in range(0, len(apexes) - 2, 2): distance = self._get_distance( From de25e592e228b383d521c0ec0031e560d73e7827 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 29 Nov 2024 13:15:22 +0900 Subject: [PATCH 166/249] docs: update installation instructions and directory structure in quickstart and install tutorials --- docs/source/quickstart/quickstart.md | 29 +++++++++++++------------ docs/source/tutorial/install/install.md | 6 ++--- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/docs/source/quickstart/quickstart.md b/docs/source/quickstart/quickstart.md index 4142999..664dcd4 100644 --- a/docs/source/quickstart/quickstart.md +++ b/docs/source/quickstart/quickstart.md @@ -12,7 +12,7 @@ パッケージをインストールするには、次のコマンドを実行します: ```bash -pip install adf_core_python +pip install git+https://github.com/adf-python/adf-core-python.git ``` ## 新規エージェントの作成 @@ -34,21 +34,22 @@ Creating a new agent team with name: my-agent ```bash . -├── config -│ ├── development.json -│ ├── launcher.yaml -│ └── module.yaml -├── main.py -└── src - └── my-agent - ├── __init__.py - └── module +└── my-agent + ├── config + │ ├── development.json + │ ├── launcher.yaml + │ └── module.yaml + ├── main.py + └── src + └── my-agent ├── __init__.py - └── complex + └── module ├── __init__.py - ├── sample_human_detector.py - ├── sample_road_detector.py - └── sample_search.py + └── complex + ├── __init__.py + ├── sample_human_detector.py + ├── sample_road_detector.py + └── sample_search.py ``` ## エージェントを実行する diff --git a/docs/source/tutorial/install/install.md b/docs/source/tutorial/install/install.md index c27a88f..032e22c 100644 --- a/docs/source/tutorial/install/install.md +++ b/docs/source/tutorial/install/install.md @@ -14,7 +14,7 @@ パッケージをインストールするには、次のコマンドを実行します: ```bash -pip install adf_core_python +pip install git+https://github.com/adf-python/adf-core-python.git ``` ## インストールの確認 @@ -22,7 +22,7 @@ pip install adf_core_python インストールを確認するには、次のコマンドを実行します: ```bash -python -c "import adf_core_python; print(adf_core_python.__version__)" +pip show adf_core_python ``` -パッケージが正しくインストールされている場合、パッケージのバージョン番号が表示されます。 +パッケージが正しくインストールされている場合、パッケージのバージョン番号などが表示されます。 From 4aab8bbbc1beb765795d5bf4c1679112dff75efe Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 29 Nov 2024 16:57:32 +0900 Subject: [PATCH 167/249] fix: correct import statement for GasStation entity --- .../src/team_name/module/complex/sample_road_detector.py | 2 +- .../implement/module/complex/default_police_target_allocator.py | 2 +- .../implement/module/complex/default_road_detector.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py b/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py index 7a560fc..3dd95d8 100644 --- a/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py @@ -1,7 +1,7 @@ from typing import Optional, cast from rcrs_core.entities.building import Building -from rcrs_core.entities.gassStation import GasStation +from rcrs_core.entities.gasStation import GasStation from rcrs_core.entities.refuge import Refuge from rcrs_core.entities.road import Road from rcrs_core.worldmodel.entityID import EntityID diff --git a/adf_core_python/implement/module/complex/default_police_target_allocator.py b/adf_core_python/implement/module/complex/default_police_target_allocator.py index d88e545..00d6702 100644 --- a/adf_core_python/implement/module/complex/default_police_target_allocator.py +++ b/adf_core_python/implement/module/complex/default_police_target_allocator.py @@ -3,7 +3,7 @@ from rcrs_core.entities.building import Building from rcrs_core.entities.entity import Entity -from rcrs_core.entities.gassStation import GasStation +from rcrs_core.entities.gasStation import GasStation from rcrs_core.entities.policeForce import PoliceForce from rcrs_core.entities.refuge import Refuge from rcrs_core.entities.road import Road diff --git a/adf_core_python/implement/module/complex/default_road_detector.py b/adf_core_python/implement/module/complex/default_road_detector.py index adad2d8..5e598b9 100644 --- a/adf_core_python/implement/module/complex/default_road_detector.py +++ b/adf_core_python/implement/module/complex/default_road_detector.py @@ -1,7 +1,7 @@ from typing import Optional, cast from rcrs_core.entities.building import Building -from rcrs_core.entities.gassStation import GasStation +from rcrs_core.entities.gasStation import GasStation from rcrs_core.entities.refuge import Refuge from rcrs_core.entities.road import Road from rcrs_core.worldmodel.entityID import EntityID From 430b04b4cec4952682357a481f2d343dfb126878 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 29 Nov 2024 17:38:59 +0900 Subject: [PATCH 168/249] docs: add instructions for downloading tutorial map and update simulation server command --- docs/source/download/tutorial_map.zip | Bin 0 -> 49348 bytes docs/source/tutorial/environment/environment.md | 9 ++++++++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 docs/source/download/tutorial_map.zip diff --git a/docs/source/download/tutorial_map.zip b/docs/source/download/tutorial_map.zip new file mode 100644 index 0000000000000000000000000000000000000000..429d2feac3cb9dd6b8e5aa6dd17ccfc0cb42dc29 GIT binary patch literal 49348 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 Qs~Ga` +をクリックしてダウンロードしてください。 + +ダウンロードしたファイルを解凍し、中のファイルを `WORKING_DIR/rcrs-server/maps/` の中に移動させてください。 + ## シュミレーションサーバーの動作確認 ```bash cd WORKING_DIR/rcrs-server/scripts -./start-comprun.sh -m ../maps/test/map -c ../maps/test/config +./start-comprun.sh -m ../maps/tutorial_fire_brigade_only/map -c ../maps/tutorial_fire_brigade_only/config ``` 何個かのウィンドウが表示されたら成功です。 From 4f5732e86d9987f43251b841bf83d1bee5294820 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 29 Nov 2024 17:48:37 +0900 Subject: [PATCH 169/249] fix: pytest move to dev dependencies --- poetry.lock | 62 +++++++++++++++++++++++++++++++++++++------------- pyproject.toml | 2 +- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3ab05f3..11eee0e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -751,22 +751,22 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "protobuf" -version = "5.28.3" +version = "5.29.0" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-5.28.3-cp310-abi3-win32.whl", hash = "sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24"}, - {file = "protobuf-5.28.3-cp310-abi3-win_amd64.whl", hash = "sha256:91fba8f445723fcf400fdbe9ca796b19d3b1242cd873907979b9ed71e4afe868"}, - {file = "protobuf-5.28.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a3f6857551e53ce35e60b403b8a27b0295f7d6eb63d10484f12bc6879c715687"}, - {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:3fa2de6b8b29d12c61911505d893afe7320ce7ccba4df913e2971461fa36d584"}, - {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135"}, - {file = "protobuf-5.28.3-cp38-cp38-win32.whl", hash = "sha256:3e6101d095dfd119513cde7259aa703d16c6bbdfae2554dfe5cfdbe94e32d548"}, - {file = "protobuf-5.28.3-cp38-cp38-win_amd64.whl", hash = "sha256:27b246b3723692bf1068d5734ddaf2fccc2cdd6e0c9b47fe099244d80200593b"}, - {file = "protobuf-5.28.3-cp39-cp39-win32.whl", hash = "sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535"}, - {file = "protobuf-5.28.3-cp39-cp39-win_amd64.whl", hash = "sha256:70585a70fc2dd4818c51287ceef5bdba6387f88a578c86d47bb34669b5552c36"}, - {file = "protobuf-5.28.3-py3-none-any.whl", hash = "sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed"}, - {file = "protobuf-5.28.3.tar.gz", hash = "sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b"}, + {file = "protobuf-5.29.0-cp310-abi3-win32.whl", hash = "sha256:ea7fb379b257911c8c020688d455e8f74efd2f734b72dc1ea4b4d7e9fd1326f2"}, + {file = "protobuf-5.29.0-cp310-abi3-win_amd64.whl", hash = "sha256:34a90cf30c908f47f40ebea7811f743d360e202b6f10d40c02529ebd84afc069"}, + {file = "protobuf-5.29.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:c931c61d0cc143a2e756b1e7f8197a508de5365efd40f83c907a9febf36e6b43"}, + {file = "protobuf-5.29.0-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:85286a47caf63b34fa92fdc1fd98b649a8895db595cfa746c5286eeae890a0b1"}, + {file = "protobuf-5.29.0-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:0d10091d6d03537c3f902279fcf11e95372bdd36a79556311da0487455791b20"}, + {file = "protobuf-5.29.0-cp38-cp38-win32.whl", hash = "sha256:0cd67a1e5c2d88930aa767f702773b2d054e29957432d7c6a18f8be02a07719a"}, + {file = "protobuf-5.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:e467f81fdd12ded9655cea3e9b83dc319d93b394ce810b556fb0f421d8613e86"}, + {file = "protobuf-5.29.0-cp39-cp39-win32.whl", hash = "sha256:17d128eebbd5d8aee80300aed7a43a48a25170af3337f6f1333d1fac2c6839ac"}, + {file = "protobuf-5.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:6c3009e22717c6cc9e6594bb11ef9f15f669b19957ad4087214d69e08a213368"}, + {file = "protobuf-5.29.0-py3-none-any.whl", hash = "sha256:88c4af76a73183e21061881360240c0cdd3c39d263b4e8fb570aaf83348d608f"}, + {file = "protobuf-5.29.0.tar.gz", hash = "sha256:445a0c02483869ed8513a585d80020d012c6dc60075f96fa0563a724987b1001"}, ] [[package]] @@ -1453,13 +1453,43 @@ files = [ [[package]] name = "tomli" -version = "2.1.0" +version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, - {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] @@ -1529,4 +1559,4 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "1efa77d66baa0c745a56be3ebde95805ef6ce36f1ec6f0ce86b7c472a6df34fd" +content-hash = "be47c53054f58d6aeac9949cdae16fe7092cc7f36a67a265d5608c52b89661d8" diff --git a/pyproject.toml b/pyproject.toml index d50216d..7b5dd66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ package-mode = true python = "^3.12" rcrs_core = { git = "https://github.com/adf-python/rcrs-core-python" } pyyaml = "^6.0.2" -pytest = "^8.3.2" types-pyyaml = "^6.0.12.20240808" scikit-learn = "^1.5.2" structlog = "^24.4.0" @@ -24,6 +23,7 @@ click = "^8.1.7" [tool.poetry.group.dev.dependencies] taskipy = "^1.12.2" +pytest = "^8.3.2" mypy = "^1.9.0" types-protobuf = "^4.25.0.20240410" ruff = "^0.4.4" From 66f71484dd5385ba329803c5b934f8acaeacbf32 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 29 Nov 2024 20:03:19 +0900 Subject: [PATCH 170/249] feat: add office agents for ambulance, fire, and police --- .../cli/template/config/module.yaml | 18 +-- adf_core_python/core/agent/office/office.py | 103 ++++++++++++++++++ .../core/agent/office/office_ambulance.py | 31 ++++++ .../core/agent/office/office_fire.py | 31 ++++++ .../core/agent/office/office_police.py | 31 ++++++ .../core/launcher/agent_launcher.py | 29 ++--- ...entre.py => connector_ambulance_center.py} | 38 ++++--- .../connect/connector_fire_station.py | 29 +++-- .../connect/connector_police_office.py | 31 ++++-- .../default_tactics_ambulance_center.py | 5 +- .../tactics/default_tactics_fire_station.py | 5 +- .../tactics/default_tactics_police_office.py | 5 +- adf_core_python/launcher.py | 24 ++++ config/launcher.yaml | 6 +- config/module.yaml | 18 +-- 15 files changed, 320 insertions(+), 84 deletions(-) create mode 100644 adf_core_python/core/agent/office/office.py create mode 100644 adf_core_python/core/agent/office/office_ambulance.py create mode 100644 adf_core_python/core/agent/office/office_fire.py create mode 100644 adf_core_python/core/agent/office/office_police.py rename adf_core_python/core/launcher/connect/{connector_ambulance_centre.py => connector_ambulance_center.py} (65%) diff --git a/adf_core_python/cli/template/config/module.yaml b/adf_core_python/cli/template/config/module.yaml index 280129b..efec4c6 100644 --- a/adf_core_python/cli/template/config/module.yaml +++ b/adf_core_python/cli/template/config/module.yaml @@ -22,17 +22,17 @@ DefaultTacticsPoliceForce: CommandExecutorPolice: adf_core_python.implement.centralized.DefaultCommandExecutorPolice CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScoutPolice -# DefaultTacticsAmbulanceCentre: -# TargetAllocator: sample_team.module.complex.SampleAmbulanceTargetAllocator -# CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerAmbulance +DefaultTacticsAmbulanceCenter: + TargetAllocator: adf_core_python.implement.module.complex.default_ambulance_target_allocator.DefaultAmbulanceTargetAllocator + # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerAmbulance -# DefaultTacticsFireStation: -# TargetAllocator: sample_team.module.complex.SampleFireTargetAllocator -# CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerFire +DefaultTacticsFireStation: + TargetAllocator: adf_core_python.implement.module.complex.default_fire_target_allocator.DefaultFireTargetAllocator + # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerFire -# DefaultTacticsPoliceOffice: -# TargetAllocator: sample_team.module.complex.SamplePoliceTargetAllocator -# CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerPolice +DefaultTacticsPoliceOffice: + TargetAllocator: adf_core_python.implement.module.complex.default_police_target_allocator.DefaultPoliceTargetAllocator + # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerPolice SampleSearch: PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning diff --git a/adf_core_python/core/agent/office/office.py b/adf_core_python/core/agent/office/office.py new file mode 100644 index 0000000..4c43c4b --- /dev/null +++ b/adf_core_python/core/agent/office/office.py @@ -0,0 +1,103 @@ +from adf_core_python.core.agent.agent import Agent +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.scenario_info import Mode +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.tactics.tactics_center import TacticsCenter +from adf_core_python.core.logger.logger import get_agent_logger + + +class Office(Agent): + def __init__( + self, + tactics_center: TacticsCenter, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + ) -> None: + super().__init__( + is_precompute, + self.__class__.__qualname__, + is_debug, + team_name, + data_storage_name, + module_config, + develop_data, + ) + self._tactics_center = tactics_center + self._team_name = team_name + self._is_precompute = is_precompute + self._is_debug = is_debug + self._data_storage_name = data_storage_name + self._module_config = module_config + self._develop_data = develop_data + + def post_connect(self) -> None: + super().post_connect() + self.precompute_data: PrecomputeData = PrecomputeData(self._data_storage_name) + + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + + self._module_manager: ModuleManager = ModuleManager( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_config, + self._develop_data, + ) + + self._message_manager.set_channel_subscriber( + self._module_manager.get_channel_subscriber( + "MessageManager.PlatoonChannelSubscriber", + "adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber", + ) + ) + self._message_manager.set_message_coordinator( + self._module_manager.get_message_coordinator( + "MessageManager.PlatoonMessageCoordinator", + "adf_core_python.implement.module.communication.default_message_coordinator.DefaultMessageCoordinator", + ) + ) + + self._tactics_center.initialize( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + self._message_manager, + self._develop_data, + ) + + match self._scenario_info.get_mode(): + case Mode.PRECOMPUTATION: + pass + case Mode.PRECOMPUTED: + pass + case Mode.NON_PRECOMPUTE: + self._tactics_center.prepare( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self.precompute_data, + self._develop_data, + ) + + def think(self) -> None: + self._tactics_center.think( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + self._message_manager, + self._develop_data, + ) diff --git a/adf_core_python/core/agent/office/office_ambulance.py b/adf_core_python/core/agent/office/office_ambulance.py new file mode 100644 index 0000000..fc8b9b7 --- /dev/null +++ b/adf_core_python/core/agent/office/office_ambulance.py @@ -0,0 +1,31 @@ +from rcrs_core.connection.URN import Entity as EntityURN + +from adf_core_python.core.agent.office.office import Office + + +class OfficeAmbulance(Office): + def __init__( + self, + tactics_center, + team_name, + is_precompute, + is_debug, + data_storage_name, + module_config, + develop_data, + ) -> None: + super().__init__( + tactics_center, + team_name, + is_precompute, + is_debug, + data_storage_name, + module_config, + develop_data, + ) + + def post_connect(self) -> None: + super().post_connect() + + def get_requested_entities(self) -> list[EntityURN]: + return [EntityURN.AMBULANCE_CENTRE] diff --git a/adf_core_python/core/agent/office/office_fire.py b/adf_core_python/core/agent/office/office_fire.py new file mode 100644 index 0000000..214734b --- /dev/null +++ b/adf_core_python/core/agent/office/office_fire.py @@ -0,0 +1,31 @@ +from rcrs_core.connection.URN import Entity as EntityURN + +from adf_core_python.core.agent.office.office import Office + + +class OfficeFire(Office): + def __init__( + self, + tactics_center, + team_name, + is_precompute, + is_debug, + data_storage_name, + module_config, + develop_data, + ) -> None: + super().__init__( + tactics_center, + team_name, + is_precompute, + is_debug, + data_storage_name, + module_config, + develop_data, + ) + + def post_connect(self) -> None: + super().post_connect() + + def get_requested_entities(self) -> list[EntityURN]: + return [EntityURN.FIRE_STATION] diff --git a/adf_core_python/core/agent/office/office_police.py b/adf_core_python/core/agent/office/office_police.py new file mode 100644 index 0000000..61ce7b8 --- /dev/null +++ b/adf_core_python/core/agent/office/office_police.py @@ -0,0 +1,31 @@ +from rcrs_core.connection.URN import Entity as EntityURN + +from adf_core_python.core.agent.office.office import Office + + +class OfficePolice(Office): + def __init__( + self, + tactics_center, + team_name, + is_precompute, + is_debug, + data_storage_name, + module_config, + develop_data, + ) -> None: + super().__init__( + tactics_center, + team_name, + is_precompute, + is_debug, + data_storage_name, + module_config, + develop_data, + ) + + def post_connect(self) -> None: + super().post_connect() + + def get_requested_entities(self) -> list[EntityURN]: + return [EntityURN.POLICE_OFFICE] diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index e7884fb..9b275f6 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -4,31 +4,26 @@ from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey - -# from rcrs_core.connection.componentLauncher import ComponentLauncher from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector - -# from adf_core_python.core.launcher.connect.connector_ambulance_centre import ( -# ConnectorAmbulanceCentre, -# ) +from adf_core_python.core.launcher.connect.connector_ambulance_center import ( + ConnectorAmbulanceCenter, +) from adf_core_python.core.launcher.connect.connector_ambulance_team import ( ConnectorAmbulanceTeam, ) from adf_core_python.core.launcher.connect.connector_fire_brigade import ( ConnectorFireBrigade, ) - -# from adf_core_python.core.launcher.connect.connector_fire_station import ( -# ConnectorFireStation, -# ) +from adf_core_python.core.launcher.connect.connector_fire_station import ( + ConnectorFireStation, +) from adf_core_python.core.launcher.connect.connector_police_force import ( ConnectorPoliceForce, ) - -# from adf_core_python.core.launcher.connect.connector_police_office import ( -# ConnectorPoliceOffice, -# ) +from adf_core_python.core.launcher.connect.connector_police_office import ( + ConnectorPoliceOffice, +) from adf_core_python.core.logger.logger import get_logger @@ -53,11 +48,11 @@ def init_connector(self) -> None: ) self.connectors.append(ConnectorAmbulanceTeam()) - # self.connectors.append(ConnectorAmbulanceCentre()) + self.connectors.append(ConnectorAmbulanceCenter()) self.connectors.append(ConnectorFireBrigade()) - # self.connectors.append(ConnectorFireStation()) + self.connectors.append(ConnectorFireStation()) self.connectors.append(ConnectorPoliceForce()) - # self.connectors.append(ConnectorPoliceOffice()) + self.connectors.append(ConnectorPoliceOffice()) def launch(self) -> None: host: str = self.config.get_value(ConfigKey.KEY_KERNEL_HOST, "localhost") diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py b/adf_core_python/core/launcher/connect/connector_ambulance_center.py similarity index 65% rename from adf_core_python/core/launcher/connect/connector_ambulance_centre.py rename to adf_core_python/core/launcher/connect/connector_ambulance_center.py index 8004fb9..98e02d8 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_centre.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_center.py @@ -1,10 +1,12 @@ import threading -from rcrs_core.agents.ambulanceCenterAgent import AmbulanceCenterAgent - from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.office.office_ambulance import OfficeAmbulance from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.component.tactics.tactics_ambulance_center import ( + TacticsAmbulanceCenter, +) from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher @@ -12,7 +14,7 @@ from adf_core_python.core.logger.logger import get_logger -class ConnectorAmbulanceCentre(Connector): +class ConnectorAmbulanceCenter(Connector): def __init__(self) -> None: super().__init__() self.logger = get_logger(__name__) @@ -30,22 +32,21 @@ def connect( threads: list[threading.Thread] = [] for _ in range(count): - # tactics_ambulance_centre: TacticsAmbulanceCentre - if loader.get_tactics_ambulance_center() is not None: + if loader.get_tactics_ambulance_center() is None: self.logger.error("Cannot load ambulance centre tactics") - # tactics_ambulance_centre = loader.get_tactics_ambulance_centre() - else: - # tactics_ambulance_centre = DummyTacticsAmbulanceCentre() - pass - module_config: ModuleConfig = ModuleConfig( # noqa: F841 + tactics_ambulance_center: TacticsAmbulanceCenter = ( + loader.get_tactics_ambulance_center() + ) + + module_config: ModuleConfig = ModuleConfig( config.get_value( ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, ModuleConfig.DEFAULT_CONFIG_FILE_NAME, ) ) - develop_data: DevelopData = DevelopData( # noqa: F841 + develop_data: DevelopData = DevelopData( config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), config.get_value( ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME @@ -53,18 +54,23 @@ def connect( ) request_id: int = component_launcher.generate_request_id() - # TODO: component_launcher.generate_request_ID can cause race condition thread = threading.Thread( target=component_launcher.connect, args=( - AmbulanceCenterAgent( + OfficeAmbulance( + tactics_ambulance_center, + "ambulance_center", config.get_value(ConfigKey.KEY_PRECOMPUTE, False), - ), # type: ignore + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + "test", + module_config, + develop_data, + ), request_id, ), - name=f"AmbulanceCentreAgent-{request_id}", + name=f"AmbulanceCenterAgent-{request_id}", ) threads.append(thread) - self.logger.info("Connected ambulance centre (count: %d)" % count) + self.logger.info("Connected ambulance center (count: %d)" % count) return threads diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/adf_core_python/core/launcher/connect/connector_fire_station.py index cd5632e..f9f4abc 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_station.py +++ b/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -1,10 +1,12 @@ import threading -from rcrs_core.agents.fireStationAgent import FireStationAgent - from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.office.office_fire import OfficeFire from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.component.tactics.tactics_fire_station import ( + TacticsFireStation, +) from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher @@ -30,22 +32,19 @@ def connect( threads: list[threading.Thread] = [] for _ in range(count): - # tactics_fire_station: TacticsFireStation - if loader.get_tactics_fire_station() is not None: + if loader.get_tactics_fire_station() is None: self.logger.error("Cannot load fire station tactics") - # tactics_fire_station = loader.get_tactics_fire_station() - else: - # tactics_fire_station = DummyTacticsFireStation() - pass - module_config: ModuleConfig = ModuleConfig( # noqa: F841 + tactics_fire_station: TacticsFireStation = loader.get_tactics_fire_station() + + module_config: ModuleConfig = ModuleConfig( config.get_value( ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, ModuleConfig.DEFAULT_CONFIG_FILE_NAME, ) ) - develop_data: DevelopData = DevelopData( # noqa: F841 + develop_data: DevelopData = DevelopData( config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), config.get_value( ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME @@ -56,9 +55,15 @@ def connect( thread = threading.Thread( target=component_launcher.connect, args=( - FireStationAgent( + OfficeFire( + tactics_fire_station, + "fire_station", config.get_value(ConfigKey.KEY_PRECOMPUTE, False), - ), # type: ignore + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + "test", + module_config, + develop_data, + ), request_id, ), name=f"FireStationAgent-{request_id}", diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py index b20da2e..01601fc 100644 --- a/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -1,10 +1,12 @@ import threading -from rcrs_core.agents.policeOfficeAgent import PoliceOfficeAgent - from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.office.office_police import OfficePolice from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.component.tactics.tactics_police_office import ( + TacticsPoliceOffice, +) from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher @@ -30,22 +32,21 @@ def connect( threads: list[threading.Thread] = [] for _ in range(count): - # tactics_police_office: TacticsPoliceOffice - if loader.get_tactics_police_office() is not None: + if loader.get_tactics_police_office() is None: self.logger.error("Cannot load police office tactics") - # tactics_police_office = loader.get_tactics_police_office() - else: - # tactics_police_office = DummyTacticsPoliceOffice() - pass - module_config: ModuleConfig = ModuleConfig( # noqa: F841 + tactics_police_office: TacticsPoliceOffice = ( + loader.get_tactics_police_office() + ) + + module_config: ModuleConfig = ModuleConfig( config.get_value( ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, ModuleConfig.DEFAULT_CONFIG_FILE_NAME, ) ) - develop_data: DevelopData = DevelopData( # noqa: F841 + develop_data: DevelopData = DevelopData( config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), config.get_value( ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME @@ -56,9 +57,15 @@ def connect( thread = threading.Thread( target=component_launcher.connect, args=( - PoliceOfficeAgent( + OfficePolice( + tactics_police_office, + "police_office", config.get_value(ConfigKey.KEY_PRECOMPUTE, False), - ), # type: ignore + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + "test", + module_config, + develop_data, + ), request_id, ), name=f"PoliceOfficeAgent-{request_id}", diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py b/adf_core_python/implement/tactics/default_tactics_ambulance_center.py index 7fbb294..909f448 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_center.py @@ -32,7 +32,7 @@ def initialize( TargetAllocator, module_manager.get_module( "DefaultTacticsAmbulanceCenter.TargetAllocator", - "adf_core_python.implement.module.complex.DefaultTargetAllocator", + "adf_core_python.implement.module.complex.default_ambulance_target_allocator.DefaultAmbulanceTargetAllocator", ), ) self.register_module(self._allocator) @@ -70,4 +70,5 @@ def think( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + # TODO: implement + pass diff --git a/adf_core_python/implement/tactics/default_tactics_fire_station.py b/adf_core_python/implement/tactics/default_tactics_fire_station.py index 41f5ccc..63693d7 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_station.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_station.py @@ -32,7 +32,7 @@ def initialize( TargetAllocator, module_manager.get_module( "DefaultTacticsFireStation.TargetAllocator", - "adf_core_python.implement.module.complex.DefaultFireTargetAllocator", + "adf_core_python.implement.module.complex.default_fire_target_allocator.DefaultFireTargetAllocator", ), ) self.register_module(self._allocator) @@ -70,4 +70,5 @@ def think( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + # TODO: implement + pass diff --git a/adf_core_python/implement/tactics/default_tactics_police_office.py b/adf_core_python/implement/tactics/default_tactics_police_office.py index bf06a37..f2d54c9 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_office.py +++ b/adf_core_python/implement/tactics/default_tactics_police_office.py @@ -32,7 +32,7 @@ def initialize( TargetAllocator, module_manager.get_module( "DefaultTacticsPoliceOffice.TargetAllocator", - "adf_core_python.implement.module.complex.DefaultPoliceTargetAllocator", + "adf_core_python.implement.module.complex.default_police_target_allocator.DefaultPoliceTargetAllocator", ), ) self.register_module(self._allocator) @@ -70,4 +70,5 @@ def think( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - raise NotImplementedError + # TODO: implement + pass diff --git a/adf_core_python/launcher.py b/adf_core_python/launcher.py index a7105ca..e523a1f 100644 --- a/adf_core_python/launcher.py +++ b/adf_core_python/launcher.py @@ -51,6 +51,27 @@ def __init__( help="number of policeforce agents(Default: all policeforce)", metavar="", ) + parser.add_argument( + "-ac", + "--ambulancecenter", + type=int, + help="number of ambulance center agents(Default: all ambulance center)", + metavar="", + ) + parser.add_argument( + "-fs", + "--firestation", + type=int, + help="number of fire station agents(Default: all fire station)", + metavar="", + ) + parser.add_argument( + "-po", + "--policeoffice", + type=int, + help="number of police office agents(Default: all police office)", + metavar="", + ) parser.add_argument( "--precompute", type=bool, @@ -66,6 +87,9 @@ def __init__( ConfigKey.KEY_AMBULANCE_TEAM_COUNT: args.ambulanceteam, ConfigKey.KEY_FIRE_BRIGADE_COUNT: args.firebrigade, ConfigKey.KEY_POLICE_FORCE_COUNT: args.policeforce, + ConfigKey.KEY_AMBULANCE_CENTRE_COUNT: args.ambulancecenter, + ConfigKey.KEY_FIRE_STATION_COUNT: args.firestation, + ConfigKey.KEY_POLICE_OFFICE_COUNT: args.policeoffice, ConfigKey.KEY_PRECOMPUTE: args.precompute, ConfigKey.KEY_DEBUG_FLAG: args.debug, } diff --git a/config/launcher.yaml b/config/launcher.yaml index 07de1ca..1b6bdfd 100644 --- a/config/launcher.yaml +++ b/config/launcher.yaml @@ -28,8 +28,8 @@ adf: count: 100 office: ambulance: - count: -1 + count: 5 fire: - count: -1 + count: 5 police: - count: -1 + count: 5 diff --git a/config/module.yaml b/config/module.yaml index 2697130..75c15a0 100644 --- a/config/module.yaml +++ b/config/module.yaml @@ -22,17 +22,17 @@ DefaultTacticsPoliceForce: CommandExecutorPolice: adf_core_python.implement.centralized.DefaultCommandExecutorPolice CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScoutPolice -# DefaultTacticsAmbulanceCentre: -# TargetAllocator: sample_team.module.complex.SampleAmbulanceTargetAllocator -# CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerAmbulance +DefaultTacticsAmbulanceCenter: + TargetAllocator: adf_core_python.implement.module.complex.default_ambulance_target_allocator.DefaultAmbulanceTargetAllocator + # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerAmbulance -# DefaultTacticsFireStation: -# TargetAllocator: sample_team.module.complex.SampleFireTargetAllocator -# CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerFire +DefaultTacticsFireStation: + TargetAllocator: adf_core_python.implement.module.complex.default_fire_target_allocator.DefaultFireTargetAllocator + # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerFire -# DefaultTacticsPoliceOffice: -# TargetAllocator: sample_team.module.complex.SamplePoliceTargetAllocator -# CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerPolice +DefaultTacticsPoliceOffice: + TargetAllocator: adf_core_python.implement.module.complex.default_police_target_allocator.DefaultPoliceTargetAllocator + # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerPolice DefaultSearch: PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning From 59704ee1cfc24391416204f25d28aba8be7112d3 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 29 Nov 2024 20:21:06 +0900 Subject: [PATCH 171/249] feat: update office agents to include type hints and refactor precompute method --- .../core/agent/office/office_ambulance.py | 21 +++++++++++-------- .../core/agent/office/office_fire.py | 21 +++++++++++-------- .../core/agent/office/office_police.py | 21 +++++++++++-------- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/adf_core_python/core/agent/office/office_ambulance.py b/adf_core_python/core/agent/office/office_ambulance.py index fc8b9b7..4076f67 100644 --- a/adf_core_python/core/agent/office/office_ambulance.py +++ b/adf_core_python/core/agent/office/office_ambulance.py @@ -1,18 +1,21 @@ from rcrs_core.connection.URN import Entity as EntityURN +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.office.office import Office +from adf_core_python.core.component.tactics.tactics_center import TacticsCenter class OfficeAmbulance(Office): def __init__( self, - tactics_center, - team_name, - is_precompute, - is_debug, - data_storage_name, - module_config, - develop_data, + tactics_center: TacticsCenter, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, ) -> None: super().__init__( tactics_center, @@ -24,8 +27,8 @@ def __init__( develop_data, ) - def post_connect(self) -> None: - super().post_connect() + def precompute(self) -> None: + pass def get_requested_entities(self) -> list[EntityURN]: return [EntityURN.AMBULANCE_CENTRE] diff --git a/adf_core_python/core/agent/office/office_fire.py b/adf_core_python/core/agent/office/office_fire.py index 214734b..bcd1946 100644 --- a/adf_core_python/core/agent/office/office_fire.py +++ b/adf_core_python/core/agent/office/office_fire.py @@ -1,18 +1,21 @@ from rcrs_core.connection.URN import Entity as EntityURN +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.office.office import Office +from adf_core_python.core.component.tactics.tactics_center import TacticsCenter class OfficeFire(Office): def __init__( self, - tactics_center, - team_name, - is_precompute, - is_debug, - data_storage_name, - module_config, - develop_data, + tactics_center: TacticsCenter, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, ) -> None: super().__init__( tactics_center, @@ -24,8 +27,8 @@ def __init__( develop_data, ) - def post_connect(self) -> None: - super().post_connect() + def precompute(self) -> None: + pass def get_requested_entities(self) -> list[EntityURN]: return [EntityURN.FIRE_STATION] diff --git a/adf_core_python/core/agent/office/office_police.py b/adf_core_python/core/agent/office/office_police.py index 61ce7b8..ccfcbca 100644 --- a/adf_core_python/core/agent/office/office_police.py +++ b/adf_core_python/core/agent/office/office_police.py @@ -1,18 +1,21 @@ from rcrs_core.connection.URN import Entity as EntityURN +from adf_core_python.core.agent.config.module_config import ModuleConfig +from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.office.office import Office +from adf_core_python.core.component.tactics.tactics_center import TacticsCenter class OfficePolice(Office): def __init__( self, - tactics_center, - team_name, - is_precompute, - is_debug, - data_storage_name, - module_config, - develop_data, + tactics_center: TacticsCenter, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, ) -> None: super().__init__( tactics_center, @@ -24,8 +27,8 @@ def __init__( develop_data, ) - def post_connect(self) -> None: - super().post_connect() + def precompute(self) -> None: + pass def get_requested_entities(self) -> list[EntityURN]: return [EntityURN.POLICE_OFFICE] From b7e4678454f666994005c01a4e660c91c7da3497 Mon Sep 17 00:00:00 2001 From: harrki Date: Fri, 29 Nov 2024 20:51:29 +0900 Subject: [PATCH 172/249] fix: Add some information for docs --- docs/source/images/launch_server.png | Bin 0 -> 1462569 bytes docs/source/index.rst | 31 ++++++++--- .../source/install/environment/environment.md | 50 ++++++++++++++++++ .../install/environment/linux/install.md | 49 +++++++++++++++++ .../source/install/environment/mac/install.md | 21 ++++++++ .../environment/windows/install.md | 0 .../{tutorial => install}/install/install.md | 0 docs/source/tutorial/agent/agent.md | 2 +- .../tutorial/environment/environment.md | 32 ++--------- 9 files changed, 149 insertions(+), 36 deletions(-) create mode 100644 docs/source/images/launch_server.png create mode 100644 docs/source/install/environment/environment.md create mode 100644 docs/source/install/environment/linux/install.md create mode 100644 docs/source/install/environment/mac/install.md rename docs/source/{tutorial => install}/environment/windows/install.md (100%) rename docs/source/{tutorial => install}/install/install.md (100%) diff --git a/docs/source/images/launch_server.png b/docs/source/images/launch_server.png new file mode 100644 index 0000000000000000000000000000000000000000..2b60ab3354a244b7253fa86f7ac4558ba437ba08 GIT binary patch literal 1462569 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%0D_hrCLa>(8 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

_OZR04lWzoJ%?C$|OM-?(~f$ta1q|4L% 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 n^fsjKTGu>LN`%LY_nLhTi?@V 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

Q+3$EEZe!Wb)xYu zzB&F5*XgG|=ssdXc)AM|uHr#!*dm zk)V{rwG3F(C0MBA65$+x>!(?yx7sNJt^tE#BLw|Zt|2y2W#(JuY$q8*PW557ylcsu zMZ;Zs{CEMSOM^tHYr;sWq6nV;+y>){XU5lsj-aD}9wa*)ewBS0uEaby5=2>OTs+ z)&DbIBB&yc*GI)z*0K+MhP7Sn?i2}O@=Er&+Xmbx4G7%ldii}jC&5jmv8%^Ph=XZ1 zA5mbqo$5I4!PVpTs_1vk_D;e-%btvyoN>OUU%n#NRqqE4R!t38LPSkIB=%XqDx4Te z=eh!X1Ir8a88aqoruR z5bIU;Y|AqmT4PNIz3Zo23kj}%Z?AA&?Z>-n8UM1IK$-QNu_ks*9qL|&z~G!injyKv|!ogwH!=iK&yF%cS#UETypzwN9Gte zsP7KG`6AHr&3M3Ucr5xot7u*Tv2>;v+K7Luvlzon=ck~c2@r|(b;L_2`krrWMI;GI z+p^;a)NZ@S!5fZk;TU5Rxk?3>N#kr5{{>1qWw$rf%yAecp+FyQ+9nuAa($PFv2(+; z?BrwI9#E|10>Kg=F)pl6S7f#?pkpI!b{5<-&EI#6o0 zUfM{pZf2)uk@fi2?qaH36U`*TeAUpn>a3(bCdD9e-8y>!?&}0^z=e+Oe?{EWyYdiD zOj?XX1e445E%NKYz=2xGwQS*ziN)#Ts;lAn-%+(Z7@tSp!`iEn8gr$RSJ-Hl+Q=l666b0@psUX?)|D-1Ts+%dF{07giF6K}E zqxJfl;NxUVCYS9{U=(vw?gWH+J6&ZY=Hg;NqdI8QT1vn85tec=uEFcw z?B*5L{F5Kz`sohk{@F6ce;e)1cy*(DJ|G{WJgNWS0xc7DNq0WxnB>OTc|v^IYWAtm zuGb}!y(prK`S#ea>a+h*@2E+GMV76OZsduD!u#?%u!%~-yjT0!TAA>Z?d&>$xl;=U zG9$08F9-u#D-!!*Cs$!O>yqoiS$`w9^X=caLK!!RmlFVC`6Y->be1HjeNN)uFdBTu z%Q>IrrPswfpN(Ht!ciokRy01m1A?H3V8;8K!`$>?F9q+lS5uzpb-u(B>kh^lyH@3P zmP0EzK}$_9R6kL%M1^cvtbPxr#hxm#gA*h{#x`-r6T05!B;O}?;0=+s_8yl;cZ}SAkDNSLBiR=ZwGQe ztQHbfdlZP7FZ);n_9)_Gegi@?_#V@~b?~1|8RF5bV@nPPL^=s9CbFaI^T8pTO zazt@{s5uB;MeB!4>7}lVNT~%=X=cX3yaF0B=uBB>c`mYMvoE_am$gH`9!lgHEdaE= zW~q{aSGdomdMz+9{J-J^@nCle5b!25XyUV`zy0zG_?0$KHcdf$s;AxajWWAY0Bo+s zzmTDb)^9HFj{s_e6W0;|6J~L{MHO2#n0@*M*6g8sn5dg z8BtS^Ro>Y}aW=-0Ix6V2;xkIAaoTL|3bn@{7CSIXTG~K5i@2{tHV{SKTVB9MWBdjE-!Jw2g?qo~HX*rJ`+M^{ES2<&Zcps49DNk!@g}-r?-Y2|Ao^3Ve=I(1>Ms<}QtlmpG>3@}TOU?Lywz|m`!%yuq3JEM>DsE5 zDwbId-b+kJjcuFb*NehE&?39<4eCCZ76%Fr_b`fk_+HQSG+i{`K5bq(ulwrOm*xGW zX&Ky&I=aifSb~$D%7Za*Qsn&!B|lE#T}m_`3iU!Z~g6s%fy|8EJ>@7=6yNh!?L~{-I-d7uM zxd@@5c-nqKt{YLirHb6k#vDy6E^TwpLPq2VeG&@3mXZgJY^s^%GlR=xN?jWqkgDkb z-s^2VCvy6xuJ~0R4_O~4K)X%YmbZ6iZ4P9yj1bv>HuU@zK>zhYD+tCEa-GVLg7MQo zOX`?PXfPwr3;-01MrCuep|=^HKl6O*_pJz@Py>1-#kbMp9(UZ*Hf|k>n?5&vYbC1X zVAzQ-edA*Gt@PE1vR$t5u;LsA-Pm$wT zAAy>8O#i4ido~%aOX$BR-n4~f#Fof1>ptN973eRZU>Y-(_vp>+?!}*{2#us}q)z;f zhW<^&Qq@c!iZmpz8|IQGG71!XWc>JgkEqKdeX%gHr|jUYU2QqluIUq2n?4^%GQX-i zlun^x*wS}!o9DQ5zHYHaOYs4+SYTOxif3D1yB|AnYH(Dk)|?@?dqc5;MBe!Nw+%>C z)6hKy^5e=&Jp|tso&qA%Clcl9p$}C_?ybopBWQ!IQftbGhSoT-X1iwOr{U|%^b;60 zB7FZajQ{mae?6~o|Gt>0i+|CG&sll4Q8zt~BN8Q3F^=w6mY17`H(2fIrsC{2QQKQ? z81Eb$W3zlO|LC@cwA>9kfduhmcR>Pp&faVd;c*lvR-Xv@xSywLSZKUI)BV!sxL#3+=#R!n8`91Sj)$UL>r*{4CfIuGz174!_rle*nyf z(%E%?QE$R7^QO%MCmbt{Wcr&`@zF;07csp?W%ZyKPocw<-AAb&G4Y#gC+0Zu+jdlb zfu}CXeiq&{b^AVSr+M3_eO}u?eu2+nYa|c)?#76;o(Le$a!MOq1|ojg5_KHGhNYwZFl%QG62PsG!)Z-D+)# z32_TQiP;FFP<$xaLJFq&GQWY|=ETp_Zg7*&aQPK~cMuhHA<^){d{~X)u5^3M5YO=w z9u_{HIfk36PIi=@PWPmiv#VCYm@I_Py?GzPSeUi;zFH{}3yU0QuC;{{@n`>urcB>n z16TE!MqS2A`Y5eVLLd_0-yHbn+vIS@z(i+8X-+xngp8N(%v>oyDb07#+~rK3YCp}r zpqY~OEbvg3w9s=Ws_T?Ity~!^@+hLayD{ zu<3M3f2i{Jf1N`fq+Gxi6x>PXp*j6gH)bHE$9Z0zot>a$9P8i*a(FUNDWTpIRaCfN zuRq<~tm%DzFlzp;qeiSSQfw ze7(6ZR$JvEJs`7?O|`CitXvhx$_3oys9c&QAr9|_QNzov4)rk$0}v?| zjv6mwXxguwcWU|hJ7H24iJ46uI z!y#WU70U;F_^Hz2B+8bBD)2r1+gI$TCIzvK9wk7U_v?+|aXL-x`*cYPO8=7iSU$0Q zyu|Bd4jH{uVn(ANeO~uDVn337pd7Mm#?a>mqT6~(L z?-|zQ{%wnfB1Hi~RC-qs=|v%QK|!UdD7{LT-U%HMLFt0@qN1RJbm>((0Ya}rP)dLR zp@onD;k>A@)_<>i*FDeK=YHGY-Y1`yYchXx%rVE9lTO(j=Z&>aWq)esuU2qS{Hmxm ztj(JH$DNef+1cgwnKwST?V6Y_?`%pr3y>p7Rqv#U{bv&TXqsleeMZm)`o1@rPjEtO zY$rEzHjEnA?vK+EVpxLu(LHZe6&h4J5UE?=my%A^^~1r+nV>mr?aZ9d0}`Xm)^Fyx z#C-WQW3GOv;?=EasZ`DEr2;yti0 z#@E%JWTu%2z&*C4GmW{&+eU5FOjDG@1`?c&xL@XDs+a93)K4WUDqyY^afj)}TCSwr zvIM(8{UmykE(Z*KzK*P4keZr!i5|I02T{&C+SES%p4$1EyCgG}Fi}w9!dAikKboyZ zQL^`$*O1Ap!)T$OqCyCCnM`4>o3U}c5Rt7VI}7E?&gvw0*XqNwY@q~_I*XK)4xD&1 zV$V|o+PjwR#_z%}v!P3g3Jb|(mY}A>?n;6#Hm)vZGG%sTjE|emIw3m9K|`%!N%POEG^zeL-d*wD?1u&{K6OVKL|YORTh~#~jCW z2r7(2X(YM9b8|t-y23{H_qVr)bUuNl4}dm^_OsNVKNN+^_ zlYdZ5u23wH^-cruK9c)Q4u>3&4)?A_)49a;ii?+hmCSXZB^0(6cu5`NLZsY*X@L7o;|p_g$j7CCiy z;!>1+cT+phAWP5-J0>8xR5j-RnNugf;BK4JD7^rB#oE&4(v$M``B!<%%Bss}mGjeG zU0J~u)Y~j9?to2#G6FOnLj^dApwAimJgJ zRDQXOCEP7?g^YQ%M67tD2yc2*4h;T8MK!H0k6#~CZyFlr2Fld)f4w%b{rY$3u;=47 z>~zB5K4L$@m-@K8EWFNFnBvU!v!EWfc&8^gVTFnqd0_dLXjCu+WV7YC(E44v(>yM| zwZ@^?*?`QinU2UhGl(I8H*!V(QwwDyIhgy@+#`ouPJBu?q%1u4AZq72$O&oK_~P4d z5@oRqFYR_{pULXBTJI7e%o;oW12xT}on1P+4D2`@RQBLL<|~VYg9ju__?xxrls;fT z*{12$+gACoxlaoKd6rer)@iLR$UtO$zX}+dZ3OW98c+MyVeTqIt2EX|Tcwe90|x#T zX?^$b!yh@xG)nv4*@~pGt z{M_H}V}#z~S7N1TR1yQhYZ`P`*QQHE&3=B6b87Cc^?cwkVIue;0)O2?1-=#c!q>nN zSnY7}Cks_rk~FT``?f4MetxZCPKj7XS~^?bsd=xLv%Z{D*rV@a1lE-~ZCa&cou3Wh*SoC0;2V%reKDk7z>amC+<&66TCotcecYLCFA1K9oumIvQ_>H8~iQY zI~kKiAou+6oi^GU45zH`WA`hCus{e+8V-FhuiXm{r67S+$o%X(sBI%hMO6rZDUWgw zp-5utFMsmpT)ZrNp`ou-ard2lCPHMkWct(e1wfCYdjad11KZjc|Gju3uLoh93G@qmKW zL2iM_`C3M^0y6}AwkH!pWP@(W1t*_-dl;5!B9GZD3_OSujgNgQ&uw(N1yN^Z`wd&bD=rrt6b+e~l z{^=2E{yp1QsiR@t@bRv&h;JGw@E{Ku3p}n;lH{%TfH8TkEstpq49a~RroE7 zsRXw~y8$kLr?kEY)2|e;nXd>W$vQ!Xa=g_iBlu($P~4fG&)l9yde8h8jcknOU?r9^ z7lyW~xUF3DxS2jCoyoR;|GnP7hS3p~x?X|eJ5+|WRN4eemOTm*F#Y!RZeEjfN7J=( zNs)sdafz01D}01-Z+EMd>%6wt9G+jdPG#c!-g-)(*`dhOS@x#B;24y!fyf{<8NaDQ zBVNBK52-sXL0szVEF0OYJX|cW?(6M+?_jri_D+gVbSumfWSsum^%G42$_jD``O@^^F6?`d_^E8PuP`MmLuPALidYaV(Jx^> zRYBj|Qyo@;Ti?{bc-D~w-|7jj?sSi{tro-^(Mi<%#E$;l&G~cV~wl`FHy1d@#9R_fGn* z0;Uujb;qjoZkL*5#NC?8&4b$lCb&AS28>Cy(CWvX+=nHZ?cl9z3@P=w>NdQnT%hUZ z1)AVv4poOM#owhB7=89BIWhHOs?M`~uI)ly4+&UjoX7Aa zMUVYNJyTFv#pgX=-(hFTGI`ql?l;I){Xkv*q)m>M4+|GPG^>A*Zn7_1?xm%fk>$wZ zOA0NbS%PgtB6PvYmDN9z{5nIt2|W_f*s(r*zD#hazmjSC8fbre$;-G`tZX-ypz_qe zZWvq?2!=dAN5W0y?BrA^b^O+I77-F(#MbQ~ww*|+Mz5*JUR zLO&EsdUs1JB*9v%J2j`5s2YFlL`?Ld= z;l4>kS-p$G&vvK;!0c2!Pn9zQ7SvD%No|@KKQ1wRrY)%{PyPu6(F7n!x0uvdC)RK$A`&4qm7iDdx~$-I0s3 z07iK=SAsWLfc`8tp1`}G=Qj4_DP#{K30+H2`Po+#4)y*i$rJlrp)D>JTSDQYN7r7f z5hplX;#eRiF#8b+;E-~nbpaDB-3_6r`Z+cVwKt`o+ozKRkV+kHymOW&4?42X+V~_y z1(b5GCz|9g9XU+z8&|U&q%Qk?8jph^zsgvbrwQkg#mg_Fe^igqIatZJ_mWetf9qnK zBm|CU;0t_kH%VsLbB_C)fAU+vFUgv}0pk|<>Twebplkk!qlS8MeFe0?{v-Q|mvuW` zzXot^>C|-zdpeWR8FSVfJA;FU+cESoH%B*??m|_CW(=0(BBF-0#*ALe#oJXP3_OIy(C9NwqB@M95YLQ_6B6)m%ctK&8srQ&@wG z=10}k%lDvGYtMB`jxV5VUD59Tq)2i1dJ4jMX#jk@;U`)b&AdK*%$wzDf_kn}Rz_Zj zfzK&oP8;#fifY#J>+4NXAVYcBQsj|PyXdzs)F&U4&NP`;OiaQH>#NZhWSs?iYRuok z-y3*}6AAh-KF&JT(zG$pSu;eJ%5v~+o2h2!-MG5vwM??0WJsN01drM!87Ceb-GZ3} z#Ux?s+sefkMJS87urKi zDARy;QY=Gs-$mWnWIrvmRy~g=_Tt8s?P9P{+hc$LllUnsB6{z}+By@1$eKPR0s}ZO zPXnie#>%?E(x>8W*o=#rLU=of$+TJ9ulU{1$jsDsnyxK=LEJ71BK^Ay^MlK!D2*sS z{l~%AbMiDjawE+59|r1x1yEnsIuY=B3veEH3bplpJ^lhDszY-M)2du4vvuIH?a(jc z&YM;5WK_+R2T$5$`HJIml=yb5A?Y%#;82biUK$Zy%5v-VF~2-~iJCFtlO~z&#Yznk z{uuD49z^s@06+4Qz{M)cjVNSyJc74-c(NTN^L_fu8XR8eSzcjO27Ue8e%Ukvb090& zGg`JQj&Mm1P%&mTcMgC0wqZVbtd-7O(4$rGTX?|!Y%@h|?& zx@nx_so|U5-(lI%-*Uyz)z6THnfamo{(4>M#y5c@E`&h+)=9Mf`QfAbJNy1C8+laB zs)&S)@yya7)Ee$PL3jPL?>%%Z@?ZTf`CcTlzDqPAy&-bQ?uoICa}u&!a$%JQ(YEgs zNsvy)feGq~oKNqDABv2#pI*2+3$6<0x-d6KV#52KvEm9J9p5v9JE_H;B*k6c$pN2) zxbMVjfGHXQ&EqF!Yf+G!`N>f;%3wJLmb>vvX&VKqDFsa0n{soqtEz#ea3fi6nBGA= zug%5RSQme9m4M}X7~5M{LsA0&hJuiH%Qi}H{8YYvGH2=H$FT@NJP$B;uy_;qbfh!v zn)08LZKKGJ{><;j`(BUO))3aEWSEoNp`F+x1%5>*K$#KQ2pMU*hIrQPhUjIX{o(mJ z@Nh88kZp2;(Qu>*oTE=Tq4)m2kO%vEYe5ZJX!x^YB~;ETRqgz0b}_I-4f>{`RhI_8 zc>3T*78Awu;qmcLZnqDFt~HgwB75H7wquEMTFE=dg^jZJGAonqq6q{BC5nI*eB@Sz6Ej!?31;Cyg{w4_E*jHL~g>h2vpLfXAh`iD&^zn6aNKc%+IGd99*YCBywg$<>UB|yM&^vN9g1^!S0Xdu$&)Gd+#RXESuz#&1V8>59AR59V;#- z>_*7h8~IIek@T8cy$zOFVqYV=o6a;Fu9mg}sx{;DE)8)~BU6~+nR^D3ibaS&Uu3&b z^i(qb9H*0-X!sq11T&HwYTk{8_O!J5)BJ(1u0Lssflh7cq*K%W6}M|P7naWl7o^!q zoyxmmeOK#V-A4*V55_In-g@T#&_SmBw+N@cz zgM*gmYpD1^Zz6LcvaXW9ZGNwO$vbISX!uxyjQ{-WzqRzy;Zl{?>6N3At#u+QeQwkH8T5_ z6-|`PC%+4app=tCUa+Ldg%x{2C*(_wo|)^IZdaTUV)K8uCVXkIfthQycuTn5UV1Yu znSSZ*g@MazO=uTUD} zzy&@*F0Q4!-Hg%H5*kf;4yU6P&N2O#xC(e5h4+;iJw(&kp@wT9`}uq`=o~HeSdSO< zj26MW0MAQTEgo&Usb*q~M+!m+MY7ZT5zxqQ>)O&9siX|owgM$11hpf2B zQ8~=Dw#SASQ$prGWm=l=0>4@J0sL_~ClZLD4v=|Kf^voP{>F3Df_8=uIM-|bACCCU z#vU5o5nmTQuD(4_J&Q+$zuuI_YFdTtt{`gVE3;j8n{WJwx7>_j?T}@Q6m5AcX}vap z;V&UtZ+>E0v-?USRQ6_dwQt$NuV@rl-jGH+`d6f>KC}YHE;fWYn_~ zYHmDl>h@#^wW9}ItjIHz>zuczr82c{Zz^**vw>>;@LJUC}Z{+@z z+c#hW29{JYq|b}ZOwYOoG~T0MR}W=k0#gmS=g%BLe+!8n-KgEy@Yn!g$p$CJLvQxU zV3LPlfw&b7b@1Ass0{x{RA_ETy>tnayTCW|PLt!~@F3D&6!oY*ztEU__JI-JWm>Ft%iM--1)hlkKkQDz9-lAquds%A4+$Yz@S zSu=EpDUMZ|%E=)GOdnE+<{Mn(T77qBA+D0_e&!DO&@H0v3pYf!>zbFta^b)PxN-X! z$)2c2{{G=c&j0vI9D%o>vgKdcKllR;fG#K<@U0P9d36W=X{653y|^vXzN!9U3H4H* zH;C+wv)zjkG1s?-W6}|DZY&R@tjpro;$Z=%RCpJ-HWU!tsA-{gPM9Dog)p9+Py|a5 zEdMM373$wI7yM$~Y4m(COmd>sbnh&YBp6)$=*jfh`w&_nGOzwiWVBuY z@(@J6aWk;>Gf?|OaHwP`lINalK}AuxSy^UaMlTdm5dpJ?IMG#QK^b+@>hlM;-WLND zfqN1lREU5w7Ef0|IrDse*Qb#eAlwCxQzd<+j}g;8-}$-UTVvIPpxGf+x2+kh=6fWU zPaoKo)D)n9wP0H)M>KHewh)IyI&rH%`)EBb(1*V?L1ji_^%_1Fl{6e6Bq)sFq_Q$` zt6g+V=s7KDFER5L^%KeYjz>TBnmA*WfWwRw%PN+h*V@)`<--Gg04W%MhUfCAp}bTle8lc|knKI7u9wEG(Q#!M!Xk zD_cxX7zVH$u_r9YtDs0Kfu>Jm--x@c`OhSBcMY^W$gu{q$j2yjRvAxn=JfmEJ-}IL z3a6kekf5>A*i{il-23Wi%u(1z9Iy1AS27{*U6c=h-<6Spbh{opbIa4*&c( zxO~JTb^tsg>fjOVq*=rL;t_xGQGUA}3j}YrG<)jTzFZTKLwni8W^Q=f@n|vJ-tgYE zKWBT$$z~beLyeVzngn7e-2sL-;)Umz9*i`_J+}_rGPLA_wYMO1ni6;^1GpgtMFp%p9IdJ$Zb2>O09e3RyH40rmsm<(U&$#EK^Wd=hWAgbQ*;l$GDQ6o$Yt85L zB+)4DXDUA1rm{yCJI-whwk9>DXgundN0Tg5SuH`1ch#^bDfG{N8K?GVyjlM@8#%n})1E~QbeHeQmO%al1iV1m=-g6}NM2^QRIxZ(h%RAP9 zEOJMH4)5vd`M}jJq`;%`tK?k8ozYs&dQjo(9;09hhp?F_O7ZLBQexzjfr7-wzSUbzy$!Xv_z`z7 zB`pCeWpbWmCvrA$y~^`15K)CNg8nY>OwMLJ$~uX$cIYqZ&XSPHK`VlQf`n*vs((Xd z<6N@ly~8}1fI>D;l!9zGsRDurcF~O}pQ@L<`A6S1-1>Sdcx>RhFi0y_YGvlNC6S0; z6Lg~8{V515_|bhpxXtWlprUwU{3-iK8yi>Dnzh3{okh3UXo;!LQgu`K{c4y0b7RRP zF{y-ETV+D9-rWF)kh|4I5)=$8QxtmD$Mo9=Wwh1Q#a#NSDNm4$XG?EU#L~(g#(L)C>cYSO?M>1LGc3>ND#luBfV2 zzzW!Dxg!)=H_c%#*G+NPG+v5Z!oZfX+ z|J)FXU_OwCp7;RG0Uw~rA?&a&{KB`++a%mH!nQNB!(DgMGzh~lv}*0c#HHvwMEcp0 zkI_!tbkEMDke@Dd@$-9FCoNN!5fqabh~LV3`1YhH>5KiHi-x%=UpVwF3u!(w5lp7q z-i=qC^m#HhutY(48R3(7i}E2_G|$zz-hf_*E~{x1-&12fU%B0zE?11kTrA$w)lgkl zBX*1qs&6jUt2n?X%GB&;7RFRANjDg4|FtjR2K%+Vxwuhga^5nR4BM7@h%B3;k?auE zDn$+Es;9Z2Udk^uo+0h^IU}Pm&leEtPfvTgP!S0d5EXfZh>iC4ja;#F&AQc(xgFyv zGR3)^;wiCYex6cESfBNvk-giomPm{^#@@`hYk*bRWI!*SE>2`m!zcAQI2V?p3=@^4 zL%tfmXU3i5)!yDd{KJesNX#THo1)R-dk@{rWDyiIq-zAyvzDI@&_>IT) z8;PhSLAk*Bx-_qbE|H}Su>Vh}*N9Bw;43NG;54o`sCD}hhlYmBFu{5RlFJd-TbMNb z*P^6$;^nqS;{1wjE-aiA3zmk78U%uc$azsm?MyYkt$2x^TDad<6holXqrJ1NkD7lp z{#??raS>b}zS>zP*|yf$zqW9R4*i_$HcNsv4_v^LTDeB7StwXEq^WVBEeDYzXZ`fY z!blENNUoIWqF>_hW}v;#&fOIv0`z<(}BWpI=E8PS^D%(Du_{*|` zx|P0ec6_y!Ipe+oLQY)`^D)#o88-sg8_rqs>!T+41woV3W@-FUg16wj&q$Zvq% z<$P^&OFxbF1#8vECjQ?CdQ-qN+B3;l6tWGqq7?%0)AbOoj0~1XY;u0<-hX#g7d-)} z4;j(YYrd?B88zZ_W!FX$o+p)~`F(5s3mPm8qb*r_V(0fhM8Kx<_jW${K8k*^mycQ4 zwiSZwWjzK9d=R6l1QX7C>B8Zr@SPfi&6S_N^?3K!D8IVwW%m=~Z3G}V<~=2L*XV*u zrW%(CB*Ruu=OA@URDSeR(%V8yybM5zjEqDx1hGA=@N}wDYg?{QwiUZ+z)PkKH;e8gADk(5+5zv~V`G|N=e)FFrWifm zZh^kfGY?qAJ_B9IqnL)nLa2+izj`##9K8Kd`_G=>LCz~7ND*0okf;zM9_+5XT}MRy_8!)Cj9&uK2|R-@J+Pu8R} zrZ$o6DQkcqP{3!9V!vfdzDc_m_!JEZV#cF!9-L&dVs3$3aS%H!7Uq zxQE-4>tXu)@3+PbZEXN5zxc_& z+4ufrU&%FpPDjB&+g@9{65bvm2V_-M9H#KYD;Mxn50P8IvsoZ#H-C`o71-snR0sC| z2E_~=KbstHP$>IehlUzp$X1VV8&Zd3^fdlzN5+XCsGU*gqbQrJJ>SgzPFL1s*;LP5 zR$#Wo&lu4Clv3#HCfoHS*i|!1gU&v$874Bo(rpzmd8>0Z`$fzkt{c;?0B#XeKJTiZ zLaRl6Yo?OoMh+GblFX$2>*ot+zk8vhhJ!jo9975!V)#_WZcw|4D01kV*tlCsDeslm z($Sew;N}n_gBym@pk~eEb1xIr zzl$Ej_=L;_34DgPRa?jA!MEL--SpQO+vz&q_7A|@ z*;&w#6`s-Vs{g>6(^(9-L6*@|wdw_+&Q>o!{}v#E=n|=uYC-l-tDM*+Cw?pGFL{~9 z@`Z@Ya!8m=3M>OGa58pfLh*;syXVe~Ds{o9ly zNFzAYJTqPd*38aK%O2i-KC&u_N|=M4n=sv1Cd!m#`cl?>=bbb6*+?EoHogXQb^cEq zZQfY3VDxv%XPM%<210#sMXvwx#{FOR z#{KRRI$B0nAiJ-sR7-mi$&PoGx)DR@%PHQnSvRUZB>!YnHa8L0fVFYok9{My~oRaa3xwZ8YGRZ+d(?~o|2#|tj$ zXxV$|Wc=|#^);nS#QBuz63MV4fiSG9Q~us!vr1n4D;VOjp4U=Dxr{y*`|J6{V6U zH#2SEzSo@u;fxYBBbnZvPioNfI3NC!dSg~xt^RyuCxx11c-!E2rnc#HidgJN2dUto z7esm{YUheN$rAls8EJ_%H1N5&^&dn%S5C}5;+OtBlEv5j*ri_8iLd!Z6Xc4w&r!?g z_U0?KvpZiff1uMNtXFSdjbna|v6%M1(%xD(xL5kQ%d1}|+F6S_IPKEC055bMCGHPE zncZTr!xg|>|5}^pr}kgWY>Zw1V`lsBnA!dW?OU0zVD*E~jx)A)-*|gO(8XWBlp2Ti zhBW3%#+yHijGvc&UhIJeUqyA+`reCHM`HcC>xn}BPl2#CKh%L zQ8g%Z#wWYMEmDS{V++#MiGEK}EfW#iz8!2k#T59JCFm1-sDHHa+t<0lkr?t*^dU=q z7On*9MVpluHX6Z#=3Oz$)@yQd6~m2pdhbv&f5Oa-Q8b2;T7l#MMmkvYZ<}9jnU`vg zIy{P__p$fF6?GZ)%>Ds-x8a%8A;=?^muCsH9?QXkCg`5e`{v}qG$BF@8$tl6%mwU2 zSd9WVz$TyJkAcFeO5J*5R+sl=eW$l&|Fgc`mbDuZar+-z+y8xA+k4b6E~NZfsa0*R);ljU?+bp zrC*7AQH+PAFik0LV)B_sJ z{_pgpKTU1iw8Kz>%-=X(pF3AI(p!tw+7kx5QK`U|7PH?FdK`Qr&YigZQT#E9m}v;6PCf zmco{~ZBkmVf3n4Ifzm$p#TaEH_($+JvV&(fk~|HaYeag+z_6EZU4_H+7JBoqmM-}mS=|#(NA-7r#Vtkpe{666 zmu+tY9;MAP03Ds_AyFu>&%S-d+;)*SYkZTT^X=<}_D#J`KW3;D(pJhz8Hi}y*96Sv z*&09otPDYUq05JmT4?c?J?Sz(c=zn+JVM*wK%EfRYwT~bwKuuVl7Z^UnJ-xD+Me6O@ZB8b0ZIRznq$TRy}iZW&BBy0bS@>UnWz@nn$fDX zEV3cwb$FSLb7p*;Vc3Uh+Uw%R*9*a)Ldtc!lV-=ZjM#2TDai!EzzC|x3KkyEea&q%hEAD2gJuH$ONKu%nE7w%ra81g4VPG`W zZAwlbbe4(=FmzoaDx&Q_b~%Xi++$R!cJxfPcE8eIy0-9jLGxzd-S@>|tOz#)IW}-= z9)-yK)+_8*GJ4@_^eVZbhttQ6&wGRQU*{9k(cj(Cdc0LIbE9a7DfnA7MvX0Ncs_vk zp)VaU#`Q%Tb)cLEF>dt|X^Az}JW$ondaE;A(;F$8$04Q;<_-R|8=86H8{H>JW_u*` zl~x^JSHh@Xw0UBWjA3qO;u5;7I}nhw(e6I4CtCkvKp*r8`V1^KYlYcfnlIO*}U7Ynqha;`@4oI(2tT`_&h@|a`wxwE&N zt;D3tVZ@_wrO^odebo5UCrw4)&AG|W%G!u1c|fopsdnn0bN7>9eAJE%2X6tx!7NjL z!3hVto%zdf05G8SLd5lUsZz>QeR~Wt6Mo&KGf$+#f+aUM*-~YRdsY(a9#@b4+#j=p>DxbFa7{%}JZ?Q6xzuDcy!)1f1`@cagC zS~p~~>>giCvMdSqsS@r|?pmF!0GR#)^1!|NO{WBv$cUXVC4*CAgxrRCWsDSI%!cL> zw1aRpHzoe=dd~GI73)OX_*dG4RyJ-qRUF@p4Bxc&lh~1tD!re7TF1XGUGyk?Nht9A zjoAo%{AixDl5h>_X!D}|Ll(gM6Bb4~93wB2neYjryKhJ`)2hdNyp;c+i5jkO_-C+m zh|%Nv3&9Eqk?H5Yn?k2m)$}F-F7B+ufA6wS@H>NC#))e(0sAfr)nQJ5}@w z$lr%rk8b4=Fa(hPXX5cftTEteR71NKg)g-$;FNnI{a@Xr- z7%as*miFq;+v7)D(WZc+n!^%wBj`DUKQeVA%f#jVy_HdNME z%W1*#Hn8<`4MZb4KX2N~#%ge93M5ne=x8<9^X9h)DKpk2Moy!+=EqeDXU&AJRebav zpp=g#y)QHg|4yMKytTTV|G5Q{rQgdJURag6XC2o z=}fL>!@D;n{P3ZJlT*z| z^mOzO38h}1@TmYH{;1X_^J#)=Kfw|8fK3fA5LJcK3RPq@+tM6|eJvi1qPto0sk**)dWdh3&6bQmzSH-X;TtTEiFSUuoF>Wz3{)7`P-SM*@! z2GI^jiCAOFT4O2e^jU|vfb$Is`N-n))rJOjI1JlsufMjO$uD$yFI8ZX<@Kdwz74QE z^u6iJ*t~5k&i_+1Q?i%*Jx8p|C5w7~CBufmqn zsb&jdNr`9|^OuV;mQWn^rr~MGc+&aJMIu0tlnu`(tDPUU#a+(y)J;foRB6bOGI`=T zx8)-MOIhXazWQx0#>c<10DUWM99Hb6NK{+2*$!x@Q4I}wnR0OilHp5Q$0CW710jF= zSokeGTlb6Z;`mEB1LZtYvP;QGgXH~oKTb@IZ`>*y?1TCR_fSr@j$YeMR*5~>7N@Oe{I0$rKS$LFz7gC>=uXy@MIBF&EVHozhLFv* z&!~R5`9Uw)6>Q6aB%^f^2aYqXzj%*WniZ ztglXSw5zgPODIBHTcX+LN2kgH47ZtTZu5Ase2l)&wrjepMZU=uj(_2H}IYf;t<|c%vvDLUt?TLg-I&T5{Or3w$VKjIy~r zNa#?I`zfc#0zEZtd%0;5Z-DW|1EF?hrw(6Np9d_pvL68sZsUXA7?Z>9Tb3XuLM_kZ zd^;Y*pFEt@!N#;l+W?#a1uyPm-qy(|SeEA~G3kT?3jPc!l&xV?f&JU$-SQnq{!`r% zh>?~ZwOooo!}w9ULn{uaLEtXpEt%8)_Pu~!9H8x@yfq^!9>z-VD=SCVXixvF6NIHH z9#%Faip(d1{NX%643#@=zQz!JlM@nFPhBx-4U!& zbv_1)kO{oXM|ej8K747T-8{f6tv+olW4!D;n{Uy6G7#9Zjy>6}<&z6K)WnJ*qG)mg zDB&pY^LY{1-rjgyw7DLSZE=S~xRarEri`Xp!L%F+pj~tHIK&5hEv;L9XH>AQ3dX5` zDar#Jm7J$g4@@GABAd6RH#g}+l+IHeM7a`sZnP$=8Wl$fzXKL)3KJE&H7V5I7sEXN zNdNqCVH;(WahsqB=@#2+lrmS+fii9Odgq3VhqLp&gY{-iFZW%ELx8>cKi>oAMhfgK z0(*j{r_fbI$^PV3X+lx~Pq4Rl_(`J6Vs(etGc{39A|Ey0#`NZoUUjS=;~@c{p&np$&Gwb)*O@Nh&$)rYf%xc zPgXd6?oR<$HPRp7fdR~Re@|0&JBQ4$`ouf&nmdP5K$?oZc9;XwH04*CVrr%l$l6@A zk36E*?|Lr?CReEYz+B( zDDZUZWK`RB`TGHx-VACMB7tW-zb3%^Y-z3n)$)@U#-$7nzwQc8g{h`jJ4rl5~)yQ;}l zHzOlMDs1TR0WJSt_n6wtvXTDXw)(3guizb?bjp+2%o{2yD#M3+?ed>_H0#lJs`eKE zARDS4dIDSj`QdZ-2*{cNK=!{5^N*Gr`QkqVI`bbY(xB4D>%q60+yn`p8!r&erJwpK zgt(-;^M$~T>@mJ62uo~Cv_=1zl>p-JWme!Y9mEtwgZJO!kCx85a_>`+65jKCd z^T`jhxt=?x67FosODXi)B-?RQMouX(XGqIQ6;Q!h9$M$B42PX%t4;fVeSX#ddVU`> zkR)%8ca=+e9{6gZ>_%Zwly_YNw6YM5OUq1x-Au{~d4-fooF4o6lIakeh<1x)335tV$Gn-Co+jsjZI)I$joi?ampGsp{8G3m z-Pv5EYvU!!JodEOI)FLGKFLItEtQ78j*^m!s|Y;;Q<`jkZzsK@|B&qETi_<*zMeK; zyZp+ZHF*&o0R!I zWeVU?SvXVF@06nsWO&hq+VLg{ZesRzT31C7ONSey(ss;g-5cq5$m1lq8Cu?hx`ad6 zxn)cR%U_UdJ0;44!5id+088}b1RL@FCpOZzLn~~T-AnZ=*@KEUCct#cliCXc152{} zB@^LjpR6R0K%@h|tFhc!qx!?kFbD;-xXIiIo-2V4(3?xqVx_r4o&X>AILJ&itnB@r zu|jEyX~SWb?Up8`84%P?ckc21iSWh*h0{h1wABxdTSde_Q%^iVbonuh$*RWj43^?+ zxiv?EM{bQ%SXR||p(d8=kEZAtBX62zYJ}l?^A9H4iyo_#mOU*p(ma|r9&ZcZ7CSg5 zH+gY&H?>5oWi^l0SgvM`3w{IaWw^=Eq!{4nA^oc*ljyjeG2rN7ISW=vEpof>^wPPU z8Jr{o7mgh_eFTzN0}oATzXDh`76FT{9_1O3X#BgDL0-@LC$ ziQzL7aJ`3r*7Ww?{AYyv2B&aDPmmkqlT+uFd{~e{307f$ew!N%g@gcP_qB2xMI-{&!lBk*@(Yph4VeS#Fw>SL7z{H5rCfk1T3B89N;ACYWnWV=TLMerEUhp}7#)iaYoh%9_kL8C(PMfS&@78*MNF-cE*iVzox=oJNbM z3Ti$IGiTqzea+ZJAf}xAlhr{#BOEbVG=G^~W zCAW_V#9MFObu4Q6&Cy=VpPCU^x~cV{6Ty9ieRN0%&?v3nP<9WEpPoA?sDMHshc$jM z)#qE4ugwicF-u8)4GFNcf?IC@4UX-dO9fEW>Wbl(dCk5?E=C=W+)~MB{)6F*0*Lzx z$#`P{+D;lXGHVsbAq%zk(PL%lz9j_}=5uPhud`?pKjrbo1OPq78G&ATfqNCc!@xaW zFqEJ1DsAQik}Ooh3RyORjV1N=L;7b z4^qG_M!o=5Mw-708a3}!yTm>^#!c4pEV$o7_&$7T=>@PNaEPig=;5E%DL4^WQg$02 zMK$j|GF&vW>9XihJg_JR8}ptb63D74!50J;cHup-)#B2_dp?7hjB=gnL*R@P^&HP@PR z<||#QdnMOVVlLqD*@2g@1hGh{3BZ4W$f~(n60_Ny%8_z*x^uiDAJ^3IM9r9lnKozFgwkshuNR09(47}n?&=7=X4J); zR!vH@1|=N;tP8h^laLN(+$rIcESolUAo#y0Gtc!$MO<&cbyAMuBUH>2S%Nc zy*y4ii`<%U@3_bD+UIb+3!BTSsLP$nt%*;rA1rz;&3V#mQ9WP16?*q;Iz#dkE9h#a ztG04d?qP(D#6v;8>JQ9^cAM5>ho4AjFw;dL08|HrCjdk}!v4ela->~x^wngo=P#Og z0@pUX`lr@mIlCC_<1Bxq`}$A}wYrH~IbOz;;}u9+y*b;sk+J=jWDb56j83~gI%eBX zcKuq?ME1ElFj=#G?zF`(6YP^A7l$%EWecvKVr6hnioqwztfiQDYaGGfGp@%|?wd~P zjYO;h!%`L9YI|B3XQ;Hq+T+!*_UJ@Jx$dx1GB9qY<)v0y&x#&@#2OS~h&-)R!yWC= zWVB)D0O=D^h0SH0g}*=5@Vr7^Ao*V6U6;`mJH--y#IZ;+mu_PBerW2L7TU2vH81IT z3F?K&alu*s>L4?fp!GpJC8I)5KpyMd`J=xffq#GmwEKWv#sAg)Iiz0*LCr2gpr#h! z#1f*G>kBY{01C}^D|~9cXv+8FwaYT=YR3bocb0uxua{rJlfPb8sPbSYu+gOF)W=PsK+2D?!;YiulH>B_9-|*Zj^8Q%!=*r8X7{gAmbWbD>5tN@`;m{7t6i5 z#+MUe9N;v=Mfdoj$Mr;-C*k(hswhu7j;&T*R8+$eT~nrTo@d%d(Pv}_DsX~}c!#I`}1peaky7+AggUtS6LB#41sp-64Byhqnnz{Ka2LJpJB(`_L|L=?^ z+6g~!Ql-Nn^I$X6X&db0uPHS>%f0gFLh4w7@4eVB{p=O?=^=Et9DZ4p-&uwIWM$no`e92 zZ&KnR22RO54k(#9Wc}25alHIHSd$7<0dcfoMN|cHkzApJx9`#hL!HacH5f{B-g31o z&S8)!1yDip%2!M3%iYLbQfNq@YQdRuf3SU!wy}Fuvf4X18Re2X-aA262y$D$iSzso z-OQg0=_~~11zQzvPq5sFcb=7gvR#;IcnSJ>(|Yki{E1RM(n;y7Ll+xgwX$OCX3eBF z0hQVG@yY!bq+n;yFYd4N;mLt?mJ$h7^zMDEwYX~)lQhA+g%KPLcJ@XH%jFgK9B z+;AD7*Hbor_vZf^X-$dzAsgPHl%x7 zIN2=t>aF7;{;_Pqt+$dSC-6MU7kg~=J#ZTr8q@W25BWs> z0)37>?ZtGv`oSKCgnB{3G^CRgwe&Q@+nN*$8vCKP?S@-(Hi1qL{-Q1@lAUUXl%>hu zF=XejP8_lT7&lwv&rgmCH1atTLK3@;P%Eg~Hq3Va6UJ`~5J}g9ZX)}uJ5K*HZV~>! z+{6Fxxrez4`gtR>khmGwL;k=LS)J?B0lRH@Lr!0+Ile`UQs14i^o_}o+z%ym_zzBO z%`U1)9+Ui-!AC&V826(g)3_(CT2{T_M)LRKev-~sc=gz{?P&#CFfl+dNn?NGvfVAY z!If?8c{07N9-7G!LtGAIf-c{dPOv+#(qyL+C)ZLubsuO)vweNh`E!8iaX>)-XN7|m z;Da;|LUX0dW@))!gSfN^A}W2KeZt>6^bMv*>R&uq>j+FJ- zi=)F1A5%O2`M{{|AtqZ7-7hI%Kz9`#WSjFM`7W7&Id$VF72`_EiI80`@IARS)4G1l z`QHBHOO3<~@V8F+yU_&@oO2FIEN$Hcg|IySZhEVxH6VUDnp#Gp7LQ+uybd&?0R?42 zm~l#K8q)wcWHG>>({&~F1)N;&bx^dyFd#W*tm~)w0u7NFta$cV>x+E;;$yNyuPLJwUZrUF*A+|xT2gJBj*7(#%X`Z^O-1b9gu9@hc*?B<7(*Pm+u>VJN|Bn zoi00cab~>gGwVl3nEk5gZF?CWlfIrM{IQoy4_x_AAc~ciX1M$ zY_?dSk9_>pbn?NG1?f{-g@3K4YmAEfoqL)ll6$5#`V)G+A|vsFH#QFoXzE_A%;XE% z+^h+rWGX5t{iPbXI=t|A;PgjbYq9RzO+Sba z*l+0NFK8TsM!Rs0=&P2KmfT!75ZP+pv;(Ut#*jse@@HfUVt{%1&X3~a7NP#f z&C{{%i-w-#UoS4c!N)~I0rs{u#XHge*t+Q?PDBah?g~gQu4+J?snJdMbLYb2WG|P# zd0@4q!u9>dXPE6iE9ZpsueU`?^jqV7(h6Kj&cu$Q2`TK9Pdm+4P-eqExrA-GDD{xA zrj3Bucx;faks$QW_R>=Nu6+OM#tw=P)(zMSGW>6B1>0ah821|%f3p0SBmIgdB4c6s!n{EvTHEp8I!>$Te?EpqvVC73eiaXNlKJivE zcvt|SXDnZE2<(poD~Z817mD7<90f#$79U9=_p;&8$Fj!NHF+bzR1mmGr~$@F zZF^8R>%W)^?y)a&UtiB#Xn~~C@4l0Zr%#5*o>VhLC#_`rR=LUk6`96H%;^09EGJ4_FmE`Yct)#i#`>8Hu$fo`R4zQz4YcC}6>u+I?yrZC}yx+n8#02mLgg5@whmFZ=%URzq)(O^QRs zb;b|v7=9&mzm+4wPm}(-VEPGI>Z_5fT`)-)Qf8VzqzZUA(}9OGKfrXTNs55Er#$H? z-T<_u-HSCck;Yrc04(i^IuejTMoD3sQccmgWw&WuNoLX>vf=Yu{h3p$)1#2+B+F;k zocg&*($GRa{r^it4EcYJhL{Asko^I^-zZhr%zG#+DWYQ0taBR4}n&B~Y+lk^wl;hitQp6QDUOQe<4>?OQ*J z{O}~xLoU1HQO*Vi9nuS@V<+HopGtL*V$BCd^fk zhv*oQEqhn+=DX9IJkZRe=5go!BQAnz=6E0@bAEH~s>c_yH&Xfon0-+e4NgZ1)dK zcC24W$>!Xjxi*%n<&CRoTa+AJqJB3I&W6W&#c>x%8G7=g5N8Kw_YWVLQkYaYMo~Wa z@f_U)Ki%5cfbSqqdK+PEWkUy({jlERy`;CL?90|d^-Sh~I781VrswxTld6E#IFq?Z zh}S6fl2P7YMzQLU7WE0r5N!8g_5Az1dA##nTVFh7QbpRXW;w!~3PaE6)a2zOrAf49 zgi~N1mFhj`1m8g+a5q@PpV#n>Db_%mV70>4)41p#Ehw?L-z;W*V?_!Qv+>UJnL0P~ zRJjH>`PuqO`fv{QI?Vv#aroA?Ku6Q5dt&K&x_RSYy>Aspo{d?%d@bB-c{BT|zEsn= zf;RmEP0~aOxMc(s$$LyD@OM-IY=5n-Uxtd}?WxzSI*a91TB%>{$>65Qa6y((BOWMT znecV{sJCsWs2myZm!X`^{?7ImLC9?am#c!2IUR z{Dm0`DZkDz7g_Ihdl`>=KtT`VU!Q1jVpWq`Ejk#Xr$$}hQy zx;NjGXPUszWcHb@e0})9p?K}N+q;C2gi@H7@EV?5(^I5} zgheuamk?2rr{LaB%*aLdc^SUJS*gtKl)$7KUHQJoyS)m&)*F4q zGnW(>qjbx|MW@(Su0GlY=k_)zy{N`hdE?^Y)3~e8k2v@-d}%2-d_BtRbZR^3#Krk? zEuXZ%ilrd17>)%8qn3cg4TJuIjjDUp4{Ue^P}!V!Oh7z&5Jf0Z-j=m(wH?S@yM&{2 z_3Oe#M#=+)71dl}_I(D132JhQr2S5bi3-;g`o;__$c>C0A<+Ta~iTTym= zC~PpAR4e4W^dZHZ1#tVD3NHt4_3x+jzENJk+4T0_IDT6~F?k=TWL-w1kyu8NSv#TW zpXi7{r^&d!_C@%1SBrsGzZ+Y^zAtYAc7J{d`{G}H>_0yMc7L*Tq6hvdaDHgTWTIR1jelX(g-@?jNQa$%WJ3IB~_JaD&4SE4}u)2HVIL zkb@+~K#=9rxo4t~O7G4c*l5L6X$>^!2P8Xu*>MmL#k6V^LXpIpEc{>z6m5)u5!4+^;VE}Qw2*hs z8ZDQkJ9AWAr(dh7{#tf-@7=??(67IcN%-@|6RhH4rj|3U?G5Wn_)RSXbRU>a-9!6nr`;T<>iDWPJQ+ltr!up_1I@U*Q_^g0u0C*76zppe{*wf__U&m zit7*`|0O~YwY1aXukZ7zpkdaw`~O{sw1oZ!Pb~evT}2)eYMZ3?QPnnkl>MxfK**JQ z$2`Hyi=u&t_pdVbgmeq6tse7W2ORza6%sbopVnxVM|$bkQwkTXh7%!(wOcu?3kdBU>KZa@ov@a;q zmSaGcpkNLH5i=>zVqZSplNa#>C zzS8}}77CLEg=}~v65pV3l!1;wT=$mhglG9NXMwS@=AyM4_qx9j5k8Xia#})|*K%F0 zvfWjU3d*(gnc1;Ph zwNh>)@KF^(Cx6hx@3Z+W7(+z8_$|h*nVSsbYX?RQ%o>k0(C1`!-d`>;n2*~BY}(Mk z4D+azw)T(K5K?Nxk^E|&tG)&4f0-X!EK!h%xP94Wi1GG z`8XZ70I@LHE}7__^|94ttSJq(%1eZe*>@ z2w!ED$7&K`QW2G&mQ}Jr>$l?^FD#bZxxHAJK1&}Y#_p~6Tm!@L;9;w9n;<>S5O9r8 zm zcNY6%V~?*6`@B!8#YOoNE!p(zO2F*rrsO8gVLL`EpCFdDiCs%TC4A8j7Ujx$*7s2ok6)8H zd41qqEHEm-qEy;rQ9cZ6&Co2#3b`iDx(~d+BlW5GE{ifQD%APi^CH0)tvX8!kMj24 z^p(HlkvY`C$pSpJrJ!M3KqY6DrmI^hM5oXu&E%oevN10>TgUW4 zTHK9WCEwQtq}Hp)eVx75dUGk0HLY8@v-qvVyp6svl5#_5P!jYOCCHQ-@VY_KIk51H z?Lf4GEQaK2(uIqlRZu1ul2Y$uhw5@l>*sTFteA#vnynb0|DVNlG?wRS1uwr`t`anp@D*~U9;Roh-BJ?`fm_|T(;jC37p^v@zAcY3RVZ3KK}Dd$^*P3OpEIxK(6 z*Z=Oi+6Rsj2mLD*s0T#viYNe%FmJVlhOs4thK8QExft*u=$?53Lvo1en1h4WNx#PD zdSebF9uYl9V@dOupQKpNc#5hgNBRu_ukzMlAd~rkc5}m%uIkf#4k(#2<;FGZn~Yos zXC$6hU8d_l6k~|ZGZtW)xyd@hHyp9APf35LoH)S0|ZnA6bU1t z^EaSL4%6F+nWV{$jIqiEC45>9<5=y7DuFv`No=T2I7!US(maJrpj^ zJMq!YGy$T8NN=Eu3py;42dfKh+b05AOp}7h3uhC9@!Vd%SeFrY@K>v>Kss9k)B5JK1>yibRso zDNov)htY$re*030p%h*Owv@I=>>&{67 ziChN3zX_KWap#@v;>iJCW*89QPx4DY13c{Cb!8ylNA24vU)nRWXe?Ct$)EKj?@Xs4 z0^)p5o~%`UVi3yK++bCUm0ugsvCv^Rz@P>j)I_nk9k}E1TJB|Xy z>=kQtDQleLyz#0B+h(_LTnxz-=Y|=BWg!pX%yWrRMW4Qo4wjiz0P=zcTnr7ju=I#? zS67nn-k}d>C|26{$zP-N%TFzU@;|iPOKM-T-W#0gO9X$^z;*=LdBHAf1lMo+k8`IsKR)KL_))6yij^cGeCQHKSBe4N5E6FCjtL}S z3Yw3h3SMMnEN`g~^~)|OwXUu9ZJ+6boc-Z-7!G(H28i}VqfDLvH@23uQ)ZubiJiVX z8GORSYikN!(XuxP_del`z?rOlHs>I%wjCStHi3?Q8J0O?A1~HeC}|JAbxs%?L&_pI z7qlsxz4*_WcmHRPx&6K<85tni|HbT(SADN91I!NH1WZ7H3n5)ALrap==F}{hJ`AV6 zy3b=$eWyV>WoAalS~E;C=PZrcnd!1B`102Cxkqb9Uq)DBD5L@mM0hET<3&u}nbB97 zUm~A*+!^*5L5{w+avC2~zwPgu9BnX@Z^T)SdLLKo#SRMe^K+!bHXHy+U7<=oJ*n{89Ik_#mtet2Jx!q#LBEW@1R>0m3!2UmkoxI z83MLJi47HpC@zSseviN6zMF#Go?&Itk*@dZ1C+?he3a2@U)K$&MJ3Rbm04T9D55oG zr)W*twN< z1~^fZ$X>bSSB`qIMRBhO( z@yx_|V|%`d$XCR2Gtw&>HAXbI&PW3(UgFIMwgk=|8ff;2A)@TSTYgx`&HCuya$4#i zIqkj({~GjH_9U^amE@J!ppP$_Bm3;m!IWErgo>;+%cT&cfbsJT0SLNvY+wl(3~;VI zcJ}L1^l}4f5vrx z-HEJsMQe|~y8}E=uB#A^)%rAViPG?lu2NFpTPL?C9x*YFo~5;YwaX*$A}eW@O;(qq zHtS7-Vvd1B$j0b1H`Hm|&xWbM&bK}E7;Wva78pS_v8PkC4%8YzY=3h&7kDZ`yreAlS>pHYl-rR@qTQHzZwU0@tfxhhz2$gsvKnP|!aFrw-<~09h{%q?D z9LTdQsxF$$Hx6&k|Gs7|V^+v6A~RK6i-X%1 zK@HI;n~C{w7CyF*sULAYNl8hv?hz63-m{>C=lOw(ts^AFxGt(b@bS$0@5bBwA32;q z3Pd^Q`y{cvHG!%anNbFzQc-ixg83BXr6N^oNRYs-1mbx$_XJh^B(>sDTBHhhBWH*m zM^72dQ^0Lus8xxr;jMhZpmI=?U0TaE!ji(J`s@-W7y6J`XPdL@FW67|aD^Z^m=1+s z(C$WEyZri@kSW?5;DfdSe9+A&isn0uhOn{t!}`X9na%(oR0lLa@9Pa{LD|dm^(8YS zxKhmeK5Q47NUccCJer&YEEn}}Mrm}0ufSj`La{5W;pjU(FmUD}^lD>mdKVogibWvZ z`?hv|!Sh42xETeL9{j#~zyJ}KjI@}!5yC16;8Gwxv0u&AKR?un%1ER<3_1Ou`|LHG zbo{BE^{mP6JTon4ZTC~dauM^V<^e{iWaQ`CK{Cifm5Y?AY?q}33Mefg1B_7eTGU9- z+HyI@;?a0sm!j%@wOM@eFYSf&AzEg|ovz#VFoo-@RQCVf`}eJr`1yIZU`h zTm!g?BV2&fgttfYQ%YY;N4dFd-}5ECWCPo`is4u>_9vBM_3sEwv_5-^o9?2xPnLW~ zz{RuQgcC33D}1>uG}g>9@$qr#`*80-wm+r5y>mlrTc+#df#m~mfY)y~0Rh)AEoE$4 zSLOOBM6GBaVrO+skFps@ADkGg`U~Com4m-OSjq0?Xi|&5 z=hXk(X3nr zO(BmVn}^Qk%?Oy&KW09&5<6hADF`?ldfdj|3uZmB>RoXG)XGVQ7(5PL1h3vXeVya% za|?ONEBpdiW?m#GB}EzjAyuE%0vuK9?6E{cw3xmC3F6=H+z$&QYz`Eh?@4Vkc+GIK zI20-nNiUANJ(sM9r+ReZbmkoN6xEt&z!uz`25gP(0bA*$_3F813QwPMI9|mzL6tB` zEkBe+N@C1-iapOf4)g3F)WLf`D=Crr`2O}5TTsu@<|NWBp~?8EY=j_~85ot5!EL$1 zpcsx%mmFB}HRkwdrQ!zt@7{FP7Jl|y1oQf#B=*UeJYaVad`$Q(%K0JEDjozk`iUZ3fL0J=m7ttXU&f%QZ6fj zaLl@&|4XR-^{kB4nL`ag9tG}2iqby;lQwVKpj_j(;Jy305a@&3du^$Zi$ScLY&OP( z!_7L)c~58mj68%4%yPN6XXi*1!gc6R1$kT2hXcQ2!gc95%isexVG!wS!}+~t{@FQE zhf;UmU6lP$X_aMlem3o{IeqB4Pc-T%^AWU5Yinz~ae-x=NbOVlVE!E1&y(vU@F?%2 zbIXPk_z@IuOruqpRHQEJ%P*|maAk|WmwG$QZp|`~lpo9DY!GOph3)%gIh#p@LD}aMDtT{ z^q0JF_rS>Wp8OvHNRtj*XU5pFwZWFKWmf*oky~QRMjKfuf-N)eXMZ*a%P3frsux&N zITkPe?(1Tr^gND;Jn+9Q#HCLs3_P0+&4qXVRO?--ACh)Ddg@;MWgS<~)# z_dcfmgb{GhQF+H5+3DKJ)j z%20pw#Y1IvG>sov(1$EF^cS~fq09DnpZRlsW7}R{mmY-Wo_-=2>Uw3 z@j>M;dq{I5(W{HRy>lNO1A6{M1O^Ul5->Y8@RgaI#liDR{6HywsCV_B75LXTD!ZlF z8YsnCa>j$dXJsZ@CI{`*MYg1AM-uw!Vn3HD0RE7H%uG|C1%(2+u3;9h7mfxn1Tqcm zSOu$TWYekbr;4Pw^jgG{C*^L_)7Yi&pKKUm6PX82(}#LEro8>2a!+!A0bZ94N?_lq zVTf9KycW@ctpKwJ5N}4@icgaZYgy~{qySjLH@#gNB32kYu8v`TXBV$eu-47gd!ASP9j#uEk`vz@bckrH|6}&~5{eJs zz8|iy<^VXymoXzFQ+m# z7vfCh>>wW?imhpfCJf`$8j+uJSn3pu5kU5?f@h z$k?*O+3A)8}7JQ}A@k2vM9xvytRZcz*{ ze(gChG&4+MrDNLU8(~*x9F4DM->RWEVsAKI+k|=@qO{K{lrcm}ncdta0-&4r05zJ6 zk!~M|d)H0Ugq}7D(~WW&weMBuGi|A)v{wu#RmXwGtLuYG?{YXcEGdlbg|OPGS1R$t2}6Xs=;QKb&=Fys~sM$C(2FhIZquSCMnPu8e~zRib_tbvKYw;(S-C^ik{4 z33+XViN4rk+V<+rxc3ZsEdBDug8)&_1xg5F0U=C$N3`Bwek&o&FR@ju>E1qcyIZ%p z#JRD;h`4_Ic>i3)zSTKD51DL{4kFXcV95&3mD`$dlxJ(UxF;a_eLn+0K7GDRKJ_l0 zov_ed3d95CQ;pi?ALLU(WVKTgfkQ`?fSeB+?J^Aei~UWKRiy@M#hqCP_QdkMm(m7c17@ z;gDJCXfW0d9q>8!0Q+*9h~|V zp)4NoH@3qwu>s76R-pGDs*xJ4IC~}*g8rvGbYaKCjBYj=VDq*M=KIe<4UZR+Hy-g$% zfd7M7841We1PQf4(ckNOI<+}^?CBlvdI~-C953BWJnsmB!t+U9=3zIv;L8eq%n5)E z!%O~PK{FPbrnm*^E(W!(9{L2E9*(aoXjun#`f_>%i3oKITlOCI!3 zN?`S1Bny4!!La0JQLNO*)RZV#0a%Ax93#uLt)!sfG*k`mc$>UsK!<&_Yfs4~`jQ_# z;a4NxK3hoMdyJ1xwJm@DLU;H8cKXI0U-mD!9mVDi_|0r`=lNu+@(L;un#?$j&Zj_| zjpv^`uUF*ocVs!waahTriD%x_p!T3EvpXZhgUz#TO=|2iE#Z!pq zbT8ao!pV0|x%#2W^3~-IwYA_?Q;cw=>NhEr)!XZox>={>P?TdZTKF0Y4eJct*yP;@ zQu^PJQg!KF>2CmgW6rb#zjqMW7aXz6KMXAs7}*5l1+lZ=BIgCwVcN!oOD#zZDuOSN zRw3w5?N=+;o1vStcM$K|Uq4oj+=eV6xc2YR23+C!*Hbs?#fHaD%XqST5E;P;?sXmM znYu(qv5%ObhEJ+nE)Xf@kSh{OQWM7xF3SW1o^56slW!FG-<|B-2{*^s=3Ue=|Pf<`oU6am)6jMqm-Vpg5T` z%tJ^k_)~zOwyZ!cXesd>q?AaJ+l%3o)Z#|-N*W8u^-X_ZVq(C6 z1LjqTE#QFJb9G-#8Si}I%&6IwPp_Qd-EEE+%c?cnzdIH@=TK^r2&w6`IkuE+5$xoC zQBm5Vv}X9)y~YLLx6|HkO_}*_?DLKS2xm$Z$7#w=Y3Cw+=;XsyQZF9 zq`*{Bhjl#9Q$U`X>Er`P09M{YP~b0n_K~{pYV_FhokF)j(Y+}DMbD2?<=Kt0#q~y3 zdLYQRSqrQIPgXS=>DYqI*{5SxT{qAm8V3d(84P0z1ni3$Ep2p-g?Q@TP*flUjj3ypc_XMVrPCxG_&Z!dD|20S90JW!mx)0{@D zsE>h)>Scj5jw3WIs@m<45VYW$Al$kaR804GY)X|`mpu113}Xs>jxDQ33fg*DSki*q z>`jHbN{UG0Y7!J9r9$6Kvxh)Mfa-d$-1EH}FiQwbv4kIWag(~fOV!ojzWERAy3fO9zSFw`A-58%UF{ge_UNs zQc|qBN*jJwX8WhgTWQl-TikB`LLH*a?0SCp#g~D;_(*WqoAeo>m&rTOBr3(+fKY3P zW{kT&*owlj0gv>A;$$wVN6T_)8Fl{8^1r<23=$k9)Gm-V1tdS7!-Tr_oc)&MqLp$e z#1+2NSNOikXRqWo!RH1$;iYJ86jzAe2_U$=Q47v|v^9eH>oXiBcJf*=#S%+-^1_m6 z2rQqJU6AII?iu0iQ@~F4I_^tT{+z504bxf;KQDR^e^IVu`G$vdeE&X{#b_EAw#KlL zOyj~D=NA<*0REdrz}?Dv;_OVgibm~tW#KjTg)c7IHd)}gZ}0Kpg*?!;R_qDc=&LiK z3+<}(_e-Bj;%7j*p!sqF!eE*u0ggKWN{WeX{M8@*F0wy|o~5`Si~cp%9P+PmH|W%l z>pH)qpyxsu=XfmWH97Nb*GpwI%=Ge{^*OG zCk_T%Ia0jKTBe=cvVpKvSJP8=%;PWA?=AMy`T3E2Iks1*)XQEHbg1r-cb`vS^iW3Y zce#cy_4ge0!}~G~^=jB&PAd_z-Y8%S5f$%ob$ZRd>(ZIwzEU&8=i;!vppk^Pj!!Z) zp^w!0?9&2OUVl&%vkbbAD@jB$D`$$00s1Ecn+ubA%rvTO{{Q(fYY#X)>LAy~AkPG` zRx;u!B}G+@YQ9y@PUHu7y5lNvzRplJkfNI1%KV{1#fnKD)-bI!Dvu=#Ygsiveek(- zR;4$M@91Dg;$(%5BrR~s>${R=C>tJ?D6NHc{J?Rdb7j>2vA|FkZsTGBhK@`> zkM?u5(pnxRhi~ur@ac((y{y98))}c!z4FTz=&6h%y<-;l2sg#;f3U^%PQIM_5sIq;R1mShM8WLOraN??mZ> z%Hp+f5{ggLBB-;>nsd#Xd-+N)Q?%BpssO5631$aQ$=C-XprAQVyurg>fIVeisP@y++&BO*D=vbDQ8U%}|H12w%?&!^O1S4daCzpi)Uae^sF@EQUP~p=}$Bj9FS9{CFb@hv6srsIKq+q@Ji^pgf2j z8evkm$=!Uhiqk(+*61N6&MzK8mn+^1yRrJmh|j_BYl1HV_j#H#D9iMOJ}JU#34kyYf>61DN+@EOK>cf@IlsG)hK!Kiq$djVpX{l*~0j$1p(h&GFv_P zA=x^HXffCzlvcu&s}_<^X<3idRXGweD{`AL|DCnxmE#19EB!S$STs*yKU4dpzU51B z!Yh^SL}IDo(&$1gp}(b0`&Rsjht>C`rVkau^M-G(Bqdv>7WnEds`$Fn64-r(TKlM0m~V9it&OJaJ*)lYO<}J6OPG9(Q;m z{7=U1^;Qgzg~A@;_M1%g+w?u-&(Y%biV2Hhh1;4THLS-clzjQ3h~(uQV*o7Q#gbp; z@5)e^JlxAz;fv)BWxq4_)W2qIB9)?imoR9D5sAA;%UD4u#2C&`=~fQ>_!!Y@TmIa$ z%hdqV?;x(m8x)kR`bh@k;942@UNs{ed!ETXMFL$TIM#lFu zmWS(3hEJhxas>&6qGll3b==>VKf{6mS}t#m>o??m*+#S1qHXxH<^;6)073QV`!%NCZCRj(?$fa#X+4i##MKj+@kBsTLdnbH?# z5##L!tOZ^thodpK;>-}3KAEr3sVb(k3D`^w%mNPGYmKYmt%Id&C8VIJM5d(>8XCoB zYaj@Wzf4!=&j6BGX8_aC{etkfW2ni)!n{uAwt`M?MP3BXh#VZy%XX8MbKosa@R3b%cq1*^Ffof zM$OV=b%3y#uL|1(lHOqn+<1B=I;PqJLXU`%rPGh#@+NViAT)@T)88WZoCk?8&w(&XJ>dblv z9owd8FrIJ!Swh+~m1%kUhYDc~wT>4p_}|tZb=XH@cVm|4Z(Chh@T`mM3}48m50)D} zhZnjs+`+lqQEe|#)7ey}eownYlYeWekeriS%BQ+u{=H0MLrfND_c!+GlZD~R1YNi;pR3^wHeJdl2 zIYLy~fsT2}+GRS8$wefDy~Dg?5o9WF{?uBlUz5C8EsxI?e&Nk$El3#S`)(b|HCg)s z?I%Su6=iG@F4L_~VVj4Vr7HNTvr{~?k+M=2{UuEjTdmxyP^0Hx zXPNnYH##~xCdtU1H2C%m$tKkGe{>p4JBpM7jv|-m{v!Muj1ts$UuooO5!{6iTMkI# zv(g7H@T!{;u`xp5Dk4HRS5;LuZej&i!yiO$lUZW5`75M{4GJkFcVyL-dA0`d0ij84 zUU?A@VAYZHzRDZi3g(#CQI+_!gN~1BKtVWvFnY@Fn^SDC6#!DPvbpZ_1g@w!_7=K( z?eg32vYlVrL+h3PCd*O9*n4W*u@Jm8YnR#K?3EG`p2ca^*8HLV?Bc7QwtB)lP=p`uqhnG3n zSzjsz@Y(K#;wxQRn3N#RzNW^B5D4DxdO`naz<2;?es;1+8snn6v+12f@To{t!M7jp z=s0c$oRDptl$Y;2)dst-L_Sd>z^OC#M;DCuFzq(&<)cyKfx@3{!6=nq;<=HJNU&7E>N|DDU#1|nvbDl^C`ge zjl76olq4)sv!$^Y{dlUxb#kQ)7Y3n0Y*JEm*b{Bu5zYtdKQJ;?F@OQdpav%O=?2G&Nrd*pvlJ5fOI8P#a;5_yZkVO)E#`T=VX~UVKVM3t~L{6 z;2PU(lNZQg@(_5Ny)AmjcXmDm8DGF9ymcTIO(?)RCR_b;Y1jn~nCiXAdOVV8DGqd; ztB+IL9PjYWqC?oG`(f$z+Y)x8sQW$IJz8+?Zv**Kg7G}Q)|~ja3fE47{M-v6!0XH! z7CoJ_Y3la%)KrYulq;Ajyp%fHF2ypx9#NyJ>ib>PFIIR8S~ubYkj#A3a6^K9Y(co$ z@7_!O{d{I^=ag6?UCXWLpIT4-Wc`kNTj?m4fdSU953s8Rn%Om#>O5CW^<2qOae+1Loau)zO3lzdq>P7@GRH?;f}r23&Rjr3&B76gxY0O z(wqZ|`(0?6+6iQ;!Lo(Lq*4^T8z{O1cc{x^`L=VW`@`OcBc#&MY%j{)!p^%Uz1ckI z(x>!>kRhw9`pNjaaN$AvgzZ+SUFWhzi?X{{L|#KQu^&iPQqi<2SeCW~vhR?0RHm)v zr;PYgox2>qd8;~kKAbd+VNN{T=#fuKcX8z|gTlWRu8oFE$Jfqd>4TFmmb)F&IsV-! zZ3~W3`GV)hXB-Uqz{j!a{NjzeIolA_ET1v7ryc*mOENh2Oq%r2W5xroL+p(gr<5eU z?f-;q6ifwjftsi5z^6+n(*Hjo?e4Y757f&)RsT-hhJQ_5WiqkyF9dz`nt)vlwlf*3kth&_ix!wS?MMmEjQ0F?!DODc6+~eZu*{H(tvm^5PaE7aX_j014|P~1KL>gw3yLrw?vF< z`5*d6_;XpzM`{TQ(>RoT_!d`G!4!?wcP5=>H)hz2Q}D5)s+1dK!<$jy^= z(}p-B3;Hmn#e)*&$>VYK<^Vkd?KYj0PIK;(a)ekHE6a1SKt97#B-E^X_{WXXRW$%XZB=b9h73*uoFRQJCV( zkICgm;g7QWq^ETwOag6Qy)Nids2vYp&9RM|Jp>^&Q2HZ%JWH$`R(w*6z>E4WtMI?9 zz`TGOvG=0-Ae(29zhBfeI`RhU!*;jUP7z_bStMy73SuJWx4ov06$z;NP29~#A-5P(c5b2@MIa#*jBfWvJYgL6QX(D4WTwpB zj`QX*9{zCQe?jX7*B{Sg_au?we-`7kSDd^{??!UupwRj6P-AL^6*+Huc}({kvQU+DjmX*WgQ=SS`c(k zyhuCA&W1BReQV@=mCp8kWtwUyX*oHmE~uU^D;x7Y2@g7_u)`T1KrM<0^!KDfTmu z>zvL(ehk0F)UqJk!U9z{%x(LrEaa{-EiqXw_=y%(q!H%<+Jhgiuwp3_O(q;Jouffr zJ!YzSJSA>woL-+HdCvanha$D?>6)`-tO}@%%}k~S@8{!dxvvKZN;Mn-<qfYPPD^fxX`UPw2ca zYyLk%=iRHzudt=Mf2qsffN;Rs5!u*X!qvB$q~t5CAeKm~v?i|{^AqpuhnKX5KrhW2 z*bZS+2Qk7J({0$Nf@@oyWoE@uv(`lQpTw$jIBj3!{^d}wj3U~>!vUnWp?k7EIc~#l zVonAPa{bTsQmrJ-+N>|R#;^n+$XX}}jPj~6^#|$?{k>ugxdL6%AtnvSgaoN21v9Lp z>EV_(zDEcE@R8}A{GEKKr}HjLRAu53C(;n>--qtntP;e@HmeoXqrk%Ft;hoo)RgT{ zT%WWnChMg2uFYz?6|hW*gBJAi~d_| z@gTN^iF*2}yRpT?24U@rF2-WZbA19WYT zL*Hy=4= zM+YrwLg^bm#fR=ISDe2|zaT|3YpW33PnDl|6BFZV$<3*)rNm1j(R097TBp%-W4VK7dBsJN^l@w?{!{TrXtzngQ$eI8q>B_@o$2wTbUrOX}p+@m52VeIKR@P8(&{jC_;(}hg$U6R@N@YO6gS& zu+f(VSGzSm8rh)JtrltcwjFizCd6LJHQRqfJ9o-P;%L{29Jm2IN=b^=9hRrQ5WXKw zvt3J(xoiqcPtL}$(t1B73mZH2ylT@*EVILCa`eWKdjjWEyRf(+mY=Z+tAF-Z@xS7n zxn3?X?b`eb3hzqCt@GLlFuGPfkb3vj?Z&aRiuug4>`FCZ+!5JFu>hFY(RT8rM` zmOix96SyqvPL_ig_+Hy%If6jPESJ}A1;aL6A&{0)OgshgOhVmf`e%+3(@s_ zAHH9u$jYxET6lBd4VRC|wKEIzF(&#mk4_Aa+Pjxz+OaTwYW#{sZ^b!=`eU)!d;0xn zeau;m!Sf+w5mxUl9if364_$gU8snK8PT#%aI?w6i1hokV$_u2WMxIu@xiNdvo!kE- z;3FBh+GC6$Rd@h)ISmbs6qA@2*@l&smQ#Uhl)XBEYKsTyUsxm(OvhFFrI5F*wYf3q@Vadh+@5K;lOL}s!;*FQk!cEMC!fYC`~JV91axd}6NHN;$z7eQgL zEk(jHm2z15A1*?Hs5NPpI3z1D!$&i6olKBYC3r>f3@RjV(r`?tTKn8IA zk#loNk;HOYrkYDWL?p)rG=(V{lQqnYamkxPly@nA(Q_TgW!fUyV((Tp+bpl=k8_sZ zevW&Wt>T-)KC!2@kUt0UCW6y#k6CTFw<(J!n=kuG}}z|U&X2Sb7-;62vt>AAC~=kx!+}U@lls8*#jVl84ks6We($u zipGN0=K6G^9Abpqwt&u2MdbItor3-xj?x=ss&*b|U#kJ6)d4BPYa!nX2|Tj8xgfm0x_;Xx9<_5kl< z_t9%o2kbR+<`%-XO!Cd!C`{ENMn)bPanerdo|C;OTZGK-t!-~}V0O>q^5=>o?=oVQ z_D~pfx60CA{-wcs4Ok?BdCTrA+-|3bMBq@|yVN))5YA%Z1Pq)UPH(2dEm-@F3M3EF zsNFb+6O}RlW9?PmJe_D|#vfV$Ti}+;Iyp`Um_g@P zK_Tmmfc`crGkI)VifrrRj{rJ9rsgK3ZNyRRJX1*NfqX9N(Fa+rCQ?7|iAew#!B#QJ zueDPGlo)qs+hEs%;zU=&q)V*~EDOtrgu*3sWy=-hMA+uc0x$u&m(~3Ub=0rhiCTSk zROo1*tdDFS1s4}q;Gi=#l~1nMtVyt|M5}#}GV`pre=pO&f3o#!WZFWu9}oCFG6DP? z7@3gOyXPW#PwxpDrG67T`>d{U4rb*Vi>(P!2JU02^%YCUc-vwf(aR};auI#v@|YRE zSk%ktOlqo~N3sypkv5vtn(VM`+O0?|YCFXy$fwL0F@Vg$q3`H?;q-L~7+03Mw{dKM zyby2gyzK-f`?CVzxe49gK{pQKLjU|uTP2#mlOJPbZ1k!%LTCq11&|O! zkM(^%dy#vrWFTGGbY?fgu4X6T5v&`yoq1#&<<1gvdwhIf4E!Ppc>cwnr`M@%qX7sf zS-S9t%JIZ|)Sb&IrsqEde+ZtOQ0*DrjLU;@prS)zGZKcEl1hy!{X@!<$ZOoX(T;8P z#UjX17j!kG-m$>#p@^7do>$jv$Bcs+M#V_@EJaOmqwz{iErL(fX0cv}@k}n`MwY=M zZ%UwyqmP-jh2N9*np%XgD6mxZ1=>fCbPhQGx9DJGmc2SfbyZc>O!*(-t=`W}mOg6t z^X-%aeMk}q5mDa%!gU1VzvM3G;+tM`ayxFrR790<=SSPz0c-K-hO^k3YQ)R( zI1arLT!kw{IEW0k$rs!km6}h&`~INqlCd`T{^12KhPNl!*~DOKb~9O#oXJ`hNR&^6 z^xWN7r~AWwTf0N72PP3%Vg>T}5g_8l-r$ ziu*U$o4l01+2>Hk=Xf_fg>$a=+6CUfrOk2D9=~hxI;I|Gq5ED%#?rv)O^8xM0$)=w zgXL{@>;^Epdap9%9Z`8b6H0b5W2`O%*-Y!nGNneSsiu|zvT~b+=05fw!a?wOOr%%Xzvcs3e^ccNGm(G#mTN| z;UD+J?-4y|d#N#Ll5h#2)-U{LD<$h+sP&}Gzu3A|YwV)dxl#c`LeaO2a}F1$Qp}gm z?iVn`HBet>vACehyd~(Nva%k1XR_9tOVB8eiUGO7q5Ggu9*DQI-kZBn0pC#k%2+*` z?q3Aqy1aw%)0aTr0!9;{ew^$xllBRqP+aMoF0Q$T1lYKiiMG- zG63iAzeH5+&Pspnvs&EJE@As+kYT$?j36hmYOhb#g3M&TmUQ?O)t1#jME`zx>AEbY z8PWcoz-7@)@kHh-P{#S`Hg~2U9V9aVE=A@8(vG*Ud4$gs4Jytu^~na5)*!`2_EDn5 zw4HJ>`*TLWXkRtCzenVi7*D(uGHV32Qh5;l$)>Yu#svLHju5(h+t?dBmxwMF0w;xo zjAX!)MB32yEAGQWxz3GOkvGcbtSQQ;EJJ-*P;*o4%1DHjNaE0Ao$(it z>!Nm_n!BXW95S+OBw}moPMe_F7AG(hS8u&qCO9uu~UzpVldNc*G#HI zkwq2cs}8UUziuM{06UiY!`SZv{r7LG&XAe-HGuwyD$`B*Epay`O{SX|Rc=&N?2ITE z0XV|cTmexeQGsTgrXa_TZu0abuz>y6_*@Zjw^VSu_44qCUZ*$M>9Z>cX_0_>u;j$O zk5d;>+E-AQ$9~ElH78WnbHC|%hu`hvN&N=-cSRG{Zl4zps5v?Ysc9OgK42^X)TUc= zbn;pvN5B;;eAfe({?OhK#XJ=o|Cb@k{nvo}FF^;*^(^c;N#TbF-U#azHIMRvHf_De zT?p}+*;ZAY(n&(}OcYpp<>7q>gQTUARC|>hocYfnOq)%5R^?me23jJTr|eQqB_Bc! z_Q%=eW~z&7Jr?DT#&&GdbjsH19H#$lu+e_%^-g>8qAf0VNN|Rh6&$t+AB1d zy(9J6F@S+GCKFTv^C6dO#zf*ej|8#I_DH9&_GW$fwu!30O-1R6!$Y{6#(2Y5@>6d2 z=I9Q(-tJf}JIV^*I0VdL5qsva`HgE=@k{H)HAmraNv6~zj8=m7VMRh3Y-o39r0wZnI3x@-grNnNv^{0MeTL`Eez2~nHx-(31)3eUI z(bB|#GPKHxzY*+YlfZ9qZkN0;{ei5san*!n@7WIP7~~%KvjIq*Ngr%5pBH zT=KKf=8WV%j()%4m3_7@fv>pWfh=E@aHC7HvQbZapBn^WQ;W$25+y$D%U|Sjh}XE# zu~yyfv@juoP0c1zK>+Yzq$y2b@Q{vBEp5)5U=m^`JIaPDnatW$xY|VU8V(eo-WuF; z&W&&-wZBZ{+7T5vo@QvI?p%3sKP!{;<#mn}Vp|?Z;jAC)mLiEl3im)% zRDmM(NkFYihuc$XLI$u+_s4uc=0_p1`MUBuW3_DQGfCME*cAw+qN)^iM?m?93s?Q} z{EvKfxVc!k@OVU%0zINU!pKC;o@Dy0mtzf>de6U>)&(B;BM zFr>NTG4wX?oK&RSB6+lD zjc+)P*W}qV=`{fMY4WVj!`|i}rhe#DLY3dASEv0^iZ?$AXLR8M<<+gJP@Qj|f3n9a zx&-l7$JM3qjLi{PMAfQgLZy2O;VQIy=)B8LUe

=UoQU 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
Vtda@pNvOI1{NSrHk$9KelZBc~ z*z-J0v)ixcyL2IQMFH>i>u+$5=ycT{chC0jMl;&?)Q*MJ{A4Ydv)f^Dg@`tX-6 z(-Orz&}I*AI8aaaajRwoRlF*aZ+LVf{<`5z1UDe?!=O4Cmh!7?)dhapHAr+zm!q~& z@Y!@Wwdm9o`(V|kDNL}1IbcoI;}kafkY}%v>L7=_#m|XJ*78jFGc%&pi$w3mYe!di z3RFlyJeMqyNk!35(l_eSV;8s^#HH9jP&ihtK6k8bzA@AvX0?d4dgZnjTgl)OSwYIr zFlmw4Ei>4;#mqLW%9NKf9;CZ6UxVtD5SPSI9Q{u>Qj5+c#I8Na$@cgUT`SY)V&FR_ z?sWY)6>Fk=b0ryuT<*hmt#n~OZ)&4D&XR}0Jy?1$0t6Qs9Fpnp|2fdx>k9#%st-(= ziLBtF0EnC^LM)|4BW&Gr2%89gh$*4d*#f5J$tmb|tf6f%KziLR=trj36$#AAjc@L0 z*a%CC=E(?YomeosN;ceJF9bZAv5&FvlOip`oHgB*wnn`Jl6K$nKiKsAv0-5{0j3X} z#-AA}T8$7>$L{E@_M@q7Om-eG3!ywYUR1u978g_UFW)ko3P0MaOGA09gqrQeM^h+= zfzkO+mC=|9qXb=i;C`(g$U_^9OS7%sQY(zn|NE$r-xL ziZ6)^z)arDCJgw@-=DK-OX112U+B2DIF9*)h1B)Y+7la9YYtG{Go;lbFCT&~zUb~C zko{cdI@mot0rZ^%0o2yBEKjxk0A$>w@n_2MJ z8HZQ>EBTK&zwY*M4$$;z0X`(l`x=?{DR=e3dT7{zHovqQAANl8t{_a-By}13)jK%v z!0f>I;0f{XsW`zH5m&Q4u9j~ADgfrj>d3I_^mNJM5Iz6W!$CfeHyfX*-F~Nu*8G~6 z9&om|udT3Xs;qci$5)^GfZxewAXMG%?$25CmG5GeetTt{9@l22p0&JqS&er7hd7yc z=Sc>X9e(}WhQV;9bZvG8tmJ%x)*P1#zw)l6Ux8jM?XCQoSFJ`nA*V0jAlOjKvlvKA zUEFvHntYsot#EClMu{EG63J#61_ZL7Fou15@HvK5$pK^CFe(!IF zC_ckF3Ok?&m!{r+@3o|pRRE_+&)-o-KSg~lt1Xugb?-GYi0 z2#K+>{B@}o_*%d3tx99R05jMiQT|_3Osx6o~@n| z98i^59NU1Z$IOv;>JE%5{jZeWy!~I&?-lBa*I&@h7V)R_D|e1O z!uqsqCb1Xd=3@2V2CCN3Mm=EeXXX)-FdO$)LQsyP=#dixQV&t@k|Ht(B7mM`y+nqR z5*b9c?AGMWk!*%$pyCiiy#p8xrGnI#~q>$oXW zi|=ho?$9jsZ=Wzwoy^uIHzt3k10Ic?k#<|0nk~2bAYI~WPTchImiPRtd8mAvl8)5^ zl;*$wkN<3pe<*YD!UQ`b zJH8!|lk`<1VBG1sz^I)|*y_S1>b%3XmLxl5 zq_ph)l{mftB&Rdmpe0aP|II$BVUxir5!%|1sP$l4VXq*p) z+Yf%4<^&?aVNP-(0eNDPhpWNMk~YF#ogg#JAv_u+d9O-^Eci=MnE9G0jMJCU!*}Bv zBA1QlTWKDAB#*zVGDYJ4%ckR{8}io*nxC6JIT-sN*sqfe!b2GFXOPHGV> zD^h&z8`DLKk1C2NxoVG^9E?jPx-$* zplpn0hZ&?t&%NYa| zfSR0(X~wZVGf+enUY?Cq^;$U1X-J-A!5$58bD0mdJdt2pNpEiz&`G|Zr9x39ff{*& z%j)|Eh6oAw#s?JH4R`D#I*Dek4*-qiz#1L*K@cQ= zhk1D-mF*-byaGn87#jDYxZfPIR`Dp!fq%^Fl6*7)5XWHM*a*e8`y4CjoO z`|P&9y~dTjr;C?9c>lc@XG*mNrvG6sMQ{epg(68xQzo^|o*8PV?WFF42w7S^vG_^d$g? z|3{p&{q2F?y{Q*7cBiJC9t*MRNP1QUk(qk%8osE7YuDa0Vu6TnnbJFO+3*%ueJZzw zgqmhUX4rhyqR>#9bdtCBb%oKd*I zc6qT}wwOTd4x487et9RbVGXe*zi1w zC+^$4vew26=Oo0JS7R3zY+SSz?+`Fo=_1xB!6OlrDk^~j?{I}hHPVF_R0U7>M|378 zg;JcXx$UtW(88pwRMd#?&B#Up@F|I>43&eaDJDJ_IaCN_uaeA9bieSmqwmO(;CXxS=q5eb+KnWs8g|T`p+!_0(W%r}ojq=v!IvIZpL!Uy|M+XnA+0;ur zZ5M@^(*pK|zp{iXi2lwmsc^M!6e{VdOmS1(G->3M78EAxNlt2&SjFAC+S|05ULjlv z^na}S6^Gcec^hH8zDW4##tquQYAgT4ABVa&t-_(+5y7qds~vxPEB|+~Pi?IMA~7~TOdv;m#w7Iex~`8YwW zbv{4y7~8FRF28H2{Q9D#Qs=tC>Z6!n>WZcOg_nFA7M{y^~3*OeJ0eV?n zW|3pNMem+$QVnn^sU3?Y-dyIHo27J^q%O$`M1)9e?yv@&VlzR756mT`Ptz5zhp{C& zp5BqIw0zrEXVM${-X(w?D)zmRv_QEI#$OZZshQ6lDn*~PoR&Rko{=jCjstxI@ua&o zwKt~je$hmS3g`}1r3M@1%1&M${aR|rTvTnrqh6L;;6x5aiRpi@sQ>oWWZ(FQwUmC& z(s=aO3p!tE{;7swufTt#;S}XceZ$^syZo6%@-WFS0P8_R^ zy>;&i?41?yPvv}UN&{R~Y}4MIGV0eG_ODm=%oBG`*3=JYE!>~W zEW=CnFvanIW1;^Hl~|gSojhIG$wfX!PMUz@cveN-9_Zb9WGtlJF?uQvE&TB+b@Ri} zN27?2FOPz@mXgGuw7)>)<%d;SEUjf5T@ElEK{k2zcTeq3p(8foH#=BX>NiUaINC)H z5+7JhrmQ@`r4qoO0ykNwt1P~o%fACbv*ZeDGUm8~%^aLYzJ8w0U_LhAH{W30H&a%I zpDM2E!7+;50x>!mL(P4GnMwYw2uyiyG(_3f#QS7_Af%(}twhDoQC&~cBumf!hz zpK&D{_EUX)T@>!PFE7EMVr%H@R1|~O$p!@t%ZUeO%zhGko%TwKHyN-*XAoVgDjtQXnR*RctBJ8e4fF7u%{ zc^3(hp0hZ2)LiNcb=T}3N$h|A zoquC-^>)U+4(i{xPo345YPVz}R!9RwL{?il#Tm6tK&>Zgd?=uTPwRI*%>>I$i(ei+ z)NYzT%Oi**{IM9Bmfwet%kSpOA1^6ieH-Up0OY~#6mG6K{fd6lg4q6G&o~?%E}|@n z#N9dVKCX2ibrn^r4_BZNFFpb7Vz zd8}~-DP2wU#Md`1%g^I-dQ6wg=8F*KzWH>PdR=k1`B3?O{S+4JTTbsCyr%Sn`Ma8D zjH-pBX&kaw?G6Tv^4)J8YA@=K_qq>Hyf3-cjxy{85ySkeCs6AaM87!Be z+RBrYqVM`@a=04YwB*+GYfAZ|)WEUYb6|q?zpS#aowS{1PFpJ_d4Zpl(4z)Yz?d zOm~*ys&rRDxz|UK8AH=O8%gbUCouwWCV{A;&^I}H2X&0zgi(shNbdHx)pSic>(f&C z#w~ZIr(0L&Ar&=Rj`um5cM*7BKLcP4y9*H{H+Felxmn~~OzuxekUSK`4M9OHegvWS zw``?&#GRAGhdM4s3+RH?4a-|c2kVviSwLzf3hgSI!U=qY zV#grH6B6FLo&dh9l&b1-^%MQ5Iq(1R$Zj>We=L#htCao$vmJXomOxr)#>P0{l>9W_ z7jGSJzA|P$xLwSic$Wbym`Z7gZ1|3vi)q;1KG~_lqv9e6Cno;#@~cffV^L??$NcDT zNEoP(Tv6p7HAyZJt>I^fynO2WQkxo3lKas8HC6Vmn?$A$p^Gj&tj)d>9xjQDvA>k% zYpd|cuG7VeE%rv!0Ie&2zrW+e0%2~!qnpjyO=?r1vX@6IO&xAO>kBGkiMngFVCG87 zH?_*&=SS`N2oXHcX9rKxu*=Ofx-D_!xn)v8mQ#EI_ls9P6s&|lV3o>$e=p_KT$OWO zcb!d7^Y-OKg~0WD3TzGw9Ktzg3-!;Y{XLYR40a^or%_2GFVYh_2fmET@*Ve$#;e7wobq3`F> z?9{X6^+k(cN3G{$daKaQM9tds1B%mTSnbH-#hjJQ`l-<(0x=%AYz^ILV5>6@a$d4} zv;3pqL2;x&@pLo!(51CVWp2u~WjpJ|BBPmQ4co(}I2=TP1d-YH;NGazVHlImbBiYH zlt$GBh--}kxlbQvgV}E(yB0Vy%j3b^yx%iEFTRlOcVQ+j7F>I>t|2w?=r*Vo@hgL3 zi6}EAiBbt@s?~kedsLAMjgA8{y6jt*s5r+tMHuuQ&n5ca7uM}O$QGv09A3OX{-r)Eub}O^t`FOHT*2sDP?B;yB5}B@&JO$0vFn#-bq!e3 zC25u-eTXCU*|x)+$^09RsMI2bqD0M#$h(UQch6by(7pGSp$$@|{dfHWX>qr&z-Fbw zZ<*PJ=_O_8s(^iXRj0=?DBQ23@0*84Zc;W`vSdVd8yb$YuV<1Bwc<<3n*hN5gzk&_ z176|Gk@twei@M;(LA@uwKCKBB4t66Ho$l@5I)0%WpdSLBfzxqTu$SuhfWHZI|MS7T z@?X;9#laKzzqYkiHI+Xh-ERQow}B+Suf4tG&WM>tE}k%K76m>(Jg9RD%*i0282Hf- zQP%F15YBV!1*+&m?lp8JN22%ZR&fnc0V9CBz=qa38B6AzN5D-H@*FI@4!8p|=?n?s z&)-nTO>8mipb!_dcGZozbt3*X_>$m#d#dGRY1;G8;AgH}V3n=!$Hh!&!9kGR-LVPf z{p);^J=rDwd#{AcR+c@H-~Y&$eh(bl48SbZ?KE8Cp6;(7e==WtW0GyV_I*glN-a+o z{q~hvuVfL?Om(+tWO=%k&tCSs-mB8#kY87=jU{cD`?(YD4ohD>y-M=#Ofwp0Q5e7! zyx}f9d2Cc@F>L3B|6#-2Vy>A2>NY|qH0`#qT)S9-ql*CZ>)88rd^?zlf=&=!DyRij z3$W}URz)*E%8h!I0gUaeLsj<4O}-J+r?9DQ4T96}L?Ov{$}9pJ^?!bsSz#l$umIob zwT7Jb^_OgVnN%FFP|HAS4}X?2I(>78V7o}Yeh}8rTHUzopm7j+_izjZ$ql`P1EBM9 zbp#&E`QwgiQbRkyzHVKzT>qm5!`K@Y?-}dn8pi`K>wp@Mm}d5dwD6u@c<0ne9qyM^ zJu|a@yA@KeTfy`BFueoy+OpBIM|f#-)3N1Z_8fWpeeu5IAAy0J@tvT+(-GVi&Wdx= zZp&;SGnVmGuU)IllB4zJ5A=6Nl`Mp>VO>?V3aJT5{iPe$r}npEV7nH$vmulY{+;on z-cAN{n7wHt>iCiXs@P3j6s*=Znwt}`Ywca$-s8@Q-%mt$ew)%1A2V#${u55^^S5Bv zKP=ebcmM%U7v_fzIz59@ZW`C!f$h7#*U9jdGW+(Ye5mo7njd@6b$4SJolsj*>W(#h zaBJW!CIKCIbPogKxIRWThN@HH=x zt+VnvG;F)Nvo#O;F-Kc*%?QLZt?Qkr9?VptGc@7%6i*4|@_VNr=89MUxFVeZ>LPgZ zzDG8k6rwFMYfjJoaK!K8d-DNvfSP`+lThwm+IN?+?mv~*sTYi+aq-rujJ^LoAU;Nx zEn4udMh@Tizr~np2u^s@4+bH(*NHOT+jL1&42+hY*B-h?Bi=}|Uf3)}iE{3x&j&|M z+Aep*t#v7DAiJCj7=x1SgTXyPq*)OI4F&;1tb{m&jNC+p=_iF z_S_#?(X{A}y<}SIW3nlym;67hy>(R7;odzugc5>7h@^A~NC^_s(vs3hN(e}IGa%C4 zASIxJq)2y23k*m}cgMiMFau27@x13f=e_ry-|ybF?)_`ln*Pq3=Xv&LfA-!Vkes?y z=+3OsV!S}djPFTXg=STu75nS!j84@kcBV9AQoeJI^*e#ZH>0YD;5zqJD>D~wLHOgZn`b?_DV3OBpjf$!B+l37!yl%vR z!23x4nb_Fat4M+14b*pUOYDM`VaIRT>$+MzI_ZsObfF#~?y2Om1zTY}*3$WCMq1xs zs_C}G)SB2_rk>$htI_Q)CG)9C!~Ktob8|zL*qrrjGhObj5ufr!$UNsL!tznb>Yl65 zmK^kK$xKwX!>$Hr{7|alv5G#vgAK4uBpTnmNZg~Nld9=38-#(UVN_sn<=3~h|F?s- zhcMp_KSx%=_t^OMs2g4Da*@S%AfTmHLxk~p9-@lW8?Q^jueheVE(+WQ$5$I8GGBOd zr)wd)p{Yh+WfKH`2{DbIJYL9@dB%iSG|k?B_vFw&DGE$1{bQ$cPSJ4uQLg}U$w4Lf zT;R2I7!?LOo~k0*knz#dTK3&ma++sq?1YKHkmT*BAXxDu3m7+T3ee~>GW+Z!Fa0B8 zfo-|cehUt5w&*^oJ80<$JFUpj;Rj%{xmk4?=}R+ac$%o%dyU{JQo8C>dY=TA2_gR@ z231$i+q)b;d}PvXMg>-zz9Pwcf+;;rzpcSg;1d_$@Y_EG>O}=Hxj%R;w428AAl0VzukH z5fqbE9#cZ{wrymgZmK@WpkI~SBo!g>J{hbSF!d>Q3#a(eKz!x$b&6gG{Ai z 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


FhQ&UXM2>`z@S@O z==X^Nj9-EkiM78{a$s&PPJH%0G4noYoI}PE22}Ix@xA=2(GuMjJbMPya428?++tjB zd@{~?HH7wH< z5u0nqwm&GC!lcXURs8@8doAGhXGFnGvGFwC0cwmpoC<$m5x=+c-=>AnP5(ZyR(xY- zz5o77Jb#H&>mN0LNhzbJhQSpOV$gbiWzynxcAza*u;KmL#oM2i20G?m?AI^o9PND) zzc;=DQt?A0z`4m4^Z1o3R)`htoY=~RDi}So++N=UZspo_`LIUx?jvVhC^tR&a%^jQ z59@az`VDr8!kn=jDiLgGwsJ?6`sB1|&#>!kOjA8d zd`7cN^qljkKRT$N&7Wn?%MSvoyZadHjp$ys_VV}VXvOaPCAwp`cGCF!O!HP2+D&~C z#A#h#HZ?b_B6~?X66?yq5QT77PYz;_sy2Qp$YWZVoTFV!=f=ipLitwXSbQQn=c#uT zy8_Vt4$oD~pNnr-P4Qx!Y!6M@qTDh~rw?wkqckgYjz-@_b&;7bEGU>N*MByB(-b`3 znFq{BN^<{`{0bI?{2zUe3)-iwo%H6mcDhl^sWV;8%J( z=jsy@1&p3hwxZp*uV{_#^Z(YG{_ylO(gqgSFJt!k{l2C?1I{o?XYGwVpg&S#+6q3U6~y;$tL#xNIgv%a7=H-bjsw zal6FBVgrSbO1649oj=omLG_A@KG~WcziAeh%wORHPr!CB9@MYs@09;`Gdu)-Rdn4< zQsCWQQl?ucnnz>ojd-g!nZ1%oIcB64-Ff7;!qUjau0(0ohSVeK;8kjz4bH?#tJ^uG z>=4Fp=#-$um+1L-xQ|X`(^L-Da`XO>NeuC1+r3Ou?a5C;p~3 z{h?LAJu4qgI?w>!YKoU!q>smGjX>$w_`@3d`zq9`#g1Isu|V6cYHr^C`dbclybDw- z%^hBsfDsxSZTH0l14|%&B+Xf;D0*ElD2X9tUvNzP@f10F>tuj1!chtF?4^`JovnEn9Xb)Z zEY5jrO@{QoFk*Q+KvY^Q!GMUD7G-CurUcMBgGd7INw|-MHpN@s!BWUWJWGvM?UC7v zaiPKiI?Z#w%M|lg?z66%jX-j$mlD^CK)xT;9X5ARFSr;?GBs5LnkLEG4X$PV?)>cU zQd{;u+5r&sFo%!b>W+o#p|4wQp*R&lARNG6UuDT-LSv%v&Xm*5Qh?bMD_J$_Eh$ly zqjPkgtv3CRi8p;v*$dXrwJPOGvBF_sR@TJ!&J?Y&uF1~3+9DEY6W#bhdh{hCA{SjP zz)mM2(0NRnU@JjDDcIMmgN#$jQXjD9YyH`pzeW;J0r_M%@wt-9|8h?dZsyl*gMBdl zKB43C=|D}adb)+RenD0`PtKO2jbOy^dmeD9ry~L({e6^PeKy#pB=mFX(H*&(E^DNM zZ~W@44h<{Sk&p_GMpBTY>&{7RP?3FU) zPB!+Xz>+Tu1CR|uOR;m44I*7slnG>mFay~juhIJwJSYTI?i!~AqNAJ%yj^R^Acsu^ z2DKU09iv5p5J^b%VJU|a;W(ZbA=qRO6~t^P2mQYA^HO0%grZz-yEk8bAPR8O)56wv zSO32C+-2yby+HMX;fjDC@MJkx%>opfE!7w=b#E90P1;K@QHt}XPuT}lXQhr1CnEq= z;5`Mz6U-hT7K({vBzT@ zVF-+9qIPH zfZq-D`XnoDT<0a!0iB-j8>_Q@KR>8Pi1r8}=-nTJJFFADlp!QdS`g2@qZx@%`!b`g;K{d;iJct54_pAgvl>_WZ3-w3!V{4vdazmsodR{H|uK zH=7EL-#MlUF2O#{RxOcXiSA4sry+X$v&vrhWEM8h-lhu~)jcecSKGo?u z{Qoc(B7|H?fz)TZ49RbuQlo15VLslAkh=~=okwAF77-iU*Zi)(#y++z8HegtfQvKKpP#TtRjjMixNC;`e=(Nv* zbuo3X8-GAQvr)DiqW-a9lTuYmzLiofyZ&99K7;Z(XZ~&aQhgC+jD;cYiwj%Wf+|kk zBNQ=GwJr#Nafbs9f|IX|*ZY%Q1Rj1_J%g*?YN#{v1~6o|d9x+tTIbKl>ZSlR+RKCh znmcLWzwQNL+h3NiXOGZ!q56%>J7)zG%w`&QNmj6@)s%Xs*xrFQvFwCM_#Xb4|NJDq zLgX(N00AwgDNk|&SdhrnJy9eK3?^GGjxp{QlfK0|oS)%X$sX4kR!4k}pU=)Jn2T0e zMsXz^5SV?-yfl}DD&Nx*Rc0`FbK_&4&nY^M?`C7H1J9V?9VkD4qrc(8A4jh z4DMkpq?(RR$%{OzsJF;WsK~e6>{DnwuNQI@Z(EO(oypyxp_R(^`TP!Ba8dH!=ex8w47-B>5cE4pcwg|CZ@>s64=o@OkoR ze=x!H!=BtMZyj@w^R}it8*_oCO}5t+xPx-MYiG8Rp8>^T-p|i~q3-{q9U2hUw!R*Q z*Sc*rBohUgUg@PZsXt)$I}@lFk!LG-(vy}_HB`}W zgDw`tZ8CVL`J`5)(Y=m`9uTW}P_^9WWdE?H zqXOtr<{cR@7Vzx)-}BaYsYgh$VV8zlu9pUe%ISh%?R&h2Ufz$l5^T00wbJ=l2KH}rd_L+m`^tRN>8z|!>3|!T-^PpK?1$OH7)I-|B?s(){04Q`h~xmpq$qin?!#X0=*A_$&nfbj{2su2)v0s zfI#>T>g9bVnKW8xF8M5>tK;Jw^mlX?)1doRuI^Rx5A zr}#ty35j>G*P6P%ujH#({%0xvX%MUd zPbio?9ghvhpA~f*d0ufXbmRlX9zh#o{4bZM0Tj~=mhbDvsCiF#W_{pyeqg+IX2Jvr z)s}ZgF47>!>aJ9fQW>}MOud;{+EI$Zl$x4pwV20ZBW+f$#2H&<74_wgx(f6;Ygtvw9Cx8f3+%@JMebKOhW8T-6E7x4j0 zH}}pd?e1}g^Jrm}t^z7Pxd-zc^;O;5My^L!JJZomY|?!GUE>cF#|p9#eZgm|lO9Yko#bd83e3 zM7>*?sQ=KUxQ!l?{?_mE=q8!<}{m`ykx8<^OkY%mpS_0oyC%sk6k zMpDP6f?#C+!~sl96hr~MGCF!zKrbXGRD14!-?+d~-i>>K6aH-TciZg=S})bj#^u-d zvBq@OZT3=}hoEqvf)I>8s9Htv_ebsz_lZ~_5v}@BOhR4Wzae=#C^&SMVB)!tmKG;1vuFLz3;i2zUTM`m?2o{rXpVK4 zH@Ke^>HLWU=(CDuxX6yY0Im0POeBC$m=(R49H_uoRB7`jD87%r41nDY!G<` zxjs=C67`6Fqg*#2aL-L1IU+mYv;Nrd?1lGGLe|r4t>gpm>o*UYXypgZZgNoxxL35X z$}Uuv2)^SrNHkcERvd+vkq4hn6>$Q;cn)_&%@f^G zf_^I8G1WKiyhFbc8$!anZ=PV`geaUF8A0+9r6&=hk~X#z1)$UX$?`3S_5{2Ds%Ye5 zK*@Hz#1Tvx5he{yt>(Tqk9st2om$wDA;6TgS6)NWy_%6clrdb~L{ZNV%~QdcV8rY^ zzkOsC;c0t)CgNF1*a0Tnw^BH_ju}bW%5?7?1R&i^DC3-z+?J<+phD&tZYTE>JD#zeOD9mbUGGGe&@a1BTphh6b81t9q~1&J7nLz6mw+ zO(q+nfi{nZI@U`!QaGjJJ(q>KUp=D484^QJ2}oMxRQ0>RVj2+L5+sY#2$YN&SXJMw zilBl+%6ga3T6 z%3M_Uh|vElak;t9v77+m2wfU@5T<_6sCNqn| zdLjW*)UUc5EXtlQgz6-2i&B14*v(`|+x1xq$s-8S?Y!X)%G}kU&-VkAqP39hLaHlj znr*(6^p*(DmaokKD*`%iMt?}Gp}DS%gek|f6>n@i-9_8g1>Q@?9sDg^B}7;0R?PxJ z#CTe&g_j^r1c9_lenb;-KL!y65SU+vyb;UaoWJa!e~I?kKY{unwX>+P6Xpa= z=i}bSFlqrcKH9bc8poNH@)*bHVe_Jbcqyj#f(R3y=cnzDie^1~kc>2@84NgNlL=C) zOWuNi5tQu~enj|fMigs8TSt*jXS(8MCW7?C91W~59HrHAUqUA*N_^&b8aB$^I>vwi zKPB})DDzLyXn(1x04}BJ4CVoSN(~-PKA08`t7q-yJ$jib*J`VBBu)|ZxkoIRp-fNk z{th~^1Lre%T_O)nxnPxxY6W()PWoVUU0;Z+l74*FI?wf}aBA(*kZ>k>1sZdJv?! zPMD)*wyu+&+tkrIMJ{BsDfGbJYfi3_Gr{!!baGCV>1my9Kh7JL66V;Zf*0KK)jqu~-@F$|8C z&8twBYDroSgV!(!0~shIc_uZ^ZwA2S0xe#nC7{{F&|ivsF8f{wkNBgLp=0 zz&maw*3V5kxCU%^Rs7ZOgYwutC-+o=MiuUg!zV)_Ja{ptEiJ-&7z;P`rn~{eIcGMi z?;3n#hk0ejP?VyH)=x6p8w$?ic`@o~4iHWT%bWIl$ealW=62ul_jNtf;h>fB1Hy!R zcAz%{vBv|zS~nc|*?_Td4e-EqW!e}Ktgv+g|I;?ILG^IDo-3frY~UbR7ohp}AQ#rp zKc8WDd2)@MkMW@AXXL`p&ZoH=q_I0^NFwu7zz7Xyd$kP|nl!aH)T^|>NbFYKT3hRz z`0afjk6!k={5~HHqg<)$YX9Q_a(`t3KOnvbr0nsXq>2Nkp9}R{#5L>!iMIJ9hYSND zMDyh>=ZHQ(0P{6k-h4d82v*ph^WN$2f?PJAe>6OQXbvYrLjZ}G^8l$RE6&5of^ko` z)AB4gf};l%+G%*X^Z;D_rf;o@3fQj6`yzr`OFO3kIBf22VE#)N8%oI}0LmP{vFq^3 zOrYg*g;RORlHZ{g+yViRVTyOu{hpxh=0skr>@qdnj`J5gsUN8EKiNU*Uu0h&_!k2^ zSrwu$c5Eufw{uE|LPF+eLX5WCAl!*bDS)?}7^$emxKMS;dYkwdg%h^ypOFGCx5dve zTluQDGST$4oxayoKQFttO7aYvLnEsv{h)j&=Raw`Re#fdSL=WqRbu{6_5fOxgGhM8 zhC{fe?0?XHVdh|C6$R=CBnzjBh-WL&W2$x7Sn3Ow-2qOH&JJ3MwY&alaDBHOT_lcixP zw2LbOi}Xe}&_Q2C`u+VoMY;p<-@@zP zjog3i<3I)+fZF2v9>s@omg}VbcU3GfcH@PcCIUiXu}fX6%SCr{ifVku4zz77EA=<_ zU`|l6@Ni8PBtAWs8alPaC`;96Xf^T`P~-;03vpm!2+_*kro=+LUk-@OZ&^lYlcB3ZLQnneJs0N>)Xz%M)sONhskaH3BAmrPxjDU?sB<@rm3=e? zG6cKTs_DS}44F|q>=pBpLce+!ZM_(TIL=5atMk( zZaRIroAxI5WYbd2n`1BTU=V9^2l{L$Llq9ZPn`kb z(TMa>kpc?x23^T$<#sO9ztf~b~ z&vDFQ?P<$_{&flDyyQ4Or73^bV>yZd!A)ch418Yv(5Jse!j<}z2KBTMV1Q1^ik%N8 zUiATNPTJf#D)*T|0L{SkFf3;N7IxcZzuKEarKcuxE%Vn`^Yatfi#L?ZaJUF@4mv$d zZ~Y^uuQhQvONi0!!7u6fHCN^3_0ai)tdlQ53CMy(RX(A#;)KYKIl6j~mk7>cLz18j@B|D;wDGfx;c`LQr}f9Z)nY_>|d z?f4GUR4E}8n-ksI)5o1w(-wMIYg$9HiziXr2i{)7B5}=fADpW$UScn;csp>t(j;X>4`L=T$jmvbWyg4Q>CpreJV`yVauY6C+FXuF2U z6~o#>v8#3Isj!Q(fuW)+(NxN8euEkJ7V)}yKW~Pi)tpCugnpNu{=%b3Z;h%L;dvd@7Ok@*BU&MFGzZ-TA4x z(+>Uu)~*XpAYbQBjl!&GI>Tiv!KL^O3x<1YD;v3hdS&H-ek&rn5-6)_%bj9=lG9(T z!4xr;AKznbcv?X{fg{1|MZaCd8V+6J8B;|i*J{ty&zjY;_XIoyuZuko7X(Gdg~5xH z^v-MagG&8rp#Bi&6{ke%nhL(I6iP2CpO2gx@3z0mcy-0b=FxO9tID49-tXx$H7;xD zId3)659glJ^rp*nM(`Y!_zOnG9MCV=T-wlxO!^%b65^cAc_xkW+(uMKe>`@M9F(zp z(V>MvV!Zb?c{1>zSnMs!l^BYIz^>)Lz__6ED0{xA%QQy{(U_89qY; zbmdUykT)*eUBW`TO8Aq5S#1)>i|Dg{tBO;`cx#nMyUm z1)Vcwf6O~Nx6-|FK1#2BjnVj?J2WYwvieNW62fh^stk^E4Z#7z3_soKrU3?*GB=8j zS6OMG{=jM^6r~H_GhdVhjta-?``~`dpa&;Z@MIvXG(tXJKeF6Dljfq51_S`K{8P0F zzuwg4hYWk+noYS^#}3zSnwJuALMI?~ZoKyT3Ucj!tcVut3#3&L)GBZ`Kc#Byf_wkG zqN9r$*xdlKVj~lKzb4Z505bKC9k4ITtIlQ5k8%innJdC%Zj&AHR(LAbqKh*61=F5$ zQ$M6H1SDRK6at~=O;`H>di%p=JvUGj_yZ(XMeYiOq__w$(|8bH`Mfl!NFD=f{~U9M z_7$SDQupS}Ak8imUSXL&DLyO}58B+w1!SjiBDHs)npB zIhqCtPsp3;CmaB?P;#jP>#mQel$0p(E)g}Lnn5P~HtPIJ>}J01NvIk>> z@NbOS`PP*l>raQ80F0S_2dMweW%G>rdjP#>)t#bh^GR3(=hrThmTCIUgUl9ylxJb3 zgE*GnHV;DaW0;&V5|CdtVni$oRqI0zGIusfiq3okPRoG7aiV|}uginQ0fEpWe8Kej zlbe_RVr!#y?IP8Y=lOK^{ODQyuVFh3R1ZnrW(&og6KYd(*%jxyl^s7pffPwD(u0ea ze&d6lwzk4{K|P%o>=aV|nppk{J*uQ4@$FDHihI-5e4hddUw*$2bo7&-PZc<%7G(6c zXsA=eJOB<3L~%PS6uY(XW29*3+MhsWfu;`vMNv z9QqBgiw|>)nQsfGl4!QXmI{`efpWsD-ghS35Lpglg(XU}_j_Y`+4L9aYVN-W9n26m z50%wBuH5@pi@2+LU%zjFrJ{0VMq7xs^7*?_N(1NOwwfualqofeo&yDgepT{jDE(}FUpN)0$^GZf z4hLR~ZYBu7ps^TU7XC*sjuIDWc|WdPRl-<^qTsLLtt5M<>LtCVI3J{yXDR6b+htMd z;V$1B4Nc@0DYr3@CF9=JOmHWBbBf9sL%SaxQT6a43PHy0o-k8KeSAWf+sbeEd0Cjy zySudCIHS<9R8SIEy`E>u@wrR;@@`<=Vg7laf^3MR(TDryq#d^`hAp_(~QG?`s@ z|KR9Gu->`}F^>1YN0d6dO?Ql^{5Aioym8y6Fw;#(Mp=V;P^#YW<>5Z={6&k3=t~sj z4QG$>>rE6_H{zzJ%9Fdp-?A5lgp(1^R9yB{$oWz?VrYeSN_7Qy3Fw{!O`R#fB_==m z2wI=#&KZxXfUu6ZSJy{?`ZYPT4CI1FM)%#4GCJS0^gp(|Dkoe|o_{0aeSCOu6Pr^D zM2gko$v)qXzE&_if8`C(QKu@}x_wp%3+SG^Ps0rcvFP`Q2!@`VxmBJ#PU_zW!Y=xi zQC4}}fCiEIR$JJ#?%aOz@y!e3s&Xg*yH)nK=poJPU z*$31E;gaobcR1W{8$NY?CU(uVvX(8kb;I1IIG2m<5j*QvUUlfbQS@s&MUN}fAgTO* zd7*4dZ%(g)v#Y7X;?pz}y%3S7e05yRXk42FN(bt(ArKUl=0ASsyU<9msP&_J61!DF z#IM8s$t5omcoT^^+))1yh`DzG@lM_4B^8|KbE6T!>wrVMpM2$)bx9l5?sIUOOe%wy z>dJTs!8??}(w%8a7oXkc!vmc!L{F= z;B$qilIc8^B%;u~{^?4qCA3y~JC8>w{#2AVLV=!?rK- zhWg_7dF^pWU@B?(|2CDR{O_qG&pF+be@sUT{d&XSkeJ+N8)|`g`M>bR6*u_eST9p~ z$j*g0l|dH+?%)Zk55x0hDeaXM8W}xIf4WQIWNx%4c*~zZD19PA^3b@lW^-tG|WmxKl&lAM6S5R^#}HvFiEMAmC=J>IbWyDXO&>03YUV=^Na75 z#&GDf>G}%4dx%vGU zdHUO&7}Jx)XOLqMBw1QY6nO=_i)FrOQ`*}%tQU-61!{%ug!{ehVlZJe;~JuvKkaZ^ zNz$|s%pc_ zc35drN|-J$b>}0&g6uhv>i*O3*wVlD#u?}7J87PBw4-YAL)ZN>f(}Kp=rx9>UrOsT zzzlaV!aF;eDYe^f@ntQ z>Q%Ak=scNRu|LqbdrvR5;0JV@li>s2a|hfe0&&TcF9YUTAAWuqzE_=nWosVga=9BK zmT`h@9J&BR1ce&*9G~F>4>#ojfYvqst4A)zQ|+pUVYG?vC#DQ!{rFU-z2Q?&Ez8=c z^$^?89_)o!%aTGr?pfe z#18a3U=2w+0gUau)+GB^%Wv~|aF)O3PL)Q^FIQ$=J-~Dmf|#h4hhA!=DO0r!|BDsP zB5QScdA-?;lW4BtdhP$dJO``uXSF1GyJx7{5(>G5)q7H2C#BHvJ}EOROvPY(asOTx zr9mnE@`Xe>#-eW!w5Xo}rnww>61Su=RU3-Ev}9BoN=h2eb_V9$A>JiL5=LfmEWCJ>SlEyss*c0)>2in^!_LIGyVmtGsVD({$P!o+82j_l4zI%7Z3f||y$ zE-(T{L%0kF7&MGHQ+?puRUmvYp#=-)r4r*OTWzT4;b_R6ZD8-o6S0_deeS;s_f#9} zG+pA#@`4(GLaP&dO=}0@1bu9YJAATh&?Z_e!X=sgrUp8}>I(PSR`W3r;)Bi=6Qz9=?n5 zm%{EJ{K?2XE^6(LDRT$1GP4#>%2=Ld0o7z@P_o(E|M>L(#}BF7*k6`+fQO(9q!$7C z{bzcTNkrOfV5;c5)qu-@_W2fyt3PVY2&m+u#mVawGuS_X?%NRLOnHGJfFQ7g27ic&;!MB#PFMNogxPzvjygaMRf`Dc4k)QKzSVpUY$X?ZCVT*b(={OFP431y+H~zRcASXP2fs_XwJe4-^#pQnW?1csjk<+bQ%!pym%WfLJz?s& z+Ob^EJMzjbY>2h_Z6{Rl1!1ukPSbD(AV?{Jxj0tC$%-Lx19<%FcWwZgL9}ZJqd*HN+U7rnPx@7!d?;5hqaFS6t*;YD+`E4m# zu1c1|z%pI{QyVTd$kw|&*MZk86@%GpF*UxM6GK=j4;%=Vy6}F{7?(K?)YJx_>r}eKHLAj44tC-kp^(9X;>1}wVY(%Rsm39RmEk5xCPShkIB1r6D zqjHXxh~$=cy5Mfy|3lha#znn$QNu$hAPq-yXatc41%{LykPcCR20CMe^^c0Ls)v79e>1928tipyS`+k5}c>pY^`8vOCn*sz1bXg zEyLrA#|@R+d4?BNX66sFUlB3j8yA~%v*chF_9I)mTP_T1NVLYzwpRDrtbkZ?5OvB`UncB90)!KE~!{Y83>mA`sOwCP^1H)ii^fR{(c%PHzF#4uB zv$V7@EtK4~f@L;L)HujI}mV1>#IKti@zhjW_370b^zIYcVt^^-c?sbJt)jZelPPOxLXr;s4UOMQnjcVrO`D>_CkP`GnG#Xwj#+RtA$nZgZvRyY_SZ1?T&8>lKs( zSKLZGB;V?0nL`I$SP2VWxzP|oNon`e>XkF?@t{->B*gRD6eVuq>#bfZc;n_AXz{2Z z@YZRtx7*9NwrE(!4OWoT)QtENV6UrQ7Z=D@8i(~xD3V;B7)Ul%4Hgs32iR1vd5di* ze}VLiWV#&L(J86bRi_H=V+ zZys~R-De|Jyv>>E^&|Ysv4$q@Ca>|&*L%l^09k+42hGvy{4em8o`yA3P^H_75H`oA z9!FmQ{k_`*SQQlCNH#`|8Q;1?ks;b=Y|(48v@&D(sJzMSValP7UHqeg4OqjPX;I=(eV0X$OtMocVL+gj<2IBGzu`_~- z6X{R{alO7{;Xy^2+ywZYRCayz^*qW_XzW+IZIYvuAPWp3k~ng`Gy;aJIt|BLD^5&G zhq~G_m6%gpo|M(Gn#sFkjQ-0$`!gun>Mz1LYneN~)>!mX($Ic*stItFlzuAQq)))& z>&!-lwh5nC&55MwPX2&8L;h^w77-v)*gtH#Ja=6PBAA1cwmT9w=K)uJ;~eh_2KDe4 zP8lyZxuIgO77lIl=4bL}G-O#~99o=p|UV#$c;Xptg;xgy{OGk5LK7hoC;EH zfB9G(>{05}CJ^O8QS6{QK;S`=m^Bx*()B&`^zbgAtp1SdcijG6^a-1>%Ibm!RWpFH zdYOvC?fFPBBl<_=s!xeSn&t9iZr_fydwfd5a+PX)_z$?svtDTQ7*<2XRao53J`+sp z&6s5|!nV`AHa3K~;@l@b+{xX_dAVUvPQTo3O|n`rt5I|CGBRd}W;`VZCR6V?)@?qv zqWbz`8fhG&JI3AQD93Sem8J`A&pRzo4tHT%p0k?$NdM5=B==mJT-vO>v>DJ=PsuZO zbCIoXZDshFB{v9NevzVC>1!n{K?i878(cndZ_Rd}fQurs%)ZKgIXqeDf)U$!b~cmE zr)>A#DS%&cp8y%I6sIfmU)|0>>wP{4?$%LVI7pglr|5(a|F3VT?+^T`(s6)h)t3-x zu?^^|H}V6z>N|o4fUdgCIfFzi=?C<}HN6|_`aS)fXz3VwX+A$Xt~Y@0^&|x<&)F=@ z#ttF@zAmCOz)O&}r#~QUDG?t_Y8}c+QUEBc$8sC?ZLkq&>4-N+bu|qM#PgW1)+)Zt zeSXi`G0<>ydRjMHZaeBt^k75XVIe?-cr{Bo{n@#TynFj(rcc|izdmk28N^KV<{exx z;J7z)6)G?!7ye*Uqkpxkjp%DvrvJ&>iXzKN?33emZdsjZsqL>;n6cw+z+Ypmh==8< znZR%xyN3kU4$LDT+lU6#j?LYd#C<;@u`?6cH4z7NRh`S zHSS7{M1rC<^1E%an>u6tfvtp-kLdjIfM*ZZC(OQVi{sV8cs=q?@Tm%W&z)Ds{){5C zzUY3f+aVHXfH7E;q0d&OqL?UM1W1}5pyORY$fEmIhbfw-C-vUy2XJMe=8KZ+mv5Mp zh0b4IA*M-;l1cY#yOz_C@P%^Ad?(uvY;Z0|pM)ruWJ?(6TQiN^3MZu>=$J^On{#1e z9N4W%h24P==X>y6H`Gsx@A zyUkI1&zXA}NRB=FAp9$aXlWi~3r3V~FQuu&w}xxMifI|n7D_e%)F0#>OuKK&u~$WC z@n{^XA23xff*PY4jMSpv^Gz?QUOUhDcE@_O8n^SN_!)bEmq}UnYJo{=j$g4EU!s9S z0L@*|W4<71HdWVNZ9+jO#eG!dH+mUqn+Xt{=X9zAHSn)T+hVB^I|#^ zG&3uM81FSlV|M+zFdk#*kxNh$5!HBoYe~x)zutUxKwb^aL}cwQx!y)Wd&j#{&h*J~fCQPb$OERQ zB60h{DxYI4VKn+nw%&f)LwjvwtCBFPQ|j3;Z{(gBJLULtf?i$Sp3Xz9`kU`cz*18> ziRWPT$M_3FlD%PP_PfCn4l@+7w zI6#WACxDHNh?0jVt+vn9NDoj1FDqU{Ilkr(_Po(m2jy#Hu?Aut_wX!|AV4+c`ef!< zoNBWPP>;_U&RBJ$6JxtSw3K*%|I~9M*>ISO(e5nwjkrs|VcopZ0ARD5G&iJ`hEYk# zwLdy%P;X?y-F>*ABA322L%R#vsYmxn&HW?`ja6+3W${1KbM`(4I`N0>J)-!l4%1bY zQ-O(uIGG5sY+kB%H}=2;*6POHV8MO#N^i21?YLk_1J+0U^9dFx#kRx}8tekcd|=lu zwhQsXy1F_nHQs}2E37rCrh2*{6T9*J95#*JsmjFfY)`T91^^!F!C~F8L27Lr9x%C* z#*^oQFFCP~$P5AXJsvjY7gFK5>~_+Ll4WCmcqdyZSdO9?9&*W1LP89GKy#ut%Y_ zpV@G~%!~bdOHGgCb^d64B1EsZN7^9pMb`$U|EF%Cqej*(1HZ!YE%Za0j}+!}kzhES zQ*hk%1=bm$b4HRjKNh9@qiafCg-y$EwcDL#Z%{&#tDp&?+bUX(++a@`t$Xi_waszw|REv`l0Y7>Xv<=Q!Ge0>Cin)$Ud`xoUG;=JOcXSCyCfSq5paI<#9HD z*>m-`l9Nn0M_4>DLho>Dys=99;%5wtaizPaglkTz6+_fQnh5U`Eq;J4ldsea=Douq zh&CsQ^UedBS-(P7b@upOl}a%9b79bf0SL5Fq*J0R6B34<%jpd4+aAfGzhVtX0DsNE6)w*;9MD zrj;avo*)9|Rg@AXciNFE5$>gE)=v^a@1GGS((K2VfSq?LRAR2eR(EZ4lP}rGBNd9v ztY(7T>OUBQ11^HeSX_TwsOyfNG^S8{#jQ1brxzu62`@n$rtHA-Is1|BQc&l1{iF)< zPC3f6^m9w}OP~>HlOtCW%Ze&w!jp1Q!*w>9A1k}eQMSA|yMok{(vy^|oAGEj&XA(5 z6UEvq%;?Y;=(8VBm6n&c_0C4${>J2?=|5uiUS_k~r_|gf3#muHuTN98mLc-R9J;o- z?po(oFqv1Ocq(A+^vhr2YQR3ZnJ|hnm4G9APoA-}x%uv-J!Rk%9$aV82hV>gG|u>5 z`8b7#>vqM0>Jt>7>UvRyYMaV8$Df%Eg=Jdm!-#VD(9<$V#J=Bp-e|I>kbS46ZhR>+(IF=kQ!t^**OBD6k|OAK8=#OOFCv?T zwI>H-_=x?xzQu`g2+len)iuo zq}<*xU2!HHO{-C5{PJ5 z+4e$Qf|z?;=!Ws{20h}K#k(!0&aAfU9kw{uSNARGwYX(X(ax%&gw1XRWOjIaLYN{0 zw>ESOsdZQvN}^!jY2MiT)VoKGn|b^(zUilB+ghq1WzZZ9S14PP(5HIN`x6ulj+(g& zM*&;%_W@R9lB;j``knmCPw=mEgD%?S&R&`X0LA@#$JA#m0s>$EJFBhVb%RNquwe?>~Ka-_wv@<1>%bOh8L+uDPG{`oDtBS>d!K2aY>t+8M@qPofOCywBU1~knnPy%`-(V;@OkS-icIGM!8eU?d$!UPU? zwKhbdqOtCnBu+u~l)RrDw-&y{IAeZQ$55_SbK4)m~D}P*<)q*-;uksA1N~L#U^dfW^Q($Ph}4DImIiQ z9M+D%!FHYcqmo)z9B)^ZEaC~H0h}CA=IK^Ua^l|7#1(47J1hQqimpe=l=uQ55yNn> zBDqWjc4Ev5<-`BJNYu83E!)-s3wSIy3$p!BDB@^=E||Y;3(L=U?Vz_N;bGY_bE=5B z08TGIQG7eN1Xh6cH2^cHf}d*7#xJ*s{?SPr%Y`$r+tTg$ID89LhwyyicD#}tLq`dB ziVT>C20Z&q3qX6fz^QuH!vH>-%lyq2K9WB7aH82%EFYW3TxuWy`e4~?YNB3aL+9u6 zkqtPOJiW0g`+#Be;E|h;sKdRU*vd;Q4!U>aB$|J)h_{|NjJ{NN>d$cJ{rak*i#`KN{ibyUQ(xJNrn zUmUntvC2(jT|r{}?;ceA=?c=Q=$+xTGjA;BH1|+TdU1jXBDTpx`E@p|KJrs$McqYY zb>`(!%;hf9gVv}rQ&3*&oJl}2PbJKC#$cO8a2qwJ>Z@|J*^aU z$%OqhIw|bMag>v9oAQ7(x-VWQfUR5p=maRZOO{GDH_G_r7_dAtIM9K5vbYNSX-oS9 zGv^y_*`MJnPnu5Nj9*pnz&Ct)xU4POU}_oMfdn4$6{`SGEafpv-WKn!O%gBP2GLV{P8rD>&~DL4D|TUa`rbXKlo{Y)>c2 zn&bn};sHy^*rS^Umb0WCwc4$!=^m{0VVT3U*pqUp7yH{&Hld*Uf~9%gvo#;;I**Iu zO~?8X8M!9zE&E$>06R)1y?+sIySBc0iz!p-C^*p$dg@~<*XQ3K4YB1#T2q};LNXFd zsS~;wn^r0v(!7V6!=og=7_|Kq3phPK&9HbDdE6rZr_ss;ODU1&1iMGHpZR5}?GW{= zg)h%MC$8R+m}4$ozmEXq8EOM+zX$wR8aL0$W7okC{kGQaJ`pw>$rdQrQ3o!yqf#}b zmP9-fEw{UQk?q1ytQytLWSLN4cvf3Z#F~ZXdz}u*){*pN9_uM`CdEAQ32E-vdzz`p zsRv`--oj1MVukml65}3(NUpNF3`BV6C2BXw5~lm2+Hlv+ z{reBVuGgk>9;kO~zMK}4ys_|HJ9I{BS16zkOgU9e6vj1(!iJnyX_;K zi6-Dk<(xCVq*+pi3~$lyOZKrxis01?@}#X$K<98|(hY1%Awj;p&{e`j^Spq3Z_PrI zVbH}>d{;hiNvVs2I+|{Amm+W8`DwG-3!De@K_90GZm!>Gp|vK=xT(N=F5#zS0`b(B zVk2+8B9(r4fRVtFI870MpSiS6SiLo>Y*pVrQ@S5Lmi@EB9XL4;kBy+F&Mr7>jl8q% z(Q`cG90Ok*+(Tzy*54%rYqK-3w2Q4PHl71>qKK7yPAOGkQia#gAFilOPs#4&jYUfT z;}*TI&d(-m-Fd%hh0K+jLu-JsQMybqAfO3_nzl?fdJG(qUbG6fkh~Dq(PH@-20fA} zgpR7S!-|dS#6CfEjsShUgN<~@+V1YM&#mhgD?PK+f$2KNElbqdM@ssY&)>|KEPAWd zWf9`g`R#a**YyDB+4D8eRYzmbfZNcEcBUp)gO}nG39N=M)kULJc9k8A_OL{-mC6!7 z2lW-eVDyLU!K8IEH^4d5TE&uTcJ?L_{#Eq=tCn9I-lO}}mAb)%|4JRiHWZ}sy}N_I zuDWCqja?!!KYG)oOo2=mG|4h@U<$A5c2dZX@!K?C$z?hJVA6OxyCVQ-0Uy9FR9vzl zR#Wxxa%ehIYB!}*nyb9GBZ`q2-=*;-rx@N>fWBj}0&jJqdWFFrt7Uydz47_xnb!0Y z_#(3|AK#aFFlyJewI9@e^%_0>jA%#$CI8vNOLFG;x`M3&5%wH(4L4Tgi2tn2w_*us zYnJEV0EY;2d-IYO0sFZufbERzJm*PfI**qMqtg~38SQrF^IEQLp>ePJ#k{D0wYA;z z&Sow9=eWE1WxkWIhbO!|gPlo_ z7-i5lZ-B@0$JSqKWA=Z?xBwfSyRPRacD^_td&Pn_OwYfK8`=BYp@+}yqtnyxLwF|g zbA`qGm3ffwVD^r^wVHiuFq~Vc51@XAdjqi4(+3?H5X-$|y}n%YxjwkDZk9;DBs{D3 zfuO@>5+$TSrSl*4?5h1n|$vm0KxiF@z0BQca|=b2rCpGa+}f5N9(zYvgdw;<)ZA zh*cdMrQTByb8y#__X0a<_0lfD&Zdk%N3;{T|9&8M;(uD+ z<==SMS|osXP1T?>=h;E!+l?$rM(X_8cm@U8b{E%EMY7FLNGyIC=#XL!bXs&4=wykG z9zN6wp62&=N?5$%l8OFu`3s&{b6-MOxRn)L2kgMTqhZA^|Zn!4$AMh!fT@Mf7Hg3L?F8x zq_ynOE89fP3j{bP!2as)D0Q3%n~Fcs zclQ(t$57t@EyXHvTCsat{WuIo5HD|P#O4atEXUI3IRAY=`{tnG&n>xPj>#AZ;Cl!qq`qM|3G+B<{SXnahBDSbFdlDe1Zp66O$6S zUuk*IT6&4zzk6q#gXOrZ%(9mzMk`aqH-^n?Zx+mRe$*-F5${4ZPRpma5SQ;%w^j8x zaTj}9w*m+)MLTg0j<%ruD-h3glMp5_(LlcGgo7dZQ{Kl#yYr8Vj~QE2rZs&7jvcnw z*Z0Dj&Wz_A<06-we0j)yCfa(i$~BbT)47#HWH>1&J|Y(~-?D9f%ovFIEbQRC+L=t7 z68=KXNZ4$j@^eG#bhG?WD8hSoCA;k_%VJ(enIm*zwkH6V^NnU%dpiR%@rLOD!olE7 z;mN^L$6?WyKVz#k^iFO|-wj>U`78=;3?M1XgSXJ7HYPOIQ`Yhq6UeJ z#HPWXGjeNw$V6;9p98@D!`MLtk=}m~sDCX?32_wPUdK7I8&6WMn&d>J{g|xbr}$wanrzcC%)zP*ez^ z9be5OEgouvgu|%P1Ah8u?xk#4&l+;8=Q#DPeXdaELPBuDty*}-^33=aC=rZi4SZ9G z%5r-aL*!EvuSS*)lhv|fulYj7oB%Dls?z&(5-VwXT`C4~P?lqDukKyK8gJ67xzRW^PlefBPnW_fw`%fqtPWov?Z2gys|a_Xe-|Hq?~Qhjc#39n~~T z)Oy~A;@?+Lo)b=iOFeLS!qNFd#; zRwL*$+1rDafant_@tNI_el9>SKJN>I>^9>WSi*7HezpHT^2H&_gp4$tn7n?*pX%|t606?d=Qi#S1 zut1g~MW4nQh-M#zpPd5YPZJ#8R{qSLK22qF+tD+4UE_JVF|xJ3dkNVynIBNvG>&bI zvrl&4nd}5yp2|?&R`O;q+G8Bih^-_C&_$~;N5t`Y;M?r*DB`d4XH_KHz5(GOK7cM% z^@S{1a<^R$_2r7m#d|4ND`V!G65p&NEplx-y#Af@-901**sMkGVb5h!ze{G@oZe*h z7SV}RJ2OV754Cp@tH7sc=~xxA0r9mJtnd`POOaXRbx2$Uuo*zS$&HrmAs#O=*@^3c zCh;*dom@0u0+Sc)xC<)#gVVI@ED~Pz3lk7f18uKW?krtIs3%hfwmE*8b)Vr7lZ`mr z;-(UMJTG7`U(O%PQA;M)VkkNq_l%nbZFTz9L ztXXD%8Uza1Kjt7eV6OUvN+%(kbc`AFuS_S`gtoKPIst6z9bnFQ4I=5e_dD$WZm(kr zE$|mU6;Itynz-t2lxf7g_J%Y+1tBtTF$d`!NZk-&>9^ioHQm1FpV65{@}w^r=C~1j zKm%L+kbpoLHLW4jo{SMS&lqDqa2W^Uq6h^Pe^Wuzk#U{Po{c}O| zrneKP=DZLGS0taQOeqlBg0=g+-$~b~Kz$BFqNH+v? zpEEj90f#?fYQ}$3YXAPUtbcb2LD=Gd;Rv{z3Zil7kc9v6WBTsmNoao*hA4 zAx_3Fw^&oQb(f~&zFqc}A;BrHUWAVS+)>{QpsuDhcjH7buiOtJfBV&4RmtU8#hwsS zMwP8?z}2=_JK$ubRVK8#&UK~7#a!$;UfZ?fXnEEUiKD?#*-?(9Xr=q$#||YXdrXaM z`RQk9TuKjir52gBKfDxMSI|V1`z_@D&IarGC;p^6qYoFW1Q*&oSzhb_r1by@(LO+S z+eP#`YjAmM0y=YN*ke}d6Tx(1X+RH3fB@{={PmHmN!UK|V)lF?_AdDIe2@!o`rY8u zT)&Qc?Iy;)#>^MHp;B<>2SA(`h-j`H9fKQzf8=1%2tfK)v@iFy3tT^(8XB0>PxO;1 zf~*oEv*!3O&bwBplsa3Kv`*PJ16h$~YNqyR15H^G51aGM<+LU{CZ<@gHYE7g^o$pm zz1qnulC2JvJB#)*wA^+hQPjc?&pzywah$BNY;_E28-{>;!U22xDj(nS_(wnv@%>v4 z-Diu%U%+gX@Te#A^P-Xc)$y!ZQ~RUH4?m@4R6o0DJwg^AI9{H$qTcldq-Kk-ASd-3 z2v;6F4uw06^^2`nf>jGEptFN4Wf|7YsJ;j^UB5VL1!E|MS^?{tj5(|;5{##oU4rAd zb>zhRmZUK*8b&ny)G|2~jZPdTKApguMKcXOyEO+33Rd7}qqx?JlQXB{? z%u6yo=Z;lC#_M*oMRf_g*U0Mr{g3Q8TaS2N$z8>KcxgZ8G6_uT zp^6F;T!T1NoqZ_j%&QWJGP?T>X!s^LrcK^R+&yn#bZPwH>a(^pMn3tOZ?-RB4=UEO z$i73GgA)`bv=?k}ByKiSzJN_BI-YtEHv5T2gGfYT16{tRsMZ3`fcqF=Z4{Nz9F}t} zHIZC0`^E$2bF;{5r1fi$-A}yD0kI4}U>f;4Zs2RCb?jr}K2~u(8al zKkc+Zdp(dMrI_$8RVaSn&JWy(5rH!8+cNd@>^Y=K0ru$EjsNs_NP`-qKE$eC}XOon!P78rE+i;S4CJHFO zh4P+SyH1d&u&%a_MWTf!AS~dSwe)ZjGO~wd6MSq8u=Y@d7gHHvhU^PPB1DVd6Kk@eP|HGR;P^XPAFkyMfX z7DG{yF{MulA}T9aMeo8-1Fd+*29OhylO6$NE(iM7UdD;hmqz#tKh5(Q{%7QBnBY(S z^rXh|I+-)U82%l%7r)}xXZzSP^(SR*-{lL;fv@n%KGp)PghVCD@{pm^R1RI(OdE*t zO7>(r0!%0ricUEw&2XzJZ z{ELVJCpQl}nBhm-i3Ha&CEP=qKy}%u=}o#i-c!TuhGTanH(~<9b!9+Uw%G|hTwv8? zy>xu82D<8`+Kjy%NN@|kn3$cFSymt2mRV#*>$e46*%R~z#w6&r=<>Xv?;`OEAl`Ee z%H*C1y=x6Ob32pu+}Ik3|cCjSpo`VpYb*bU9!Kh9q_GeR4bfuO#e zk?T*49q<<{J?6GxrpOFolPHK&ymKl`c~$&)tUm1J!;+y7vr795(IsAqG^qw?Pt1os;m<0Joagoz?l3zHXNnF zg_9DJ&cj60MI8Ea)8268r`tB2vET-4f-5{ri+Z&k@?!NliO|Nvm)2F7C#;^sS%D3Q zjz{%+h1}{%8&T`EO^X7l;E5r?ZxfI5+17;l+yZ8whHz#c-_%?FVNhdZqbtCwgYQep zlKZN3B$Scin}xMg*oHs+Qj$k3SpTKSpt|^bW-u}BiRaZ;*ZhHj0o2h3t~xL@!=!IgeblvfP%Wtx{tpxkCE{}{L%k*3LBI&oFJ#o$%jd4 zEiz8bWC#gV2>a>RmmSJ1#+PTT5mO;r%Ig(u zD4{c66~0yxZ}eDemduVNo!V#()F&*K8y=41ackv6WLQhe$5MGxu|djDWOoeQ--dzu zGXIU*9mEMT!Bmo34&{8SazWNVzhGp{RXEy*6eEmY`li+Oh|pRYz0W!oak63tr`K_< zy@5MQKe3Kxcr~e%RhA-NE=#>}Teu|Z_%>V-H6eUS>Q}*po*PX#hDX+T^`TM~JJ}}( zy(@yG(_2Lpp|s%~$HrDLhcVhTza?pO8IuJ}=GvXM?tw||K+{I;=RFB;wp@#PT^&Ah z-O$ylbIEg(?lje%PHP8miAGCG3ZDOHJbp?GZM!iQb@5<8LP4;0bLN8dv7Py9K~ZY* zIk4-Z3Pc#f7@{;K0EXGK)rUI&m5KlC1Z>0p?5^7wKMDNZU5j4*)m`_F1GQ$YY~^$F z`y-m$ex$6sWm?&d6kt&X=bq9F7M`&UwyOzg$D@-7afcFL5v~MKMl|l)-IhCAQ{JA@U`CDHwDHjLdJ(P6p=6+`7w#Wt@yB;*k>5lS6A{ z&0XfaSHo!^I7tkeasMERIG?DHYbz!-m&(j;s9#ZH43UBtx8BZM5`t6sl!~%l{P-i1 zTJXSns;2^KJn^X%PU_vN5*6hTyW$PKBr7Ktd-I4L#Q1+1;_iZwH9t>26_*k=_x4J2 zfTl@CU!x|@k}K0vHbk^^&~7SsRB&`hg%M=0B}c+*bc`%?;3 z7ivnoDgp2`)4~;$LhPR!-n8242y?mL?? zBHHL>b>Hd1hBwJZ$|XVmoyQBbOF#7GBi|dNe=rBzQ+V0NB$`LdG6Z=xtG0S%uXYYz z45J1a5$t7AdbvvnIs;xGB8;Q6e&i0twn@LO+>!z|GIekyYus|9GidBkm z3wxV!fD=L3ER1}ZL4sDwbdzJ^47M_jEu z8JB{f%I&KMgEygFrqyN~V=7`|4xb}5?PtK^5Q|u*=ea6w*6pWkbrH$EVK*XISS3+xG|EQ7DyMCNlM zvpJEerSwTA-mfMxB9~?69`{ zK}%h0GkOet9q-S6r|=14|4RRBfX-Ltn8uUhf-BwPr^$ewS>k!FzkMttf-(&Q*Q3;V zottTsE%0Qi+-c#s<~fTb4sVzP)F>Bt~Np?N22O&5MsOeORZ{c6)QA3~|QR zCMTqHPYj(i|6^yiXh-3XtE&LYA9~kL2)?$?G`x0S^4>y9Gf3h=3@u838?P#;4lnq7 zugDWZs>do8reLbG8JU}XaX6vGctmhx+E&V>Bu`Yp&n&c<(yE{Mg(CNF@EHeQ!f5 z?C2c@LvmduCvt*G&0A9;;P<7JZDQ@w!Ag<2&PlU&Rk=JEWT|Bw6FBmwr2IOme&56O zxM6y_pI=W4T+CAe#qXq z^{^!Oa%v_Oll;+tl(@CDh4chZf z)B?uTL(JgRFBaQ-Q0`cjdK`WO=D`@%U`CeRIU0KA%(Tj_lhnB>X!nf5qb0!_Zy`o{ z<6*c7#p@Q4FRDy439CK}8|ewD_lx6J=%{Ed9PSKE5zQ`JHz{h*i0%zlwgF~~>l^&m zPXWxgj@&fE30PiJ3x)q(+y3jblg7I4m8*QNq5sU?DE=Uy9VWRx9kcr z?GKv3(7;oTFCJO4#9;W!R3rKilJr=$`76brNT*x+$ByI78=A(hd)IrlRnC(jaErOv z?$BPsSSKmL1geGw3HT^@lxR@4s55NX<}bf8WtE*9ETLiY)BKr68wvHqRK}e>gKGCe zNtO#)9H6qC|mMxKtrxyaN>yyH6vMXr=Z|>!Dc&n~&oUoRzp$#I{By*~tf{DE+KY54LCk zc|VBR_jf?8yY+8i3k4Ri`JdbN$x}k+sk!E`n#QQS-umP zDiG9CB9p1H5;skc!6<-?VNbi>IpYG2KT%-*t$&H5zXInjp@zYq)oyCe*!>@qDh^?! zWl1S>lgDf@7Gp^-Z6n(aYfK{FVI4mJ?ym1s4xfy#irAJ+qs zDoY6rI1*_u?lTVHfz>8cou7t9ufWN~m>|IZljL^JXJJqVX;&^8Dle(+)ykb-WExn6 zLfAR+ExBPqd04iApD*Je!Ey3Qp?Ek0MWBzjch7v5uP?6|dResQOy7+|?-L4mGmwb2zW{cQ8Zj5T!_I=`Gh9LS=Z>btSzq@8=KrT_Kms z`-IgKa$7Y#DCDqj#uz|EOhBV@6G{BT`!R2!KuMhf95Iq0RpNCtYok}GI z|9q5y*n(j%EeB+Fe92X-Oj~dP4PuCMTq8C99~`9}Su1Nz6fNKQvilMJhpb+oE5Vp` z5nuM*r7wrwk)VtLoH?%*P&^Zvg7T(ew7I+l!(b`h+w2L0t-F-3X7OM8W+?1QW-ttw za)8-vH6MwPeWAq#WFY}Egk$`W>8AT$5JXiJE2_qa`3q!NS;FvINklTks6dr z0EERv*Bqbo{S{0He%OWy_6J2Et$?APzn`^}ew96Ztt<@6#Dugr1bo9nqv(G5hS7#U zx8QyQu6j06JdtfXQY-J^#AW4C$kbnD(Uz)_SU}BR#Vc9Le;DzapHaeL%tqXPsUohN zB=cEvh<{oOiJ@KwM=l1w>>~2UX_L+2b`_-|ab0Bm9w@laJSN32I-FAy+xN z-_I3MqYrEt;XQ>u*%-c1{_zUe#+W44p&h-p*Q9oA4veji@NQ`2wbJYitzgb7;< zlPS&3dOr@s(<5KaNcVD^ww-9BUGf^MDdmc1wh@H_ z^s#@wy`lfV154`9z?$7DJNZXoS^v5S>}*&GAw-qY&V#tHhh7MQDKj?c0Fl|;c7E-1(F*>g__~nw0Nhv8-(gIX%Hbdvo)=>XtDrV9%@rtjrkR%D zeh)6wmqV{}SQxuqDFm#}neB{g-dbZ)$I}NgrhV|n%nmq~8wiZ6zc*rny@$A`3Qv>$ zeoW$^WUn$*j~c?t*#!$6mY-s}sskdgz#nXYA$2MqKy7EnIVvjGD%q&rKbiw}_AJKn zH2*b&{PWVq`>&n-Df6epzY_md^FDCB=Hmcpzx3uPM?^qDRT%V)MtY^Y!QDf<0nuTn zJrhWXhN+T_Nq@`@KS9P;{V!Ke^0(D)fT}aD7CE_>u!vK;P|$=HY7}n zA{{}x`Q^HllvKY8Nx_qhv~V}Y{k*9KGREpE+qUDetwF5q)?hZUcNX5#=rNj`>UeyE z#UQzAhDK~p(1XXdRE>0?0b>O@oPlXi*4yer0Qn?cz|FiK{VxE&l{oINBMy0x@l(K< z3>BeKs@iVW1)kr$mAiT(8P=~h3F7U_?Rz8I>Zqw@Nv3{p&uPq@DI@?AO3K7%uY9Ge zv!eSfNs<%U&}V5r2ZovTlnZnkGWJO7XPal$iTE(CF)*bL#$gnyxY#+8SGr(Y#(g6% z-O^zm9rF5BofPy`EAm;SzPj26GBKmQB3U@Qk?i9+AKAw{k*4sbo8%ew`IRi@-9O(+ zjkVH2qbI*q`MYY{a9ifi??XEJ6xfGel?O~#*Y{sgpW0W+N`o;2kp=>T>od34O7~LW;@TpX%oLy;YeN3a*hq^JCO&3 z?dpfQpHD=lcQqr87eu7`FIkvz!!_H&uW8)74B{l!oliQ<9HFqgL^75F6!EfkIa}MK z;`K=iE&Rnu3Np^f5V9qW1tkLRr=w&Pz~5MegY|lHsf{EAlq{+SU*@(guwpH^UXc(k z!()Tn)HqHajRO{3JSX)3vEV`@HCJ9yM;B&D0D^E;0Pb!7zn)*+h`Ul(Jq~^u zMeXm~Gn+Yw%17~kEqC+qNS&21t+Jr|KDxEJ`C+r+i*D(ARA0D_jKtCpn~2YJArf47 zTqz$B$W$g2L8cyS)l3$AF;M(v6D3D-@x=h*nLawTU^3D6tk!4R>QsdCt+7doGDvpM zSsA1i=>ErlXKYYB{sK_MiN~~nfKRm28-oJw>n{%5dtREvdCAX#Z%iAPCRXlP!vP?%j zHC~TASTj-X!Bl>3j;fVzdMmRZJdg(JbC)f6JD>kOylE9$5)*7q-RMZe7#)O%DLc$jztGsIfWH9d;Exsu5KrTF&c6%VKf>pCBdKxsF8)HQGwq$L zfBi-Zrd;fUWO9oRsAAsUB!1r6S@hG3E6zlEy4;JfKn~D&QIqwxrpW!eFxTWeIAe?Y zOXQaXV~1>@bgJG??U((N+?gN^Xj=^{SYR)WgK0T<1eDoV)`w_;wXflI{$U~7Vy0zH zutz6IMkXPD)Xe|VsK5L?G3bv?8}CdT7Wm!=?H{+87(Zyso=;V6Werc9V&*C?OEt>x7n3$fzRG3T#+<4WdQ^q>yblfFPzn!Kc zhq-#yBXKT^IR+#xBm}smgha&uuDqE#2K6L+5+peedmlj(0!L`+nl!-~%()b)M^7=YOsB`%kn^ zNOM}|$4y@H-YbyCE<1X-QaxmrH=3xP$Kv_?Mn@ISn!u^&arkp}Hl)$M7_5@Xc^-n? z3Kpe|)+LQ0QOUV6mw$q;bzmJgrOp%oP#d6|be;*E^>DKuBnLg8O=mXu$1%+wsZmR7 zVS`=yVL6lxPOz7{sMSnonYF|68M{_d$Lo%!p6T5{4r<)rwLKWEGTVpJoRhA8T;aEB zBUu+X7o0s0-!!?px=JlCPe;IxV+_HEPM4V~clJ)Xv%UZ2G5lk7sXNdDPiqiampS_9 z{OcA`XLh->xwopehkH`rK!Zc#TW_Pv z6*dBwNAp_v%ihl{!StMW%pq7}qU#WRm-`{axR^Z$=B<0uUsesW#gX_NzONH?q zZvY6=&IlHzPM{cLt#kxJ3|gb8fb((lW^c`j6+3gtCghFTTM6MD`l$97PdMdPjV z9i%7|YQDfeoA(?>q{~ID%5ttChxAnAj^wD?j1#U0)58{}>^rgRdn+9yVBO8$rOY+5 z7*}Dbs(G^cO@g+*4|Pp;KD_G}xxb3^UK7Q4 z6SWO?NUlE=%rl_6!^3nD2(LUg)?^^?*`7^23L04#@LR>d&C~Vb__zt&YGwbW3;ZvQ z`S;7wMY%%@>iZg-L-XgzykNegwNLojOs#z9XOUgU0EgKO^wGHUAZ&_&`VO!2qX8YP@kY z?YR^@FU^A8Gi8>z!Lr^E$Y&he*9FIed^8Bzmv~nO{Ls0n1?SYh%jDVp*^9EKr1GQB zp4{S~71bd?3L(Ra`-4Xe4f?&BF!0@Iu8xm^JZ5Wk%&l~gUAkk;^Ti$9CNc~?sag#@ zTCJoXPe%9Vya8DY+GM8#MBHe&DUPX zW!k_cZz#Bu_UU0YUZ8;7r+3)bp8fgHe{VZL#>~shvs^yU?ixt#xQ;$`tw-&0sL?MIsN__hJ|63|+-~zyX8ran4lstXi}R*j z>gcm6&L77et@+@ZbFsuPm-CE!H2#C@2E{u6GBV1y3 zRytybCDY`)PZxQVtfFl%1j>_=e5`I3+SAM&i?oXmwhVX47ELHhsOvECnh&<-3{4IS zlN6XDOu}m8zM`Hz`b><#G#<^=YKRw{^R!usPA{D}fCGvFppM^*YW}~~@qJ8R5OmS3 z5S{VwXuZ{hS<)o)+34032H#ClL8cC+Ns}cZlBHhhzN=feWP~fx-F)gxitpe6Fxvd* zrgT$hc<-wq&|3@+afY8Epf(i#U^o#3x0$=mw>bira0t)mE|_>>O2EuRCOS>@C9)=u zyjDbJq;SKA(yge8e@;x;Jg0yIR)mQWrcbQrd%b-nLAlNXD3``>Wc1^wfoYQeHatUs z!2V+1{_j+r;YOhol{_~`XE`!@9>Fo~%QcS_Kh(O*Gk?+HYm2%+iqrtQsSgZU9dukM znUKwB0%3M>?E$vhufQ|f6i4CQs+OSnb*eHTdCKfDblQEOgi+$RW8IBZ z>_sS!$%eV~44mT1=C|WT0dL_mCf;X*TZ87*(n=A~t>yXc`C#&U3~I1T>*|=nA%fnK z$(_nJz*VU7oLwB0hd=n@UyAFm-2s@=vh@7S= zxtI**k8WXmYpZVS%9R@K)uwI_PI3FS7sR8+dEuT4Rw0c3e6XCe_wn|Mcvs^tW$txh zqB0NELIR6O@N{}S!B-;ma&oS4FMu_s!BTbqUb$*yQowPs;0m%`Cn1yS3TaRihk?S| zmav`NHd%g+MoX_2CrLWiFK>JuzI{fL8#opgP}qF+oJ*-$ee=a|LcoeqiVEgWX*=An z<|Qa7RkYZ#^j5_`NN-3`IRaRcF*&_ej+5kot0|xgoH)okzr0nz8`J(x_WzS4cB`WS z6v#$i1<^kR%wv1BiFt;aZ4;xegNJz8JnBJ5XNLyrZaY}&{@Xc=#n-s$ZlzbsCuZlY z!3vTf9t@QHK8ISi9!CmoDM0@oizDbJO|)@ou%IS89@)lW_-gKp>2t@HV-LztE{!|W z-#JgcL5c>Ujha%1LEYQm7?$~n`g|%R0(s8xCn-TB#4e;lsP(Cd1(GD|YbfE5TJu%_ zBy9cRc<)LZg(KIeu&2Oh9+CX>R-<13j!nY1t>DwfXmhEljx$hR-$D!v{S~R#7@v(_ zZoLn%fNSKB5ZLnb^H1CJ9TX}yRg&s`{y;Xd){-YC)0u|KW=XONQ5D668yQdN2fduI zp)hTxBR)2fSbo8fPjMyidNxhfALNwL#R#ZOng5&iUAK@`q8?z7 zW=LNp&nCy(yJ<-=cUw2U?YP4u?ajxtW#>8$4d~IgRro`3@_xNmLJ~QN>V1Tper;iz{3hJd z54@ei8d-ennsD~`P&+BeB{qj5UrzkaN+p^>HGm(fv6ti9d`sX)$a&j2^ru4lKk}g8 zzc``-Mbs1YZv!-cpK6%yel+Zlgx!x|w6{>kn(pGBF~5OWPt}o`{d)D^{s`?$I?9XL z8XY=M^Rm>GqG2kkoPV(Z^4A%56B{keXR!taGtt!fw4aHqkn`GK*DrV4rf`C_HPjlR=743pg>${p8=HYm^P z7bwHZiTeH|oI+r#aQ<|0W~?))s`vtEZ5(&L;db~d;>rd7&XRytRR_~bAf0;FSz|w~ z8t{(PMkfnOzN|=L{Il{AA7iaXA?KiUOT3sKgkLLGzPC%) zafi!CF~I5I17&qd9-(x#L7!$eF78&tOKT;bHZBN6eUw1MuMQg<`y8D+JIF4C7Q`U+ zFS?Vbo8)c%nOPyPK8;zWs@pd4Nfirngu0-J@+bP|oP_2tIA=>OQ;!x{k$u!bmZsvl zOPj1b`4q1y;r6W7NjQ1QamqKZuIvNB$fcSzgczlcJ*D%0SA_A%E5*E5Yx?ncqX|dB zsm#m8Y)g`z*2&F}&%=zQ+TWMgNeHd%gNx&;obxrz4hLHeI8@7jy8Wi`_cNIL!3^p7 zs>UfDGCZJtbhP4=4wp!j8JxLuJ~M1?uQRP8id#4B z#?fYoQ0(p`W`6rWR~082u&VS9w}ikNZxSw}npOUW@rcg*bvF@3ekxBoG=aw0L62>zUXdc!wqP%6g2Z2@)AGxg>gpAXUhX=4 z`k2l(+X98jQ>r^}HBzru#tJMJvV&%EO+#iHK?x z>@)dULTq70uK^YvXbA>hYQ~l@XTkK@!&poBvh63_!g&&ZLQ_eM5Et43HirV1kd~d2 z@=l4ClxF%sUm*d%7>_uE)RM?sHaZ9C8hfm5gS9#m7aKcqBGCCztM~&3dHCOKbUtyA zO~xVppDl^;8jA&-MQ3FO?HHb{WyaAZ=Vn<}Xv& z@euZYCBIPWVFAZ(;N8rqWco2Swxu(+N{FVG(rW`@+WSf>ozDc@WgmIY)eyg+?!9=$ zx7||wt-;%eWdAYUmxVY$sFFaRsWXWc^jQPgP|n`7kzXvG^nmsPJhoSVB_My^&fjYa zg@zqTfRAFgGOHE%{_lKY%$9(}KcOM!YQmZrFp#ltlxzt4%GKs{_fp?zbDm0Fq3pWX zp-!jrmW&>YTY*aSlf{>x`3R3ECYgqsco}K_r_q=h;w`g=Ym2B(WhPxN%O9=at zdebc!T}luf7lw5|B3Lpa>7JUKgnAd^FcG(@8h87ed-jmvme7z3oZ;JJr0ZczSK71PJ!v@rU}8$0jOM%At7C4;5GHg^0yl5A2?git8cTE=L{RJh$QvVv41_iW>A*kEXLSX1kJPku)rUj+D%gg?M?1+;N+OlzzL|kYXRYJ z5bQGQ{2Cmv(|{+Kdu&fB%kol!{E%g-BF|K&#H=b<=W?>#j7_QBm`N|*K1Qwj;9d>z z+);jg{4C_V;rD`QsTA9n$S?eFclUo9XI&*YbGUU}wUUSVL^u;j~$gkL4S zS)Xe#T}3Q1qoCLgt_Tsh8O>c9t5Jh?3)Z@Codd@;;S7_;80VM9*qa*}k!Pu23V`KT zqAz;}0LGdIhKIQgDIWe&JLH0XOXv3i|3xpXH-NOQTB2{UUa(e2(n%5yVe-L=83>6) z=YAO$3h>>f>$RU)5OY}Y-`9GhZjc;V)7vX8pb1ODm@JveL62L>H{$0jZkFJ(p?nQdA3;)bvt3y2TPyBO;TihP0(bGbF9Zsd# ze2&6Y)fDPbj!~1|z<$ZN&zpWKhM`epXiE+jU85Rr6-P9ZM*_Q zaE3&32uUa@YYD|0-R%K*TtSMFbWG=Fv5utDiV2rrBqc6!n1jv4J4=Oxrfs$NBIAX|LoX0pwz1ji#c&k z0=lJSi1>#8r0o9)V}I>G^)y=G3i-CSm@M+|^Oc*ziQt-r?K9|4kTY>J>C!oFGA6RNSHa~sdf@V z0hepJ7@^f^{+Ncm7xxv3Gy~?rc;IwP4W;EAku~a+?o&XmlD-CTGUFu7|{ZhccGF{^r0E`~l0gSDJFZ^`ChbpXjm63nwgX+SXK`A_K4H)p zqi)^9Y}825bkr2}E`DA{?`_M^p(n&q&HNn~#}l`th3~+i`U@73U6OFgVlAoW?1=Va ziCj|zi;lT^0ruyQ3oNm`kxh8Q&2(t~AcXJ6AHn7?aW-oDwuh=KzoPSA17KHHMiUX@ zm|e*T`^*shT?@$iYY`$1Pgn;a>c9Hn2OIe9>gQDBaZi}BWS;6jB)Z_CW-r6R%9C_Ik36STE1!x^=!!k=q-w1 ziR}VrCXQ;dLKlSqb;`8;m};QITPIm8`g!Q-s^h}T;DuI z(x5@TqrKSk6%D;BRy<3Yt=y;!!&V%A6mweYYc_3Y*}(_nxEtyslJ_OO&`{$@*8?4< zh#qV^I1Bt>m@J!?J2%!thRO)d*urdSYQrx=fzj_1Z*o({AiHKd#ATNrT|M(u%qcj~ zIrISbGo%kS0Y$3e7LU{^7k@w$;@0N<{=r{gDh>O0o0a5u(9~dcC9`Tx3LthXdL~K} zHwrbI`g#h!y5FXgbb0#FCb8E1d#fzZ_Iu+1NdETcyuk>m+OdbQ6>1Lx_@MDvW)HGJ z^lfYBPrw;m9y_6z~RL39*HLw$7ML2U>}l|GkwMP}^@i(pM+{FS>5m z>h=;%UkFqcDv9^uxj7vLY*RVvWjwK8a+iXvuN;+w<+M+YQ%Hh|YyuLRlu*|G=1Otp zl3a9w=qi`D83+75(dephWc*hIQPGh@B+hUv8ToQ6(of%Ke`y;Dv-(&OvfDg= zzl);G{i(G6ZT4@0^rqhfnz_+L1mZY7;SKh^vB~+e^Taq`&*F^>}HNN>%EyGnG@EEhuj2bW|&f&T$Fbfn%av1Sk z_B`T#d4xV-dvIb7Wuo|U3O;ywW9E7>S!A);dH|R-j2;C^CTM$!5W4RI2(jf`99T|d z-C?RukHKHR&=msiMA(f?EFzb#`g@ zW~y>5PD4Qbq%xU9Ood^7Mu}fQpk``YN^VMHbS(T`Z1`~j!)^<_#Xd%>>Cuk>C>&K+ z%ZiME)LEd1K9yp^mg3lZkvn!u9VvN3{Rmb!xT(G*C#1hfLPEkA%W`M$sW=E1bo6hG zWH<;-Jw9B`x#vjd9=WEq?HQEY4;a=3x@$f?Kx)w^^VP>qY9M7tJ~0z39xhKkvbTv3 zTx%F|CjuiUhuUDr@Nawm(0df!adHgj9Neq@M@~`J6f1+nnu^@+$9xXMW*c-e&J()V z-BYBbA8x6$abUt_Wf-<1eAuaez^hKbjGj0&(quqrQ>HNFa{!JC+#r=V&*$V>Jku%X zoi^xsaF4F*rG|U5WNbj+(pEc};ucy>g77VMHpy&6$ocTtLw)P$>GIkN*Z+o{<$8ko z_T(sheddO#vcpij9CHoHNfdKI7?hn~S7kh2)MDZVz~F{O1u}|9ecXu1{dPI(Q5LDk z>cfmE`T9m(R`A@Fhgyhfcr4m{nt0FR-`-&lObZ23EICC2yhUZ_Qn~$dVoXnd@X%F~%nXV?wBWLR3 zEeHJCnC=wNd|$r?%*8CbSeD}*cP#Fh+uD|g!1%8?=hYBfQ)qA}*3&JKjK=6xU@)?q z)8-mLF+)&H=oH+?j;m*{dao!3g@jZ` zu&R8CKOaixzh`~AiZa!hP}1yThqN^q@}6J>zu8s0IZ}%R28_nIr>zFxEn5URPT}Wj z4O7w91gY*#^8rJ}f_It$6g-D6Te-=kJdi!GMozlkTuqajcfY1@FuM+NyjvYyy@Yw>ZlPz^cq~c_r42)pZZU z^*tpp5gGxIq{mOr-Ue`I_|5rfHQ!?D4*+b}PuEe(qYTM#QCz;}3-&LLwlntWW)B*cd)ozkx=j3#KBz#jHgfQKi0o|kQF%$zw*+E|+r+@t)#tN1s-0D@zH8fie+jks5W{loeic_>)aC?_=~If; zVG4z5Xfviu*D-cEt+c-&$?GT&)48g#S56i{_LFYU8o?IFw0ZK9^GkpUI^+2CUa%xQ zNNWh|`*Y;s7M5hEem#@SjfWF?v;I}OOToyK3(|Z^Iffi=O*Rcgz=(? z$`vlQ*tJP*Nsz9`i;*BHJm)ph$(gzE)nTaZ-Ov_);tT#>?Hq=;MCIdO%yN#xohc%L zeeFy!8$BcJ?BYE$e?f>BUwZ?9{`YWG^E-ddY{x1Lmd;+#$83cEon!xJzWN6bv+^YnlRc2+fVCM-}B~$@mJLoO#6emr&|*S zo2L@jEo>lX7=0|Ldv1-|L!X{MyceG@k+}~=k79!^mdV;Oh1KAc&$`{H^9#e=;d$?Y zr-H65eM8|ndO#$N7TeIm+Yo`#L7}Si3tJKcY=C;}vK_dYI|Uq5;3(=J1XskXPki|- zWENr1`<0sX+=vqh+99NOPZa^+jM%4{^qCMz%JpWR`@ZOofK}MEqy%ZTuL4a=+kG zCzS+Sh;7 z4rTNZ`AQv(_FlFD<%RnZLgcQF-{3+A^z;xW68RNEi!aiSiS^JkVDBh@d5R#whBM#pvqqc?WxW-8Lqw%6Uv@Q{!=8Gw1k8_K$JYN?TK>2uZc-;7F6x8k^ z#n$(V&fgN;uV%4u^~h|!{4)N1y0UFUVHBE?@MwBDH-^S&`f&L@EN$6YNRjML5*X!l zNs(cKA591SrRX-OTOu5I@$P;+wmVZ?dc6pE^ORwBE}*0EkhYAOny8iUW(*p=_;Db_ zHINUTtS%_)x-o7$sbp1y3SaH+nJ*NU@ekzXTAtJ+Egt^ApI1Y>2sPMEveg-`ivIVr zY$zslm%b$IjdIqF;n2xCV^30xEY3G3v3iHsp-^SaU9%eB%jbd(!MEcp$J*m?>S}}} z;H07!U^D1HSoA9*7*~uK%e)w$hvPeUGBKq}xUO%6Lo8_Nv^c5f3ov#CN4>~hBb_DtW{ zcKF-CjL~z5fF+$9|>LnF)g!5Xx@{s`(8y%u#B|8cFAF;jC1FAUwQbHSe?je_bzrbn18#xr8!6r!Ru@=1bTL24nYfxB|RJ27>K%8^8EP3p=pjJA__J@c@URNZy#JqCvPwW+iyvGM@ zIwU~@b&~u0$4^(o8*6D%sznQeG$L1V4B2a@OJG)yh8hy3Y>h|{i z7`_A5Wl8c|6Ge|6$>z-tq^TUv9(|gz4YPfQW#3HqMnO@4x`o2=okIAeTx>h48oTXy zO@92&$=8dPTJze76LEO`OvK(^RmwfiBi_-XwT6Xf%n7}gFg!&Wl`sk|z1~axhi_J= zr%&DsfNoZJ>kGNxr%Y&vz8k1LnK3#k3l9TQD!mDZ&D>nfSf&M$DN~jDhxP5`P_&Wb z3t(#s+u3qJaM0ZCT+Wku5Oz7a@Dii!sjFQl<$rm6>>T)*7;_;O3CWRvqTJq=SR&z# z=b&_g)-mzv^G+KOIchK8Mlxk*}Um5x$T-ZG{cDjO5Wi}lir`-G~go`Gp@?Sowm`ogimT=^9X-%8XQzghvOtvrrC`anJ4 zx4@?2d^9|qMRhrc(PKcksKqXAXN?coov_Y$6KNgS!j%me%N;{1F709)$A zbIPYI{Y(3MQKDu!q_ZLWqGI&)FQ0pckV=<#8a;N*KQc%U;3@3knM?s3-bWDl9UkV! z^+2+|#S7R^I5iV0IobNp0{)2~t+#5|lKcl~e88I04K=gATh+UD7in#jj?4siR{%_2 zxwyA15&|ORwp;0req^ACioJ2r9q)9|As?pCA-1Fo^Kc;3zX2Ryb;;SLEZQj68&L7y z0=_Sbf}0hTjhI#(4UeGQ7;`NK8({_pTNQ;oy`eDH#fIlvtuZfHR|Cke%wC&d$4Ev* ztMqn@3AXy-FO$ev3i6PD;$+wB&e&(rVJ~A13}mXYeAw^)JkdKufOZkH`w54gQfVRE zay#-sNFtTemxC49)D0+6rO3_v`f;N7iw6^ZB!_H| zSa|!)kTq6u&^B?&uefMW9>6{%#GWIfDj8rOdAgIpDMvItvagcsRx0z#*LNA;#6)JV z>08ouXF98H$LeMVoPG=1IGY;V3~5y+SCl#_GOl2%WSU2=Ik+rj(`_o(;m$3;BDUzQ zp)v-cV|@1ICn~mDS>>a${}^u6nXw%cpgL>89U(5P%y!q6tuI<&>pp{tP+FO9@7BrS zvKTZ{IG=9mLOk9d!Fk{s{-pnUc`*x2I+lRC2ji$D;6CY}w4|VN*6cCWP~A-6-t;uQ z`IsNO2<&V+tj(VQGA-dP^GK-egh6@)Z4r8B41*_JW74F&bo0kAh7uxeG##5PVZ(Ga z(x>hG;rS{TO;20}DkZ*Lt55fF2Py%lF9DS_bVOcqqV&_wwXIOzzr;rpQcO?Oz4nT} z{;oek)6RzWm!Ip;b-nIfeo(TlTKWU?979;}(P472vzOF5SSLA=%jy)Z(fR^}aG92L zPo8{SQCu)g@frhgt0dqg%8=T5S@}a`Sn6?P=qEI5YTm5(DO@?&>>1`wxVxU@62Yw3 z3q2tmU;{O1q3Irs*Yj8Jq3Q@kk-RskXorM3`B4B=o>?vA5py%jY4BGzYe^oQNK|%d zA0^Yu)M>qVhIn#b)AcKtJ@8&*g_j;0UC6D~gdnFkRlg+QQ#S8XqY8L}rvW>`@z>IB zn^P3q>*Q$Z0OJ_HH`4$aY`my)R_%NsasW8x#&hhrDY}p9GU;)fI?SJ@_!L!N3Z0tZ zQ)mEt;1%zTomSUM1heAzvI)ayFdG)EGD{Hs{S#moXscY5BI)RX1&QG*C;+*!Q@v}d z6U4Vd;!kNp9G-u%Q{6X1SJE$at-2`;7)3q8A!V2ab~AIKMWY0{$;WTMhR;Oy90B7C zt=>vwO_8NThZq4{Q+{MOCkv^b(oT?Xd$Sm}sf6QBWjY^MXpjdhyz0r(#LWX&M2ao2 z0KPjTg4EIYN!76^fh2(F=2w&dK#2icZ|}}x)?)Kzx=;{Z5x0?-F|)h%vzuZ97VqjX zQtodS%TZAJ3{ql8el0wSasrvbFP+Ws>YV z!~{uZ?6x*Ta4$|rTA$k2R2iQYl{}U_pRpc|qXim$NM`?>3jf%!r0#&#ExFk{LWXxm z+>~mXodL@S1Lc@tn?}CE2O2dGk#N@Q8ABbrxdfSGPi`>MnA8LL$!w zC;7hUutjAQ_<~Frw$)!b{pn2V0^eFi+SYIxVHuoWO;<30V0c%#_l1F8%|M>dwUK-w z1J=1WAH!eL$n6C2f%uGF8ZCE*N64g1Ieo}{CKwHlP+37JS=0CClHrMglty3Kt2KC( zy^4eyLtFWlwmrJmoa#8zE3UCriC)-*iwnU>(2Ei4km2)e7t0cdXyO~DRh`TTUfQlr zM9>nqD%fqF@BJ4-+y)3TDZy>RgdH=2&3XNe$#l#+@$oHo{WXHMkFH$G=i$=JMm|I4 z9kbGTArZo#PhjsBAN9$&Hv13;l@5p4n1ol~#06#l*?50v#}epw=0Q-79oQjo0{?Zz zh>yc8LCS~(3F+;OuYdvq-NwbWQ~9N9g00T^lk_6%RC$QPJn;Bk_|A*$1)wRBddarq zPYU*V?UP4JPz%r`)jj*U+lsUhT~RM32*c*JB<_z_e8uHmKvi?wx7-Wg7js<<`f{xx_C!5I#e=H%IzV1} zGy&U|ZPJ|zN-vEV3+n;+?o`>XeS#;SJ*RTZXR0zosZ2D6P~UZWYyKo@tD7{f?n#nL zgg60SWmEZr=i`XJ$2T0I z!iFEZ7fTqcHv`SY?_Mt!p%w&D9PIE1Fcj+bXuWmO61$^2bi$?8cxfAMDK@GB zW#_LiXY^=dmuzqKKeN)gj`e}D$*z$GaK<~WM8~|~)`1c7v<^iP&j_Kr5o?WXK@o!_ z)n6R)++LIgitE=G0jO8UW~$~s;2X|#kPnHWslUg)|JVBb=kbDn=NrhW38y?IkU>Uz zu@zn{m2b@6;uHa--MzABh)PzZ>_F*tmu#%AtPdZf;j@*ln8_@b1gwk8FGqo{UQ`BI zi%t)pj~)qimfPFt8k*23UHeKc(v63wSA{JquPbN2gLgOayM+E+)4 z+b+raR6g=!8}02vF6F$b>)WiwQ~avj`~1kJCL!yM;Oeds`rdL?Yg=66ya!1RlN7i< zaz8MP-C2d^*+GeU|4%MtN)VY^`1)}LJ=ksh zYdk)TZNMH>N4^cp@CLbayee} zTsW;xmuI<;OziKVPp<{VtSeLv!xBX|#p(xgomd27IY&z_Yd5MpudR^K@FTJ~UWXaR z(QW4x+!IU+hCaH!nN8J`s%G7;bAgwn{pzza``d^1AI=Wiceem@F|I4*LQN%|1EQ`@ z=;B=CVlgH4`MUezGcDe`9D(=IydYmM z@s;Gaoe{>(h5q{LUc)dGr)#U@XALXV*(F))2tDhNb*Id>((sPiwBEkay;i=lyire7 zc3Kg}qE5P1OwlTG<2khTR??JxS(ohz!%y}WcSG~{4?rMsx)ISR{cS~lwA|SYD(;7fprM&G+dwt8GHe3&U@LClD*Gwz~ViC{N37*_K zVcV@f6(M97)&uG96+0ntwB=xKmpoe|2*GN-0n_6b(^$t2lD$bcPKlosTG-0kt>< zYqu5)qBf1N4I8zqc6??1tMAXaW(5MQ;1g|U^Mw=w^Zoul2eyiNc(JR zVHo#7%c#iyByd%G(D&3cIX_-4&%U1NseLhKW0;Xd4c5|JpFrX@DLCNz*#`^~OT2XH zxQVw2DXJHkCFJUn@Am>8bT~BrlpJU!H&dS8yW=qprOkoU*eS5nFDYtqHL5FV$+nVe z!*DEmcJ;bR5O&UsQP(P@Y9i` z+O0xdUp%qw3zZR!_Qe&VqXnT_{Hv@dauOT9m}pUjaY=IzcsTEIjS@^ zQGN3%;i4+%D=$Xibm!X z#0E18hic#H(g9o+rSbOaW_Rs3uL(){(pszafs%{`knvrN(^4R+5d;Q)E`=Y4=Q};2 z0Hi6LC)W)aLh260JB(S=s07KZ@s{U&I+A!{?0cV%-qZQ7;c`3sXZoR`s(+UpF}`mcF8go5FAw%Kay@eKhzfZoHsJln)<(7j#)v2{e3z8h@W-p5cN;i7kcFKhTA-aZjk zb*~!4*bWKV(d2r8yJQlRD!L4wn`&d#oI@NlmRm95Hu(QU87s(f_Chts z@t4;l(L6jlivDSSd%ukHuz$fw%Pz||{;I;|=acv#=z&y>kaN|xZf*%Ma-`TSP494y z!<4VO-=4P*QC?WOUpOd8;JjOZ<8s0B-Fx6ti|T?T{nOKjnGj0C!iR0`5{DjCm(;aD z4WuZ?vh7krXA=iP7A`w+TgW#|`D}ww5K~yTg)v`v&=nFbr)u=13ms(|ejId|`6lsg zkQ+A+A|u*!nsu$dvWUjwBTuT)FTEZ-l`(C)ldw_FpVZ#^8c*=PV=kT$2|^^_uI1Tp z1#OkY`tl&MzQndsUdd#5Xng9vLY+h7u8;7gh6CVWW`ZEW?(QM*^PA`@Mgm#ztob~j z1bzIjsjn>=E)t*oZi5Wh=c^mQ{x^K)FpZZ~3AW@jxx(0bC^>uI)y@dKB~c?Y=@`DhzEi!e zx$2Ogbz_13Z#f!CG-f->Y1I}8s?#~d6>r@QCFqfpjcwzwJ+goP?eQU7>9melRFC5s z;KZkL(U8W2M;}!A6sAw~KIcub%X$^%>YMmY#i@>m5vu8Fn1px}E6OR>X7FHmn1zt) zj1Ei@+SldJb`VV4t3$e<^7hpXhAr*0E;;Yb^W+h%l!9bzs&lh#Jrt*DxAUFTEvH(X z&J0{>g)?cPcpfWg0maF&xh7bETX`n^9tWRkZlHQ=bwQdp&2UeU3=CC6E$ zYpJSOR@bVPzd0wG;d9KY%}s3@*iDgegg#zit3dSp))gwIAjhn@x5r@xIG~TMlx95T zh5%JmskZ9{28Fh?_8vpkvj8%F$ z-AUu3a|2l4fc>4iIT^pBviH{KJoEn*L`_A`a}c{bf>NP$CCjubatPMBl0JMk-N~RF zL(z3S>Uo=?p`ZVbUXCs9vrmC0C%`aD)1V$BUXe$NXkS1Aqa`i)*ZY}1>3D_?uO?1% zhR6dXrLHK5hkDcu4`XhiqP#hG$mjq*V>|0i$WQHylgL&vcZXX+FyaW~;1Y-Euvh=WHW4pagE*1yMIBMVTZ2ArVK?idcj9^8 zoiknJc;$@+oqVYAV;b#+BoqS~-1DHfO}ZlQN$lFkJgn>=S0|R5p)JuhVJ`b7gsG1Z zR7o6`e3SFUW~fLL^1jDUCZpXcf#2D-PFs#ntdQ4lbG=G1t?aT$BE|G7xeCx+`W1l2_ z-w^j%I0tH;<(efy-(#kOAJQCVW#_#f^0J%fhS`+eX&Jq>{mB|QAJYnYcl$d1r*@=c znY?B5ipjYx7m2W0k39Pk*p}umn@MGd0(RAxku^}j+*fx98Bt!-65ztFe+o1;#QQ3P z=HFfe-eXelCJMvekj703hnrY8e|~*}1l$1L73=mGeg-ePnUsywPs92IA2E@%eZeLh zd!ht46eU23(x05j1P}wbh3%t(XpbM1((?YIBK>^yCBb)G$+*yq+t1@Uyahs{HKx-F zd#F)1W%1qUC%w`svae&8N&5J@Bc3#Xf_69pSdNgI*Xv#(=UU@bP% zp-&KmDb)`=xZwPG!#X1}HN_!`V>T62XVzLLZ35b&wkD(Yt2lR zJaZYu-v?b>&RAU3C0%#89u(W{5MQ#|g2GJ56Ie_4*8^12CuiAf6>{fK|MkX6Qixd) z(LuIVIy$XwWZCT%jmkM&`YH_o&R}=NnRkemCmA6Wv*C3^j2nCzb;z8R>b>w@FQ=aT z=<2GkoknNSDda1*$#7Ol>&$tiQU2y^w~wI8(I;t>@NFiXc-o1jFUw)8MW;@&MOR(+ z)hTj#iu3Po_zo=MmsU&B2N;!UN*kXXS+w`)oh@3!_NU^t7{`ZjB~Rr>qU0yiaYh&! zcefRIDssa_@8qv;llmj*>!N=w%c1Mgos6A9Y-jb(^Qv**L2X9-L(DlC2@Rh9HTP+H_iLw_bfOSAFY<03G{Un;$NGbJk}Q*i>az z+NzX+6VjSkwJ=Ap*``J*C&a5*S^ZlhmumUSBL^NcJ;x$uQueD#aSCktwOSkAHvT(wM3@cK zwpoc+;jP8AlwVgE8-{H4<{mzai1lds;Zs55_6eR(gxj!9btiuC9nbh)(tbB70Q=hvu`Li{et{eWr~wzSAysVF*FR71q*EdG5MPYsUXVOz`rNGj*%=;*T7Paoz2^{O{O86vK8 z3*LJRf||^Y{aGskV!4pZCy{uO7s(^>>>K}6njoNq<+i3a0T>@0L}tMl?&Y51O4M;r z%w=1tE|Z2>czlbn3>n$JB218ww7K7gk8%!_Fck&!*&OHGF}?`x>e8aW?F-5I@xumG z2e?QM-sW9K{;PX6$lhY)IU9Dioi~k3I?(x^5O?w=p>BZ)kH93h{uN2gt**CzYsJhb zMp#R(8EJQhONA274cHSklMs zQ`J+nMkY%Hmes>*+O)^s3N1@%Vf)Lm4RiG#S}M!47-$oirOD;Z94(zKzkax;ZleSxlSQ=hNWjCKM7Uk3Z+~KNjo~AgKkGr}z;v zEL$(%dnox(;lcc#0Zf@bCfezG6E2G2w9V~twlMF%EL%C;KpGB2C;v=du@j5$i%o3{ zS?F9H%waf5(7L#ctjt^K$;{T({iUj} zveu+GJk5p?OW5~%L_nZv@Klb~HL(c#$}m!kMW!kW<|@+ZI%lm^DquosUg?B{*}PmvCZD_~U5*a)A0M-+JsL4;&&!cJpr2pT8mXxzm^62%VE{6Efr-nU@)h~CP%IS`(3>OPZ_N9=37XBG(z!{2F%2$2 z{~3G!stc&=*mm+eT}L~6Up!}w8Af`N8AkYH-e(+XP{)P^Hc;9|b$^X*E!#qDRD_jL46 z`}d>RGzYTM4U7+ob(edAn%YD@UKmBkZIs%*E*(`>vfgSE5hbw5^y4^qm~ZDLXr{dQ zIi0asSyb%$llGiLOHSmT8N$Up%ky>1BG8$t8S?+|_10lguH73bC8JUzN=ON)fOIpo zQX*1PA`Jr4-9w2|64Ko%-JJqM3?Vsm!_b{Wop<){+u!!P&OYZ)V7M-L=b81ab+0=N zWvsJQpr+?WZ0=-}7Z)O&N;)bp-`nmRb{XruelC|H1QzUK~5{=OJ~RbGU&&%jtflgBb- zOi|6TaDIBG0C_?=X_}nel5Q-Y#gDz*dY`c0I}bYps6x;(Dv*2LoZ?$fn<$RE2vX<8 zQT&sSw_Ccm^u2ry)Ft)koS`kGcMLmZDMO-7!?-`Cd5~=&YvFv= z>CA9gEX$x5CNTmJ1b_nJp?iOyy+5iCuy6qO?{bLZmc_Nk_?1y^8bLG!+2;YWXhh+< z8Kf8{^myq*VyxOR9XO<1M6|!5f6cXUtvL!okwLlP3W-%*n;7QwCJZuwsqA~e!ouHlmxzjP7% zUu!+GhNw4x#b%nU?OlD&D-12nF30BpF#Yjfw~*py+mF8%t99UtEeEE2=3@O=Vm02O z=x#l-IuRe{vj!h*ZSdm`x1d^JVx>{em%6J+ws*89 zj2bLP{7Y{>3f!}|{;iaT^YkspP*A#0@2lBzF9|wfV8JV+6Y`td^hjgecj$7zVJ})> zj5^v5zd9u|+A~}EVrr3{;*daPehhZR9mQV?{XU1E53h*Xp#mVe4RhVggy$a1QQs3C))S4QloDgN^TpxivzN{nNgb|#zVCl8d0T(e{qFf_ z!jDF9?GZry=MDAQPmF%KYGg3Z{FG$)s5|8q(!^o+?Xs3cbFsPf@XGmCHN^x+8slpr+RQ zb3kjefoJU^-trbmY1OP>^do03nmdTmB+;^3qh*u- z`G!#yK%L1-y;`9kyHX2MdAzx`#g!Q^HQ?%-u1GJifg-zqv`@Y0Ga2roPc6)85UJiI zz>govk&`8*)_3sr!IOIP02`AOUkvEbFU$lFYL;Y^b&eKk4Cqar_@qUGJpw+8JsWqz zZ#@aJIWTv;RmmA5M2rEMG{4Iyw3F^uJTIHR07?t;xPwR`tETy+Xamr@REbKwsE7FyE1+0$>SWJPF%j0!^naE0QaA zbIA`{l=0$vIc-JCAN-y#G#1>B`n<0R9r9SrkS4OUcy&GeO9+a?yp#{k`A3)-6YVn3qT4=1|bWE3#k)(?t6z zpBY7UsXD8#uuJOpX>I4po^xp|J>Qqje;%#>KoO(|cNfrvoVF(%u21+7l%I1a-0s^e z2Y^{Sghu$<%VlLv@bkmg>Qc6j^wxf^+jEnwCE0=D4jc)hvMu7;(Mk>b^z$cttT?OO zw_|lvO9|ya)_Rs`W*{)c)wKLLPjPe?NrrsPJF``SY6D9QL z5@%vc`8B*X;%sP;IRCJWyOF>j@Le$+5uvH#__*Z)wPX6A+T7sn$I~FV!Z-8 zY+e^I2Y`D%iG1*ZFg)VqWMd>(Qu8TnCPM8`lkWGJt6HJ^`=eLYF5ba zSD;B~>{u|X0#JP%X-#x>?S)kt)%Z|^1*Qu=0c!jUQ2yTLs;k-P8D|j*MX!6C8=HJW z$U2bgE>8_aC9)KVEIIcq$7ou~pwM(WV(n~4q)>D%?=WVKS!D;FzXGx=ft`G+JigUj z=O{sKOc0sOcX=HZhlll7-5Giq=GJ5qXwQeV;UzLw*2^0m(Rh6@_KrYQ%k4^Mm9?HY z_wTCnKR1y%nveYU>@Rc|yqSQ{w0|~BF95EZP9hg#o7;9ci(AC{SfGA_olIMR+t{J{ zwnnU)$6q&U_xyeukRxFoZoyis=3((tud{0UNo{iL6I-+#C0wd!akv+7#X@_ABh zmQ_dPE2fSX@YFEK3f%^<=_@a*J?v!~0g?hm1Y;v#gtqaE0_)`Z>jcxT8aQOmodJFU zG$xeSQ{$ZpPH3!;Vf>|g01EK1GgNCH&L~vb;ip=lwop2nSq@k4kjD zK2oc&?r9J;#KsYRhAaopnr=S!L&3d9d(}0at>s5e%b|iF?InyC>P#hsOoXU^A9PXc zdW?fGTTq;ejSy={=D{35VtgoNaW(sm9ivlh3$-lNKH`hLtc9^QOgLVX478AT=-Kdk zUb%3Ix$n8pYw8xd&6?j91OqfhP0Q%d)cz1$%`X@}^1whAc=lp%`wmX*3IpRvAG*u- z1cyj-pI@N`hHr(HeCewy@|UnyVpa>iWoTcpvPas6p}404Kj9Idj_cq`nW3>+|4~E6 zO>Z=nWLL6V6SH6as4q8b4!e zqr|b}EZ#}Bx;~ZW_nSn-Ku+obQYXx{QZV~{7&oPiI(d@#ig?!1;z!3?-eh?rOIbJi z-pIKC5=$lgh|r+&VQ~$q1J&wR;Hr{(Gv$qa|F{ZYligRv;z87%VTUI|3)at>Jk&%@ zF||qY8y0$|(9V@pPB0b`_?>zQ`5s?EXB|e)6hmQTdoKy#@Ovb^#+CMh1BnhjJCLiC zaQu>0S4x9}jBguRRek8^fKV%=sObyX{HCzyHGcr1oc&TqV72G>5r|CD_z5WAnUN1V zTF)cj!+-omSSfIbgwm!J{Y4N(BtU_SlZcXWcm#y>@q)bWY(5Bqs&FQ=- zeKvk2V_u!36=V6=NJa#{L?&$gbnygb=x(SqOL;Jj_{rY*tfL0AUJ*Wly*vIWJnEQy z->7{-_-v*^iAc?_T+u^iudO~6(>3L-R>SOiZAZ~IH`4gnVJELpcaOcGz8@IEmP96i zA%iLp|vDFeERY%Cq+Pys39KFD#t+AGG_Qn#1mJ*TLYg znakhH2LI2QixML_7!Cm9#dn@*T3?@V;IwkKTD;Iw59=4}pLu`K1%I8*#G{KqkSD zP=UkJ{8!&N$QN}N(Poh7{D>rb=GW$1@2~AS;#xTrDp90h6oJ6$qMiDNUB|`1vrHWDCM_r?|Fi*Iy+xoT zWs3Q00h?Zt;+gJQJzV379m8hPbCu?)gB_lh8Q~{%&vs7$H1bFFtdnK~ssOZ%yI|?z z8Xp%jynhuKn8XXKbV5h~z(?6*$4xY?xfjfKy7Mdi7)k0Cya}U51;f=)7H><`2=ooGpN(lTR zk$>woscl(IZl~1)+sqLDqckFMxRR?)e`~`tXd3^KT1d)~9$@C~ zXhSh@r^lo(h=T>Yv92>|P4JlA<*ZWcOBwsEh0yj;AW%-sSZE2bp;R6w8^hVec%C(6 zBkkz?nGgSW8~mTLvT&CHW8V=z8{w%zya4I7~k@h^m9H_0W~c?XiNP z6bvM-GOx&_V9#Xw&M1Dl9v|u`%uB?39VA#ho}~Npj#8-G14o*Yt*c(4RW2_n5ke$+ zRl(s(IFyHa?76m4l!E)PU^o^g`R{8O}s+k8^V4 zKx-29O>27jF^Nvrq_KW{Ds0CbV;?d1hPP@D$pdd2J2dB1l=tux6|#)vG(qgpeeYR^ zWL0Q}^HFOCVD1NYsjp@!%nm1nqFn*miaDki&aG>v7M$JCA|Q>Wpcsb#Y1RuEqV`SN z-_SDBjQ~CpNh^7Evrf~`c^yAd5^Libg$eHRPjf#s(d3O60s`SJ?WOcZ6v27(^496R z{Iobo#&HCU2a=M_H7Ha;C*2W2jY!@M69}&WE=@hueV7JL;icfU0(f)^{NE8ZaW<5%yRsxS%p1I z?D#efv;LJLfD06miH2{O`@a{dlQ93OIDq+xSi`{+x3Ee3fnNMpARW)C5tBK?TE1(~ z8**0^vx2@mc5Pd8=qNMR|5HV)5`bo1N3KybZJuz=(}bXH%PlHyK`~!IBo)6de*Chb zl^jjuKMZ!07M^;7re`7c#F5`WADakV)JL8SQ^J-Go~f*YRRv3K3&BBV=Zjae9tY8x z*!ZoacOd8M?FEF>pPjQJDP(~}n*cw>%1c7-!qHc>N{YJIo9fe;{nx`sqp!xQC0+2Z zH1yQ3RRiy)Z)44{v|;*J0o-S{5OO|NAesAGU71)mhFOL{Xy1Fa1qia2L9GA^j7<6y zqES;!L-WEHoW6z31dXHxiM;ius?T2;`;!IXxa+w zJ7#pp3>lGwIp4-Ob;%=-65x%{UGQE4(4MfDk@FE^qyddd#iEBrS4}L0;yr=Lxt_Tf z@_55|MJ($xp`REjPSM8QR->;ovaEGko4Cw%E>^Y}mQp|+^g40v+<0P`+s`*I-id~2nzu0Kmi zRCi92yrk12!1ipkM+vLl6>b3e7B79b8`-&Ve;h}9#087tx!S|CdnWd{L+5f3fKMPt z%2k=EbE$iXa7B1N@FDe$NfS#;ER*apS6FA{o)n$pzU8F)m+fFIYQUA{^c&Tify!agI?p#okhy{A zqes>g?`s;1op@r4tM=?6#owOS78D{8_MTBbLHzF$i&N8zV{0DYWAYWs-?IE!2w!f9 z83ZTJB84G-CjnOkoJ}8`*r5j#aEeBc!)Fx(+W6M~JS~zqBsqZd$&ibp@zcs2VD_>F zWIwV1J+3Oy{h*U17f=icq6kej=1ErM;GwFI@IA>rJHX(*T7zixAD;lt#rXnpjRecqS+F6{}gWrSI3KpM5}t!%q6*zzo#ri z(1Ok}P?q48zR|&p5vgkpW6mdsLU_;d0Squ`WApR!uq>Ngzr!$zwu6!YK(CXF0@7oj zA?cn3UxI%l?rHoMeRXxSm1!yqmFhaY&|m&IM<{{quXCJ15-(CW1|9sXLV+;P+92~o z2(@dn^&|Ig8h@3&dwa&b9|6dN!u$W=<`5*Q>ECbe|GeYk{$li}Rsi?h;T(kc^Cwd@ zzk6JEj=9!8igA94d{u7$8(i;*5%emFN%s_oN{czS;=;9BxHS~x5-mDYhlP!#`&+3J z>3fhKF0RDgFAEPRC|^Plc2U2qK6~rUy&5YZQ*^X5C*gjwAiazV)Pk_;1t5|` zeAP;R&m@`e!p&9qv_&N^^9S@tw&s#Zno8#d@sx3*rG@{6&&-}Y(zl)Mt>s-K4q!}q zW_soU%eyo~)fZYqfDU*vPK#fKA`c;}M6>{2kDpa}&x;p0@~ix@&f@dP@tR?f==rOH z!^qUDEK3QEgI!=Fc(}bE(*E)o;K)H~Urb9Le6!!Wz4W>ryvYGJ{=#D-N?CB^NJaZ8 zz=1a_Iw0?At4kqe=+?vW<#Gz!`zp{z;LF7ObfM-8=KfL2S>xD;*wA`%t6F^|LiBdB zqz~ZQ$33Dsw@7(s}={&Z3YsJ01^18s{ioxj|Sw`(- zgcqC{CoM2T3$3zH^}aL+YAPiCO(V()#TPHT2hvNY z?)vjg{ioVU@d6sn%n$VkIn(dZdGvps_d<9T)VL&GUMp3laZlah9F5ynOJL8|5wQMMTD_s-Jo9d1FJ%FBoi;H(1qUt-l*xNdZ1A z6K(lMr)1vz#Li%I@vu>ui{SeRG zc4|Pr;W3zNB`t-Is^Gv#&Op`aG+SHWWXI%6&T1quosUbcwA8K;qF9`jt6rzfzlCq{ zkvIPfBjkguR{1Ae0oUCCE%=fBTo z7s__LEv8AjL~%0&*(mpZ@0AW*Um-+Ams>iVqN-nC6s+((LQ+>Ld#$+YSyCcQ*Mpdf zO9Ki{RzGSx1M)9N>W-F@k}^yiHWYdjiQ6CA{#^9Xq2Mq0X51?qXn5ewr&SJs+W1KU z%GBYW^o&%8wPXvu0|SV{MyzT=Z)VU(L}C77z&XDqjHGelPEO4vK#j>SuM+s#wX@3R zRVSHu-Ew;=T5viX+su8vMAGs`;-0m{$%*bszIu^b;pCnPyA50FPn~(^yL0)XMw+vY z1hxZE8FB%DIyDm8a%CjQ)`Aj$9M{8}SkuYBxquG&sGj`QCnfG5{EH}D>}e_V!FtCW z3xzj)Vkf}w&wJ?uo{!Tq&+)lQ%7x5yn&WH`X6ZJrlp#s@tv<;D(z8aBUC^fxMjf*E zT2dnn?~W&EeRZhQUOr)(r1lzH>dpk3BK0kt#u1J9AtyIOSnqNk{_l;TG2eSd@~{uD zPS7!o?ssv@t-kTc&BDcpry%_BMwJpP{S8ZNzB8-w)a*g225<}mEmnS9EU3KtT9rwM zQHrU0LB$Hvqn(W*^xkQ!M&zZsVAd~?Ev6hTN`K{ImCv1h81!yYTtXMH{|+9Uo;$7X z7})rYl5d6}zl&4Jr-lii#;xj~`@FZ_o+u-pJ*r5(ws5R*VfxYXW7bQZF+J*2thLh* z|7KvON&GU(It8hEpOtUM)yaMVcrNCEhwMTPGo3~e_2l(C)zi$#+|6xZY`)jYMW~ZV z*f4mDvO^*%s*JqRJ3lIqZR%rZcCCQj==)Quy-*#TKG=Dlx?VaZL;dYW6R22b4HD6_ z78U)&C^^pe-Jref{kHUE{a3Alzi(W*yjToR(>FzyVXg$o>FM1ZvN5tk6F374YrHY1%8U;gTz@lC3z zRb?NzVSQoryCJ)w)L~xNyoT32GVT?2fBs-=gje&ukfZFBFiGJhLa9dieN7`nrA_i% zp_?>j-&0lGeU^4m!Tu-6eHP6Gy1h@J1!1T8&S0B%^a;Jdl7A~}LO@vy13F=CuXYyG zBKR7d@4RR=Ex)`hu37leo3ga-!)UUbM2>22~^s$ zRa$~9b;Rwm*D$mnOg(0}tKIvYDr%^aLZ`|V(tlA^$VC6KN|nk~)^d$Vxl}231Ki6y zKogx=xXD!H^hulQqDmypZXk2VY}UK#PFTlx`CeB|%xE4=m-NU7yLJOryyeVKH#mpn zkq4dr^rOUm~2w=MfE-gzHf(EYGxvA^q2zi06O(y zFQ;kui6}SJUU8H1St}qx(Q@pN&~10*8wD2hV;+j`9v&gUh&2Q(=~F*_cA{Xt2+!5@ zP-h!-thixnBBA*GZLu&6vd#eLEt|P5r?|_i=}h@+4mIcXeDU6icF&6r{a-O#-=8?X zNcX4Ww8a;_vibh0@T~Hag@eV{X+UvSa(r6T(o}EDiL^V0NnjP{N}X)MxG%49v3Gqh zn#FhjuWbUD!#M!rG@_@UsYUxdlZYr?*TmP{Qbo>f15M~JkXr3_iS*x~qf%p3<&rax zE$k{Ip6(T?C;B-VP?#nEi2>I2KYKO0dngik1tKXL(7%0Z*3{6q;=QByNg%bC2N87q zJ@oOlh&0=BfWddYDTw-prjBUpx56~I)a)qvd(c8cnU8Z8cSM?PvD|s@$Ww95VTWDH z=NL@#Bvw~6?bu*ty(^lz!B#F^uKj+Cmk{>Xw!ZhqXa*@68*Qh2rA0{FK+*&*?cX7^ zRV>=o-0ABAOZ&boMYPBO(eAdB0J_l60h08hU;G|VKU=a0D34;mo?#MMRzgINd@ z36TKaZEsL)G%$a4p-OC>%YnhPWjvC9{$@kLE1#(h`Fy}i(8FyHI5VSi!9gw1vw=_aAy>Em#^Jvo+N z^yufyQ8;{}^vjin#?eq~wu{x!)#Jx>cPOF)?_UKf zKxos~Nft|k#0&DY8=`>4Tu1I?xOQn^Z?E8!JLjW6l@lLpvosNb)LxeTXtP#y0NgIJ zP%c}nlT9gJWadX$~ObRKr0~%Ongsda%Oksf~I}ta_73 zB92Nn)BP7y*Gd!EGBtf{MEnSZ@-W*H>k0=GJ>=gtZ`ws!y>V2TH~M+-CH0WszXMjH zWs>ElLk!Z24XhrO?zNYHQvRySzAMzcN}6ONVh!21wTROuUB2~3cvXp5T4y8Ur3SQw z*aMzeB4=T zB%G9?r=Wr_7zU?|@t~G{GmM$|1r? z42NfcW+S;t)Yeln)?4SX!0pwM9Dx}iLvO21Z4y$DIDiJ~R>F1Mvo$J=<8^T8H8M=S zjuPi9ogal8OZMBeKaD#JIDP@Bvzj&_lU>-CICnkjr}b;`(A<9lvOVdgVQRC{UuYHN9=KuHitx-wy3j-KHoEr7m~t=EkC--g6e?MYLx9y2cf^p z^l3far90p;64(pdZD8Qsk&dLc+l@{M_nCVvoGPRYnTU>;c`SuD`#|a$X{4A$({rSy zpc|k`>hpPzpPbDBvMVDJ9!O1!<+fdt`2Oo{r9xEXAkbFzhu1K-gB<~(+_vW+Y7b{F zKfv&kCgZ{Fajs~Uee)FKd&ZH!%AU}EJ311$CRc_7l51>7nL{+A;F|$7Ju`Loau-GO zUK%Oxy1?Rr(pzcGppn*q&nl@qK}j<%rVd4{+^K;rad2Xn_C}o^8AG)kdwI>^9>& zYx$Pb&ilNvYpxe8%{F3Pgo!33;@^P5vqo~`MkATO^gU)fUJ6V;V19F@C-U;~b4vg~ zBkoDWb=q6w%Saw~_0n?N$d=0#Jo|Wy+T?*5Vf&u|0$fEu4tv0R73&N^lzxW0>(=vE zP3`(4(ctoc45p=~z*pq*EdbEg5)+4hKNw*hkcZ9ZjHte-aI8j>n zs^ATU8B-`7=r4fY*O{-S;SNsBF}cm9cS6}h{?#r;Y*z@=;fG<+*eIo-)4SEZ>@XHm z*{~<8dCLT`*_P*K$1uQx8j7KXKe>$`+IQsQ7*2#1pcKIRN{!%mccI~L- zirmB!!Ev7meUs&5939$TnYjcSaqGV;W%2~j=3PdLs%eM`y(f=@PL$!*cH*pRUUhT0QKmvI`Yi+bGQHbxVdki-xRqMO9fh*v#S%w2E|f)=ePljT+W1 zUq*)z9GO&E_bqJpSr(}{_Keh*uD~68e1YHBCRmj(&r*`IcMN^d|Jy5k<*_dN2Vo5B z;vcumpIAhy4nspI{A_l=2_L?PPD?ijDHkv9)zV;Ykpux;c}meuSHPu7YNIdGp#bEk zQP>#jLsUHl0;{IS~e?D<{P{M`-M+>Po~7_#xVMw(aw z#3=<7&!IqWpZSo=gv*#Pkw({XOmz$|CR&8_tRfb15B!^aIjPURitMk^)7ot$F@Lhi z2IX|~PC9@J=htLnmAxga+{Xgsia4J9c#V?jZwQ9hrML_#tpV)# zWTTC}pS`vU{^BU=WrS$xzg}VDVJIjpVYz~eoY33mZpvlzC;do<#l!HdhHyC!_OT)0sR9zzouK zP^7@4$=E_QV1;WNSRmo3zM1bx${&p4mG_+HSTmmzrSI3YTo-+CaBKt|msoiE)1SMk(hpp=xY*HUC)o2>;+KdX*>15BlS}e}~*P z1SKU2(s-8ecfg4TR{_ISEBo@Xd6Hhm(=Rxz?3e*afmO$nlfck-msiA_8-UB{Ta-?F zWPSK#)p3^5lYj6~zO8i$ptvfXY=)oR3D4q4SfKp)P>OcdJ)^Hc)V~WL$g8DVz%h43 zhZ1a!5oFiEw)OXDZIOOv_<52P3U)*Lfw_ORjn?O$+yZ;xs~^xc*F{kEir3eOPtRCv z*RPT{ZeUq*^TPU@Z6-lAkX|{D7hjRS&qd08*^t1|8+0X@MKd>hieLT;Yl0+^wgETb@%%ZOd$5&ur_Wd6MZ!!-26cWEIUkv<)2MHp+Iw z!%mv~SijHfCl1@e8L>yRPD<>>{zHWx8IeYZtatGsHnkS5+jfm_>$rA^F!&I5MYEci zfCSaEo2*y?*5M0wbCYCmp{|gsI|XLC16@Q}UbMbyYOB1TLoW?>)IRy<=8lbnMm=L@ zUdoDu&Uso|roTu$YhP7K{Tg5F;mdWdj9-^!-A-t+`0LIFU{|PiAa?p!84HhOh+ijt zIu4&ytRQnNZ+VN!54ZO~NCPPG`rPLHUw6vv|37`#XFeTq0@1TRg}CcG)uihi zRXx?)4}ZrarO9&*gBGeEto41jx6a0u!kRGAd&n-ZMKv?<%M$n9S&$4m6qCY_6Qh*u zj>?lmd%nBj7!!C8v>rsWjF|Zma(=N94@p=qA%AC+++ZMecl_tf3rEwb9oiW^U!f`6 zVXIaA3_+(KY|D7T6y=6?*u?RrR^IgTk4bqc06OROqnXD!bf@e9m_9?0fwqkGeVIm- z^!h_BncKKP6Nj4!|KZ{sAPQPuYlQ8tty=XYA8JLevj1vDC#C(!EO=8*>*Q}x(dM;XBN1;z$oAu)MZ zLYybjS|S1Y2#?PqP^?UbsOnwSk3UjN8_-@ar471&yUd%njMd(#=Wm#v2UckJG9U6* zw0=fM*it~@qesJXx2~b5Q|4@2^k=x3zy?qZ<$F*-Aq}9Pz>c9(Z;{UUU5_6bE!p}$ z{%X=0nRGFk1!73wGu5?zZ-CBs_M=E6L*~IVr6?Cr@rOn1zL-={&VEM#$<3AAv6~g{ z_D;#NlFR23&Sps}7uTqTq?8b^o=98XWmVjBo89@p{1bUq{*~S&hK6NhXTa({3$QME zj!6(Fsb#KK3C#WShgv+hC588h?vZ+QbD82E8QduGkE{N0juu@Hf!?-aQ%3^A%#?A?AFC zz~)TO5}d28E4qJz9oq&cN~)O$#ga^3C!AJYq^?M<4Y^txJSOx%YKhhm`6%ra;27{+ zmN++_z9^Z{yNF2qyej8}wZEQI#V##VFH`~^B${tsPjcOx z@S{X$4!NbXQi2%UIZ+f=nC(^4rSiZe%y+#lYvU03CI6Xy7oj&pa9m-L)Ym}THIK(P zQ%ZE;-_$2&OU>E_$aOQs#r+3ffB=1FXIen>i{o=~nzJ+qk+?rtM*j>*r2oejun}VZ zv-`lM#@K-BX7L7XZ(VH(l>^=~%ybb=(U}~A4WEMYOv_BM(UE#r1Qk;6F zMo-<(Koc8n`vq<*(yjbNNRR(j)0NQz(zTh}Q(;=Zn4e4vOYcGP5a0+!*9J&#Vs-%o z050y*O__wo?XXmc7zZK|J|iRlx`5eAo=h~u^sjGx-?4!;||IRSq|`ySh5Lx zxDzUd#a`ZekJR@YpwwsUPXgOxWjxTPV#cEKI3ALCWE~19Vc!DG*j>Fce33yytSP)w znAH+m@W|dTNq#hia(n1q&ZmH>Q>Z{Nrp!^eE=xJ#GDa`r)-|AeC?pPS@QkV5XwYPI zeVKq-^PK0(BOp+p-LG!H>w&son^j?fPZR9DJ^nbqq%?Vwv6RHEc06SWiAlRAF#8Dr z?G>zn8u91z|Ib?$tc7KRW;{}}Roub|=u$cdCR>AC+3Fwlb9i`e%7k5QZCh9L%Y@W1x>E_6=S%YCs=II3X2%pg57bSjA+@Z(gYk9|OWKeE{AdBI`b z8r{wCV=hphqu*GvZt3K@UbE zolP@w*kjinbh^q&d4c9BVxju%MIfQEs8C7dZ@T09W1rv4XF+& zgQj!2XMg<9xy2gS0SxL&`Umh^&*>J>f_0kK!WQZw#}^ussXaUB{B$AZS9GxJ`!jQ| z2HK+9!@TWuYMiW2{HM@K(Ir^Fl0Ver_eSH)YPD%C0RvFE{DXXRKkT|h@3i{;NToZ{ z#XU@eKldDyBM<8^h{y~|+XckxD7>SOn14O_?bbjl&a7F_EEvZ8PMXs7bCo5N(61K1 z5p>dZTMWqrieU zCUfs*M<9_LbJFl27?E@gNDO;YWHSO$SPihCbsNMk6Za$hibhxmz@cZAKvSDr0HnrY zoe0$nOx&FTR0yXO$*ybhrU0Pn+b?o13HBcJ1(7%(D@G8D5IXkcQ(9ucvsYICHL5{aY?v`mXP zH)qBW72&iQkMqrY%fz&-r_g>r-qqb~PcY=EzFNP_-p3E`DzQTAbBVFVQ6e$ku8#yi z^T#aRg;4xSKl+6v6$!9!Y2f(AgWt&LZ}!%uDFz z05Q;ej{!y?FIcr&RJWuZqGcC3=+B*3h;YkJAhMr)Vd*lxe=HJEGKDZMT~j>t0bLxA zgv~s4hGu&d;n|2zCiTI}Jb&OE%$FY9MbVdGNLu-y;97^-K^m{`r}i2f@!#ZoAjQC+ zv|`{izax`{wj?6^FPv{F@mpsNiFy9BMoekC`<3c;(%t%X`M>OKQds^2RSm{8JhuDi z#J9eYNSk%ROTGZKz?FCtt7z}0NO|?7l~V7`)^Fqft|6~`BAR=Ny)>cy2E-RESi5V>?={G;c2yKh8M9EfxK z5)zh=Q8&PvPxDw%m}GW_e5}ofRN8>TboL^boK;-?w6J2JckvfOh5Q3RS=a%097unC zMG`6xqICEX0cc(v;WNOG|K_wXTMp{H_^uwcN4eXgJb6iWZ+g!TwrpDW>m8R#-X&kNXA!u<^TB|GocUXJ@^@)#dbr>+WU zfK6zns3(OTvj&JH=*2ksOL3mIKA?#uPsUTQS^3s`%D&#pPYYjxg^-kwd?l;!#u7S2 zr=q3cURboO*LtB~Wv+t#|m8&tDr?Bte|Ng`-)B&P1Op6l@)Xsyo2VhK&oZlm|c(CJZ ziW}`A)Lr`xCyWRr4hiwPz5cR zTfZ5}Y33J13p)WbjV$yg*;6|3Ui=+ztN_FtuXUubN_d18lo#jG4kF415-!*p&leG% z&t}+aJ;QKEVtL=Cs()jI`lLxD-&Tc9bqrVWS^G4WSMM7{XWh{eua+^~l5y~I=X#Oq zi*!iMysXRbP#29lW)OKuIDVV-F}DC;#F4#$<4gJ0LRm43H}wGg4fq`biU+Z!|0*83 zfr=?g<@@);|DW^c^q+dUqZoYk=L`Js{~~H2fDCO;BeR4be1`OIODNoinhd?#{N|$C zbHqNmY8e)U_w+6(>p4Byb=MQPPBQic)jj1US7A>K=yQKV=}kC+#ZHH@=dIg-j`ey~ zC;lo+JfF_+-XS{H`-E5?oalU|FX~<1ZImv_VP!Sk_E-JbxTCZL;`cx!ZKc?Dta5F# z*iWpO7ySq%^no%|N5)IxIng#&-L%_%gAZEZ@w%l3`paX0qw-uB7%EKx(-^r#i*gxS zr1gJo;MgDj<+RBGKsW72B&+CWmE8KjTwuoUesbiIZy}$~1lu3Q{mxu2tUO5HYmOYT zqlA9VJc5#!`$gNALh~qQ(c;T~67+Jg>qO%jldVMN*s?4S3kmRm-x!t_A21t^MKi^4 zy$1IAGOfMQOovs{A6~~iW>TBN=4sfgwwFL-wT2{t{`o-v{p{I)5dwN#o@Jo<1f!Zy zr@tbP)tvPtsX1RV{V2bA!4wgf9wDDFXlXlRbh9y#nS>~O89(=Y(EL)60I6_Tz}HlH_JVO#0D?lUU1x`T=A)=IUieYdcL811MdnSq@yh*S1fP%W-l z7LfHe2m_uu(#n?v^`~LDj zpEL7280lTQOjgQw$^KjY|NAsrpZwi^F4sJN@~6#JS`bQ;I$1IJ7Vsf{yIg_EF5ouF zk!J7zyNoHH&2_>+?@1bD6EF=fUlaUsvVYDZw&E(_L=@HOMKf)SYXpQkvWBoe!`gU$ z!u7P9>}77f!&~gTD?R6}cr@%z-8(CkJshfv+M&P(@*e0LueqgaFf}Ksu}gScFQbi( z=f#th{ikigjCQ-_78C4VH|-k%?F4?>ig$c$&_bnY{_d>rVvRpTMI}5391%#EAJyOsvoGM492s~*pfPT^C!~w= zoV_}Wd3oSUl`NAp>(jelD5tB1%qRsk9vpl6sddXx=LM$WVVv`~1HHZmgU2w6>!9<-WG7dewLy_sdLl=Fb(P7{d&%YgsUJZP z?|I0i%x6HBo(-yNU#yU;ae$0&Hs8MVk9I9#%7TQxES=>4ez#_S2yi{@h4mbhjr2TI z_EU!$waN9J6AjpU;ygJ2jPnu3iN);7aa$s@^{-BWZh;h5Jr z0`aVZhP}ys&1VlS{+w8B<Y|vQuJpZ+n z*`Z3Awn<6DO^z9sp8A8j-LDrf1)i*>_HoFiN72`9t%Q_o`M>{8{0<01<|WB~Hy*#Q zUyP$5dUj#SYHFGQg3qI`Es4}f=0;BQaA;w#x?aW@HZo>_8++dnXS-83;;(vGu}~5+ z%11=IB`GVoWskZ3>T*<=ii&aD1sK^eKu)lB z_)}&3$2;Y=kGyu%i+h&mhWP?GA^AHMRN+T7Q!ov1$$0YxI>HPZ_{4=lPE;yQ;C<#y-Cme2iHQN( zeXen%eLU;VLp#YjP7@Uu>>ltT_=tIyBO+_d}pxt;f{m;a~81`pLPL>3%i$n(A! zJQCyKfS4<{$|ft2O`P+T>Kzz^&zv<%DQZgV38OX7(GN%?oaEMmLrO%S(yh*LB=X!D z)2$nJw4TwtR44p3F7P?lE4s2V2(T`i+w`T|BCfNBsWT6nGvkZ^EEVfRf@3BMZ%~ld zD9%5V>p!RIpK|3>Mfe{nwPj-kjs4$E1z=&Sjv{3EVeUsUDwkh(^4BzU3jk(Or6#2c z<3*Y7vD%o@5KJ@E^ZP4rM`2n0U5>?>U;I@a&&A2m-2Pq=Z@h24EemYv996`CcEIe=;9IUuHOsBRuy_->z<>dlBX zMAM#koL_Z$A(}}o=P~%@0iA427GN9HLyHA~B+F#~mT?g5fb|IdoE|=>hu^vk4H+ai zFhb}6hHi1~37CwGyz~77g%?;Lt-+E$!}!dIl(2-6h{393;iT_bXX%9lmd)Bf+mqCe zEH`ZjAnJ|LIrVQ531Mp;ORpM|Vo##hjy{)2e{{uT19wH_p7OsQNabo-3r2$5*@yjf zv*0jV@S?84@{V%55v?eEtvA87zC1?kiBPhcN6Y|Didm{%^NVm8@b`RBigwu3mB*+_ zs(HE9H%i>t6T40EE$pChKZ9bUmmXuEE{s`*KE);}J6!a^eb9nW8nNSl`>Ma6`9J3Z zb)57pCI18VY3^BdQ~qG)s4E%=gK1V4#)6CBMgaw3pg*DLvgK8@y2(z zPW0P(D3|Db30yAm_vbi@7h%$?WBA+q-cEMn*gohAxX+$$)(zcheCgqBGT{E#KD@Ew z0>}ZeYN*rvcK&t|je_9SKmSCb3<@}w)mTAm)LV?;HXVkV&eYn1U;qBV^b62e0j81m_0Hg{c6a)wLE$_(^XLSZs2H$ z&F*3%_JF}qS=+oYD4n;`VMCet|KaPbqoU6K|35TH%PQS1HGp)3gp#6C(nCr&LnBDY zP=bV%3W5mIpui9U4n0VBGjzky;P1_TS9gDB_k8|T&+**3?|bj7A5W+)Fze|QKts}u zs~+$lgMi=#avkis4VSMHE>R-vnjJT|=%9QpF4p^Y~>7W=;E?r|N>rP+2n72R;yCb#&Gyi+$A0E~$pQy|A>$@FZ83k>J zxw=CQ9ppvZr#8wM5>cInj&_7l!-+7)(FrS!4T>3(%l@s(17lOwfnGOpeGaG73hNpY zxBX5|Jumwa0C@t;9TmwaWczB1*Kz}QMWt9;U7j!`4UlXp!5jqPHc7Pn#F4E^-(LbcETC+1z-w>%FRz`6%oV6iAPSH3GlJiU+9(z8Ejni3X*Ydv zpS0EZmSqrcV5e|TYWvbDm*4Bm2Zpow>c|3#*jnf8GNE`CLF-hLi;4^8O$`9(%-RX|cn1+Uk7X`zzIdSai5sz7)D zd2Zd%v5f=rd!C?ef#%8E;QiP(xXaM3;JpadbdqgzX9w(v+HU#tgfD$K$6XYL8FN#) z=gU{N`COESJU>)yYUNY-zV!8{fbUxQ*tu1qjoPUqNEW_Hk&a>Ic|> z=6Y9ZSLy}$ZLoZyu^TW35<-d4mOc>8HREyPk2m z0~H_C2LXqD;Grr5&|xOb^4=AgPWfjmnn>t%-jzFyE@VZ(ATwVp%cehg`fV+h@k&%P zjFvh~*wk8|9%g#E8rMk0TR-p45M0t@eQ@$>kJ*b#=otv8Ee$2@f!Bs08uynhoW#Rm zL+tVUR4aZHAG^dy#EL93Hx>;f$T(>up9R!QOHl}1hlK$6BKjZ-xtx0_mHfHy5q0kiZq|m_iGfR0+Xh5S?dWB$^{*1L^;Mq65L z6SNK!4!}C z%?AZ4X@T-R?j~qG#)^FaiQ3dzwvDVLLM8SlNJ{9Y{Gcl!vVUUe5KgPwMzUTc<;K!n zzK;vbkYj={4q0A{#2bN#uhlhU_Ht9z6n%-pwQ3|hIXYu$_M!R@gU<-K8v)zRRC zq0GsO=6f9y!fV{|+Y^qXE8lyZgxjVh@QeP45=k4Pr)@;kY4!wiw@gH_WiPN+RiZ*ljDz6E)Th9 zFcMUm8fZ9Ud6&d1f|S|Lvg$kndBE>A&<$8^mN(%+JlZbITpn71GQ-g~1}a_X#<_fI zpdi_xi9do#Lwn?(<2Pd|Xj=c)NB@0T{_)$XO7N>JcQ`P2T^s72effRF%7mDHbop*2 zuNAx|py0SIYHs^krn9k=y2E3aNp5gZkJa_1=Yr!9Nhs4&3qO<9iPrP-?%!YBYXXw#82Mt)G8h)q}*U~Bw(d}I&m`#j-nMv>AUfeO3*nC1R z0ZSePOUmp=%scjy)#gVEvM;Ku)x(sj1B4ma`3?nD;>5#FzsKxd#1JtAePqmiifFM9 zad7LJ_*pE5|4lH$#EN#v+_!#oX&SCY9zt&uNbs+H)q`R)fhrY-pXi(EAAqJFW)uh! z*VOXhj|U{3K6C0aifMgT0hn{-2NeKDa^nZVB7$g!ejy{nd&tp1Yo=Sr( zwO>#*P=68{uKvKAgsLm0M?FsyX;bXd;pk}a6ghh!fw}cSB5sC2iGAaKZ30e=fqs{J z-Xji~1BBn>jwL`&RBoi`{Z7vg<)|q9LpJ^p{SJtQJF3Bu(J5DJBFq#VU!!csDpP_p z8sk8|Vbq_>cBNtxt(wfvcgAqf4EZ_PA5W_xx?fkctGdS1%j@-cRu<~(Vq9T$nC=!H zdB|kD2K)a$T8)1lt=)#kXSKhN7WjhP&KmAL&{~TW}FH-(@1kvy(<(j!O*C;t~=A;-Fz4 zX>qB_{h?Ic38^_l?SjM%15qtfrW;JqWc!X$7#owt*Kha8f;2TX#Z(yk^?3AzKfe7; zrJROmJ~X~jw--cWttFV`Sk8;@TfsDX+} zoue1WJzR}2Cn8V#WT!MVOm&W+`YKox=VqsYo1OC$4h3bK%+&TRRk+T#uwa5uxPP5* zvfrN46*JGBlcYo}bN2>9P1joyKG9%rbqgw1hKJ zyF;k$k=cRZXo+t21yLLG@N?oMZ~QXsE~e!*(b5$){|c^^4l6(uzTutJ;oFja(j|vl zer;*|`shc_mMLEISrR3-RZ08(LH-5iqcQLw_S8pm*7pyCy^SriLlo&TeieKKqxYnW*CWoM){i^fGpC_^ zG=g%1kAXK$EW>Ai-)f^H+TGOZ& zTwyN#oop8K?_XGNXG&Q2$@sj+n+th3`+F%Nzi*;B{*VmF%*$llZgZ>xP^>zWPKoBq zw%ZE-$zaLHx!fVRV^w))Ij0;skN^=bGH8!Jb>o-)4&W3Gmy4DJ1vnVmca1kzh72Bc zgbH;Sz=+~2orPPLo76Ye{PB}xy>SagCa9gF4-sb+`|m2n2WRgc-fGF-`u*_^{Mywb~SfBmk(YP8R^%~a*Ln{ zDJOTW$A9`bvQ5219Ras*|Q|C@{e^#@N^MFDGao zccsyQ>}Nu%A#HX8m{RhHKLkJ`eMTx{JvbRyX+MpO_9Na(bo9EF_ zF@&L9CMG5*U^HnrP>M&U3cXrF2uQ zNn$;4jSC-2lH^wH*zZstSF7AHx-6zwc%g`~1D~iXn4oIAHPMLt`gUS`^t1FmXg1u{OgA7F0lNV1=UZ`;5Yk9ycw1#h0%&37nh_k#vRCnU(2%^OhnbXz~x{Sp>x*`^(hT3<;r>x)#(P>A;Fgh&s2X75` zFxtpDa5KHGqurzL;2=l9qxS%51qB^BnY|}chdoLV3TyE;ohV-&`R1q6?flGTU3{@d~c)EAcf}|vu%<9sE@A+cA z-VOChi!Y;MYeiRN1xk9?5|v8~8P^2GI7jS)*I8qQdIp7&P(-B@G%qeQ)xSQWn$)^N zW-VH5BTA^8F=#ZW{zvTT^58sT+3=1Nyj zuqkp=+>xXuJJidsiz&m}*!$Xp%1+2)(oz!2@}(gr>w~ZAKvoXMBp6I$D+*b?@)_j5 z2;kE`lxZ`8ada@>%W_`RiK(B*va=j9e*qo<(fuKh&7mdfPIy}$l+A}(1{puOiSae` zh2`IC1nAFQAgQMq7`{Def?A+#akux-ar!kZd))T>m~ne7ST;o7sc*z)9}?jNf_lfm zL|G6=bqhp6hakerl|iLiaQPnP;)8w1&8;w(1%MC94d^CRKvLuRJm%sqbcC!RYEN4wXWa?6`_alaSz_w6jZ!J2?Q#{&aNTeP$Z z)#bLXWEV$(TUqeL+brS3WpKtA8 za!B2@xDsrpLaGjaDA#Vxs2C@26w$M&)&WUY5?#8lJw6mI?RF?MtD^L*qzjnk{}g)g zEJK%sx=W1lAP~djsO{s^%aIUY7kQ`^rj!-IP-H+A9}??aMS}7W&1=a#=}U=$RCm}g zcGIlnTVofW=#`52ID2kTpIu&~=YMQI&Dy6>`Nnd~QRFG-)43hye0rba0%iY+pSLl- z2KGEw{GYDV{4dvOcB3feZ`Y}nF;oxXz?mbRs{xi(j2t&Bo5^AH%c;SkbK8H7>=4?) zS;Q!g%dL1b|Gc|CuIw8=c5Be{7iVm@>o1#u7b#!ZSzbqQ_if_ckV7jrRVCWlB|zUa zmgt#bVZ1b^>RDXO5}R_~JoZYm_5!x*=>3cye3k=d69Xr^y@2!mgWc8JTLs3xpa@Wa z&KJ@CxF-@(Aov}#K(xtUzoj`Q{!icO$;}Wc+ddvhdz=UBQe0fWYzhRl6=q9|wG5JO zFR;kV4702Hp5_`J%QqOKMYW1ZOLk*F12@uJuq{#*`8^+f4KanU`^u^`9K{O>z{%W` zquo4c)>N)^;m2ueui^^X+ZzZeBj&f3WPChW9mqozR2BYXH-W%z)${b0J}rjt^Y5zX zbZMt0+CqU^G*Um#>Y__0m*OgWoW|X1R1QDf`(XT*R;q6~fU==>SuqG%+TWWgrPR~P zn&XKy91MkJI!G9%2njYJ2eK{gtE=A`ohAbXL9{iEq_7U>3U?~BKgA$Jv8JAG?OmvQ z?aX5nPr|H-Tj8b@CDH!xio+)=zVZZDeO|9F4@ zHt-Pwn4ib$qe0RErKLvv*uMflqF`(z>e4Ks%PJv@i*TV&Dj-CDh?f^;yC0qN^UqBl zNSX`$%E%71?Td%at5MF81Xet*rGUc@I8W$ZbcHpzd7a(Zrn zX$wF-8BK{5rFp6nZ<;ImGgg_!I~6U#PoYVepq=&AD5kPxAnjbp1$pX}4AJV67K`h7 z7zTED&d?D9^(a|fYI%|jaVj{clUi;nq-3Xd8A?Kb_tQnLB%Qo8t~S02(J#nKNjxni zz3i#pGotg@wtFhVw|oItjnb_Dp)JyDW$RfNv;=BZEHPu&)*b0CPfT@qM64@xIz+2F zp>pZ?Y?4V(^>VI%7pX2|EM~T`V7GJo{fwPU73*1t6kbz4$Cs})q7gysOJ@1jQXPv) z*mH%CWYw*)NC5LjC3)+Qzg!8X--qQNyI@ZB%e?KjeCquDQa3*c`_tL(EAMR+t& z;F%a5Dd+n*K_fqBoh9qUES@hmmkha-d;WR)pwCeRf7r9DcsrwJ)R8UX6(}NnE1^A+S(g>SdJ75nhF>4ixsit zwU4L-=7-#|%$KNnaHA>1mcVbVGou7~7u(lQgb~$(VWXi^F4ZTp2-?*jT3oR6u`=vULe`CY=|v?A?DRJEK9^ zbBU~~De3yA5QY%W6cM0VL|CWWv=^{|!)?Z5%Z=VP6nzMIvPeqW21~{UcaN7aJ#+zk zy%SBpT-ozFu=hjF?R#{m#^;@W70$~u$@jBXHKz?ZSCYFuOUD{@U2+?Lygu#FN9P+O zFB7{17Kkt36}ZiGsM8yYt{ff;%uwGIEkrj`j|l#Zuen=>*|bU-KDDY2>5Xytqe5Zs zgRJ7wxEF0nOg)UvaWvZ7(?!6Eq+zvBT#~?8c3_p%y1!dcXlrstInR-S63F4qDA9?b z(;^&`?KYlb&(+MmD*Nqo|JTuLrTZ&UzD#M@`^(*B!j2I6D}4|#Y32$1uZk1GWy@OT zy)uJQwoGz;w5kKIPH3;iDau51Bn<}JZ6IngApKY(JGzyCsCJ16GK(?!#Q}4cgEHQa zT?K9mn6D7{nYJc{kq60~Bp{0^GQ=N43bJUd+YBKV8Xj6|6|PjqZ8&dbiD-dYc#~h* zO?ICC(U%;cMf5g64dDfJmbIYc-KNVLXX}irMLH{iSpDbew%Od;a+jXtejA^ApU$1= zJyFy8ZKiz50Rfu&oJwxKNNScA7H!>s zq-{&`CMR=6#knUeyAX?B<=c&`KdBrg#UBe%#QksQcsDv?}; zZ~V|_Dq|sNrh0QdfrPr&T}leB4gUJ&s}DXDmU48Jb^LG&Z~g`8O`r=zTDNB0hBh(8 zKT=EL*KafR{L{MTGJ#5Km)}_HtxO5IEg-b6zrPhsV4MhnJCxwX_pLp~;$tRmjomkM z30OL{ZJWF>A_6ACL=NG~gbIv}^(XtG1xcXa_FH4L68w}>EbgH2CHQSl6sZweRh+SI ziM?)j+=xO_-wsa93=coC>GgweRvu?t_rF&Lk|L3h&3-NC_csZB!hnI_oU0n|jk&8r z8_VYqXE-QmK|=FwxmA$-bAzgFi@ijOUSK;?%0m>fA6sJZsC>V+L~$?T*epY;@=BM7 z5%t`4(?L)#?rxU{+uf$7()^@$Xv(}aT1Rv=D=KLwiGN)&t{F~m(s6TTjP8?HLJrz? zyjXa!U)vn%+)%C^JN8s6Uv??O%j$B8T-?UrkT9%_FdI$_0`QDx2`4!3sCcnrhQ5hJ z*Wfi4$JcJx#f^3Fx+#q4Ef#10(DbX3I=nmaqD1+K!4kQN6ncVf-sJorckLf%510i8 zg2U_7=$GetR=+m_gBg4mD3F*+oi0&tCph*PIn)Ne_`F`KM3T<1i;omP!aQNN zV@RYSs)Lo}Gi~hGiYFHi%H$!;wHE*l><+C9+|AHu+G~S>2%N{f6ga$&>hy>C$CIAr z0IZz-lg0SdEboa<^p{WS?nV5VVzU7rwLh=U=|g~7x>6Xm@Uk)vM0xb*dvCqh4P5xw zh*awsz8c?$>$>bboS2Ru_kh28O|a}_2YsQ3J7%cginR+D^4=6b4r-%fmh1Qq7+q3P znySQHsL;L%)N*Q-Rg1{ZDWL2PafwH9q8At$-t$z}yG0cokX$?fNq^ZCfrChY zrEhG)F(GyMFjw+f_^g5WjY5MQi?}XHHOG)!2YMqH0W5KIexSrG$y=4dFbUF z8(G4Wj?>FZOR?Rm6@p?V7pDVDhILXBb`BsUu(*kUx|F_)^dsxJz@QO;r=_v3Hk}95 zY}*P2MzPgcv)7noG?2aA+CjNn!xe>6-39z$bwImvQkVU-#MyzS=d@qj=TS<SaTwl8N$P>E6v-v-bW!zOMU1UKtAtL$4k0b|glN#p%0u0|S z+l{Szw)F}OA(P4)`~SM182{J@BHO=GX5wZR)xW~N5HhN5{6jHQh)fWWGXwlVxC??r zs#)gDND>}qyb@_UcZ6NDyiNOJbmVl9gT?8#IERGz=+*rg?YC z4BvMY%;!AV7MB21p66QnU|!;UUB9Vr6X>}|y)F%~&QWIc-xQn$EzCzU9?hZvAxO#l zr*c<|a$SJiFlsx#tPym)0~GVzFVUl^GDl;%@T2Ndr`v1ZB#jGx&J=+7?bULnyj^RUNe zas$X~d@KsU&vHF=s(vC92u{4h9eXlOjJZpp0%F)WHlmspnxTD2D=Z{y1-QWtRE=-+ z=@6KLA8o?z7-GThHIO(nYEJOcy@)1C%oY2w?h}vlc(K-~v^@eZvZkY6l_liKk~{g! zi(;PT?gUNRJfkJ|s~84FWPM?duZWIz#Ip3jef9JnRqYyq?g5VF<(c3atf6&eMMt^T zEoRSr=QsDp*P$N_U!V1V9W$dd z(^2r*(VKR37e{J8es0531svf^w?p?nXuqPDdPQ2UIK8Ll_(eOT+;=Y~;RGg3UXMrdgc7j7?+&LI!Ti6_-(>+Ta+!e zX&)+2YsVR|ORJexYgHJIZjQN)9qagk5Y0?V-Ym3q;V;K(=~|zI@4mI@|HeBCev9AY z`~*ODLm5u97KN7=XOg9GORwIX9&|Z9^&tCwo z3k$nSxYlC{mro7jcwT~F7!-K|@SloTsQ7Syj-ATTg^Xk3W2(G2hnUw;AkxPuA=J^h zEhDEQFr_gDU>1F;so4+Xo7KtNZ)rB0^rMf1huH+_$eOXvt~I##ueBKI009~lUO;=uGvc)&1Tw94z zRfAeQbVZA>M~hB6K4QW%^B%XhZm0Kg$M;+3NZgOT_+?gM}x&H1`>b>Qr;bsY;fT&2M^G(sxIAX|sWyH#q zh>!qkUwSm}lb4l$A_ST3^?_eqG)s`uHf|=#c)eFh&?Wwuup>#^@X$8U{AL;IjUMv2 z|0eSUy@Nry1z(;M>(MPHA+x^KGw_;c|*A zW_@CHV*N)}73zV56~oWc-s55J0wBGvy}|sU&dK45&j@9Z^DXYqe|LWT!|S?kq4~3( z$kX~-kMZ|EgwDVGGsUr|EwvScgY0?(LHLRdBF(XB-%ZMm>Hp%OrsgUI3>EbYq$a0f5V?5sS2b?gM zx;2jDp{P-{ZGxuqOWE}iI7XIhyzAvDmQk6xLM;H>NF}t0MqI1WZli>Dp1{g>EeX;M z-+5u1eiARS!`yyZ(uC&W>P!{F1i(1%rvUtWHC$5~ezcN!3<&N7oC2^v;~iZy>^VTG zAo&X%#dkH83!1UrWjMd41NH#i`I5ykNW9lgSD9}toLQc_rvOBJ>AtV_^_1@pg9&q* zETHY;)eqpu>h*7K#EQ+VTA}J(*#~Z5_yTM=0ZL<6g%6MW`e*Q)!#)RtPx^HyswbW6 zU#l`e+i0(@lQVEotdDl%+QSrwK9MbMc`{i5z9~bfy9G=R&mppXu@HoJFTdQg!IUq$U%wT3-Ru{G~(8Vlz*)HQ=CD>1On;IBSifIRGC zjnp@{qOOAc(3o`Zd8q|+B_O0sTcHOqgO$e&&fz?mWv%c3T^Vb>6A!e4EH4X}!fSxa zTnTI&j9Df?r?73kO$+d%S&-*wqSKNrsJ+)!&(P3HPjM^Qwnq6Q(fx~9s7Ia+{c0!B zm)2vSd({Jp)6CRPS)})?HYvoS-Uo`Z%S^nF_`CJHIigdf710KK5bDNe6+@eO-Wqbw zJUZ*jfP9g-1gL#*f}cI5+XywyJv*L0_>AZB)C^AMQ$r~YZ zbJdG^^J2hPYON7$03*N#%Cwus33wW&L0Z`73rtkR2${>Ui?@S;8AoZms0i-XpuzL<} z_cbz}G~;yvleG41z5OVF)4Hv~@&W`Kodab0kTPf?Q6`<+13Q35-_8ovml*&>)j0l~ z^Ks*=5GnhCJV527UYz_C@M*oGz44pL3^Q_2ub*|P%I=dNOFq?Pl^r|73uA0@O2{7{ zdhS{R^+vP`W8`*U6U@-?>=GGP?g+3P>RJDF0f9UN?9a}=F7ATYmN0itjKUzsx z9YF>$@m`l@6wSf_qPK%r@I2TTA6t~Tod?Xkg_lsSYs!#>B~Dy^f`_khwZH^MeML%mPMc6}?HNxdGu#DdMea2B!+chuv31 zG;ZuM5sF21GSMK23dLYLAfAnu)`@Gw4Mai4rBdfrcNq2mz@U0Hv0-{F*;HHW@>L5O zOmyV8B-17HtpVrVnkIk@H6K0;#4=n58gDl89S(ZM1OOIapSwe_7>efv`#nROG$va; zF!km2gKwb@{@Bg9AA9PS$xjzSkw9=Pon-6}FwZjXj z1J~LrfnV57&`qg#ZjO5`+?jFS^VP761Q8`39ZkBp=*{k&nr|=?1tk<*2ZAn(wz3E5 z`&l~y1yB8V0Ls=+lMk^nCO?k>TZRIdzWYMg!kj70Z)5?*4eQEYQIF@~CI_+z>J96U zBf;Nq=~G#FmG(S49|J1*uN6Tysab}V%igrE?blG#V-hN5@_FLfk8*da-t8{kyPSF^hBmBYB_926U;Ry&9Q;N9iOkBwiG8-#134wOd?rY$)v~7cG|lMQJxkqA+aOqR@cI4XUU}R&3H}4r$08$v z4bkeBlE_n!k)5s|G+mbsfhp_)D?LjGod=&tmeL*CB_AhP-B;pRS*Ig-{3s*h99koW zHY(}7XNb)*O;+|Z!TXW6l5Cm1^7v8|b>tfeaH_$FhY?7JwcN8LC5*E{o%8b;_m?620x6`tanf7I{k|E%9<1w8wfIJMgEFtkN7ce5;CtNAvD z5(sG++(+h3Jw_Bxk8{fJfLYvHiC{b;E#7%5acC&H?}(IL0tf~)2pnQd8yf5%^0HLK zKRRs2H>Fi!vS1AbQ6(~u*0IpLXEC8zDr7Tm>gM_f#d6k6I8tv+^A(QRWB2)O&FshI zp#%0C7pHxx<2GYVq|GFcF0b=?KLP!OTTwbwhf_*W?O|HcsHj<=>hs8R5|?NIL_W=Q zSLGoC{`uCIoE%s9W<~eQ;{Ip1cYfM zI?*8Kj!gG1Zk+vFq5jq@Sw2VNsB4CK{FiePDmwp-zQuUC?HSc?xdjfydt^I#htcOH zoYZIiv>23@(>Yg%1^X}c;VlFy*JZr;X=fCTV%-wMccpb8<1(qK?Sl59sGwM{1z3@U z;SGR(=B#y+rYJXnN)H?d!p02cOSst7Sv{$ruF521(22#*`Zo|$o?0Ic%{NP9KMH0P zrH-9kByl~=Nb;^wFAnzWe$dWYdFc^ zOBH1Xz-@Dvvj1}RZ7x#GwuJayy75-@(G&KEvw--o7CsUVc-Lc{Gl7XYq!LRB`?-vl z9Wlnf^Dj_=JN{<3k6E!jf|{6rDq=F&UDuoHl0X+<$wMH`^nK}vkFAv86IAa7JDjP& z!c3$}6A|rmW1eh?B*8ILM>gaA)`W4Q7iP_GC5Ze_1Gj;aswjSGtjX36o|N5H99Qb% zqWBjg9uss(IJwBaAP3rURJ*pMMEQfq2=6gyYIwMsNKne{$G`0{(0IoOblo46Z0+-u zxRW+RM(7qjIy3RYa6R+G|GQ3moUP#z5Wd#p|CwWA9u>DUIGxRYl$ zP+Vpvr3&Bh{R`SIQhm->A6%M7kG9fDUV=j_ zmwd8Qu}PwqJR|c4bJlU}u;lw#!C6iL*Tw1Ql<3kQ6a=+b_i(Fu%OyX?Z7iTiCJ^<{ z)g0iA9cQ0Wrs9+BiO0l_k||#YO6;B<=F${^SB5eq?WE3RrYBx+I1y6v^JZmL{uy*y z+xyo8T^ioxN$~%8AOCieTdDslZX)#!oc>YVl)}7>9W_wTahsr=y25-)G_vNAw1o(M z*8YnHP(^JXD{m_wc{%%Y%bV24)AK852Oudbt-PT+;RR%M z&n0H)m=ze@+Q?0{ubdRF!dBITh2|tY_m?`Mbv@)e3D;t^DfeA~W^r-RV?380bIg7| zteF?VC#{T<2D-K*JHjJRUI=@Fei*&#T#=JlFphP5Nzzl3*Y)yqVH>88NEeF)2Ox2}AdY zfHPA^OYZXUSw(w>n*&<*dfH&wtM;cY$8`@-s|Gl?uH+1StZ{0xv(=keZ-ON;6 zVCP)eZFS4H83QjF&?RiwdX;G%-qP3tq_)sPUh9u3LwyO|PI_H}DszBvVJ7(JWj79` z@tpe<>GiH|4DZ4uaXY{3HArVw1C!1U0pBjYvErImyXy zJAmee=YUAxE&QMH()()hy=TDpdfLR{t0D`?{}{*Z(>j)gwx6@?C|``nwXnLRe56)Z;N z#n+xaXz`Y={vhw0Gi%O%$CB?&fd|qn^>r;F;|E)(D)dx)?{oZRx?Zfj`xuNq*~)L6 zI7wOIsB2Mo^?k%55L-!Ix<7f>Wd>dW%<^2+W(P-_hBp4r)BNrCn!i@Q?r9c0c=Kn= zK0SS|qqEVCgQxK)=ZD6X-zdPej~jGUCg8&}ap%&vcU55E(+Ox#I+pGN_Hrw)0r|qE z^afaA^>d@#5wyHF5)zcGnjXlkltq^=$C~7TO;;H-p`D@2 z-~B=3KmrZ+>AM)Z-{5G_-9l?!(Kyr%eWJha3M&p$t6E*>ulzbt!}bTKq2x%uo&RBI zHIL`LSz)b)y(^{}vG6P39J0%}>QXxar+ANv7D-Iz$$J^9Kob}H5i%5~`@0HDzW0RI zoJ`xbhwSzUg)xTFpZY(a7saG z6!8?5X{WO!OHv}N;Q(WJy2F01X!WkwWiTE}f zJa<1r&8bY4z8J0UfPYbldWC0RqRbivnrePuN|nve3E0&b8k=o7TvwbU950 zGdkl7Dct+66~ml?w5^6v>*v^MrRC>1gzLd(>Ejw8L^lt%41QsFYQv`WcKl%k-H5d9iWK4!ENl>uZ+6Z6|yw}cy!zI<(v`&xDP-Kym7-<^5GE|gz421 zbJi5Ej@kj$0wIs(o@GZlx#cuSmssOotTXhOpJ2au$0x zL4cb__T$tfoXGDKm3ls_ev|Z^1@v=Stf|od1?t!z5K!8#PW4Y{BFJWSd=S#^2>)I~ z;fq+H&T2;BtZoPpC5O}l)iA8@*UH94E>_<>Xfpk8h)sJw8V8&RnW8$u??7p5rO!#| zgs%=rNci0`{S*uVfKrj&*BS%P?ts8aPB$PvvSgnSTA-0Wz}GaKbX_NsAq!!?^hQR2 zq(>HE7_=&_RP4eWL^oYw7c|+izOu|NKer5Th3(PV0giSnsv}-O0^RPxhwNum(jZC{ z98s2w_-aq3VFXNF$ZI^MS(*z_Sx$_`e7Sp7KNf}X=-Isu!kh}w2^7-^Nr8Om``PVN?3?YS7r@$;9|K9#n~ zDir9eY)IG2&}DSR=1#3TBr=|?N~sxdKZiKw{%orZ7>aRxm6k}N-E3OWAQcv^EuG6;Dv?NXC2`bv8`Jh`*R)l+5!n-g;qy1rWBLrV2dGwW0DN6v;kHSQ{Kn zR)&Wkh*XiCMF?8!+*|2=)Mxr5|QpprD0}uqMzZO2!dIONy(HCElk{fw%oEfZz-GI1& zpYeRGz01M$;LUT&9|1oUb~P$^(Mrp0=T3f07sI7NPq@^ZPm0AVJVpiw-O9xEqSFf* zU0(ajqNv@59;&{+&KYM{1B`Yl>YD;}WG{TittpQiFRp382b>yda~YU<3~{|@d!J%# zt-+>19-&(Rh{(^p?lA~W0eNWop;jG=klLMWfCRkXsU1`S-*R&CSh&t&bYJ@t`*J@I z8Xeo&)#Uf5wT=0 zFw)!z`*t+NribJ%rk5f}8it1-`PHLyAxtOpfwB!KhJf8~+XRgTM|0^}v$rvW7saMr z!`i7?_cW|p2}ISJ{RM)%dqW+30C`BLyS8Z;^T+piq)SyRuhjYN_4)^(SKC%OXLs6M zegIvUn^2iA8sPZWTotdjuY+fI2ezdUaTg2~QY13~6nWntRGy0ANd-WWMXBqzea|dX zvv<2s-)sqxdWX!qi%AwWZ`^3nY7EhH1@dcOw&hpUZv=O#y?9v^$a;UKU1;TS4X$Yt7}sq) zyxb(4gI+7)q>5};rN?0hhNLMMn7eLx9XkEWSEYs6oIlXki9IL~pO0%V4w(O>nMF(v% z=Vq%XIkdT4+C#qkgm<}AVCt%hV`Z$^NIU1Z-zdD~8*Hw4;lrzhBs5^TAMTq&j%IG> zTrXWKbkb8XF_a6Gj}zsuIwmVlqL*`e(9rj;w!sCM^KDfh_}|?dE`N1z_?khg|4v{~ z&HUND(R%?5k)Psj-Uo3wvC)=Q>bQhfqXBoIVOoiI@u{C<2l6bqCZmxz)G>xuA;j62!qfCo>O4Vk0UpyFr zRHY1#OCi}6egXo}iZzHoMvJ7K*9bH$RlA#!C|?31b?}|OM%QnZ!fin<*Tri;cx?}- z>>feiirDF=q7rwPvW_Yx(6Si4``QkW$FM;+zH6?ZL6=w$oIhyJl?=8$Sn2aAHv@to z6c*zNFq60r-MZ-t=ym-xZD4Ie@wssOUPYWtJhS@}bV!2Tv;!mzfcBZQWU-j@p8#BX zy*!%AZ22H=_Vf8pQxkvtPJ6G|?B>eFQJAvGZh#deG#QHGF*7{wFn*n3$7XD*2lz@Z zo_+npaJ|J#;<6&YSD-10^JpRGO>r?t29Y~a^%5aAywQ8SO&M*AbV^Sb6!1Ctu$ojm zmI0vjLLQ${)sHwTZ3rFhq9)??(&TXEY$BLRMHRcdYENio_zc_;8ckO!flp86#~jJE5#)6qJaJj8>6&g1cjyk#!W$RkT< z+I!mg@8JKBwXY0|a*f&@dXNSsq(cUj?xB$u1QjKurKNl5l9HB|6cD7lyOBna?(Xh9 z@9e$ReO=$a&iCV-|NMEG=Xusz_qxMlp3{cTc4c~yd-Xe%vj1#gp8RNX(1)deLL&&} z@$#S~73*qm@v69crUCTW=td3g@)fpFoxf{P?&XGx7yN_qW&A{c*(uukYB3@)+J1y(%%NNP^d3gg@#`bNQ z`p1t7!oH|MP}MQs@1V<@+~HesTu&?$-n^7vN_0q1!0J1f1{6%kR0zZVUv>1IHW1DM zQqX}-6r~I<0*vZgcJTkbZFulc+fbmQvh#-*3s*n_q7b_u#~jv6v9;p8ML6IV%hNe? zgd*l5kG#fKMjdmJv721LN*X{v(G(F9LW{(~y9gW~egY)^?GCUZ<{+DUlT~Vm*6Aj@ zH(1Y`i@}N(NfARFJ(!^G3%8FCgCVX?fQ}7q?>;gT_-VN?CVyUlFNo@xN3h0MS7IdW zK7u!@%}b-Qo*p<oTVVYQ_GFq&^&>WzmleBe=kl@lJi=u!og(&vi{e6L%nW11}a? z#;?%*B=;tHm5gQz0M-@DDE6-tlD%+*s{{}#S@a_wJ4H46aD`q1Av=2%+7UZ}uIMJj zX@mXC(Jo|DX8fIBsFMU_$PbhK(vLa9#u*5S!u=XV`z3Hx#pAzcJ( zW}(BUAAD265tR?A9^L}e@e$o+f<6>K=aLbFqyTyiydY2D(Pe`1rXE02z)#7XvG6qw z+j(f41>T3`I#cDX&RWjdAayL5wXaO5|BT@3eKe@o zO~|18KBU`lViQj>ANFLxCG+!`(lwBg`&eM7OlgMn4Ddi+v1vhv9Metey{$#oNFD+N zFAj$yT4Ni$jXto7NAnw`iX91-4NhSv+>i?OJJAEZbab9XDnJgHz8ld{Ed+~vKJfk6 z_c;Xz+{)Xau>SN=X?F=uc~Nj!RSPh(?iwS}I;$D0cH9#jpdC<7RusB1%#io!1bCa- zwCGKx{A&*pV6G(Q1B1kvoN*mJF9c1Sc`y@OQ{(1p$kLUulHdjne$8E+vvfGsT!j5z zJkBI4X%GWw82e+rf$p-o{>Ser8*03OGZ>OUwihRsb&BgCpI{=5pxTX5NHqq9omFMz2YH3dfmQZy>D@UI^5p zyW{BtC-yL`pS>PK?P&OnL2sdZ=Iepn3GXMO2C8|sH~{Gclqj@}YFsLwsyi*{0(z0* z7+Yyi(c^=eV0#V>42ecb>)nTH(wSc^^yANrNS1SJa@=@+DmR{F`JtB#g?%Hqj`+^a zy7+~4Gl1uag^G5=kM#jizB)6XZrxiYjEMp=Fb9vm_$BK zb+o8&{NP8z2dzq>B5r4%V=3$eZ_MOQXvIT94O9iMDHG%&B9f|-egUf%D=V_;)0RWO zD9|+9V=!%KhZC{rAOq8bmQlK3%vbawkGjxspJw`sdB^6EKKc-X_#u#L-;Y9N&%|O~ z2Ng34aPJNTaR!iRd9A#9Q1e5SHuzD?J_4&*xC;O(ZV&ow+DlKoxdKEXw}m2FTGY?* zsNxnMl{}<+EtW^aMPMZ>03XIH8%~(hR{TY-b)f*WMQHZ=&7&}7^rr9~c;TtyOIFoB z`aEX$HiW z{V8;!oKOMm1BmKK2rg+rXMRKjoZuosQ`o#7*UEszuu8q3VBzQ1Lpsos+QY&TScc#E z)y06vtkAbpY0z%IcKr<6&#S_$tJ>-?CoSf&UAF{&XMqYEZI;5zSV+H5J+LA0u@lr@ zvW^2547^qTS|ZtQ3-!c{{91+Zp!8U{=cW4`&5y?ju*ou$6(FVLv+2!wr^v4U9IM-g zoR%XohmsNJ7X$DF@K*66onmAts$OA{PliW?&IHR{uND*K65Dd)a0j#;1KDC}i2*P=B=f+e= zMPQ3@?3Ww%0w!*~8R8GM)&)ZDtpfe;C@DL31!4>hhzUZAj2z##a`g9FKc=;{^ZR1E z-PG#l>BWv(DtP2nmgOqARw_@tvkHr`392Pzf6AQ{BHOgsuIbR9n#{iS?x>()ocG&= zOW_QFd%gTm+{+WtNdfHM=qH9+N&roC{>ZX!io?>NpJa=ATgud0U1sq)-3C+}dy(a6 zU_9^RS*&syg||+3%Y(pd0%q5oH3vf!y&3w|#hNCwWb60^)G zlwxe3ZVvEWFeW&Wq5p(BSG03pKN$;2n^99Npr-at?tmV$hpolg^K72spFSXt7mX@+ zrxB-&-x>}vhU|p$J*uXk*UBHTsmDC53k$%tH{$M+b{Rx*m+@sXMLH%W*;Gs`l#8QU z5gfM;ERV1+QE*9dy*^N?lh?1fW5#LP(aXgD*Q54}*6?TDh^hTgTYs*lU9RzaFG+k0 z=ZYo>-yD|=jTak~;d!d1D+>6Pnd`U-)n9({XZB;>7&E72Xhn0kBSRzRtCESQsMbTu57fvV*ver$GNFD|w2Y%m&fadee?TKhX zTP@2O%Nd-{(je~zWa(vU068Yf*byH&V9mXrkbb(C3n=gy%KkiyPN1S+9_k{BZT^sB z-JzeF`c67`-hud?P2A&y+A{s~ht7!mhfNU`r(-G2_)J&BilpET?s&dPhko>*rmAi{ z&Y5mlF`(WfOb)UXU|!(<=^(&jiD-3ld)2Md@UBC*(=oTOGV*=(tUgEmMMK~5Y-m2N zCz{gTiv_{)t5RM;nN9&Ft#{~VGDX0O&sngMukms-_vU_BEj(7h@B)uRFhd7%HoH1( z+zjMx>X%?qlf4aeXYMsXoM{XYAebQ>*UWIzgoKml61?IhD{u;s=a^=9z!#1HQ=6F> zgTs>_zyK0IOg;bR8|i>vRiEt&&{FSFSW*w-JJWXa##&|p>pzu~)Y++3)Uq8o#T)pH za^A>3?8DD0Q&><^?`wc_zV#i3Wk}!!B;56;lmYwI14w8W45;H zq7fWo6cP6UlpbZEJzctRL9c}ju%(yRu_Dtlmx}W`?wULwKpp2v#s0DLK87NJ_k~}h ztzQwX^x_7@VOk7FPtNX&Gts)>>qNF2ab!txa0v4b?<4_pte4Bf{f@ZU?@cm=1mxE- zj`sFnChxQc!?3#tQM-GSXVgwV*VXnM2&DZvxBsfyo{r3B680<6V=>mJyViZ&o#%C2 z^#_xWpPAzYp5Q0UO0;kg<08yPR9USrrg~sIB9RbMx^ESfoJhKM zP3p8-T-^1*l%IlEZMCMFlck0s(u?onC1vThRBE#P0PM}^mEK|(ZddN+sl?T@YQaoQ z>DB#WSz?2T9TrNrfIbsy@ugNb*WVC;R=Lws(iSXs8DTRN$7O+41oh zjY(1pU%jLt)Q(#JE{vyES{Rp&ZO@!!SxHZ9v5-i$-nAuQDju?KX1Q&Uz`W{l7H`JN zOgj0KvA&o0aP;y!{5I9K}*XsZU6(<8Fnft|a%H)ZdQB0<;o|^Q0 z;>_{>)zz;BfD;#xHK$>j%WL@H4)B!NBc1QjzAcGNyNuaPzVf&`^H>q3Z7~37Yr-#B z>h_eZX*$&|x#bQ}4& zZz2C&>f-2I%ov@Cds=0BGxlT5fS$69v_j0YwJGF9z2Ak#QSjuL4tmoY%O5b%mxjCZ zusa5L=(+Y8u;1toOb8RcG%lYMgV$%A5?Z=u<)`HuwP)rYm+I6YoXp!VElx#eM%q=P zpL!c_v~M<5R)D@-hxdTa+(B-84LPitIKl9Iyi-`&9f-BN*Z|l;<(SoH)oaO5aD5I$ z<(sCUS_274S|$KFeT~DYpX^NE_Mn}p7BX*Gt=vCFJ%5@Nb_LHN`5KhuaeJ`>UmbWz zP9A~;iLZBo_n`#1sW2B{f@wrF6!HZ$RmHnE7oJGi48SX6q4*BPvJp;9x&W3E3lf$A zGq}a@bn%usqJ>=Tn;a4!1@+A9T&g(Wn-juA% z{jNH{%2QD{ZIb}W>ZVF>UCk~6A7Q||9l-zpNOiTcm)%Y5JAHNT)FR%|tA5!q-*0hu zmm2p;)Dst5HHeb%!Y=YUfwJ$^_ zs|#XnE2>x%%H4Qb@&&xixdcMR^zB!mWd@>*IgR0aG_3X+7RRr*26$*9o#H9iWP3y0 zKds@C@VBM+cNprAwAS96>|j5!?K?~qEMPXLr)tg864|4DzsHk?JvsWV(ZMOreb|?& zhLB11MRHLHC`;kSOtVsDa5Wa(WR6JxUmJ;EAOE*X`7alSJl5ietiQW;OxwQ(Zr5(i z%EoWbAP3gQQAac8cg!MP^?8k@!UOznlk|KNhTd!~RhW4@1X2$hsq*k!PN{mJl!F|q zxs*1DY*>9AJfU2Lx^|*9StI)FRN(XvT!iT2SVV`PTqWb#yqiEYn8Ynr03omC1@)$+ z3UPjh7t(T<6!=?-<~M0x@MoSdJtjac)jvH!1LE~OQpK0U5xL&aOyo0P2}|wtzM)&J zKQ1qH%O{;$rWBu5yt%)_D-fbvg;NDN2ng^WazJvX;+p{$&tKEi)9V(nEZ5((!!d7h zU}(Rv1T;t8a5Soh2|1_GZO7F8Hs8`MmkbExIjMV(Ttbd4_B0wKsMir}Gij3L#6L@X z&^`X%*mscMU)V)@Gwtmv_5> z>!*2T!MU;2ETFa=KAQrrt2Gc zS$})?Vi8`}Sv7Jf-W}|J)}Lo;wOwzx+U*%O2!$&>ZZ1k&u?3<4UQHDc=SjHI%Q=wO zbBGDovD{Q+Aqd}LZz2+U5eqsU>MJfz90Fzibt%S)=v$BN;!X{m59&O!#Kx-0$nyhk zyS56>Uzr7MvlLTiElVe}0jj&uQ7@+fkW{!ohbkDl^rk**PQG9aAVIC~bm$2sf0;sm zqc{XK*n8c8(B;(Ndd<|l_`$ngS$AUo$POutaIfrO4j!g&J!zfW_Rk^g z7{uF?2#Re%`DVo1QkuciX%rtw^^_JhgKB^serJ!c%bpq`^V{V{+HXpKj`g2K@*7eB zMDLZO1}Vm|4K4LDJBF5J>d5-(?(=;p z^{QuxN3Lt2P}$9|-wilmvl%IH%OP$wZZ?0%F_szbm z5-vJBEK4~fRNhMi2cr}B%O~S_F=yg;zuxrB z3n065gPZmXtYlUlh^%nUbSb7lcjJ@%rh6s{O?Vcfv{(h657H8Sr(Fe?oaEPZPq?qY z9E0S)s^M89uGuk!>0XED`Eywq+i|U=*||o>P`(1u29tkcOO(RR5KlIa!VnKJQ}HBC z_8|4cV$@r&5%WoZ|BTJT^YzdKG?&a_U2N8GFmLa{bI>ZqeCB{i+={j2Aw_GIugkks zRtNv<0RU*BaKJ+yXOknz1aF)Bjtxji2Cd+$*tQc!pJ z?32)I^d_KynI&z@(Vsm+o`kboSf*GIU_g)0!fDihT!IUQ*I*aHM!%1s>mcpxv|V(2 zK=tU@as)^rV}G7Hll8hy<^Fgp6%Cr6a)SWs_ zDfFl(bJ;GQIh+zQ@%Nlc+07YShog=woGBObf0u35>l})YmenhSXV){+qFq1Dn3^oN z;MB%O$mbPj{x!$o5GYLf7YRr3g~zGuU z^VzguLoFE3?b=NH`sj{MmA`fqcU>-6bGRw4I7^ljx3X^E@IHkXyMDi&e#~bG`C~A- zaS)$g3|XzSfxcR*P=U{=9qAlTR|ix<5~6E8w)n$9D{yUso(5o1<+^}l&~*Hz{!?r3 zbquM68Cw36;Lp2F?vs+V_VX7jg4WMqtKs|C^ETG2N0gc*cBt0A{lkP}{GHwqPG z)z{8o_vmvxWsfbts6gpv%L=Ks@v%!hBvZ=Ff!OQ2tv@3tDU9hUdk_7Qv$p=shRI2d zQu^sSgmA5m!IC8OrNz0Vv#okb(t4IANt$~x$l5HIW6NbO37J#xBOPkE%Kxy)S5U7CmCwrn&7r57$J&3v+n^@{TX21@3&w1~Ia z9@2`r_Cm(?kj?SESQCKTuxi2lWi*~;qYYeff) z2nrXrR*Em2$Ay=j1-35ohHOJ;kiBe$b;oon2=W%j%y^~Z^f>hwD2qlFDYQJ)t_L{8 zHxc4c-_eD8Jl276<-#sBhB-8JMr=A}v@GT&p zefx-pR%epzb~@1$9%LeN#)&|)F{*B4A-1^bdE9{Svd2OYO; zn3i738D`%P8ae>X-3*gYWr}h~JpdhcEOX^_2}|Qn#Z(M@@$Npm9Zz7HTIkeg+6`>N z*0j{9jaI|3P%eKYJUDisOeNsBN)Hp>O!FsqP`zlzaevV$QtEnS&30y9TCjSgyxZe7 zDiXzqb9!sm@#VrbE6L#y3w0l+pt8rylSRKbkmxns_y5e9CJBIQir87@-w9#z!ICMC{ko&pxN!zq9 zNhrK?Yre5se>`2Sy=xNeF3Qy1`La`==V;BS3RxoFgM2Hj&{D2-9f`g<3-~HvJ$^5u z(s<#i2aYuWi*1^ku#Ek`_uvZ%<2$UQs;y)D=FKzY9z*t{$7%!qIHHN|E~T&59eV9O zWE#isAya~!0K#P`_TqW#xZ3tV?*x^xy$DDe7`CDw<}-Y|8NdUgtRa}ErSfg_VuWD7pML7I3z@AS=q%KHeGwM$S!s`=YPF!scsQ0b;e(DF6!rTPzB?uZB2{rsGI@K%4wnQSjhHkGNll9CxxNHSdCC=|d`; z`m_4I6FN2@o#5C3mAb_Tvx?md5PCLiXp?6WBF?BQr%p6i>I_6lR2V z9d;D|bMF-^h0mLDVt`QbY%@TRqA(9&i^FiOnE4+2849w+?hGcZ?HbsVZDrxg!e?*D ziuV#w=cOS6tK~c2TMf%^4H4NS^y((@sRG2j-GKe^hL%?&z^{#h&+A9$-V6b^m~I=- z<7FeAA>Ma@4wVT;*;fRI(M*Ojqv0gj&>n!X@)}r&r$m+Z^Y;`#s|HwfY7Lcoxo`$8 z<+DUI7mrgv=}g-_Yn*ph-~A8>APT*C?qNXdKOl5AZ36)Q)hvKg9Z`XUAu!zIfCiH~ z$qFD7kJU@t3R_H9af}Hj;loznd_PK7z3`S#(I0zO;=;HPpS*B2ch^wqb5!WM@&pB^ zAK6{8N+iKU#r-&)@ac!?H7**6Z*>@Ykm zkp&qj0nGkZz!`Y1`41j$Zydp86tf4Rj#V1&UP1%7$qQ!0;R}G4j}`ao0*ksRz8#Bq zI(%X#^t$6fy)%>WdKZ%mu+UeSkp(3ST?LjbNRh^)&9fV-)>Z|R{akQcsdR4xcet_d z8)DA;brbh}7Gq!P1fX%}hA`Ua2QmVXq!M<+vKGRoU6dbe$ptG;Qp{mo`Ze2U&QUWW z>+0|HUTuDHN2DdeESy;Oi!`Qed$+g{?=L2N5tL6EtW?Dl31ILC!~exSMMlK+bn3J{ z;pUC9HVfdcX?;}E|O=pHE)Y8#R5hqHM_R1?18$>s5sM`5-(s>&p0&< zH7HSBhR{>X&6Hobf5mxeo@cfp8`wdy~MR|7eMilo{#g_P$uO_ zl}bTUoPCaqv)3@{k?X>}g|m0hhL&^EU!%cN#=AH+mobcIC>dsnx2EPncEo>R;oP4% zVK8ak`4NO!b?xu;lP;k8Ip_{TlaVRXuoQMWGMYlQ>AecR z^1+#vi+?S~V+s+~7X;#S>%m%<2kukaaUvcK1iPOCE&ywWNukHDLLI~?e9L6(9`bNJ z9ZmtZN#2fKa8SB#+kn-U#`29HpTPj%66W1hmmq~8G4cVgq%pk(R?IoNJFX_6t69h^ z0XQc73VA;IliI4Ye1AjtxtF|vFNrq<36am%jZ<`g7403+0CdrbJZ%BIi@8m(=TCvI zB6Ay{u_pgyK_D({1hfXGf=i#H#2Grpxsl}&+l~N*KwYA3Kh#}oM}@O^9A(?|N5(9A zQ`i34K7eYN$6xXG*nU_JCYpRt+~uauJf~EbbX79*&nzLN`|`P-I06sKyq0h34ZtBR z|2$(<_W+UY9@X_C3J=o20h})<+Xy@E^E@*HxBP6fZMe8l=_~B0w)okH$$|5ZWuRIa zap3k~sne0*xwuMKBkX#`>3Qui_z(~`IaLFR*Upa=aVh{jUs`&dU;x)$938&@yEDx2 zw_xiKKLfV+n-@O|H*Y%a^^m*(Wjr{oFd74eqJ&3hA!S&6Uu zES}5)zJ)nV=uKe*pY_HJ^v-TcQTu|g-n9}5BKlWS{@C9RvgT-;XM44JS>hx`%rXU7 zv^b%b96VYQ?+9i03l=-ar(Jm!$^m8hLZ;XK4Z@T7B_!$*&V}o)lHZnRa@`uh$?g z;ZCHa=7$s==(P^|4bMblUi8VuCt|F*@~pL!eebbb?dtswEsXhIrJ|#qxK>MS_rhr8 z&bu!}FLHt}>@-83B4Plxxh#lu@gW@j6Z_Gz8Ikm%F&gy>pm4WD!dYhUH;a+x3G7mv zjUFd@9&r7Qa$7BH@v}tZA-U)~^A9Ev6o1rNHT|2B_>(l=&3k#@L?#u5#Fiy}qKiz) z`Jp_OBHe8+Sj$5j&iRTM!JuB30Jux$sHczM|FcVGFWi#evPpRITqQ3z>p6n88Hg|Q zMk141e4d!+p{|D~VDWWL5)LuXFdu2K3(Q|4y&$Ru9b;t!aa5vf#yYJGG@m8Ye-P6M zL}B$~8%|8)$Lm8Vdja8M>gaE9ir}CgTxv3yKMtpq!R?5M0#xCkwToHX6rFzVaVQkfo5SHS zyC<;_nEQ3Fk?@kl0^Q9ge_n+;&4x9hJw`~P)zgo)yb1wsAK+FI27qX{aLoYuDG@ST z0Sbjn0#b(|%q`DZ%rEsX*j&vn{%9qHc2@Fg6K9O6UP#=tRnOYC>1qW);#a;CKh6id z9_%CNx}VB>tbg%OlyLd}DE6Eaje7TuOk;aC9YQWP` zi>MasQU+m7zd6BKe5B>8#Cyi`K^^R&ZdNSmG@LN|8UW|9Efs#++Iu)N4$irG_QLi0 zp@8|44S-eHyu}GuIQVo82zNV$d`3CnM>y5_;ULaYPEn7a z9mXSv%~@cF_fpqZ;=^uBo1bzYkNUCeb)3(Qt_h!2m&`hEy$P}l`s*c$#ae*gG@R8J z!p+$Zjbt;2$1L5p)I;4e--LzxT?pid_Hd-kQOZnl8uqHL)_ldB^_zI@X@oNSso5;` zOCy5A8%uo815jPFAL%9MeGbcq-S6~!O2dn(mV}>Tf7^Mvb@QG5kyF>CM{6^<{#1T| zT|dS*FX)zPqk}Kb$YHmMfd3tnylyvV#z_rR@}&*aQ#UM4%yT_&&tg^&T3GZd(8YGL zus{&mz)${TKnLwLzzSi2Q8N@F?remKo*k{v$&!hX-H};1*5I~BBNA%S8)#0^n?`W7 z+C3fPn24;;*qX?@GNhTXb4m938k5}-yX7&;Co8fVy;c>wL+X&Gy0|8%(Y2%Vx9(j$ zt38Rl{!G&R2DZs5ov^V5&SMLE75l7EnJopTO!J9ZshJEEDXzdXln*)m-zYKlguDe2 zH-7S&=u>w_oIIhaBc=MVt173!z34vPma_e46Ue}n(-|kX+TSiI2`zNssZmc#0wylPbt*f`FD#u zZkp=>Kzo4CiD<7eyg{oQA?+bD6QY(DFltW@m@zgW3-HIE_PhMt}7RL6uoNcp1LaRBBG!gf3X!|3oIfGa-KXOMu zqp-HS_>*=u_E^fA`LzMeG1gR0jlQS-ntwBc;UrvIhNL^yM>G*<1q@j7)f4zVOU}~h z>JUIUHiW9YBUc=#E2em`rCx19bdz;!h7;r%^khete6(h=7U`Au_?GD#W$jT}mqCox zp^A0WjL!0TXKUtnnoll2watBdau86G@g*hvru@||Ip2xlRP5W@YQMam-ra2`z+6kd zKKP08ksR_o4?<+szuNEL`-mOFkweyRYU$Ipn?A7D$IZJy0Wn;TW=CuM^7|S z&mI3`O+AtW=#V)}hn?2utN9OtPOzAL+5Qvl8KP}q=kSv2e+UpVT5 zcTykVmv4h;RS|Ki{Fiaz{Oxk2B0nC+-WOcDqcA_g0+(E`D>@|W0@e=EKvOjwC>ujoy^oT~~c1=crgYAw9Pi0( zDycWSgwFAxzj1Db+mN){a{DPLmTZ9gnmWesAvH(E_Z2;z*aUwriNm^R9V`&1W9<*qekLypm^V|n!YglCU#}bZe!TfG(xeHAuxwW15UUO zOBX)T>e~6mqnwxyMxj?EHyQ57bIG*hH)%|EZ@q35)i`ca$ayFFn+~AhcXr25mh&%i z^7j+i>63+y&rT*dlR4{BhL#Q*gFu4ry|2ap2Riug-o0NkH3-w?vL73aYd)qxWN)by_59FpF+@7p?@J7{-@K{yxw>eZb|V zdx+qY7MDgP6K8_SUqcsMV}|R)`OTLqtU1KzYau1#zIA@XE8CJRO<54Ci%b(B%ZiP( z1N>C>ewcd6yZX@#e!Zz}tzSE8!cGbI7`lQ}A_x4gT>!t7D8RgIX=Ob<>{Js9plKUE zTctV)fE8gCT%tbf;ec=pXQ>$W#tq%hKVpQ}ZelNxKpv|`)u5we{qSWzwuEyZJa3Zr0h!0+qBqp;>0LU(^RbU z&3-{b#)qXz^g=MbA0OFi(OUCJsn0JjfCRTX-6g-0Nrx1X#Q^bgB&%5&^531r?-%cB zYMBG5Vbzbrb~KN}?b0r#lLOBH@|M1yOs1DR-aqpQ+$h5r-7@g1r#SA-x%K|R3&=|N z!sm;}2E9Rg!s@Kcn`2z6jJ^C_i?LrUp()Al-6r)1wl+uuYla4>%2%)BO1f|kE&AKV zAf9k-4WH`e%WJ`ySt9}*+^%zdX}Qv zC8hq;7ChYUariXmeFg5sWVu1#=1&EX)S5&jxQReg;pBh5`oB-IC+@lg@?;>8`cy@* zmv_nZyG7e(0S6ltE_i4=+)65d=%t#LXnM1;17o9vZv+>O6g}KJUQqc{xL(U18yq;; zWoRe+&T_JN`8F~pl#~L=Vb=WeMu(sEY;_Wi*d$S(Q?>T^+-IutMhMGN3OP#L4+1JfEr&sV@2*_?Gr$(YAG?BC{cE#;yi%*6(t6;@=Ig^^?<(A z?yp1#d19~VCn_;;1uxuIA*l%a&3!~Tqo^7X6!riZ;~l`kjq#c>1JXU*ZgP8i-Hf6s zXhC`sSMS)yBil+phXj&0DKx3NB^0APM}<0!HM=Li=Rx2=7PqCVt~bsSKRqM1VXdI) z;5%K8VYV0LE*bqW6=!mkme9CVCJmt?x>ArgtaWa9Lq$dEgqS?sm9;T`&>8BmAh0)% z$UTWb92NZ^KNJ;S7rCcOUR6VHGH2GY-PyT9VevCF+?Vl=3hW*jwwkVt8?j<&wksN3 zLHxa9*6Qrq{nh3b4{R5{i3{O#WQWz|5jfj+PVZmoJ6#&oF<&ZgJf(E13zyYgHkuK% z90lS^dRw8NUMX(&@{=iPk6_)LYLySfQ3;Q=0a{nBesL8+UYQb$GR4MHini${u1WOX z5|$I=R_BjWBbWC15ShU!+73<>BHrq%SoZ#79|KY{P0Oz_w#i=0IW0rVxXBTH%S+{^ zGCe-49vhVFtVS$QxA7{aiWwk~Z0COqBy&Lf`)I-(tA-!`2_zfL{_ALpYgcTiahUR8 z6m|L~Ei4%=9KRpaK5Ad`2pjij^z|L!B`pML3ohWq4?u-)^N7CS(d&KM?XM)T6URQ? zN)iM0lM*%G!^;SFvh%wJk9&gfnjcWe>s+X5X)Be|Yi1;mB#B`z+s=-V0)JH{3bm(c zqVen%1vgE;Mv(*Qpz7m_A~e5?-YIfeMDLUcNB$Uf%LHzUT;S0FY=Bk(Vt~~}JFELk z<*9pDeaJ5_0D`>xMWx485u>i+_(aKUs4u#3a3?nHpM zRA$oa-^$u@On4{L>$2s<%nf9fg+8j?zhF4hUd>vtMdvVQmLp#+SL2=f<^~OaWfJCO z)t?VN>=x{AwNx7Gsf(~RFL2Y+=-b$k8#UjgP1%y4xEqlcHfK{Duq`+ev|owfvC--; zo|UbhCE;^i?BhvOIREnbrr&*tLQjlk@)QlhfkH2Xb;z)c{L=ee?uxpPPj5&wSxFTI z;&7Bl?W(cdu6p2MAi~ty`7EX0?bnpJw?I&-=!^X zoB@Xd8jwKD$y7=!c9Xp&edh$6TVYXuGHL@1RMFBxn=e=J!eA1*XWEZ`eQG3vmjmW@ z8v^1!;LMf?611EZoHD5Fc`g#H7i_%UQbfcahh8&dR(s)~cdrH^#GKfxUw7&i$<=+R znS8LVgItT&@&HTD!DHY3Ggzj90RfHl;0 zar(#7fJQBEgfryucM_Hp_d&vmNK4D9Fo zDtM;#ij_*WI`Xt%>u=pa+tiGb7o3Jt{(p3j)oZcU9n$N?=Rv_QyI@F*G&21!*(hBt zq+*=ZgVBo6NTc6)VX*936Ss*;Xwp1W>TQr!{z}KiF9VOmTW_H)CFE|C-ySg@#ys>=bVV(R^<~+%O~bq4nGfUyq$L_ z?JLaCp_?CO62)p>HpI`k$kWMoPH2s1;lXO>16l>U-XiM^zWgsr8Muirsi5ZaEiGuE z{AkvSE1tC-5-$u1d0-r`?KelSl4xMj)AHuQQ0&7HXD^F9!Gt5t)u9dBFM>BB>xKPCsPumb zI_nz$v}Cq>h0cF0W$lFjdYYK)Q+ty*EA3Q}b>7|E&yD1(=wG0ZyGt^4-B&UnE;;Tb zP!x?0&)1_L^h)%{8;70@HliI0d;-b0_C+CJEDxc-!VWg==d5bQ$gRZuC=(yG)Rn8I zV1-zPWi^cjtUbi|E10 zE*J=&#_|3=K^Af183T>~El^Ah=5jbXF?UUd!7zG2?)=hB(%W7(g3^KUl(K^EbR~*W zX=b&pqqoVhyIAsjtDo6w9>n3IY?s`6l+Yd2VJ3b_O7}PSs#~ zdO$)MHM}>@KF&3Tf-!=Li5W#pM5YWhj6vY02mh5-Mh``l1!-CLofxe4s(r0=e9L{8 z-_@`RIei$J=qYBAn|uBIS!SZclFXcOiln(u9BIu`Jc_eYjJx&R^jP~)GP6YO zkajnLQZn>hh`ahrH?N=j=uj(w5ZXQ)oDWokUNwZ-AgFg8Gd!ux zAfATUI~7cHVumOJf*^_pxChLmc~i)!L9WDBMmD@~|4u5qadQKLaF+Z-njbu01g31r zLEqTmug}^>MDE}yO3?HM)d3{M=-RyvR$IdX=KA4GUuF|OJ4cmDG!SiAJ~5pZ;wSc) zRs}j@!mDBv4?oSVm*p|qJANPB-beqaXwz9EtBs2CYJEw8(1q)PevdwY=!(@U z&v9EEijS=G*1n#rz6%X9>s2%_S4-$L6h2IhG=|&=^cuxKsU7%wh+-Y; zdg)%0WR_6vm}f)EKa@~Y`n(`J_F}fE{_AW_{b2M;myx!TbpKZFZe@42(iV=;iM^(B z<%6BIL<%6c+*SAG*ztH{+?}*`_;+D;hhwp>km3v~s9wCCQF_?Z!FdA4bh@>Q z$9gc24cm1|kK}aDj)yBl?)ag(r%UNSYK+v3>UTiBwgH^6iCRKgU0NY~41g8z{-?6v zH<{}9dHI9XCxwU#8~_nLj;BJ(d%u1ouvfoSD!I$f^iluWc@V`ELR{1V=J~Yj`5|Z6 zk$lvey&D~CVB6`Jax@MEA@b=d7yvprGD%uroxaNp5ha^7Y)BT{-3 z6&gqG%$~+xMBBxO^vsrv9DUXWNRDdwZ_vO|iKNt+y|Fa;EvR zX+rD~c-@|Kw0ksPea)*|ZCw6ErXWtVmk*ETk6V{Di080vls6mV5=F8+{A(B^chXW0_va+E!+S0) zla16hXyv+{Y&K=)@H?3Xj<&^1nJ+))7`hSeEb`m4uSwBg#hTNKUu7)tr&~sGo*4mI zV(TT@oW;SH!86h4iy$g>_{^NrD8Yh6> zn)ILEdfO#$_D^pOKSHOIB?d&%@jyhO)*>s@s0cxW-K(+JL3d{%W~MEy3XWBX=NoF{ z0ze07m2?uAE&5;#&lJ}hQNR}@pWYgXhkRe63C#;^$AI*x$`C}B-5GSmbcQh1B;=V! zgE@wPx?b)Hj)YJ(4+wc9db0{(qBSGBX}wRiQlLT=xK4xkP(P(_f`oB^iG6%XLqK&o zWFFI|n*Oth2zZ_+vVT6nfgW*(czl8q+%%k}$O=aT>YR>};6LYs(?>zgabj3%V;s|3 z!J;deMUog89TIG|$j|g>{mcW%F#_X67nr8rRirbXtQH$YkU#wBYwA5hS`wE%LTkXr zwx}zhel?y6R&&EP$+A%Z?bF-6okK%IlhdB-wi#)m?d}L61?m}g_Ux}W`_CPWgeQa5 z*VhBl#86x1cwxO7K(5I339#m)*|dGRnsQx-D_mg4>(XRP*a za(d#1^QFip%EGuCyK*$@=xxp|>A z2s5ql(x^zWFYW79WQs4HGTwe*doGwMyez#bDiT;CXy|d^D>jazn2hBqa(JGN-$-@e z$3Q27zX~#R)exKUNR7~z!x+m;u z-2kMsD*P|z`J&Rag9>#($UV7R&`+n|r!Tb?=sC0m&Zdvt#7jtAc4bvTc!(67UVDOi zgnWn3<{FOfV>iZu_QYJyrZkrp zro-t@e=dsiG#^qGf;Hq>bdJwFvsh3EU61Sol#5`?AwtEpB19!n&F+Shxay3F3s z+*6JsEvLOiHo6{CFG|#W457O(^ScQg($y1@fpi^cd$6guLjt-00f*(2^@(67M7uLj zJ~Qtqy;6L6IZ$D_u~YF=oDP@e>UN7qCODnDZC#m3Z}+;yiyXEaO5Jaqrx0NFP@865 z8W8E@Xa9PZejRgM@G^FjiZ;Lj;`M0g^LKgNV#yc!^zk)k@5=+9Hbhl+%l1Nir(Ium z>ws^?sWH!OnJ*Fs)ZAuG;}^CL;)||SysO?uYNz+9f}gl?k30%FCWwf2h8o?<-*GH7 zem@+MBmw**3dqftv*crjwJwvomdHpB#bb^V6Qr-IVoU1Oc-$T%;DHiDvjYm8GG z`0${|83PuBgp^zUpt|$t)c!5#bU-Ho`keFU-u%bM4;F*V2=?d^tvYA5?Ei?w$FXpU zd$d|98|@~1r9jC`LB*}u`{ZuSDKnnK19NY~r(Jg&Ucgq?I$0IF<4{6nu@&Y-oDS}Z z+I~48uR*fnLixhujmGz>gcGj}m%QhQ%na2=U$~Rlc)Owl1m>X%<4=ryi$D-F#+2~h zrc1Pq>V>zkRZ=sJyTwVMxhUnb_GZ>B-Fs2c?$*|mLXnlW(NOa;feE1Bi@YI7J^1yK zj#mRsoDQ#x5`pI)H^8IjFrVbAM5r`to=tc=5j^fc{)uorXvRcfO9##GWr(k_z(c%c zv(7Su2`8c{0ur+~uaKqu-9Fwxmro6m@$}Sxpni8eD4y3CHneAo8(Yq)GAWfzXRQ`s zLmAIxCkfEeJRTGd)N7o`lBT$LTB%r$1SEU#{=R{~TF#~n=~PLOmb*E?=&*hH z#%nKP>AI8JzDh{BbmIT?ZYb}#XP#5HVd`_0jdI@?1QK#;0*qXmSOtde(tN6x#jR}F z@~)L@BB6{#$1IO7&RpKf?;obSB(%Eqq0S{9<$~_1bilpY4iWJa$Q8*#GhAqX5mno7s4@InLtYL^uh;FyE$p7%4GPkat*Z=Fs z!9^qwg_pVNwWK}LPknLhtoQNJaD6wVgIgynPf`7Yb#w(ltbaF3qI*(tq$$C@(H6@?_D_AOXYTqD;{C=>!%6&7#C{}1)5h&zc!m_GAWg+)g{de zNi1ti5?S)NLF8KbS0$B}K9?k;s~L7;Baam4vL>%Q6`I zIw%rZLdY5-*_SL?L-w_7V;lR}ml-?5_cJ}+chC2@@8kLY@%#PXacG$9y58q`zR%Y? z7C2Z_HZ@zXK*>w1{{*)rOaK|IttBo!UnIT~Js&HddJbi*zmkT!s>~mFd43`R)*AbT zdv}pZaBJUp&f^~rX6SXsnXxuBRQSYdAQ;75GtKJU zQ=T{(Y}99EQf_zWC9fBdFKS%(IoWsEY-BuNA{9=9XbXC0@wb8|_{T{CW->0ni?^Sf zrAL3ZWnDxF&5^bT+k#coCSAaWra`tYcsg7(p;K{*h4lgULSqx|&{20r;8Cxa+4W@f zb3ad??c^Bx?Q=8@3i;=RL4zS@UzNxjJ%5Vyr+apbL`Z>%HAu+LfB5MF(6#i~S7YLp zUt1Y>KG5t;4|$>r7QfFko+Yc+I3VD={dw3A?EOL@@EU*nw;jdLrXgRn%ZVI8I(5Jh zm4edf&s7V#5>P_1W`zanZe(Sro6LJ%FK_SsD@qLGqCv5opTxr*%?vsF8L+WBE^NDs z6s=a_Z0J#YlB8Q}9bP1yD^(QY)|R%}#$)6Lx8})a-@K^Ta0vUVZs)vqeyyT#HwIzl zlGXDX_uBW)Sn-tGlrZ7e19GZ;HbWW&Gu^+m9=T-!8@9KXS10E3NIYO~iJt-{IyG~z zqy*t33A99)&JaP@Lz3a5p!%emZmKPMJwk;{_4@8WK@D0*)S@g)IA>bV&(V*dr^`@wi8)NSR|GR?!S zu{tuK^`@Aw!{hmd@;8QgEQfoMC2U&HQ#h?h+6zq@rrbeLZ1>OAuL835K+W9t|Dq=> z+5P@9m2H-I{nwXC9N$g48*iisgvROkkeFiA&}L%?!#x2TBgtsZh7SLQD1HdIG()KS#pUnxj7=AO)vR=xB0w6}9~-CuiKgmQ;<>n<}YqUEU5VQQZ7^ZDT{6 zQ8>_;?73AAqnp;}3me5wwn^fFut~~-Ku=zBT=f+Vrk}l86AK0Qb~>06*JoY!PdU%b z;yLKU2B{|u1mBgpt&f#@4SjabS_j=@ra@qP|NUYD^b%iT07xe1UXplE%Dow`CI%4z zaL{SwGD&3ye@NGO9&;Ta=RK|D&JhW@cI{ANb>>%WjuJ^k!6(pRf_Nh8VMt4De6?M7 zlDXzR&*6oH`VaXryHbnQd}Wmbg#*!K%=&^Bwj3hP`Vlhd`R_8aYL!yf)=aUM-W1X7 z4_M}rjS{;SD6Co?{~QCT@A?mVoy*lwI+CvA+n&?&VgcO$4KXUNT&q^z39jyhyN zq{P1Q`-#3H8lmh)+k0w>S`nXPvBANPk77kud5b9kAL&lXuzhAk;|(Fvfa~^L01hNwU;+G zglnAo?K*@$=Z;TK>L9(J=9t~pO1xOJOC5eU{bq&PmDz;B_V0z#OSwT7H~TBjclJPE zPW~q(=J@GW$Yf{rc-AoSSrZH8&&hG|@%T{CF7c5)$#iB!KV>6Xam3%hxIbGH_uqni zfXD72P;U*?ouS)p*&I)(7KPvxOu64J=lK!#5`J-{y{3!|7Lzuoi?oB!c7G%0Zal&XJyn_>5nfo+Aa*r}%YPQ`%!79|q-OSNN*MkTWu$ z7C}OM9s)vNdFE8B4m1onxc`)Fe+kAvzX70O!0=m1%|r6*|0t=HUrNfL&*<@DZ;FAJ zcg~kt^687y+B`IHc{b3<0ZR0?p-QWfa&^~xke`Ro3C$V8pOvtfx5Q`mmR_=N2rXjS z228Kqc-t7B6;w-lH9bcE-Jz661lX54A<&LnubW{_o@MS@V*nc|Yn5g3XU~c>k*Gjs zjUw80PA!&Qbwf*2qTP-hlhxsZn?!V@>C6b`>|vk=y?Nc`Fa0D>x&{W12^W_!0x#Y( z@U&2scinaO8%@C!$7SNB!tXc{3_&u4u*n2*?}0ZqH*O~sj+aC^@3*1(!E0&FRh9Mh^lxpAy=IOy2S2Z%zynuGaU4N6bO^}SF;Sfw_yM=BHh?Oh!=aUgG7KeGQyJSSzWo z53P4N(Ma|X8Ic~5Tf$$r&Kw_KBR0plrWl%vr(U*@>sReOgdA$H(jN}AC2VTbRedL{ z)Ulls{5c!H>^~dGVqYqLuK-04t6X8kOz?GL4&^IgviqG#xd<7U=|D-#mkk3Mtyg}n zQgPLklfo)T5q^D@ejgj`n}aWW1d_}F6*c>uX$P>K0ADKe?5qEBoY?;EUAhPmuD8^L zf+ni>b!Gop8>LwTO4h_qw$LV9f-L?D{KS4oh8B>yi(4gaeDs<;m)OnNle=xvl8e#8 z*0pWc)E9+@X0LJda+pzMOb7uvB6yh|crN1u1=B0Zm+fJo7gnBNa}S;ZxT~o$vBLcG z4Irx!5jxSEAntQtyo&UmBR^9tg_l|)X{=4*yQ#s;yFUA~To!r~17x{@jls8hm^VkJ z(x+aNHXc#`myDwQC8JE(K%2nE3Wk#`Pf9C?zc16O8=c!%E4`WnjX*+iIa<7J(U@2U&lLDRKRO}w zoUK#s0}@>9N8`ezuT8qQt2>Z#YUR9r(e&`suQ2uNnx!rW+1De52j@{XtiB4fmM4d` z>_^n#>3-&{sU<<*NZYXxghr=druk>tq|ug$<~>;Zk?QJBN(=ne5HOw-jkh5&B~ku2 zcf_AB6ls7swG9t`V_y9K-fDBB{w2)E<8Rg!cfXg#%YzF%HG*E6l7!0BR^5uNMEyD^ zn=PjD;NH=5CnpSy-)IhZkaKf=2Ny8DG<$#-fxm3n5~j> zl(bHPI2#HnqIo64-}vph$Nj)BY2V0b!Y_I)X^Bn;Ow(=h`7W~JBF?5N-&A-Tq^%cc zGb5_$T>%PJtD^Pa$D=Vq)s&y821!KP80B|N!UpQY!0j5PZ4%Ng?3&mJLY`_MM6;%f zNun#y+?c;rKeM6bN($(D5Ycm2kH0jzP3&kgpKE&Lb(E=vET-w&=TLjxBvhsVQOls^Df4=D_IJTOFoyM;68h|quQ$5 zKZyD&FhV&lZOZR^ZvW#dd}Ps3da2gQW;i|Zk9QZDJkNY-dl$iKZt!s%yp}8_3HrnjsL<+hKJsoas*W`Wkup^nB@3^M9s(aEtR$(&kAOxkW$Q=giBWR5kY1TktoUNgF%)w_Gd zpK*?x;t9jXDh)G*zZL5i?lNKz;av$Pt`rRbe~d zteFTb+vj3bP57@zsJLvH5(>ScMkrX9nlmLSkW<9g^5;;7`aBd(tECW6r;yDWjCTFp zE%KdMbDF3l=pjFVtADI;iHWnS`P|ygqfX&osrMdv%wvobl6pr+y6^BWnpf$Dh1_e9 z3{sBZYQyibVn9k*qaLwanS?rMgSD!%J5_9)D5<6qYN=ZPkw9;vg7;;fY!_jUuX*4c2Del?cGca||&ATjnnD#x=_BYB=Mdwbx8udI| zd1bolYCCd7*{FI`#t;J1eYN~w7x&*4biW$(2JZ0h8PeGLIxMso4ffxOuV zz7t;I$_p7hKvYUv?bol_Uk%|?@3Bu8za?hK?4*a;(v93{;`^!=`#o982O4W_Jgnqj zk!GJRxX>TdqHB@K->Rf9%C0IG)iF4jX8s{u+$sOIF8j=|A8)6P+_*V3(r_>FXh1Hl zb=;HM5q^S%de!=_YO$GNhCE{fnC-vMnjljn2gY9A?znPyf>o*PHbn)?BV5O%&zo{` zg;BCT1?mWKHhJ9_Pk+8}pu084YAzpKvLMhD{CpzrXE8CyTQ(2+fs1hKg{k(x z>BGMo|A5}U^jmNDC*?W((c3lW@b?k0EB8YLcIr1oxHgm4mq0APr3Zpn%(b@M2=dL`hxfBo(fRPDJ#ndkT7_# zq`YAx*|fRrNWDuzNJw+f;q$pv`T1*N-+)U44=kvOSSS>Vs@{ zCj(vAFfy8CsbzBG7qUz~MH6W_T3O9UCGBZHQCvcizf#GJpn@|1ZcE1%jenHXLd!HZG>D=n4;K5z`$!un zg{>{G8um!-H(HycyR{NyL~<;RyC=$0X;u9Qufiu{FQ{I&{ENa(evw!6=FSh5>;!+^ z-p`Ip<@4~u9z&+OTnM@;njSoE!|B#_+x_BebADlWP39;)_U1*VRG;}$$`K^@*U~yW zPV7U8((dCjN{oh4kFaP<&thMoUCR9UJ}O6^cr#yI+|twTMX(S zS*vKu|DT7@ga4AwA7>M30CPyA-rm(yIB=B%-RDHBu#&R7u!~;Xb+rE90;k$(w4*w@ zs<;$ zpi#gs>K>V$OhUQ-)&v2}5x?#}B6U!`^#X!n0t@(T>x{!wqAGwuu3(yt zZ`D?`ts9a`4y>&!kayOu#bh~Sy$TE$lIEw&A4sXBi&pS*nUJrEjl*Nkb(_t{#JkW(27zr|{WIbE>k<&^ zz`vk?KcUdrFGY4Pbv4YWwB{!K{$lbyb*X)3SPy*@g;-i%o6KcACYmkM*vI?|hx>;Mm*@x+7cE^a-J)(-gl*^QKk5WDT&z;89!b^=D2p6S#@@uy>9VCfj4 z2=-+&HFtM+oxL=MQI?a}8dBde-ZE*@G%^O^gI$_(B|indiaDcP#|a@L7@@mO+;5nD z#Y=1>#&0~WvIfCMYwr<%?OtSdVICC^3JUt<#z=yDj2shwSc2pmWVo^*?LqVI=%jfb+1vwQ|ZOCiH{=x{@waI3Rk$H7r)_S&Z_HA3rc#n+mNFF=Z`WTPv%-L9$J$}%MME)g~l;k0Wd zO}~KD>j+74C-rKbo;S_TsUvmXaAy2*!Ko~Woypw%p~QNW3~Wy@FeFFvo5XQTv`)Zq zfr;*8{pt0Z7;?%6j^%SnOu5Sk>;y> zy{lBFP~yR?bkv*en({Q&q+K(M^5(MhM|iw1752Tz#!X*t_U61HXdh=jfQbkX=mU!w zju+S7HMaU1b$jG!ceSeyXbD}rJ2m)Y{Z8v4uumcR|5_ww{>vY@bMrC!&!tMK`nNxj z+)Q|@&U;P%vbKEw?E_5EMA#WKgU=R2(lm9Vpx zNq8xk#9rBvpzd=J+3Fd5}%$r$;->fX_Iw-W(f~y2HiuA>u>^0Xc{nB@^)v8nbm;eQ3UnYx6&8a|U9< zs{>6=X6YA7B7N6{EX{{lxlgRUzPY1!ergHIyr=?}D?@fMKmf2))2WgBwbTvL#*>tFKnDVAq&i>i z+)XGfXP=WjT-*dzm}FA~N30fWNm=VoQykrfhSo%dmM}Igwq%FPTVskf*ob~!aTV&+ zrb|iSZyEIBFpZZf(+|ma0I!*$f{m`Ovl)(O1O0I(eqS74*8}p%t?zf0L6nla|D_#& z*LDeOfN#Lc`24RW6TjYxx7N1#ib;^R)>=vTok_F41Uo!|H(2W$(*wYrd(qZ4m!8+> zX18k++nP#JCOh%>Akt%oKTwItXNPzF5p_-yBDR9>eGtw8|%4rniI7x z{2u=1P|{UpxJTNv`zw?BfWN^+mt*JA{Kz^wq9NcMrXa<+Kz1jJex10wQor&!ahw3- zI@)tO07c%tiYAqzl81$dVskIkaoaf0U^(PMkuuutM@mXF-g8q&4WDUG^P78$Ahh1nMyxchkke`_o-kWs-qyq{MaKfU*i=VWx*7I#}`5BZ)P-HcFL7$!c`?1hg!i4WuG8XZ6VA3ppj1>33{W^`$B%Sd`d%imLmn9D8Yh}m10QA^+!8V!Nfg} zON>HC@{NrmBxmne8YqL@Y<9T+9;5tKy(R462LL7w&tKBwEfw|({G9tD+h1A?hsm<$ z_2}y9G})$AJjL*++;&PvoC2^B=Sfb0nlUbO&W^fFe_x+2n$KhPGB$)(cih}`}@dVL(6TlJjWH`W>@wRm7; z{gH1BfaEU7)EJgox$670J~NKTgyMaIf$vxT`hb5`M*mme2v|1#k54dX%4qiwNpzI? zmrt;ve(fYI8Ne??dTCNcH0-4kvWkQK&hbTlsFHf7BvNL{t2pun3 zq3e#CFC(>36Y5t-fJ41#Tpp09_Y-)PI!um#hY}S^^V4@>ka66Z0MQSn>tsakw6RsWj^h%*) zsroNFvXrDf8Ms=`?M-PHO`lvkHO%Qbv9 z!{LZOPM629w0RA`_PRN0fI1q+R8-dW^dZ-e$h15HHyz0puo4Z28hO}Fzx|rZJ54XQ zE+AdY2Pp{-0Enfl)LL~Xth`JDPX zHDY=7JH5%o!3P)2-NknJxPK$uV{)DMrFmX($DOM&5! z*TMmNoIem)?L-uY+5*_WJRu+F91!@r9R3RENv@jM15jNYd}?TDodYmYQbiZ@sM8E~ z3&{DsWVR6u%p!}`fLuxv?zM%yTPv>Ls`&>q-)#*jk6-nPfjPY*gf-&yMVIyr-u{cgc0VaGv7? zJqvGSUsVKifG8F6{Xv)Hwysn7wpaBVcBj{;!{oEXlqqI5r2U-_Vzx{ z{ne#=(dd8xypvZUAn%ix+gG+{<|eG?1TT&pU}uOt5)T({-U+B27RH@M`8l;%0f3jJCV{Op*1`z$24{{t|9kC0%bFBY)sa2;`ylij|*6?^Q3qf z^4V~;i6uq?WsVO}tUwZ@m}RO>YG<44aPC3gz332nutNLmKCj`j3X_s&dS6|hRWD_P z1uqA-?*qByo_bRo&IAhBvNW9CGT`mShoN?9r($BssMaqRdEBm^ea-h|7a!Or`HU7L zxS!U73mk1POsl1oUP{MQ_xTp;kYp?aa8&dNKIEB2Xm(f6tBnH`snZ~akZX>?$0y!= zxUAc7G=u>T<4A$ABAZdYkEawsqe=121rWYf%?25~5$BpnR8V`gpUcAcBXVlR-sHeE-&bhF(K=IaMJNVezOuB3Vh zA$z+69At>e6;oT+gA)P0V$)I7hXNV|a{EAC%j@l?Y2P-V$sv$8su4)tQ}_+9l24A5 zSdXGMTh{1@#`mTgjHFI4(1o`*T>unzWG9gEHufttTIyDcJ1`UjL}d#Xh>BvuU@t|g z>|PqQnb0=mj;?#;_=OV|jg;4u48am8Z>mSZpH{ghZhovunw~$4oLE zA$=}2+_@H3pFHm6n>5$D(KtB6;M)z;X6>uf1H~M|xOuLQpGX43Q*G$n!q-%)xsj(( zqfN2+uT+#fs-c@=~kaqRJp*!~Z=qcp=aD`zd6UzoYzLhZ{or%TTd& zH{Mv!swdUn=f(0=Sh)U{9tmHFlKktJEfPtnso865CY@rD$!J@uun>8Hm5X82VO6xv zGhsfPYIuDa1!A6+fpi`gr=KU3xslktZ>$UBMGdNg&INFQqi(sJ zY`+p!O8=)%$AEh5Uv+K}tlRlfYBP`_>>412sgZtj8#Fb9OR_okkvniW0pJb-A*_BL z)r;?KgLBP7kSBBE`RQLh2EvRV6@Fd0(GZwU_5f~SraA%OOz52ACcThO3AgA7u3)wS zF=@01$XRPv+8K-N!oB`ylpJ}UXP;^EhBfSPQf%MW%>K9KXpf?t%T5z^|@Nz7( z)afFw1_*8IidJQjdO{yN67XxIAS4z%9p{^8HxP12=P|MV!o=c9>z9S?eL@Jecc6|d zsn;8GYF{!vmEe@O;ujM0k@2G;gJg^%;aeIiogY)j6Cb~Vt~}NLA-*uk`9R`S)78__ zVO}G5ZB#FxIEia*l$1YPliz}NZ(W6xmWpC0K9i1R2DK24g2UAQ`79E!3oTRQ*(ZkZ zx+3{5#rIMuOXJrlF7;9wsz8~m7E@jyG#0aD4D63gF~|pH{FcrF;rE6+(0j3#P`Jh7 zyrIPEyyp~u;XumaQutow=!7I);v3t=#hIYzEJ^6xm{W_4WM$(hljfpW+^ibIdd$Pr zQ<6h*HNf5nf4N9l)(+;CT&=n2ok_m*e7Tt7FoN}dJL~<-h-WF^2EIvOZ#teQ>NI`D zbU&7Y>IYloQM=Wh6sFv}S3NbEG4D%<$t3IQ`55`qm6Ayv^Xm!bEZZfg+x53zrjU3_ zs-@#y{5TvnBGP?ws%omUkOOpv=cALb`V{*oRHL2bR;N?etHpdMJ+>KUv27b4w0u{z zD^)*^ERf7x6Z8yCgIk1*o%o=D4#h%|o%>I>ANT`GNO05(*>n+MQ?{ue#0Mgf^KAvS z-5XzUv|R@BOJYXOc8mPiA@ikto5O1F%fA4v8(qM)ls~+`0Sshr;-encUyrV5H36wa zFgt^uFZe)I+%e?6OoKZ9KjAAw zO@rF6-@mNZlLb7t=)SZ~i>Gc1^#Dd$McW`k?)2bS&`VeS!FR!T4aYSNj5-eG-Juf= z*{RI3eq({@y(sebhA2dw8`ZDn>dbqDLJ*MLpdVjO;qM4hj*FYz6>M&-yceDy zj~$}w%6WN}hEK;)FwUKeV0%v=xiSF@qa8FVww%}bTxvf}K=q?+D0e~S%fdS*W9;R+ zt4-69WQt&JEna@p;^%;;x@E?H!5P>=Q~tLdwEJwJ{hggyz{Sx@)+p1~lGws@3tczE z-xQ+#x!U=_C}FNOd35ocWJ-dBfl9BvC!NM@q72*$qs=loWH*BDkF|bX4d;J%`z04s z$cEmQSQ<7))y(+btn@h6(?>^>1wNlF2$vPHl*QyGVNYaQMZMBKcrzm1wB1);)xNh2 z*PJh-9Eolx4_~_2N3Ny;7+HN)jkw zs1?yCq@9L8r)=yq0#BG2Rp~mJir@5P2&Oz?&GFo-O^qPxy`BCp_0(7xyQC z98Wwd8R<3fQUI24Qn5>0UwDub=2tLRWv=&~gb$G>bELt4u}~)`2^c z?0kAAS0K;(pmtM&P8DK<`>lK=iC!6$-^jjbQaxe|Y%-9z=8y)+)ryfKgN>~ zaAFtawImNcA{ZDMtCVKxS2UtR6i;72Mv0%*wl}qX@sGM2>53Ud4B>b@w3-}biynv_*m*pRC z*;F4)$3nyDYQsrcN>h|Cdy9ZUMK@7Rdk8;Ryaz0GQ2tYs53g%a;AxiNK;Nw(R(_ro zmxoqgB6d~*$((Oe2Dv`})NSe-g2G3Q8bAYzq~2Qt*O-0_tgS^wPpSY3i)Z_OhnbeQ zTnFgDqpHZlkF@HXZEzse28CzeXgomjm79 zJ?0eO_pABg@UgSl4tiIe8ZCNj>`JgsahMxfBR;V#`AW}z;_)GnTjrK^qrqWH+l{Us zs0d{Iy;md0MqhQ~^Cn*Q(dGOy#RWU7f9`wp3sG^U<%Hoy-)yE}5%k ze7?iu+5#s?6^o*6G2YkF_eOH#6a*7gQD*tktown--`7bS9*18GZ5VhwOXJ#cx7)e) zahE%ZW?krl)vCmPl=V*{9B!|6eTVMd2XCKEp!}i|RR80i`IiIVkNho}tx@K?#(!&O z{7O#A$duQHNMwVlQfW9dTPp${ad?MonzO1eMG|yOFMP>0Ss8PA>nE)cbqTSz#@z6Z#NTkyxvl*M^PQD_30DI0l)P8*>zj5toA8N0n@{8L? z=3G8^+Ltap8y}-`)D3rpuiE&!Dp_ap+)o#u z*A|Z-Z0^S(pR_7(n5>Kq%=Nj|1J}VjJf+-^j6t#itzT&VpS32=y#FS?deZEKr*tUN zO`h&2!cSApa>VWLm)*c!O&J8(CNE_f$-7;~VWPoO;^t#%?ORDH4MR)WnEq_`JVs*K zuk2Tpz$$_q~hEEiphLH&ueyZr7bWuwu7$;2jp`X0YA6ZUDg=&4`0QfE#9RANsmzegi6v(%KX z{?bF~bmxdWEW7a3q`usrtjeBCot`e;NxYGsuKcBOBDoT~FHYI^AF424gs;CV>z1C{ z;i*Q{vCBO)S?S?~uMLLU?GZ62kJYR`l9Fu6pJQ(;$}vgGEYt5mqhHS0X>I}Lju ziMP`D@t#*qEn{2b)~r6ztN6ar?If3(wiI?cUbXJ`Kqa-(x-ItF&oiV`%sK^AT9Dq_ z&yTsvvW!~gm!Z(sma>r3dZ>ZgR`~G2C($Xu9Opq3Q~NK){huyyO89Sk2!{DF>dzz- zSb+Ku=>!HOh16_56~2=7xI5r*Ip>Mb?d)!Jg7Epo5Uo@VM@}f{2h8!3*|w9}#aFqX z+P?#1_hbcHS9vAko670(d}=L(#QC~V@s%i!PaJ_zj!p#P_4`}llG`L%gRDdm0oJDH z(z7&oT0Fsu^PM~FtM6;y^oRU|`g);m>Ww-M*kAXqOiR%~euxJ%ECVFto#{l}J_;3@ zHDA`u2Ob}|cu4(Jea+|Oqo8kYvG>gvjkZN~!@CM>y|(}_0A)fHN)3qPi`aDT+^4>{ z22`LaFAk$ZWLhGDX@~n~1{J%WZY4^otdpN|IBm~vbC$W9w~JKFkW&GZ`ibv0XMptH zH41=-jC2V$)ZUK=JGrI#?rmdBrZnYnJDAh`2GsYdgI0-mR6f&0<~qpdhr_R`@*Wr5 zI9{(z+e{_py<(`s9k|E_i(#YTPBnyS2wG;X-$Y}7;qA0Fr9R4RdiKE;kt;)aT?SK_ zcNo^QZPqDb3k)#k>GDg3^|*-^r5wkXj!XMtlTdb(T5fMF_eZz0x&&&voj&y9XPY%; znrTqqdzaTQB9QhFcM@43%fJV{IuKh%>$(BHATBZK5))<*!wrme%C!IdRm7ldj(Ow` z)tA4pbq`zw1N#WOxeGv(`pafHg|O$|8ghX|?m!_7e+48=*8*JkE_%}GvYw4f8jxh| zWj880AkAEs_U$&{##x^sG^l>Rgj1UAz2fhdgi>=fb-xIuGCAzjEmdoyRAD>Yn0NOv zP?~}D1G8TJje6^JV7DU8cf@PLuSK&ZwMD46#r=i^hx<74?|7(fhp=N6ajS7gniViY_0l3e$H+dn(t-oaBAThOG-PEhwum{ ztR`Ka*(rlx;**H)`Aw0u^W%Eg`Eb15u*BwvDh9%77n~i!*zN_zDN+uG`XR~6oVzR{ zWQP_TlGfDx)s|lS5$B2>G5Cg_ux$pjUUwZWRFK)vbU|C@}<1!BoE{dSqcTTl38;sd1KI(G4eE4F`uYX(#g-psUs1mjt6eW5Z!QUvPPZtdBw5 zHzu;x0w_8Q{i9CQ+A*98il^y8l$fEzG~Ajsy<3Z2wiXa6wvb>RvCZH9CCX0p4OdhI z*Td2>p?Qr~(!|7y_qpma^`}RxT|hq17{9~n$!$Te2T$9yf9!2cBbl_|B&IG(={>CL zXQ!KI4(nI%Ff0QAcL99c8+QUEExO*rHBD7~q)%MG+2^Lhk~FUa<-|v;hJ@Z4|7cpf z4@)Sepc8{p^A2ccikFKyMWIHjJ>1<&wh<^PciY4Hy|QqMud2-^$g}{ zq+u!#D+Q#KqNE83#%8eqI&3ON=SiZB;N)gKwN4UwSw2^z&&%KDSurn5&shkm=m?F%IEeb=$^rlh|2>NDkW9}oBD{s$k-`?Nk8K5oai*d*Lw)qB?&mksfS=!e*jX|ZaQO`wsF zN5(T?aRNKhZG6Y6mP`X0cZ>jr5RzMUafUbE>!G!_?n>C#4>6wJ?X!?Tuo12GGrnDC zmahq`ZE(aOJ@zO+q~V6p8@K1(_F1PV2yw|AdtkLyJdNspgx*`f=rC^v53W`uvFeCH zu4@YQ>-AGr@AAv*Y)PRa{!p_v1p~Vx|MZ6B>iM0gbDBuENDfeCSnT}2=$Storofo? zKMPxvGn%#(e+zW^91i@l1eGtcG^{fp$=?WV4oNH_ZsGJ0F_Aglf8{Gw4=Q13)&9Pz zb)d8ek0_E`y>0WNBO$8>yAGa=3c`6=QzOj5F5%p(oan#s!34~|@xk5&?*t0fav?!i z+UII>B?2#VII^!QBW{Bd1E>*Sa!VAY6$F=f5QKr}YF?yzGzk}o4~XFzFXsM)Y(zqKOYC)qS%fs2inttKH1y>mjV~jz_}WZyw}-X0urfbjJiIOuOEZ-^I-#8 z<9nmKu{()`kc9)iQQ6V)O52}no=8D~CEJRwvOzq0Igr+emcS#h zG57ud6xht6zXkSuflkgJfeoYz1O98EZ;K*rg!7-8y+590ke&$I*>9#r_`hyQ@jqB} z>f#JS$@;nHV8%Q}4526WS>9?jW>J^_YRg%LO1b&?~Q2osC&NLE{5H zw$%Ls^Bco`d1?gvJHK^X6YSy?(D;o9QcNI8Ec;Fda{=>m{--C3TLVgf|Bp~1j@9Z% zo80NP^T~GG@gZKKV&0F`w)SD|4ZNa}Qy9AtPKaVz(k)!j!STDw+VAc^m;q2r^zn$D z`~2avoRr7-;8L$Wya9?5=#NKp?AM%jX!$)n2rn?K{&B{=Rqe1L>DzEV)1v$?D0ruB zKeXJU<1MDpz0UTM{ddA);wO;jtbj#iA->_{V;5dOU!B_|%QA6G;PwR`U*0~?UbqVF z&9x1R2M0jRgt_N8a^w3BRWrnAoT*KKizC{8!>|W6j<4`7febOXjLoqxw!Q>`cnk1Q z>3s#ikef zDrhD|TU>*La!w`$I>?(ds-(k5D;~8%Z&Nb~q-T@oE;<)3qBf0ofIb(aek45{5pYI!8?Bhg)!+gG+& zSSXNICeqh?D2CNH&JDskI@~rk{3*CtD@78ySH@yFkQVj9SGg=%q!%mg`Y|XxI%_fe z-ik%)}_`%q|)#x@K~@hLarf1cqKz?1e12f8w;)9_zs7~qeueE|}z8@k1ygg%!})(_227z`StHR_#3 zO93pR3__MaJ5ZA3WLscU`c=@Y1cLJ%e(d66w%@7NC3=ttVU1w13@QPDbWzYJjnqH^eQ0| z0#BQBZUsm~9sbx^zug8x|O~-v?CR(>xFU*&Bf*OvUD8jca-*K(bul@hxYPg~zJn zG7PpdFpp-&&JgGh`v^e(kB@6OJ8sy=;@dq7H7HymZ4i6WBIR4;nx0o>Wim>TG`AL? zd}mqK905rJb#HuKdwVyq(XWL@m14E8oxD9W1lW6_&G+{9?gGlt4$O*fgICsQ!U#~o zCP84V-fJbAvn(2iK=1ttCG@~l&})X=M6a-J?};30>L~Nex&K4jcSbd}ZqbG&0#XE( zCQXq@k)qPOqKF8HsGuN0>AiPI02QPwMMOF(h)C}>0R*H51nCfZhlHMll(+Goqj$V> z-u?6Z$AB^1+4;V;=A3J;wR{PlMgD2*CF33>Xp9g;`Y5{!jM;yrB{aO`xx_~^);hc! zS>WI~^82^KDKY9|r^(ufCo`UYpZirR&eUx>3QZ{2`U`_{zWkf0LN4+ViMU1<*p-#C zIiZ)!6Z*@G7>?2*nuSvBDlIp& z+1k8DJ_7A?EtqsW!MWlG$}Qu!Q<9!|)~9Bg9%`mtGQJk0P@X~Y!^EI*^m`jey_P~w zV!6Mp!z;u%jV3UZAoMr_C~r{z6X>x7xhg#I+^cBB^0+_szWUc-a7(GLM$s|^nVMNK zcjJet1+N+hZ3Y=U%oj#e(U+5SG8h)*;W0Ii-Toc9Jgf^!63&$=jj3NFO}dNZyQtMG zwizGn39E~bUvtjszHTiv*1g}+Rl2ObEC8!EDc4rP51_tb%QpJ7YNFqe_)R`JJl9^m z{=MRg{|)~rVX5v$OU1LcRF4cFSLksoeNao^HqznWDWLf`p~ijj z?Pdh%&K(MA7Ha6hu^7EEc2`aa#Lf)yptcVCX+Gy`Vh>+sxlz=6Ri zYj8~LwI=wMykwy~a+*ov>(u3o=Pd8VoG-GHZ#ubN*ap%X3_j7l?C$B_Rk7oMY83%S zgVU;)t@6@4u$!%xUBN6~mg7+T$^)BN%g&zS2^K6)^Z23l;oOhjB(uQ?bBk*s0TXk) zOM=^kADCnx{(AvlAun2%SVQ=~jf8&$_706`dClb=W{`0iN4(SafiJELVtQ1_#P20F z?ta?-k{jn2!Ls!03mFzk>`?|B+ zc)gxCEsI~jlD>5U4mnc6(?EU)lisp*6_fuVr9C`u5^%!ycBf&Z+Y61rj>6h?mU_FN zA}ETTE(oaLrG4jU4usP;vf0k!4|`xOTHjVnRoX4HZG{(s=}$9C^|$2@ET_7CSwiA>JC%}Z zgm<0}N;>ywJnBvvhy}(t^@TfIW6vrs!n0!c&8JfQE~~~$u>)2{R>&L2p%0Ank50YS zx02V~Zs^8$&!$N&^==LMjxv@0X9oMyI#^8-1(sgu==72wvRp~|qScWw;Bc^LI!Dh(LW_TMAeIW?CVQS5pbLq$*=cwzfROVx{WiPX~qw#t9^2Q(mEqB^rv$!bBoCGwg1xJ;sFzt z!v(^Eb~H%IdQ)j>X4)9so5^Vp3T9UgY`f;tJsD)cM1~3OOzt4(u7tF|#tn|Pusaq# zyX+-MAxA}XIVi+lL3>t-&G$9ONSoZ+)h>z`ZV#ov_bA+`eU1BQA2oMge#6&v{G`J* zm0-RgHVS?XRSgW4I`hY`@xP6cUi+<=wEG)wmU@`6mM9Elr!S& ztE$(_?#4{?VW;#tp@l;<#^>o6NEGz z{d(xgc|mR7b|rmq!WIkIv2GqAT*1GH%Leb-E3>9j&bt-cLsmyGezopBMl|RjHM>dKo|Pg z$~5mYuRs&^8w!8yLw_gGJAVH<$$sW?>mnR$_pGo>De+rLdeV?aKN&eygRt{@;!TWN zno?v=I`iIFA?R$gHvQ*>%xfp_rG)Vbhiu>cT&^=WqMLa&2J+>UYW%s64`~Ew=qW2^ z_0?XT3)YyeGC0YrL!lmgPhH@%j?zKzh)8+sl>;gBA!j0-th}U~I)U!BXd^9>4zP#4 zD*i{SQpr0#J_Th%W89BNb1TLjMoTtig&!~H8N6#J@vJ{`SlF-W4p1M=szSH#_-wu} zXnzL|=oxo+L&{YheA}~ZoC}y;fG+0-=va^QDI6H^;UbYfs{6@d?PEFCfqvHfkaulo zGVtdQA*E49!5KrXb(95`yS`gd8u`?wiywS?nhRKcWJR^A)7jkC6WEhm4bkV6!U8;$ zRPc|O&1bqQBB3JV$P@AxsnfQ2XPam@xozvLogrGFDQ?p&CQ5bwG}nr1BtQ%y9-zBL`6@E6~i znc5wsK!l%RdX<@hqgZ1NKF!D)Buqgqf666`77;)#ld4=NGvSYXVt9I_FMdJ|;_N{w z3Fm=MkQ@W0GZ(yD!j^iS7j6Upl~5sc*gJ3-<4x*C;Yw#+n-_O2Oc!^FyB{C05>3(6 z6J2^-=|UiPg|!UQ0IYTi3RoGg@DJBpR(*Sbo$y-=phJ)e8RVTAW@47=RrKJvhZ3!B z{RQ0H?PwG>Ilugi3acY3DEkWK!2$z$W zfM<5XgURz5CJ$Jz-XW1t%(rm1(+TzU1*aq~3A>DfSFoK% z7id2)WNLgcLn|#D=UR!py`Q)lq?>Qx>qqq-Pd3b-+ars5oL)N}^>PYdVZ0Vh7kKyBEdO(S3irDjRO45C#V6;UJe7*4w6FHD(L{Hdt zSl^fxglsCO`!E=}78y~s(wQVtn+aW*ZVHj-N2oKK@LW_kqx3N0Y!9nF^lz%bFlByr z-5i*uyZRZJQ7OSCdx2#jmo4-Ll7l|;E|}#x8#KA4i6#3aw!B}HvTbqk<^@m&&n6z> zEgc&`7c2`j2E|Kgnas(ypkFb=lZ$Tlqt&%!2~)S)F06PIE$9*RRHZribb)6N?;0P~_ShNMnNvh|om77j3S4di;) z&W*!~<6(Tne!dMh(@8Jfo;7sQI+VPnN@|bM=<*$E(W-!N$&%Vgvd=nqoQVZ+=>WjX zl`7*n>^3Ienur6oZr55rbjO;Aux4W#CeE|kG@k(K@E!rh? zFLgbE<5J)?Y(HSbX#A7$+R6>2Ca2W5$B}qIWq1`xR&?y6Eo@p^V2qacBF#)=+Y)4Hkb4 zUO1(EgUkmlzE3!3(68f${ie6JMOx@&@U+8jmC6(%@azz4ce? z(Y@oL(b3UUl2qx!|B_mY;V1clJ@GEUV<~zC$hqy_q6%UBa&I3KTLZWw5w}2<>WFxUNw=B7?58+An>lqPdbTho&RaKdPRFZ?MV+azRTAKioa+w z(|#7)d<)nJF$HXd*sYh4gxA%^LgNoma9OZ;b

W0ECuMw? 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

    wT|H9D~2FrL2^Nz8yS%s6xU8tg@Q)7 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 diff --git a/docs/source/conf.py b/docs/source/conf.py index 42b8350..38604f8 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -5,6 +5,7 @@ import os import sys +import yaml sys.path.insert(0, os.path.abspath("../../")) @@ -32,10 +33,55 @@ templates_path = ["_templates"] exclude_patterns = [] -language = "ja" # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = "sphinx_book_theme" +html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] + +locale_dirs = ["locale"] +language = "en" + +# get the environment variable build_all_docs and pages_root +build_all_docs = os.environ.get("build_all_docs") +pages_root = os.environ.get("pages_root", "") + +# if not there, we dont call this +if build_all_docs is not None: + # we get the current language and version + current_language = os.environ.get("current_language") + current_version = os.environ.get("current_version") + + # we set the html_context wit current language and version + # and empty languages and versions for now + html_context = { + 'current_language' : current_language, + 'languages' : [], + 'current_version' : current_version, + 'versions' : [], + } + + + # and we append all versions and langauges accordingly + # we treat t he main branch as latest + if (current_version == 'latest'): + html_context['languages'].append(['en', pages_root]) + html_context['languages'].append(['ja', pages_root+'/ja']) + + if (current_language == 'en'): + html_context['versions'].append(['latest', pages_root]) + if (current_language == 'ja'): + html_context['versions'].append(['latest', pages_root+'/ja']) + + # and loop over all other versions from our yaml file + # to set versions and languages + with open("../versions.yaml", "r") as yaml_file: + docs = yaml.safe_load(yaml_file) + + if (current_version != 'latest'): + for language in docs[current_version].get('languages', []): + html_context['languages'].append([language, pages_root+'/'+current_version+'/'+language]) + + for version, details in docs.items(): + html_context['versions'].append([version, pages_root+'/'+version+'/'+current_language]) diff --git a/docs/source/index.rst b/docs/source/index.rst index ced7ae6..bca95b1 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -14,6 +14,11 @@ adf-core-pythonのドキュメント パッケージとしてまだ公開していないため、pip でインストールすることはできません。 +.. warning:: + + 以下の言語のドキュメントは機械翻訳を使用しています。翻訳の正確性については保証できません。 + 英語 + 概要 ---- diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.cli.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.cli.po new file mode 100644 index 0000000..b20a78c --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.cli.po @@ -0,0 +1,38 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.cli.rst:2 +msgid "adf\\_core\\_python.cli package" +msgstr "" + +#: ../../source/adf_core_python.cli.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.cli.rst:8 +msgid "adf\\_core\\_python.cli.cli module" +msgstr "" + +#: ../../source/adf_core_python.cli.rst:16 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.ambulance.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.ambulance.po new file mode 100644 index 0000000..fe4db96 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.ambulance.po @@ -0,0 +1,52 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.action.ambulance.rst:2 +msgid "adf\\_core\\_python.core.agent.action.ambulance package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.ambulance.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.ambulance.rst:8 +msgid "adf\\_core\\_python.core.agent.action.ambulance.action\\_load module" +msgstr "" + +#: adf_core_python.core.agent.action.ambulance.action_load.ActionLoad:1 +#: adf_core_python.core.agent.action.ambulance.action_rescue.ActionRescue:1 +#: adf_core_python.core.agent.action.ambulance.action_unload.ActionUnload:1 of +msgid "Bases: :py:class:`~adf_core_python.core.agent.action.action.Action`" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.ambulance.rst:16 +msgid "adf\\_core\\_python.core.agent.action.ambulance.action\\_rescue module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.ambulance.rst:24 +msgid "adf\\_core\\_python.core.agent.action.ambulance.action\\_unload module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.ambulance.rst:32 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.common.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.common.po new file mode 100644 index 0000000..75bd5e4 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.common.po @@ -0,0 +1,47 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.action.common.rst:2 +msgid "adf\\_core\\_python.core.agent.action.common package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.common.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.common.rst:8 +msgid "adf\\_core\\_python.core.agent.action.common.action\\_move module" +msgstr "" + +#: adf_core_python.core.agent.action.common.action_move.ActionMove:1 +#: adf_core_python.core.agent.action.common.action_rest.ActionRest:1 of +msgid "Bases: :py:class:`~adf_core_python.core.agent.action.action.Action`" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.common.rst:16 +msgid "adf\\_core\\_python.core.agent.action.common.action\\_rest module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.common.rst:24 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.fire.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.fire.po new file mode 100644 index 0000000..bb9922c --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.fire.po @@ -0,0 +1,52 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.action.fire.rst:2 +msgid "adf\\_core\\_python.core.agent.action.fire package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.fire.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.fire.rst:8 +msgid "adf\\_core\\_python.core.agent.action.fire.action\\_extinguish module" +msgstr "" + +#: adf_core_python.core.agent.action.fire.action_extinguish.ActionExtinguish:1 +#: adf_core_python.core.agent.action.fire.action_refill.ActionRefill:1 +#: adf_core_python.core.agent.action.fire.action_rescue.ActionRescue:1 of +msgid "Bases: :py:class:`~adf_core_python.core.agent.action.action.Action`" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.fire.rst:16 +msgid "adf\\_core\\_python.core.agent.action.fire.action\\_refill module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.fire.rst:24 +msgid "adf\\_core\\_python.core.agent.action.fire.action\\_rescue module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.fire.rst:32 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.po new file mode 100644 index 0000000..b6471d8 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.po @@ -0,0 +1,46 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.action.rst:2 +msgid "adf\\_core\\_python.core.agent.action package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.rst:5 +msgid "Subpackages" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.rst:16 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.rst:19 +msgid "adf\\_core\\_python.core.agent.action.action module" +msgstr "" + +#: adf_core_python.core.agent.action.action.Action:1 of +msgid "Bases: :py:class:`~abc.ABC`" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.rst:27 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.police.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.police.po new file mode 100644 index 0000000..c1e631f --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.police.po @@ -0,0 +1,48 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.action.police.rst:2 +msgid "adf\\_core\\_python.core.agent.action.police package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.police.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.police.rst:8 +msgid "adf\\_core\\_python.core.agent.action.police.action\\_clear module" +msgstr "" + +#: adf_core_python.core.agent.action.police.action_clear.ActionClear:1 +#: adf_core_python.core.agent.action.police.action_clear_area.ActionClearArea:1 +#: of +msgid "Bases: :py:class:`~adf_core_python.core.agent.action.action.Action`" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.police.rst:16 +msgid "adf\\_core\\_python.core.agent.action.police.action\\_clear\\_area module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.action.police.rst:24 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.po new file mode 100644 index 0000000..93f8b72 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.po @@ -0,0 +1,251 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.communication.rst:2 +msgid "adf\\_core\\_python.core.agent.communication package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.rst:5 +msgid "Subpackages" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.rst:13 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.rst:16 +msgid "adf\\_core\\_python.core.agent.communication.message\\_manager module" +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.add_heard_agent_help_message_count:1 +#: of +msgid "Add the heard agent help message count." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.add_message:1 +#: of +msgid "Add the message." +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.rst +msgid "Parameters" +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.add_message:3 +#: of +msgid "The message." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.add_received_message:1 +#: of +msgid "Add the received message." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.add_received_message:3 +#: of +msgid "The received message." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.coordinate_message:1 +#: of +msgid "Coordinate the message." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.coordinate_message:3 +#: adf_core_python.core.agent.communication.message_manager.MessageManager.subscribe:3 +#: of +msgid "The agent info." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.coordinate_message:5 +#: adf_core_python.core.agent.communication.message_manager.MessageManager.subscribe:5 +#: of +msgid "The world info." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.coordinate_message:7 +#: adf_core_python.core.agent.communication.message_manager.MessageManager.subscribe:7 +#: of +msgid "The scenario info." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_channel_send_message_list:1 +#: of +msgid "Get the channel send message list." +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.rst +msgid "Returns" +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_channel_send_message_list:3 +#: of +msgid "The channel send message list." +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.rst +msgid "Return type" +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_channel_subscriber:1 +#: of +msgid "Get the channel subscriber." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_channel_subscriber:3 +#: adf_core_python.core.agent.communication.message_manager.MessageManager.set_channel_subscriber:3 +#: of +msgid "The channel subscriber." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_heard_agent_help_message_count:1 +#: of +msgid "Get the heard agent help message count." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_heard_agent_help_message_count:3 +#: of +msgid "The heard agent help message count." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_is_subscribed:1 +#: of +msgid "Get the flag to indicate if the agent is subscribed to the channel." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_is_subscribed:3 +#: adf_core_python.core.agent.communication.message_manager.MessageManager.set_is_subscribed:3 +#: of +msgid "The flag to indicate if the agent is subscribed to the channel." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_message_class:1 +#: of +msgid "Get the message class." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_message_class:3 +#: of +msgid "The index." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_message_class:6 +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_message_class_index:3 +#: adf_core_python.core.agent.communication.message_manager.MessageManager.register_message_class:3 +#: of +msgid "The message class." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_message_class_index:1 +#: of +msgid "Get the message class index." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_message_class_index:6 +#: of +msgid "The message class index." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_received_message_list:1 +#: of +msgid "Get the received message list." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_received_message_list:3 +#: of +msgid "The received message list." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_send_message_list:1 +#: of +msgid "Get the send message list." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_send_message_list:3 +#: of +msgid "The send message list." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_subscribed_channels:1 +#: of +msgid "Get the subscribed channels." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.get_subscribed_channels:3 +#: adf_core_python.core.agent.communication.message_manager.MessageManager.set_subscribed_channels:3 +#: of +msgid "The subscribed channels." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.refresh:1 +#: of +msgid "Refresh the message manager." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.register_message_class:1 +#: of +msgid "Register the message class." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.set_channel_subscriber:1 +#: of +msgid "Set the channel subscriber." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.set_is_subscribed:1 +#: of +msgid "Set the flag to indicate if the agent is subscribed to the channel." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.set_message_coordinator:1 +#: of +msgid "Set the message coordinator." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.set_message_coordinator:3 +#: of +msgid "The message coordinator." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.set_subscribed_channels:1 +#: of +msgid "Set the subscribed channels." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.subscribe:1 +#: of +msgid "Subscribe to the channel." +msgstr "" + +#: adf_core_python.core.agent.communication.message_manager.MessageManager.subscribe:11 +#: of +msgid "If the ChannelSubscriber is not set." +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.rst:24 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.bundle.centralized.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.bundle.centralized.po new file mode 100644 index 0000000..8796bc3 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.bundle.centralized.po @@ -0,0 +1,77 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.centralized.rst:2 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.bundle.centralized " +"package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.centralized.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.centralized.rst:8 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.bundle.centralized.command\\_ambulance" +" module" +msgstr "" + +#: adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance.CommandAmbulance:1 +#: adf_core_python.core.agent.communication.standard.bundle.centralized.command_fire.CommandFire:1 +#: adf_core_python.core.agent.communication.standard.bundle.centralized.command_police.CommandPolice:1 +#: adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout.CommandScout:1 +#: adf_core_python.core.agent.communication.standard.bundle.centralized.message_report.MessageReport:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.agent.communication.standard.bundle.standard_message.StandardMessage`" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.centralized.rst:16 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.bundle.centralized.command\\_fire" +" module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.centralized.rst:24 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.bundle.centralized.command\\_police" +" module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.centralized.rst:32 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.bundle.centralized.command\\_scout" +" module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.centralized.rst:40 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.bundle.centralized.message\\_report" +" module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.centralized.rst:48 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.bundle.information.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.bundle.information.po new file mode 100644 index 0000000..46d33d0 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.bundle.information.po @@ -0,0 +1,84 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.information.rst:2 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.bundle.information " +"package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.information.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.information.rst:8 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.bundle.information.message\\_ambulance\\_team" +" module" +msgstr "" + +#: adf_core_python.core.agent.communication.standard.bundle.information.message_ambulance_team.MessageAmbulanceTeam:1 +#: adf_core_python.core.agent.communication.standard.bundle.information.message_building.MessageBuilding:1 +#: adf_core_python.core.agent.communication.standard.bundle.information.message_civilian.MessageCivilian:1 +#: adf_core_python.core.agent.communication.standard.bundle.information.message_fire_brigade.MessageFireBrigade:1 +#: adf_core_python.core.agent.communication.standard.bundle.information.message_police_force.MessagePoliceForce:1 +#: adf_core_python.core.agent.communication.standard.bundle.information.message_road.MessageRoad:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.agent.communication.standard.bundle.standard_message.StandardMessage`" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.information.rst:16 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.bundle.information.message\\_building" +" module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.information.rst:24 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.bundle.information.message\\_civilian" +" module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.information.rst:32 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.bundle.information.message\\_fire\\_brigade" +" module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.information.rst:40 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.bundle.information.message\\_police\\_force" +" module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.information.rst:48 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.bundle.information.message\\_road" +" module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.information.rst:56 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.bundle.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.bundle.po new file mode 100644 index 0000000..29b826d --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.bundle.po @@ -0,0 +1,67 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.rst:2 +msgid "adf\\_core\\_python.core.agent.communication.standard.bundle package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.rst:5 +msgid "Subpackages" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.rst:14 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.rst:17 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.bundle.standard\\_message" +" module" +msgstr "" + +#: adf_core_python.core.agent.communication.standard.bundle.standard_message.StandardMessage:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.communication.communication_message.CommunicationMessage`" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.rst:25 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.bundle.standard\\_message\\_priority" +" module" +msgstr "" + +#: adf_core_python.core.agent.communication.standard.bundle.standard_message_priority.StandardMessagePriority:1 +#: of +msgid "Bases: :py:class:`~enum.Enum`" +msgstr "" + +#: adf_core_python.core.agent.communication.standard.bundle.standard_message_priority.StandardMessagePriority:1 +#: of +msgid "Standard message priorities." +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.bundle.rst:33 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.po new file mode 100644 index 0000000..0e0d129 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.po @@ -0,0 +1,51 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.communication.standard.rst:2 +msgid "adf\\_core\\_python.core.agent.communication.standard package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.rst:5 +msgid "Subpackages" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.rst:14 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.rst:17 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.standard\\_communication\\_module" +" module" +msgstr "" + +#: adf_core_python.core.agent.communication.standard.standard_communication_module.StandardCommunicationModule:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.communication.communication_module.CommunicationModule`" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.rst:25 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.utility.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.utility.po new file mode 100644 index 0000000..69e19d9 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.utility.po @@ -0,0 +1,133 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.communication.standard.utility.rst:2 +msgid "adf\\_core\\_python.core.agent.communication.standard.utility package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.utility.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.utility.rst:8 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.utility.apply\\_to\\_world\\_info" +" module" +msgstr "" + +#: adf_core_python.core.agent.communication.standard.utility.apply_to_world_info.apply_to_world_info:1 +#: of +msgid "Apply to world info." +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.utility.rst +msgid "Parameters" +msgstr "" + +#: adf_core_python.core.agent.communication.standard.utility.apply_to_world_info.apply_to_world_info:3 +#: of +msgid "The world info to apply to." +msgstr "" + +#: adf_core_python.core.agent.communication.standard.utility.apply_to_world_info.apply_to_world_info:5 +#: of +msgid "The standard message to apply to world info." +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.utility.rst:16 +msgid "" +"adf\\_core\\_python.core.agent.communication.standard.utility.bitarray\\_with\\_exits\\_flag" +" module" +msgstr "" + +#: adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag.read_with_exist_flag:1 +#: of +msgid "" +"Read value from bit_array with an exist flag. If the first bit is " +"IS_NOT_EXIST_FLAG, return None. If the first bit is IS_EXIST_FLAG, read " +"and return value from bit_array." +msgstr "" + +#: adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag.read_with_exist_flag:5 +#: of +msgid "The bitarray to read from." +msgstr "" + +#: adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag.read_with_exist_flag:7 +#: of +msgid "The number of bits to read to get value." +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.utility.rst +msgid "Returns" +msgstr "" + +#: adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag.read_with_exist_flag:10 +#: of +msgid "The value read from bit_array." +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.utility.rst +msgid "Return type" +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.utility.rst +msgid "Raises" +msgstr "" + +#: adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag.read_with_exist_flag:13 +#: of +msgid "If the first bit is not IS_EXIST_FLAG or IS_NOT_EXIST_FLAG." +msgstr "" + +#: adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag.write_with_exist_flag:1 +#: of +msgid "" +"Write value to bit_array with an exist flag. If value is None, write " +"IS_NOT_EXIST_FLAG to bit_array. If value is not None, write IS_EXIST_FLAG" +" to bit_array and then write value to bit_array." +msgstr "" + +#: adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag.write_with_exist_flag:5 +#: of +msgid "The bitarray to write to." +msgstr "" + +#: adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag.write_with_exist_flag:7 +#: of +msgid "The value to write." +msgstr "" + +#: adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag.write_with_exist_flag:9 +#: of +msgid "The number of bits to use to write value." +msgstr "" + +#: adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag.write_with_exist_flag:12 +#: of +msgid "If value is too large to fit into bit_size bits." +msgstr "" + +#: ../../source/adf_core_python.core.agent.communication.standard.utility.rst:24 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.config.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.config.po new file mode 100644 index 0000000..e647ed2 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.config.po @@ -0,0 +1,42 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.config.rst:2 +msgid "adf\\_core\\_python.core.agent.config package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.config.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.config.rst:8 +msgid "adf\\_core\\_python.core.agent.config.module\\_config module" +msgstr "" + +#: adf_core_python.core.agent.config.module_config.ModuleConfig:1 of +msgid "Bases: :py:class:`~rcrs_core.config.config.Config`" +msgstr "" + +#: ../../source/adf_core_python.core.agent.config.rst:16 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.develop.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.develop.po new file mode 100644 index 0000000..70a33ee --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.develop.po @@ -0,0 +1,78 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.develop.rst:2 +msgid "adf\\_core\\_python.core.agent.develop package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.develop.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.develop.rst:8 +msgid "adf\\_core\\_python.core.agent.develop.develop\\_data module" +msgstr "" + +#: adf_core_python.core.agent.develop.develop_data.DevelopData:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: adf_core_python.core.agent.develop.develop_data.DevelopData.get_value:1 of +msgid "" +"Get value from develop data If develop mode is disabled, return default " +"value" +msgstr "" + +#: ../../source/adf_core_python.core.agent.develop.rst +msgid "Parameters" +msgstr "" + +#: adf_core_python.core.agent.develop.develop_data.DevelopData.get_value:4 of +msgid "Key" +msgstr "" + +#: ../../source/adf_core_python.core.agent.develop.rst +msgid "Returns" +msgstr "" + +#: adf_core_python.core.agent.develop.develop_data.DevelopData.get_value:7 of +msgid "Value" +msgstr "" + +#: ../../source/adf_core_python.core.agent.develop.rst +msgid "Return type" +msgstr "" + +#: adf_core_python.core.agent.develop.develop_data.DevelopData.is_develop_mode:1 +#: of +msgid "Check if develop mode is enabled" +msgstr "" + +#: adf_core_python.core.agent.develop.develop_data.DevelopData.is_develop_mode:3 +#: of +msgid "True if develop mode is enabled" +msgstr "" + +#: ../../source/adf_core_python.core.agent.develop.rst:16 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.info.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.info.po new file mode 100644 index 0000000..e50dbf1 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.info.po @@ -0,0 +1,317 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.info.rst:2 +msgid "adf\\_core\\_python.core.agent.info package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.info.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.info.rst:8 +msgid "adf\\_core\\_python.core.agent.info.agent\\_info module" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo:1 +#: adf_core_python.core.agent.info.scenario_info.ScenarioInfo:1 +#: adf_core_python.core.agent.info.scenario_info.ScenarioInfoKeys:1 +#: adf_core_python.core.agent.info.world_info.WorldInfo:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_change_set:1 +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_change_set:1 of +msgid "Get the change set" +msgstr "" + +#: ../../source/adf_core_python.core.agent.info.rst +msgid "Returns" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_change_set:3 +#: adf_core_python.core.agent.info.agent_info.AgentInfo.set_change_set:3 +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_change_set:3 +#: adf_core_python.core.agent.info.world_info.WorldInfo.set_change_set:3 of +msgid "Change set" +msgstr "" + +#: ../../source/adf_core_python.core.agent.info.rst +msgid "Return type" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_entity_id:1 of +msgid "Get the entity ID of the agent" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_entity_id:3 of +msgid "Entity ID of the agent" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_executed_action:1 +#: of +msgid "Get the executed action at the given time" +msgstr "" + +#: ../../source/adf_core_python.core.agent.info.rst +msgid "Parameters" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_executed_action:3 +#: adf_core_python.core.agent.info.agent_info.AgentInfo.set_executed_action:3 +#: of +msgid "Time" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_heard_commands:1 of +msgid "Get the heard commands" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_heard_commands:3 +#: adf_core_python.core.agent.info.agent_info.AgentInfo.set_heard_commands:3 of +msgid "Heard commands" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_myself:1 of +msgid "Get the entity of the agent" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_myself:3 of +msgid "Entity of the agent" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_position_entity_id:1 +#: of +msgid "Get the position entity ID of the agent" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_position_entity_id:3 +#: of +msgid "Position entity ID of the agent" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_think_time:1 of +msgid "Get the time taken for thinking" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_think_time:3 of +msgid "Time taken for thinking" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_time:1 of +msgid "Get the current time of the agent" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.get_time:3 +#: adf_core_python.core.agent.info.agent_info.AgentInfo.set_time:3 of +msgid "Current time" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.record_think_start_time:1 +#: of +msgid "Record the start time of thinking" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.set_change_set:1 +#: adf_core_python.core.agent.info.world_info.WorldInfo.set_change_set:1 of +msgid "Set the change set" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.set_executed_action:1 +#: of +msgid "Set the executed action at the given time" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.set_executed_action:5 +#: of +msgid "Executed action" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.set_heard_commands:1 of +msgid "Set the heard commands" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.set_time:1 of +msgid "Set the current time of the agent" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.some_one_on_board:1 of +msgid "Get the human if someone is on board" +msgstr "" + +#: adf_core_python.core.agent.info.agent_info.AgentInfo.some_one_on_board:3 of +msgid "Human if someone is on board, None otherwise" +msgstr "" + +#: ../../source/adf_core_python.core.agent.info.rst:16 +msgid "adf\\_core\\_python.core.agent.info.scenario\\_info module" +msgstr "" + +#: adf_core_python.core.agent.info.scenario_info.Mode:1 of +msgid "Bases: :py:class:`~enum.IntEnum`" +msgstr "" + +#: adf_core_python.core.agent.info.scenario_info.ScenarioInfo.get_config:1 of +msgid "Get the configuration" +msgstr "" + +#: adf_core_python.core.agent.info.scenario_info.ScenarioInfo.get_config:3 +#: adf_core_python.core.agent.info.scenario_info.ScenarioInfo.set_config:3 of +msgid "Configuration" +msgstr "" + +#: adf_core_python.core.agent.info.scenario_info.ScenarioInfo.get_mode:1 of +msgid "Get the mode of the scenario" +msgstr "" + +#: adf_core_python.core.agent.info.scenario_info.ScenarioInfo.get_mode:3 of +msgid "Mode of the scenario" +msgstr "" + +#: adf_core_python.core.agent.info.scenario_info.ScenarioInfo.get_value:1 of +msgid "Get the value of the configuration" +msgstr "" + +#: adf_core_python.core.agent.info.scenario_info.ScenarioInfo.get_value:3 of +msgid "Key of the configuration" +msgstr "" + +#: adf_core_python.core.agent.info.scenario_info.ScenarioInfo.get_value:5 of +msgid "Default value of the configuration" +msgstr "" + +#: adf_core_python.core.agent.info.scenario_info.ScenarioInfo.get_value:8 of +msgid "Value of the configuration" +msgstr "" + +#: adf_core_python.core.agent.info.scenario_info.ScenarioInfo.set_config:1 of +msgid "Set the configuration" +msgstr "" + +#: ../../source/adf_core_python.core.agent.info.rst:24 +msgid "adf\\_core\\_python.core.agent.info.world\\_info module" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.add_entity:1 of +msgid "Add the entity" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.add_entity:3 +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_entity:6 of +msgid "Entity" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_blockades:1 of +msgid "Get the blockades in the area" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_blockades:3 of +msgid "Blockade" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_distance:1 of +msgid "Get the distance between two entities" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_distance:3 of +msgid "Entity ID 1" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_distance:5 of +msgid "Entity ID 2" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_distance:8 of +msgid "Distance" +msgstr "" + +#: ../../source/adf_core_python.core.agent.info.rst +msgid "Raises" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_distance:11 of +msgid "If one or both entities are invalid or the location is invalid" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_entities_of_types:1 +#: of +msgid "Get the entities of the specified types" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_entities_of_types:3 +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_entity_ids_of_types:3 +#: of +msgid "List of entity types" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_entities_of_types:6 +#: of +msgid "Entities" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_entity:1 of +msgid "Get the entity" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_entity:3 +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_entity_position:3 +#: of +msgid "Entity ID" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_entity_ids_of_types:1 +#: of +msgid "Get the entity IDs of the specified types" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_entity_ids_of_types:6 +#: of +msgid "Entity IDs" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_entity_position:1 +#: of +msgid "Get the entity position" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_entity_position:6 +#: of +msgid "Entity position" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_entity_position:9 +#: of +msgid "If the entity is invalid" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_world_model:1 of +msgid "Get the world model" +msgstr "" + +#: adf_core_python.core.agent.info.world_info.WorldInfo.get_world_model:3 of +msgid "World model" +msgstr "" + +#: ../../source/adf_core_python.core.agent.info.rst:32 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.module.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.module.po new file mode 100644 index 0000000..b98e2fc --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.module.po @@ -0,0 +1,42 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.module.rst:2 +msgid "adf\\_core\\_python.core.agent.module package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.module.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.module.rst:8 +msgid "adf\\_core\\_python.core.agent.module.module\\_manager module" +msgstr "" + +#: adf_core_python.core.agent.module.module_manager.ModuleManager:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: ../../source/adf_core_python.core.agent.module.rst:16 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.platoon.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.platoon.po new file mode 100644 index 0000000..fee57f7 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.platoon.po @@ -0,0 +1,60 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.platoon.rst:2 +msgid "adf\\_core\\_python.core.agent.platoon package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.platoon.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.platoon.rst:8 +msgid "adf\\_core\\_python.core.agent.platoon.platoon module" +msgstr "" + +#: adf_core_python.core.agent.platoon.platoon.Platoon:1 of +msgid "Bases: :py:class:`~adf_core_python.core.agent.agent.Agent`" +msgstr "" + +#: ../../source/adf_core_python.core.agent.platoon.rst:16 +msgid "adf\\_core\\_python.core.agent.platoon.platoon\\_ambulance module" +msgstr "" + +#: adf_core_python.core.agent.platoon.platoon_ambulance.PlatoonAmbulance:1 +#: adf_core_python.core.agent.platoon.platoon_fire.PlatoonFire:1 +#: adf_core_python.core.agent.platoon.platoon_police.PlatoonPolice:1 of +msgid "Bases: :py:class:`~adf_core_python.core.agent.platoon.platoon.Platoon`" +msgstr "" + +#: ../../source/adf_core_python.core.agent.platoon.rst:24 +msgid "adf\\_core\\_python.core.agent.platoon.platoon\\_fire module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.platoon.rst:32 +msgid "adf\\_core\\_python.core.agent.platoon.platoon\\_police module" +msgstr "" + +#: ../../source/adf_core_python.core.agent.platoon.rst:40 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.po new file mode 100644 index 0000000..d9d8a76 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.po @@ -0,0 +1,46 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.rst:2 +msgid "adf\\_core\\_python.core.agent package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.rst:5 +msgid "Subpackages" +msgstr "" + +#: ../../source/adf_core_python.core.agent.rst:20 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.rst:23 +msgid "adf\\_core\\_python.core.agent.agent module" +msgstr "" + +#: adf_core_python.core.agent.agent.Agent:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: ../../source/adf_core_python.core.agent.rst:31 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.precompute.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.precompute.po new file mode 100644 index 0000000..67620c4 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.precompute.po @@ -0,0 +1,93 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.agent.precompute.rst:2 +msgid "adf\\_core\\_python.core.agent.precompute package" +msgstr "" + +#: ../../source/adf_core_python.core.agent.precompute.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.agent.precompute.rst:8 +msgid "adf\\_core\\_python.core.agent.precompute.precompute\\_data module" +msgstr "" + +#: adf_core_python.core.agent.precompute.precompute_data.PrecomputeData:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: adf_core_python.core.agent.precompute.precompute_data.PrecomputeData.is_available:1 +#: of +msgid "Check if the precompute data is available." +msgstr "" + +#: ../../source/adf_core_python.core.agent.precompute.rst +msgid "Returns" +msgstr "" + +#: adf_core_python.core.agent.precompute.precompute_data.PrecomputeData.is_available:3 +#: of +msgid "True if the precompute data is available, False otherwise." +msgstr "" + +#: ../../source/adf_core_python.core.agent.precompute.rst +msgid "Return type" +msgstr "" + +#: adf_core_python.core.agent.precompute.precompute_data.PrecomputeData.read_json_data:1 +#: of +msgid "Read the precompute data from the file." +msgstr "" + +#: adf_core_python.core.agent.precompute.precompute_data.PrecomputeData.read_json_data:3 +#: of +msgid "The precompute data." +msgstr "" + +#: ../../source/adf_core_python.core.agent.precompute.rst +msgid "Raises" +msgstr "" + +#: adf_core_python.core.agent.precompute.precompute_data.PrecomputeData.remove_precompute_data:1 +#: of +msgid "Remove the precompute data file." +msgstr "" + +#: adf_core_python.core.agent.precompute.precompute_data.PrecomputeData.write_json_data:1 +#: of +msgid "Write the precompute data to the file." +msgstr "" + +#: ../../source/adf_core_python.core.agent.precompute.rst +msgid "Parameters" +msgstr "" + +#: adf_core_python.core.agent.precompute.precompute_data.PrecomputeData.write_json_data:3 +#: of +msgid "The data to write." +msgstr "" + +#: ../../source/adf_core_python.core.agent.precompute.rst:16 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.action.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.action.po new file mode 100644 index 0000000..6d326d0 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.action.po @@ -0,0 +1,42 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.component.action.rst:2 +msgid "adf\\_core\\_python.core.component.action package" +msgstr "" + +#: ../../source/adf_core_python.core.component.action.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.component.action.rst:8 +msgid "adf\\_core\\_python.core.component.action.extend\\_action module" +msgstr "" + +#: adf_core_python.core.component.action.extend_action.ExtendAction:1 of +msgid "Bases: :py:class:`~abc.ABC`" +msgstr "" + +#: ../../source/adf_core_python.core.component.action.rst:16 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.communication.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.communication.po new file mode 100644 index 0000000..1e125a3 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.communication.po @@ -0,0 +1,103 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.component.communication.rst:2 +msgid "adf\\_core\\_python.core.component.communication package" +msgstr "" + +#: ../../source/adf_core_python.core.component.communication.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.component.communication.rst:8 +msgid "" +"adf\\_core\\_python.core.component.communication.channel\\_subscriber " +"module" +msgstr "" + +#: adf_core_python.core.component.communication.channel_subscriber.ChannelSubscriber:1 +#: adf_core_python.core.component.communication.communication_message.CommunicationMessage:1 +#: adf_core_python.core.component.communication.communication_module.CommunicationModule:1 +#: adf_core_python.core.component.communication.message_coordinator.MessageCoordinator:1 +#: of +msgid "Bases: :py:class:`~abc.ABC`" +msgstr "" + +#: adf_core_python.core.component.communication.channel_subscriber.ChannelSubscriber.subscribe:1 +#: of +msgid "Subscribe to the channel." +msgstr "" + +#: ../../source/adf_core_python.core.component.communication.rst +msgid "Parameters" +msgstr "" + +#: adf_core_python.core.component.communication.channel_subscriber.ChannelSubscriber.subscribe:3 +#: of +msgid "The agent info." +msgstr "" + +#: adf_core_python.core.component.communication.channel_subscriber.ChannelSubscriber.subscribe:5 +#: of +msgid "The world info." +msgstr "" + +#: adf_core_python.core.component.communication.channel_subscriber.ChannelSubscriber.subscribe:7 +#: of +msgid "The scenario info." +msgstr "" + +#: ../../source/adf_core_python.core.component.communication.rst +msgid "Returns" +msgstr "" + +#: adf_core_python.core.component.communication.channel_subscriber.ChannelSubscriber.subscribe:10 +#: of +msgid "The list of subscribed channels." +msgstr "" + +#: ../../source/adf_core_python.core.component.communication.rst +msgid "Return type" +msgstr "" + +#: ../../source/adf_core_python.core.component.communication.rst:16 +msgid "" +"adf\\_core\\_python.core.component.communication.communication\\_message " +"module" +msgstr "" + +#: ../../source/adf_core_python.core.component.communication.rst:24 +msgid "" +"adf\\_core\\_python.core.component.communication.communication\\_module " +"module" +msgstr "" + +#: ../../source/adf_core_python.core.component.communication.rst:32 +msgid "" +"adf\\_core\\_python.core.component.communication.message\\_coordinator " +"module" +msgstr "" + +#: ../../source/adf_core_python.core.component.communication.rst:40 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.module.algorithm.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.module.algorithm.po new file mode 100644 index 0000000..33fd90e --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.module.algorithm.po @@ -0,0 +1,50 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.component.module.algorithm.rst:2 +msgid "adf\\_core\\_python.core.component.module.algorithm package" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.algorithm.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.algorithm.rst:8 +msgid "adf\\_core\\_python.core.component.module.algorithm.clustering module" +msgstr "" + +#: adf_core_python.core.component.module.algorithm.clustering.Clustering:1 +#: adf_core_python.core.component.module.algorithm.path_planning.PathPlanning:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.module.abstract_module.AbstractModule`" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.algorithm.rst:16 +msgid "adf\\_core\\_python.core.component.module.algorithm.path\\_planning module" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.algorithm.rst:24 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.module.complex.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.module.complex.po new file mode 100644 index 0000000..65e3113 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.module.complex.po @@ -0,0 +1,122 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.component.module.complex.rst:2 +msgid "adf\\_core\\_python.core.component.module.complex package" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.complex.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.complex.rst:8 +msgid "" +"adf\\_core\\_python.core.component.module.complex.ambulance\\_target\\_allocator" +" module" +msgstr "" + +#: adf_core_python.core.component.module.complex.ambulance_target_allocator.AmbulanceTargetAllocator:1 +#: adf_core_python.core.component.module.complex.fire_target_allocator.FireTargetAllocator:1 +#: adf_core_python.core.component.module.complex.police_target_allocator.PoliceTargetAllocator:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.module.complex.target_allocator.TargetAllocator`" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.complex.rst:16 +msgid "" +"adf\\_core\\_python.core.component.module.complex.fire\\_target\\_allocator" +" module" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.complex.rst:24 +msgid "adf\\_core\\_python.core.component.module.complex.human\\_detector module" +msgstr "" + +#: adf_core_python.core.component.module.complex.human_detector.HumanDetector:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.module.complex.target_detector.TargetDetector`\\" +" [:py:class:`~rcrs_core.entities.human.Human`]" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.complex.rst:32 +msgid "" +"adf\\_core\\_python.core.component.module.complex.police\\_target\\_allocator" +" module" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.complex.rst:40 +msgid "adf\\_core\\_python.core.component.module.complex.road\\_detector module" +msgstr "" + +#: adf_core_python.core.component.module.complex.road_detector.RoadDetector:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.module.complex.target_detector.TargetDetector`\\" +" [:py:class:`~rcrs_core.entities.road.Road`]" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.complex.rst:48 +msgid "adf\\_core\\_python.core.component.module.complex.search module" +msgstr "" + +#: adf_core_python.core.component.module.complex.search.Search:1 of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.module.complex.target_detector.TargetDetector`\\" +" [:py:class:`~rcrs_core.entities.area.Area`]" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.complex.rst:56 +msgid "" +"adf\\_core\\_python.core.component.module.complex.target\\_allocator " +"module" +msgstr "" + +#: adf_core_python.core.component.module.complex.target_allocator.TargetAllocator:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.module.abstract_module.AbstractModule`" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.complex.rst:64 +msgid "adf\\_core\\_python.core.component.module.complex.target\\_detector module" +msgstr "" + +#: adf_core_python.core.component.module.complex.target_detector.TargetDetector:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.module.abstract_module.AbstractModule`," +" :py:class:`~typing.Generic`\\ " +"[:py:obj:`~adf_core_python.core.component.module.complex.target_detector.T`]" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.complex.rst:72 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.module.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.module.po new file mode 100644 index 0000000..4a2d3ba --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.module.po @@ -0,0 +1,46 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.component.module.rst:2 +msgid "adf\\_core\\_python.core.component.module package" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.rst:5 +msgid "Subpackages" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.rst:14 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.rst:17 +msgid "adf\\_core\\_python.core.component.module.abstract\\_module module" +msgstr "" + +#: adf_core_python.core.component.module.abstract_module.AbstractModule:1 of +msgid "Bases: :py:class:`~abc.ABC`" +msgstr "" + +#: ../../source/adf_core_python.core.component.module.rst:25 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.po new file mode 100644 index 0000000..448531c --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.po @@ -0,0 +1,46 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.component.rst:2 +msgid "adf\\_core\\_python.core.component package" +msgstr "" + +#: ../../source/adf_core_python.core.component.rst:5 +msgid "Subpackages" +msgstr "" + +#: ../../source/adf_core_python.core.component.rst:16 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.component.rst:19 +msgid "adf\\_core\\_python.core.component.abstract\\_loader module" +msgstr "" + +#: adf_core_python.core.component.abstract_loader.AbstractLoader:1 of +msgid "Bases: :py:class:`~abc.ABC`" +msgstr "" + +#: ../../source/adf_core_python.core.component.rst:27 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.tactics.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.tactics.po new file mode 100644 index 0000000..c32d319 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.tactics.po @@ -0,0 +1,95 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.component.tactics.rst:2 +msgid "adf\\_core\\_python.core.component.tactics package" +msgstr "" + +#: ../../source/adf_core_python.core.component.tactics.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.component.tactics.rst:8 +msgid "adf\\_core\\_python.core.component.tactics.tactics\\_agent module" +msgstr "" + +#: adf_core_python.core.component.tactics.tactics_agent.TacticsAgent:1 +#: adf_core_python.core.component.tactics.tactics_center.TacticsCenter:1 of +msgid "Bases: :py:class:`~abc.ABC`" +msgstr "" + +#: ../../source/adf_core_python.core.component.tactics.rst:16 +msgid "" +"adf\\_core\\_python.core.component.tactics.tactics\\_ambulance\\_center " +"module" +msgstr "" + +#: adf_core_python.core.component.tactics.tactics_ambulance_center.TacticsAmbulanceCenter:1 +#: adf_core_python.core.component.tactics.tactics_fire_station.TacticsFireStation:1 +#: adf_core_python.core.component.tactics.tactics_police_office.TacticsPoliceOffice:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.tactics.tactics_center.TacticsCenter`" +msgstr "" + +#: ../../source/adf_core_python.core.component.tactics.rst:24 +msgid "" +"adf\\_core\\_python.core.component.tactics.tactics\\_ambulance\\_team " +"module" +msgstr "" + +#: adf_core_python.core.component.tactics.tactics_ambulance_team.TacticsAmbulanceTeam:1 +#: adf_core_python.core.component.tactics.tactics_fire_brigade.TacticsFireBrigade:1 +#: adf_core_python.core.component.tactics.tactics_police_force.TacticsPoliceForce:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.tactics.tactics_agent.TacticsAgent`" +msgstr "" + +#: ../../source/adf_core_python.core.component.tactics.rst:32 +msgid "adf\\_core\\_python.core.component.tactics.tactics\\_center module" +msgstr "" + +#: ../../source/adf_core_python.core.component.tactics.rst:40 +msgid "adf\\_core\\_python.core.component.tactics.tactics\\_fire\\_brigade module" +msgstr "" + +#: ../../source/adf_core_python.core.component.tactics.rst:48 +msgid "adf\\_core\\_python.core.component.tactics.tactics\\_fire\\_station module" +msgstr "" + +#: ../../source/adf_core_python.core.component.tactics.rst:56 +msgid "adf\\_core\\_python.core.component.tactics.tactics\\_police\\_force module" +msgstr "" + +#: ../../source/adf_core_python.core.component.tactics.rst:64 +msgid "" +"adf\\_core\\_python.core.component.tactics.tactics\\_police\\_office " +"module" +msgstr "" + +#: ../../source/adf_core_python.core.component.tactics.rst:72 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.config.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.config.po new file mode 100644 index 0000000..47ee2de --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.config.po @@ -0,0 +1,42 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.config.rst:2 +msgid "adf\\_core\\_python.core.config package" +msgstr "" + +#: ../../source/adf_core_python.core.config.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.config.rst:8 +msgid "adf\\_core\\_python.core.config.config module" +msgstr "" + +#: adf_core_python.core.config.config.Config:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: ../../source/adf_core_python.core.config.rst:16 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.launcher.connect.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.launcher.connect.po new file mode 100644 index 0000000..dc9c05d --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.launcher.connect.po @@ -0,0 +1,134 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.launcher.connect.rst:2 +msgid "adf\\_core\\_python.core.launcher.connect package" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.connect.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.connect.rst:8 +msgid "adf\\_core\\_python.core.launcher.connect.component\\_launcher module" +msgstr "" + +#: adf_core_python.core.launcher.connect.component_launcher.ComponentLauncher:1 +#: adf_core_python.core.launcher.connect.connection.Connection:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.connect.rst:16 +msgid "adf\\_core\\_python.core.launcher.connect.connection module" +msgstr "" + +#: adf_core_python.core.launcher.connect.connection.Connection.connect:1 of +msgid "Connect to the kernel" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.connect.rst +msgid "Raises" +msgstr "" + +#: adf_core_python.core.launcher.connect.connection.Connection.connect:3 of +msgid "If the connection times out" +msgstr "" + +#: adf_core_python.core.launcher.connect.connection.Connection.connect:4 of +msgid "If there is an error connecting to the socket" +msgstr "" + +#: adf_core_python.core.launcher.connect.connection.Connection.parse_message_from_kernel:1 +#: of +msgid "Parse messages from the kernel" +msgstr "" + +#: adf_core_python.core.launcher.connect.connection.Connection.parse_message_from_kernel:3 +#: of +msgid "If there is an error reading from the socket" +msgstr "" + +#: adf_core_python.core.launcher.connect.connection.Connection.parse_message_from_kernel:4 +#: of +msgid "If there is an error in the agent calculation" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.connect.rst:24 +msgid "adf\\_core\\_python.core.launcher.connect.connector module" +msgstr "" + +#: adf_core_python.core.launcher.connect.connector.Connector:1 of +msgid "Bases: :py:class:`~abc.ABC`" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.connect.rst:32 +msgid "" +"adf\\_core\\_python.core.launcher.connect.connector\\_ambulance\\_center " +"module" +msgstr "" + +#: adf_core_python.core.launcher.connect.connector_ambulance_center.ConnectorAmbulanceCenter:1 +#: adf_core_python.core.launcher.connect.connector_ambulance_team.ConnectorAmbulanceTeam:1 +#: adf_core_python.core.launcher.connect.connector_fire_brigade.ConnectorFireBrigade:1 +#: adf_core_python.core.launcher.connect.connector_fire_station.ConnectorFireStation:1 +#: adf_core_python.core.launcher.connect.connector_police_force.ConnectorPoliceForce:1 +#: adf_core_python.core.launcher.connect.connector_police_office.ConnectorPoliceOffice:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.launcher.connect.connector.Connector`" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.connect.rst:40 +msgid "" +"adf\\_core\\_python.core.launcher.connect.connector\\_ambulance\\_team " +"module" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.connect.rst:48 +msgid "" +"adf\\_core\\_python.core.launcher.connect.connector\\_fire\\_brigade " +"module" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.connect.rst:56 +msgid "" +"adf\\_core\\_python.core.launcher.connect.connector\\_fire\\_station " +"module" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.connect.rst:64 +msgid "" +"adf\\_core\\_python.core.launcher.connect.connector\\_police\\_force " +"module" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.connect.rst:72 +msgid "" +"adf\\_core\\_python.core.launcher.connect.connector\\_police\\_office " +"module" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.connect.rst:80 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.launcher.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.launcher.po new file mode 100644 index 0000000..ae5e451 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.launcher.po @@ -0,0 +1,51 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.launcher.rst:2 +msgid "adf\\_core\\_python.core.launcher package" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.rst:5 +msgid "Subpackages" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.rst:13 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.rst:16 +msgid "adf\\_core\\_python.core.launcher.agent\\_launcher module" +msgstr "" + +#: adf_core_python.core.launcher.agent_launcher.AgentLauncher:1 +#: adf_core_python.core.launcher.config_key.ConfigKey:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.rst:24 +msgid "adf\\_core\\_python.core.launcher.config\\_key module" +msgstr "" + +#: ../../source/adf_core_python.core.launcher.rst:32 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.logger.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.logger.po new file mode 100644 index 0000000..0890f2a --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.logger.po @@ -0,0 +1,79 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.logger.rst:2 +msgid "adf\\_core\\_python.core.logger package" +msgstr "" + +#: ../../source/adf_core_python.core.logger.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.core.logger.rst:8 +msgid "adf\\_core\\_python.core.logger.logger module" +msgstr "" + +#: adf_core_python.core.logger.logger.get_agent_logger:1 of +msgid "" +"Get a logger with the given name and agent information. For agent " +"logging, use this function to get a logger." +msgstr "" + +#: ../../source/adf_core_python.core.logger.rst +msgid "Parameters" +msgstr "" + +#: adf_core_python.core.logger.logger.get_agent_logger:4 +#: adf_core_python.core.logger.logger.get_logger:4 of +msgid "The name of the logger." +msgstr "" + +#: adf_core_python.core.logger.logger.get_agent_logger:6 of +msgid "The agent information." +msgstr "" + +#: ../../source/adf_core_python.core.logger.rst +msgid "Returns" +msgstr "" + +#: adf_core_python.core.logger.logger.get_agent_logger:9 of +msgid "The logger with the given name and agent information." +msgstr "" + +#: ../../source/adf_core_python.core.logger.rst +msgid "Return type" +msgstr "" + +#: adf_core_python.core.logger.logger.get_logger:1 of +msgid "" +"Get a logger with the given name. For kernel logging, use this function " +"to get a logger." +msgstr "" + +#: adf_core_python.core.logger.logger.get_logger:7 of +msgid "The logger with the given name." +msgstr "" + +#: ../../source/adf_core_python.core.logger.rst:16 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.po new file mode 100644 index 0000000..5da18eb --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.core.po @@ -0,0 +1,34 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.core.rst:2 +msgid "adf\\_core\\_python.core package" +msgstr "" + +#: ../../source/adf_core_python.core.rst:5 +msgid "Subpackages" +msgstr "" + +#: ../../source/adf_core_python.core.rst:17 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.action.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.action.po new file mode 100644 index 0000000..f850489 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.action.po @@ -0,0 +1,68 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.implement.action.rst:2 +msgid "adf\\_core\\_python.implement.action package" +msgstr "" + +#: ../../source/adf_core_python.implement.action.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.implement.action.rst:8 +msgid "" +"adf\\_core\\_python.implement.action.default\\_extend\\_action\\_clear " +"module" +msgstr "" + +#: adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear:1 +#: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove:1 +#: adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue:1 +#: adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.action.extend_action.ExtendAction`" +msgstr "" + +#: ../../source/adf_core_python.implement.action.rst:16 +msgid "" +"adf\\_core\\_python.implement.action.default\\_extend\\_action\\_move " +"module" +msgstr "" + +#: ../../source/adf_core_python.implement.action.rst:24 +msgid "" +"adf\\_core\\_python.implement.action.default\\_extend\\_action\\_rescue " +"module" +msgstr "" + +#: ../../source/adf_core_python.implement.action.rst:32 +msgid "" +"adf\\_core\\_python.implement.action.default\\_extend\\_action\\_transport" +" module" +msgstr "" + +#: ../../source/adf_core_python.implement.action.rst:40 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.algorithm.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.algorithm.po new file mode 100644 index 0000000..5bab530 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.algorithm.po @@ -0,0 +1,60 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.implement.module.algorithm.rst:2 +msgid "adf\\_core\\_python.implement.module.algorithm package" +msgstr "" + +#: ../../source/adf_core_python.implement.module.algorithm.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.implement.module.algorithm.rst:8 +msgid "" +"adf\\_core\\_python.implement.module.algorithm.a\\_star\\_path\\_planning" +" module" +msgstr "" + +#: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.module.algorithm.path_planning.PathPlanning`" +msgstr "" + +#: ../../source/adf_core_python.implement.module.algorithm.rst:16 +msgid "" +"adf\\_core\\_python.implement.module.algorithm.k\\_means\\_clustering " +"module" +msgstr "" + +#: adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.module.algorithm.clustering.Clustering`" +msgstr "" + +#: ../../source/adf_core_python.implement.module.algorithm.rst:24 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.communication.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.communication.po new file mode 100644 index 0000000..a598623 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.communication.po @@ -0,0 +1,97 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.implement.module.communication.rst:2 +msgid "adf\\_core\\_python.implement.module.communication package" +msgstr "" + +#: ../../source/adf_core_python.implement.module.communication.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.implement.module.communication.rst:8 +msgid "" +"adf\\_core\\_python.implement.module.communication.default\\_channel\\_subscriber" +" module" +msgstr "" + +#: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.communication.channel_subscriber.ChannelSubscriber`" +msgstr "" + +#: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber.subscribe:1 +#: of +msgid "Subscribe to the channel." +msgstr "" + +#: ../../source/adf_core_python.implement.module.communication.rst +msgid "Parameters" +msgstr "" + +#: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber.subscribe:3 +#: of +msgid "The agent info." +msgstr "" + +#: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber.subscribe:5 +#: of +msgid "The world info." +msgstr "" + +#: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber.subscribe:7 +#: of +msgid "The scenario info." +msgstr "" + +#: ../../source/adf_core_python.implement.module.communication.rst +msgid "Returns" +msgstr "" + +#: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber.subscribe:10 +#: of +msgid "The list of subscribed channels." +msgstr "" + +#: ../../source/adf_core_python.implement.module.communication.rst +msgid "Return type" +msgstr "" + +#: ../../source/adf_core_python.implement.module.communication.rst:16 +msgid "" +"adf\\_core\\_python.implement.module.communication.default\\_message\\_coordinator" +" module" +msgstr "" + +#: adf_core_python.implement.module.communication.default_message_coordinator.DefaultMessageCoordinator:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.communication.message_coordinator.MessageCoordinator`" +msgstr "" + +#: ../../source/adf_core_python.implement.module.communication.rst:24 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.complex.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.complex.po new file mode 100644 index 0000000..1c260b9 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.complex.po @@ -0,0 +1,116 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.implement.module.complex.rst:2 +msgid "adf\\_core\\_python.implement.module.complex package" +msgstr "" + +#: ../../source/adf_core_python.implement.module.complex.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.implement.module.complex.rst:8 +msgid "" +"adf\\_core\\_python.implement.module.complex.default\\_ambulance\\_target\\_allocator" +" module" +msgstr "" + +#: adf_core_python.implement.module.complex.default_ambulance_target_allocator.DefaultAmbulanceTargetAllocator:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.module.complex.ambulance_target_allocator.AmbulanceTargetAllocator`" +msgstr "" + +#: adf_core_python.implement.module.complex.default_ambulance_target_allocator.DefaultAmbulanceTargetAllocator.AmbulanceTeamInfo:1 +#: adf_core_python.implement.module.complex.default_fire_target_allocator.DefaultFireTargetAllocator.FireBrigadeInfo:1 +#: adf_core_python.implement.module.complex.default_police_target_allocator.DefaultPoliceTargetAllocator.PoliceForceInfo:1 +#: of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: ../../source/adf_core_python.implement.module.complex.rst:16 +msgid "" +"adf\\_core\\_python.implement.module.complex.default\\_fire\\_target\\_allocator" +" module" +msgstr "" + +#: adf_core_python.implement.module.complex.default_fire_target_allocator.DefaultFireTargetAllocator:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.module.complex.fire_target_allocator.FireTargetAllocator`" +msgstr "" + +#: ../../source/adf_core_python.implement.module.complex.rst:24 +msgid "" +"adf\\_core\\_python.implement.module.complex.default\\_human\\_detector " +"module" +msgstr "" + +#: adf_core_python.implement.module.complex.default_human_detector.DefaultHumanDetector:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.module.complex.human_detector.HumanDetector`" +msgstr "" + +#: ../../source/adf_core_python.implement.module.complex.rst:32 +msgid "" +"adf\\_core\\_python.implement.module.complex.default\\_police\\_target\\_allocator" +" module" +msgstr "" + +#: adf_core_python.implement.module.complex.default_police_target_allocator.DefaultPoliceTargetAllocator:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.module.complex.police_target_allocator.PoliceTargetAllocator`" +msgstr "" + +#: ../../source/adf_core_python.implement.module.complex.rst:40 +msgid "" +"adf\\_core\\_python.implement.module.complex.default\\_road\\_detector " +"module" +msgstr "" + +#: adf_core_python.implement.module.complex.default_road_detector.DefaultRoadDetector:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.module.complex.road_detector.RoadDetector`" +msgstr "" + +#: ../../source/adf_core_python.implement.module.complex.rst:48 +msgid "adf\\_core\\_python.implement.module.complex.default\\_search module" +msgstr "" + +#: adf_core_python.implement.module.complex.default_search.DefaultSearch:1 of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.module.complex.search.Search`" +msgstr "" + +#: ../../source/adf_core_python.implement.module.complex.rst:56 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.po new file mode 100644 index 0000000..8f4619a --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.po @@ -0,0 +1,34 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.implement.module.rst:2 +msgid "adf\\_core\\_python.implement.module package" +msgstr "" + +#: ../../source/adf_core_python.implement.module.rst:5 +msgid "Subpackages" +msgstr "" + +#: ../../source/adf_core_python.implement.module.rst:15 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.po new file mode 100644 index 0000000..2d6cdd9 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.po @@ -0,0 +1,48 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.implement.rst:2 +msgid "adf\\_core\\_python.implement package" +msgstr "" + +#: ../../source/adf_core_python.implement.rst:5 +msgid "Subpackages" +msgstr "" + +#: ../../source/adf_core_python.implement.rst:15 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.implement.rst:18 +msgid "adf\\_core\\_python.implement.default\\_loader module" +msgstr "" + +#: adf_core_python.implement.default_loader.DefaultLoader:1 of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.abstract_loader.AbstractLoader`" +msgstr "" + +#: ../../source/adf_core_python.implement.rst:26 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.tactics.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.tactics.po new file mode 100644 index 0000000..27c82fc --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.tactics.po @@ -0,0 +1,112 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.implement.tactics.rst:2 +msgid "adf\\_core\\_python.implement.tactics package" +msgstr "" + +#: ../../source/adf_core_python.implement.tactics.rst:5 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.implement.tactics.rst:8 +msgid "" +"adf\\_core\\_python.implement.tactics.default\\_tactics\\_ambulance\\_center" +" module" +msgstr "" + +#: adf_core_python.implement.tactics.default_tactics_ambulance_center.DefaultTacticsAmbulanceCenter:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.tactics.tactics_ambulance_center.TacticsAmbulanceCenter`" +msgstr "" + +#: ../../source/adf_core_python.implement.tactics.rst:16 +msgid "" +"adf\\_core\\_python.implement.tactics.default\\_tactics\\_ambulance\\_team" +" module" +msgstr "" + +#: adf_core_python.implement.tactics.default_tactics_ambulance_team.DefaultTacticsAmbulanceTeam:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.tactics.tactics_ambulance_team.TacticsAmbulanceTeam`" +msgstr "" + +#: ../../source/adf_core_python.implement.tactics.rst:24 +msgid "" +"adf\\_core\\_python.implement.tactics.default\\_tactics\\_fire\\_brigade " +"module" +msgstr "" + +#: adf_core_python.implement.tactics.default_tactics_fire_brigade.DefaultTacticsFireBrigade:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.tactics.tactics_fire_brigade.TacticsFireBrigade`" +msgstr "" + +#: ../../source/adf_core_python.implement.tactics.rst:32 +msgid "" +"adf\\_core\\_python.implement.tactics.default\\_tactics\\_fire\\_station " +"module" +msgstr "" + +#: adf_core_python.implement.tactics.default_tactics_fire_station.DefaultTacticsFireStation:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.tactics.tactics_fire_station.TacticsFireStation`" +msgstr "" + +#: ../../source/adf_core_python.implement.tactics.rst:40 +msgid "" +"adf\\_core\\_python.implement.tactics.default\\_tactics\\_police\\_force " +"module" +msgstr "" + +#: adf_core_python.implement.tactics.default_tactics_police_force.DefaultTacticsPoliceForce:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.tactics.tactics_police_force.TacticsPoliceForce`" +msgstr "" + +#: ../../source/adf_core_python.implement.tactics.rst:48 +msgid "" +"adf\\_core\\_python.implement.tactics.default\\_tactics\\_police\\_office" +" module" +msgstr "" + +#: adf_core_python.implement.tactics.default_tactics_police_office.DefaultTacticsPoliceOffice:1 +#: of +msgid "" +"Bases: " +":py:class:`~adf_core_python.core.component.tactics.tactics_police_office.TacticsPoliceOffice`" +msgstr "" + +#: ../../source/adf_core_python.implement.tactics.rst:56 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/adf_core_python.po b/docs/source/locale/en/LC_MESSAGES/adf_core_python.po new file mode 100644 index 0000000..537693c --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/adf_core_python.po @@ -0,0 +1,46 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/adf_core_python.rst:2 +msgid "adf\\_core\\_python package" +msgstr "" + +#: ../../source/adf_core_python.rst:5 +msgid "Subpackages" +msgstr "" + +#: ../../source/adf_core_python.rst:15 +msgid "Submodules" +msgstr "" + +#: ../../source/adf_core_python.rst:18 +msgid "adf\\_core\\_python.launcher module" +msgstr "" + +#: adf_core_python.launcher.Launcher:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: ../../source/adf_core_python.rst:26 +msgid "Module contents" +msgstr "" + diff --git a/docs/source/locale/en/LC_MESSAGES/hands-on.po b/docs/source/locale/en/LC_MESSAGES/hands-on.po new file mode 100644 index 0000000..47429b6 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/hands-on.po @@ -0,0 +1,284 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/hands-on/clustering.md:1 +msgid "クラスタリングモジュール" +msgstr "Clustering Module" + +#: ../../source/hands-on/clustering.md:3 +msgid "クラスタリングモジュールの目的" +msgstr "Purpose of the Clustering Module" + +#: ../../source/hands-on/clustering.md:5 +msgid "複数のエージェントを動かす場合は、それらのエージェントにどのように協調させるかが重要になります。RRSでは多くのチームが、エージェントに各々の担当地域を持たせ役割分担をおこなう協調を取り入れています(他の手段による協調も取り入れています)。担当地域を割り振るためには、地図上のオブジェクトをいくつかのグループに分ける必要があります。このようなグループ分けをしてそれらを管理する場合には、クラスタリングモジュールと呼ばれるモジュールを用います。" +msgstr "When operating multiple agents, it is crucial to determine how to coordinate them. In RRS, many teams adopt coordination methods that assign each agent a designated area of responsibility (as well as other coordination methods). To assign these areas, objects on the map need to be divided into several groups. A module called the clustering module is used to manage these groupings." + +#: ../../source/hands-on/clustering.md:7 +msgid "本資料では、多くの世界大会参加チームが使用しているアルゴリズムを用いたクラスタリングモジュールの実装をおこないます。" +msgstr "In this document, we will implement a clustering module using algorithms commonly employed by many teams participating in world championships." + +#: ../../source/hands-on/clustering.md:9 +msgid "開発するクラスタリングモジュールの概要" +msgstr "Overview of the Clustering Module to Be Developed" + +#: ../../source/hands-on/clustering.md:11 +msgid "本資料で開発するモジュールは下の画像のように、" +msgstr "The module developed in this document, as shown in the image below," + +#: ../../source/hands-on/clustering.md:13 +msgid "k-means++アルゴリズムによって地図上のオブジェクトをエージェント数分の区画に分けます。" +msgstr "Divides the objects on the map into sections equal to the number of agents using the k-means++ algorithm." + +#: ../../source/hands-on/clustering.md:14 +msgid "Hungarianアルゴリズムによってそれらの区画とエージェントを (間の距離の総和が最も小さくなるように)1対1で結びつけます。" +msgstr "Assigns the sections to the agents in a one-to-one manner using the Hungarian algorithm, minimizing the total distance between them." + +#: ../../source/hands-on/clustering.md:16 +msgid "![クラスタリングの画像](./../images/clustering_image.jpg)" +msgstr "![Clustering Image](./../images/clustering_image.jpg)" + +#: ../../source/hands-on/clustering.md:16 +#: ../../source/hands-on/clustering.md:362 +msgid "クラスタリングの画像" +msgstr "Clustering Image" + +#: ../../source/hands-on/clustering.md:18 +msgid "クラスタリングモジュールの実装" +msgstr "Implementation of the Clustering Module" + +#: ../../source/hands-on/clustering.md:21 ../../source/hands-on/search.md:10 +msgid "以降の作業では、カレントディレクトリがプロジェクトのルートディレクトリであることを前提としています。" +msgstr "The following tasks assume that the current directory is the root directory of the project." + +#: ../../source/hands-on/clustering.md:24 +msgid "まず、クラスタリングモジュールを記述するためのファイルを作成します。" +msgstr "First, create a file to write the clustering module." + +#: ../../source/hands-on/clustering.md:31 +msgid "次に、クラスタリングモジュールの実装を行います。 以下のコードを `k_means_pp_clustering.py` に記述してください。" +msgstr "Next, implement the clustering module. Write the following code into `k_means_pp_clustering.py`." + +#: ../../source/hands-on/clustering.md:295 +msgid "k-means++の実装は、scikit-learnの`KMeans`クラスを使用しています。`KMeans`クラスは、`n_clusters`で指定したクラスター数によって地図上のオブジェクトをクラスタリングします。クラスタリング結果は、`labels_`属性に格納されます。また、`cluster_centers_`属性には各クラスターの中心座標が格納されます。" +msgstr "The implementation of k-means++ uses the `KMeans` class from scikit-learn. The `KMeans` class clusters objects on the map based on the number of clusters specified by `n_clusters`. The clustering results are stored in the `labels_` attribute, and the coordinates of each cluster center are stored in the `cluster_centers_` attribute." + +#: ../../source/hands-on/clustering.md:297 +msgid "hungarianアルゴリズムの実装は、scipyの`linear_sum_assignment`関数を使用しています。`linear_sum_assignment`関数は、コスト行列を引数として受け取り、最適な割り当てを行います。" +msgstr "The implementation of the Hungarian algorithm uses the `linear_sum_assignment` function from scipy. The `linear_sum_assignment` function takes a cost matrix as an argument and performs optimal assignment." + +#: ../../source/hands-on/clustering.md:299 +msgid "次に、作成したモジュールを登録します。`config/module.yaml` を以下のように編集してください。" +msgstr "Next, register the created module. Edit `config/module.yaml` as follows." + +#: ../../source/hands-on/clustering.md:310 ../../source/hands-on/search.md:160 +msgid "ターミナルを2つ起動します。" +msgstr "Open two terminals." + +#: ../../source/hands-on/clustering.md:312 ../../source/hands-on/search.md:162 +msgid "片方のターミナルを開き、シミュレーションサーバーを以下のコマンドで起動します:" +msgstr "Open one terminal and start the simulation server with the following command:" + +#: ../../source/hands-on/clustering.md:320 ../../source/hands-on/search.md:170 +msgid "その後、別のターミナルを開き、エージェントを起動します:" +msgstr "Then open another terminal and start the agent:" + +#: ../../source/hands-on/clustering.md:328 +msgid "エージェントが起動すると、標準出力にクラスタリング結果が表示されます。" +msgstr "When the agent starts, the clustering results will be displayed in the standard output." + +#: ../../source/hands-on/clustering.md:335 +msgid "このままだと、クラスタリング結果がわかりにくいので、クラスタリング結果を地図上に表示してみましょう。" +msgstr "As it is, the clustering results are not very clear, so let's display the clustering results on the map." + +#: ../../source/hands-on/clustering.md:337 +msgid "{download}`クラスターの可視化用スクリプト <./../download/cluster_plot.zip>`をダウンロードして解凍し、中の`main.py`の以下の部分に" +msgstr "{download}`Download the cluster visualization script <./../download/cluster_plot.zip>` and extract it. Then, in `main.py`, modify the following part." + +#: ../../source/hands-on/clustering.md:344 +msgid "出力の`Clustered entities: `の後ろの部分の配列をコピーして貼り付けてください。" +msgstr "Copy the array after `Clustered entities:` in the output and paste it." + +#: ../../source/hands-on/clustering.md:346 +msgid "例" +msgstr "Example" + +#: ../../source/hands-on/clustering.md:353 +msgid "貼り付けたら、以下のコマンドを実行してください。" +msgstr "After pasting, execute the following command." + +#: ../../source/hands-on/clustering.md:360 +msgid "以下のような画像が出力されます。" +msgstr "An image like the one below will be output." + +#: ../../source/hands-on/clustering.md:362 +msgid "![クラスタリングの画像](./../images/cluster.png)" +msgstr "![Clustering Image](./../images/cluster.png)" + +#: ../../source/hands-on/search.md:1 +msgid "サーチモジュール" +msgstr "Search Module" + +#: ../../source/hands-on/search.md:3 +msgid "サーチモジュールの概要" +msgstr "Overview of the Search Module" + +#: ../../source/hands-on/search.md:5 +msgid "" +"今回開発するモジュールは、`KMeansPPClustering` モジュールを用いた情報探索対象決定 (`Search`) モジュールです。 " +"クラスタリングモジュールによってエージェント間で担当地域の分割をおこない、 担当地域内からランダムに探索対象として選択します。" +msgstr "" +"The module to be developed this time is the information search target determination (`Search`) module using the `KMeansPPClustering` module. " +"The clustering module divides the areas of responsibility among agents and randomly selects search targets within the assigned areas." + +#: ../../source/hands-on/search.md:7 +msgid "サーチモジュールの実装の準備" +msgstr "Preparation for Implementing the Search Module" + +#: ../../source/hands-on/search.md:13 +msgid "まず、サーチモジュールを記述するためのファイルを作成します。" +msgstr "First, create a file to write the search module." + +#: ../../source/hands-on/search.md:19 +msgid "" +"次に、サーチモジュールの実装を行います。 以下のコードを `k_means_pp_search.py` に記述してください。 " +"これが今回実装するサーチモジュールの雛形になります。" +msgstr "" +"Next, implement the search module. Write the following code into `k_means_pp_search.py`. " +"This will be the template for the search module to be implemented this time." + +#: ../../source/hands-on/search.md:67 +msgid "モジュールの登録" +msgstr "Module Registration" + +#: ../../source/hands-on/search.md:69 +msgid "次に、作成したモジュールを登録します。 以下のように`config/module.yaml`の該当箇所を変更してください" +msgstr "Next, register the created module. Modify the relevant section of `config/module.yaml` as follows." + +#: ../../source/hands-on/search.md:83 +msgid "モジュールの実装" +msgstr "Module Implementation" + +#: ../../source/hands-on/search.md:85 +msgid "まず、`KMeansPPClustering` モジュールを呼び出せるようにします。" +msgstr "First, make it possible to call the `KMeansPPClustering` module." + +#: ../../source/hands-on/search.md:87 +msgid "以下のコードを`config/module.yaml`に追記してください。" +msgstr "Add the following code to `config/module.yaml`." + +#: ../../source/hands-on/search.md:94 +msgid "次に、`KMeansPPSearch` モジュールで `KMeansPPClustering` モジュールを呼び出せるようにします。" +msgstr "Next, make it possible to call the `KMeansPPClustering` module in the `KMeansPPSearch` module." + +#: ../../source/hands-on/search.md:96 ../../source/hands-on/search.md:136 +msgid "以下のコードを `k_means_pp_search.py` に追記してください。" +msgstr "Add the following code to `k_means_pp_search.py`." + +#: ../../source/hands-on/search.md:134 +msgid "そして、`calculate` メソッドでクラスタリングモジュールを呼び出し、探索対象を決定するように変更します。" +msgstr "Then, modify the `calculate` method to call the clustering module and determine the search target." + +#: ../../source/hands-on/search.md:158 +msgid "以上で、`KMeansPPClustering` モジュールを用いた `KMeansPPSearch` モジュールの実装が完了しました。" +msgstr "This completes the implementation of the `KMeansPPSearch` module using the `KMeansPPClustering` module." + +#: ../../source/hands-on/search.md:178 +msgid "モジュールの改善" +msgstr "Module Improvement" + +#: ../../source/hands-on/search.md:180 +msgid "" +"`KMeansPPSearch` モジュールは、クラスタリングモジュールを用いて担当地域内からランダムに探索対象を選択しています。 " +"そのため、以下のような問題があります。" +msgstr "" +"The `KMeansPPSearch` module randomly selects search targets within the assigned areas using the clustering module. " +"As a result, the following issues arise:" + +#: ../../source/hands-on/search.md:183 +msgid "探索対象がステップごとに変わってしまう" +msgstr "The search target changes with each step" + +#: ../../source/hands-on/search.md:184 +msgid "目標にたどり着く前に探索対象が変わってしまうため、なかなか目標にたどり着けない" +msgstr "The search target changes before reaching the goal, making it difficult to reach the goal" + +#: ../../source/hands-on/search.md:185 +msgid "色んなところにランダムに探索対象を選択することで、効率的な探索ができない" +msgstr "Randomly selecting search targets in various places makes efficient searching impossible" + +#: ../../source/hands-on/search.md:186 +msgid "すでに探索したエンティティを再度探索対象として選択してしまうため、効率的な探索ができない" +msgstr "Selecting already searched entities as search targets again makes efficient searching impossible" + +#: ../../source/hands-on/search.md:187 ../../source/hands-on/search.md:329 +msgid "近くに未探索のエンティティがあるのに、遠くのエンティティを探索対象として選択してしまう" +msgstr "Selecting distant entities as search targets even though there are unexplored entities nearby" + +#: ../../source/hands-on/search.md:189 +msgid "などの問題があります。" +msgstr "These are some of the issues." + +#: ../../source/hands-on/search.md:191 +msgid "課題" +msgstr "Challenges" + +#: ../../source/hands-on/search.md:193 +msgid "`KMeansPPSearch` モジュールを改善し、より効率的な探索を行うモジュールを実装して見てください。" +msgstr "Improve the `KMeansPPSearch` module and implement a module that performs more efficient searches." + +#: ../../source/hands-on/search.md:196 +msgid "ここに上げた問題以外にも、改善すべき点が存在すると思うので、それを改善していただいても構いません。" +msgstr "There may be other points for improvement besides the issues listed here, so feel free to address them as well." + +#: ../../source/hands-on/search.md:200 +msgid "プログラム例のプログラムにも一部改善点があるので、余裕があったら修正してみてください。" +msgstr "There are also some points for improvement in the example program, so try to fix them if you have time." + +#: ../../source/hands-on/search.md:203 +msgid "探索対象がステップごとに変わってしまう問題" +msgstr "Issue of the search target changing with each step" + +#: ../../source/hands-on/search.md:205 ../../source/hands-on/search.md:248 +#: ../../source/hands-on/search.md:331 +msgid "方針のヒント" +msgstr "Hints for the Approach" + +#: ../../source/hands-on/search.md:208 +msgid "一度選択した探索対象に到達するまで、探索対象を変更しないようにする" +msgstr "Do not change the search target until the initially selected target is reached" + +#: ../../source/hands-on/search.md:211 ../../source/hands-on/search.md:254 +#: ../../source/hands-on/search.md:337 +msgid "プログラム例" +msgstr "Example Program" + +#: ../../source/hands-on/search.md:246 +msgid "すでに探索したエンティティを再度探索対象として選択してしまう問題" +msgstr "Issue of selecting already searched entities as search targets again" + +#: ../../source/hands-on/search.md:251 +msgid "すでに探索したエンティティを何かしらの方法で記録し、再度探索対象として選択しないようにする" +msgstr "Record already searched entities in some way to avoid selecting them as search targets again" + +#: ../../source/hands-on/search.md:334 +msgid "エンティティ間の距離を計算し、もっとも近いエンティティを探索対象として選択する" +msgstr "Calculate the distance between entities and select the closest entity as the search target" diff --git a/docs/source/locale/en/LC_MESSAGES/index.po b/docs/source/locale/en/LC_MESSAGES/index.po new file mode 100644 index 0000000..56c8eec --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/index.po @@ -0,0 +1,98 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/index.rst:35 +msgid "インストール" +msgstr "Installation" + +#: ../../source/index.rst:42 +msgid "クイックスタート" +msgstr "Quick Start" + +#: ../../source/index.rst:48 +msgid "チュートリアル" +msgstr "Tutorial" + +#: ../../source/index.rst:58 +msgid "ハンズオン" +msgstr "Hands-On" + +#: ../../source/index.rst:65 +msgid "APIドキュメント" +msgstr "API Documentation" + +#: ../../source/index.rst:7 +msgid "adf-core-pythonのドキュメント" +msgstr "adf-core-python Documentation" + +#: ../../source/index.rst:11 +msgid "現在このパッケージは開発中です。破壊的な変更が行われる可能性があります。" +msgstr "This package is currently under development. Breaking changes may occur." + +#: ../../source/index.rst:15 +msgid "パッケージとしてまだ公開していないため、pip でインストールすることはできません。" +msgstr "Since it has not been released as a package yet, it cannot be installed via pip." + +#: ../../source/index.rst:19 +msgid "以下の言語のドキュメントは機械翻訳を使用しています。翻訳の正確性については保証できません。 英語" +msgstr "The following language documents use machine translation. The accuracy of the translation cannot be guaranteed. English" + +#: ../../source/index.rst:19 +msgid "概要" +msgstr "Overview" + +#: ../../source/index.rst:20 +msgid "" +"adf-core-pythonは、RoboCup Rescue " +"Simulation(RRS)におけるエージェント開発を支援するためのライブラリ及びフレームワークです。 adf-core-" +"pythonを使用することで、エージェントの開発を効率化し、再利用性を向上させることができます。" +msgstr "" +"adf-core-python is a library and framework to support agent development in RoboCup Rescue " +"Simulation (RRS). By using adf-core-python, you can streamline agent development and improve reusability." + +#: ../../source/index.rst:24 +msgid "特徴" +msgstr "Features" + +#: ../../source/index.rst:25 +msgid "adf-core-pythonには以下のような特徴があります。" +msgstr "adf-core-python has the following features:" + +#: ../../source/index.rst:27 +msgid "**モジュール単位での開発**: モジュール単位でエージェント開発を行い、モジュールの入れ替えが容易です。" +msgstr "**Module-based development**: Develop agents on a module basis, making it easy to replace modules." + +#: ../../source/index.rst:28 +msgid "**モジュールの再利用**: 他のエージェントで使用されているモジュールを再利用することができます。" +msgstr "**Module reuse**: Reuse modules used by other agents." + +#: ../../source/index.rst:29 +msgid "**エージェントの開発に集中**: シミュレーションサーバーとの通信やログ出力などの共通処理をライブラリが提供します。" +msgstr "**Focus on agent development**: The library provides common processing such as communication with the simulation server and log output." + +#: ../../source/index.rst:32 +msgid "はじめに" +msgstr "Getting Started" + +#: ../../source/index.rst:33 +msgid "adf-core-pythonを始めるには、インストールに従い、このドキュメントに記載されているチュートリアルやハンズオンを参照してください。" +msgstr "To get started with adf-core-python, follow the installation and refer to the tutorials and hands-on in this document." diff --git a/docs/source/locale/en/LC_MESSAGES/install.po b/docs/source/locale/en/LC_MESSAGES/install.po new file mode 100644 index 0000000..9e2e4c3 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/install.po @@ -0,0 +1,387 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/install/environment/environment.md:1 +msgid "環境構築" +msgstr "Environment Setup" + +#: ../../source/install/environment/environment.md:3 +msgid "" +"adf-core-pythonをインストールするには以下の必要条件が必要です。 " +"既にお使いのPCにインストールされている場合は再度インストールする必要はありません。" +msgstr "" +"To install adf-core-python, the following prerequisites are required. " +"If they are already installed on your PC, you do not need to reinstall them." + +#: ../../source/install/environment/environment.md:6 +msgid "必要条件" +msgstr "Prerequisites" + +#: ../../source/install/environment/environment.md:8 +msgid "Git" +msgstr "Git" + +#: ../../source/install/environment/environment.md:9 +#: ../../source/install/install/install.md:9 +msgid "Python 3.12 以上" +msgstr "Python 3.12 or higher" + +#: ../../source/install/environment/environment.md:10 +msgid "OpenJDK 17" +msgstr "OpenJDK 17" + +#: ../../source/install/environment/environment.md:12 +msgid "各OSでのインストール方法は以下のページをそれぞれ参照してください" +msgstr "Refer to the following pages for installation methods for each OS" + +#: ../../source/install/environment/environment.md:14 +msgid "[Windowsでの必要条件のインストール方法](./windows/install.md)" +msgstr "[How to install prerequisites on Windows](./windows/install.md)" + +#: ../../source/install/environment/environment.md:16 +msgid "[MacOSでの必要条件のインストール方法](./mac/install.md)" +msgstr "[How to install prerequisites on MacOS](./mac/install.md)" + +#: ../../source/install/environment/environment.md:18 +msgid "[Linuxでの必要条件のインストール方法](./linux/install.md)" +msgstr "[How to install prerequisites on Linux](./linux/install.md)" + +#: ../../source/install/environment/environment.md:20 +msgid "シミュレーションサーバーのインストール" +msgstr "Installing the Simulation Server" + +#: ../../source/install/environment/environment.md:22 +msgid "次にRoboCup Rescue Simulationのシミュレーションサーバーをインストールします。" +msgstr "Next, install the RoboCup Rescue Simulation server." + +#: ../../source/install/environment/environment.md:25 +msgid "WORKING_DIR は任意のディレクトリを作成、指定してください。" +msgstr "Create and specify any directory as WORKING_DIR." + +#: ../../source/install/environment/environment.md:36 +msgid "ビルドした際に以下のようなメッセージが表示されたら成功です。" +msgstr "If the following message is displayed when you build, it is successful." + +#: ../../source/install/environment/environment.md:42 +msgid "シミュレーションサーバーの動作確認" +msgstr "Simulation Server Operation Check" + +#: ../../source/install/environment/environment.md:49 +msgid "![シミュレーションサーバーの起動](../../images/launch_server.png)" +msgstr "![Launching the Simulation Server](../../images/launch_server.png)" + +#: ../../source/install/environment/environment.md:49 +msgid "シミュレーションサーバーの起動" +msgstr "Launching the Simulation Server" + +#: ../../source/install/environment/environment.md:51 +msgid "" +"上記のように何個かのウィンドウが表示されたら成功です。 コマンドラインで `Ctrl + C` (MacOSの場合は `Command + C`" +" ) を押すとシミュレーションサーバーが終了します。" +msgstr "" +"If several windows are displayed as shown above, it is successful. Press `Ctrl + C` (or `Command + C` on MacOS) in the command line to stop the simulation server." + +#: ../../source/install/environment/environment.md:55 +msgid "シミュレーションサーバーを停止させたあとは、プロセスが残ってしまう場合があるので`./kill.sh` を実行してください。" +msgstr "After stopping the simulation server, run `./kill.sh` as there may be remaining processes." + +#: ../../source/install/environment/linux/install.md:1 +msgid "Linuxでの環境構築" +msgstr "Environment Setup on Linux" + +#: ../../source/install/environment/linux/install.md:3 +#: ../../source/install/environment/windows/install.md:3 +msgid "1. Gitのインストール" +msgstr "1. Installing Git" + +#: ../../source/install/environment/linux/install.md:5 +#: ../../source/install/environment/mac/install.md:3 +#: ../../source/install/environment/mac/install.md:20 +msgid "Terminalを起動し、以下のコマンドを実行します。" +msgstr "Open the terminal and run the following command." + +#: ../../source/install/environment/linux/install.md:10 +msgid "もし、`command not found`などのエラーが出た場合、OS標準のパッケージマネージャーを使用してインストールします。" +msgstr "If you get an error like `command not found`, use the OS's standard package manager to install." + +#: ../../source/install/environment/linux/install.md:11 +#: ../../source/install/environment/linux/install.md:85 +#: ../../source/install/environment/linux/install.md:122 +msgid "DebianベースのOSの場合(Ubuntuなど)" +msgstr "For Debian-based OS (e.g., Ubuntu)" + +#: ../../source/install/environment/linux/install.md:17 +#: ../../source/install/environment/linux/install.md:91 +#: ../../source/install/environment/linux/install.md:128 +msgid "Red HatベースのOSの場合(Fedoraなど)" +msgstr "For Red Hat-based OS (e.g., Fedora)" + +#: ../../source/install/environment/linux/install.md:26 +#: ../../source/install/environment/linux/install.md:109 +#: ../../source/install/environment/linux/install.md:137 +#: ../../source/install/environment/mac/install.md:28 +#: ../../source/install/environment/mac/install.md:43 +#: ../../source/install/environment/mac/install.md:58 +msgid "以下のコマンドを入力し、バージョンが表示されたら成功です。(表示されない方はTerminalを再起動してください)" +msgstr "Enter the following command, and if the version is displayed, it is successful. (If not displayed, restart the terminal.)" + +#: ../../source/install/environment/linux/install.md:31 +#: ../../source/install/environment/windows/install.md:14 +msgid "2. Pythonのインストール" +msgstr "2. Installing Python" + +#: ../../source/install/environment/linux/install.md:33 +#: ../../source/install/environment/mac/install.md:35 +msgid "Terminalを起動し、以下のコマンドを実行します。また、バージョンが3.12以上になっていることを確認します。" +msgstr "Open the terminal and run the following command. Also, make sure the version is 3.12 or higher." + +#: ../../source/install/environment/linux/install.md:38 +msgid "" +"もし、`command not " +"found`などのエラーが出た場合やバージョンが低い場合、Pythonのバージョン管理ツールであるpyenvを使用してインストールします" +msgstr "" +"If you get an error like `command not found` or the version is low, use the Python version management tool pyenv to install." + +#: ../../source/install/environment/linux/install.md:41 +msgid "インストール方法の内容が最新ではない場合があるため、[https://github.com/pyenv/pyenv](https://github.com/pyenv/pyenv)を参照してください。" +msgstr "The installation method may not be up to date, so please refer to [https://github.com/pyenv/pyenv](https://github.com/pyenv/pyenv)." + +#: ../../source/install/environment/linux/install.md:44 +msgid "以下のコマンドを実行します。" +msgstr "Run the following command." + +#: ../../source/install/environment/linux/install.md:49 +msgid "次に以下のコマンドを実行して、使用しているShellを確認します。" +msgstr "Next, run the following command to check the shell you are using." + +#: ../../source/install/environment/linux/install.md:54 +msgid "表示されたShellに従ってコマンドを実行してください。" +msgstr "Follow the commands according to the displayed shell." + +#: ../../source/install/environment/linux/install.md:56 +msgid "`bash`が表示された方は以下のコマンドを実行してください" +msgstr "If `bash` is displayed, run the following command." + +#: ../../source/install/environment/linux/install.md:70 +msgid "`zsh`が表示された方は以下のコマンドを実行してください" +msgstr "If `zsh` is displayed, run the following command." + +#: ../../source/install/environment/linux/install.md:77 +msgid "`fish`が表示された方は以下のコマンドを実行してください" +msgstr "If `fish` is displayed, run the following command." + +#: ../../source/install/environment/linux/install.md:84 +msgid "必要パッケージのインストール" +msgstr "Installing necessary packages" + +#: ../../source/install/environment/linux/install.md:100 +msgid "python3.12のインストール" +msgstr "Installing python3.12" + +#: ../../source/install/environment/linux/install.md:114 +#: ../../source/install/environment/mac/install.md:48 +#: ../../source/install/environment/windows/install.md:24 +msgid "3. OpenJDKのインストール" +msgstr "3. Installing OpenJDK" + +#: ../../source/install/environment/linux/install.md:116 +#: ../../source/install/environment/mac/install.md:50 +msgid "Terminalを起動し、以下のコマンドを実行します。また、バージョンが17になっていることを確認します。" +msgstr "Open the terminal and run the following command. Also, make sure the version is 17." + +#: ../../source/install/environment/linux/install.md:121 +msgid "" +"もし、`command not " +"found`などのエラーが出た場合やバージョンが異なる場合、OS標準のパッケージマネージャーを使用してインストールします" +msgstr "" +"If you get an error like `command not found` or the version is different, use the OS's standard package manager to install." + +#: ../../source/install/environment/mac/install.md:1 +msgid "Macでの環境構築" +msgstr "Environment Setup on Mac" + +#: ../../source/install/environment/mac/install.md:2 +msgid "1. Homebrewのインストール" +msgstr "1. Installing Homebrew" + +#: ../../source/install/environment/mac/install.md:8 +#: ../../source/install/environment/mac/install.md:24 +msgid "もし、`command not found`などのエラーが出た場合、以下のコマンドを実行します。" +msgstr "If you get an error like `command not found`, run the following command." + +#: ../../source/install/environment/mac/install.md:13 +msgid "もう一度、以下のコマンドを実行してバージョンが表示されたら成功です。(表示されない方はTerminalを再起動してください)" +msgstr "Run the following command again, and if the version is displayed, it is successful. (If not displayed, restart the terminal.)" + +#: ../../source/install/environment/mac/install.md:18 +msgid "2. Gitのインストール" +msgstr "2. Installing Git" + +#: ../../source/install/environment/mac/install.md:33 +msgid "3. Pythonのインストール" +msgstr "3. Installing Python" + +#: ../../source/install/environment/mac/install.md:39 +msgid "もし、`command not found`などのエラーが出た場合やバージョンが低い場合、以下のコマンドを実行します。" +msgstr "If you get an error like `command not found` or the version is low, run the following command." + +#: ../../source/install/environment/mac/install.md:54 +msgid "もし、`command not found`などのエラーが出た場合やバージョンが異なる場合、以下のコマンドを実行します。" +msgstr "If you get an error like `command not found` or the version is different, run the following command." + +#: ../../source/install/environment/windows/install.md:1 +msgid "Windowsでの環境構築" +msgstr "Environment Setup on Windows" + +#: ../../source/install/environment/windows/install.md:5 +msgid "[Git for Windows](https://gitforwindows.org/)の公式サイトにアクセスします。" +msgstr "Access the [Git for Windows](https://gitforwindows.org/) official site." + +#: ../../source/install/environment/windows/install.md:6 +msgid "トップページの\"Download\"をクリックします" +msgstr "Click \"Download\" on the top page." + +#: ../../source/install/environment/windows/install.md:7 +#: ../../source/install/environment/windows/install.md:18 +msgid "ダウンロードが完了した後、インストーラーを実行します。" +msgstr "After the download is complete, run the installer." + +#: ../../source/install/environment/windows/install.md:8 +msgid "全て\"Next\"をクリックします。" +msgstr "Click \"Next\" for all steps." + +#: ../../source/install/environment/windows/install.md:9 +#: ../../source/install/environment/windows/install.md:20 +msgid "インストールが完了するまで待ちます。" +msgstr "Wait for the installation to complete." + +#: ../../source/install/environment/windows/install.md:10 +msgid "インストールが完了したら\"Finish\"をクリックします。" +msgstr "Click \"Finish\" when the installation is complete." + +#: ../../source/install/environment/windows/install.md:11 +msgid "検索バーに\"Git Bash\"と入力し、Git Bashを実行します。" +msgstr "Enter \"Git Bash\" in the search bar and run Git Bash." + +#: ../../source/install/environment/windows/install.md:12 +msgid "画面が表示されていたらインストール成功です。" +msgstr "If the screen is displayed, the installation is successful." + +#: ../../source/install/environment/windows/install.md:16 +msgid "[Python](https://www.python.org/)の公式サイトにアクセスします。" +msgstr "Access the [Python](https://www.python.org/) official site." + +#: ../../source/install/environment/windows/install.md:17 +msgid "トップページの\"Download Python ~\"をクリックします" +msgstr "Click \"Download Python ~\" on the top page." + +#: ../../source/install/environment/windows/install.md:19 +msgid "\"Add python.exe to PATH\"にチェックが入っていることを確認した後、\"Install Now\"をクリックします。" +msgstr "Make sure \"Add python.exe to PATH\" is checked, then click \"Install Now\"." + +#: ../../source/install/environment/windows/install.md:21 +msgid "インストールが完了したら\"Close\"をクリックします。" +msgstr "Click \"Close\" when the installation is complete." + +#: ../../source/install/environment/windows/install.md:22 +msgid "" +"Git Bashを開き、`python --version`と入力し、`Python " +"[バージョン]`が表示されたら成功です。(もし表示されない場合はGit Bashを開き直してください)" +msgstr "" +"Open Git Bash, enter `python --version`, and if `Python [version]` is displayed, it is successful. (If not displayed, reopen Git Bash.)" + +#: ../../source/install/environment/windows/install.md:26 +msgid "[OpenJDK](https://jdk.java.net/archive/)のダウンロードページにアクセスします。" +msgstr "Access the [OpenJDK](https://jdk.java.net/archive/) download page." + +#: ../../source/install/environment/windows/install.md:27 +msgid "17.0.2のWindowsの横にある\"zip\"をクリックします。" +msgstr "Click \"zip\" next to Windows for version 17.0.2." + +#: ../../source/install/environment/windows/install.md:28 +msgid "ダウンロードしたzipを展開(解凍)します。" +msgstr "Extract the downloaded zip file." + +#: ../../source/install/environment/windows/install.md:29 +msgid "展開(解凍)すると\"jdk-17.0.2\"のようなフォルダができるのを確認します。" +msgstr "Confirm that a folder like \"jdk-17.0.2\" is created after extraction." + +#: ../../source/install/environment/windows/install.md:30 +msgid "このフォルダ\"jdk-17.0.2\"を`C:¥`の直下に移動させます。" +msgstr "Move this folder \"jdk-17.0.2\" to the root of `C:¥`." + +#: ../../source/install/environment/windows/install.md:31 +msgid "Windowsでコマンドプロンプトを管理者として実行します。" +msgstr "Run the command prompt as an administrator on Windows." + +#: ../../source/install/environment/windows/install.md:32 +msgid "開いたら以下のコマンドを実行します。" +msgstr "Once opened, run the following command." + +#: ../../source/install/environment/windows/install.md:36 +msgid "次に以下のコマンドを実行します。" +msgstr "Next, run the following command." + +#: ../../source/install/environment/windows/install.md:40 +msgid "Git Bashを開き、`java -version`と入力します。 以下のような文字が表示されたらインストール成功です。" +msgstr "Open Git Bash and enter `java -version`. If the following text is displayed, the installation is successful." + +#: ../../source/install/install/install.md:1 +msgid "インストール" +msgstr "Installation" + +#: ../../source/install/install/install.md:3 +msgid "このセクションでは、パッケージのインストール方法について説明します。" +msgstr "This section explains how to install the package." + +#: ../../source/install/install/install.md:5 +msgid "前提条件" +msgstr "Prerequisites" + +#: ../../source/install/install/install.md:7 +msgid "インストールする前に、以下の前提条件を確認してください:" +msgstr "Before installing, please check the following prerequisites:" + +#: ../../source/install/install/install.md:10 +msgid "pip" +msgstr "pip" + +#: ../../source/install/install/install.md:12 +msgid "パッケージのインストール" +msgstr "Installing the package" + +#: ../../source/install/install/install.md:14 +msgid "パッケージをインストールするには、次のコマンドを実行します:" +msgstr "To install the package, run the following command:" + +#: ../../source/install/install/install.md:20 +msgid "インストールの確認" +msgstr "Verifying the installation" + +#: ../../source/install/install/install.md:22 +msgid "インストールを確認するには、次のコマンドを実行します:" +msgstr "To verify the installation, run the following command:" + +#: ../../source/install/install/install.md:28 +msgid "パッケージが正しくインストールされている場合、パッケージのバージョン番号などが表示されます。" +msgstr "If the package is installed correctly, the package version number, etc., will be displayed." + diff --git a/docs/source/locale/en/LC_MESSAGES/modules.po b/docs/source/locale/en/LC_MESSAGES/modules.po new file mode 100644 index 0000000..49c7a03 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/modules.po @@ -0,0 +1,26 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/modules.rst:2 +msgid "adf_core_python" +msgstr "adf_core_python" + diff --git a/docs/source/locale/en/LC_MESSAGES/quickstart.po b/docs/source/locale/en/LC_MESSAGES/quickstart.po new file mode 100644 index 0000000..af56f57 --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/quickstart.po @@ -0,0 +1,77 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/quickstart/quickstart.md:1 +msgid "クイックスタート" +msgstr "Quick Start" + +#: ../../source/quickstart/quickstart.md:3 +msgid "前提条件" +msgstr "Prerequisites" + +#: ../../source/quickstart/quickstart.md:5 +msgid "インストールする前に、以下の前提条件を確認してください:" +msgstr "Before installing, please check the following prerequisites:" + +#: ../../source/quickstart/quickstart.md:7 +msgid "Python 3.12 以上" +msgstr "Python 3.12 or higher" + +#: ../../source/quickstart/quickstart.md:8 +msgid "pip" +msgstr "pip" + +#: ../../source/quickstart/quickstart.md:10 +msgid "パッケージのインストール" +msgstr "Package Installation" + +#: ../../source/quickstart/quickstart.md:12 +msgid "パッケージをインストールするには、次のコマンドを実行します:" +msgstr "To install the package, run the following command:" + +#: ../../source/quickstart/quickstart.md:18 +msgid "新規エージェントの作成" +msgstr "Creating a New Agent" + +#: ../../source/quickstart/quickstart.md:20 +msgid "新規エージェントを作成するには、次のコマンドを実行します:" +msgstr "To create a new agent, run the following command:" + +#: ../../source/quickstart/quickstart.md:26 +msgid "実行すると、下記のような対話形式のプロンプトが表示されます:" +msgstr "When executed, an interactive prompt like the one below will be displayed:" + +#: ../../source/quickstart/quickstart.md:33 +msgid "入力後、下記のようなエージェントのテンプレートがカレントディレクトリに作成されます。" +msgstr "After input, an agent template like the one below will be created in the current directory." + +#: ../../source/quickstart/quickstart.md:55 +msgid "エージェントを実行する" +msgstr "Running the Agent" + +#: ../../source/quickstart/quickstart.md:57 +msgid "エージェントを実行するには、シミュレーションサーバーを起動し、次のコマンドを実行します:" +msgstr "To run the agent, start the simulation server and run the following command:" + +#: ../../source/quickstart/quickstart.md:63 +msgid "エージェントの実行が開始され、シミュレーションサーバーとの通信が開始されます。" +msgstr "The agent execution will start, and communication with the simulation server will begin." diff --git a/docs/source/locale/en/LC_MESSAGES/tutorial.po b/docs/source/locale/en/LC_MESSAGES/tutorial.po new file mode 100644 index 0000000..8aa509e --- /dev/null +++ b/docs/source/locale/en/LC_MESSAGES/tutorial.po @@ -0,0 +1,626 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, Haruki Uehara, Yuki Shimada +# This file is distributed under the same license as the adf-core-python +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: adf-core-python \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 13:59+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../source/tutorial/agent/agent.md:1 +msgid "エージェントの作成" +msgstr "Creating an Agent" + +#: ../../source/tutorial/agent/agent.md:3 +msgid "このセクションでは、`adf-core-python` ライブラリの使用方法の概要を提供します。" +msgstr "This section provides an overview of how to use the `adf-core-python` library." + +#: ../../source/tutorial/agent/agent.md:5 +msgid "新規エージェントの作成" +msgstr "Creating a New Agent" + +#: ../../source/tutorial/agent/agent.md:7 +msgid "新規エージェントを作成するには、次のコマンドを実行します:" +msgstr "To create a new agent, run the following command:" + +#: ../../source/tutorial/agent/agent.md:13 +msgid "実行すると、下記のような対話形式のプロンプトが表示されます:" +msgstr "When executed, an interactive prompt like the one below will be displayed:" + +#: ../../source/tutorial/agent/agent.md:21 +msgid "" +"エージェントチーム名は、エージェントのディレクトリ名として使用されます。 以後、エージェントチーム名を `` " +"として参照します。" +msgstr "" +"The agent team name will be used as the directory name for the agent. Hereafter, the agent team name will be referred to as ``." + +#: ../../source/tutorial/agent/agent.md:25 +msgid "入力後、下記のようなエージェントのテンプレートがカレントディレクトリに作成されます。" +msgstr "After entering, an agent template like the one below will be created in the current directory." + +#: ../../source/tutorial/agent/agent.md:47 +msgid "シミュレーションを実行する" +msgstr "Running the Simulation" + +#: ../../source/tutorial/agent/agent.md:49 +#: ../../source/tutorial/agent/agent_control.md:110 +#: ../../source/tutorial/agent/agent_control.md:330 +msgid "ターミナルを2つ起動します。" +msgstr "Open two terminals." + +#: ../../source/tutorial/agent/agent.md:51 +#: ../../source/tutorial/agent/agent_control.md:112 +#: ../../source/tutorial/agent/agent_control.md:332 +msgid "片方のターミナルを開き、シミュレーションサーバーを以下のコマンドで起動します:" +msgstr "Open one terminal and start the simulation server with the following command:" + +#: ../../source/tutorial/agent/agent.md:59 +#: ../../source/tutorial/agent/agent_control.md:120 +#: ../../source/tutorial/agent/agent_control.md:340 +msgid "その後、別のターミナルを開き、エージェントを起動します:" +msgstr "Then, open another terminal and start the agent:" + +#: ../../source/tutorial/agent/agent.md:67 +msgid "" +"エージェントが正常に起動すると、シミュレーションサーバーに接続され、エージェントがシミュレーションに参加し、エージェントが動き出します。 " +"途中で止めたい場合は、それぞれのコマンドラインで `Ctrl + C` (MacOSの場合は `Command + C` ) を押してください。" +msgstr "" +"When the agent starts successfully, it will connect to the simulation server, participate in the simulation, and start moving. " +"If you want to stop it midway, press `Ctrl + C` (or `Command + C` on MacOS) in each command line." + +#: ../../source/tutorial/agent/agent.md:71 +msgid "シミュレーションサーバーを停止させたあとは、プロセスが残ってしまう場合があるので`./kill.sh` を実行してください。" +msgstr "After stopping the simulation server, run `./kill.sh` as there may be remaining processes." + +#: ../../source/tutorial/agent/agent_control.md:1 +msgid "エージェントの制御" +msgstr "Agent Control" + +#: ../../source/tutorial/agent/agent_control.md:3 +msgid "このセクションでは、エージェントの制御用のプログラムを作成する方法について説明します。" +msgstr "This section explains how to create a program to control agents." + +#: ../../source/tutorial/agent/agent_control.md:5 +msgid "エージェントの制御について" +msgstr "About Agent Control" + +#: ../../source/tutorial/agent/agent_control.md:7 +msgid "RRSの災害救助エージェントは3種類あり、種類毎にそれぞれ異なるプログラムを書く必要があります。しかし、初めから全てのプログラムを書くことは困難です。ここではまず初めに、消防隊エージェントを操作するプログラムの一部を書いてみましょう。" +msgstr "There are three types of disaster rescue agents in RRS, and each type requires a different program. However, it is difficult to write all the programs from the beginning. Here, let's first write part of the program to control the fire brigade agent." + +#: ../../source/tutorial/agent/agent_control.md:10 +msgid "" +"エージェントを操作するプログラムは、エージェントの種類毎に同一です。 プログラムは各エージェントに配られ、そのエージェントのみの操作を担います。 " +"消防隊エージェントを操作するプログラムを書けば、それがすべての消防隊エージェント上でそれぞれ動作します。" +msgstr "" +"The program that controls the agents is the same for each type of agent. The program is distributed to each agent and is responsible for controlling only that agent. " +"If you write a program to control the fire brigade agent, it will run on all fire brigade agents." + +#: ../../source/tutorial/agent/agent_control.md:13 +msgid "![消防隊エージェント](./../../images/agent_control.jpg)" +msgstr "![Fire Brigade Agent](./../../images/agent_control.jpg)" + +#: ../../source/tutorial/agent/agent_control.md:13 +msgid "消防隊エージェント" +msgstr "Fire Brigade Agent" + +#: ../../source/tutorial/agent/agent_control.md:15 +msgid "エージェントの動作フロー" +msgstr "Agent Operation Flow" + +#: ../../source/tutorial/agent/agent_control.md:17 +msgid "エージェントの動作を決めているのはTacticsというプログラムです。" +msgstr "The program that determines the agent's actions is called Tactics." + +#: ../../source/tutorial/agent/agent_control.md:19 +msgid "" +"消防隊の思考ルーチンは下の図の通りにおおよそおこなわれます。 消防隊の動作としては、まず救助対象の市民を捜索し(`Human " +"Detector`)、見つかった市民を救助します(`Action Rescue`)。 " +"救助対象の市民が見つからない場合は、探索場所を変更して市民を捜索するため、次の捜索場所を決定して(`Search`)移動します(`Action " +"Ext move`)。 " +"なお、エージェントは1ステップ内で、移動と救助活動を同時におこなうことが出来ません。つまり、ステップごとに更新される自身の周辺情報を確認して、動作の対象と動作内容を決定していくということになります。" +" これらそれぞれの機能が、モジュールと呼ばれるプログラムとして分割して表現されています。" +msgstr "" +"The fire brigade's thought routine is roughly as shown in the diagram below. The fire brigade's actions are to first search for the target citizen (`Human Detector`) and rescue the found citizen (`Action Rescue`). " +"If no target citizen is found, the search location is changed to search for citizens, the next search location is determined (`Search`), and the agent moves (`Action Ext move`). " +"Note that the agent cannot perform both movement and rescue activities within one step. In other words, the agent checks the updated surrounding information at each step and determines the target and action content. " +"Each of these functions is expressed as a program called a module." + +#: ../../source/tutorial/agent/agent_control.md:21 +msgid "今回はこの中で、救助(掘り起こし)対象を決定する `Human Detector` モジュールを開発します。" +msgstr "This time, we will develop the `Human Detector` module that determines the rescue (digging) target." + +#: ../../source/tutorial/agent/agent_control.md:23 +msgid "![消防隊エージェントの動作フロー](./../../images/agent_flow.png)" +msgstr "![Fire Brigade Agent Operation Flow](./../../images/agent_flow.png)" + +#: ../../source/tutorial/agent/agent_control.md:23 +msgid "消防隊エージェントの動作フロー" +msgstr "Fire Brigade Agent Operation Flow" + +#: ../../source/tutorial/agent/agent_control.md:25 +msgid "`Human Detector` モジュールの実装の準備" +msgstr "Preparing to Implement the `Human Detector` Module" + +#: ../../source/tutorial/agent/agent_control.md:27 +msgid "まず、`Human Detector` モジュールを記述するためのファイルを作成します。" +msgstr "First, create a file to describe the `Human Detector` module." + +#: ../../source/tutorial/agent/agent_control.md:34 +msgid "" +"次に、`Human Detector` モジュールの雛形の実装を行います。 " +"以下のコードを`fire_brigade_human_detector.py` に記述してください。" +msgstr "" +"Next, implement the template for the `Human Detector` module. " +"Please write the following code in `fire_brigade_human_detector.py`." + +#: ../../source/tutorial/agent/agent_control.md:92 +msgid "モジュールの登録" +msgstr "Registering the Module" + +#: ../../source/tutorial/agent/agent_control.md:94 +msgid "次に、作成したモジュールを登録します。" +msgstr "Next, register the created module." + +#: ../../source/tutorial/agent/agent_control.md:96 +msgid "`WORKING_DIR//config/module.yaml` ファイルを開き、以下の部分を" +msgstr "Open the `WORKING_DIR//config/module.yaml` file and" + +#: ../../source/tutorial/agent/agent_control.md:103 +msgid "以下のように変更してください。" +msgstr "change the following part as shown below." + +#: ../../source/tutorial/agent/agent_control.md:128 +msgid "標準出力に `Calculate FireBrigadeHumanDetector` と表示されれば成功です。" +msgstr "If `Calculate FireBrigadeHumanDetector` is displayed in the standard output, it is successful." + +#: ../../source/tutorial/agent/agent_control.md:130 +msgid "`Human Detector` モジュールの設計" +msgstr "Designing the `Human Detector` Module" + +#: ../../source/tutorial/agent/agent_control.md:132 +msgid "救助対象選択プログラムの中身を書き、消防隊エージェントが救助活動をおこなえるように修正します。" +msgstr "Write the content of the rescue target selection program and modify it so that the fire brigade agent can perform rescue activities." + +#: ../../source/tutorial/agent/agent_control.md:134 +msgid "" +"消防隊エージェントの救助対象を最も簡単に選択する方法は、埋没している市民の中で最も自身に近い市民を選択する方法です。 " +"今回は、この方法を採用した消防隊エージェントの行動対象決定モジュールを書いてみましょう。" +msgstr "" +"The simplest way for a fire brigade agent to select a rescue target is to choose the closest buried citizen. " +"This time, let's write a module to determine the action target of the fire brigade agent using this method." + +#: ../../source/tutorial/agent/agent_control.md:136 +msgid "埋没している市民の中で最も自身に近い市民を救助対象として選択する方法は、フローチャートで表すと下図のようになります。" +msgstr "" +"The method of selecting the closest buried citizen as the rescue target is shown in the flowchart below." + +#: ../../source/tutorial/agent/agent_control.md:138 +msgid "![救助対象選択フローチャート](./../../images/human_detector_flow.png)" +msgstr "![Rescue Target Selection Flowchart](./../../images/human_detector_flow.png)" + +#: ../../source/tutorial/agent/agent_control.md:138 +msgid "救助対象選択フローチャート" +msgstr "Rescue Target Selection Flowchart" + +#: ../../source/tutorial/agent/agent_control.md:140 +msgid "" +"このフローチャート中の各処理を、次小節で紹介する各クラス・メソッド等で置き換えたものを、`fire_brigade_human_detector.py`" +" に記述していくことで、救助対象選択プログラムを完成させます。" +msgstr "" +"By replacing each process in this flowchart with the classes and methods introduced in the next subsection, and writing them in `fire_brigade_human_detector.py`, you will complete the rescue target selection program." + +#: ../../source/tutorial/agent/agent_control.md:142 +msgid "`Human Detector` モジュールの実装で使用するクラス・メソッド" +msgstr "Classes and Methods Used in the Implementation of the `Human Detector` Module" + +#: ../../source/tutorial/agent/agent_control.md:144 +msgid "Entity" +msgstr "Entity" + +#: ../../source/tutorial/agent/agent_control.md:146 +msgid "`Entity` クラスは、エンティティの基底クラスです。 このクラスは、エンティティの基本情報を保持します。" +msgstr "" +"The `Entity` class is the base class for entities. This class holds the basic information of an entity." + +#: ../../source/tutorial/agent/agent_control.md:148 +msgid "" +"RRS上のエンティティは下図のように `Entity` を継承したクラスで表現されています。 " +"赤枠で囲まれたクラスは、クラスの意味がそのままRRSの直接的な構成要素を表しています。" +msgstr "" +"Entities in RRS are represented by classes that inherit from `Entity` as shown in the diagram below. " +"The classes enclosed in red boxes directly represent the components of RRS." + +#: ../../source/tutorial/agent/agent_control.md:150 +msgid "例: Road クラスのインスタンスの中には、 Hydrant クラスを継承してない通常の道路を表すものも存在しています。" +msgstr "" +"For example, among the instances of the Road class, there are those that represent ordinary roads that do not inherit from the Hydrant class." + +#: ../../source/tutorial/agent/agent_control.md:152 +msgid "![エンティティの継承関係](./../../images/entity.png)" +msgstr "![Entity Inheritance Relationship](./../../images/entity.png)" + +#: ../../source/tutorial/agent/agent_control.md:152 +msgid "エンティティの継承関係" +msgstr "Entity Inheritance Relationship" + +#: ../../source/tutorial/agent/agent_control.md:154 +msgid "EntityID" +msgstr "EntityID" + +#: ../../source/tutorial/agent/agent_control.md:156 +msgid "" +"`EntityID` クラスは、全てのエージェント/オブジェクトを一意に識別するためのID(識別子)を表すクラスです。 " +"RRSではエージェントとオブジェクトをまとめて、エンティティと呼んでいます。" +msgstr "" +"The `EntityID` class represents an ID (identifier) that uniquely identifies all agents/objects. " +"In RRS, agents and objects are collectively referred to as entities." + +#: ../../source/tutorial/agent/agent_control.md:158 +msgid "Civilian" +msgstr "Civilian" + +#: ../../source/tutorial/agent/agent_control.md:160 +msgid "`Civilian` クラスは、市民を表すクラスです。このクラスからは、エージェントの位置や負傷の進行状況を取得することができます。" +msgstr "" +"The `Civilian` class represents a citizen. From this class, you can obtain the position of the agent and the progress of injuries." + +#: ../../source/tutorial/agent/agent_control.md:162 +msgid "`entity` が市民であるかどうかを判定する" +msgstr "Determine if `entity` is a civilian" + +#: ../../source/tutorial/agent/agent_control.md:168 +msgid "エンティティIDを取得する" +msgstr "Get the Entity ID" + +#: ../../source/tutorial/agent/agent_control.md:174 +msgid "市民が生きているかどうかを判定する" +msgstr "Determine if the civilian is alive" + +#: ../../source/tutorial/agent/agent_control.md:182 +msgid "市民が埋まっているかどうかを判定する" +msgstr "Determine if the civilian is buried" + +#: ../../source/tutorial/agent/agent_control.md:190 +msgid "WorldInfo" +msgstr "WorldInfo" + +#: ../../source/tutorial/agent/agent_control.md:192 +msgid "" +"`WorldInfo` クラスは、エージェントが把握している情報とそれに関する操作をおこなうメソッドをもったクラスです。 " +"エージェントはこのクラスのインスタンスを通して、他のエージェントやオブジェクトの状態を確認します。" +msgstr "" +"The `WorldInfo` class is a class that has methods for the information that the agent knows and the operations related to it. " +"Through an instance of this class, the agent checks the status of other agents and objects." + +#: ../../source/tutorial/agent/agent_control.md:194 +msgid "モジュール内では、`WorldInfo` クラスのインスタンスを `self._world_info` として保持しています。" +msgstr "" +"In the module, an instance of the `WorldInfo` class is held as `self._world_info`." + +#: ../../source/tutorial/agent/agent_control.md:196 +msgid "エンティティIDからエンティティを取得する" +msgstr "Get the entity from the Entity ID" + +#: ../../source/tutorial/agent/agent_control.md:202 +msgid "指定したクラスのエンティティを全て取得する" +msgstr "Get all entities of the specified class" + +#: ../../source/tutorial/agent/agent_control.md:208 +msgid "エージェントの位置から指定したエンティティまでの距離を取得する" +msgstr "Get the distance from the agent's position to the specified entity" + +#: ../../source/tutorial/agent/agent_control.md:214 +#: ../../source/tutorial/agent/agent_control.md:228 +msgid "[詳細はこちら](../../adf_core_python.core.agent.info.rst)" +msgstr "[Details here](../../adf_core_python.core.agent.info.rst)" + +#: ../../source/tutorial/agent/agent_control.md:216 +msgid "AgentInfo" +msgstr "AgentInfo" + +#: ../../source/tutorial/agent/agent_control.md:218 +msgid "" +"`AgentInfo` クラスは、エージェント自身の情報とそれに関する操作をおこなうメソッドをもったクラスです。 " +"エージェントはこのクラスのインスタンスを通して、エージェント自身の状態を取得します。" +msgstr "" +"The `AgentInfo` class is a class that has methods for the agent's own information and operations related to it. " +"Through an instance of this class, the agent obtains its own status." + +#: ../../source/tutorial/agent/agent_control.md:220 +msgid "モジュール内では、`AgentInfo` クラスのインスタンスを `self._agent_info` として保持しています。" +msgstr "" +"In the module, an instance of the `AgentInfo` class is held as `self._agent_info`." + +#: ../../source/tutorial/agent/agent_control.md:222 +msgid "自分自身のエンティティIDを取得する" +msgstr "Get your own entity ID" + +#: ../../source/tutorial/agent/agent_control.md:230 +msgid "`Human Detector` モジュールの実装" +msgstr "Implementation of the `Human Detector` Module" + +#: ../../source/tutorial/agent/agent_control.md:232 +msgid "" +"`Human Detector` モジュールの実装を行います。 以下のコードを`fire_brigade_human_detector.py` " +"に記述してください。" +msgstr "" +"Implement the `Human Detector` module. Please write the following code in `fire_brigade_human_detector.py`." + +#: ../../source/tutorial/agent/agent_control.md:348 +msgid "埋没している市民を消防隊エージェントが救出できるようになっていれば成功です。" +msgstr "" +"It is successful if the fire brigade agent can rescue the buried citizens." + +#: ../../source/tutorial/config/config.md:1 +msgid "コンフィグについて" +msgstr "About Config" + +#: ../../source/tutorial/config/config.md:3 +msgid "コンフィグについての説明" +msgstr "Explanation about Config" + +#: ../../source/tutorial/config/config.md:14 +msgid "`config` ディレクトリには、エージェントの設定ファイルが格納されています。" +msgstr "" +"The `config` directory contains the agent's configuration files." + +#: ../../source/tutorial/config/config.md:16 +msgid "launcher.yaml" +msgstr "launcher.yaml" + +#: ../../source/tutorial/config/config.md:18 +msgid "" +"`launcher.yaml` は、エージェントの起動時に読み込まれる設定ファイルです。 " +"シミュレーションサーバーの指定や読み込むモジュールが記述されたファイルの指定などが記述されています。" +msgstr "" +"`launcher.yaml` is a configuration file that is read when the agent starts. " +"It specifies the simulation server and the files of the modules to be loaded." + +#: ../../source/tutorial/config/config.md:21 +msgid "module.yaml" +msgstr "module.yaml" + +#: ../../source/tutorial/config/config.md:23 +msgid "" +"`module.yaml` は、エージェントが読み込むモジュールの設定ファイルです。 読み込むモジュールのパスなどが記述されています。 " +"パスを指定する際は、プロジェクトの`main.py`からの相対パスで指定してください。" +msgstr "" +"`module.yaml` is a configuration file for the modules that the agent loads. It specifies the paths of the modules to be loaded. " +"When specifying the path, use the relative path from the project's `main.py`." + +#: ../../source/tutorial/config/config.md:27 +msgid "例" +msgstr "Example" + +#: ../../source/tutorial/config/config.md:36 +msgid "development.json" +msgstr "development.json" + +#: ../../source/tutorial/config/config.md:38 +msgid "" +"`development.json` は、開発時に使用する設定ファイルです。 エージェントの開発時に外部から値を取得する際に使用します。 " +"そのため、競技時には使用することができません。" +msgstr "" +"`development.json` is a configuration file used during development. It is used to obtain values from external sources during agent development. " +"Therefore, it cannot be used during competitions." + +#: ../../source/tutorial/environment/environment.md:1 +msgid "環境構築" +msgstr "Environment Setup" + +#: ../../source/tutorial/environment/environment.md:2 +msgid "今回はチュートリアル用のシナリオを使用してチュートリアルを行います。" +msgstr "" +"This time, we will use a tutorial scenario for the tutorial." + +#: ../../source/tutorial/environment/environment.md:4 +msgid "チュートリアルで使用するマップのダウンロード" +msgstr "Download the map used in the tutorial" + +#: ../../source/tutorial/environment/environment.md:6 +msgid "" +"{download}`マップのダウンロード <./../../download/tutorial_map.zip>` " +"をクリックしてダウンロードしてください。" +msgstr "" +"Click {download}`Download the map <./../../download/tutorial_map.zip>` to download." + +#: ../../source/tutorial/environment/environment.md:9 +msgid "ダウンロードしたファイルを解凍し、中のファイルを `rcrs-server/maps/` の中に移動させてください。" +msgstr "" +"Unzip the downloaded file and move the files inside to `rcrs-server/maps/`." + +#: ../../source/tutorial/environment/environment.md:11 +msgid "シミュレーションサーバーの動作確認" +msgstr "Check the operation of the simulation server" + +#: ../../source/tutorial/environment/environment.md:18 +msgid "" +"何個かのウィンドウが表示されたら成功です。 コマンドラインで `Ctrl + C` (MacOSの場合は `Command + C` ) " +"を押すとシミュレーションサーバーが終了します。" +msgstr "" +"If several windows are displayed, it is successful. Press `Ctrl + C` (or `Command + C` on MacOS) in the command line to stop the simulation server." + +#: ../../source/tutorial/module/module.md:1 +msgid "モジュールについて" +msgstr "About Modules" + +#: ../../source/tutorial/module/module.md:3 +msgid "モジュールの指定方法" +msgstr "How to specify modules" + +#: ../../source/tutorial/module/module.md:5 +msgid "モジュールを指定するには、module.yamlにモジュールの名前とパスを記述します。" +msgstr "" +"To specify a module, write the module name and path in module.yaml." + +#: ../../source/tutorial/module/module.md:7 +msgid "例えば、以下のように記述します:" +msgstr "For example, write as follows:" + +#: ../../source/tutorial/module/module.md:15 +msgid "" +"この場合、`SampleSearch` というモジュールで使用される、`PathPlanning` と `Clustering` " +"というモジュールを指定しています。" +msgstr "" +"In this case, the modules `PathPlanning` and `Clustering` used in the `SampleSearch` module are specified." + +#: ../../source/tutorial/module/module.md:17 +msgid "モジュールの読み込み方法" +msgstr "How to load modules" + +#: ../../source/tutorial/module/module.md:19 +msgid "モジュール内で、他のモジュールを読み込む場合は、次のように記述します:" +msgstr "" +"To load other modules within a module, write as follows:" + +#: ../../source/tutorial/module/module.md:68 +msgid "モジュールの種類について" +msgstr "About the types of modules" + +#: ../../source/tutorial/module/module.md:70 +msgid "adf-core-pythonでは、以下のモジュールが提供されています。" +msgstr "" +"The following modules are provided in adf-core-python." + +#: ../../source/tutorial/module/module.md +msgid "クラス" +msgstr "Class" + +#: ../../source/tutorial/module/module.md +msgid "役割" +msgstr "Role" + +#: ../../source/tutorial/module/module.md +msgid "TacticsFireBrigade" +msgstr "TacticsFireBrigade" + +#: ../../source/tutorial/module/module.md +msgid "消防隊用のモジュール呼び出し (競技では変更不可)" +msgstr "" +"Module call for fire brigade (cannot be changed in competition)" + +#: ../../source/tutorial/module/module.md +msgid "TacticsPoliceForce" +msgstr "TacticsPoliceForce" + +#: ../../source/tutorial/module/module.md +msgid "土木隊用のモジュール呼び出し (競技では変更不可)" +msgstr "" +"Module call for police force (cannot be changed in competition)" + +#: ../../source/tutorial/module/module.md +msgid "TacticsAmbulanceTeam" +msgstr "TacticsAmbulanceTeam" + +#: ../../source/tutorial/module/module.md +msgid "救急隊用のモジュール呼び出し (競技では変更不可)" +msgstr "" +"Module call for ambulance team (cannot be changed in competition)" + +#: ../../source/tutorial/module/module.md +msgid "BuildingDetector" +msgstr "BuildingDetector" + +#: ../../source/tutorial/module/module.md +msgid "消防隊の活動対象の選択" +msgstr "" +"Selection of activity target for fire brigade" + +#: ../../source/tutorial/module/module.md +msgid "RoadDetector" +msgstr "RoadDetector" + +#: ../../source/tutorial/module/module.md +msgid "土木隊の活動対象の選択" +msgstr "" +"Selection of activity target for police force" + +#: ../../source/tutorial/module/module.md +msgid "HumanDetector" +msgstr "HumanDetector" + +#: ../../source/tutorial/module/module.md +msgid "救急隊の活動対象の選択" +msgstr "" +"Selection of activity target for ambulance team" + +#: ../../source/tutorial/module/module.md +msgid "Search" +msgstr "Search" + +#: ../../source/tutorial/module/module.md +msgid "情報探索に向けた活動対象の選択" +msgstr "" +"Selection of activity target for information search" + +#: ../../source/tutorial/module/module.md +msgid "PathPlanning" +msgstr "PathPlanning" + +#: ../../source/tutorial/module/module.md +msgid "経路探索" +msgstr "Path Planning" + +#: ../../source/tutorial/module/module.md +msgid "DynamicClustering" +msgstr "DynamicClustering" + +#: ../../source/tutorial/module/module.md +msgid "シミュレーションの進行によってクラスタが変化するクラスタリング" +msgstr "" +"Clustering that changes as the simulation progresses" + +#: ../../source/tutorial/module/module.md +msgid "StaticClustering" +msgstr "StaticClustering" + +#: ../../source/tutorial/module/module.md +msgid "シミュレーション開始時にクラスタが定まるクラスタリング" +msgstr "" +"Clustering that is determined at the start of the simulation" + +#: ../../source/tutorial/module/module.md +msgid "ExtAction" +msgstr "ExtAction" + +#: ../../source/tutorial/module/module.md +msgid "活動対象決定後のエージェントの動作決定" +msgstr "" +"Determination of agent actions after selecting the activity target" + +#: ../../source/tutorial/module/module.md +msgid "MessageCoordinator" +msgstr "MessageCoordinator" + +#: ../../source/tutorial/module/module.md +msgid "通信でメッセージ(情報)を送信する経路である、チャンネルへのメッセージの分配" +msgstr "" +"Distribution of messages to channels, which are the routes for sending messages (information) via communication" + +#: ../../source/tutorial/module/module.md +msgid "ChannelSubscriber" +msgstr "ChannelSubscriber" + +#: ../../source/tutorial/module/module.md +msgid "通信によってメッセージを受け取るチャンネルの選択" +msgstr "" +"Selection of channels to receive messages via communication" + +#: ../../source/tutorial/module/module.md:88 +msgid "自分で上記の役割以外のモジュールを作成する際は、`AbstractModule` を継承してください。" +msgstr "" +"When creating a module with a role other than the above, inherit `AbstractModule`." diff --git a/docs/versions.yaml b/docs/versions.yaml new file mode 100644 index 0000000..f255c46 --- /dev/null +++ b/docs/versions.yaml @@ -0,0 +1,10 @@ +"1.0": + tag: "1.0" + languages: + - "en" + - "ja" +"2.0": + tag: "2.0" + languages: + - "en" + - "ja" diff --git a/poetry.lock b/poetry.lock index 1b520aa..7064ae6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "accessible-pygments" @@ -1029,11 +1029,6 @@ files = [ {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f60021ec1574e56632be2a36b946f8143bf4e5e6af4a06d85281adc22938e0dd"}, {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:394397841449853c2290a32050382edaec3da89e35b3e03d6cc966aebc6a8ae6"}, {file = "scikit_learn-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:57cc1786cfd6bd118220a92ede80270132aa353647684efa385a74244a41e3b1"}, - {file = "scikit_learn-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9a702e2de732bbb20d3bad29ebd77fc05a6b427dc49964300340e4c9328b3f5"}, - {file = "scikit_learn-1.5.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:b0768ad641981f5d3a198430a1d31c3e044ed2e8a6f22166b4d546a5116d7908"}, - {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:178ddd0a5cb0044464fc1bfc4cca5b1833bfc7bb022d70b05db8530da4bb3dd3"}, - {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7284ade780084d94505632241bf78c44ab3b6f1e8ccab3d2af58e0e950f9c12"}, - {file = "scikit_learn-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:b7b0f9a0b1040830d38c39b91b3a44e1b643f4b36e36567b80b7c6bd2202a27f"}, {file = "scikit_learn-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:757c7d514ddb00ae249832fe87100d9c73c6ea91423802872d9e74970a0e40b9"}, {file = "scikit_learn-1.5.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:52788f48b5d8bca5c0736c175fa6bdaab2ef00a8f536cda698db61bd89c551c1"}, {file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:643964678f4b5fbdc95cbf8aec638acc7aa70f5f79ee2cdad1eec3df4ba6ead8"}, @@ -1279,6 +1274,45 @@ sphinx = ">=1.8" code-style = ["pre-commit (==2.12.1)"] rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] +[[package]] +name = "sphinx-intl" +version = "2.3.1" +description = "Sphinx utility that make it easy to translate and to apply translation." +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinx_intl-2.3.1-py3-none-any.whl", hash = "sha256:e3f4be80743e0e4c778fbc07e72b062c13c88c50645a69b62557a3f57a24b7fc"}, + {file = "sphinx_intl-2.3.1.tar.gz", hash = "sha256:e2d04b907f57a029d46b4da344fd791e660d935eab6ced8a274fc65446389af1"}, +] + +[package.dependencies] +babel = "*" +click = "*" +setuptools = "*" +sphinx = "*" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "sphinx-rtd-theme" +version = "3.0.2" +description = "Read the Docs theme for Sphinx" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13"}, + {file = "sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85"}, +] + +[package.dependencies] +docutils = ">0.18,<0.22" +sphinx = ">=6,<9" +sphinxcontrib-jquery = ">=4,<5" + +[package.extras] +dev = ["bump2version", "transifex-client", "twine", "wheel"] + [[package]] name = "sphinx-togglebutton" version = "0.3.2" @@ -1347,6 +1381,20 @@ lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +description = "Extension to include jQuery on newer Sphinx releases" +optional = false +python-versions = ">=2.7" +files = [ + {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, + {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, +] + +[package.dependencies] +Sphinx = ">=1.8" + [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" @@ -1564,4 +1612,4 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "be47c53054f58d6aeac9949cdae16fe7092cc7f36a67a265d5608c52b89661d8" +content-hash = "776b51ba845a3c8e4835e1c260470c151d9cf1abd835017a177251127ee06d84" diff --git a/pyproject.toml b/pyproject.toml index 7b5dd66..aa334d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,8 @@ sphinx-book-theme = "^1.1.3" sphinx-copybutton = "^0.5.2" sphinxcontrib-mermaid = "^1.0.0" sphinx-togglebutton = "^0.3.2" +sphinx-intl = "^2.3.1" +sphinx-rtd-theme = "^3.0.2" [build-system] requires = ["poetry-core"] From d65bf5c594c71d32e633b072a8a882ec20a84755 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 19 Dec 2024 17:57:44 +0900 Subject: [PATCH 205/249] fix: Update pages_root URL for documentation to point to the live site --- docs/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build.py b/docs/build.py index 430bfad..469f697 100644 --- a/docs/build.py +++ b/docs/build.py @@ -23,7 +23,7 @@ def move_dir(src, dst): # to separate a single local build from all builds we have a flag, see conf.py os.environ["build_all_docs"] = str(True) -os.environ["pages_root"] = "http://127.0.0.1:5500/docs/pages" +os.environ["pages_root"] = "https://adf-python.github.io/adf-core-python/" # manually the main branch build in the current supported languages build_doc("latest", "en", "main") From 0049e99e66ba29e8c37c786c152d8041a5993d2c Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 20 Dec 2024 15:15:41 +0900 Subject: [PATCH 206/249] V0.1.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index aa334d6..3702187 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "adf_core_python" -version = "0.1.0" +version = "0.1.1" description = "Agent Development Framework for Python" authors = [ "Haruki Uehara ", From 2c2628be3e71b0724abbffbdad4dfb46e25e1529 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 23 Dec 2024 20:41:31 +0900 Subject: [PATCH 207/249] feat: Integrate ActionRest to send command messages in Office agent --- adf_core_python/core/agent/office/office.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/adf_core_python/core/agent/office/office.py b/adf_core_python/core/agent/office/office.py index 97fcf6e..ae06c87 100644 --- a/adf_core_python/core/agent/office/office.py +++ b/adf_core_python/core/agent/office/office.py @@ -1,6 +1,7 @@ from threading import Event from typing import Optional +from adf_core_python.core.agent.action.common.action_rest import ActionRest from adf_core_python.core.agent.agent import Agent from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -125,3 +126,8 @@ def think(self) -> None: self._message_manager, self._develop_data, ) + self.send_msg( + ActionRest() + .get_command(self.agent_id, self._agent_info.get_time()) + .prepare_cmd() + ) From cb57f8e1971f8a2d0b888df9b4fc06c8092cd12e Mon Sep 17 00:00:00 2001 From: shima004 Date: Sat, 28 Dec 2024 16:07:40 +0900 Subject: [PATCH 208/249] fix: Update position retrieval and action movement logic in DefaultExtendActionClear --- .../implement/action/default_extend_action_clear.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/adf_core_python/implement/action/default_extend_action_clear.py b/adf_core_python/implement/action/default_extend_action_clear.py index b3e3bb0..b5d0fd2 100644 --- a/adf_core_python/implement/action/default_extend_action_clear.py +++ b/adf_core_python/implement/action/default_extend_action_clear.py @@ -627,8 +627,11 @@ def _get_neighbour_position_action( ) -> Optional[Action]: agent_x = police_entity.get_x() agent_y = police_entity.get_y() - position = police_entity.get_position() - edge = target.get_edge_to(position) + position = self.world_info.get_entity(police_entity.get_position()) + if position is None: + return None + + edge = target.get_edge_to(position.get_id()) if edge is None: return None @@ -703,7 +706,7 @@ def _get_neighbour_position_action( if isinstance(target, Road): road = cast(Road, target) if road.get_blockades() == []: - return ActionMove([position, target.get_id()]) + return ActionMove([position.get_id(), target.get_id()]) target_blockade: Optional[Blockade] = None min_point_distance = sys.float_info.max @@ -742,4 +745,4 @@ def _get_neighbour_position_action( self._old_clear_y = clear_y return ActionClearArea(clear_x, clear_y) - return ActionMove([position, target.get_id()]) + return ActionMove([position.get_id(), target.get_id()]) From 897bddfa6b7faa1e7e2c2ec658cdefa3970d76f9 Mon Sep 17 00:00:00 2001 From: shima004 Date: Sat, 28 Dec 2024 21:06:44 +0900 Subject: [PATCH 209/249] fix: Ensure midpoints are only removed from points if they exist in the list --- .../implement/action/default_extend_action_clear.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/adf_core_python/implement/action/default_extend_action_clear.py b/adf_core_python/implement/action/default_extend_action_clear.py index b5d0fd2..d0a1912 100644 --- a/adf_core_python/implement/action/default_extend_action_clear.py +++ b/adf_core_python/implement/action/default_extend_action_clear.py @@ -477,7 +477,8 @@ def _get_move_points(self, road: Road) -> set[tuple[float, float]]: for edge in road.get_edges(): mid_x = (edge.get_start_x() + edge.get_end_x()) / 2.0 mid_y = (edge.get_start_y() + edge.get_end_y()) / 2.0 - points.remove((mid_x, mid_y)) + if (mid_x, mid_y) in points: + points.remove((mid_x, mid_y)) self._move_point_cache[road.get_id()] = points From 0f19d83700f47e0c419ca9de7c67ed623ebfb17d Mon Sep 17 00:00:00 2001 From: shima004 Date: Sat, 4 Jan 2025 01:57:58 +0900 Subject: [PATCH 210/249] feat: Add support for retrieving paths to multiple destinations in path planning algorithms feat: Add support for retrieving paths to multiple destinations in path planning algorithms fix: Change destination_entity_ids type from list to set in path planning algorithms fix: Add type annotations for queue, distance, previous, and path in DijkstraPathPlanning fix: Add type annotation for path variable in DijkstraPathPlanning --- .../module/algorithm/path_planning.py | 6 + .../module/algorithm/gateway_path_planning.py | 21 ++- .../module/algorithm/a_star_path_planning.py | 5 + .../algorithm/dijkstra_path_planning.py | 134 ++++++++++++++++++ .../module/algorithm/PathPlanningMapper.java | 48 +++++-- 5 files changed, 205 insertions(+), 9 deletions(-) create mode 100644 adf_core_python/implement/module/algorithm/dijkstra_path_planning.py diff --git a/adf_core_python/core/component/module/algorithm/path_planning.py b/adf_core_python/core/component/module/algorithm/path_planning.py index b741862..6445ac0 100644 --- a/adf_core_python/core/component/module/algorithm/path_planning.py +++ b/adf_core_python/core/component/module/algorithm/path_planning.py @@ -36,6 +36,12 @@ def get_path( ) -> list[EntityID]: pass + @abstractmethod + def get_path_to_multiple_destinations( + self, from_entity_id: EntityID, destination_entity_ids: set[EntityID] + ) -> list[EntityID]: + pass + @abstractmethod def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: pass diff --git a/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py b/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py index 7d3b218..6a2bc4e 100644 --- a/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py +++ b/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py @@ -14,8 +14,8 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.gateway.gateway_module import GatewayModule @@ -77,6 +77,25 @@ def get_path( entity_ids.append(EntityID(entity_id)) return entity_ids + def get_path_to_multiple_destinations( + self, from_entity_id: EntityID, destination_entity_ids: set[EntityID] + ) -> list[EntityID]: + arguments: dict[str, str] = { + "From": str(from_entity_id.get_value()), + "Destinations": json.dumps( + [entity_id.get_value() for entity_id in destination_entity_ids] + ), + } + result = self._gateway_module.execute( + "getResult(EntityID, List[EntityID])", arguments + ) + json_str = result.get_value_or_default("Result", "[]") + raw_entity_ids: list[int] = json.loads(json_str) + entity_ids: list[EntityID] = [] + for entity_id in raw_entity_ids: + entity_ids.append(EntityID(entity_id)) + return entity_ids + def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: arguments: dict[str, str] = { "From": str(from_entity_id.get_value()), diff --git a/adf_core_python/implement/module/algorithm/a_star_path_planning.py b/adf_core_python/implement/module/algorithm/a_star_path_planning.py index 63a258f..d69267a 100644 --- a/adf_core_python/implement/module/algorithm/a_star_path_planning.py +++ b/adf_core_python/implement/module/algorithm/a_star_path_planning.py @@ -69,6 +69,11 @@ def get_path( return [] + def get_path_to_multiple_destinations( + self, from_entity_id: EntityID, destination_entity_ids: set[EntityID] + ) -> list[EntityID]: + raise NotImplementedError + def heuristic(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: # Implement a heuristic function, for example, Euclidean distance return self._world_info.get_distance(from_entity_id, to_entity_id) diff --git a/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py b/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py new file mode 100644 index 0000000..e8697d6 --- /dev/null +++ b/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py @@ -0,0 +1,134 @@ +from __future__ import annotations + +import heapq +from typing import Optional + +from rcrs_core.entities.area import Area +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning + + +class DijkstraPathPlanning(PathPlanning): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self.graph: dict[EntityID, list[tuple[EntityID, float]]] = {} + # グラフの構築 + for area in self._world_info.get_entities_of_types([Area]): + if not isinstance(area, Area): + continue + if (neighbors := area.get_neighbours()) is None: + continue + area_id = area.get_id() + self.graph[area_id] = [ + ( + neighbor, + self._world_info.get_distance(area_id, entity_id2=neighbor), + ) + for neighbor in neighbors + if neighbor.get_value() != 0 + ] + + def calculate(self) -> PathPlanning: + return self + + def get_path( + self, from_entity_id: EntityID, to_entity_id: EntityID + ) -> list[EntityID]: + # ダイクストラ法で最短経路を計算 + queue: list[tuple[float, EntityID]] = [] + heapq.heappush(queue, (0, from_entity_id)) + distance: dict[EntityID, float] = {from_entity_id: 0} + previous: dict[EntityID, Optional[EntityID]] = {from_entity_id: None} + + while queue: + current_distance, current_node = heapq.heappop(queue) + if current_node == to_entity_id: + break + + self._logger.info( + f"current_node: {current_node}, current_entity: {self._world_info.get_entity(current_node)}" + ) + + for neighbor, weight in self.graph[current_node]: + new_distance = current_distance + weight + if neighbor not in distance or new_distance < distance[neighbor]: + distance[neighbor] = new_distance + heapq.heappush(queue, (new_distance, neighbor)) + previous[neighbor] = current_node + + path: list[EntityID] = [] + current_node = to_entity_id + while current_node is not None: + path.append(current_node) + current_node = previous[current_node] + + return path[::-1] + + def get_path_to_multiple_destinations( + self, from_entity_id: EntityID, destination_entity_ids: set[EntityID] + ) -> list[EntityID]: + open_list = [from_entity_id] + ancestors = {from_entity_id: from_entity_id} + found = False + next_node = None + + while open_list and not found: + next_node = open_list.pop(0) + if self.is_goal(next_node, destination_entity_ids): + found = True + break + + neighbors = self.graph.get(next_node, []) + if not neighbors: + continue + + for neighbor, _ in neighbors: + if self.is_goal(neighbor, destination_entity_ids): + ancestors[neighbor] = next_node + next_node = neighbor + found = True + break + elif neighbor not in ancestors: + open_list.append(neighbor) + ancestors[neighbor] = next_node + + if not found: + return [] + + path: list[EntityID] = [] + current = next_node + while current != from_entity_id: + if current is None: + raise RuntimeError( + "Found a node with no ancestor! Something is broken." + ) + path.insert(0, current) + current = ancestors.get(current) + path.insert(0, from_entity_id) + + return path + + def is_goal(self, entity_id: EntityID, target_ids: set[EntityID]) -> bool: + return entity_id in target_ids + + def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: + path = self.get_path(from_entity_id, to_entity_id) + distance = 0.0 + for i in range(len(path) - 1): + distance += self._world_info.get_distance(path[i], path[i + 1]) + return distance diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/PathPlanningMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/PathPlanningMapper.java index 3bfb84f..335a642 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/PathPlanningMapper.java +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/PathPlanningMapper.java @@ -1,18 +1,19 @@ package adf_core_python.gateway.mapper.module.algorithm; +import java.util.Collection; +import java.util.List; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + import adf.core.agent.communication.MessageManager; import adf_core_python.agent.precompute.PrecomputeData; import adf_core_python.component.module.algorithm.PathPlanning; import adf_core_python.gateway.mapper.module.AbstractModuleMapper; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import rescuecore2.config.Config; import rescuecore2.worldmodel.EntityID; -import java.util.Collection; -import java.util.List; - public class PathPlanningMapper extends AbstractModuleMapper { public PathPlanningMapper(PathPlanning pathPlanning, PrecomputeData precomputeData, MessageManager messageManager) { super(pathPlanning, precomputeData, messageManager); @@ -42,10 +43,24 @@ public Config execMethod(String methodName, Config arguments) { result = execGetDistance(); } if (methodName.equals("getDistance(EntityID, EntityID)")) { - result = execGetDistance(new EntityID(arguments.getIntValue("From")), new EntityID(arguments.getIntValue("Dest"))); + result = execGetDistance(new EntityID(arguments.getIntValue("From")), + new EntityID(arguments.getIntValue("Dest"))); } if (methodName.equals("getResult(EntityID, EntityID)")) { - result = execGetResult(new EntityID(arguments.getIntValue("From")), new EntityID(arguments.getIntValue("Dest"))); + result = execGetResult(new EntityID(arguments.getIntValue("From")), + new EntityID(arguments.getIntValue("Dest"))); + } + if (methodName.equals("getResult(EntityID, List[EntityID])")) { + ObjectMapper objectMapper = new ObjectMapper(); + Collection destinations; + try { + destinations = objectMapper.readValue(arguments.getValue("Destinations"), + new TypeReference>() { + }); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + result = execGetResult(new EntityID(arguments.getIntValue("From")), destinations); } return result; } @@ -105,4 +120,21 @@ private Config execGetResult(EntityID from, EntityID dest) { result.setValue("Result", String.valueOf(jsonStr)); return result; } + + private Config execGetResult(EntityID from, Collection destinations) { + PathPlanning pathPlanning = (PathPlanning) abstractModule; + pathPlanning.setFrom(from); + pathPlanning.setDestination(destinations); + List entityIDs = pathPlanning.getResult(); + Config result = new Config(); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonStr; + try { + jsonStr = objectMapper.writeValueAsString(entityIDs.stream().map(EntityID::getValue).toArray()); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + result.setValue("Result", String.valueOf(jsonStr)); + return result; + } } From 4e820ab4edf5e64bd815b215a3841f2967dd58b5 Mon Sep 17 00:00:00 2001 From: shima004 Date: Sat, 4 Jan 2025 02:55:40 +0900 Subject: [PATCH 211/249] feat: Implement placeholder for get_path_to_multiple_destinations method in AStarPathPlanning --- .../implement/module/algorithm/a_star_path_planning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adf_core_python/implement/module/algorithm/a_star_path_planning.py b/adf_core_python/implement/module/algorithm/a_star_path_planning.py index d69267a..6f2fce5 100644 --- a/adf_core_python/implement/module/algorithm/a_star_path_planning.py +++ b/adf_core_python/implement/module/algorithm/a_star_path_planning.py @@ -72,7 +72,7 @@ def get_path( def get_path_to_multiple_destinations( self, from_entity_id: EntityID, destination_entity_ids: set[EntityID] ) -> list[EntityID]: - raise NotImplementedError + return [] def heuristic(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: # Implement a heuristic function, for example, Euclidean distance From 08488a895cabd42b8f4868bdc663a864a8a4a3f0 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 8 Jan 2025 01:12:26 +0900 Subject: [PATCH 212/249] fix: Update module and action names in ModuleManager and command executors for consistency --- .../core/agent/module/module_manager.py | 50 +++++++++++++------ .../default_command_executor_ambulance.py | 4 +- .../default_command_executor_fire.py | 4 +- .../default_command_executor_police.py | 4 +- .../default_command_executor_scout_police.py | 2 +- config/module.yaml | 2 +- 6 files changed, 43 insertions(+), 23 deletions(-) diff --git a/adf_core_python/core/agent/module/module_manager.py b/adf_core_python/core/agent/module/module_manager.py index 0d5695c..96c506b 100644 --- a/adf_core_python/core/agent/module/module_manager.py +++ b/adf_core_python/core/agent/module/module_manager.py @@ -68,6 +68,11 @@ def get_module( return instance class_name = self._module_config.get_value(module_name) + if class_name is None: + self._logger.warning( + f"Module {module_name} not found in config. Using default module {default_module_class_name}" + ) + if class_name is not None: try: module_class: type = self._load_module(class_name) @@ -124,9 +129,12 @@ def get_module( def get_extend_action( self, action_name: str, default_action_class_name: str ) -> ExtendAction: - class_name = self._module_config.get_value_or_default( - action_name, default_action_class_name - ) + class_name = self._module_config.get_value(action_name) + if class_name is None: + self._logger.warning( + f"Action {action_name} not found in config. Using default action {default_action_class_name}" + ) + class_name = default_action_class_name action_class: type = self._load_module(class_name) @@ -150,9 +158,12 @@ def get_extend_action( def get_channel_subscriber( self, channel_subscriber_name: str, default_channel_subscriber_name: str ) -> ChannelSubscriber: - class_name = self._module_config.get_value_or_default( - channel_subscriber_name, default_channel_subscriber_name - ) + class_name = self._module_config.get_value(channel_subscriber_name) + if class_name is None: + self._logger.warning( + f"Channel subscriber {channel_subscriber_name} not found in config. Using default channel subscriber {default_channel_subscriber_name}" + ) + class_name = default_channel_subscriber_name channel_subscriber_class: type = self._load_module(class_name) @@ -172,9 +183,12 @@ def get_channel_subscriber( def get_message_coordinator( self, message_coordinator_name: str, default_message_coordinator_name: str ) -> MessageCoordinator: - class_name = self._module_config.get_value_or_default( - message_coordinator_name, default_message_coordinator_name - ) + class_name = self._module_config.get_value(message_coordinator_name) + if class_name is None: + self._logger.warning( + f"Channel subscriber {message_coordinator_name} not found in config. Using default channel subscriber {default_message_coordinator_name}" + ) + class_name = default_message_coordinator_name message_coordinator_class: type = self._load_module(class_name) @@ -194,9 +208,12 @@ def get_message_coordinator( def get_command_executor( self, command_executor_name: str, default_command_executor_name: str ) -> CommandExecutor: - class_name = self._module_config.get_value_or_default( - command_executor_name, default_command_executor_name - ) + class_name = self._module_config.get_value(command_executor_name) + if class_name is None: + self._logger.warning( + f"Command executor {command_executor_name} not found in config. Using default command executor {default_command_executor_name}" + ) + class_name = default_command_executor_name command_executor_class: type = self._load_module(class_name) @@ -220,9 +237,12 @@ def get_command_executor( def get_command_picker( self, command_picker_name: str, default_command_picker_name: str ) -> CommandPicker: - class_name = self._module_config.get_value_or_default( - command_picker_name, default_command_picker_name - ) + class_name = self._module_config.get_value(command_picker_name) + if class_name is None: + self._logger.warning( + f"Command picker {command_picker_name} not found in config. Using default command picker {default_command_picker_name}" + ) + class_name = default_command_picker_name command_picker_class: type = self._load_module(class_name) diff --git a/adf_core_python/implement/centralized/default_command_executor_ambulance.py b/adf_core_python/implement/centralized/default_command_executor_ambulance.py index ac29680..adfb80b 100644 --- a/adf_core_python/implement/centralized/default_command_executor_ambulance.py +++ b/adf_core_python/implement/centralized/default_command_executor_ambulance.py @@ -58,11 +58,11 @@ def __init__( ), ) self._action_transport = module_manager.get_extend_action( - "DefaultCommandExecutorAmbulance.ExtActionTransport", + "DefaultCommandExecutorAmbulance.ExtendActionTransport", "adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport", ) self._action_move = module_manager.get_extend_action( - "DefaultCommandExecutorAmbulance.ExtActionMove", + "DefaultCommandExecutorAmbulance.ExtendActionMove", "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", ) diff --git a/adf_core_python/implement/centralized/default_command_executor_fire.py b/adf_core_python/implement/centralized/default_command_executor_fire.py index 680320b..c4fdf64 100644 --- a/adf_core_python/implement/centralized/default_command_executor_fire.py +++ b/adf_core_python/implement/centralized/default_command_executor_fire.py @@ -55,11 +55,11 @@ def __init__( ), ) self._action_fire_rescue = module_manager.get_extend_action( - "DefaultCommandExecutorFire.ExtActionFireRescue", + "DefaultCommandExecutorFire.ExtendActionFireRescue", "adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue", ) self._action_move = module_manager.get_extend_action( - "DefaultCommandExecutorFire.ExtActionMove", + "DefaultCommandExecutorFire.ExtendActionMove", "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", ) diff --git a/adf_core_python/implement/centralized/default_command_executor_police.py b/adf_core_python/implement/centralized/default_command_executor_police.py index 69ad622..b137337 100644 --- a/adf_core_python/implement/centralized/default_command_executor_police.py +++ b/adf_core_python/implement/centralized/default_command_executor_police.py @@ -56,11 +56,11 @@ def __init__( ), ) self._action_clear = module_manager.get_extend_action( - "DefaultCommandExecutorPolice.ExtActionClear", + "DefaultCommandExecutorPolice.ExtendActionClear", "adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear", ) self._action_move = module_manager.get_extend_action( - "DefaultCommandExecutorPolice.ExtActionMove", + "DefaultCommandExecutorPolice.ExtendActionMove", "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", ) diff --git a/adf_core_python/implement/centralized/default_command_executor_scout_police.py b/adf_core_python/implement/centralized/default_command_executor_scout_police.py index ad17d67..e0345a5 100644 --- a/adf_core_python/implement/centralized/default_command_executor_scout_police.py +++ b/adf_core_python/implement/centralized/default_command_executor_scout_police.py @@ -51,7 +51,7 @@ def __init__( ), ) self._action_clear: ExtendAction = module_manager.get_extend_action( - "DefaultCommandExecutorScoutPolice.ExtActionClear", + "DefaultCommandExecutorScoutPolice.ExtendActionClear", "adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear", ) diff --git a/config/module.yaml b/config/module.yaml index 706c82e..7d00fd8 100644 --- a/config/module.yaml +++ b/config/module.yaml @@ -63,7 +63,7 @@ DefaultCommandExecutorAmbulance: DefaultCommandExecutorFire: PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning - EtxActionFireRescue: adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue + ExtendActionFireRescue: adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove DefaultCommandExecutorPolice: From 97cdaf0bde76aa12df01a3049422f6a823344943 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 21 Feb 2025 10:26:18 +0900 Subject: [PATCH 213/249] =?UTF-8?q?feat:=20ADF=E3=82=B3=E3=82=A2Python?= =?UTF-8?q?=E3=83=A2=E3=82=B8=E3=83=A5=E3=83=BC=E3=83=AB=E3=81=AE=E3=83=89?= =?UTF-8?q?=E3=82=AD=E3=83=A5=E3=83=A1=E3=83=B3=E3=83=88=E3=81=AE=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 4 +- docs/build.py | 10 +-- .../adf_core_python/adf_core_python.cli.rst | 21 +++++ ...ore_python.core.agent.action.ambulance.rst | 37 ++++++++ ...f_core_python.core.agent.action.common.rst | 29 +++++++ ...adf_core_python.core.agent.action.fire.rst | 37 ++++++++ ...f_core_python.core.agent.action.police.rst | 29 +++++++ .../adf_core_python.core.agent.action.rst | 32 +++++++ ...f_core_python.core.agent.communication.rst | 29 +++++++ ...munication.standard.bundle.centralized.rst | 53 ++++++++++++ ...munication.standard.bundle.information.rst | 61 +++++++++++++ ...re.agent.communication.standard.bundle.rst | 38 +++++++++ ...thon.core.agent.communication.standard.rst | 30 +++++++ ...e.agent.communication.standard.utility.rst | 29 +++++++ .../adf_core_python.core.agent.config.rst | 21 +++++ .../adf_core_python.core.agent.develop.rst | 21 +++++ .../adf_core_python.core.agent.info.rst | 37 ++++++++ .../adf_core_python.core.agent.module.rst | 21 +++++ .../adf_core_python.core.agent.platoon.rst | 45 ++++++++++ .../adf_core_python.core.agent.precompute.rst | 21 +++++ .../adf_core_python.core.agent.rst | 36 ++++++++ .../adf_core_python.core.component.action.rst | 21 +++++ ...re_python.core.component.communication.rst | 45 ++++++++++ ...python.core.component.module.algorithm.rst | 29 +++++++ ...e_python.core.component.module.complex.rst | 77 +++++++++++++++++ .../adf_core_python.core.component.module.rst | 30 +++++++ .../adf_core_python.core.component.rst | 32 +++++++ ...adf_core_python.core.component.tactics.rst | 77 +++++++++++++++++ .../adf_core_python.core.config.rst | 21 +++++ ...ore.gateway.component.module.algorithm.rst | 29 +++++++ ....core.gateway.component.module.complex.rst | 77 +++++++++++++++++ ...e_python.core.gateway.component.module.rst | 30 +++++++ ...adf_core_python.core.gateway.component.rst | 18 ++++ .../adf_core_python.core.gateway.message.rst | 77 +++++++++++++++++ ...f_core_python.core.gateway.message.urn.rst | 21 +++++ .../adf_core_python.core.gateway.rst | 54 ++++++++++++ .../adf_core_python.core.launcher.connect.rst | 85 +++++++++++++++++++ .../adf_core_python.core.launcher.rst | 37 ++++++++ .../adf_core_python.core.logger.rst | 21 +++++ .../adf_core_python/adf_core_python.core.rst | 23 +++++ .../adf_core_python.implement.action.rst | 45 ++++++++++ ...core_python.implement.module.algorithm.rst | 37 ++++++++ ..._python.implement.module.communication.rst | 29 +++++++ ...f_core_python.implement.module.complex.rst | 61 +++++++++++++ .../adf_core_python.implement.module.rst | 20 +++++ .../adf_core_python.implement.rst | 31 +++++++ .../adf_core_python.implement.tactics.rst | 61 +++++++++++++ .../adf_core_python/adf_core_python.rst | 31 +++++++ docs/source/adf_core_python/modules.rst | 7 ++ 49 files changed, 1760 insertions(+), 7 deletions(-) create mode 100644 docs/source/adf_core_python/adf_core_python.cli.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.action.ambulance.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.action.common.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.action.fire.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.action.police.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.action.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.communication.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.bundle.centralized.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.bundle.information.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.bundle.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.utility.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.config.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.develop.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.info.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.module.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.platoon.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.precompute.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.agent.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.component.action.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.component.communication.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.component.module.algorithm.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.component.module.complex.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.component.module.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.component.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.component.tactics.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.config.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.gateway.component.module.algorithm.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.gateway.component.module.complex.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.gateway.component.module.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.gateway.component.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.gateway.message.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.gateway.message.urn.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.gateway.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.launcher.connect.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.launcher.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.logger.rst create mode 100644 docs/source/adf_core_python/adf_core_python.core.rst create mode 100644 docs/source/adf_core_python/adf_core_python.implement.action.rst create mode 100644 docs/source/adf_core_python/adf_core_python.implement.module.algorithm.rst create mode 100644 docs/source/adf_core_python/adf_core_python.implement.module.communication.rst create mode 100644 docs/source/adf_core_python/adf_core_python.implement.module.complex.rst create mode 100644 docs/source/adf_core_python/adf_core_python.implement.module.rst create mode 100644 docs/source/adf_core_python/adf_core_python.implement.rst create mode 100644 docs/source/adf_core_python/adf_core_python.implement.tactics.rst create mode 100644 docs/source/adf_core_python/adf_core_python.rst create mode 100644 docs/source/adf_core_python/modules.rst diff --git a/docs/README.md b/docs/README.md index f9d0012..4d78f23 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,6 +3,6 @@ ## Generate documentation ```bash -sphinx-apidoc -f -o ./docs/source ./adf_core_python -sphinx-build -M html ./docs/source ./docs/build -a +sphinx-apidoc -f -o ./docs/source/adf_core_python ./adf_core_python +sphinx-build -M html ./docs/source/adf_core_python ./docs/build -a ``` diff --git a/docs/build.py b/docs/build.py index 469f697..920d1d2 100644 --- a/docs/build.py +++ b/docs/build.py @@ -13,9 +13,9 @@ def build_doc(version, language, tag): subprocess.run("git checkout main -- versions.yaml", shell=True) subprocess.run("doxygen Doxyfile", shell=True) os.environ['SPHINXOPTS'] = "-D language='{}'".format(language) - subprocess.run("make html", shell=True) + subprocess.run("make html", shell=True) -# a move dir method because we run multiple builds and bring the html folders to a +# a move dir method because we run multiple builds and bring the html folders to a # location which we then push to github pages def move_dir(src, dst): subprocess.run(["mkdir", "-p", dst]) @@ -23,11 +23,11 @@ def move_dir(src, dst): # to separate a single local build from all builds we have a flag, see conf.py os.environ["build_all_docs"] = str(True) -os.environ["pages_root"] = "https://adf-python.github.io/adf-core-python/" +os.environ["pages_root"] = "https://adf-python.github.io/adf-core-python/" # manually the main branch build in the current supported languages build_doc("latest", "en", "main") -move_dir("./build/html/", "./pages/") +move_dir("./build/html/", "./pages/en") build_doc("latest", "ja", "main") move_dir("./build/html/", "./pages/ja/") @@ -38,6 +38,6 @@ def move_dir(src, dst): # and looping over all values to call our build with version, language and its tag # for version, details in docs.items(): # tag = details.get('tag', '') -# for language in details.get('languages', []): +# for language in details.get('languages', []): # build_doc(version, language, version) # move_dir("./build/html/", "./pages/"+version+'/'+language+'/') diff --git a/docs/source/adf_core_python/adf_core_python.cli.rst b/docs/source/adf_core_python/adf_core_python.cli.rst new file mode 100644 index 0000000..202435d --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.cli.rst @@ -0,0 +1,21 @@ +adf\_core\_python.cli package +============================= + +Submodules +---------- + +adf\_core\_python.cli.cli module +-------------------------------- + +.. automodule:: adf_core_python.cli.cli + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.cli + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.action.ambulance.rst b/docs/source/adf_core_python/adf_core_python.core.agent.action.ambulance.rst new file mode 100644 index 0000000..6850cac --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.action.ambulance.rst @@ -0,0 +1,37 @@ +adf\_core\_python.core.agent.action.ambulance package +===================================================== + +Submodules +---------- + +adf\_core\_python.core.agent.action.ambulance.action\_load module +----------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.action.ambulance.action_load + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.action.ambulance.action\_rescue module +------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.action.ambulance.action_rescue + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.action.ambulance.action\_unload module +------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.action.ambulance.action_unload + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.action.ambulance + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.action.common.rst b/docs/source/adf_core_python/adf_core_python.core.agent.action.common.rst new file mode 100644 index 0000000..28fdba8 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.action.common.rst @@ -0,0 +1,29 @@ +adf\_core\_python.core.agent.action.common package +================================================== + +Submodules +---------- + +adf\_core\_python.core.agent.action.common.action\_move module +-------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.action.common.action_move + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.action.common.action\_rest module +-------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.action.common.action_rest + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.action.common + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.action.fire.rst b/docs/source/adf_core_python/adf_core_python.core.agent.action.fire.rst new file mode 100644 index 0000000..c3fe550 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.action.fire.rst @@ -0,0 +1,37 @@ +adf\_core\_python.core.agent.action.fire package +================================================ + +Submodules +---------- + +adf\_core\_python.core.agent.action.fire.action\_extinguish module +------------------------------------------------------------------ + +.. automodule:: adf_core_python.core.agent.action.fire.action_extinguish + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.action.fire.action\_refill module +-------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.action.fire.action_refill + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.action.fire.action\_rescue module +-------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.action.fire.action_rescue + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.action.fire + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.action.police.rst b/docs/source/adf_core_python/adf_core_python.core.agent.action.police.rst new file mode 100644 index 0000000..c3d899c --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.action.police.rst @@ -0,0 +1,29 @@ +adf\_core\_python.core.agent.action.police package +================================================== + +Submodules +---------- + +adf\_core\_python.core.agent.action.police.action\_clear module +--------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.action.police.action_clear + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.action.police.action\_clear\_area module +--------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.action.police.action_clear_area + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.action.police + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.action.rst b/docs/source/adf_core_python/adf_core_python.core.agent.action.rst new file mode 100644 index 0000000..fbb4898 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.action.rst @@ -0,0 +1,32 @@ +adf\_core\_python.core.agent.action package +=========================================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.core.agent.action.ambulance + adf_core_python.core.agent.action.common + adf_core_python.core.agent.action.fire + adf_core_python.core.agent.action.police + +Submodules +---------- + +adf\_core\_python.core.agent.action.action module +------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.action.action + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.action + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.communication.rst b/docs/source/adf_core_python/adf_core_python.core.agent.communication.rst new file mode 100644 index 0000000..8b8db00 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.communication.rst @@ -0,0 +1,29 @@ +adf\_core\_python.core.agent.communication package +================================================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.core.agent.communication.standard + +Submodules +---------- + +adf\_core\_python.core.agent.communication.message\_manager module +------------------------------------------------------------------ + +.. automodule:: adf_core_python.core.agent.communication.message_manager + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.communication + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.bundle.centralized.rst b/docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.bundle.centralized.rst new file mode 100644 index 0000000..45fa534 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.bundle.centralized.rst @@ -0,0 +1,53 @@ +adf\_core\_python.core.agent.communication.standard.bundle.centralized package +============================================================================== + +Submodules +---------- + +adf\_core\_python.core.agent.communication.standard.bundle.centralized.command\_ambulance module +------------------------------------------------------------------------------------------------ + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.communication.standard.bundle.centralized.command\_fire module +------------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle.centralized.command_fire + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.communication.standard.bundle.centralized.command\_police module +--------------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle.centralized.command_police + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.communication.standard.bundle.centralized.command\_scout module +-------------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.communication.standard.bundle.centralized.message\_report module +--------------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle.centralized.message_report + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle.centralized + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.bundle.information.rst b/docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.bundle.information.rst new file mode 100644 index 0000000..1866f08 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.bundle.information.rst @@ -0,0 +1,61 @@ +adf\_core\_python.core.agent.communication.standard.bundle.information package +============================================================================== + +Submodules +---------- + +adf\_core\_python.core.agent.communication.standard.bundle.information.message\_ambulance\_team module +------------------------------------------------------------------------------------------------------ + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle.information.message_ambulance_team + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.communication.standard.bundle.information.message\_building module +----------------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle.information.message_building + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.communication.standard.bundle.information.message\_civilian module +----------------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle.information.message_civilian + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.communication.standard.bundle.information.message\_fire\_brigade module +---------------------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle.information.message_fire_brigade + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.communication.standard.bundle.information.message\_police\_force module +---------------------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle.information.message_police_force + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.communication.standard.bundle.information.message\_road module +------------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle.information.message_road + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle.information + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.bundle.rst b/docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.bundle.rst new file mode 100644 index 0000000..e5a03c5 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.bundle.rst @@ -0,0 +1,38 @@ +adf\_core\_python.core.agent.communication.standard.bundle package +================================================================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.core.agent.communication.standard.bundle.centralized + adf_core_python.core.agent.communication.standard.bundle.information + +Submodules +---------- + +adf\_core\_python.core.agent.communication.standard.bundle.standard\_message module +----------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle.standard_message + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.communication.standard.bundle.standard\_message\_priority module +--------------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle.standard_message_priority + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.bundle + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.rst b/docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.rst new file mode 100644 index 0000000..d3fd611 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.rst @@ -0,0 +1,30 @@ +adf\_core\_python.core.agent.communication.standard package +=========================================================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.core.agent.communication.standard.bundle + adf_core_python.core.agent.communication.standard.utility + +Submodules +---------- + +adf\_core\_python.core.agent.communication.standard.standard\_communication\_module module +------------------------------------------------------------------------------------------ + +.. automodule:: adf_core_python.core.agent.communication.standard.standard_communication_module + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.communication.standard + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.utility.rst b/docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.utility.rst new file mode 100644 index 0000000..afb37b3 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.communication.standard.utility.rst @@ -0,0 +1,29 @@ +adf\_core\_python.core.agent.communication.standard.utility package +=================================================================== + +Submodules +---------- + +adf\_core\_python.core.agent.communication.standard.utility.apply\_to\_world\_info module +----------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.utility.apply_to_world_info + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.communication.standard.utility.bitarray\_with\_exits\_flag module +---------------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.communication.standard.utility + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.config.rst b/docs/source/adf_core_python/adf_core_python.core.agent.config.rst new file mode 100644 index 0000000..baf652d --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.config.rst @@ -0,0 +1,21 @@ +adf\_core\_python.core.agent.config package +=========================================== + +Submodules +---------- + +adf\_core\_python.core.agent.config.module\_config module +--------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.config.module_config + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.config + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.develop.rst b/docs/source/adf_core_python/adf_core_python.core.agent.develop.rst new file mode 100644 index 0000000..3ab6ca9 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.develop.rst @@ -0,0 +1,21 @@ +adf\_core\_python.core.agent.develop package +============================================ + +Submodules +---------- + +adf\_core\_python.core.agent.develop.develop\_data module +--------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.develop.develop_data + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.develop + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.info.rst b/docs/source/adf_core_python/adf_core_python.core.agent.info.rst new file mode 100644 index 0000000..9bf3a65 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.info.rst @@ -0,0 +1,37 @@ +adf\_core\_python.core.agent.info package +========================================= + +Submodules +---------- + +adf\_core\_python.core.agent.info.agent\_info module +---------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.info.agent_info + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.info.scenario\_info module +------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.info.scenario_info + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.info.world\_info module +---------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.info.world_info + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.info + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.module.rst b/docs/source/adf_core_python/adf_core_python.core.agent.module.rst new file mode 100644 index 0000000..a971149 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.module.rst @@ -0,0 +1,21 @@ +adf\_core\_python.core.agent.module package +=========================================== + +Submodules +---------- + +adf\_core\_python.core.agent.module.module\_manager module +---------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.module.module_manager + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.module + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.platoon.rst b/docs/source/adf_core_python/adf_core_python.core.agent.platoon.rst new file mode 100644 index 0000000..ce60c8f --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.platoon.rst @@ -0,0 +1,45 @@ +adf\_core\_python.core.agent.platoon package +============================================ + +Submodules +---------- + +adf\_core\_python.core.agent.platoon.platoon module +--------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.platoon.platoon + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.platoon.platoon\_ambulance module +-------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.platoon.platoon_ambulance + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.platoon.platoon\_fire module +--------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.platoon.platoon_fire + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.agent.platoon.platoon\_police module +----------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.platoon.platoon_police + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.platoon + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.precompute.rst b/docs/source/adf_core_python/adf_core_python.core.agent.precompute.rst new file mode 100644 index 0000000..1e1c752 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.precompute.rst @@ -0,0 +1,21 @@ +adf\_core\_python.core.agent.precompute package +=============================================== + +Submodules +---------- + +adf\_core\_python.core.agent.precompute.precompute\_data module +--------------------------------------------------------------- + +.. automodule:: adf_core_python.core.agent.precompute.precompute_data + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent.precompute + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.agent.rst b/docs/source/adf_core_python/adf_core_python.core.agent.rst new file mode 100644 index 0000000..c62ed6d --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.agent.rst @@ -0,0 +1,36 @@ +adf\_core\_python.core.agent package +==================================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.core.agent.action + adf_core_python.core.agent.communication + adf_core_python.core.agent.config + adf_core_python.core.agent.develop + adf_core_python.core.agent.info + adf_core_python.core.agent.module + adf_core_python.core.agent.platoon + adf_core_python.core.agent.precompute + +Submodules +---------- + +adf\_core\_python.core.agent.agent module +----------------------------------------- + +.. automodule:: adf_core_python.core.agent.agent + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.agent + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.component.action.rst b/docs/source/adf_core_python/adf_core_python.core.component.action.rst new file mode 100644 index 0000000..41a180b --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.component.action.rst @@ -0,0 +1,21 @@ +adf\_core\_python.core.component.action package +=============================================== + +Submodules +---------- + +adf\_core\_python.core.component.action.extend\_action module +------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.action.extend_action + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.component.action + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.component.communication.rst b/docs/source/adf_core_python/adf_core_python.core.component.communication.rst new file mode 100644 index 0000000..92879f4 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.component.communication.rst @@ -0,0 +1,45 @@ +adf\_core\_python.core.component.communication package +====================================================== + +Submodules +---------- + +adf\_core\_python.core.component.communication.channel\_subscriber module +------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.communication.channel_subscriber + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.communication.communication\_message module +---------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.communication.communication_message + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.communication.communication\_module module +--------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.communication.communication_module + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.communication.message\_coordinator module +-------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.communication.message_coordinator + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.component.communication + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.component.module.algorithm.rst b/docs/source/adf_core_python/adf_core_python.core.component.module.algorithm.rst new file mode 100644 index 0000000..6ad25d3 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.component.module.algorithm.rst @@ -0,0 +1,29 @@ +adf\_core\_python.core.component.module.algorithm package +========================================================= + +Submodules +---------- + +adf\_core\_python.core.component.module.algorithm.clustering module +------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.module.algorithm.clustering + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.module.algorithm.path\_planning module +----------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.module.algorithm.path_planning + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.component.module.algorithm + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.component.module.complex.rst b/docs/source/adf_core_python/adf_core_python.core.component.module.complex.rst new file mode 100644 index 0000000..86de2b8 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.component.module.complex.rst @@ -0,0 +1,77 @@ +adf\_core\_python.core.component.module.complex package +======================================================= + +Submodules +---------- + +adf\_core\_python.core.component.module.complex.ambulance\_target\_allocator module +----------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.module.complex.ambulance_target_allocator + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.module.complex.fire\_target\_allocator module +------------------------------------------------------------------------------ + +.. automodule:: adf_core_python.core.component.module.complex.fire_target_allocator + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.module.complex.human\_detector module +---------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.module.complex.human_detector + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.module.complex.police\_target\_allocator module +-------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.module.complex.police_target_allocator + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.module.complex.road\_detector module +--------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.module.complex.road_detector + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.module.complex.search module +------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.module.complex.search + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.module.complex.target\_allocator module +------------------------------------------------------------------------ + +.. automodule:: adf_core_python.core.component.module.complex.target_allocator + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.module.complex.target\_detector module +----------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.module.complex.target_detector + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.component.module.complex + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.component.module.rst b/docs/source/adf_core_python/adf_core_python.core.component.module.rst new file mode 100644 index 0000000..f07feac --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.component.module.rst @@ -0,0 +1,30 @@ +adf\_core\_python.core.component.module package +=============================================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.core.component.module.algorithm + adf_core_python.core.component.module.complex + +Submodules +---------- + +adf\_core\_python.core.component.module.abstract\_module module +--------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.module.abstract_module + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.component.module + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.component.rst b/docs/source/adf_core_python/adf_core_python.core.component.rst new file mode 100644 index 0000000..c33224e --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.component.rst @@ -0,0 +1,32 @@ +adf\_core\_python.core.component package +======================================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.core.component.action + adf_core_python.core.component.communication + adf_core_python.core.component.module + adf_core_python.core.component.tactics + +Submodules +---------- + +adf\_core\_python.core.component.abstract\_loader module +-------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.abstract_loader + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.component + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.component.tactics.rst b/docs/source/adf_core_python/adf_core_python.core.component.tactics.rst new file mode 100644 index 0000000..d640551 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.component.tactics.rst @@ -0,0 +1,77 @@ +adf\_core\_python.core.component.tactics package +================================================ + +Submodules +---------- + +adf\_core\_python.core.component.tactics.tactics\_agent module +-------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.tactics.tactics_agent + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.tactics.tactics\_ambulance\_center module +-------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.tactics.tactics_ambulance_center + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.tactics.tactics\_ambulance\_team module +------------------------------------------------------------------------ + +.. automodule:: adf_core_python.core.component.tactics.tactics_ambulance_team + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.tactics.tactics\_center module +--------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.tactics.tactics_center + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.tactics.tactics\_fire\_brigade module +---------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.tactics.tactics_fire_brigade + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.tactics.tactics\_fire\_station module +---------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.tactics.tactics_fire_station + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.tactics.tactics\_police\_force module +---------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.tactics.tactics_police_force + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.component.tactics.tactics\_police\_office module +----------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.component.tactics.tactics_police_office + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.component.tactics + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.config.rst b/docs/source/adf_core_python/adf_core_python.core.config.rst new file mode 100644 index 0000000..5806b37 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.config.rst @@ -0,0 +1,21 @@ +adf\_core\_python.core.config package +===================================== + +Submodules +---------- + +adf\_core\_python.core.config.config module +------------------------------------------- + +.. automodule:: adf_core_python.core.config.config + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.config + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.gateway.component.module.algorithm.rst b/docs/source/adf_core_python/adf_core_python.core.gateway.component.module.algorithm.rst new file mode 100644 index 0000000..2197aef --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.gateway.component.module.algorithm.rst @@ -0,0 +1,29 @@ +adf\_core\_python.core.gateway.component.module.algorithm package +================================================================= + +Submodules +---------- + +adf\_core\_python.core.gateway.component.module.algorithm.gateway\_clustering module +------------------------------------------------------------------------------------ + +.. automodule:: adf_core_python.core.gateway.component.module.algorithm.gateway_clustering + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.component.module.algorithm.gateway\_path\_planning module +---------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.component.module.algorithm.gateway_path_planning + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.gateway.component.module.algorithm + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.gateway.component.module.complex.rst b/docs/source/adf_core_python/adf_core_python.core.gateway.component.module.complex.rst new file mode 100644 index 0000000..819f4ac --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.gateway.component.module.complex.rst @@ -0,0 +1,77 @@ +adf\_core\_python.core.gateway.component.module.complex package +=============================================================== + +Submodules +---------- + +adf\_core\_python.core.gateway.component.module.complex.gateway\_ambulance\_target\_allocator module +---------------------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.component.module.complex.gateway_ambulance_target_allocator + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.component.module.complex.gateway\_fire\_target\_allocator module +----------------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.component.module.complex.gateway_fire_target_allocator + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.component.module.complex.gateway\_human\_detector module +--------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.component.module.complex.gateway_human_detector + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.component.module.complex.gateway\_police\_target\_allocator module +------------------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.component.module.complex.gateway_police_target_allocator + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.component.module.complex.gateway\_road\_detector module +-------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.component.module.complex.gateway_road_detector + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.component.module.complex.gateway\_search module +------------------------------------------------------------------------------ + +.. automodule:: adf_core_python.core.gateway.component.module.complex.gateway_search + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.component.module.complex.gateway\_target\_allocator module +----------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.component.module.complex.gateway_target_allocator + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.component.module.complex.gateway\_target\_detector module +---------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.component.module.complex.gateway_target_detector + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.gateway.component.module.complex + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.gateway.component.module.rst b/docs/source/adf_core_python/adf_core_python.core.gateway.component.module.rst new file mode 100644 index 0000000..4d69a96 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.gateway.component.module.rst @@ -0,0 +1,30 @@ +adf\_core\_python.core.gateway.component.module package +======================================================= + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.core.gateway.component.module.algorithm + adf_core_python.core.gateway.component.module.complex + +Submodules +---------- + +adf\_core\_python.core.gateway.component.module.gateway\_abstract\_module module +-------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.component.module.gateway_abstract_module + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.gateway.component.module + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.gateway.component.rst b/docs/source/adf_core_python/adf_core_python.core.gateway.component.rst new file mode 100644 index 0000000..29a66e8 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.gateway.component.rst @@ -0,0 +1,18 @@ +adf\_core\_python.core.gateway.component package +================================================ + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.core.gateway.component.module + +Module contents +--------------- + +.. automodule:: adf_core_python.core.gateway.component + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.gateway.message.rst b/docs/source/adf_core_python/adf_core_python.core.gateway.message.rst new file mode 100644 index 0000000..265575a --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.gateway.message.rst @@ -0,0 +1,77 @@ +adf\_core\_python.core.gateway.message package +============================================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.core.gateway.message.urn + +Submodules +---------- + +adf\_core\_python.core.gateway.message.am\_agent module +------------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.message.am_agent + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.message.am\_exec module +------------------------------------------------------ + +.. automodule:: adf_core_python.core.gateway.message.am_exec + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.message.am\_module module +-------------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.message.am_module + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.message.am\_update module +-------------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.message.am_update + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.message.ma\_exec\_response module +---------------------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.message.ma_exec_response + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.message.ma\_module\_response module +------------------------------------------------------------------ + +.. automodule:: adf_core_python.core.gateway.message.ma_module_response + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.message.moduleMessageFactory module +------------------------------------------------------------------ + +.. automodule:: adf_core_python.core.gateway.message.moduleMessageFactory + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.gateway.message + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.gateway.message.urn.rst b/docs/source/adf_core_python/adf_core_python.core.gateway.message.urn.rst new file mode 100644 index 0000000..fa3e1e8 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.gateway.message.urn.rst @@ -0,0 +1,21 @@ +adf\_core\_python.core.gateway.message.urn package +================================================== + +Submodules +---------- + +adf\_core\_python.core.gateway.message.urn.urn module +----------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.message.urn.urn + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.gateway.message.urn + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.gateway.rst b/docs/source/adf_core_python/adf_core_python.core.gateway.rst new file mode 100644 index 0000000..ca47556 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.gateway.rst @@ -0,0 +1,54 @@ +adf\_core\_python.core.gateway package +====================================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.core.gateway.component + adf_core_python.core.gateway.message + +Submodules +---------- + +adf\_core\_python.core.gateway.gateway\_agent module +---------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.gateway_agent + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.gateway\_launcher module +------------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.gateway_launcher + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.gateway\_module module +----------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.gateway_module + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.gateway.module\_dict module +-------------------------------------------------- + +.. automodule:: adf_core_python.core.gateway.module_dict + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.gateway + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.launcher.connect.rst b/docs/source/adf_core_python/adf_core_python.core.launcher.connect.rst new file mode 100644 index 0000000..63501ff --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.launcher.connect.rst @@ -0,0 +1,85 @@ +adf\_core\_python.core.launcher.connect package +=============================================== + +Submodules +---------- + +adf\_core\_python.core.launcher.connect.component\_launcher module +------------------------------------------------------------------ + +.. automodule:: adf_core_python.core.launcher.connect.component_launcher + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.launcher.connect.connection module +--------------------------------------------------------- + +.. automodule:: adf_core_python.core.launcher.connect.connection + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.launcher.connect.connector module +-------------------------------------------------------- + +.. automodule:: adf_core_python.core.launcher.connect.connector + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.launcher.connect.connector\_ambulance\_center module +--------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.launcher.connect.connector_ambulance_center + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.launcher.connect.connector\_ambulance\_team module +------------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.launcher.connect.connector_ambulance_team + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.launcher.connect.connector\_fire\_brigade module +----------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.launcher.connect.connector_fire_brigade + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.launcher.connect.connector\_fire\_station module +----------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.launcher.connect.connector_fire_station + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.launcher.connect.connector\_police\_force module +----------------------------------------------------------------------- + +.. automodule:: adf_core_python.core.launcher.connect.connector_police_force + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.launcher.connect.connector\_police\_office module +------------------------------------------------------------------------ + +.. automodule:: adf_core_python.core.launcher.connect.connector_police_office + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.launcher.connect + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.launcher.rst b/docs/source/adf_core_python/adf_core_python.core.launcher.rst new file mode 100644 index 0000000..e93773b --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.launcher.rst @@ -0,0 +1,37 @@ +adf\_core\_python.core.launcher package +======================================= + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.core.launcher.connect + +Submodules +---------- + +adf\_core\_python.core.launcher.agent\_launcher module +------------------------------------------------------ + +.. automodule:: adf_core_python.core.launcher.agent_launcher + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.core.launcher.config\_key module +-------------------------------------------------- + +.. automodule:: adf_core_python.core.launcher.config_key + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.launcher + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.logger.rst b/docs/source/adf_core_python/adf_core_python.core.logger.rst new file mode 100644 index 0000000..a0bf1f5 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.logger.rst @@ -0,0 +1,21 @@ +adf\_core\_python.core.logger package +===================================== + +Submodules +---------- + +adf\_core\_python.core.logger.logger module +------------------------------------------- + +.. automodule:: adf_core_python.core.logger.logger + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.core.logger + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.core.rst b/docs/source/adf_core_python/adf_core_python.core.rst new file mode 100644 index 0000000..78f4955 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.core.rst @@ -0,0 +1,23 @@ +adf\_core\_python.core package +============================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.core.agent + adf_core_python.core.component + adf_core_python.core.config + adf_core_python.core.gateway + adf_core_python.core.launcher + adf_core_python.core.logger + +Module contents +--------------- + +.. automodule:: adf_core_python.core + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.implement.action.rst b/docs/source/adf_core_python/adf_core_python.implement.action.rst new file mode 100644 index 0000000..e26c2e6 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.implement.action.rst @@ -0,0 +1,45 @@ +adf\_core\_python.implement.action package +========================================== + +Submodules +---------- + +adf\_core\_python.implement.action.default\_extend\_action\_clear module +------------------------------------------------------------------------ + +.. automodule:: adf_core_python.implement.action.default_extend_action_clear + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.action.default\_extend\_action\_move module +----------------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.action.default_extend_action_move + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.action.default\_extend\_action\_rescue module +------------------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.action.default_extend_action_rescue + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.action.default\_extend\_action\_transport module +---------------------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.action.default_extend_action_transport + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.implement.action + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.implement.module.algorithm.rst b/docs/source/adf_core_python/adf_core_python.implement.module.algorithm.rst new file mode 100644 index 0000000..ce3486d --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.implement.module.algorithm.rst @@ -0,0 +1,37 @@ +adf\_core\_python.implement.module.algorithm package +==================================================== + +Submodules +---------- + +adf\_core\_python.implement.module.algorithm.a\_star\_path\_planning module +--------------------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.module.algorithm.a_star_path_planning + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.module.algorithm.dijkstra\_path\_planning module +---------------------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.module.algorithm.dijkstra_path_planning + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.module.algorithm.k\_means\_clustering module +------------------------------------------------------------------------ + +.. automodule:: adf_core_python.implement.module.algorithm.k_means_clustering + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.implement.module.algorithm + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.implement.module.communication.rst b/docs/source/adf_core_python/adf_core_python.implement.module.communication.rst new file mode 100644 index 0000000..bca3100 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.implement.module.communication.rst @@ -0,0 +1,29 @@ +adf\_core\_python.implement.module.communication package +======================================================== + +Submodules +---------- + +adf\_core\_python.implement.module.communication.default\_channel\_subscriber module +------------------------------------------------------------------------------------ + +.. automodule:: adf_core_python.implement.module.communication.default_channel_subscriber + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.module.communication.default\_message\_coordinator module +------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.module.communication.default_message_coordinator + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.implement.module.communication + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.implement.module.complex.rst b/docs/source/adf_core_python/adf_core_python.implement.module.complex.rst new file mode 100644 index 0000000..7c28aa1 --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.implement.module.complex.rst @@ -0,0 +1,61 @@ +adf\_core\_python.implement.module.complex package +================================================== + +Submodules +---------- + +adf\_core\_python.implement.module.complex.default\_ambulance\_target\_allocator module +--------------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.module.complex.default_ambulance_target_allocator + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.module.complex.default\_fire\_target\_allocator module +---------------------------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.module.complex.default_fire_target_allocator + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.module.complex.default\_human\_detector module +-------------------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.module.complex.default_human_detector + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.module.complex.default\_police\_target\_allocator module +------------------------------------------------------------------------------------ + +.. automodule:: adf_core_python.implement.module.complex.default_police_target_allocator + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.module.complex.default\_road\_detector module +------------------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.module.complex.default_road_detector + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.module.complex.default\_search module +----------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.module.complex.default_search + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.implement.module.complex + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.implement.module.rst b/docs/source/adf_core_python/adf_core_python.implement.module.rst new file mode 100644 index 0000000..12058ce --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.implement.module.rst @@ -0,0 +1,20 @@ +adf\_core\_python.implement.module package +========================================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.implement.module.algorithm + adf_core_python.implement.module.communication + adf_core_python.implement.module.complex + +Module contents +--------------- + +.. automodule:: adf_core_python.implement.module + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.implement.rst b/docs/source/adf_core_python/adf_core_python.implement.rst new file mode 100644 index 0000000..83e213c --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.implement.rst @@ -0,0 +1,31 @@ +adf\_core\_python.implement package +=================================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.implement.action + adf_core_python.implement.module + adf_core_python.implement.tactics + +Submodules +---------- + +adf\_core\_python.implement.default\_loader module +-------------------------------------------------- + +.. automodule:: adf_core_python.implement.default_loader + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.implement + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.implement.tactics.rst b/docs/source/adf_core_python/adf_core_python.implement.tactics.rst new file mode 100644 index 0000000..8076aea --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.implement.tactics.rst @@ -0,0 +1,61 @@ +adf\_core\_python.implement.tactics package +=========================================== + +Submodules +---------- + +adf\_core\_python.implement.tactics.default\_tactics\_ambulance\_center module +------------------------------------------------------------------------------ + +.. automodule:: adf_core_python.implement.tactics.default_tactics_ambulance_center + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.tactics.default\_tactics\_ambulance\_team module +---------------------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.tactics.default_tactics_ambulance_team + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.tactics.default\_tactics\_fire\_brigade module +-------------------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.tactics.default_tactics_fire_brigade + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.tactics.default\_tactics\_fire\_station module +-------------------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.tactics.default_tactics_fire_station + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.tactics.default\_tactics\_police\_force module +-------------------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.tactics.default_tactics_police_force + :members: + :undoc-members: + :show-inheritance: + +adf\_core\_python.implement.tactics.default\_tactics\_police\_office module +--------------------------------------------------------------------------- + +.. automodule:: adf_core_python.implement.tactics.default_tactics_police_office + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python.implement.tactics + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/adf_core_python.rst b/docs/source/adf_core_python/adf_core_python.rst new file mode 100644 index 0000000..774319a --- /dev/null +++ b/docs/source/adf_core_python/adf_core_python.rst @@ -0,0 +1,31 @@ +adf\_core\_python package +========================= + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + adf_core_python.cli + adf_core_python.core + adf_core_python.implement + +Submodules +---------- + +adf\_core\_python.launcher module +--------------------------------- + +.. automodule:: adf_core_python.launcher + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: adf_core_python + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/adf_core_python/modules.rst b/docs/source/adf_core_python/modules.rst new file mode 100644 index 0000000..d0160b5 --- /dev/null +++ b/docs/source/adf_core_python/modules.rst @@ -0,0 +1,7 @@ +adf_core_python +=============== + +.. toctree:: + :maxdepth: 4 + + adf_core_python From 8fbb175234f6ef8c2f65fa5a2d1b2e98a4036389 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 21 Feb 2025 10:36:53 +0900 Subject: [PATCH 214/249] =?UTF-8?q?feat:=20=E3=83=89=E3=82=AD=E3=83=A5?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E3=83=AF=E3=83=BC=E3=82=AF=E3=83=95?= =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=81=AE=E3=83=91=E3=82=B9=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E3=81=97=E3=80=81=E8=8B=B1=E8=AA=9E=E3=83=89=E3=82=AD?= =?UTF-8?q?=E3=83=A5=E3=83=A1=E3=83=B3=E3=83=88=E3=81=B8=E3=81=AE=E3=83=AA?= =?UTF-8?q?=E3=83=80=E3=82=A4=E3=83=AC=E3=82=AF=E3=83=88=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/document.yaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/document.yaml b/.github/workflows/document.yaml index b1b958a..986bc11 100644 --- a/.github/workflows/document.yaml +++ b/.github/workflows/document.yaml @@ -7,16 +7,16 @@ on: - main - develop paths: - - 'docs/**' + - "docs/**" pull_request: branches: - main - develop paths: - - 'docs/**' + - "docs/**" concurrency: - group: 'pages' + group: "pages" cancel-in-progress: true permissions: @@ -57,6 +57,12 @@ jobs: cd docs python build.py + - name: Redirect to the en documentation + run: | + username=$(echo $GITHUB_REPOSITORY | cut -d '/' -f 1) + repository=$(echo $GITHUB_REPOSITORY | cut -d '/' -f 2) + echo '' > ./docs/pages/index.html + - name: Deploy documentation uses: peaceiris/actions-gh-pages@v4 with: From 0bece13a31eef7c8ddb21fe70ec6c571cb3b6b70 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 21 Feb 2025 11:42:39 +0900 Subject: [PATCH 215/249] =?UTF-8?q?feat:=20=E3=83=89=E3=82=AD=E3=83=A5?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E3=81=AE=E3=83=90=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E8=A1=A8=E7=A4=BA=E3=82=92=E6=94=B9=E5=96=84?= =?UTF-8?q?=E3=81=97=E3=80=81=E3=83=86=E3=83=BC=E3=83=9E=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/source/_templates/versions.html | 91 ++++++++++++++++++++++++---- docs/source/conf.py | 13 ++-- docs/source/index.rst | 1 - 3 files changed, 87 insertions(+), 18 deletions(-) diff --git a/docs/source/_templates/versions.html b/docs/source/_templates/versions.html index 7b7d0bc..0b3a1c8 100644 --- a/docs/source/_templates/versions.html +++ b/docs/source/_templates/versions.html @@ -1,10 +1,59 @@ + +
    - + Language: {{ current_language }} @@ -12,20 +61,36 @@ {% if languages|length >= 1 %}
    {{ _('Languages') }}
    - {% for the_language, url in languages %} -
    {{ the_language }}
    - {% endfor %} +
    + {% for the_language, url in languages %} + {{ the_language }} + {% endfor %} +
    {% endif %} +
    +
    {{ _('Versions') }}
    + {% for the_version, url in versions %} +
    {{ the_version }}
    + {% endfor %} +
    + {% endif %} +
    + -->
    + + diff --git a/docs/source/conf.py b/docs/source/conf.py index 38604f8..fc0ea64 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -37,7 +37,12 @@ # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = "sphinx_rtd_theme" +html_theme = "sphinx_book_theme" + +html_sidebars = { + "**": ["navbar-logo.html", "versions.html", "search-button-field.html", "sbt-sidebar-nav.html"] +} + html_static_path = ["_static"] locale_dirs = ["locale"] @@ -53,7 +58,7 @@ current_language = os.environ.get("current_language") current_version = os.environ.get("current_version") - # we set the html_context wit current language and version + # we set the html_context wit current language and version # and empty languages and versions for now html_context = { 'current_language' : current_language, @@ -63,8 +68,8 @@ } - # and we append all versions and langauges accordingly - # we treat t he main branch as latest + # and we append all versions and langauges accordingly + # we treat t he main branch as latest if (current_version == 'latest'): html_context['languages'].append(['en', pages_root]) html_context['languages'].append(['ja', pages_root+'/ja']) diff --git a/docs/source/index.rst b/docs/source/index.rst index bca95b1..e10fb8d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -71,7 +71,6 @@ adf-core-pythonを始めるには、インストールに従い、このドキ :maxdepth: 1 :caption: APIドキュメント - genindex modindex search From 9d30da85f931845ffa986e2136c008200950e886 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 21 Feb 2025 11:46:52 +0900 Subject: [PATCH 216/249] update: jinja2 3.1.4 -> 3.1.5 --- poetry.lock | 96 +++++++++++++++++++++++++++++++++++++++++++------- pyproject.toml | 1 + 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7064ae6..26d42bc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "accessible-pygments" @@ -6,6 +6,7 @@ version = "0.0.5" description = "A collection of accessible pygments styles" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7"}, {file = "accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872"}, @@ -24,6 +25,7 @@ version = "1.0.0" description = "A light, configurable Sphinx theme" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"}, {file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"}, @@ -35,6 +37,7 @@ version = "2.16.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, @@ -49,6 +52,7 @@ version = "4.12.3" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" +groups = ["dev"] files = [ {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, @@ -70,6 +74,7 @@ version = "3.0.0" description = "efficient arrays of booleans -- C extension" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "bitarray-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5ddbf71a97ad1d6252e6e93d2d703b624d0a5b77c153b12f9ea87d83e1250e0c"}, {file = "bitarray-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0e7f24a0b01e6e6a0191c50b06ca8edfdec1988d9d2b264d669d2487f4f4680"}, @@ -216,6 +221,7 @@ version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, @@ -227,6 +233,7 @@ version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" +groups = ["dev"] files = [ {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, @@ -341,6 +348,7 @@ version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, @@ -355,10 +363,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\""} [[package]] name = "docutils" @@ -366,6 +376,7 @@ version = "0.21.2" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, @@ -377,6 +388,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -391,6 +403,7 @@ version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["dev"] files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, @@ -402,6 +415,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -409,13 +423,14 @@ files = [ [[package]] name = "jinja2" -version = "3.1.4" +version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, + {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, + {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, ] [package.dependencies] @@ -430,6 +445,7 @@ version = "1.4.2" description = "Lightweight pipelining with Python functions" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, @@ -441,6 +457,7 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -465,6 +482,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -535,6 +553,7 @@ version = "0.4.2" description = "Collection of plugins for markdown-it-py" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, @@ -554,6 +573,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -565,6 +585,8 @@ version = "1.3.0" description = "shlex for windows" optional = false python-versions = ">=3.5" +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "mslex-1.3.0-py3-none-any.whl", hash = "sha256:c7074b347201b3466fc077c5692fbce9b5f62a63a51f537a53fbbd02eff2eea4"}, {file = "mslex-1.3.0.tar.gz", hash = "sha256:641c887d1d3db610eee2af37a8e5abda3f70b3006cdfd2d0d29dc0d1ae28a85d"}, @@ -576,6 +598,7 @@ version = "1.13.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, @@ -628,6 +651,7 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -639,6 +663,7 @@ version = "4.0.0" description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "myst_parser-4.0.0-py3-none-any.whl", hash = "sha256:b9317997552424448c6096c2558872fdb6f81d3ecb3a40ce84a7518798f3f28d"}, {file = "myst_parser-4.0.0.tar.gz", hash = "sha256:851c9dfb44e36e56d15d05e72f02b80da21a9e0d07cba96baf5e2d476bb91531"}, @@ -665,6 +690,7 @@ version = "2.1.3" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.10" +groups = ["main"] files = [ {file = "numpy-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff"}, {file = "numpy-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5"}, @@ -729,6 +755,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -740,6 +767,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -755,6 +783,7 @@ version = "5.29.0" description = "" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "protobuf-5.29.0-cp310-abi3-win32.whl", hash = "sha256:ea7fb379b257911c8c020688d455e8f74efd2f734b72dc1ea4b4d7e9fd1326f2"}, {file = "protobuf-5.29.0-cp310-abi3-win_amd64.whl", hash = "sha256:34a90cf30c908f47f40ebea7811f743d360e202b6f10d40c02529ebd84afc069"}, @@ -775,6 +804,7 @@ version = "6.1.0" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["dev"] files = [ {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, @@ -805,6 +835,7 @@ version = "0.16.0" description = "Bootstrap-based Sphinx theme from the PyData community" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pydata_sphinx_theme-0.16.0-py3-none-any.whl", hash = "sha256:18c810ee4e67e05281e371e156c1fb5bb0fa1f2747240461b225272f7d8d57d8"}, {file = "pydata_sphinx_theme-0.16.0.tar.gz", hash = "sha256:721dd26e05fa8b992d66ef545536e6cbe0110afb9865820a08894af1ad6f7707"}, @@ -832,6 +863,7 @@ version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, @@ -846,6 +878,7 @@ version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, @@ -866,6 +899,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -928,6 +962,7 @@ version = "0.1.0" description = "RoboCup Rescue Simulation(RCRS Agent Development Library)" optional = false python-versions = "*" +groups = ["main"] files = [] develop = false @@ -947,6 +982,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -968,6 +1004,7 @@ version = "1.3.0" description = "R-Tree spatial index for Python GIS" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "Rtree-1.3.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:80879d9db282a2273ca3a0d896c84583940e9777477727a277624ebfd424c517"}, {file = "Rtree-1.3.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4328e9e421797c347e6eb08efbbade962fe3664ebd60c1dffe82c40911b1e125"}, @@ -987,6 +1024,7 @@ version = "0.4.10" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"}, {file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"}, @@ -1013,6 +1051,7 @@ version = "1.5.2" description = "A set of python modules for machine learning and data mining" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "scikit_learn-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:299406827fb9a4f862626d0fe6c122f5f87f8910b86fe5daa4c32dcd742139b6"}, {file = "scikit_learn-1.5.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:2d4cad1119c77930b235579ad0dc25e65c917e756fe80cab96aa3b9428bd3fb0"}, @@ -1029,6 +1068,11 @@ files = [ {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f60021ec1574e56632be2a36b946f8143bf4e5e6af4a06d85281adc22938e0dd"}, {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:394397841449853c2290a32050382edaec3da89e35b3e03d6cc966aebc6a8ae6"}, {file = "scikit_learn-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:57cc1786cfd6bd118220a92ede80270132aa353647684efa385a74244a41e3b1"}, + {file = "scikit_learn-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9a702e2de732bbb20d3bad29ebd77fc05a6b427dc49964300340e4c9328b3f5"}, + {file = "scikit_learn-1.5.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:b0768ad641981f5d3a198430a1d31c3e044ed2e8a6f22166b4d546a5116d7908"}, + {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:178ddd0a5cb0044464fc1bfc4cca5b1833bfc7bb022d70b05db8530da4bb3dd3"}, + {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7284ade780084d94505632241bf78c44ab3b6f1e8ccab3d2af58e0e950f9c12"}, + {file = "scikit_learn-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:b7b0f9a0b1040830d38c39b91b3a44e1b643f4b36e36567b80b7c6bd2202a27f"}, {file = "scikit_learn-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:757c7d514ddb00ae249832fe87100d9c73c6ea91423802872d9e74970a0e40b9"}, {file = "scikit_learn-1.5.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:52788f48b5d8bca5c0736c175fa6bdaab2ef00a8f536cda698db61bd89c551c1"}, {file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:643964678f4b5fbdc95cbf8aec638acc7aa70f5f79ee2cdad1eec3df4ba6ead8"}, @@ -1058,6 +1102,7 @@ version = "1.14.1" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.10" +groups = ["main"] files = [ {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, @@ -1100,7 +1145,7 @@ numpy = ">=1.23.5,<2.3" [package.extras] dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "setuptools" @@ -1108,19 +1153,20 @@ version = "75.6.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"}, {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"] -core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.7.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (>=1.12,<1.14)", "pytest-mypy"] [[package]] name = "shapely" @@ -1128,6 +1174,7 @@ version = "2.0.6" description = "Manipulation and analysis of geometric objects" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "shapely-2.0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29a34e068da2d321e926b5073539fd2a1d4429a2c656bd63f0bd4c8f5b236d0b"}, {file = "shapely-2.0.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c84c3f53144febf6af909d6b581bc05e8785d57e27f35ebaa5c1ab9baba13b"}, @@ -1186,6 +1233,7 @@ version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, @@ -1197,6 +1245,7 @@ version = "2.6" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, @@ -1208,6 +1257,7 @@ version = "8.1.3" description = "Python documentation generator" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2"}, {file = "sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927"}, @@ -1242,6 +1292,7 @@ version = "1.1.3" description = "A clean book theme for scientific explanations and documentation with Sphinx" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "sphinx_book_theme-1.1.3-py3-none-any.whl", hash = "sha256:a554a9a7ac3881979a87a2b10f633aa2a5706e72218a10f71be38b3c9e831ae9"}, {file = "sphinx_book_theme-1.1.3.tar.gz", hash = "sha256:1f25483b1846cb3d353a6bc61b3b45b031f4acf845665d7da90e01ae0aef5b4d"}, @@ -1262,6 +1313,7 @@ version = "0.5.2" description = "Add a copy button to each of your code cells." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"}, {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"}, @@ -1280,6 +1332,7 @@ version = "2.3.1" description = "Sphinx utility that make it easy to translate and to apply translation." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "sphinx_intl-2.3.1-py3-none-any.whl", hash = "sha256:e3f4be80743e0e4c778fbc07e72b062c13c88c50645a69b62557a3f57a24b7fc"}, {file = "sphinx_intl-2.3.1.tar.gz", hash = "sha256:e2d04b907f57a029d46b4da344fd791e660d935eab6ced8a274fc65446389af1"}, @@ -1300,6 +1353,7 @@ version = "3.0.2" description = "Read the Docs theme for Sphinx" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13"}, {file = "sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85"}, @@ -1319,6 +1373,7 @@ version = "0.3.2" description = "Toggle page content and collapse admonitions in Sphinx." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "sphinx-togglebutton-0.3.2.tar.gz", hash = "sha256:ab0c8b366427b01e4c89802d5d078472c427fa6e9d12d521c34fa0442559dc7a"}, {file = "sphinx_togglebutton-0.3.2-py3-none-any.whl", hash = "sha256:9647ba7874b7d1e2d43413d8497153a85edc6ac95a3fea9a75ef9c1e08aaae2b"}, @@ -1339,6 +1394,7 @@ version = "2.0.0" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, @@ -1355,6 +1411,7 @@ version = "2.0.0" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, @@ -1371,6 +1428,7 @@ version = "2.1.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, @@ -1387,6 +1445,7 @@ version = "4.1" description = "Extension to include jQuery on newer Sphinx releases" optional = false python-versions = ">=2.7" +groups = ["dev"] files = [ {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, @@ -1401,6 +1460,7 @@ version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, @@ -1415,6 +1475,7 @@ version = "1.0.0" description = "Mermaid diagrams in yours Sphinx powered docs" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "sphinxcontrib_mermaid-1.0.0-py3-none-any.whl", hash = "sha256:60b72710ea02087f212028feb09711225fbc2e343a10d34822fe787510e1caa3"}, {file = "sphinxcontrib_mermaid-1.0.0.tar.gz", hash = "sha256:2e8ab67d3e1e2816663f9347d026a8dee4a858acdd4ad32dd1c808893db88146"}, @@ -1433,6 +1494,7 @@ version = "2.0.0" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, @@ -1449,6 +1511,7 @@ version = "2.0.0" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, @@ -1465,6 +1528,7 @@ version = "24.4.0" description = "Structured Logging for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "structlog-24.4.0-py3-none-any.whl", hash = "sha256:597f61e80a91cc0749a9fd2a098ed76715a1c8a01f73e336b746504d1aad7610"}, {file = "structlog-24.4.0.tar.gz", hash = "sha256:b27bfecede327a6d2da5fbc96bd859f114ecc398a6389d664f62085ee7ae6fc4"}, @@ -1482,6 +1546,7 @@ version = "1.14.1" description = "tasks runner for python projects" optional = false python-versions = "<4.0,>=3.6" +groups = ["dev"] files = [ {file = "taskipy-1.14.1-py3-none-any.whl", hash = "sha256:6e361520f29a0fd2159848e953599f9c75b1d0b047461e4965069caeb94908f1"}, {file = "taskipy-1.14.1.tar.gz", hash = "sha256:410fbcf89692dfd4b9f39c2b49e1750b0a7b81affd0e2d7ea8c35f9d6a4774ed"}, @@ -1499,6 +1564,7 @@ version = "3.5.0" description = "threadpoolctl" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"}, {file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"}, @@ -1510,6 +1576,7 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -1551,6 +1618,7 @@ version = "4.25.0.20240417" description = "Typing stubs for protobuf" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types-protobuf-4.25.0.20240417.tar.gz", hash = "sha256:c34eff17b9b3a0adb6830622f0f302484e4c089f533a46e3f147568313544352"}, {file = "types_protobuf-4.25.0.20240417-py3-none-any.whl", hash = "sha256:e9b613227c2127e3d4881d75d93c93b4d6fd97b5f6a099a0b654a05351c8685d"}, @@ -1562,6 +1630,7 @@ version = "6.0.12.20240917" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"}, {file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"}, @@ -1573,6 +1642,7 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -1584,13 +1654,14 @@ version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -1601,6 +1672,7 @@ version = "0.45.1" description = "A built-package format for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248"}, {file = "wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729"}, @@ -1610,6 +1682,6 @@ files = [ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.12" -content-hash = "776b51ba845a3c8e4835e1c260470c151d9cf1abd835017a177251127ee06d84" +content-hash = "ef636c2598fceae154f3fa16c130570837d1390353d9f79978f798c2190785f5" diff --git a/pyproject.toml b/pyproject.toml index 3702187..13cb957 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ structlog = "^24.4.0" bitarray = "^3.0.0" shapely = "^2.0.6" click = "^8.1.7" +jinja2 = "3.1.5" [tool.poetry.group.dev.dependencies] From 91b5d551442131f62dd211414e1f4aeb820279d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Mar 2025 05:06:20 +0000 Subject: [PATCH 217/249] chore(deps): bump jinja2 from 3.1.5 to 3.1.6 Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.5 to 3.1.6. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.1.5...3.1.6) --- updated-dependencies: - dependency-name: jinja2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 26d42bc..8f98a4f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -423,14 +423,14 @@ files = [ [[package]] name = "jinja2" -version = "3.1.5" +version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, - {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ] [package.dependencies] @@ -1684,4 +1684,4 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [metadata] lock-version = "2.1" python-versions = "^3.12" -content-hash = "ef636c2598fceae154f3fa16c130570837d1390353d9f79978f798c2190785f5" +content-hash = "39cb164105bc42d0880084e72c11894dfd5381d9f39a17beeda770b54e881949" diff --git a/pyproject.toml b/pyproject.toml index 13cb957..3f3522a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ structlog = "^24.4.0" bitarray = "^3.0.0" shapely = "^2.0.6" click = "^8.1.7" -jinja2 = "3.1.5" +jinja2 = "3.1.6" [tool.poetry.group.dev.dependencies] From 0ca7f5da701a74ebf2f70977d788fc7cf752111b Mon Sep 17 00:00:00 2001 From: harrki Date: Tue, 18 Mar 2025 03:22:30 +0900 Subject: [PATCH 218/249] feat: Add Communication Module in Java --- adf_core_python/core/gateway/gateway_agent.py | 7 +- adf_core_python/launcher.py | 3 + .../java/adf_core_python/agent/Agent.java | 72 +++++- .../agent/communication/MessageManager.java | 242 ++++++++++++++++++ .../standard/StandardCommunicationModule.java | 176 +++++++++++++ .../communication/ChannelSubscriber.java | 19 ++ .../communication/CommunicationModule.java | 11 + .../communication/MessageCoordinator.java | 18 ++ .../adf_core_python/gateway/Coordinator.java | 2 +- 9 files changed, 541 insertions(+), 9 deletions(-) create mode 100644 java/lib/src/main/java/adf_core_python/agent/communication/MessageManager.java create mode 100644 java/lib/src/main/java/adf_core_python/agent/communication/standard/StandardCommunicationModule.java create mode 100644 java/lib/src/main/java/adf_core_python/component/communication/ChannelSubscriber.java create mode 100644 java/lib/src/main/java/adf_core_python/component/communication/CommunicationModule.java create mode 100644 java/lib/src/main/java/adf_core_python/component/communication/MessageCoordinator.java diff --git a/adf_core_python/core/gateway/gateway_agent.py b/adf_core_python/core/gateway/gateway_agent.py index 036c49a..31008bb 100644 --- a/adf_core_python/core/gateway/gateway_agent.py +++ b/adf_core_python/core/gateway/gateway_agent.py @@ -2,7 +2,7 @@ from typing import Optional, TYPE_CHECKING, Callable -from rcrs_core.connection import RCRSProto_pb2 +from rcrs_core.connection import RCRSProto_pb2, URN from adf_core_python.core.agent.info.agent_info import AgentInfo from adf_core_python.core.agent.info.scenario_info import ScenarioInfo @@ -111,3 +111,8 @@ def message_received(self, msg: RCRSProto_pb2) -> None: self._gateway_modules[c_msg.module_id].set_execute_response(c_msg.result) self._gateway_modules[c_msg.module_id].set_is_executed(True) + + if msg.urn == URN.Command.AK_SPEAK: + if self.send_msg is None: + raise RuntimeError("send_msg is None") + self.send_msg(msg) diff --git a/adf_core_python/launcher.py b/adf_core_python/launcher.py index 89cb927..94d0c6e 100644 --- a/adf_core_python/launcher.py +++ b/adf_core_python/launcher.py @@ -1,4 +1,5 @@ import argparse +import resource from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.agent_launcher import AgentLauncher @@ -11,6 +12,8 @@ def __init__( self, launcher_config_file: str, ) -> None: + resource.setrlimit(resource.RLIMIT_NOFILE, (8192, 9223372036854775807)) + configure_logger() self.logger = get_logger(__name__) diff --git a/java/lib/src/main/java/adf_core_python/agent/Agent.java b/java/lib/src/main/java/adf_core_python/agent/Agent.java index 07c9f0a..983325e 100644 --- a/java/lib/src/main/java/adf_core_python/agent/Agent.java +++ b/java/lib/src/main/java/adf_core_python/agent/Agent.java @@ -1,14 +1,19 @@ package adf_core_python.agent; -import adf.core.agent.communication.MessageManager; +import adf.core.agent.communication.standard.bundle.StandardMessageBundle; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; +import adf.core.launcher.ConsoleOutput; +import adf_core_python.agent.communication.MessageManager; +import adf_core_python.agent.communication.standard.StandardCommunicationModule; import adf_core_python.agent.config.ModuleConfig; import adf_core_python.agent.develop.DevelopData; import adf_core_python.agent.info.AgentInfo; import adf_core_python.agent.module.ModuleManager; import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.communication.CommunicationModule; import adf_core_python.component.module.AbstractModule; +import adf_core_python.gateway.Coordinator; import adf_core_python.gateway.mapper.AbstractMapper; import adf_core_python.gateway.mapper.MapperDict; import jakarta.annotation.Nonnull; @@ -17,40 +22,50 @@ import org.apache.logging.log4j.Logger; import rescuecore2.config.Config; import rescuecore2.messages.Command; +import rescuecore2.messages.Message; import rescuecore2.standard.entities.StandardEntityURN; import rescuecore2.standard.entities.StandardWorldModel; +import rescuecore2.standard.messages.AKSubscribe; import rescuecore2.worldmodel.ChangeSet; import rescuecore2.worldmodel.Entity; import rescuecore2.worldmodel.EntityID; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Objects; public class Agent { - private final AgentInfo agentInfo; - private final WorldInfo worldInfo; - private final ScenarioInfo scenarioInfo; + public final AgentInfo agentInfo; + public final WorldInfo worldInfo; + public final ScenarioInfo scenarioInfo; private final ModuleManager moduleManager; private final DevelopData developData; private final PrecomputeData precomputeData; private final MessageManager messageManager; + private CommunicationModule communicationModule; private final HashMap modules = new HashMap<>(); private final MapperDict mapperDict; private final Logger logger; + private int ignoreTime; + private final Coordinator coordinator; - public Agent(EntityID entityID, Collection entities, ScenarioInfo scenarioInfo, DevelopData developData, ModuleConfig moduleConfig) { + public Agent(EntityID entityID, Collection entities, ScenarioInfo scenarioInfo, DevelopData developData, ModuleConfig moduleConfig, Coordinator coordinator) { StandardWorldModel worldModel = new StandardWorldModel(); worldModel.addEntities(entities); worldModel.index(); + this.ignoreTime = scenarioInfo.getRawConfig() + .getIntValue(kernel.KernelConstants.IGNORE_AGENT_COMMANDS_KEY); + this.agentInfo = new AgentInfo(entityID, worldModel); this.worldInfo = new WorldInfo(worldModel); this.scenarioInfo = scenarioInfo; this.developData = developData; this.moduleManager = new ModuleManager(this.agentInfo, this.worldInfo, this.scenarioInfo, moduleConfig, this.developData); + this.coordinator = coordinator; String dataStorageName = ""; StandardEntityURN agentURN = Objects.requireNonNull(this.worldInfo.getEntity(this.agentInfo.getID())).getStandardURN(); @@ -97,13 +112,48 @@ public Class registerModule(@Nonnull String moduleID, @Nonnull String moduleN } public void update(int time, ChangeSet changed, Collection heard) { + worldInfo.setTime(time); + worldInfo.merge(changed); agentInfo.recordThinkStartTime(); agentInfo.setTime(time); + + if (time == 1) { + if (this.communicationModule != null) { + ConsoleOutput.out(ConsoleOutput.State.ERROR, + "[ERROR ] Loader is not found."); + ConsoleOutput.out(ConsoleOutput.State.NOTICE, + "CommunicationModule is modified - " + this); + } else { + this.communicationModule = new StandardCommunicationModule(); + } + + this.messageManager.registerMessageBundle(new StandardMessageBundle()); + } + + // agents can subscribe after ignore time + if (time >= ignoreTime) { + this.messageManager.subscribe(this.agentInfo, this.worldInfo, + this.scenarioInfo); + + if (!this.messageManager.getIsSubscribed()) { + int[] channelsToSubscribe = this.messageManager.getChannels(); + if (channelsToSubscribe != null) { + this.messageManager.setIsSubscribed(true); + } + } + } + agentInfo.setHeard(heard); agentInfo.setChanged(changed); - worldInfo.setTime(time); - worldInfo.merge(changed); worldInfo.setChanged(changed); + + this.messageManager.refresh(); + this.communicationModule.receive(this, this.messageManager); + + this.messageManager.coordinateMessages(this.agentInfo, this.worldInfo, + this.scenarioInfo); + this.communicationModule.send(this, this.messageManager); + logger.debug("Agent Update (Time: {}, Changed: {}, Heard: {})", agentInfo.getTime(), agentInfo.getChanged(), agentInfo.getHeard()); } @@ -113,4 +163,12 @@ public Config execModuleMethod(String moduleID, String methodName, Config argume logger.debug("Executed Method Result (MethodName: {}, Result: {}", methodName, result); return result; } + + public EntityID getID() { + return this.agentInfo.getID(); + } + + public void send(Message[] messages) { + Arrays.stream(messages).forEach(coordinator::sendMessage); + } } diff --git a/java/lib/src/main/java/adf_core_python/agent/communication/MessageManager.java b/java/lib/src/main/java/adf_core_python/agent/communication/MessageManager.java new file mode 100644 index 0000000..2d71d47 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/agent/communication/MessageManager.java @@ -0,0 +1,242 @@ +package adf_core_python.agent.communication; + +import adf.core.agent.communication.standard.bundle.StandardMessageBundle; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf.core.component.communication.CommunicationMessage; +import adf.core.component.communication.MessageBundle; +import adf.core.launcher.ConsoleOutput; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.component.communication.ChannelSubscriber; +import adf_core_python.component.communication.MessageCoordinator; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; + +import java.util.*; + +public class MessageManager { + + private int standardMessageClassCount; + private int customMessageClassCount; + private HashMap> messageClassMap; + private HashMap, + Integer> messageClassIDMap; + private ArrayList sendMessageList; + private List> channelSendMessageList; + private List receivedMessageList; + private int heardAgentHelpCount; + private MessageCoordinator messageCoordinator; + + private Set checkDuplicationCache; + + private ChannelSubscriber channelSubscriber; + private int[] subscribedChannels; + private boolean isSubscribed; + + public MessageManager() { + this.standardMessageClassCount = 1; // 00001 + this.customMessageClassCount = 16; // 10000 + this.messageClassMap = new HashMap<>(32); + this.messageClassIDMap = new HashMap<>(32); + this.sendMessageList = new ArrayList<>(); + this.channelSendMessageList = new ArrayList<>(); + this.checkDuplicationCache = new HashSet<>(); + this.receivedMessageList = new ArrayList<>(); + this.heardAgentHelpCount = 0; + + this.messageCoordinator = null; + + channelSubscriber = null; + subscribedChannels = new int[1]; + // by default subscribe to channel 1 + subscribedChannels[0] = 1; + isSubscribed = false; + } + + + public void subscribeToChannels(int[] channels) { + subscribedChannels = channels; + isSubscribed = false; + } + + + public int[] getChannels() { + return subscribedChannels; + } + + + public boolean getIsSubscribed() { + return isSubscribed; + } + + + public void setIsSubscribed(boolean subscribed) { + isSubscribed = subscribed; + } + + + public void setMessageCoordinator(MessageCoordinator mc) { + this.messageCoordinator = mc; + } + + + public void setChannelSubscriber(ChannelSubscriber cs) { + channelSubscriber = cs; + } + + + public void subscribe(AgentInfo agentInfo, WorldInfo worldInfo, + ScenarioInfo scenarioInfo) { + if (channelSubscriber != null) { + channelSubscriber.subscribe(agentInfo, worldInfo, scenarioInfo, this); + } + } + + + public boolean registerMessageClass(int index, + @Nonnull Class messageClass) { + if (index > 31) { + throw new IllegalArgumentException("index maximum is 31"); + } + + if (messageClassMap.containsKey(index)) { + ConsoleOutput.out(ConsoleOutput.State.WARN, + "index(" + index + ") is already registered/" + messageClass.getName() + + " is ignored"); + return false; + } + + messageClassMap.put(index, messageClass); + messageClassIDMap.put(messageClass, index); + + return true; + } + + + public void registerMessageBundle(@Nonnull MessageBundle messageBundle) { + if (messageBundle == null) { + return; + } + + for (Class messageClass : messageBundle + .getMessageClassList()) { + this.registerMessageClass( + (messageBundle.getClass().equals(StandardMessageBundle.class) + ? standardMessageClassCount++ + : customMessageClassCount++), + messageClass); + } + } + + + @Nullable + public Class getMessageClass(int index) { + if (!messageClassMap.containsKey(index)) { + return null; + } + + return messageClassMap.get(index); + } + + + public int getMessageClassIndex(@Nonnull CommunicationMessage message) { + if (!messageClassMap.containsValue(message.getClass())) { + throw new IllegalArgumentException( + message.getClass().getName() + " is not registered with the manager"); + } + + return messageClassIDMap.get(message.getClass()); + } + + + public void addMessage(@Nonnull CommunicationMessage message) { + this.addMessage(message, true); + } + + + public void addMessage(@Nonnull CommunicationMessage message, + boolean checkDuplication) { + if (message == null) { + return; + } + + String checkKey = message.getCheckKey(); + if (checkDuplication && !this.checkDuplicationCache.contains(checkKey)) { + this.sendMessageList.add(message); + this.checkDuplicationCache.add(checkKey); + } else { + this.sendMessageList.add(message); + this.checkDuplicationCache.add(checkKey); + } + } + + + @Nonnull + public List> getSendMessageList() { + return this.channelSendMessageList; + } + + + public void addReceivedMessage(@Nonnull CommunicationMessage message) { + receivedMessageList.add(message); + } + + + @Nonnull + public List getReceivedMessageList() { + return this.receivedMessageList; + } + + + @SafeVarargs + @Nonnull + public final List getReceivedMessageList( + Class... messageClasses) { + List resultList = new ArrayList<>(); + for (CommunicationMessage message : this.receivedMessageList) { + for (Class< + ? extends CommunicationMessage> messageClass : messageClasses) { + if (messageClass.isAssignableFrom(message.getClass())) { + resultList.add(message); + } + } + } + return resultList; + } + + + public void coordinateMessages(AgentInfo agentInfo, WorldInfo worldInfo, + ScenarioInfo scenarioInfo) { + // create a list of messages for every channel including the voice comm + // channel + this.channelSendMessageList = new ArrayList>( + scenarioInfo.getCommsChannelsCount()); + for (int i = 0; i < scenarioInfo.getCommsChannelsCount(); i++) { + this.channelSendMessageList.add(new ArrayList()); + } + + if (messageCoordinator != null) { + messageCoordinator.coordinate(agentInfo, worldInfo, scenarioInfo, this, + this.sendMessageList, this.channelSendMessageList); + } + } + + + public void addHeardAgentHelpCount() { + this.heardAgentHelpCount++; + } + + + public int getHeardAgentHelpCount() { + return this.heardAgentHelpCount; + } + + + public void refresh() { + this.sendMessageList.clear(); + this.checkDuplicationCache.clear(); + this.receivedMessageList.clear(); + this.heardAgentHelpCount = 0; + } +} diff --git a/java/lib/src/main/java/adf_core_python/agent/communication/standard/StandardCommunicationModule.java b/java/lib/src/main/java/adf_core_python/agent/communication/standard/StandardCommunicationModule.java new file mode 100644 index 0000000..b1d4142 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/agent/communication/standard/StandardCommunicationModule.java @@ -0,0 +1,176 @@ +package adf_core_python.agent.communication.standard; + +import adf.core.agent.communication.standard.bundle.StandardMessage; +import adf.core.component.communication.CommunicationMessage; +import adf.core.component.communication.util.BitOutputStream; +import adf.core.component.communication.util.BitStreamReader; +import adf.core.launcher.ConsoleOutput; +import adf_core_python.agent.Agent; +import adf_core_python.agent.communication.MessageManager; +import adf_core_python.component.communication.CommunicationModule; +import jakarta.annotation.Nonnull; +import rescuecore2.messages.Command; +import rescuecore2.messages.Message; +import rescuecore2.standard.messages.AKSpeak; +import rescuecore2.worldmodel.EntityID; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.List; + +public class StandardCommunicationModule extends CommunicationModule { + + final private int ESCAPE_CHAR = 0x41; + final private int SIZE_ID = 5; + final private int SIZE_TTL = 3; + + @Override + public void receive(@Nonnull Agent agent, + @Nonnull MessageManager messageManager) { + Collection heardList = agent.agentInfo.getHeard(); + + for (Command heard : heardList) { + if (heard instanceof AKSpeak) { + EntityID senderID = heard.getAgentID(); + + if (agent.getID().equals(senderID)) { + continue; + } + + AKSpeak received = (AKSpeak) heard; + byte[] receivedData = received.getContent(); + boolean isRadio = (received.getChannel() != 0); + + if (receivedData.length <= 0) { + continue; + } + + if (isRadio) { + addReceivedMessage(messageManager, Boolean.TRUE, senderID, + receivedData); + } else { + String voiceString = new String(receivedData); + if ("Help".equalsIgnoreCase(voiceString) + || "Ouch".equalsIgnoreCase(voiceString)) { + messageManager.addHeardAgentHelpCount(); + continue; + } + + BitOutputStream messageTemp = new BitOutputStream(); + for (int i = 0; i < receivedData.length; i++) { + if (receivedData[i] == ESCAPE_CHAR) { + if ((i + 1) >= receivedData.length) { + addReceivedMessage(messageManager, Boolean.FALSE, senderID, + messageTemp.toByteArray()); + break; + } else if (receivedData[i + 1] != ESCAPE_CHAR) { + addReceivedMessage(messageManager, Boolean.FALSE, senderID, + messageTemp.toByteArray()); + messageTemp.reset(); + continue; + } + + i += 1; + } + messageTemp.write(receivedData[i]); + } + } + } + } + } + + final Class[] standardMessageArgTypes = {boolean.class, int.class, + int.class, BitStreamReader.class}; + + private void addReceivedMessage(@Nonnull MessageManager messageManager, + boolean isRadio, @Nonnull EntityID senderID, byte[] data) { + BitStreamReader bitStreamReader = new BitStreamReader(data); + int messageClassIndex = bitStreamReader.getBits(SIZE_ID); + if (messageClassIndex <= 0) { + ConsoleOutput.out(ConsoleOutput.State.WARN, + "ignore Message Class Index (0)"); + return; + } + + int messageTTL = (isRadio ? -1 : bitStreamReader.getBits(SIZE_TTL)); + + Object[] args = {Boolean.valueOf(isRadio), + Integer.valueOf(senderID.getValue()), Integer.valueOf(messageTTL), + bitStreamReader}; + try { + messageManager + .addReceivedMessage(messageManager.getMessageClass(messageClassIndex) + .getConstructor(standardMessageArgTypes).newInstance(args)); + } catch (NoSuchMethodException | IllegalArgumentException e) { + e.printStackTrace(); + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + } + } + + + @Override + public void send(@Nonnull Agent agent, + @Nonnull MessageManager messageManager) { + final int voiceLimitBytes = agent.scenarioInfo.getVoiceMessagesSize(); + int voiceMessageLeft = voiceLimitBytes; + ByteArrayOutputStream voiceMessageStream = new ByteArrayOutputStream(); + + Message[] messages = new Message[1]; + + List> sendMessageList = messageManager + .getSendMessageList(); + for (int channel = 0; channel < sendMessageList.size(); channel++) { + for (CommunicationMessage message : sendMessageList.get(channel)) { + int messageClassIndex = messageManager.getMessageClassIndex(message); + + BitOutputStream bitOutputStream = new BitOutputStream(); + bitOutputStream.writeBits(messageClassIndex, SIZE_ID); + + if (channel == 0) { + bitOutputStream.writeBits(((StandardMessage) message).getTTL(), + SIZE_TTL); + } + + bitOutputStream.writeBits(message.toBitOutputStream()); + + if (channel > 0) { + messages[0] = new AKSpeak(agent.getID(), agent.agentInfo.getTime(), + channel, bitOutputStream.toByteArray()); + agent.send(messages); + } else { + // voice channel + int messageSize = (int) Math + .ceil(((double) bitOutputStream.size()) / 8.0); + if (messageSize <= voiceMessageLeft) { + byte[] messageData = bitOutputStream.toByteArray(); + ByteArrayOutputStream escapedMessage = new ByteArrayOutputStream(); + for (int i = 0; i < messageSize; i++) { + if (messageData[i] == ESCAPE_CHAR) { + escapedMessage.write(ESCAPE_CHAR); + } + escapedMessage.write(messageData[i]); + } + escapedMessage.toByteArray(); + escapedMessage.write(ESCAPE_CHAR); + if (escapedMessage.size() <= voiceMessageLeft) { + voiceMessageLeft -= escapedMessage.size(); + try { + voiceMessageStream.write(escapedMessage.toByteArray()); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + } + } + + if (voiceMessageStream.size() > 0) { + messages[0] = new AKSpeak(agent.getID(), agent.agentInfo.getTime(), 0, + voiceMessageStream.toByteArray()); + agent.send(messages); + } + } +} diff --git a/java/lib/src/main/java/adf_core_python/component/communication/ChannelSubscriber.java b/java/lib/src/main/java/adf_core_python/component/communication/ChannelSubscriber.java new file mode 100644 index 0000000..c7d28b5 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/communication/ChannelSubscriber.java @@ -0,0 +1,19 @@ +package adf_core_python.component.communication; + +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.communication.MessageManager; +import adf_core_python.agent.info.AgentInfo; + +public class ChannelSubscriber { + + public void subscribe(AgentInfo agentInfo, WorldInfo worldInfo, + ScenarioInfo scenarioInfo, MessageManager messageManager) { + // default channel subscriber subscribes to only channel 1 + if (agentInfo.getTime() == 1) { + int[] channels = new int[1]; + channels[0] = 1; + messageManager.subscribeToChannels(channels); + } + } +} diff --git a/java/lib/src/main/java/adf_core_python/component/communication/CommunicationModule.java b/java/lib/src/main/java/adf_core_python/component/communication/CommunicationModule.java new file mode 100644 index 0000000..a095191 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/communication/CommunicationModule.java @@ -0,0 +1,11 @@ +package adf_core_python.component.communication; + +import adf_core_python.agent.Agent; +import adf_core_python.agent.communication.MessageManager; + +abstract public class CommunicationModule { + + abstract public void receive(Agent agent, MessageManager messageManager); + + abstract public void send(Agent agent, MessageManager messageManager); +} diff --git a/java/lib/src/main/java/adf_core_python/component/communication/MessageCoordinator.java b/java/lib/src/main/java/adf_core_python/component/communication/MessageCoordinator.java new file mode 100644 index 0000000..d1e7f65 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/communication/MessageCoordinator.java @@ -0,0 +1,18 @@ +package adf_core_python.component.communication; + +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf.core.component.communication.CommunicationMessage; +import adf_core_python.agent.communication.MessageManager; +import adf_core_python.agent.info.AgentInfo; + +import java.util.ArrayList; +import java.util.List; + +abstract public class MessageCoordinator { + + abstract public void coordinate(AgentInfo agentInfo, WorldInfo worldInfo, + ScenarioInfo scenarioInfo, MessageManager messageManager, + ArrayList sendMessageList, + List> channelSendMessageList); +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/Coordinator.java b/java/lib/src/main/java/adf_core_python/gateway/Coordinator.java index 309e92d..c94253e 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/Coordinator.java +++ b/java/lib/src/main/java/adf_core_python/gateway/Coordinator.java @@ -68,7 +68,7 @@ private void handleMessage(Message message) { mode = ScenarioInfo.Mode.PRECOMPUTATION_PHASE; break; } - agent = new Agent(amAgent.getAgentID(), amAgent.getEntities(), new ScenarioInfo(amAgent.getConfig(), mode), new DevelopData(), new ModuleConfig()); + agent = new Agent(amAgent.getAgentID(), amAgent.getEntities(), new ScenarioInfo(amAgent.getConfig(), mode), new DevelopData(), new ModuleConfig(), this); } else if (message instanceof AMModule amModule) { if (agent == null) { throw new IllegalStateException("Agent not found. Make sure agent has been registered."); From daa9c4d3dada2b61cbb85907694897f71cc830a7 Mon Sep 17 00:00:00 2001 From: harrki Date: Tue, 18 Mar 2025 22:29:14 +0900 Subject: [PATCH 219/249] fix: Add Communication Module in Java --- java/lib/build.gradle | 3 + .../src/main/java/adf_core_python/Main.java | 2 +- .../agent/develop/DevelopData.java | 40 - .../agent/precompute/PreData.java | 56 -- .../agent/precompute/PrecomputeData.java | 400 --------- .../{ => core}/agent/Agent.java | 31 +- .../agent/communication/MessageManager.java | 24 +- .../standard/StandardCommunicationModule.java | 13 +- .../{ => core}/agent/config/ModuleConfig.java | 2 +- .../{ => core}/agent/info/AgentInfo.java | 2 +- .../agent/module/ModuleManager.java | 15 +- .../communication/ChannelSubscriber.java | 6 +- .../communication/CommunicationModule.java | 6 +- .../communication/MessageCoordinator.java | 6 +- .../component/extaction/ExtAction.java | 10 +- .../component/module/AbstractModule.java | 12 +- .../module/algorithm/Clustering.java | 14 +- .../module/algorithm/DynamicClustering.java | 8 +- .../module/algorithm/PathPlanning.java | 14 +- .../module/algorithm/StaticClustering.java | 8 +- .../complex/AmbulanceTargetAllocator.java | 12 +- .../module/complex/BuildingDetector.java | 12 +- .../module/complex/FireTargetAllocator.java | 12 +- .../module/complex/HumanDetector.java | 12 +- .../module/complex/PoliceTargetAllocator.java | 12 +- .../module/complex/RoadDetector.java | 12 +- .../component/module/complex/Search.java | 12 +- .../module/complex/TargetAllocator.java | 14 +- .../module/complex/TargetDetector.java | 14 +- .../{ => core}/gateway/Coordinator.java | 18 +- .../{ => core}/gateway/Gateway.java | 2 +- .../gateway/mapper/AbstractMapper.java | 2 +- .../{ => core}/gateway/mapper/MapperDict.java | 28 +- .../mapper/module/AbstractModuleMapper.java | 10 +- .../module/algorithm/ClusteringMapper.java | 10 +- .../algorithm/DynamicClusteringMapper.java | 8 +- .../module/algorithm/PathPlanningMapper.java | 22 +- .../algorithm/StaticClusteringMapper.java | 8 +- .../AmbulanceTargetAllocatorMapper.java | 8 +- .../complex/BuildingDetectorMapper.java | 8 +- .../complex/FireTargetAllocatorMapper.java | 8 +- .../module/complex/HumanDetectorMapper.java | 8 +- .../complex/PoliceTargetAllocatorMapper.java | 8 +- .../module/complex/RoadDetectorMapper.java | 8 +- .../mapper/module/complex/SearchMapper.java | 11 + .../module/complex/TargetAllocatorMapper.java | 10 +- .../module/complex/TargetDetectorMapper.java | 10 +- .../{ => core}/gateway/message/AMAgent.java | 6 +- .../{ => core}/gateway/message/AMExec.java | 6 +- .../{ => core}/gateway/message/AMModule.java | 6 +- .../{ => core}/gateway/message/AMUpdate.java | 6 +- .../gateway/message/MAExecResponse.java | 6 +- .../gateway/message/MAModuleResponse.java | 6 +- .../message/ModuleControlMessageFactory.java | 4 +- .../urn/ModuleMessageComponentURN.java | 2 +- .../gateway/message/urn/ModuleMessageURN.java | 2 +- .../mapper/module/complex/SearchMapper.java | 11 - .../impl/extaction/DefaultExtActionClear.java | 803 ++++++++++++++++++ .../DefaultExtActionFireFighting.java | 351 ++++++++ .../extaction/DefaultExtActionFireRescue.java | 227 +++++ .../impl/extaction/DefaultExtActionMove.java | 210 +++++ .../extaction/DefaultExtActionTransport.java | 348 ++++++++ .../module/algorithm/AStarPathPlanning.java | 198 +++++ .../algorithm/DijkstraPathPlanning.java | 154 ++++ .../impl/module/algorithm/FireClustering.java | 261 ++++++ .../module/algorithm/KMeansClustering.java | 641 ++++++++++++++ .../module/comm/DefaultChannelSubscriber.java | 96 +++ .../comm/DefaultMessageCoordinator.java | 199 +++++ .../DefaultAmbulanceTargetAllocator.java | 362 ++++++++ .../complex/DefaultBuildingDetector.java | 209 +++++ .../complex/DefaultFireTargetAllocator.java | 345 ++++++++ .../module/complex/DefaultHumanDetector.java | 252 ++++++ .../complex/DefaultPoliceTargetAllocator.java | 323 +++++++ .../module/complex/DefaultRoadDetector.java | 362 ++++++++ .../impl/module/complex/DefaultSearch.java | 164 ++++ 75 files changed, 5773 insertions(+), 758 deletions(-) delete mode 100644 java/lib/src/main/java/adf_core_python/agent/develop/DevelopData.java delete mode 100644 java/lib/src/main/java/adf_core_python/agent/precompute/PreData.java delete mode 100644 java/lib/src/main/java/adf_core_python/agent/precompute/PrecomputeData.java rename java/lib/src/main/java/adf_core_python/{ => core}/agent/Agent.java (90%) rename java/lib/src/main/java/adf_core_python/{ => core}/agent/communication/MessageManager.java (93%) rename java/lib/src/main/java/adf_core_python/{ => core}/agent/communication/standard/StandardCommunicationModule.java (96%) rename java/lib/src/main/java/adf_core_python/{ => core}/agent/config/ModuleConfig.java (96%) rename java/lib/src/main/java/adf_core_python/{ => core}/agent/info/AgentInfo.java (98%) rename java/lib/src/main/java/adf_core_python/{ => core}/agent/module/ModuleManager.java (97%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/communication/ChannelSubscriber.java (75%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/communication/CommunicationModule.java (56%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/communication/MessageCoordinator.java (77%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/extaction/ExtAction.java (91%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/module/AbstractModule.java (91%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/module/algorithm/Clustering.java (84%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/module/algorithm/DynamicClustering.java (61%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/module/algorithm/PathPlanning.java (85%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/module/algorithm/StaticClustering.java (61%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/module/complex/AmbulanceTargetAllocator.java (75%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/module/complex/BuildingDetector.java (75%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/module/complex/FireTargetAllocator.java (74%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/module/complex/HumanDetector.java (75%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/module/complex/PoliceTargetAllocator.java (74%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/module/complex/RoadDetector.java (75%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/module/complex/Search.java (74%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/module/complex/TargetAllocator.java (73%) rename java/lib/src/main/java/adf_core_python/{ => core}/component/module/complex/TargetDetector.java (74%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/Coordinator.java (87%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/Gateway.java (97%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/mapper/AbstractMapper.java (91%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/mapper/MapperDict.java (63%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/mapper/module/AbstractModuleMapper.java (84%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/mapper/module/algorithm/ClusteringMapper.java (94%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/mapper/module/algorithm/DynamicClusteringMapper.java (57%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/mapper/module/algorithm/PathPlanningMapper.java (92%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/mapper/module/algorithm/StaticClusteringMapper.java (56%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/mapper/module/complex/AmbulanceTargetAllocatorMapper.java (58%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/mapper/module/complex/BuildingDetectorMapper.java (56%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/mapper/module/complex/FireTargetAllocatorMapper.java (57%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/mapper/module/complex/HumanDetectorMapper.java (54%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/mapper/module/complex/PoliceTargetAllocatorMapper.java (57%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/mapper/module/complex/RoadDetectorMapper.java (54%) create mode 100644 java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/SearchMapper.java rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/mapper/module/complex/TargetAllocatorMapper.java (76%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/mapper/module/complex/TargetDetectorMapper.java (76%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/message/AMAgent.java (91%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/message/AMExec.java (89%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/message/AMModule.java (89%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/message/AMUpdate.java (90%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/message/MAExecResponse.java (87%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/message/MAModuleResponse.java (87%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/message/ModuleControlMessageFactory.java (93%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/message/urn/ModuleMessageComponentURN.java (96%) rename java/lib/src/main/java/adf_core_python/{ => core}/gateway/message/urn/ModuleMessageURN.java (96%) delete mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/SearchMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionClear.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionFireFighting.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionFireRescue.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionMove.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionTransport.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/module/algorithm/AStarPathPlanning.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/module/algorithm/DijkstraPathPlanning.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/module/algorithm/FireClustering.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/module/algorithm/KMeansClustering.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/module/comm/DefaultChannelSubscriber.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/module/comm/DefaultMessageCoordinator.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultAmbulanceTargetAllocator.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultBuildingDetector.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultFireTargetAllocator.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultHumanDetector.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultPoliceTargetAllocator.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultRoadDetector.java create mode 100644 java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultSearch.java diff --git a/java/lib/build.gradle b/java/lib/build.gradle index 386d59b..61869cf 100644 --- a/java/lib/build.gradle +++ b/java/lib/build.gradle @@ -39,6 +39,9 @@ dependencies { implementation 'org.apache.logging.log4j:log4j-core:2.24.2' implementation 'org.apache.logging.log4j:log4j-api:2.24.2' + //Algorithm + implementation 'com.google.common:google-collect:0.5' + testImplementation libs.junit.jupiter testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } diff --git a/java/lib/src/main/java/adf_core_python/Main.java b/java/lib/src/main/java/adf_core_python/Main.java index d6fbd5f..a85ba19 100644 --- a/java/lib/src/main/java/adf_core_python/Main.java +++ b/java/lib/src/main/java/adf_core_python/Main.java @@ -1,6 +1,6 @@ package adf_core_python; -import adf_core_python.gateway.Gateway; +import adf_core_python.core.gateway.Gateway; public class Main { public static void main(String[] args) { diff --git a/java/lib/src/main/java/adf_core_python/agent/develop/DevelopData.java b/java/lib/src/main/java/adf_core_python/agent/develop/DevelopData.java deleted file mode 100644 index 00652e6..0000000 --- a/java/lib/src/main/java/adf_core_python/agent/develop/DevelopData.java +++ /dev/null @@ -1,40 +0,0 @@ -package adf_core_python.agent.develop; - -import jakarta.annotation.Nonnull; - -import java.util.List; -import java.util.Map; - -public class DevelopData { - private boolean developFlag = false; - - private Map intValues; - private Map doubleValues; - private Map stringValues; - private Map boolValues; - - private Map> intLists; - private Map> doubleLists; - private Map> stringLists; - private Map> boolLists; - - @Nonnull - public Integer getInteger(@Nonnull String name, int defaultValue) { - if (this.developFlag) { - Integer value = this.intValues.get(name); - if (value == null) { - String rawData = this.stringValues.get(name); - if (rawData != null && !rawData.equals("")) { - value = Integer.valueOf(rawData); - } - if (value != null) { - this.intValues.put(name, value); - } - } - if (value != null) { - return value; - } - } - return defaultValue; - } -} diff --git a/java/lib/src/main/java/adf_core_python/agent/precompute/PreData.java b/java/lib/src/main/java/adf_core_python/agent/precompute/PreData.java deleted file mode 100644 index 847b4f7..0000000 --- a/java/lib/src/main/java/adf_core_python/agent/precompute/PreData.java +++ /dev/null @@ -1,56 +0,0 @@ -package adf_core_python.agent.precompute; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public final class PreData { - - public Map intValues; - public Map doubleValues; - public Map stringValues; - public Map idValues; - public Map boolValues; - - public Map> intLists; - public Map> doubleLists; - public Map> stringLists; - public Map> idLists; - public Map> boolLists; - - public boolean isReady; - public String readyID; - - public PreData() { - this.intValues = new HashMap<>(); - this.doubleValues = new HashMap<>(); - this.stringValues = new HashMap<>(); - this.idValues = new HashMap<>(); - this.boolValues = new HashMap<>(); - this.intLists = new HashMap<>(); - this.doubleLists = new HashMap<>(); - this.stringLists = new HashMap<>(); - this.idLists = new HashMap<>(); - this.boolLists = new HashMap<>(); - this.isReady = false; - this.readyID = ""; - } - - - public PreData copy() { - PreData preData = new PreData(); - preData.intValues = new HashMap<>(this.intValues); - preData.doubleValues = new HashMap<>(this.doubleValues); - preData.stringValues = new HashMap<>(this.stringValues); - preData.idValues = new HashMap<>(this.idValues); - preData.boolValues = new HashMap<>(this.boolValues); - preData.intLists = new HashMap<>(this.intLists); - preData.doubleLists = new HashMap<>(this.doubleLists); - preData.stringLists = new HashMap<>(this.stringLists); - preData.idLists = new HashMap<>(this.idLists); - preData.boolLists = new HashMap<>(this.boolLists); - preData.isReady = this.isReady; - preData.readyID = this.readyID; - return preData; - } -} diff --git a/java/lib/src/main/java/adf_core_python/agent/precompute/PrecomputeData.java b/java/lib/src/main/java/adf_core_python/agent/precompute/PrecomputeData.java deleted file mode 100644 index d838c68..0000000 --- a/java/lib/src/main/java/adf_core_python/agent/precompute/PrecomputeData.java +++ /dev/null @@ -1,400 +0,0 @@ -package adf_core_python.agent.precompute; - -import adf.core.agent.info.WorldInfo; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.msgpack.jackson.dataformat.MessagePackFactory; -import rescuecore2.worldmodel.EntityID; - -import java.io.*; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -public final class PrecomputeData { - - public static final String DEFAULT_FILE_NAME = "data.bin"; - public static final File PRECOMP_DATA_DIR = new File("precomp_data"); - - private final String fileName; - - private PreData data; - - public PrecomputeData() { - this(DEFAULT_FILE_NAME); - } - - - public PrecomputeData(String name) { - this.fileName = name; - this.init(); - } - - - private PrecomputeData(String name, PreData precomputeDatas) { - this.fileName = name; - this.data = precomputeDatas; - } - - - public static void removeData(String name) { - if (!PRECOMP_DATA_DIR.exists()) { - return; - } - - File file = new File(PRECOMP_DATA_DIR, name); - if (!file.exists()) { - return; - } - - file.delete(); - } - - - public static void removeData() { - removeData(DEFAULT_FILE_NAME); - } - - - public PrecomputeData copy() { - return new PrecomputeData(this.fileName, this.data.copy()); - } - - - private void init() { - this.data = this.read(this.fileName); - if (this.data == null) { - this.data = new PreData(); - } - } - - - private PreData read(String name) { - try { - if (!PRECOMP_DATA_DIR.exists()) { - if (!PRECOMP_DATA_DIR.mkdir()) { - return null; - } - } - - File readFile = new File(PRECOMP_DATA_DIR, name); - if (!readFile.exists()) { - return null; - } - - FileInputStream fis = new FileInputStream(readFile); - BufferedInputStream bis = new BufferedInputStream(fis); - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - byte[] binary = new byte[1024]; - while (true) { - int len = bis.read(binary); - if (len < 0) { - break; - } - bout.write(binary, 0, len); - } - - binary = bout.toByteArray(); - ObjectMapper om = new ObjectMapper(new MessagePackFactory()); - PreData ds = om.readValue(binary, PreData.class); - bis.close(); - fis.close(); - return ds; - } catch (IOException e) { - return null; - } - } - - - public boolean write() { - try { - if (!PRECOMP_DATA_DIR.exists()) { - if (!PRECOMP_DATA_DIR.mkdir()) { - return false; - } - } - ObjectMapper om = new ObjectMapper(new MessagePackFactory()); - byte[] binary = om.writeValueAsBytes(this.data); - FileOutputStream fos = new FileOutputStream( - new File(PRECOMP_DATA_DIR, this.fileName)); - fos.write(binary); - fos.close(); - return true; - } catch (IOException e) { - e.printStackTrace(); - ; - return false; - } - } - - - public Integer setInteger(String name, int value) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.intValues.put(callClassName + ":" + name, value); - } - - - public Double setDouble(String name, double value) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.doubleValues.put(callClassName + ":" + name, value); - } - - - public Boolean setBoolean(String name, boolean value) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.boolValues.put(callClassName + ":" + name, value); - } - - - public String setString(String name, String value) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.stringValues.put(callClassName + ":" + name, value); - } - - - public EntityID setEntityID(String name, EntityID value) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - Integer id = this.data.idValues.put(callClassName + ":" + name, - value.getValue()); - return id == null ? null : new EntityID(id); - } - - - public List setIntegerList(String name, List list) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.intLists.put(callClassName + ":" + name, list); - } - - - public List setDoubleList(String name, List list) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.doubleLists.put(callClassName + ":" + name, list); - } - - - public List setStringList(String name, List list) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.stringLists.put(callClassName + ":" + name, list); - } - - - public List setEntityIDList(String name, List list) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - List cvtList = new ArrayList<>(); - for (EntityID id : list) { - cvtList.add(id.getValue()); - } - - cvtList = this.data.idLists.put(callClassName + ":" + name, cvtList); - return cvtList == null ? null - : cvtList.stream().map(EntityID::new).collect(Collectors.toList()); - } - - - public List setBooleanList(String name, List list) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.boolLists.put(callClassName + ":" + name, list); - } - - - public boolean setReady(boolean isReady, WorldInfo worldInfo) { - this.data.isReady = isReady; - this.data.readyID = makeReadyID(worldInfo); - return (this.data.isReady - && this.data.readyID.equals(this.makeReadyID(worldInfo))); - } - - - public Integer getInteger(String name) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.intValues.get(callClassName + ":" + name); - } - - - public Double getDouble(String name) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.doubleValues.get(callClassName + ":" + name); - } - - - public Boolean getBoolean(String name) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.boolValues.get(callClassName + ":" + name); - } - - - public String getString(String name) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.stringValues.get(callClassName + ":" + name); - } - - - public EntityID getEntityID(String name) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - Integer id = this.data.idValues.get(callClassName + ":" + name); - return id == null ? null : new EntityID(id); - } - - - public List getIntegerList(String name) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.intLists.get(callClassName + ":" + name); - } - - - public List getDoubleList(String name) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.doubleLists.get(callClassName + ":" + name); - } - - - public List getStringList(String name) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.stringLists.get(callClassName + ":" + name); - } - - - public List getEntityIDList(String name) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - List cvtList = this.data.idLists.get(callClassName + ":" + name); - return cvtList == null ? null - : cvtList.stream().map(EntityID::new).collect(Collectors.toList()); - } - - - public List getBooleanList(String name) { - StackTraceElement[] stackTraceElements = Thread.currentThread() - .getStackTrace(); - if (stackTraceElements.length == 0) { - return null; - } - - String callClassName = stackTraceElements[2].getClassName(); - return this.data.boolLists.get(callClassName + ":" + name); - } - - - public boolean isReady(WorldInfo worldInfo) { - return (this.data.isReady - && this.data.readyID.equals(this.makeReadyID(worldInfo))); - } - - - private String makeReadyID(WorldInfo worldInfo) { - return "" + worldInfo.getBounds().getX() + worldInfo.getBounds().getY() - + worldInfo.getAllEntities().size(); - } -} diff --git a/java/lib/src/main/java/adf_core_python/agent/Agent.java b/java/lib/src/main/java/adf_core_python/core/agent/Agent.java similarity index 90% rename from java/lib/src/main/java/adf_core_python/agent/Agent.java rename to java/lib/src/main/java/adf_core_python/core/agent/Agent.java index 983325e..5524e7d 100644 --- a/java/lib/src/main/java/adf_core_python/agent/Agent.java +++ b/java/lib/src/main/java/adf_core_python/core/agent/Agent.java @@ -1,21 +1,21 @@ -package adf_core_python.agent; +package adf_core_python.core.agent; import adf.core.agent.communication.standard.bundle.StandardMessageBundle; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; import adf.core.launcher.ConsoleOutput; -import adf_core_python.agent.communication.MessageManager; -import adf_core_python.agent.communication.standard.StandardCommunicationModule; -import adf_core_python.agent.config.ModuleConfig; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.agent.module.ModuleManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.communication.CommunicationModule; -import adf_core_python.component.module.AbstractModule; -import adf_core_python.gateway.Coordinator; -import adf_core_python.gateway.mapper.AbstractMapper; -import adf_core_python.gateway.mapper.MapperDict; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.communication.standard.StandardCommunicationModule; +import adf_core_python.core.agent.config.ModuleConfig; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.communication.CommunicationModule; +import adf_core_python.core.component.module.AbstractModule; +import adf_core_python.core.gateway.Coordinator; +import adf_core_python.core.gateway.mapper.AbstractMapper; +import adf_core_python.core.gateway.mapper.MapperDict; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import org.apache.logging.log4j.LogManager; @@ -25,7 +25,6 @@ import rescuecore2.messages.Message; import rescuecore2.standard.entities.StandardEntityURN; import rescuecore2.standard.entities.StandardWorldModel; -import rescuecore2.standard.messages.AKSubscribe; import rescuecore2.worldmodel.ChangeSet; import rescuecore2.worldmodel.Entity; import rescuecore2.worldmodel.EntityID; @@ -45,12 +44,12 @@ public class Agent { private final DevelopData developData; private final PrecomputeData precomputeData; private final MessageManager messageManager; - private CommunicationModule communicationModule; private final HashMap modules = new HashMap<>(); private final MapperDict mapperDict; private final Logger logger; - private int ignoreTime; private final Coordinator coordinator; + private CommunicationModule communicationModule; + private int ignoreTime; public Agent(EntityID entityID, Collection entities, ScenarioInfo scenarioInfo, DevelopData developData, ModuleConfig moduleConfig, Coordinator coordinator) { StandardWorldModel worldModel = new StandardWorldModel(); diff --git a/java/lib/src/main/java/adf_core_python/agent/communication/MessageManager.java b/java/lib/src/main/java/adf_core_python/core/agent/communication/MessageManager.java similarity index 93% rename from java/lib/src/main/java/adf_core_python/agent/communication/MessageManager.java rename to java/lib/src/main/java/adf_core_python/core/agent/communication/MessageManager.java index 2d71d47..e82e0a5 100644 --- a/java/lib/src/main/java/adf_core_python/agent/communication/MessageManager.java +++ b/java/lib/src/main/java/adf_core_python/core/agent/communication/MessageManager.java @@ -1,4 +1,4 @@ -package adf_core_python.agent.communication; +package adf_core_python.core.agent.communication; import adf.core.agent.communication.standard.bundle.StandardMessageBundle; import adf.core.agent.info.ScenarioInfo; @@ -6,9 +6,9 @@ import adf.core.component.communication.CommunicationMessage; import adf.core.component.communication.MessageBundle; import adf.core.launcher.ConsoleOutput; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.component.communication.ChannelSubscriber; -import adf_core_python.component.communication.MessageCoordinator; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.component.communication.ChannelSubscriber; +import adf_core_python.core.component.communication.MessageCoordinator; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -16,20 +16,18 @@ public class MessageManager { - private int standardMessageClassCount; - private int customMessageClassCount; - private HashMap> messageClassMap; - private HashMap, + private final HashMap, Integer> messageClassIDMap; - private ArrayList sendMessageList; + private final ArrayList sendMessageList; + private final List receivedMessageList; + private final Set checkDuplicationCache; + private int standardMessageClassCount; + private int customMessageClassCount; private List> channelSendMessageList; - private List receivedMessageList; private int heardAgentHelpCount; private MessageCoordinator messageCoordinator; - - private Set checkDuplicationCache; - private ChannelSubscriber channelSubscriber; private int[] subscribedChannels; private boolean isSubscribed; diff --git a/java/lib/src/main/java/adf_core_python/agent/communication/standard/StandardCommunicationModule.java b/java/lib/src/main/java/adf_core_python/core/agent/communication/standard/StandardCommunicationModule.java similarity index 96% rename from java/lib/src/main/java/adf_core_python/agent/communication/standard/StandardCommunicationModule.java rename to java/lib/src/main/java/adf_core_python/core/agent/communication/standard/StandardCommunicationModule.java index b1d4142..90bf17d 100644 --- a/java/lib/src/main/java/adf_core_python/agent/communication/standard/StandardCommunicationModule.java +++ b/java/lib/src/main/java/adf_core_python/core/agent/communication/standard/StandardCommunicationModule.java @@ -1,13 +1,13 @@ -package adf_core_python.agent.communication.standard; +package adf_core_python.core.agent.communication.standard; import adf.core.agent.communication.standard.bundle.StandardMessage; import adf.core.component.communication.CommunicationMessage; import adf.core.component.communication.util.BitOutputStream; import adf.core.component.communication.util.BitStreamReader; import adf.core.launcher.ConsoleOutput; -import adf_core_python.agent.Agent; -import adf_core_python.agent.communication.MessageManager; -import adf_core_python.component.communication.CommunicationModule; +import adf_core_python.core.agent.Agent; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.component.communication.CommunicationModule; import jakarta.annotation.Nonnull; import rescuecore2.messages.Command; import rescuecore2.messages.Message; @@ -21,6 +21,8 @@ public class StandardCommunicationModule extends CommunicationModule { + final Class[] standardMessageArgTypes = {boolean.class, int.class, + int.class, BitStreamReader.class}; final private int ESCAPE_CHAR = 0x41; final private int SIZE_ID = 5; final private int SIZE_TTL = 3; @@ -80,9 +82,6 @@ public void receive(@Nonnull Agent agent, } } - final Class[] standardMessageArgTypes = {boolean.class, int.class, - int.class, BitStreamReader.class}; - private void addReceivedMessage(@Nonnull MessageManager messageManager, boolean isRadio, @Nonnull EntityID senderID, byte[] data) { BitStreamReader bitStreamReader = new BitStreamReader(data); diff --git a/java/lib/src/main/java/adf_core_python/agent/config/ModuleConfig.java b/java/lib/src/main/java/adf_core_python/core/agent/config/ModuleConfig.java similarity index 96% rename from java/lib/src/main/java/adf_core_python/agent/config/ModuleConfig.java rename to java/lib/src/main/java/adf_core_python/core/agent/config/ModuleConfig.java index 3232d7a..7a6f4de 100644 --- a/java/lib/src/main/java/adf_core_python/agent/config/ModuleConfig.java +++ b/java/lib/src/main/java/adf_core_python/core/agent/config/ModuleConfig.java @@ -1,4 +1,4 @@ -package adf_core_python.agent.config; +package adf_core_python.core.agent.config; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/java/lib/src/main/java/adf_core_python/agent/info/AgentInfo.java b/java/lib/src/main/java/adf_core_python/core/agent/info/AgentInfo.java similarity index 98% rename from java/lib/src/main/java/adf_core_python/agent/info/AgentInfo.java rename to java/lib/src/main/java/adf_core_python/core/agent/info/AgentInfo.java index e0d63d1..0a2e4f4 100644 --- a/java/lib/src/main/java/adf_core_python/agent/info/AgentInfo.java +++ b/java/lib/src/main/java/adf_core_python/core/agent/info/AgentInfo.java @@ -1,4 +1,4 @@ -package adf_core_python.agent.info; +package adf_core_python.core.agent.info; import adf.core.agent.action.Action; import jakarta.annotation.Nonnull; diff --git a/java/lib/src/main/java/adf_core_python/agent/module/ModuleManager.java b/java/lib/src/main/java/adf_core_python/core/agent/module/ModuleManager.java similarity index 97% rename from java/lib/src/main/java/adf_core_python/agent/module/ModuleManager.java rename to java/lib/src/main/java/adf_core_python/core/agent/module/ModuleManager.java index abc0621..68c7254 100644 --- a/java/lib/src/main/java/adf_core_python/agent/module/ModuleManager.java +++ b/java/lib/src/main/java/adf_core_python/core/agent/module/ModuleManager.java @@ -1,5 +1,6 @@ -package adf_core_python.agent.module; +package adf_core_python.core.agent.module; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; import adf.core.component.centralized.CommandExecutor; @@ -7,11 +8,10 @@ import adf.core.component.communication.ChannelSubscriber; import adf.core.component.communication.CommunicationMessage; import adf.core.component.communication.MessageCoordinator; -import adf.core.component.extaction.ExtAction; -import adf_core_python.agent.config.ModuleConfig; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.component.module.AbstractModule; +import adf_core_python.core.agent.config.ModuleConfig; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.component.extaction.ExtAction; +import adf_core_python.core.component.module.AbstractModule; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import org.apache.logging.log4j.LogManager; @@ -67,6 +67,7 @@ public ModuleManager(@Nonnull AgentInfo agentInfo, @Nonnull WorldInfo worldInfo, Class moduleClass; try { moduleClass = Class.forName(className); + logger.info(moduleClass.getName()); } catch (ClassNotFoundException | NullPointerException e) { logger.warn("Module " + moduleName + " not found. Using default class " + defaultClassName); className = defaultClassName; @@ -89,7 +90,7 @@ public ModuleManager(@Nonnull AgentInfo agentInfo, @Nonnull WorldInfo worldInfo, } throw new IllegalArgumentException( - "Module name is not found : " + className); + "Module is not found : " + className); } diff --git a/java/lib/src/main/java/adf_core_python/component/communication/ChannelSubscriber.java b/java/lib/src/main/java/adf_core_python/core/component/communication/ChannelSubscriber.java similarity index 75% rename from java/lib/src/main/java/adf_core_python/component/communication/ChannelSubscriber.java rename to java/lib/src/main/java/adf_core_python/core/component/communication/ChannelSubscriber.java index c7d28b5..5a017b4 100644 --- a/java/lib/src/main/java/adf_core_python/component/communication/ChannelSubscriber.java +++ b/java/lib/src/main/java/adf_core_python/core/component/communication/ChannelSubscriber.java @@ -1,9 +1,9 @@ -package adf_core_python.component.communication; +package adf_core_python.core.component.communication; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.communication.MessageManager; -import adf_core_python.agent.info.AgentInfo; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; public class ChannelSubscriber { diff --git a/java/lib/src/main/java/adf_core_python/component/communication/CommunicationModule.java b/java/lib/src/main/java/adf_core_python/core/component/communication/CommunicationModule.java similarity index 56% rename from java/lib/src/main/java/adf_core_python/component/communication/CommunicationModule.java rename to java/lib/src/main/java/adf_core_python/core/component/communication/CommunicationModule.java index a095191..b5135a1 100644 --- a/java/lib/src/main/java/adf_core_python/component/communication/CommunicationModule.java +++ b/java/lib/src/main/java/adf_core_python/core/component/communication/CommunicationModule.java @@ -1,7 +1,7 @@ -package adf_core_python.component.communication; +package adf_core_python.core.component.communication; -import adf_core_python.agent.Agent; -import adf_core_python.agent.communication.MessageManager; +import adf_core_python.core.agent.Agent; +import adf_core_python.core.agent.communication.MessageManager; abstract public class CommunicationModule { diff --git a/java/lib/src/main/java/adf_core_python/component/communication/MessageCoordinator.java b/java/lib/src/main/java/adf_core_python/core/component/communication/MessageCoordinator.java similarity index 77% rename from java/lib/src/main/java/adf_core_python/component/communication/MessageCoordinator.java rename to java/lib/src/main/java/adf_core_python/core/component/communication/MessageCoordinator.java index d1e7f65..4ebaa2a 100644 --- a/java/lib/src/main/java/adf_core_python/component/communication/MessageCoordinator.java +++ b/java/lib/src/main/java/adf_core_python/core/component/communication/MessageCoordinator.java @@ -1,10 +1,10 @@ -package adf_core_python.component.communication; +package adf_core_python.core.component.communication; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; import adf.core.component.communication.CommunicationMessage; -import adf_core_python.agent.communication.MessageManager; -import adf_core_python.agent.info.AgentInfo; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; import java.util.ArrayList; import java.util.List; diff --git a/java/lib/src/main/java/adf_core_python/component/extaction/ExtAction.java b/java/lib/src/main/java/adf_core_python/core/component/extaction/ExtAction.java similarity index 91% rename from java/lib/src/main/java/adf_core_python/component/extaction/ExtAction.java rename to java/lib/src/main/java/adf_core_python/core/component/extaction/ExtAction.java index 0dad82a..f7ba688 100644 --- a/java/lib/src/main/java/adf_core_python/component/extaction/ExtAction.java +++ b/java/lib/src/main/java/adf_core_python/core/component/extaction/ExtAction.java @@ -1,13 +1,13 @@ -package adf_core_python.component.extaction; +package adf_core_python.core.component.extaction; import adf.core.agent.action.Action; -import adf.core.agent.communication.MessageManager; import adf.core.agent.develop.DevelopData; -import adf.core.agent.info.AgentInfo; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf.core.agent.module.ModuleManager; -import adf.core.agent.precompute.PrecomputeData; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; import rescuecore2.worldmodel.EntityID; abstract public class ExtAction { diff --git a/java/lib/src/main/java/adf_core_python/component/module/AbstractModule.java b/java/lib/src/main/java/adf_core_python/core/component/module/AbstractModule.java similarity index 91% rename from java/lib/src/main/java/adf_core_python/component/module/AbstractModule.java rename to java/lib/src/main/java/adf_core_python/core/component/module/AbstractModule.java index 830ca29..e6e6f65 100644 --- a/java/lib/src/main/java/adf_core_python/component/module/AbstractModule.java +++ b/java/lib/src/main/java/adf_core_python/core/component/module/AbstractModule.java @@ -1,12 +1,12 @@ -package adf_core_python.component.module; +package adf_core_python.core.component.module; -import adf.core.agent.communication.MessageManager; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.agent.module.ModuleManager; -import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; import java.util.ArrayList; import java.util.List; diff --git a/java/lib/src/main/java/adf_core_python/component/module/algorithm/Clustering.java b/java/lib/src/main/java/adf_core_python/core/component/module/algorithm/Clustering.java similarity index 84% rename from java/lib/src/main/java/adf_core_python/component/module/algorithm/Clustering.java rename to java/lib/src/main/java/adf_core_python/core/component/module/algorithm/Clustering.java index aba296d..1c84cdb 100644 --- a/java/lib/src/main/java/adf_core_python/component/module/algorithm/Clustering.java +++ b/java/lib/src/main/java/adf_core_python/core/component/module/algorithm/Clustering.java @@ -1,13 +1,13 @@ -package adf_core_python.component.module.algorithm; +package adf_core_python.core.component.module.algorithm; -import adf.core.agent.communication.MessageManager; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.agent.module.ModuleManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.AbstractModule; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.AbstractModule; import rescuecore2.standard.entities.StandardEntity; import rescuecore2.worldmodel.EntityID; diff --git a/java/lib/src/main/java/adf_core_python/component/module/algorithm/DynamicClustering.java b/java/lib/src/main/java/adf_core_python/core/component/module/algorithm/DynamicClustering.java similarity index 61% rename from java/lib/src/main/java/adf_core_python/component/module/algorithm/DynamicClustering.java rename to java/lib/src/main/java/adf_core_python/core/component/module/algorithm/DynamicClustering.java index 208d200..16d302a 100644 --- a/java/lib/src/main/java/adf_core_python/component/module/algorithm/DynamicClustering.java +++ b/java/lib/src/main/java/adf_core_python/core/component/module/algorithm/DynamicClustering.java @@ -1,10 +1,10 @@ -package adf_core_python.component.module.algorithm; +package adf_core_python.core.component.module.algorithm; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.agent.module.ModuleManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; public abstract class DynamicClustering extends Clustering { diff --git a/java/lib/src/main/java/adf_core_python/component/module/algorithm/PathPlanning.java b/java/lib/src/main/java/adf_core_python/core/component/module/algorithm/PathPlanning.java similarity index 85% rename from java/lib/src/main/java/adf_core_python/component/module/algorithm/PathPlanning.java rename to java/lib/src/main/java/adf_core_python/core/component/module/algorithm/PathPlanning.java index 6101ac0..7a40f63 100644 --- a/java/lib/src/main/java/adf_core_python/component/module/algorithm/PathPlanning.java +++ b/java/lib/src/main/java/adf_core_python/core/component/module/algorithm/PathPlanning.java @@ -1,13 +1,13 @@ -package adf_core_python.component.module.algorithm; +package adf_core_python.core.component.module.algorithm; -import adf.core.agent.communication.MessageManager; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.agent.module.ModuleManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.AbstractModule; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.AbstractModule; import rescuecore2.misc.Pair; import rescuecore2.worldmodel.EntityID; diff --git a/java/lib/src/main/java/adf_core_python/component/module/algorithm/StaticClustering.java b/java/lib/src/main/java/adf_core_python/core/component/module/algorithm/StaticClustering.java similarity index 61% rename from java/lib/src/main/java/adf_core_python/component/module/algorithm/StaticClustering.java rename to java/lib/src/main/java/adf_core_python/core/component/module/algorithm/StaticClustering.java index b9647da..f3fb404 100644 --- a/java/lib/src/main/java/adf_core_python/component/module/algorithm/StaticClustering.java +++ b/java/lib/src/main/java/adf_core_python/core/component/module/algorithm/StaticClustering.java @@ -1,10 +1,10 @@ -package adf_core_python.component.module.algorithm; +package adf_core_python.core.component.module.algorithm; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.agent.module.ModuleManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; public abstract class StaticClustering extends Clustering { diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/AmbulanceTargetAllocator.java b/java/lib/src/main/java/adf_core_python/core/component/module/complex/AmbulanceTargetAllocator.java similarity index 75% rename from java/lib/src/main/java/adf_core_python/component/module/complex/AmbulanceTargetAllocator.java rename to java/lib/src/main/java/adf_core_python/core/component/module/complex/AmbulanceTargetAllocator.java index 41f2e7b..4ebfb1d 100644 --- a/java/lib/src/main/java/adf_core_python/component/module/complex/AmbulanceTargetAllocator.java +++ b/java/lib/src/main/java/adf_core_python/core/component/module/complex/AmbulanceTargetAllocator.java @@ -1,12 +1,12 @@ -package adf_core_python.component.module.complex; +package adf_core_python.core.component.module.complex; -import adf.core.agent.communication.MessageManager; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.agent.module.ModuleManager; -import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; import rescuecore2.worldmodel.EntityID; import java.util.Map; diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/BuildingDetector.java b/java/lib/src/main/java/adf_core_python/core/component/module/complex/BuildingDetector.java similarity index 75% rename from java/lib/src/main/java/adf_core_python/component/module/complex/BuildingDetector.java rename to java/lib/src/main/java/adf_core_python/core/component/module/complex/BuildingDetector.java index 34567c8..cc22e34 100644 --- a/java/lib/src/main/java/adf_core_python/component/module/complex/BuildingDetector.java +++ b/java/lib/src/main/java/adf_core_python/core/component/module/complex/BuildingDetector.java @@ -1,12 +1,12 @@ -package adf_core_python.component.module.complex; +package adf_core_python.core.component.module.complex; -import adf.core.agent.communication.MessageManager; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.agent.module.ModuleManager; -import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; import rescuecore2.standard.entities.Building; public abstract class BuildingDetector extends TargetDetector { diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/FireTargetAllocator.java b/java/lib/src/main/java/adf_core_python/core/component/module/complex/FireTargetAllocator.java similarity index 74% rename from java/lib/src/main/java/adf_core_python/component/module/complex/FireTargetAllocator.java rename to java/lib/src/main/java/adf_core_python/core/component/module/complex/FireTargetAllocator.java index 65c757e..f6791d6 100644 --- a/java/lib/src/main/java/adf_core_python/component/module/complex/FireTargetAllocator.java +++ b/java/lib/src/main/java/adf_core_python/core/component/module/complex/FireTargetAllocator.java @@ -1,12 +1,12 @@ -package adf_core_python.component.module.complex; +package adf_core_python.core.component.module.complex; -import adf.core.agent.communication.MessageManager; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.agent.module.ModuleManager; -import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; import rescuecore2.worldmodel.EntityID; import java.util.Map; diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/HumanDetector.java b/java/lib/src/main/java/adf_core_python/core/component/module/complex/HumanDetector.java similarity index 75% rename from java/lib/src/main/java/adf_core_python/component/module/complex/HumanDetector.java rename to java/lib/src/main/java/adf_core_python/core/component/module/complex/HumanDetector.java index e99db72..f0491ff 100644 --- a/java/lib/src/main/java/adf_core_python/component/module/complex/HumanDetector.java +++ b/java/lib/src/main/java/adf_core_python/core/component/module/complex/HumanDetector.java @@ -1,12 +1,12 @@ -package adf_core_python.component.module.complex; +package adf_core_python.core.component.module.complex; -import adf.core.agent.communication.MessageManager; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.agent.module.ModuleManager; -import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; import rescuecore2.standard.entities.Human; public abstract class HumanDetector extends TargetDetector { diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/PoliceTargetAllocator.java b/java/lib/src/main/java/adf_core_python/core/component/module/complex/PoliceTargetAllocator.java similarity index 74% rename from java/lib/src/main/java/adf_core_python/component/module/complex/PoliceTargetAllocator.java rename to java/lib/src/main/java/adf_core_python/core/component/module/complex/PoliceTargetAllocator.java index 846dc06..4300895 100644 --- a/java/lib/src/main/java/adf_core_python/component/module/complex/PoliceTargetAllocator.java +++ b/java/lib/src/main/java/adf_core_python/core/component/module/complex/PoliceTargetAllocator.java @@ -1,12 +1,12 @@ -package adf_core_python.component.module.complex; +package adf_core_python.core.component.module.complex; -import adf.core.agent.communication.MessageManager; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.agent.module.ModuleManager; -import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; import rescuecore2.worldmodel.EntityID; import java.util.Map; diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/RoadDetector.java b/java/lib/src/main/java/adf_core_python/core/component/module/complex/RoadDetector.java similarity index 75% rename from java/lib/src/main/java/adf_core_python/component/module/complex/RoadDetector.java rename to java/lib/src/main/java/adf_core_python/core/component/module/complex/RoadDetector.java index 9f6acac..532b853 100644 --- a/java/lib/src/main/java/adf_core_python/component/module/complex/RoadDetector.java +++ b/java/lib/src/main/java/adf_core_python/core/component/module/complex/RoadDetector.java @@ -1,12 +1,12 @@ -package adf_core_python.component.module.complex; +package adf_core_python.core.component.module.complex; -import adf.core.agent.communication.MessageManager; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.agent.module.ModuleManager; -import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; import rescuecore2.standard.entities.Road; public abstract class RoadDetector extends TargetDetector { diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/Search.java b/java/lib/src/main/java/adf_core_python/core/component/module/complex/Search.java similarity index 74% rename from java/lib/src/main/java/adf_core_python/component/module/complex/Search.java rename to java/lib/src/main/java/adf_core_python/core/component/module/complex/Search.java index 13173bb..77fbe62 100644 --- a/java/lib/src/main/java/adf_core_python/component/module/complex/Search.java +++ b/java/lib/src/main/java/adf_core_python/core/component/module/complex/Search.java @@ -1,12 +1,12 @@ -package adf_core_python.component.module.complex; +package adf_core_python.core.component.module.complex; -import adf.core.agent.communication.MessageManager; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.agent.module.ModuleManager; -import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; import rescuecore2.standard.entities.Area; public abstract class Search extends TargetDetector { diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/TargetAllocator.java b/java/lib/src/main/java/adf_core_python/core/component/module/complex/TargetAllocator.java similarity index 73% rename from java/lib/src/main/java/adf_core_python/component/module/complex/TargetAllocator.java rename to java/lib/src/main/java/adf_core_python/core/component/module/complex/TargetAllocator.java index 3a85b34..f0b93ab 100644 --- a/java/lib/src/main/java/adf_core_python/component/module/complex/TargetAllocator.java +++ b/java/lib/src/main/java/adf_core_python/core/component/module/complex/TargetAllocator.java @@ -1,13 +1,13 @@ -package adf_core_python.component.module.complex; +package adf_core_python.core.component.module.complex; -import adf.core.agent.communication.MessageManager; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.agent.module.ModuleManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.AbstractModule; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.AbstractModule; import rescuecore2.worldmodel.EntityID; import java.util.Map; diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/TargetDetector.java b/java/lib/src/main/java/adf_core_python/core/component/module/complex/TargetDetector.java similarity index 74% rename from java/lib/src/main/java/adf_core_python/component/module/complex/TargetDetector.java rename to java/lib/src/main/java/adf_core_python/core/component/module/complex/TargetDetector.java index bddd091..d0bc184 100644 --- a/java/lib/src/main/java/adf_core_python/component/module/complex/TargetDetector.java +++ b/java/lib/src/main/java/adf_core_python/core/component/module/complex/TargetDetector.java @@ -1,13 +1,13 @@ -package adf_core_python.component.module.complex; +package adf_core_python.core.component.module.complex; -import adf.core.agent.communication.MessageManager; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.agent.info.AgentInfo; -import adf_core_python.agent.module.ModuleManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.AbstractModule; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.AbstractModule; import rescuecore2.standard.entities.StandardEntity; import rescuecore2.worldmodel.EntityID; diff --git a/java/lib/src/main/java/adf_core_python/gateway/Coordinator.java b/java/lib/src/main/java/adf_core_python/core/gateway/Coordinator.java similarity index 87% rename from java/lib/src/main/java/adf_core_python/gateway/Coordinator.java rename to java/lib/src/main/java/adf_core_python/core/gateway/Coordinator.java index c94253e..f056e94 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/Coordinator.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/Coordinator.java @@ -1,10 +1,11 @@ -package adf_core_python.gateway; +package adf_core_python.core.gateway; +import adf.core.agent.develop.DevelopData; import adf.core.agent.info.ScenarioInfo; -import adf_core_python.agent.Agent; -import adf_core_python.agent.config.ModuleConfig; -import adf_core_python.agent.develop.DevelopData; -import adf_core_python.gateway.message.*; +import adf.core.launcher.ConfigKey; +import adf_core_python.core.agent.Agent; +import adf_core_python.core.agent.config.ModuleConfig; +import adf_core_python.core.gateway.message.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import rescuecore2.config.Config; @@ -68,7 +69,12 @@ private void handleMessage(Message message) { mode = ScenarioInfo.Mode.PRECOMPUTATION_PHASE; break; } - agent = new Agent(amAgent.getAgentID(), amAgent.getEntities(), new ScenarioInfo(amAgent.getConfig(), mode), new DevelopData(), new ModuleConfig(), this); + Config config = amAgent.getConfig(); + agent = new Agent(amAgent.getAgentID(), amAgent.getEntities(), new ScenarioInfo(config, mode), new DevelopData( + config.getBooleanValue(ConfigKey.KEY_DEVELOP_FLAG, false), + config.getValue(ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, + DevelopData.DEFAULT_FILE_NAME), + config.getArrayValue(ConfigKey.KEY_DEVELOP_DATA, "")), new ModuleConfig(), this); } else if (message instanceof AMModule amModule) { if (agent == null) { throw new IllegalStateException("Agent not found. Make sure agent has been registered."); diff --git a/java/lib/src/main/java/adf_core_python/gateway/Gateway.java b/java/lib/src/main/java/adf_core_python/core/gateway/Gateway.java similarity index 97% rename from java/lib/src/main/java/adf_core_python/gateway/Gateway.java rename to java/lib/src/main/java/adf_core_python/core/gateway/Gateway.java index 2e97b29..dc5c4c5 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/Gateway.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/Gateway.java @@ -1,4 +1,4 @@ -package adf_core_python.gateway; +package adf_core_python.core.gateway; import rescuecore2.registry.Registry; import rescuecore2.standard.entities.StandardEntityFactory; diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/AbstractMapper.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/AbstractMapper.java similarity index 91% rename from java/lib/src/main/java/adf_core_python/gateway/mapper/AbstractMapper.java rename to java/lib/src/main/java/adf_core_python/core/gateway/mapper/AbstractMapper.java index 3a27ca9..4f95dfb 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/AbstractMapper.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/AbstractMapper.java @@ -1,4 +1,4 @@ -package adf_core_python.gateway.mapper; +package adf_core_python.core.gateway.mapper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/MapperDict.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/MapperDict.java similarity index 63% rename from java/lib/src/main/java/adf_core_python/gateway/mapper/MapperDict.java rename to java/lib/src/main/java/adf_core_python/core/gateway/mapper/MapperDict.java index 28e50f3..5cb4e16 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/MapperDict.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/MapperDict.java @@ -1,17 +1,17 @@ -package adf_core_python.gateway.mapper; - -import adf_core_python.component.module.AbstractModule; -import adf_core_python.component.module.algorithm.Clustering; -import adf_core_python.component.module.algorithm.DynamicClustering; -import adf_core_python.component.module.algorithm.PathPlanning; -import adf_core_python.component.module.algorithm.StaticClustering; -import adf_core_python.component.module.complex.*; -import adf_core_python.gateway.mapper.module.AbstractModuleMapper; -import adf_core_python.gateway.mapper.module.algorithm.ClusteringMapper; -import adf_core_python.gateway.mapper.module.algorithm.DynamicClusteringMapper; -import adf_core_python.gateway.mapper.module.algorithm.PathPlanningMapper; -import adf_core_python.gateway.mapper.module.algorithm.StaticClusteringMapper; -import adf_core_python.gateway.mapper.module.complex.*; +package adf_core_python.core.gateway.mapper; + +import adf_core_python.core.component.module.AbstractModule; +import adf_core_python.core.component.module.algorithm.Clustering; +import adf_core_python.core.component.module.algorithm.DynamicClustering; +import adf_core_python.core.component.module.algorithm.PathPlanning; +import adf_core_python.core.component.module.algorithm.StaticClustering; +import adf_core_python.core.component.module.complex.*; +import adf_core_python.core.gateway.mapper.module.AbstractModuleMapper; +import adf_core_python.core.gateway.mapper.module.algorithm.ClusteringMapper; +import adf_core_python.core.gateway.mapper.module.algorithm.DynamicClusteringMapper; +import adf_core_python.core.gateway.mapper.module.algorithm.PathPlanningMapper; +import adf_core_python.core.gateway.mapper.module.algorithm.StaticClusteringMapper; +import adf_core_python.core.gateway.mapper.module.complex.*; import java.util.HashMap; diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/AbstractModuleMapper.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/AbstractModuleMapper.java similarity index 84% rename from java/lib/src/main/java/adf_core_python/gateway/mapper/module/AbstractModuleMapper.java rename to java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/AbstractModuleMapper.java index 984d801..0c66bad 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/AbstractModuleMapper.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/AbstractModuleMapper.java @@ -1,9 +1,9 @@ -package adf_core_python.gateway.mapper.module; +package adf_core_python.core.gateway.mapper.module; -import adf.core.agent.communication.MessageManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.AbstractModule; -import adf_core_python.gateway.mapper.AbstractMapper; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.AbstractModule; +import adf_core_python.core.gateway.mapper.AbstractMapper; import rescuecore2.config.Config; public class AbstractModuleMapper extends AbstractMapper { diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/ClusteringMapper.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/algorithm/ClusteringMapper.java similarity index 94% rename from java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/ClusteringMapper.java rename to java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/algorithm/ClusteringMapper.java index a063c20..0e57311 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/ClusteringMapper.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/algorithm/ClusteringMapper.java @@ -1,10 +1,10 @@ -package adf_core_python.gateway.mapper.module.algorithm; +package adf_core_python.core.gateway.mapper.module.algorithm; -import adf.core.agent.communication.MessageManager; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.algorithm.Clustering; -import adf_core_python.gateway.mapper.module.AbstractModuleMapper; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.algorithm.Clustering; +import adf_core_python.core.gateway.mapper.module.AbstractModuleMapper; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import rescuecore2.config.Config; diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/DynamicClusteringMapper.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/algorithm/DynamicClusteringMapper.java similarity index 57% rename from java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/DynamicClusteringMapper.java rename to java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/algorithm/DynamicClusteringMapper.java index 3dfdb43..bc60931 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/DynamicClusteringMapper.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/algorithm/DynamicClusteringMapper.java @@ -1,9 +1,9 @@ -package adf_core_python.gateway.mapper.module.algorithm; +package adf_core_python.core.gateway.mapper.module.algorithm; -import adf.core.agent.communication.MessageManager; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.algorithm.DynamicClustering; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.algorithm.DynamicClustering; public class DynamicClusteringMapper extends ClusteringMapper { public DynamicClusteringMapper(DynamicClustering dynamicClustering, PrecomputeData precomputeData, MessageManager messageManager, WorldInfo worldInfo) { diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/PathPlanningMapper.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/algorithm/PathPlanningMapper.java similarity index 92% rename from java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/PathPlanningMapper.java rename to java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/algorithm/PathPlanningMapper.java index 335a642..00d6bfe 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/PathPlanningMapper.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/algorithm/PathPlanningMapper.java @@ -1,19 +1,19 @@ -package adf_core_python.gateway.mapper.module.algorithm; - -import java.util.Collection; -import java.util.List; +package adf_core_python.core.gateway.mapper.module.algorithm; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.algorithm.PathPlanning; +import adf_core_python.core.gateway.mapper.module.AbstractModuleMapper; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; - -import adf.core.agent.communication.MessageManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.algorithm.PathPlanning; -import adf_core_python.gateway.mapper.module.AbstractModuleMapper; import rescuecore2.config.Config; import rescuecore2.worldmodel.EntityID; +import java.io.IOException; +import java.util.Collection; +import java.util.List; + public class PathPlanningMapper extends AbstractModuleMapper { public PathPlanningMapper(PathPlanning pathPlanning, PrecomputeData precomputeData, MessageManager messageManager) { super(pathPlanning, precomputeData, messageManager); @@ -34,7 +34,7 @@ public Config execMethod(String methodName, Config arguments) { try { targets = objectMapper.readValue(arguments.getValue("Targets"), new TypeReference>() { }); - } catch (JsonProcessingException e) { + } catch (IOException e) { throw new RuntimeException(e); } execSetDestination(targets); @@ -57,7 +57,7 @@ public Config execMethod(String methodName, Config arguments) { destinations = objectMapper.readValue(arguments.getValue("Destinations"), new TypeReference>() { }); - } catch (JsonProcessingException e) { + } catch (IOException e) { throw new RuntimeException(e); } result = execGetResult(new EntityID(arguments.getIntValue("From")), destinations); diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/StaticClusteringMapper.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/algorithm/StaticClusteringMapper.java similarity index 56% rename from java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/StaticClusteringMapper.java rename to java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/algorithm/StaticClusteringMapper.java index daeaec3..16ec923 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/StaticClusteringMapper.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/algorithm/StaticClusteringMapper.java @@ -1,9 +1,9 @@ -package adf_core_python.gateway.mapper.module.algorithm; +package adf_core_python.core.gateway.mapper.module.algorithm; -import adf.core.agent.communication.MessageManager; import adf.core.agent.info.WorldInfo; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.algorithm.StaticClustering; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.algorithm.StaticClustering; public class StaticClusteringMapper extends ClusteringMapper { public StaticClusteringMapper(StaticClustering staticClustering, PrecomputeData precomputeData, MessageManager messageManager, WorldInfo worldInfo) { diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/AmbulanceTargetAllocatorMapper.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/AmbulanceTargetAllocatorMapper.java similarity index 58% rename from java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/AmbulanceTargetAllocatorMapper.java rename to java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/AmbulanceTargetAllocatorMapper.java index e7beb49..4e5df45 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/AmbulanceTargetAllocatorMapper.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/AmbulanceTargetAllocatorMapper.java @@ -1,8 +1,8 @@ -package adf_core_python.gateway.mapper.module.complex; +package adf_core_python.core.gateway.mapper.module.complex; -import adf.core.agent.communication.MessageManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.complex.AmbulanceTargetAllocator; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.complex.AmbulanceTargetAllocator; public class AmbulanceTargetAllocatorMapper extends TargetAllocatorMapper { public AmbulanceTargetAllocatorMapper(AmbulanceTargetAllocator ambulanceTargetAllocator, PrecomputeData precomputeData, MessageManager messageManager) { diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/BuildingDetectorMapper.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/BuildingDetectorMapper.java similarity index 56% rename from java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/BuildingDetectorMapper.java rename to java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/BuildingDetectorMapper.java index 448dd1c..3a9bddd 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/BuildingDetectorMapper.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/BuildingDetectorMapper.java @@ -1,8 +1,8 @@ -package adf_core_python.gateway.mapper.module.complex; +package adf_core_python.core.gateway.mapper.module.complex; -import adf.core.agent.communication.MessageManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.complex.BuildingDetector; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.complex.BuildingDetector; public class BuildingDetectorMapper extends TargetDetectorMapper { public BuildingDetectorMapper(BuildingDetector buildingDetector, PrecomputeData precomputeData, MessageManager messageManager) { diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/FireTargetAllocatorMapper.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/FireTargetAllocatorMapper.java similarity index 57% rename from java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/FireTargetAllocatorMapper.java rename to java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/FireTargetAllocatorMapper.java index 235d3fa..6234471 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/FireTargetAllocatorMapper.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/FireTargetAllocatorMapper.java @@ -1,8 +1,8 @@ -package adf_core_python.gateway.mapper.module.complex; +package adf_core_python.core.gateway.mapper.module.complex; -import adf.core.agent.communication.MessageManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.complex.FireTargetAllocator; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.complex.FireTargetAllocator; public class FireTargetAllocatorMapper extends TargetAllocatorMapper { public FireTargetAllocatorMapper(FireTargetAllocator fireTargetAllocator, PrecomputeData precomputeData, MessageManager messageManager) { diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/HumanDetectorMapper.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/HumanDetectorMapper.java similarity index 54% rename from java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/HumanDetectorMapper.java rename to java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/HumanDetectorMapper.java index 2db6fed..078fe16 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/HumanDetectorMapper.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/HumanDetectorMapper.java @@ -1,8 +1,8 @@ -package adf_core_python.gateway.mapper.module.complex; +package adf_core_python.core.gateway.mapper.module.complex; -import adf.core.agent.communication.MessageManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.complex.HumanDetector; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.complex.HumanDetector; public class HumanDetectorMapper extends TargetDetectorMapper { public HumanDetectorMapper(HumanDetector humanDetector, PrecomputeData precomputeData, MessageManager messageManager) { diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/PoliceTargetAllocatorMapper.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/PoliceTargetAllocatorMapper.java similarity index 57% rename from java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/PoliceTargetAllocatorMapper.java rename to java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/PoliceTargetAllocatorMapper.java index 5092b72..1d6dd41 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/PoliceTargetAllocatorMapper.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/PoliceTargetAllocatorMapper.java @@ -1,8 +1,8 @@ -package adf_core_python.gateway.mapper.module.complex; +package adf_core_python.core.gateway.mapper.module.complex; -import adf.core.agent.communication.MessageManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.complex.PoliceTargetAllocator; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.complex.PoliceTargetAllocator; public class PoliceTargetAllocatorMapper extends TargetAllocatorMapper { public PoliceTargetAllocatorMapper(PoliceTargetAllocator policeTargetAllocator, PrecomputeData precomputeData, MessageManager messageManager) { diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/RoadDetectorMapper.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/RoadDetectorMapper.java similarity index 54% rename from java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/RoadDetectorMapper.java rename to java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/RoadDetectorMapper.java index 4945c38..412634f 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/RoadDetectorMapper.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/RoadDetectorMapper.java @@ -1,8 +1,8 @@ -package adf_core_python.gateway.mapper.module.complex; +package adf_core_python.core.gateway.mapper.module.complex; -import adf.core.agent.communication.MessageManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.complex.RoadDetector; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.complex.RoadDetector; public class RoadDetectorMapper extends TargetDetectorMapper { public RoadDetectorMapper(RoadDetector roadDetector, PrecomputeData precomputeData, MessageManager messageManager) { diff --git a/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/SearchMapper.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/SearchMapper.java new file mode 100644 index 0000000..7cf6d41 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/SearchMapper.java @@ -0,0 +1,11 @@ +package adf_core_python.core.gateway.mapper.module.complex; + +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.complex.Search; + +public class SearchMapper extends TargetDetectorMapper { + public SearchMapper(Search search, PrecomputeData precomputeData, MessageManager messageManager) { + super(search, precomputeData, messageManager); + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/TargetAllocatorMapper.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/TargetAllocatorMapper.java similarity index 76% rename from java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/TargetAllocatorMapper.java rename to java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/TargetAllocatorMapper.java index 78ba9d2..d211833 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/TargetAllocatorMapper.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/TargetAllocatorMapper.java @@ -1,9 +1,9 @@ -package adf_core_python.gateway.mapper.module.complex; +package adf_core_python.core.gateway.mapper.module.complex; -import adf.core.agent.communication.MessageManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.complex.TargetAllocator; -import adf_core_python.gateway.mapper.module.AbstractModuleMapper; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.complex.TargetAllocator; +import adf_core_python.core.gateway.mapper.module.AbstractModuleMapper; import rescuecore2.config.Config; import rescuecore2.worldmodel.EntityID; diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/TargetDetectorMapper.java b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/TargetDetectorMapper.java similarity index 76% rename from java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/TargetDetectorMapper.java rename to java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/TargetDetectorMapper.java index e0bb851..caaba9b 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/TargetDetectorMapper.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/mapper/module/complex/TargetDetectorMapper.java @@ -1,9 +1,9 @@ -package adf_core_python.gateway.mapper.module.complex; +package adf_core_python.core.gateway.mapper.module.complex; -import adf.core.agent.communication.MessageManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.complex.TargetDetector; -import adf_core_python.gateway.mapper.module.AbstractModuleMapper; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.complex.TargetDetector; +import adf_core_python.core.gateway.mapper.module.AbstractModuleMapper; import rescuecore2.config.Config; import rescuecore2.worldmodel.EntityID; diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/AMAgent.java b/java/lib/src/main/java/adf_core_python/core/gateway/message/AMAgent.java similarity index 91% rename from java/lib/src/main/java/adf_core_python/gateway/message/AMAgent.java rename to java/lib/src/main/java/adf_core_python/core/gateway/message/AMAgent.java index a7e34a5..17775b8 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/message/AMAgent.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/message/AMAgent.java @@ -1,7 +1,7 @@ -package adf_core_python.gateway.message; +package adf_core_python.core.gateway.message; -import adf_core_python.gateway.message.urn.ModuleMessageComponentURN; -import adf_core_python.gateway.message.urn.ModuleMessageURN; +import adf_core_python.core.gateway.message.urn.ModuleMessageComponentURN; +import adf_core_python.core.gateway.message.urn.ModuleMessageURN; import rescuecore2.config.Config; import rescuecore2.messages.AbstractMessage; import rescuecore2.messages.components.ConfigComponent; diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/AMExec.java b/java/lib/src/main/java/adf_core_python/core/gateway/message/AMExec.java similarity index 89% rename from java/lib/src/main/java/adf_core_python/gateway/message/AMExec.java rename to java/lib/src/main/java/adf_core_python/core/gateway/message/AMExec.java index 199f97d..5f6da2d 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/message/AMExec.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/message/AMExec.java @@ -1,7 +1,7 @@ -package adf_core_python.gateway.message; +package adf_core_python.core.gateway.message; -import adf_core_python.gateway.message.urn.ModuleMessageComponentURN; -import adf_core_python.gateway.message.urn.ModuleMessageURN; +import adf_core_python.core.gateway.message.urn.ModuleMessageComponentURN; +import adf_core_python.core.gateway.message.urn.ModuleMessageURN; import rescuecore2.config.Config; import rescuecore2.messages.AbstractMessage; import rescuecore2.messages.components.ConfigComponent; diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/AMModule.java b/java/lib/src/main/java/adf_core_python/core/gateway/message/AMModule.java similarity index 89% rename from java/lib/src/main/java/adf_core_python/gateway/message/AMModule.java rename to java/lib/src/main/java/adf_core_python/core/gateway/message/AMModule.java index a597897..10217ed 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/message/AMModule.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/message/AMModule.java @@ -1,7 +1,7 @@ -package adf_core_python.gateway.message; +package adf_core_python.core.gateway.message; -import adf_core_python.gateway.message.urn.ModuleMessageComponentURN; -import adf_core_python.gateway.message.urn.ModuleMessageURN; +import adf_core_python.core.gateway.message.urn.ModuleMessageComponentURN; +import adf_core_python.core.gateway.message.urn.ModuleMessageURN; import rescuecore2.messages.AbstractMessage; import rescuecore2.messages.components.StringComponent; import rescuecore2.messages.protobuf.RCRSProto; diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/AMUpdate.java b/java/lib/src/main/java/adf_core_python/core/gateway/message/AMUpdate.java similarity index 90% rename from java/lib/src/main/java/adf_core_python/gateway/message/AMUpdate.java rename to java/lib/src/main/java/adf_core_python/core/gateway/message/AMUpdate.java index ab3b03e..c3c21b5 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/message/AMUpdate.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/message/AMUpdate.java @@ -1,7 +1,7 @@ -package adf_core_python.gateway.message; +package adf_core_python.core.gateway.message; -import adf_core_python.gateway.message.urn.ModuleMessageComponentURN; -import adf_core_python.gateway.message.urn.ModuleMessageURN; +import adf_core_python.core.gateway.message.urn.ModuleMessageComponentURN; +import adf_core_python.core.gateway.message.urn.ModuleMessageURN; import rescuecore2.messages.AbstractMessage; import rescuecore2.messages.Command; import rescuecore2.messages.components.ChangeSetComponent; diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/MAExecResponse.java b/java/lib/src/main/java/adf_core_python/core/gateway/message/MAExecResponse.java similarity index 87% rename from java/lib/src/main/java/adf_core_python/gateway/message/MAExecResponse.java rename to java/lib/src/main/java/adf_core_python/core/gateway/message/MAExecResponse.java index e648838..ecd26df 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/message/MAExecResponse.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/message/MAExecResponse.java @@ -1,7 +1,7 @@ -package adf_core_python.gateway.message; +package adf_core_python.core.gateway.message; -import adf_core_python.gateway.message.urn.ModuleMessageComponentURN; -import adf_core_python.gateway.message.urn.ModuleMessageURN; +import adf_core_python.core.gateway.message.urn.ModuleMessageComponentURN; +import adf_core_python.core.gateway.message.urn.ModuleMessageURN; import rescuecore2.config.Config; import rescuecore2.messages.AbstractMessage; import rescuecore2.messages.components.ConfigComponent; diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/MAModuleResponse.java b/java/lib/src/main/java/adf_core_python/core/gateway/message/MAModuleResponse.java similarity index 87% rename from java/lib/src/main/java/adf_core_python/gateway/message/MAModuleResponse.java rename to java/lib/src/main/java/adf_core_python/core/gateway/message/MAModuleResponse.java index d568a61..bbd50d9 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/message/MAModuleResponse.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/message/MAModuleResponse.java @@ -1,7 +1,7 @@ -package adf_core_python.gateway.message; +package adf_core_python.core.gateway.message; -import adf_core_python.gateway.message.urn.ModuleMessageComponentURN; -import adf_core_python.gateway.message.urn.ModuleMessageURN; +import adf_core_python.core.gateway.message.urn.ModuleMessageComponentURN; +import adf_core_python.core.gateway.message.urn.ModuleMessageURN; import rescuecore2.messages.AbstractMessage; import rescuecore2.messages.components.StringComponent; import rescuecore2.messages.protobuf.RCRSProto; diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/ModuleControlMessageFactory.java b/java/lib/src/main/java/adf_core_python/core/gateway/message/ModuleControlMessageFactory.java similarity index 93% rename from java/lib/src/main/java/adf_core_python/gateway/message/ModuleControlMessageFactory.java rename to java/lib/src/main/java/adf_core_python/core/gateway/message/ModuleControlMessageFactory.java index 3952131..c6b4ac7 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/message/ModuleControlMessageFactory.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/message/ModuleControlMessageFactory.java @@ -1,6 +1,6 @@ -package adf_core_python.gateway.message; +package adf_core_python.core.gateway.message; -import adf_core_python.gateway.message.urn.ModuleMessageURN; +import adf_core_python.core.gateway.message.urn.ModuleMessageURN; import rescuecore2.messages.Message; import rescuecore2.messages.protobuf.RCRSProto.MessageProto; diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/urn/ModuleMessageComponentURN.java b/java/lib/src/main/java/adf_core_python/core/gateway/message/urn/ModuleMessageComponentURN.java similarity index 96% rename from java/lib/src/main/java/adf_core_python/gateway/message/urn/ModuleMessageComponentURN.java rename to java/lib/src/main/java/adf_core_python/core/gateway/message/urn/ModuleMessageComponentURN.java index 868439c..c89232d 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/message/urn/ModuleMessageComponentURN.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/message/urn/ModuleMessageComponentURN.java @@ -1,4 +1,4 @@ -package adf_core_python.gateway.message.urn; +package adf_core_python.core.gateway.message.urn; import rescuecore2.URN; diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/urn/ModuleMessageURN.java b/java/lib/src/main/java/adf_core_python/core/gateway/message/urn/ModuleMessageURN.java similarity index 96% rename from java/lib/src/main/java/adf_core_python/gateway/message/urn/ModuleMessageURN.java rename to java/lib/src/main/java/adf_core_python/core/gateway/message/urn/ModuleMessageURN.java index 8c90a4e..83358b5 100644 --- a/java/lib/src/main/java/adf_core_python/gateway/message/urn/ModuleMessageURN.java +++ b/java/lib/src/main/java/adf_core_python/core/gateway/message/urn/ModuleMessageURN.java @@ -1,4 +1,4 @@ -package adf_core_python.gateway.message.urn; +package adf_core_python.core.gateway.message.urn; import rescuecore2.URN; diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/SearchMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/SearchMapper.java deleted file mode 100644 index 94f42db..0000000 --- a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/SearchMapper.java +++ /dev/null @@ -1,11 +0,0 @@ -package adf_core_python.gateway.mapper.module.complex; - -import adf.core.agent.communication.MessageManager; -import adf_core_python.agent.precompute.PrecomputeData; -import adf_core_python.component.module.complex.Search; - -public class SearchMapper extends TargetDetectorMapper { - public SearchMapper(Search search, PrecomputeData precomputeData, MessageManager messageManager) { - super(search, precomputeData, messageManager); - } -} diff --git a/java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionClear.java b/java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionClear.java new file mode 100644 index 0000000..bb415cb --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionClear.java @@ -0,0 +1,803 @@ +package adf_core_python.impl.extaction; + +import adf.core.agent.action.Action; +import adf.core.agent.action.common.ActionMove; +import adf.core.agent.action.common.ActionRest; +import adf.core.agent.action.police.ActionClear; +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.extaction.ExtAction; +import adf_core_python.core.component.module.algorithm.PathPlanning; +import com.google.common.collect.Lists; +import rescuecore2.config.NoSuchConfigOptionException; +import rescuecore2.misc.geometry.GeometryTools2D; +import rescuecore2.misc.geometry.Line2D; +import rescuecore2.misc.geometry.Point2D; +import rescuecore2.misc.geometry.Vector2D; +import rescuecore2.standard.entities.*; +import rescuecore2.worldmodel.EntityID; + +import java.util.*; +import java.util.stream.Collectors; + +public class DefaultExtActionClear extends ExtAction { + + private PathPlanning pathPlanning; + + private int clearDistance; + private int forcedMove; + private int thresholdRest; + private int kernelTime; + + private EntityID target; + private Map> movePointCache; + private int oldClearX; + private int oldClearY; + private int count; + + public DefaultExtActionClear(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + this.clearDistance = si.getClearRepairDistance(); + this.forcedMove = developData + .getInteger("adf.impl.extaction.DefaultExtActionClear.forcedMove", 3); + this.thresholdRest = developData + .getInteger("adf.impl.extaction.DefaultExtActionClear.rest", 100); + + this.target = null; + this.movePointCache = new HashMap<>(); + this.oldClearX = 0; + this.oldClearY = 0; + this.count = 0; + + switch (si.getMode()) { + case PRECOMPUTATION_PHASE: + case PRECOMPUTED: + case NON_PRECOMPUTE: + this.pathPlanning = moduleManager.getModule( + "DefaultExtActionClear.PathPlanning", + "adf_core_python.impl.module.algorithm.DijkstraPathPlanning"); + break; + } + } + + + @Override + public ExtAction precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + if (this.getCountPrecompute() >= 2) { + return this; + } + this.pathPlanning.precompute(precomputeData); + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + return this; + } + + + @Override + public ExtAction resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + if (this.getCountResume() >= 2) { + return this; + } + this.pathPlanning.resume(precomputeData); + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + return this; + } + + + @Override + public ExtAction preparate() { + super.preparate(); + if (this.getCountPreparate() >= 2) { + return this; + } + this.pathPlanning.preparate(); + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + return this; + } + + + @Override + public ExtAction updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + if (this.getCountUpdateInfo() >= 2) { + return this; + } + this.pathPlanning.updateInfo(messageManager); + return this; + } + + + @Override + public ExtAction setTarget(EntityID target) { + this.target = null; + StandardEntity entity = this.worldInfo.getEntity(target); + if (entity != null) { + if (entity instanceof Road) { + this.target = target; + } else if (entity.getStandardURN().equals(StandardEntityURN.BLOCKADE)) { + this.target = ((Blockade) entity).getPosition(); + } else if (entity instanceof Building) { + this.target = target; + } + } + return this; + } + + + @Override + public ExtAction calc() { + this.result = null; + PoliceForce policeForce = (PoliceForce) this.agentInfo.me(); + + if (this.needRest(policeForce)) { + List list = new ArrayList<>(); + if (this.target != null) { + list.add(this.target); + } + this.result = this.calcRest(policeForce, this.pathPlanning, list); + if (this.result != null) { + return this; + } + } + + if (this.target == null) { + return this; + } + EntityID agentPosition = policeForce.getPosition(); + StandardEntity targetEntity = this.worldInfo.getEntity(this.target); + StandardEntity positionEntity = Objects + .requireNonNull(this.worldInfo.getEntity(agentPosition)); + if (targetEntity == null || !(targetEntity instanceof Area)) { + return this; + } + if (positionEntity instanceof Road) { + this.result = this.getRescueAction(policeForce, (Road) positionEntity); + if (this.result != null) { + return this; + } + } + if (agentPosition.equals(this.target)) { + this.result = this.getAreaClearAction(policeForce, targetEntity); + } else if (((Area) targetEntity).getEdgeTo(agentPosition) != null) { + this.result = this.getNeighbourPositionAction(policeForce, + (Area) targetEntity); + } else { + List path = this.pathPlanning.getResult(agentPosition, + this.target); + if (path != null && path.size() > 0) { + int index = path.indexOf(agentPosition); + if (index == -1) { + Area area = (Area) positionEntity; + for (int i = 0; i < path.size(); i++) { + if (area.getEdgeTo(path.get(i)) != null) { + index = i; + break; + } + } + } else if (index >= 0) { + index++; + } + if (index >= 0 && index < (path.size())) { + StandardEntity entity = this.worldInfo.getEntity(path.get(index)); + this.result = this.getNeighbourPositionAction(policeForce, + (Area) entity); + if (this.result != null + && this.result.getClass() == ActionMove.class) { + if (!((ActionMove) this.result).getUsePosition()) { + this.result = null; + } + } + } + if (this.result == null) { + this.result = new ActionMove(path); + } + } + } + return this; + } + + + private Action getRescueAction(PoliceForce police, Road road) { + if (!road.isBlockadesDefined()) { + return null; + } + Collection blockades = this.worldInfo.getBlockades(road).stream() + .filter(Blockade::isApexesDefined).collect(Collectors.toSet()); + Collection agents = this.worldInfo.getEntitiesOfType( + StandardEntityURN.AMBULANCE_TEAM, StandardEntityURN.FIRE_BRIGADE); + + double policeX = police.getX(); + double policeY = police.getY(); + double minDistance = Double.MAX_VALUE; + Action moveAction = null; + for (StandardEntity entity : agents) { + Human human = (Human) entity; + if (!human.isPositionDefined() + || human.getPosition().getValue() != road.getID().getValue()) { + continue; + } + double humanX = human.getX(); + double humanY = human.getY(); + ActionClear actionClear = null; + for (Blockade blockade : blockades) { + if (!this.isInside(humanX, humanY, blockade.getApexes())) { + continue; + } + double distance = this.getDistance(policeX, policeY, humanX, humanY); + if (this.intersect(policeX, policeY, humanX, humanY, road)) { + Action action = this.getIntersectEdgeAction(policeX, policeY, humanX, + humanY, road); + if (action == null) { + continue; + } + if (action.getClass() == ActionClear.class) { + if (actionClear == null) { + actionClear = (ActionClear) action; + continue; + } + if (actionClear.getTarget() != null) { + Blockade another = (Blockade) this.worldInfo + .getEntity(actionClear.getTarget()); + if (another != null && this.intersect(blockade, another)) { + return new ActionClear(another); + } + int anotherDistance = this.worldInfo.getDistance(police, another); + int blockadeDistance = this.worldInfo.getDistance(police, + blockade); + if (anotherDistance > blockadeDistance) { + return action; + } + } + return actionClear; + } else if (action.getClass() == ActionMove.class + && distance < minDistance) { + minDistance = distance; + moveAction = action; + } + } else if (this.intersect(policeX, policeY, humanX, humanY, blockade)) { + Vector2D vector = this + .scaleClear(this.getVector(policeX, policeY, humanX, humanY)); + int clearX = (int) (policeX + vector.getX()); + int clearY = (int) (policeY + vector.getY()); + vector = this.scaleBackClear(vector); + int startX = (int) (policeX + vector.getX()); + int startY = (int) (policeY + vector.getY()); + if (this.intersect(startX, startY, clearX, clearY, blockade)) { + if (actionClear == null) { + actionClear = new ActionClear(clearX, clearY, blockade); + } else { + if (actionClear.getTarget() != null) { + Blockade another = (Blockade) this.worldInfo + .getEntity(actionClear.getTarget()); + if (another != null && this.intersect(blockade, another)) { + return new ActionClear(another); + } + int distance1 = this.worldInfo.getDistance(police, another); + int distance2 = this.worldInfo.getDistance(police, blockade); + if (distance1 > distance2) { + return new ActionClear(clearX, clearY, blockade); + } + } + return actionClear; + } + } else if (distance < minDistance) { + minDistance = distance; + moveAction = new ActionMove(Lists.newArrayList(road.getID()), + (int) humanX, (int) humanY); + } + } + } + if (actionClear != null) { + return actionClear; + } + } + return moveAction; + } + + + private Action getAreaClearAction(PoliceForce police, + StandardEntity targetEntity) { + if (targetEntity instanceof Building) { + return null; + } + Road road = (Road) targetEntity; + if (!road.isBlockadesDefined() || road.getBlockades().isEmpty()) { + return null; + } + Collection blockades = this.worldInfo.getBlockades(road).stream() + .filter(Blockade::isApexesDefined).collect(Collectors.toSet()); + int minDistance = Integer.MAX_VALUE; + Blockade clearBlockade = null; + for (Blockade blockade : blockades) { + for (Blockade another : blockades) { + if (!blockade.getID().equals(another.getID()) + && this.intersect(blockade, another)) { + int distance1 = this.worldInfo.getDistance(police, blockade); + int distance2 = this.worldInfo.getDistance(police, another); + if (distance1 <= distance2 && distance1 < minDistance) { + minDistance = distance1; + clearBlockade = blockade; + } else if (distance2 < minDistance) { + minDistance = distance2; + clearBlockade = another; + } + } + } + } + if (clearBlockade != null) { + if (minDistance < this.clearDistance) { + return new ActionClear(clearBlockade); + } else { + return new ActionMove(Lists.newArrayList(police.getPosition()), + clearBlockade.getX(), clearBlockade.getY()); + } + } + double agentX = police.getX(); + double agentY = police.getY(); + clearBlockade = null; + Double minPointDistance = Double.MAX_VALUE; + int clearX = 0; + int clearY = 0; + for (Blockade blockade : blockades) { + int[] apexes = blockade.getApexes(); + for (int i = 0; i < (apexes.length - 2); i += 2) { + double distance = this.getDistance(agentX, agentY, apexes[i], + apexes[i + 1]); + if (distance < minPointDistance) { + clearBlockade = blockade; + minPointDistance = distance; + clearX = apexes[i]; + clearY = apexes[i + 1]; + } + } + } + if (clearBlockade != null) { + if (minPointDistance < this.clearDistance) { + Vector2D vector = this + .scaleClear(this.getVector(agentX, agentY, clearX, clearY)); + clearX = (int) (agentX + vector.getX()); + clearY = (int) (agentY + vector.getY()); + return new ActionClear(clearX, clearY, clearBlockade); + } + return new ActionMove(Lists.newArrayList(police.getPosition()), clearX, + clearY); + } + return null; + } + + + private Action getNeighbourPositionAction(PoliceForce police, Area target) { + double agentX = police.getX(); + double agentY = police.getY(); + StandardEntity position = Objects + .requireNonNull(this.worldInfo.getPosition(police)); + Edge edge = target.getEdgeTo(position.getID()); + if (edge == null) { + return null; + } + if (position instanceof Road) { + Road road = (Road) position; + if (road.isBlockadesDefined() && road.getBlockades().size() > 0) { + double midX = (edge.getStartX() + edge.getEndX()) / 2; + double midY = (edge.getStartY() + edge.getEndY()) / 2; + if (this.intersect(agentX, agentY, midX, midY, road)) { + return this.getIntersectEdgeAction(agentX, agentY, edge, road); + } + ActionClear actionClear = null; + ActionMove actionMove = null; + Vector2D vector = this + .scaleClear(this.getVector(agentX, agentY, midX, midY)); + int clearX = (int) (agentX + vector.getX()); + int clearY = (int) (agentY + vector.getY()); + vector = this.scaleBackClear(vector); + int startX = (int) (agentX + vector.getX()); + int startY = (int) (agentY + vector.getY()); + for (Blockade blockade : this.worldInfo.getBlockades(road)) { + if (blockade == null || !blockade.isApexesDefined()) { + continue; + } + if (this.intersect(startX, startY, midX, midY, blockade)) { + if (this.intersect(startX, startY, clearX, clearY, blockade)) { + if (actionClear == null) { + actionClear = new ActionClear(clearX, clearY, blockade); + if (this.equalsPoint(this.oldClearX, this.oldClearY, clearX, + clearY)) { + if (this.count >= this.forcedMove) { + this.count = 0; + return new ActionMove(Lists.newArrayList(road.getID()), + clearX, clearY); + } + this.count++; + } + this.oldClearX = clearX; + this.oldClearY = clearY; + } else { + if (actionClear.getTarget() != null) { + Blockade another = (Blockade) this.worldInfo + .getEntity(actionClear.getTarget()); + if (another != null && this.intersect(blockade, another)) { + return new ActionClear(another); + } + } + return actionClear; + } + } else if (actionMove == null) { + actionMove = new ActionMove(Lists.newArrayList(road.getID()), + (int) midX, (int) midY); + } + } + } + if (actionClear != null) { + return actionClear; + } else if (actionMove != null) { + return actionMove; + } + } + } + if (target instanceof Road) { + Road road = (Road) target; + if (!road.isBlockadesDefined() || road.getBlockades().isEmpty()) { + return new ActionMove( + Lists.newArrayList(position.getID(), target.getID())); + } + Blockade clearBlockade = null; + Double minPointDistance = Double.MAX_VALUE; + int clearX = 0; + int clearY = 0; + for (EntityID id : road.getBlockades()) { + Blockade blockade = (Blockade) this.worldInfo.getEntity(id); + if (blockade != null && blockade.isApexesDefined()) { + int[] apexes = blockade.getApexes(); + for (int i = 0; i < (apexes.length - 2); i += 2) { + double distance = this.getDistance(agentX, agentY, apexes[i], + apexes[i + 1]); + if (distance < minPointDistance) { + clearBlockade = blockade; + minPointDistance = distance; + clearX = apexes[i]; + clearY = apexes[i + 1]; + } + } + } + } + if (clearBlockade != null && minPointDistance < this.clearDistance) { + Vector2D vector = this + .scaleClear(this.getVector(agentX, agentY, clearX, clearY)); + clearX = (int) (agentX + vector.getX()); + clearY = (int) (agentY + vector.getY()); + if (this.equalsPoint(this.oldClearX, this.oldClearY, clearX, clearY)) { + if (this.count >= this.forcedMove) { + this.count = 0; + return new ActionMove(Lists.newArrayList(road.getID()), clearX, + clearY); + } + this.count++; + } + this.oldClearX = clearX; + this.oldClearY = clearY; + return new ActionClear(clearX, clearY, clearBlockade); + } + } + return new ActionMove(Lists.newArrayList(position.getID(), target.getID())); + } + + + private Action getIntersectEdgeAction(double agentX, double agentY, Edge edge, + Road road) { + double midX = (edge.getStartX() + edge.getEndX()) / 2; + double midY = (edge.getStartY() + edge.getEndY()) / 2; + return this.getIntersectEdgeAction(agentX, agentY, midX, midY, road); + } + + + private Action getIntersectEdgeAction(double agentX, double agentY, + double pointX, double pointY, Road road) { + Set movePoints = this.getMovePoints(road); + Point2D bestPoint = null; + double bastDistance = Double.MAX_VALUE; + for (Point2D p : movePoints) { + if (!this.intersect(agentX, agentY, p.getX(), p.getY(), road)) { + if (!this.intersect(pointX, pointY, p.getX(), p.getY(), road)) { + double distance = this.getDistance(pointX, pointY, p.getX(), + p.getY()); + if (distance < bastDistance) { + bestPoint = p; + bastDistance = distance; + } + } + } + } + if (bestPoint != null) { + double pX = bestPoint.getX(); + double pY = bestPoint.getY(); + if (!road.isBlockadesDefined()) { + return new ActionMove(Lists.newArrayList(road.getID()), (int) pX, + (int) pY); + } + ActionClear actionClear = null; + ActionMove actionMove = null; + Vector2D vector = this.scaleClear(this.getVector(agentX, agentY, pX, pY)); + int clearX = (int) (agentX + vector.getX()); + int clearY = (int) (agentY + vector.getY()); + vector = this.scaleBackClear(vector); + int startX = (int) (agentX + vector.getX()); + int startY = (int) (agentY + vector.getY()); + for (Blockade blockade : this.worldInfo.getBlockades(road)) { + if (this.intersect(startX, startY, pX, pY, blockade)) { + if (this.intersect(startX, startY, clearX, clearY, blockade)) { + if (actionClear == null) { + actionClear = new ActionClear(clearX, clearY, blockade); + } else { + if (actionClear.getTarget() != null) { + Blockade another = (Blockade) this.worldInfo + .getEntity(actionClear.getTarget()); + if (another != null && this.intersect(blockade, another)) { + return new ActionClear(another); + } + } + return actionClear; + } + } else if (actionMove == null) { + actionMove = new ActionMove(Lists.newArrayList(road.getID()), + (int) pX, (int) pY); + } + } + } + if (actionClear != null) { + return actionClear; + } else if (actionMove != null) { + return actionMove; + } + } + Action action = this.getAreaClearAction((PoliceForce) this.agentInfo.me(), + road); + if (action == null) { + action = new ActionMove(Lists.newArrayList(road.getID()), (int) pointX, + (int) pointY); + } + return action; + } + + + private boolean equalsPoint(double p1X, double p1Y, double p2X, double p2Y) { + return this.equalsPoint(p1X, p1Y, p2X, p2Y, 1000.0D); + } + + + private boolean equalsPoint(double p1X, double p1Y, double p2X, double p2Y, + double range) { + return (p2X - range < p1X && p1X < p2X + range) + && (p2Y - range < p1Y && p1Y < p2Y + range); + } + + + private boolean isInside(double pX, double pY, int[] apex) { + Point2D p = new Point2D(pX, pY); + Vector2D v1 = (new Point2D(apex[apex.length - 2], apex[apex.length - 1])) + .minus(p); + Vector2D v2 = (new Point2D(apex[0], apex[1])).minus(p); + double theta = this.getAngle(v1, v2); + + for (int i = 0; i < apex.length - 2; i += 2) { + v1 = (new Point2D(apex[i], apex[i + 1])).minus(p); + v2 = (new Point2D(apex[i + 2], apex[i + 3])).minus(p); + theta += this.getAngle(v1, v2); + } + return Math.round(Math.abs((theta / 2) / Math.PI)) >= 1; + } + + + private boolean intersect(double agentX, double agentY, double pointX, + double pointY, Area area) { + for (Edge edge : area.getEdges()) { + double startX = edge.getStartX(); + double startY = edge.getStartY(); + double endX = edge.getEndX(); + double endY = edge.getEndY(); + if (java.awt.geom.Line2D.linesIntersect(agentX, agentY, pointX, pointY, + startX, startY, endX, endY)) { + double midX = (edge.getStartX() + edge.getEndX()) / 2; + double midY = (edge.getStartY() + edge.getEndY()) / 2; + if (!equalsPoint(pointX, pointY, midX, midY) + && !equalsPoint(agentX, agentY, midX, midY)) { + return true; + } + } + } + return false; + } + + + private boolean intersect(Blockade blockade, Blockade another) { + if (blockade.isApexesDefined() && another.isApexesDefined()) { + int[] apexes0 = blockade.getApexes(); + int[] apexes1 = another.getApexes(); + for (int i = 0; i < (apexes0.length - 2); i += 2) { + for (int j = 0; j < (apexes1.length - 2); j += 2) { + if (java.awt.geom.Line2D.linesIntersect(apexes0[i], apexes0[i + 1], + apexes0[i + 2], apexes0[i + 3], apexes1[j], apexes1[j + 1], + apexes1[j + 2], apexes1[j + 3])) { + return true; + } + } + } + for (int i = 0; i < (apexes0.length - 2); i += 2) { + if (java.awt.geom.Line2D.linesIntersect(apexes0[i], apexes0[i + 1], + apexes0[i + 2], apexes0[i + 3], apexes1[apexes1.length - 2], + apexes1[apexes1.length - 1], apexes1[0], apexes1[1])) { + return true; + } + } + for (int j = 0; j < (apexes1.length - 2); j += 2) { + if (java.awt.geom.Line2D.linesIntersect(apexes0[apexes0.length - 2], + apexes0[apexes0.length - 1], apexes0[0], apexes0[1], apexes1[j], + apexes1[j + 1], apexes1[j + 2], apexes1[j + 3])) { + return true; + } + } + } + return false; + } + + + private boolean intersect(double agentX, double agentY, double pointX, + double pointY, Blockade blockade) { + List lines = GeometryTools2D.pointsToLines( + GeometryTools2D.vertexArrayToPoints(blockade.getApexes()), true); + for (Line2D line : lines) { + Point2D start = line.getOrigin(); + Point2D end = line.getEndPoint(); + double startX = start.getX(); + double startY = start.getY(); + double endX = end.getX(); + double endY = end.getY(); + if (java.awt.geom.Line2D.linesIntersect(agentX, agentY, pointX, pointY, + startX, startY, endX, endY)) { + return true; + } + } + return false; + } + + + private double getDistance(double fromX, double fromY, double toX, + double toY) { + double dx = toX - fromX; + double dy = toY - fromY; + return Math.hypot(dx, dy); + } + + + private double getAngle(Vector2D v1, Vector2D v2) { + double flag = (v1.getX() * v2.getY()) - (v1.getY() * v2.getX()); + double angle = Math.acos(((v1.getX() * v2.getX()) + (v1.getY() * v2.getY())) + / (v1.getLength() * v2.getLength())); + if (flag > 0) { + return angle; + } + if (flag < 0) { + return -1 * angle; + } + return 0.0D; + } + + + private Vector2D getVector(double fromX, double fromY, double toX, + double toY) { + return (new Point2D(toX, toY)).minus(new Point2D(fromX, fromY)); + } + + + private Vector2D scaleClear(Vector2D vector) { + return vector.normalised().scale(this.clearDistance); + } + + + private Vector2D scaleBackClear(Vector2D vector) { + return vector.normalised().scale(-510); + } + + + private Set getMovePoints(Road road) { + Set points = this.movePointCache.get(road.getID()); + if (points == null) { + points = new HashSet<>(); + int[] apex = road.getApexList(); + for (int i = 0; i < apex.length; i += 2) { + for (int j = i + 2; j < apex.length; j += 2) { + double midX = (apex[i] + apex[j]) / 2; + double midY = (apex[i + 1] + apex[j + 1]) / 2; + if (this.isInside(midX, midY, apex)) { + points.add(new Point2D(midX, midY)); + } + } + } + for (Edge edge : road.getEdges()) { + double midX = (edge.getStartX() + edge.getEndX()) / 2; + double midY = (edge.getStartY() + edge.getEndY()) / 2; + points.remove(new Point2D(midX, midY)); + } + this.movePointCache.put(road.getID(), points); + } + return points; + } + + + private boolean needRest(Human agent) { + int hp = agent.getHP(); + int damage = agent.getDamage(); + if (damage == 0 || hp == 0) { + return false; + } + int activeTime = (hp / damage) + ((hp % damage) != 0 ? 1 : 0); + if (this.kernelTime == -1) { + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + } + return damage >= this.thresholdRest + || (activeTime + this.agentInfo.getTime()) < this.kernelTime; + } + + + private Action calcRest(Human human, PathPlanning pathPlanning, + Collection targets) { + EntityID position = human.getPosition(); + Collection refuges = this.worldInfo + .getEntityIDsOfType(StandardEntityURN.REFUGE); + int currentSize = refuges.size(); + if (refuges.contains(position)) { + return new ActionRest(); + } + List firstResult = null; + while (refuges.size() > 0) { + pathPlanning.setFrom(position); + pathPlanning.setDestination(refuges); + List path = pathPlanning.calc().getResult(); + if (path != null && path.size() > 0) { + if (firstResult == null) { + firstResult = new ArrayList<>(path); + if (targets == null || targets.isEmpty()) { + break; + } + } + EntityID refugeID = path.get(path.size() - 1); + pathPlanning.setFrom(refugeID); + pathPlanning.setDestination(targets); + List fromRefugeToTarget = pathPlanning.calc().getResult(); + if (fromRefugeToTarget != null && fromRefugeToTarget.size() > 0) { + return new ActionMove(path); + } + refuges.remove(refugeID); + // remove failed + if (currentSize == refuges.size()) { + break; + } + currentSize = refuges.size(); + } else { + break; + } + } + return firstResult != null ? new ActionMove(firstResult) : null; + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionFireFighting.java b/java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionFireFighting.java new file mode 100644 index 0000000..f5ae30c --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionFireFighting.java @@ -0,0 +1,351 @@ +package adf_core_python_core_python.impl.extaction; + +import adf.core.agent.action.Action; +import adf.core.agent.action.common.ActionMove; +import adf.core.agent.action.common.ActionRest; +import adf.core.agent.action.fire.ActionExtinguish; +import adf.core.agent.action.fire.ActionRefill; +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.extaction.ExtAction; +import adf_core_python.core.component.module.algorithm.PathPlanning; +import rescuecore2.config.NoSuchConfigOptionException; +import rescuecore2.standard.entities.*; +import rescuecore2.worldmodel.EntityID; + +import java.util.*; + +import static rescuecore2.standard.entities.StandardEntityURN.HYDRANT; +import static rescuecore2.standard.entities.StandardEntityURN.REFUGE; + +public class DefaultExtActionFireFighting extends ExtAction { + + private PathPlanning pathPlanning; + + private int maxExtinguishDistance; + private int maxExtinguishPower; + private int thresholdRest; + private int kernelTime; + private int refillCompleted; + private int refillRequest; + private boolean refillFlag; + + private EntityID target; + + public DefaultExtActionFireFighting(AgentInfo agentInfo, WorldInfo worldInfo, ScenarioInfo scenarioInfo, ModuleManager moduleManager, DevelopData developData) { + super(agentInfo, worldInfo, scenarioInfo, moduleManager, developData); + this.maxExtinguishDistance = scenarioInfo.getFireExtinguishMaxDistance(); + this.maxExtinguishPower = scenarioInfo.getFireExtinguishMaxSum(); + this.thresholdRest = developData.getInteger( + "adf.impl.extaction.DefaultExtActionFireFighting.rest", 100); + int maxWater = scenarioInfo.getFireTankMaximum(); + this.refillCompleted = (maxWater / 10) * developData.getInteger( + "adf.impl.extaction.DefaultExtActionFireFighting.refill.completed", 10); + this.refillRequest = this.maxExtinguishPower * developData.getInteger( + "adf.impl.extaction.DefaultExtActionFireFighting.refill.request", 1); + this.refillFlag = false; + + this.target = null; + + switch (scenarioInfo.getMode()) { + case PRECOMPUTATION_PHASE: + case PRECOMPUTED: + case NON_PRECOMPUTE: + this.pathPlanning = moduleManager.getModule( + "DefaultExtActionFireFighting.PathPlanning", + "adf_core_python.impl.module.algorithm.DijkstraPathPlanning"); + break; + } + } + + + @Override + public ExtAction precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + if (this.getCountPrecompute() >= 2) { + return this; + } + this.pathPlanning.precompute(precomputeData); + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + return this; + } + + + @Override + public ExtAction resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + if (this.getCountResume() >= 2) { + return this; + } + this.pathPlanning.resume(precomputeData); + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + return this; + } + + + @Override + public ExtAction preparate() { + super.preparate(); + if (this.getCountPreparate() >= 2) { + return this; + } + this.pathPlanning.preparate(); + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + return this; + } + + + @Override + public ExtAction updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + if (this.getCountUpdateInfo() >= 2) { + return this; + } + this.pathPlanning.updateInfo(messageManager); + return this; + } + + + @Override + public ExtAction setTarget(EntityID target) { + this.target = null; + if (target != null) { + StandardEntity entity = this.worldInfo.getEntity(target); + if (entity instanceof Building) { + this.target = target; + } + } + return this; + } + + + @Override + public ExtAction calc() { + this.result = null; + FireBrigade agent = (FireBrigade) this.agentInfo.me(); + + this.refillFlag = this.needRefill(agent, this.refillFlag); + if (this.refillFlag) { + this.result = this.calcRefill(agent, this.pathPlanning, this.target); + if (this.result != null) { + return this; + } + } + + if (this.needRest(agent)) { + this.result = this.calcRefugeAction(agent, this.pathPlanning, this.target, + false); + if (this.result != null) { + return this; + } + } + + if (this.target == null) { + return this; + } + this.result = this.calcExtinguish(agent, this.pathPlanning, this.target); + return this; + } + + + private Action calcExtinguish(FireBrigade agent, PathPlanning pathPlanning, + EntityID target) { + EntityID agentPosition = agent.getPosition(); + StandardEntity positionEntity = Objects + .requireNonNull(this.worldInfo.getPosition(agent)); + if (StandardEntityURN.REFUGE == positionEntity.getStandardURN()) { + Action action = this.getMoveAction(pathPlanning, agentPosition, target); + if (action != null) { + return action; + } + } + + List neighbourBuilding = new ArrayList<>(); + StandardEntity entity = this.worldInfo.getEntity(target); + if (entity instanceof Building) { + if (this.worldInfo.getDistance(positionEntity, + entity) < this.maxExtinguishDistance) { + neighbourBuilding.add(entity); + } + } + + if (neighbourBuilding.size() > 0) { + neighbourBuilding.sort(new DistanceSorter(this.worldInfo, agent)); + return new ActionExtinguish(neighbourBuilding.get(0).getID(), + this.maxExtinguishPower); + } + return this.getMoveAction(pathPlanning, agentPosition, target); + } + + + private Action getMoveAction(PathPlanning pathPlanning, EntityID from, + EntityID target) { + pathPlanning.setFrom(from); + pathPlanning.setDestination(target); + List path = pathPlanning.calc().getResult(); + if (path != null && path.size() > 0) { + StandardEntity entity = this.worldInfo + .getEntity(path.get(path.size() - 1)); + if (entity instanceof Building) { + if (entity.getStandardURN() != StandardEntityURN.REFUGE) { + path.remove(path.size() - 1); + } + } + return new ActionMove(path); + } + return null; + } + + + private boolean needRefill(FireBrigade agent, boolean refillFlag) { + if (refillFlag) { + StandardEntityURN positionURN = Objects + .requireNonNull(this.worldInfo.getPosition(agent)).getStandardURN(); + return !(positionURN == REFUGE || positionURN == HYDRANT) + || agent.getWater() < this.refillCompleted; + } + return agent.getWater() <= this.refillRequest; + } + + + private boolean needRest(Human agent) { + int hp = agent.getHP(); + int damage = agent.getDamage(); + if (hp == 0 || damage == 0) { + return false; + } + int activeTime = (hp / damage) + ((hp % damage) != 0 ? 1 : 0); + if (this.kernelTime == -1) { + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + } + return damage >= this.thresholdRest + || (activeTime + this.agentInfo.getTime()) < this.kernelTime; + } + + + private Action calcRefill(FireBrigade agent, PathPlanning pathPlanning, + EntityID target) { + StandardEntityURN positionURN = Objects + .requireNonNull(this.worldInfo.getPosition(agent)).getStandardURN(); + if (positionURN == REFUGE) { + return new ActionRefill(); + } + Action action = this.calcRefugeAction(agent, pathPlanning, target, true); + if (action != null) { + return action; + } + action = this.calcHydrantAction(agent, pathPlanning, target); + if (action != null) { + if (positionURN == HYDRANT + && action.getClass().equals(ActionMove.class)) { + pathPlanning.setFrom(agent.getPosition()); + pathPlanning.setDestination(target); + double currentDistance = pathPlanning.calc().getDistance(); + List path = ((ActionMove) action).getPath(); + pathPlanning.setFrom(path.get(path.size() - 1)); + pathPlanning.setDestination(target); + double newHydrantDistance = pathPlanning.calc().getDistance(); + if (currentDistance <= newHydrantDistance) { + return new ActionRefill(); + } + } + return action; + } + return null; + } + + + private Action calcRefugeAction(Human human, PathPlanning pathPlanning, + EntityID target, boolean isRefill) { + return this.calcSupplyAction(human, pathPlanning, + this.worldInfo.getEntityIDsOfType(StandardEntityURN.REFUGE), target, + isRefill); + } + + + private Action calcHydrantAction(Human human, PathPlanning pathPlanning, + EntityID target) { + Collection hydrants = this.worldInfo.getEntityIDsOfType(HYDRANT); + hydrants.remove(human.getPosition()); + return this.calcSupplyAction(human, pathPlanning, hydrants, target, true); + } + + + private Action calcSupplyAction(Human human, PathPlanning pathPlanning, + Collection supplyPositions, EntityID target, boolean isRefill) { + EntityID position = human.getPosition(); + int size = supplyPositions.size(); + if (supplyPositions.contains(position)) { + return isRefill ? new ActionRefill() : new ActionRest(); + } + List firstResult = null; + while (supplyPositions.size() > 0) { + pathPlanning.setFrom(position); + pathPlanning.setDestination(supplyPositions); + List path = pathPlanning.calc().getResult(); + if (path != null && path.size() > 0) { + if (firstResult == null) { + firstResult = new ArrayList<>(path); + if (target == null) { + break; + } + } + EntityID supplyPositionID = path.get(path.size() - 1); + pathPlanning.setFrom(supplyPositionID); + pathPlanning.setDestination(target); + List fromRefugeToTarget = pathPlanning.calc().getResult(); + if (fromRefugeToTarget != null && fromRefugeToTarget.size() > 0) { + return new ActionMove(path); + } + supplyPositions.remove(supplyPositionID); + // remove failed + if (size == supplyPositions.size()) { + break; + } + size = supplyPositions.size(); + } else { + break; + } + } + return firstResult != null ? new ActionMove(firstResult) : null; + } + + private class DistanceSorter implements Comparator { + + private StandardEntity reference; + private WorldInfo worldInfo; + + DistanceSorter(WorldInfo wi, StandardEntity reference) { + this.reference = reference; + this.worldInfo = wi; + } + + + public int compare(StandardEntity a, StandardEntity b) { + int d1 = this.worldInfo.getDistance(this.reference, a); + int d2 = this.worldInfo.getDistance(this.reference, b); + return d1 - d2; + } + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionFireRescue.java b/java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionFireRescue.java new file mode 100644 index 0000000..05f4bb7 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionFireRescue.java @@ -0,0 +1,227 @@ +package adf_core_python.impl.extaction; + +import adf.core.agent.action.Action; +import adf.core.agent.action.ambulance.ActionRescue; +import adf.core.agent.action.common.ActionMove; +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.extaction.ExtAction; +import adf_core_python.core.component.module.algorithm.PathPlanning; +import rescuecore2.config.NoSuchConfigOptionException; +import rescuecore2.standard.entities.*; +import rescuecore2.worldmodel.EntityID; + +import java.util.ArrayList; +import java.util.List; + +import static rescuecore2.standard.entities.StandardEntityURN.BLOCKADE; + +public class DefaultExtActionFireRescue extends ExtAction { + + private PathPlanning pathPlanning; + + private int thresholdRest; + private int kernelTime; + + private EntityID target; + + public DefaultExtActionFireRescue(AgentInfo agentInfo, WorldInfo worldInfo, ScenarioInfo scenarioInfo, ModuleManager moduleManager, DevelopData developData) { + super(agentInfo, worldInfo, scenarioInfo, moduleManager, developData); + this.target = null; + this.thresholdRest = developData + .getInteger("adf.impl.extaction.DefaultExtActionFireRescue.rest", 100); + + switch (scenarioInfo.getMode()) { + case PRECOMPUTATION_PHASE: + case PRECOMPUTED: + case NON_PRECOMPUTE: + this.pathPlanning = moduleManager.getModule( + "DefaultExtActionFireRescue.PathPlanning", + "adf_core_python.impl.module.algorithm.DijkstraPathPlanning"); + break; + } + } + + + public ExtAction precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + if (this.getCountPrecompute() >= 2) { + return this; + } + this.pathPlanning.precompute(precomputeData); + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + return this; + } + + + public ExtAction resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + if (this.getCountResume() >= 2) { + return this; + } + this.pathPlanning.resume(precomputeData); + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + return this; + } + + + public ExtAction preparate() { + super.preparate(); + if (this.getCountPreparate() >= 2) { + return this; + } + this.pathPlanning.preparate(); + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + return this; + } + + + public ExtAction updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + if (this.getCountUpdateInfo() >= 2) { + return this; + } + this.pathPlanning.updateInfo(messageManager); + return this; + } + + + @Override + public ExtAction setTarget(EntityID target) { + this.target = null; + if (target != null) { + StandardEntity entity = this.worldInfo.getEntity(target); + if (entity instanceof Human || entity instanceof Area) { + this.target = target; + return this; + } + } + return this; + } + + + @Override + public ExtAction calc() { + this.result = null; + FireBrigade agent = (FireBrigade) this.agentInfo.me(); + + if (this.needRest(agent)) { + EntityID areaID = this.convertArea(this.target); + ArrayList targets = new ArrayList<>(); + if (areaID != null) { + targets.add(areaID); + } + } + if (this.target != null) { + this.result = this.calcRescue(agent, this.pathPlanning, this.target); + } + return this; + } + + + private Action calcRescue(FireBrigade agent, PathPlanning pathPlanning, + EntityID targetID) { + StandardEntity targetEntity = this.worldInfo.getEntity(targetID); + if (targetEntity == null) { + return null; + } + EntityID agentPosition = agent.getPosition(); + if (targetEntity instanceof Human) { + Human human = (Human) targetEntity; + if (!human.isPositionDefined()) { + return null; + } + if (human.isHPDefined() && human.getHP() == 0) { + return null; + } + EntityID targetPosition = human.getPosition(); + if (agentPosition.getValue() == targetPosition.getValue()) { + if (human.isBuriednessDefined() && human.getBuriedness() > 0) { + return new ActionRescue(human); + } + } else { + List path = pathPlanning.getResult(agentPosition, + targetPosition); + if (path != null && path.size() > 0) { + return new ActionMove(path); + } + } + return null; + } + if (targetEntity.getStandardURN() == BLOCKADE) { + Blockade blockade = (Blockade) targetEntity; + if (blockade.isPositionDefined()) { + targetEntity = this.worldInfo.getEntity(blockade.getPosition()); + } + } + if (targetEntity instanceof Area) { + List path = pathPlanning.getResult(agentPosition, + targetEntity.getID()); + if (path != null && path.size() > 0) { + return new ActionMove(path); + } + } + return null; + } + + + private boolean needRest(Human agent) { + int hp = agent.getHP(); + int damage = agent.getDamage(); + if (hp == 0 || damage == 0) { + return false; + } + int activeTime = (hp / damage) + ((hp % damage) != 0 ? 1 : 0); + if (this.kernelTime == -1) { + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + } + return damage >= this.thresholdRest + || (activeTime + this.agentInfo.getTime()) < this.kernelTime; + } + + + private EntityID convertArea(EntityID targetID) { + StandardEntity entity = this.worldInfo.getEntity(targetID); + if (entity == null) { + return null; + } + if (entity instanceof Human) { + Human human = (Human) entity; + if (human.isPositionDefined()) { + EntityID position = human.getPosition(); + if (this.worldInfo.getEntity(position) instanceof Area) { + return position; + } + } + } else if (entity instanceof Area) { + return targetID; + } else if (entity.getStandardURN() == BLOCKADE) { + Blockade blockade = (Blockade) entity; + if (blockade.isPositionDefined()) { + return blockade.getPosition(); + } + } + return null; + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionMove.java b/java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionMove.java new file mode 100644 index 0000000..4f79cab --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionMove.java @@ -0,0 +1,210 @@ +package adf_core_python.impl.extaction; + +import adf.core.agent.action.Action; +import adf.core.agent.action.common.ActionMove; +import adf.core.agent.action.common.ActionRest; +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.extaction.ExtAction; +import adf_core_python.core.component.module.algorithm.PathPlanning; +import rescuecore2.config.NoSuchConfigOptionException; +import rescuecore2.standard.entities.*; +import rescuecore2.worldmodel.EntityID; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class DefaultExtActionMove extends ExtAction { + + private PathPlanning pathPlanning; + + private int thresholdRest; + private int kernelTime; + + private EntityID target; + + public DefaultExtActionMove(AgentInfo agentInfo, WorldInfo worldInfo, ScenarioInfo scenarioInfo, ModuleManager moduleManager, DevelopData developData) { + super(agentInfo, worldInfo, scenarioInfo, moduleManager, developData); + this.target = null; + this.thresholdRest = developData + .getInteger("adf.impl.extaction.DefaultExtActionMove.rest", 100); + + switch (scenarioInfo.getMode()) { + case PRECOMPUTATION_PHASE: + case PRECOMPUTED: + case NON_PRECOMPUTE: + this.pathPlanning = moduleManager.getModule( + "DefaultExtActionMove.PathPlanning", + "adf_core_python.impl.module.algorithm.DijkstraPathPlanning"); + break; + } + } + + + @Override + public ExtAction precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + if (this.getCountPrecompute() >= 2) { + return this; + } + this.pathPlanning.precompute(precomputeData); + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + return this; + } + + + @Override + public ExtAction resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + if (this.getCountResume() >= 2) { + return this; + } + this.pathPlanning.resume(precomputeData); + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + return this; + } + + + @Override + public ExtAction preparate() { + super.preparate(); + if (this.getCountPreparate() >= 2) { + return this; + } + this.pathPlanning.preparate(); + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + return this; + } + + + @Override + public ExtAction updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + if (this.getCountUpdateInfo() >= 2) { + return this; + } + this.pathPlanning.updateInfo(messageManager); + return this; + } + + + @Override + public ExtAction setTarget(EntityID target) { + this.target = null; + StandardEntity entity = this.worldInfo.getEntity(target); + if (entity != null) { + if (entity.getStandardURN().equals(StandardEntityURN.BLOCKADE)) { + entity = this.worldInfo.getEntity(((Blockade) entity).getPosition()); + } else if (entity instanceof Human) { + entity = this.worldInfo.getPosition((Human) entity); + } + if (entity != null && entity instanceof Area) { + this.target = entity.getID(); + } + } + return this; + } + + + @Override + public ExtAction calc() { + this.result = null; + Human agent = (Human) this.agentInfo.me(); + + if (this.needRest(agent)) { + this.result = this.calcRest(agent, this.pathPlanning, this.target); + if (this.result != null) { + return this; + } + } + if (this.target == null) { + return this; + } + this.pathPlanning.setFrom(agent.getPosition()); + this.pathPlanning.setDestination(this.target); + List path = this.pathPlanning.calc().getResult(); + if (path != null && path.size() > 0) { + this.result = new ActionMove(path); + } + return this; + } + + + private boolean needRest(Human agent) { + int hp = agent.getHP(); + int damage = agent.getDamage(); + if (hp == 0 || damage == 0) { + return false; + } + int activeTime = (hp / damage) + ((hp % damage) != 0 ? 1 : 0); + if (this.kernelTime == -1) { + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + } + return damage >= this.thresholdRest + || (activeTime + this.agentInfo.getTime()) < this.kernelTime; + } + + + private Action calcRest(Human human, PathPlanning pathPlanning, + EntityID target) { + EntityID position = human.getPosition(); + Collection refuges = this.worldInfo + .getEntityIDsOfType(StandardEntityURN.REFUGE); + int currentSize = refuges.size(); + if (refuges.contains(position)) { + return new ActionRest(); + } + List firstResult = null; + while (refuges.size() > 0) { + pathPlanning.setFrom(position); + pathPlanning.setDestination(refuges); + List path = pathPlanning.calc().getResult(); + if (path != null && path.size() > 0) { + if (firstResult == null) { + firstResult = new ArrayList<>(path); + if (target == null) { + break; + } + } + EntityID refugeID = path.get(path.size() - 1); + pathPlanning.setFrom(refugeID); + pathPlanning.setDestination(target); + List fromRefugeToTarget = pathPlanning.calc().getResult(); + if (fromRefugeToTarget != null && fromRefugeToTarget.size() > 0) { + return new ActionMove(path); + } + refuges.remove(refugeID); + // remove failed + if (currentSize == refuges.size()) { + break; + } + currentSize = refuges.size(); + } else { + break; + } + } + return firstResult != null ? new ActionMove(firstResult) : null; + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionTransport.java b/java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionTransport.java new file mode 100644 index 0000000..3d134e9 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/extaction/DefaultExtActionTransport.java @@ -0,0 +1,348 @@ +package adf_core_python.impl.extaction; + +import adf.core.agent.action.Action; +import adf.core.agent.action.ambulance.ActionLoad; +import adf.core.agent.action.ambulance.ActionUnload; +import adf.core.agent.action.common.ActionMove; +import adf.core.agent.action.common.ActionRest; +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.extaction.ExtAction; +import adf_core_python.core.component.module.algorithm.PathPlanning; +import com.google.common.collect.Lists; +import rescuecore2.config.NoSuchConfigOptionException; +import rescuecore2.standard.entities.*; +import rescuecore2.worldmodel.EntityID; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static rescuecore2.standard.entities.StandardEntityURN.*; + +public class DefaultExtActionTransport extends ExtAction { + + private PathPlanning pathPlanning; + + private int thresholdRest; + private int kernelTime; + + private EntityID target; + + public DefaultExtActionTransport(AgentInfo agentInfo, WorldInfo worldInfo, ScenarioInfo scenarioInfo, ModuleManager moduleManager, DevelopData developData) { + super(agentInfo, worldInfo, scenarioInfo, moduleManager, developData); + this.target = null; + this.thresholdRest = developData + .getInteger("adf.impl.extaction.DefaultExtActionTransport.rest", 100); + + switch (scenarioInfo.getMode()) { + case PRECOMPUTATION_PHASE: + case PRECOMPUTED: + case NON_PRECOMPUTE: + this.pathPlanning = moduleManager.getModule( + "DefaultExtActionTransport.PathPlanning", + "adf_core_python.impl.module.algorithm.DijkstraPathPlanning"); + break; + } + } + + + public ExtAction precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + if (this.getCountPrecompute() >= 2) { + return this; + } + this.pathPlanning.precompute(precomputeData); + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + return this; + } + + + public ExtAction resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + if (this.getCountResume() >= 2) { + return this; + } + this.pathPlanning.resume(precomputeData); + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + return this; + } + + + public ExtAction preparate() { + super.preparate(); + if (this.getCountPreparate() >= 2) { + return this; + } + this.pathPlanning.preparate(); + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + return this; + } + + + public ExtAction updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + if (this.getCountUpdateInfo() >= 2) { + return this; + } + this.pathPlanning.updateInfo(messageManager); + return this; + } + + + @Override + public ExtAction setTarget(EntityID target) { + this.target = null; + if (target != null) { + StandardEntity entity = this.worldInfo.getEntity(target); + if (entity instanceof Human || entity instanceof Area) { + this.target = target; + return this; + } + } + return this; + } + + + @Override + public ExtAction calc() { + this.result = null; + AmbulanceTeam agent = (AmbulanceTeam) this.agentInfo.me(); + Human transportHuman = this.agentInfo.someoneOnBoard(); + + if (transportHuman != null) { + this.result = this.calcUnload(agent, this.pathPlanning, transportHuman, + this.target); + if (this.result != null) { + return this; + } + } + if (this.needRest(agent)) { + EntityID areaID = this.convertArea(this.target); + ArrayList targets = new ArrayList<>(); + if (areaID != null) { + targets.add(areaID); + } + this.result = this.calcRefugeAction(agent, this.pathPlanning, targets, + false); + if (this.result != null) { + return this; + } + } + if (this.target != null) { + this.result = this.calcRescue(agent, this.pathPlanning, this.target); + } + return this; + } + + + private Action calcRescue(AmbulanceTeam agent, PathPlanning pathPlanning, + EntityID targetID) { + StandardEntity targetEntity = this.worldInfo.getEntity(targetID); + if (targetEntity == null) { + return null; + } + EntityID agentPosition = agent.getPosition(); + if (targetEntity instanceof Human) { + Human human = (Human) targetEntity; + if (!human.isPositionDefined()) { + return null; + } + if (human.isHPDefined() && human.getHP() == 0) { + return null; + } + EntityID targetPosition = human.getPosition(); + if (agentPosition.getValue() == targetPosition.getValue()) { + if ((human.getStandardURN() == CIVILIAN) && + (!human.isBuriednessDefined() || (human.isBuriednessDefined() && (human.getBuriedness() == 0)))) { + return new ActionLoad(human.getID()); + } + } else { + List path = pathPlanning.getResult(agentPosition, + targetPosition); + if (path != null && path.size() > 0) { + return new ActionMove(path); + } + } + return null; + } + if (targetEntity.getStandardURN() == BLOCKADE) { + Blockade blockade = (Blockade) targetEntity; + if (blockade.isPositionDefined()) { + targetEntity = this.worldInfo.getEntity(blockade.getPosition()); + } + } + if (targetEntity instanceof Area) { + List path = pathPlanning.getResult(agentPosition, + targetEntity.getID()); + if (path != null && path.size() > 0) { + return new ActionMove(path); + } + } + return null; + } + + + private Action calcUnload(AmbulanceTeam agent, PathPlanning pathPlanning, + Human transportHuman, EntityID targetID) { + if (transportHuman == null) { + return null; + } + if (transportHuman.isHPDefined() && transportHuman.getHP() == 0) { + return new ActionUnload(); + } + EntityID agentPosition = agent.getPosition(); + if (targetID == null + || transportHuman.getID().getValue() == targetID.getValue()) { + StandardEntity position = this.worldInfo.getEntity(agentPosition); + if (position != null && position.getStandardURN() == REFUGE) { + return new ActionUnload(); + } else { + pathPlanning.setFrom(agentPosition); + pathPlanning.setDestination(this.worldInfo.getEntityIDsOfType(REFUGE)); + List path = pathPlanning.calc().getResult(); + if (path != null && path.size() > 0) { + return new ActionMove(path); + } + } + } + if (targetID == null) { + return null; + } + StandardEntity targetEntity = this.worldInfo.getEntity(targetID); + if (targetEntity != null && targetEntity.getStandardURN() == BLOCKADE) { + Blockade blockade = (Blockade) targetEntity; + if (blockade.isPositionDefined()) { + targetEntity = this.worldInfo.getEntity(blockade.getPosition()); + } + } + if (targetEntity instanceof Area) { + if (agentPosition.getValue() == targetID.getValue()) { + return new ActionUnload(); + } else { + pathPlanning.setFrom(agentPosition); + pathPlanning.setDestination(targetID); + List path = pathPlanning.calc().getResult(); + if (path != null && path.size() > 0) { + return new ActionMove(path); + } + } + } else if (targetEntity instanceof Human) { + Human human = (Human) targetEntity; + if (human.isPositionDefined()) { + return calcRefugeAction(agent, pathPlanning, + Lists.newArrayList(human.getPosition()), true); + } + pathPlanning.setFrom(agentPosition); + pathPlanning.setDestination(this.worldInfo.getEntityIDsOfType(REFUGE)); + List path = pathPlanning.calc().getResult(); + if (path != null && path.size() > 0) { + return new ActionMove(path); + } + } + return null; + } + + + private boolean needRest(Human agent) { + int hp = agent.getHP(); + int damage = agent.getDamage(); + if (hp == 0 || damage == 0) { + return false; + } + int activeTime = (hp / damage) + ((hp % damage) != 0 ? 1 : 0); + if (this.kernelTime == -1) { + try { + this.kernelTime = this.scenarioInfo.getKernelTimesteps(); + } catch (NoSuchConfigOptionException e) { + this.kernelTime = -1; + } + } + return damage >= this.thresholdRest + || (activeTime + this.agentInfo.getTime()) < this.kernelTime; + } + + + private EntityID convertArea(EntityID targetID) { + StandardEntity entity = this.worldInfo.getEntity(targetID); + if (entity == null) { + return null; + } + if (entity instanceof Human) { + Human human = (Human) entity; + if (human.isPositionDefined()) { + EntityID position = human.getPosition(); + if (this.worldInfo.getEntity(position) instanceof Area) { + return position; + } + } + } else if (entity instanceof Area) { + return targetID; + } else if (entity.getStandardURN() == BLOCKADE) { + Blockade blockade = (Blockade) entity; + if (blockade.isPositionDefined()) { + return blockade.getPosition(); + } + } + return null; + } + + + private Action calcRefugeAction(Human human, PathPlanning pathPlanning, + Collection targets, boolean isUnload) { + EntityID position = human.getPosition(); + Collection refuges = this.worldInfo + .getEntityIDsOfType(StandardEntityURN.REFUGE); + int size = refuges.size(); + if (refuges.contains(position)) { + return isUnload ? new ActionUnload() : new ActionRest(); + } + List firstResult = null; + while (refuges.size() > 0) { + pathPlanning.setFrom(position); + pathPlanning.setDestination(refuges); + List path = pathPlanning.calc().getResult(); + if (path != null && path.size() > 0) { + if (firstResult == null) { + firstResult = new ArrayList<>(path); + if (targets == null || targets.isEmpty()) { + break; + } + } + EntityID refugeID = path.get(path.size() - 1); + pathPlanning.setFrom(refugeID); + pathPlanning.setDestination(targets); + List fromRefugeToTarget = pathPlanning.calc().getResult(); + if (fromRefugeToTarget != null && fromRefugeToTarget.size() > 0) { + return new ActionMove(path); + } + refuges.remove(refugeID); + // remove failed + if (size == refuges.size()) { + break; + } + size = refuges.size(); + } else { + break; + } + } + return firstResult != null ? new ActionMove(firstResult) : null; + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/module/algorithm/AStarPathPlanning.java b/java/lib/src/main/java/adf_core_python/impl/module/algorithm/AStarPathPlanning.java new file mode 100644 index 0000000..d8d8fba --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/module/algorithm/AStarPathPlanning.java @@ -0,0 +1,198 @@ +package adf_core_python.impl.module.algorithm; + +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.algorithm.PathPlanning; +import rescuecore2.misc.collections.LazyMap; +import rescuecore2.standard.entities.Area; +import rescuecore2.worldmodel.Entity; +import rescuecore2.worldmodel.EntityID; + +import java.util.*; + +public class AStarPathPlanning extends PathPlanning { + + private Map> graph; + + private EntityID from; + private Collection targets; + private List result; + + public AStarPathPlanning(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + this.init(); + } + + + private void init() { + Map> neighbours = new LazyMap>() { + + @Override + public Set createValue() { + return new HashSet<>(); + } + }; + for (Entity next : this.worldInfo) { + if (next instanceof Area) { + Collection areaNeighbours = ((Area) next).getNeighbours(); + neighbours.get(next.getID()).addAll(areaNeighbours); + } + } + this.graph = neighbours; + } + + + @Override + public List getResult() { + return this.result; + } + + + @Override + public PathPlanning setFrom(EntityID id) { + this.from = id; + return this; + } + + + @Override + public PathPlanning setDestination(Collection targets) { + this.targets = targets; + return this; + } + + + @Override + public PathPlanning precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + return this; + } + + + @Override + public PathPlanning resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + return this; + } + + + @Override + public PathPlanning preparate() { + super.preparate(); + return this; + } + + + @Override + public PathPlanning calc() { + // 1 + List open = new LinkedList<>(); + List close = new LinkedList<>(); + Map nodeMap = new HashMap<>(); + + // 3 + open.add(this.from); + nodeMap.put(this.from, new Node(null, this.from)); + close.clear(); + + while (true) { + // 4 + if (open.size() < 0) { + this.result = null; + return this; + } + + // 5 + Node n = null; + for (EntityID id : open) { + Node node = nodeMap.get(id); + + if (n == null) { + n = node; + } else if (node.estimate() < n.estimate()) { + n = node; + } + } + + // 6 + if (targets.contains(n.getID())) { + // 9 + List path = new LinkedList<>(); + while (n != null) { + path.add(0, n.getID()); + n = nodeMap.get(n.getParent()); + } + + this.result = path; + return this; + } + open.remove(n.getID()); + close.add(n.getID()); + + // 7 + Collection neighbours = this.graph.get(n.getID()); + for (EntityID neighbour : neighbours) { + Node m = new Node(n, neighbour); + + if (!open.contains(neighbour) && !close.contains(neighbour)) { + open.add(m.getID()); + nodeMap.put(neighbour, m); + } else if (open.contains(neighbour) + && m.estimate() < nodeMap.get(neighbour).estimate()) { + nodeMap.put(neighbour, m); + } else if (!close.contains(neighbour) + && m.estimate() < nodeMap.get(neighbour).estimate()) { + nodeMap.put(neighbour, m); + } + } + } + } + + private class Node { + + EntityID id; + EntityID parent; + + double cost; + double heuristic; + + public Node(Node from, EntityID id) { + this.id = id; + + if (from == null) { + this.cost = 0; + } else { + this.parent = from.getID(); + this.cost = from.getCost() + worldInfo.getDistance(from.getID(), id); + } + + this.heuristic = worldInfo.getDistance(id, + targets.toArray(new EntityID[targets.size()])[0]); + } + + + public EntityID getID() { + return id; + } + + + public double getCost() { + return cost; + } + + + public double estimate() { + return cost + heuristic; + } + + + public EntityID getParent() { + return this.parent; + } + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/module/algorithm/DijkstraPathPlanning.java b/java/lib/src/main/java/adf_core_python/impl/module/algorithm/DijkstraPathPlanning.java new file mode 100644 index 0000000..c43aecd --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/module/algorithm/DijkstraPathPlanning.java @@ -0,0 +1,154 @@ +package adf_core_python.impl.module.algorithm; + +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.algorithm.PathPlanning; +import rescuecore2.misc.collections.LazyMap; +import rescuecore2.standard.entities.Area; +import rescuecore2.worldmodel.Entity; +import rescuecore2.worldmodel.EntityID; + +import java.util.*; + +public class DijkstraPathPlanning extends PathPlanning { + + private Map> graph; + + private EntityID from; + private Collection targets; + private List result; + + public DijkstraPathPlanning(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + this.init(); + } + + + private void init() { + Map> neighbours = new LazyMap>() { + + @Override + public Set createValue() { + return new HashSet<>(); + } + }; + for (Entity next : this.worldInfo) { + if (next instanceof Area) { + Collection areaNeighbours = ((Area) next).getNeighbours(); + neighbours.get(next.getID()).addAll(areaNeighbours); + } + } + this.graph = neighbours; + } + + + @Override + public List getResult() { + return this.result; + } + + + @Override + public PathPlanning setFrom(EntityID id) { + this.from = id; + return this; + } + + + @Override + public PathPlanning setDestination(Collection targets) { + this.targets = targets; + return this; + } + + + @Override + public PathPlanning updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + return this; + } + + + @Override + public PathPlanning precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + return this; + } + + + @Override + public PathPlanning resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + return this; + } + + + @Override + public PathPlanning preparate() { + super.preparate(); + return this; + } + + + @Override + public PathPlanning calc() { + List open = new LinkedList<>(); + Map ancestors = new HashMap<>(); + open.add(this.from); + EntityID next; + boolean found = false; + ancestors.put(this.from, this.from); + do { + next = open.remove(0); + if (isGoal(next, targets)) { + found = true; + break; + } + Collection neighbours = graph.get(next); + if (neighbours.isEmpty()) { + continue; + } + for (EntityID neighbour : neighbours) { + if (isGoal(neighbour, targets)) { + ancestors.put(neighbour, next); + next = neighbour; + found = true; + break; + } else { + if (!ancestors.containsKey(neighbour)) { + open.add(neighbour); + ancestors.put(neighbour, next); + } + } + } + } while (!found && !open.isEmpty()); + if (!found) { + // No path + this.result = null; + } + // Walk back from goal to this.from + EntityID current = next; + LinkedList path = new LinkedList<>(); + do { + path.add(0, current); + current = ancestors.get(current); + if (current == null) { + throw new RuntimeException( + "Found a node with no ancestor! Something is broken."); + } + } while (current != this.from); + this.result = path; + return this; + } + + + private boolean isGoal(EntityID e, Collection test) { + return test.contains(e); + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/module/algorithm/FireClustering.java b/java/lib/src/main/java/adf_core_python/impl/module/algorithm/FireClustering.java new file mode 100644 index 0000000..b00053e --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/module/algorithm/FireClustering.java @@ -0,0 +1,261 @@ +package adf_core_python.impl.module.algorithm; + +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.algorithm.Clustering; +import adf_core_python.core.component.module.algorithm.DynamicClustering; +import rescuecore2.standard.entities.Building; +import rescuecore2.standard.entities.StandardEntity; +import rescuecore2.standard.entities.StandardEntityURN; +import rescuecore2.worldmodel.EntityID; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +public class FireClustering extends DynamicClustering { + + List> clusterList = new LinkedList<>(); + private int groupingDistance; + + public FireClustering(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + this.groupingDistance = developData.getInteger( + "adf.impl.module.algorithm.FireClustering.groupingDistance", 30); + } + + + /** + * calculation phase; update cluster + * + * @return own instance for method chaining + */ + @Override + public Clustering calc() { + for (EntityID changed : worldInfo.getChanged().getChangedEntities()) { + StandardEntity changedEntity = worldInfo.getEntity(changed); + if (changedEntity.getStandardURN().equals(StandardEntityURN.BUILDING)) { // changedEntity(cE) + // is + // a + // building + Building changedBuilding = (Building) changedEntity; + if (this.getClusterIndex(changedEntity) < 0) { // cE is not contained in + // cluster + if (isBurning(changedBuilding)) { // cE is burning building + ArrayList< + EntityID> hostClusterPropertyEntityIDs = new ArrayList<>(); + + // search host cluster + for (List cluster : this.clusterList) { + for (StandardEntity entity : cluster) { + if (worldInfo.getDistance(entity, + changedBuilding) <= groupingDistance) { + hostClusterPropertyEntityIDs.add(entity.getID()); + break; + } + } + } + + if (hostClusterPropertyEntityIDs.size() == 0) { // there is not host + // cluster : form + // new cluster + List cluster = new ArrayList<>(); + clusterList.add(cluster); + cluster.add(changedBuilding); + } else if (hostClusterPropertyEntityIDs.size() == 1) { // there is + // one host + // cluster : + // add + // building + // to the + // cluster + int hostIndex = this + .getClusterIndex(hostClusterPropertyEntityIDs.get(0)); + clusterList.get(hostIndex).add(changedBuilding); + } else { // there are multiple host clusters : add building to the + // cluster & combine clusters + int hostIndex = this + .getClusterIndex(hostClusterPropertyEntityIDs.get(0)); + List hostCluster = clusterList.get(hostIndex); + hostCluster.add(changedBuilding); + for (int index = 1; index < hostClusterPropertyEntityIDs + .size(); index++) { + int tergetClusterIndex = this + .getClusterIndex(hostClusterPropertyEntityIDs.get(index)); + hostCluster.addAll(clusterList.get(tergetClusterIndex)); + clusterList.remove(tergetClusterIndex); + } + } + } + } else { // cE is contained in cluster + if (!(isBurning(changedBuilding))) { // cE is not burning building + int hostClusterIndex = this.getClusterIndex(changedBuilding); + List< + StandardEntity> hostCluster = clusterList.get(hostClusterIndex); + + hostCluster.remove(changedBuilding); + + if (hostCluster.isEmpty()) { // host cluster is empty + clusterList.remove(hostClusterIndex); + } else { + // update cluster + List relatedBuilding = new ArrayList<>(); + relatedBuilding.addAll(hostCluster); + hostCluster.clear(); + + int clusterCount = 0; + while (!(relatedBuilding.isEmpty())) { + if ((clusterCount++) > 0) { + List cluster = new ArrayList<>(); + clusterList.add(cluster); + hostCluster = cluster; + } + + List openedBuilding = new LinkedList<>(); + openedBuilding.add(relatedBuilding.get(0)); + hostCluster.add(relatedBuilding.get(0)); + relatedBuilding.remove(0); + + while (!(openedBuilding.isEmpty())) { + for (StandardEntity entity : relatedBuilding) { + if (worldInfo.getDistance(openedBuilding.get(0), + entity) <= groupingDistance) { + openedBuilding.add(entity); + hostCluster.add(entity); + } + } + openedBuilding.remove(0); + relatedBuilding.removeAll(openedBuilding); + } + } + } + } + } + } + } + return this; + } + + + @Override + public Clustering updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + if (this.getCountUpdateInfo() > 1) { + return this; + } + + this.calc(); // invoke calc() + + this.debugStdOut("Cluster : " + clusterList.size()); + + return this; + } + + + @Override + public Clustering precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + if (this.getCountPrecompute() > 1) { + return this; + } + return this; + } + + + @Override + public Clustering resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + if (this.getCountResume() > 1) { + return this; + } + return this; + } + + + @Override + public Clustering preparate() { + super.preparate(); + if (this.getCountPreparate() > 1) { + return this; + } + return this; + } + + + @Override + public int getClusterNumber() { + return clusterList.size(); + } + + + @Override + public int getClusterIndex(StandardEntity standardEntity) { + for (int index = 0; index < clusterList.size(); index++) { + if (clusterList.get(index).contains(standardEntity)) { + return index; + } + } + return -1; + } + + + @Override + public int getClusterIndex(EntityID entityID) { + return getClusterIndex(worldInfo.getEntity(entityID)); + } + + + @Override + public Collection getClusterEntities(int i) { + return clusterList.get(i); + } + + + @Override + public Collection getClusterEntityIDs(int i) { + ArrayList list = new ArrayList<>(); + for (StandardEntity entity : getClusterEntities(i)) { + list.add(entity.getID()); + } + return list; + } + + + /** + * classify burning building + * + * @param building target building + * @return is building burning + */ + private boolean isBurning(Building building) { + if (building.isFierynessDefined()) { + switch (building.getFieryness()) { + case 1: + case 2: + case 3: + return true; + default: + return false; + } + } + return false; + } + + + /** + * output text with class name to STDOUT when debug-mode. + * + * @param text output text + */ + private void debugStdOut(String text) { + if (scenarioInfo.isDebugMode()) { + System.out.println("[" + this.getClass().getSimpleName() + "] " + text); + } + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/module/algorithm/KMeansClustering.java b/java/lib/src/main/java/adf_core_python/impl/module/algorithm/KMeansClustering.java new file mode 100644 index 0000000..1dae975 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/module/algorithm/KMeansClustering.java @@ -0,0 +1,641 @@ +package adf_core_python.impl.module.algorithm; + +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.algorithm.Clustering; +import adf_core_python.core.component.module.algorithm.StaticClustering; +import rescuecore2.misc.Pair; +import rescuecore2.misc.collections.LazyMap; +import rescuecore2.misc.geometry.Point2D; +import rescuecore2.standard.entities.*; +import rescuecore2.worldmodel.Entity; +import rescuecore2.worldmodel.EntityID; + +import java.util.*; + +public class KMeansClustering extends StaticClustering { + + private static final String KEY_CLUSTER_SIZE = "clustering.size"; + private static final String KEY_CLUSTER_CENTER = "clustering.centers"; + private static final String KEY_CLUSTER_ENTITY = "clustering.entities."; + private static final String KEY_ASSIGN_AGENT = "clustering.assign"; + + private int repeatPrecompute; + private int repeatPreparate; + + private Collection entities; + + private List centerList; + private List centerIDs; + private Map> clusterEntitiesList; + private List> clusterEntityIDsList; + + private int clusterSize; + + private boolean assignAgentsFlag; + + private Map> shortestPathGraph; + + public KMeansClustering(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + this.repeatPrecompute = developData.getInteger( + "adf.impl.module.algorithm.KMeansClustering.repeatPrecompute", 7); + this.repeatPreparate = developData.getInteger( + "adf.impl.module.algorithm.KMeansClustering.repeatPreparate", 30); + this.clusterSize = developData.getInteger( + "adf.impl.module.algorithm.KMeansClustering.clusterSize", 5); + if (agentInfo.me().getStandardURN() + .equals(StandardEntityURN.AMBULANCE_TEAM)) { + this.clusterSize = scenarioInfo.getScenarioAgentsAt(); + } else if (agentInfo.me().getStandardURN() + .equals(StandardEntityURN.FIRE_BRIGADE)) { + this.clusterSize = scenarioInfo.getScenarioAgentsFb(); + } else if (agentInfo.me().getStandardURN() + .equals(StandardEntityURN.POLICE_FORCE)) { + this.clusterSize = scenarioInfo.getScenarioAgentsPf(); + } + this.assignAgentsFlag = developData.getBoolean( + "adf.impl.module.algorithm.KMeansClustering.assignAgentsFlag", true); + this.clusterEntityIDsList = new ArrayList<>(); + this.centerIDs = new ArrayList<>(); + this.clusterEntitiesList = new HashMap<>(); + this.centerList = new ArrayList<>(); + this.entities = wi.getEntitiesOfType(StandardEntityURN.ROAD, + StandardEntityURN.HYDRANT, StandardEntityURN.BUILDING, + StandardEntityURN.REFUGE, StandardEntityURN.GAS_STATION, + StandardEntityURN.AMBULANCE_CENTRE, StandardEntityURN.FIRE_STATION, + StandardEntityURN.POLICE_OFFICE); + } + + + @Override + public Clustering updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + if (this.getCountUpdateInfo() >= 2) { + return this; + } + this.centerList.clear(); + this.clusterEntitiesList.clear(); + return this; + } + + + @Override + public Clustering precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + if (this.getCountPrecompute() >= 2) { + return this; + } + this.calcPathBased(this.repeatPrecompute); + this.entities = null; + // write + precomputeData.setInteger(KEY_CLUSTER_SIZE, this.clusterSize); + precomputeData.setEntityIDList(KEY_CLUSTER_CENTER, this.centerIDs); + for (int i = 0; i < this.clusterSize; i++) { + precomputeData.setEntityIDList(KEY_CLUSTER_ENTITY + i, + this.clusterEntityIDsList.get(i)); + } + precomputeData.setBoolean(KEY_ASSIGN_AGENT, this.assignAgentsFlag); + return this; + } + + + @Override + public Clustering resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + if (this.getCountResume() >= 2) { + return this; + } + this.entities = null; + // read + this.clusterSize = precomputeData.getInteger(KEY_CLUSTER_SIZE); + this.centerIDs = new ArrayList<>( + precomputeData.getEntityIDList(KEY_CLUSTER_CENTER)); + this.clusterEntityIDsList = new ArrayList<>(this.clusterSize); + for (int i = 0; i < this.clusterSize; i++) { + this.clusterEntityIDsList.add(i, + precomputeData.getEntityIDList(KEY_CLUSTER_ENTITY + i)); + } + this.assignAgentsFlag = precomputeData.getBoolean(KEY_ASSIGN_AGENT); + return this; + } + + + @Override + public Clustering preparate() { + super.preparate(); + if (this.getCountPreparate() >= 2) { + return this; + } + this.calcStandard(this.repeatPreparate); + this.entities = null; + return this; + } + + + @Override + public int getClusterNumber() { + // The number of clusters + return this.clusterSize; + } + + + @Override + public int getClusterIndex(StandardEntity entity) { + return this.getClusterIndex(entity.getID()); + } + + + @Override + public int getClusterIndex(EntityID id) { + for (int i = 0; i < this.clusterSize; i++) { + if (this.clusterEntityIDsList.get(i).contains(id)) { + return i; + } + } + return -1; + } + + + @Override + public Collection getClusterEntities(int index) { + List result = this.clusterEntitiesList.get(index); + if (result == null || result.isEmpty()) { + List list = this.clusterEntityIDsList.get(index); + result = new ArrayList<>(list.size()); + for (int i = 0; i < list.size(); i++) { + result.add(i, this.worldInfo.getEntity(list.get(i))); + } + this.clusterEntitiesList.put(index, result); + } + return result; + } + + + @Override + public Collection getClusterEntityIDs(int index) { + return this.clusterEntityIDsList.get(index); + } + + + @Override + public Clustering calc() { + return this; + } + + + private void calcStandard(int repeat) { + this.initShortestPath(this.worldInfo); + Random random = new Random(); + + List entityList = new ArrayList<>(this.entities); + this.centerList = new ArrayList<>(this.clusterSize); + this.clusterEntitiesList = new HashMap<>(this.clusterSize); + + // init list + for (int index = 0; index < this.clusterSize; index++) { + this.clusterEntitiesList.put(index, new ArrayList<>()); + this.centerList.add(index, entityList.get(0)); + } + System.out.println("[" + this.getClass().getSimpleName() + "] Cluster : " + + this.clusterSize); + // init center + for (int index = 0; index < this.clusterSize; index++) { + StandardEntity centerEntity; + do { + centerEntity = entityList + .get(Math.abs(random.nextInt()) % entityList.size()); + } while (this.centerList.contains(centerEntity)); + this.centerList.set(index, centerEntity); + } + // calc center + for (int i = 0; i < repeat; i++) { + this.clusterEntitiesList.clear(); + for (int index = 0; index < this.clusterSize; index++) { + this.clusterEntitiesList.put(index, new ArrayList<>()); + } + for (StandardEntity entity : entityList) { + StandardEntity tmp = this.getNearEntityByLine(this.worldInfo, + this.centerList, entity); + this.clusterEntitiesList.get(this.centerList.indexOf(tmp)).add(entity); + } + for (int index = 0; index < this.clusterSize; index++) { + int sumX = 0, sumY = 0; + for (StandardEntity entity : this.clusterEntitiesList.get(index)) { + Pair location = this.worldInfo.getLocation(entity); + sumX += location.first(); + sumY += location.second(); + } + int centerX = sumX / this.clusterEntitiesList.get(index).size(); + int centerY = sumY / this.clusterEntitiesList.get(index).size(); + StandardEntity center = this.getNearEntityByLine(this.worldInfo, + this.clusterEntitiesList.get(index), centerX, centerY); + if (center instanceof Area) { + this.centerList.set(index, center); + } else if (center instanceof Human) { + this.centerList.set(index, + this.worldInfo.getEntity(((Human) center).getPosition())); + } else if (center instanceof Blockade) { + this.centerList.set(index, + this.worldInfo.getEntity(((Blockade) center).getPosition())); + } + } + if (scenarioInfo.isDebugMode()) { + System.out.print("*"); + } + } + + if (scenarioInfo.isDebugMode()) { + System.out.println(); + } + + // set entity + this.clusterEntitiesList.clear(); + for (int index = 0; index < this.clusterSize; index++) { + this.clusterEntitiesList.put(index, new ArrayList<>()); + } + for (StandardEntity entity : entityList) { + StandardEntity tmp = this.getNearEntityByLine(this.worldInfo, + this.centerList, entity); + this.clusterEntitiesList.get(this.centerList.indexOf(tmp)).add(entity); + } + + // this.clusterEntitiesList.sort(comparing(List::size, reverseOrder())); + + if (this.assignAgentsFlag) { + List firebrigadeList = new ArrayList<>( + this.worldInfo.getEntitiesOfType(StandardEntityURN.FIRE_BRIGADE)); + List policeforceList = new ArrayList<>( + this.worldInfo.getEntitiesOfType(StandardEntityURN.POLICE_FORCE)); + List ambulanceteamList = new ArrayList<>( + this.worldInfo.getEntitiesOfType(StandardEntityURN.AMBULANCE_TEAM)); + + this.assignAgents(this.worldInfo, firebrigadeList); + this.assignAgents(this.worldInfo, policeforceList); + this.assignAgents(this.worldInfo, ambulanceteamList); + } + + this.centerIDs = new ArrayList<>(); + for (int i = 0; i < this.centerList.size(); i++) { + this.centerIDs.add(i, this.centerList.get(i).getID()); + } + for (int index = 0; index < this.clusterSize; index++) { + List entities = this.clusterEntitiesList.get(index); + List list = new ArrayList<>(entities.size()); + for (int i = 0; i < entities.size(); i++) { + list.add(i, entities.get(i).getID()); + } + this.clusterEntityIDsList.add(index, list); + } + } + + + private void calcPathBased(int repeat) { + this.initShortestPath(this.worldInfo); + Random random = new Random(); + List entityList = new ArrayList<>(this.entities); + this.centerList = new ArrayList<>(this.clusterSize); + this.clusterEntitiesList = new HashMap<>(this.clusterSize); + + for (int index = 0; index < this.clusterSize; index++) { + this.clusterEntitiesList.put(index, new ArrayList<>()); + this.centerList.add(index, entityList.get(0)); + } + for (int index = 0; index < this.clusterSize; index++) { + StandardEntity centerEntity; + do { + centerEntity = entityList + .get(Math.abs(random.nextInt()) % entityList.size()); + } while (this.centerList.contains(centerEntity)); + this.centerList.set(index, centerEntity); + } + for (int i = 0; i < repeat; i++) { + this.clusterEntitiesList.clear(); + for (int index = 0; index < this.clusterSize; index++) { + this.clusterEntitiesList.put(index, new ArrayList<>()); + } + for (StandardEntity entity : entityList) { + StandardEntity tmp = this.getNearEntity(this.worldInfo, this.centerList, + entity); + this.clusterEntitiesList.get(this.centerList.indexOf(tmp)).add(entity); + } + for (int index = 0; index < this.clusterSize; index++) { + int sumX = 0, sumY = 0; + for (StandardEntity entity : this.clusterEntitiesList.get(index)) { + Pair location = this.worldInfo.getLocation(entity); + sumX += location.first(); + sumY += location.second(); + } + int centerX = sumX / clusterEntitiesList.get(index).size(); + int centerY = sumY / clusterEntitiesList.get(index).size(); + + // this.centerList.set(index, getNearEntity(this.worldInfo, + // this.clusterEntitiesList.get(index), centerX, centerY)); + StandardEntity center = this.getNearEntity(this.worldInfo, + this.clusterEntitiesList.get(index), centerX, centerY); + if (center instanceof Area) { + this.centerList.set(index, center); + } else if (center instanceof Human) { + this.centerList.set(index, + this.worldInfo.getEntity(((Human) center).getPosition())); + } else if (center instanceof Blockade) { + this.centerList.set(index, + this.worldInfo.getEntity(((Blockade) center).getPosition())); + } + } + if (scenarioInfo.isDebugMode()) { + System.out.print("*"); + } + } + + if (scenarioInfo.isDebugMode()) { + System.out.println(); + } + + this.clusterEntitiesList.clear(); + for (int index = 0; index < this.clusterSize; index++) { + this.clusterEntitiesList.put(index, new ArrayList<>()); + } + for (StandardEntity entity : entityList) { + StandardEntity tmp = this.getNearEntity(this.worldInfo, this.centerList, + entity); + this.clusterEntitiesList.get(this.centerList.indexOf(tmp)).add(entity); + } + // this.clusterEntitiesList.sort(comparing(List::size, reverseOrder())); + if (this.assignAgentsFlag) { + List fireBrigadeList = new ArrayList<>( + this.worldInfo.getEntitiesOfType(StandardEntityURN.FIRE_BRIGADE)); + List policeForceList = new ArrayList<>( + this.worldInfo.getEntitiesOfType(StandardEntityURN.POLICE_FORCE)); + List ambulanceTeamList = new ArrayList<>( + this.worldInfo.getEntitiesOfType(StandardEntityURN.AMBULANCE_TEAM)); + this.assignAgents(this.worldInfo, fireBrigadeList); + this.assignAgents(this.worldInfo, policeForceList); + this.assignAgents(this.worldInfo, ambulanceTeamList); + } + + this.centerIDs = new ArrayList<>(); + for (int i = 0; i < this.centerList.size(); i++) { + this.centerIDs.add(i, this.centerList.get(i).getID()); + } + for (int index = 0; index < this.clusterSize; index++) { + List entities = this.clusterEntitiesList.get(index); + List list = new ArrayList<>(entities.size()); + for (int i = 0; i < entities.size(); i++) { + list.add(i, entities.get(i).getID()); + } + this.clusterEntityIDsList.add(index, list); + } + } + + + private void assignAgents(WorldInfo world, List agentList) { + int clusterIndex = 0; + while (agentList.size() > 0) { + StandardEntity center = this.centerList.get(clusterIndex); + StandardEntity agent = this.getNearAgent(world, agentList, center); + this.clusterEntitiesList.get(clusterIndex).add(agent); + agentList.remove(agent); + clusterIndex++; + if (clusterIndex >= this.clusterSize) { + clusterIndex = 0; + } + } + } + + + private StandardEntity getNearEntityByLine(WorldInfo world, + List srcEntityList, StandardEntity targetEntity) { + Pair location = world.getLocation(targetEntity); + return this.getNearEntityByLine(world, srcEntityList, location.first(), + location.second()); + } + + + private StandardEntity getNearEntityByLine(WorldInfo world, + List srcEntityList, int targetX, int targetY) { + StandardEntity result = null; + for (StandardEntity entity : srcEntityList) { + result = ((result != null) + ? this.compareLineDistance(world, targetX, targetY, result, entity) + : entity); + } + return result; + } + + + private StandardEntity getNearAgent(WorldInfo worldInfo, + List srcAgentList, StandardEntity targetEntity) { + StandardEntity result = null; + for (StandardEntity agent : srcAgentList) { + Human human = (Human) agent; + if (result == null) { + result = agent; + } else { + if (this + .comparePathDistance(worldInfo, targetEntity, result, + worldInfo.getPosition(human)) + .equals(worldInfo.getPosition(human))) { + result = agent; + } + } + } + return result; + } + + + private StandardEntity getNearEntity(WorldInfo worldInfo, + List srcEntityList, int targetX, int targetY) { + StandardEntity result = null; + for (StandardEntity entity : srcEntityList) { + result = (result != null) + ? this.compareLineDistance(worldInfo, targetX, targetY, result, + entity) + : entity; + } + return result; + } + + + private Point2D getEdgePoint(Edge edge) { + Point2D start = edge.getStart(); + Point2D end = edge.getEnd(); + return new Point2D(((start.getX() + end.getX()) / 2.0D), + ((start.getY() + end.getY()) / 2.0D)); + } + + + private double getDistance(double fromX, double fromY, double toX, + double toY) { + double dx = fromX - toX; + double dy = fromY - toY; + return Math.hypot(dx, dy); + } + + + private double getDistance(Pair from, Point2D to) { + return getDistance(from.first(), from.second(), to.getX(), to.getY()); + } + + + private double getDistance(Pair from, Edge to) { + return getDistance(from, getEdgePoint(to)); + } + + + private double getDistance(Point2D from, Point2D to) { + return getDistance(from.getX(), from.getY(), to.getX(), to.getY()); + } + + + private double getDistance(Edge from, Edge to) { + return getDistance(getEdgePoint(from), getEdgePoint(to)); + } + + + private StandardEntity compareLineDistance(WorldInfo worldInfo, int targetX, + int targetY, StandardEntity first, StandardEntity second) { + Pair firstLocation = worldInfo.getLocation(first); + Pair secondLocation = worldInfo.getLocation(second); + double firstDistance = getDistance(firstLocation.first(), + firstLocation.second(), targetX, targetY); + double secondDistance = getDistance(secondLocation.first(), + secondLocation.second(), targetX, targetY); + return (firstDistance < secondDistance ? first : second); + } + + + private StandardEntity getNearEntity(WorldInfo worldInfo, + List srcEntityList, StandardEntity targetEntity) { + StandardEntity result = null; + for (StandardEntity entity : srcEntityList) { + result = (result != null) + ? this.comparePathDistance(worldInfo, targetEntity, result, entity) + : entity; + } + return result; + } + + + private StandardEntity comparePathDistance(WorldInfo worldInfo, + StandardEntity target, StandardEntity first, StandardEntity second) { + double firstDistance = getPathDistance(worldInfo, + shortestPath(target.getID(), first.getID())); + double secondDistance = getPathDistance(worldInfo, + shortestPath(target.getID(), second.getID())); + return (firstDistance < secondDistance ? first : second); + } + + + private double getPathDistance(WorldInfo worldInfo, List path) { + if (path == null) + return Double.MAX_VALUE; + if (path.size() <= 1) + return 0.0D; + + double distance = 0.0D; + int limit = path.size() - 1; + + Area area = (Area) worldInfo.getEntity(path.get(0)); + distance += getDistance(worldInfo.getLocation(area), + area.getEdgeTo(path.get(1))); + area = (Area) worldInfo.getEntity(path.get(limit)); + distance += getDistance(worldInfo.getLocation(area), + area.getEdgeTo(path.get(limit - 1))); + + for (int i = 1; i < limit; i++) { + area = (Area) worldInfo.getEntity(path.get(i)); + distance += getDistance(area.getEdgeTo(path.get(i - 1)), + area.getEdgeTo(path.get(i + 1))); + } + return distance; + } + + + private void initShortestPath(WorldInfo worldInfo) { + Map> neighbours = new LazyMap>() { + + @Override + public Set createValue() { + return new HashSet<>(); + } + }; + for (Entity next : worldInfo) { + if (next instanceof Area) { + Collection areaNeighbours = ((Area) next).getNeighbours(); + neighbours.get(next.getID()).addAll(areaNeighbours); + } + } + for (Map.Entry> graph : neighbours.entrySet()) {// fix + // graph + for (EntityID entityID : graph.getValue()) { + neighbours.get(entityID).add(graph.getKey()); + } + } + this.shortestPathGraph = neighbours; + } + + + private List shortestPath(EntityID start, EntityID... goals) { + return shortestPath(start, Arrays.asList(goals)); + } + + + private List shortestPath(EntityID start, + Collection goals) { + List open = new LinkedList<>(); + Map ancestors = new HashMap<>(); + open.add(start); + EntityID next; + boolean found = false; + ancestors.put(start, start); + do { + next = open.remove(0); + if (isGoal(next, goals)) { + found = true; + break; + } + Collection neighbours = shortestPathGraph.get(next); + if (neighbours.isEmpty()) + continue; + + for (EntityID neighbour : neighbours) { + if (isGoal(neighbour, goals)) { + ancestors.put(neighbour, next); + next = neighbour; + found = true; + break; + } else if (!ancestors.containsKey(neighbour)) { + open.add(neighbour); + ancestors.put(neighbour, next); + } + } + } while (!found && !open.isEmpty()); + if (!found) { + // No path + return null; + } + // Walk back from goal to start + EntityID current = next; + List path = new LinkedList<>(); + do { + path.add(0, current); + current = ancestors.get(current); + if (current == null) + throw new RuntimeException( + "Found a node with no ancestor! Something is broken."); + } while (current != start); + return path; + } + + + private boolean isGoal(EntityID e, Collection test) { + return test.contains(e); + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/module/comm/DefaultChannelSubscriber.java b/java/lib/src/main/java/adf_core_python/impl/module/comm/DefaultChannelSubscriber.java new file mode 100644 index 0000000..b84ff49 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/module/comm/DefaultChannelSubscriber.java @@ -0,0 +1,96 @@ +package adf_core_python.impl.module.comm; + +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.component.communication.ChannelSubscriber; +import rescuecore2.standard.entities.StandardEntityURN; + +public class DefaultChannelSubscriber extends ChannelSubscriber { + + public static int getChannelNumber(StandardEntityURN agentType, + int channelIndex, int numChannels) { + int agentIndex = 0; + if (agentType == StandardEntityURN.FIRE_BRIGADE + || agentType == StandardEntityURN.FIRE_STATION) { + agentIndex = 1; + } else if (agentType == StandardEntityURN.POLICE_FORCE + || agentType == StandardEntityURN.POLICE_OFFICE) { + agentIndex = 2; + } else if (agentType == StandardEntityURN.AMBULANCE_TEAM + || agentType == StandardEntityURN.AMBULANCE_CENTRE) { + agentIndex = 3; + } + + int index = (3 * channelIndex) + agentIndex; + if ((index % numChannels) == 0) { + index = numChannels; + } else { + index = index % numChannels; + } + return index; + } + + public static void main(String[] args) { + int numChannels = 6; + int maxChannels = 2; + for (int i = 0; i < maxChannels; i++) { + System.out.println("FIREBRIGADE-" + i + ":" + + getChannelNumber(StandardEntityURN.FIRE_BRIGADE, i, numChannels)); + } + for (int i = 0; i < maxChannels; i++) { + System.out.println("POLICE-" + i + ":" + + getChannelNumber(StandardEntityURN.POLICE_OFFICE, i, numChannels)); + } + for (int i = 0; i < maxChannels; i++) { + System.out.println("AMB-" + i + ":" + getChannelNumber( + StandardEntityURN.AMBULANCE_CENTRE, i, numChannels)); + } + } + + @Override + public void subscribe(AgentInfo agentInfo, WorldInfo worldInfo, + ScenarioInfo scenarioInfo, MessageManager messageManager) { + // subscribe only once at the beginning + if (agentInfo.getTime() == 1) { + int numChannels = scenarioInfo.getCommsChannelsCount() - 1; // 0th channel + // is the + // voice + // channel + + int maxChannelCount = 0; + boolean isPlatoon = isPlatoonAgent(agentInfo, worldInfo); + if (isPlatoon) { + maxChannelCount = scenarioInfo.getCommsChannelsMaxPlatoon(); + } else { + maxChannelCount = scenarioInfo.getCommsChannelsMaxOffice(); + } + + StandardEntityURN agentType = getAgentType(agentInfo, worldInfo); + int[] channels = new int[maxChannelCount]; + for (int i = 0; i < maxChannelCount; i++) { + channels[i] = getChannelNumber(agentType, i, numChannels); + } + + messageManager.subscribeToChannels(channels); + } + } + + protected boolean isPlatoonAgent(AgentInfo agentInfo, WorldInfo worldInfo) { + StandardEntityURN agentType = getAgentType(agentInfo, worldInfo); + if (agentType == StandardEntityURN.FIRE_BRIGADE + || agentType == StandardEntityURN.POLICE_FORCE + || agentType == StandardEntityURN.AMBULANCE_TEAM) { + return true; + } + return false; + } + + protected StandardEntityURN getAgentType(AgentInfo agentInfo, + WorldInfo worldInfo) { + StandardEntityURN agentType = worldInfo.getEntity(agentInfo.getID()) + .getStandardURN(); + return agentType; + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/module/comm/DefaultMessageCoordinator.java b/java/lib/src/main/java/adf_core_python/impl/module/comm/DefaultMessageCoordinator.java new file mode 100644 index 0000000..07ee3f2 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/module/comm/DefaultMessageCoordinator.java @@ -0,0 +1,199 @@ +package adf_core_python.impl.module.comm; + +import adf.core.agent.communication.standard.bundle.StandardMessage; +import adf.core.agent.communication.standard.bundle.StandardMessagePriority; +import adf.core.agent.communication.standard.bundle.centralized.*; +import adf.core.agent.communication.standard.bundle.information.*; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf.core.component.communication.CommunicationMessage; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.component.communication.MessageCoordinator; +import rescuecore2.standard.entities.StandardEntityURN; + +import java.util.ArrayList; +import java.util.List; + +public class DefaultMessageCoordinator extends MessageCoordinator { + + @Override + public void coordinate(AgentInfo agentInfo, WorldInfo worldInfo, + ScenarioInfo scenarioInfo, MessageManager messageManager, + ArrayList sendMessageList, + List> channelSendMessageList) { + + // have different lists for every agent + ArrayList policeMessages = new ArrayList<>(); + ArrayList ambulanceMessages = new ArrayList<>(); + ArrayList fireBrigadeMessages = new ArrayList<>(); + + ArrayList voiceMessages = new ArrayList<>(); + + StandardEntityURN agentType = getAgentType(agentInfo, worldInfo); + + for (CommunicationMessage msg : sendMessageList) { + if (msg instanceof StandardMessage + && !((StandardMessage) msg).isRadio()) { + voiceMessages.add(msg); + } else { + if (msg instanceof MessageBuilding) { + fireBrigadeMessages.add(msg); + } else if (msg instanceof MessageCivilian) { + ambulanceMessages.add(msg); + } else if (msg instanceof MessageRoad) { + fireBrigadeMessages.add(msg); + ambulanceMessages.add(msg); + policeMessages.add(msg); + } else if (msg instanceof CommandAmbulance) { + ambulanceMessages.add(msg); + } else if (msg instanceof CommandFire) { + fireBrigadeMessages.add(msg); + } else if (msg instanceof CommandPolice) { + policeMessages.add(msg); + } else if (msg instanceof CommandScout) { + if (agentType == StandardEntityURN.FIRE_STATION) { + fireBrigadeMessages.add(msg); + } else if (agentType == StandardEntityURN.POLICE_OFFICE) { + policeMessages.add(msg); + } else if (agentType == StandardEntityURN.AMBULANCE_CENTRE) { + ambulanceMessages.add(msg); + } + } else if (msg instanceof MessageReport) { + if (agentType == StandardEntityURN.FIRE_BRIGADE) { + fireBrigadeMessages.add(msg); + } else if (agentType == StandardEntityURN.POLICE_FORCE) { + policeMessages.add(msg); + } else if (agentType == StandardEntityURN.AMBULANCE_TEAM) { + ambulanceMessages.add(msg); + } + } else if (msg instanceof MessageFireBrigade) { + fireBrigadeMessages.add(msg); + ambulanceMessages.add(msg); + policeMessages.add(msg); + } else if (msg instanceof MessagePoliceForce) { + ambulanceMessages.add(msg); + policeMessages.add(msg); + } else if (msg instanceof MessageAmbulanceTeam) { + ambulanceMessages.add(msg); + policeMessages.add(msg); + } + } + } + + if (scenarioInfo.getCommsChannelsCount() > 1) { + // send radio messages if there are more than one communication channel + int[] channelSize = new int[scenarioInfo.getCommsChannelsCount() - 1]; + + setSendMessages(scenarioInfo, StandardEntityURN.POLICE_FORCE, agentInfo, + worldInfo, policeMessages, channelSendMessageList, channelSize); + setSendMessages(scenarioInfo, StandardEntityURN.AMBULANCE_TEAM, agentInfo, + worldInfo, ambulanceMessages, channelSendMessageList, channelSize); + setSendMessages(scenarioInfo, StandardEntityURN.FIRE_BRIGADE, agentInfo, + worldInfo, fireBrigadeMessages, channelSendMessageList, channelSize); + } + + ArrayList voiceMessageLowList = new ArrayList<>(); + ArrayList voiceMessageNormalList = new ArrayList<>(); + ArrayList voiceMessageHighList = new ArrayList<>(); + + for (CommunicationMessage msg : voiceMessages) { + if (msg instanceof StandardMessage) { + StandardMessage m = (StandardMessage) msg; + switch (m.getSendingPriority()) { + case LOW: + voiceMessageLowList.add(m); + break; + case NORMAL: + voiceMessageNormalList.add(m); + break; + case HIGH: + voiceMessageHighList.add(m); + break; + } + } + } + + // set the voice channel messages + channelSendMessageList.get(0).addAll(voiceMessageHighList); + channelSendMessageList.get(0).addAll(voiceMessageNormalList); + channelSendMessageList.get(0).addAll(voiceMessageLowList); + } + + + protected int[] getChannelsByAgentType(StandardEntityURN agentType, + AgentInfo agentInfo, WorldInfo worldInfo, ScenarioInfo scenarioInfo, + int channelIndex) { + int numChannels = scenarioInfo.getCommsChannelsCount() - 1; // 0th channel + // is the voice + // channel + int maxChannelCount = 0; + boolean isPlatoon = isPlatoonAgent(agentInfo, worldInfo); + if (isPlatoon) { + maxChannelCount = scenarioInfo.getCommsChannelsMaxPlatoon(); + } else { + maxChannelCount = scenarioInfo.getCommsChannelsMaxOffice(); + } + int[] channels = new int[maxChannelCount]; + + for (int i = 0; i < maxChannelCount; i++) { + channels[i] = DefaultChannelSubscriber.getChannelNumber(agentType, i, + numChannels); + } + return channels; + } + + + protected boolean isPlatoonAgent(AgentInfo agentInfo, WorldInfo worldInfo) { + StandardEntityURN agentType = getAgentType(agentInfo, worldInfo); + if (agentType == StandardEntityURN.FIRE_BRIGADE + || agentType == StandardEntityURN.POLICE_FORCE + || agentType == StandardEntityURN.AMBULANCE_TEAM) { + return true; + } + return false; + } + + + protected StandardEntityURN getAgentType(AgentInfo agentInfo, + WorldInfo worldInfo) { + StandardEntityURN agentType = worldInfo.getEntity(agentInfo.getID()) + .getStandardURN(); + return agentType; + } + + + protected void setSendMessages(ScenarioInfo scenarioInfo, + StandardEntityURN agentType, AgentInfo agentInfo, WorldInfo worldInfo, + List messages, + List> channelSendMessageList, + int[] channelSize) { + int channelIndex = 0; + int[] channels = getChannelsByAgentType(agentType, agentInfo, worldInfo, + scenarioInfo, channelIndex); + int channel = channels[channelIndex]; + int channelCapacity = scenarioInfo.getCommsChannelBandwidth(channel); + // start from HIGH, NORMAL, to LOW + for (int i = StandardMessagePriority.values().length - 1; i >= 0; i--) { + for (CommunicationMessage msg : messages) { + StandardMessage smsg = (StandardMessage) msg; + if (smsg.getSendingPriority() == StandardMessagePriority.values()[i]) { + channelSize[channel - 1] += smsg.getByteArraySize(); + if (channelSize[channel - 1] > channelCapacity) { + channelSize[channel - 1] -= smsg.getByteArraySize(); + channelIndex++; + if (channelIndex < channels.length) { + channel = channels[channelIndex]; + channelCapacity = scenarioInfo.getCommsChannelBandwidth(channel); + channelSize[channel - 1] += smsg.getByteArraySize(); + } else { + // if there is no new channel for that message types, just break + break; + } + } + channelSendMessageList.get(channel).add(smsg); + } + } + } + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultAmbulanceTargetAllocator.java b/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultAmbulanceTargetAllocator.java new file mode 100644 index 0000000..75d2b0e --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultAmbulanceTargetAllocator.java @@ -0,0 +1,362 @@ +package adf_core_python.impl.module.complex; + +import adf.core.agent.communication.standard.bundle.MessageUtil; +import adf.core.agent.communication.standard.bundle.centralized.CommandAmbulance; +import adf.core.agent.communication.standard.bundle.centralized.MessageReport; +import adf.core.agent.communication.standard.bundle.information.MessageAmbulanceTeam; +import adf.core.agent.communication.standard.bundle.information.MessageCivilian; +import adf.core.agent.communication.standard.bundle.information.MessageFireBrigade; +import adf.core.agent.communication.standard.bundle.information.MessagePoliceForce; +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf.core.component.communication.CommunicationMessage; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.complex.AmbulanceTargetAllocator; +import rescuecore2.standard.entities.*; +import rescuecore2.worldmodel.EntityID; + +import java.util.*; + +import static rescuecore2.standard.entities.StandardEntityURN.REFUGE; + +public class DefaultAmbulanceTargetAllocator extends AmbulanceTargetAllocator { + + private Collection priorityHumans; + private Collection targetHumans; + + private Map ambulanceTeamInfoMap; + + public DefaultAmbulanceTargetAllocator(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + this.priorityHumans = new HashSet<>(); + this.targetHumans = new HashSet<>(); + this.ambulanceTeamInfoMap = new HashMap<>(); + } + + + @Override + public AmbulanceTargetAllocator resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + if (this.getCountResume() >= 2) { + return this; + } + for (EntityID id : this.worldInfo + .getEntityIDsOfType(StandardEntityURN.AMBULANCE_TEAM)) { + this.ambulanceTeamInfoMap.put(id, new AmbulanceTeamInfo(id)); + } + return this; + } + + + @Override + public AmbulanceTargetAllocator preparate() { + super.preparate(); + if (this.getCountPrecompute() >= 2) { + return this; + } + for (EntityID id : this.worldInfo + .getEntityIDsOfType(StandardEntityURN.AMBULANCE_TEAM)) { + this.ambulanceTeamInfoMap.put(id, new AmbulanceTeamInfo(id)); + } + return this; + } + + + @Override + public Map getResult() { + return this.convert(this.ambulanceTeamInfoMap); + } + + + @Override + public AmbulanceTargetAllocator calc() { + List agents = this + .getActionAgents(this.ambulanceTeamInfoMap); + Collection removes = new ArrayList<>(); + int currentTime = this.agentInfo.getTime(); + for (EntityID target : this.priorityHumans) { + if (agents.size() > 0) { + StandardEntity targetEntity = this.worldInfo.getEntity(target); + if (targetEntity != null && targetEntity instanceof Human + && ((Human) targetEntity).isPositionDefined()) { + agents.sort(new DistanceSorter(this.worldInfo, targetEntity)); + StandardEntity result = agents.get(0); + agents.remove(0); + AmbulanceTeamInfo info = this.ambulanceTeamInfoMap + .get(result.getID()); + if (info != null) { + info.canNewAction = false; + info.target = target; + info.commandTime = currentTime; + this.ambulanceTeamInfoMap.put(result.getID(), info); + removes.add(target); + } + } + } + } + this.priorityHumans.removeAll(removes); + removes.clear(); + for (EntityID target : this.targetHumans) { + if (agents.size() > 0) { + StandardEntity targetEntity = this.worldInfo.getEntity(target); + if (targetEntity != null && targetEntity instanceof Human + && ((Human) targetEntity).isPositionDefined()) { + agents.sort(new DistanceSorter(this.worldInfo, targetEntity)); + StandardEntity result = agents.get(0); + agents.remove(0); + AmbulanceTeamInfo info = this.ambulanceTeamInfoMap + .get(result.getID()); + if (info != null) { + info.canNewAction = false; + info.target = target; + info.commandTime = currentTime; + this.ambulanceTeamInfoMap.put(result.getID(), info); + removes.add(target); + } + } + } + } + this.targetHumans.removeAll(removes); + return this; + } + + + @Override + public AmbulanceTargetAllocator updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + if (this.getCountUpdateInfo() >= 2) { + return this; + } + int currentTime = this.agentInfo.getTime(); + for (CommunicationMessage message : messageManager + .getReceivedMessageList()) { + Class messageClass = message.getClass(); + if (messageClass == MessageCivilian.class) { + MessageCivilian mc = (MessageCivilian) message; + MessageUtil.reflectMessage(this.worldInfo, mc); + if (mc.isBuriednessDefined() && mc.getBuriedness() > 0) { + this.targetHumans.add(mc.getAgentID()); + } else { + this.priorityHumans.remove(mc.getAgentID()); + this.targetHumans.remove(mc.getAgentID()); + } + } else if (messageClass == MessageFireBrigade.class) { + MessageFireBrigade mfb = (MessageFireBrigade) message; + MessageUtil.reflectMessage(this.worldInfo, mfb); + if (mfb.isBuriednessDefined() && mfb.getBuriedness() > 0) { + this.priorityHumans.add(mfb.getAgentID()); + } else { + this.priorityHumans.remove(mfb.getAgentID()); + this.targetHumans.remove(mfb.getAgentID()); + } + } else if (messageClass == MessagePoliceForce.class) { + MessagePoliceForce mpf = (MessagePoliceForce) message; + MessageUtil.reflectMessage(this.worldInfo, mpf); + if (mpf.isBuriednessDefined() && mpf.getBuriedness() > 0) { + this.priorityHumans.add(mpf.getAgentID()); + } else { + this.priorityHumans.remove(mpf.getAgentID()); + this.targetHumans.remove(mpf.getAgentID()); + } + } + } + for (CommunicationMessage message : messageManager + .getReceivedMessageList(MessageAmbulanceTeam.class)) { + MessageAmbulanceTeam mat = (MessageAmbulanceTeam) message; + MessageUtil.reflectMessage(this.worldInfo, mat); + if (mat.isBuriednessDefined() && mat.getBuriedness() > 0) { + this.priorityHumans.add(mat.getAgentID()); + } else { + this.priorityHumans.remove(mat.getAgentID()); + this.targetHumans.remove(mat.getAgentID()); + } + AmbulanceTeamInfo info = this.ambulanceTeamInfoMap.get(mat.getAgentID()); + if (info == null) { + info = new AmbulanceTeamInfo(mat.getAgentID()); + } + if (currentTime >= info.commandTime + 2) { + this.ambulanceTeamInfoMap.put(mat.getAgentID(), this.update(info, mat)); + } + } + for (CommunicationMessage message : messageManager + .getReceivedMessageList(CommandAmbulance.class)) { + CommandAmbulance command = (CommandAmbulance) message; + if (command.getAction() == CommandAmbulance.ACTION_RESCUE + && command.isBroadcast()) { + this.priorityHumans.add(command.getTargetID()); + this.targetHumans.add(command.getTargetID()); + } else if (command.getAction() == CommandAmbulance.ACTION_LOAD + && command.isBroadcast()) { + this.priorityHumans.add(command.getTargetID()); + this.targetHumans.add(command.getTargetID()); + } + } + for (CommunicationMessage message : messageManager + .getReceivedMessageList(MessageReport.class)) { + MessageReport report = (MessageReport) message; + AmbulanceTeamInfo info = this.ambulanceTeamInfoMap + .get(report.getSenderID()); + if (info != null && report.isDone()) { + info.canNewAction = true; + this.priorityHumans.remove(info.target); + this.targetHumans.remove(info.target); + info.target = null; + this.ambulanceTeamInfoMap.put(info.agentID, info); + } + } + return this; + } + + + private Map + convert(Map map) { + Map result = new HashMap<>(); + for (EntityID id : map.keySet()) { + AmbulanceTeamInfo info = map.get(id); + if (info != null && info.target != null) { + result.put(id, info.target); + } + } + return result; + } + + + private List + getActionAgents(Map map) { + List result = new ArrayList<>(); + for (StandardEntity entity : this.worldInfo + .getEntitiesOfType(StandardEntityURN.POLICE_FORCE)) { + AmbulanceTeamInfo info = map.get(entity.getID()); + if (info != null && info.canNewAction + && ((AmbulanceTeam) entity).isPositionDefined()) { + result.add(entity); + } + } + return result; + } + + + private AmbulanceTeamInfo update(AmbulanceTeamInfo info, + MessageAmbulanceTeam message) { + if (message.isBuriednessDefined() && message.getBuriedness() > 0) { + info.canNewAction = false; + if (info.target != null) { + this.targetHumans.add(info.target); + info.target = null; + } + return info; + } + if (message.getAction() == MessageAmbulanceTeam.ACTION_REST) { + info.canNewAction = true; + if (info.target != null) { + this.targetHumans.add(info.target); + info.target = null; + } + } else if (message.getAction() == MessageAmbulanceTeam.ACTION_MOVE) { + if (message.getTargetID() != null) { + StandardEntity entity = this.worldInfo.getEntity(message.getTargetID()); + if (entity != null) { + if (entity instanceof Area) { + if (entity.getStandardURN() == REFUGE) { + info.canNewAction = false; + return info; + } + StandardEntity targetEntity = this.worldInfo.getEntity(info.target); + if (targetEntity != null) { + if (targetEntity instanceof Human) { + targetEntity = this.worldInfo.getPosition((Human) targetEntity); + if (targetEntity == null) { + this.priorityHumans.remove(info.target); + this.targetHumans.remove(info.target); + info.canNewAction = true; + info.target = null; + return info; + } + } + if (targetEntity.getID().getValue() == entity.getID() + .getValue()) { + info.canNewAction = false; + } else { + info.canNewAction = true; + if (info.target != null) { + this.targetHumans.add(info.target); + info.target = null; + } + } + } else { + info.canNewAction = true; + info.target = null; + } + return info; + } else if (entity instanceof Human) { + if (entity.getID().getValue() == info.target.getValue()) { + info.canNewAction = false; + } else { + info.canNewAction = true; + this.targetHumans.add(info.target); + this.targetHumans.add(entity.getID()); + info.target = null; + } + return info; + } + } + } + info.canNewAction = true; + if (info.target != null) { + this.targetHumans.add(info.target); + info.target = null; + } + } else if (message.getAction() == MessageAmbulanceTeam.ACTION_RESCUE) { + info.canNewAction = true; + if (info.target != null) { + this.targetHumans.add(info.target); + info.target = null; + } + } else if (message.getAction() == MessageAmbulanceTeam.ACTION_LOAD) { + info.canNewAction = false; + } else if (message.getAction() == MessageAmbulanceTeam.ACTION_UNLOAD) { + info.canNewAction = true; + this.priorityHumans.remove(info.target); + this.targetHumans.remove(info.target); + info.target = null; + } + return info; + } + + private class AmbulanceTeamInfo { + + EntityID agentID; + EntityID target; + boolean canNewAction; + int commandTime; + + AmbulanceTeamInfo(EntityID id) { + agentID = id; + target = null; + canNewAction = true; + commandTime = -1; + } + } + + private class DistanceSorter implements Comparator { + + private StandardEntity reference; + private WorldInfo worldInfo; + + DistanceSorter(WorldInfo wi, StandardEntity reference) { + this.reference = reference; + this.worldInfo = wi; + } + + + public int compare(StandardEntity a, StandardEntity b) { + int d1 = this.worldInfo.getDistance(this.reference, a); + int d2 = this.worldInfo.getDistance(this.reference, b); + return d1 - d2; + } + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultBuildingDetector.java b/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultBuildingDetector.java new file mode 100644 index 0000000..a266d8a --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultBuildingDetector.java @@ -0,0 +1,209 @@ +package adf_core_python.impl.module.complex; + +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.algorithm.Clustering; +import adf_core_python.core.component.module.complex.BuildingDetector; +import rescuecore2.misc.geometry.Vector2D; +import rescuecore2.standard.entities.Building; +import rescuecore2.standard.entities.StandardEntity; +import rescuecore2.standard.entities.StandardEntityURN; +import rescuecore2.worldmodel.EntityID; + +import java.util.*; + +public class DefaultBuildingDetector extends BuildingDetector { + + private EntityID result; + + private Clustering clustering; + + public DefaultBuildingDetector(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + switch (si.getMode()) { + case PRECOMPUTATION_PHASE: + case PRECOMPUTED: + case NON_PRECOMPUTE: + this.clustering = moduleManager.getModule( + "DefaultBuildingDetector.Clustering", + "adf_core_python_core_python.impl.module.algorithm.KMeansClustering"); + break; + } + registerModule(this.clustering); + } + + + @Override + public BuildingDetector updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + if (this.getCountUpdateInfo() >= 2) { + return this; + } + + return this; + } + + + @Override + public BuildingDetector calc() { + this.result = this.calcTargetInCluster(); + if (this.result == null) { + this.result = this.calcTargetInWorld(); + } + return this; + } + + + private EntityID calcTargetInCluster() { + int clusterIndex = this.clustering.getClusterIndex(this.agentInfo.getID()); + Collection elements = this.clustering + .getClusterEntities(clusterIndex); + if (elements == null || elements.isEmpty()) { + return null; + } + StandardEntity me = this.agentInfo.me(); + List agents = new ArrayList<>( + this.worldInfo.getEntitiesOfType(StandardEntityURN.FIRE_BRIGADE)); + Set fireBuildings = new HashSet<>(); + for (StandardEntity entity : elements) { + if (entity instanceof Building && ((Building) entity).isOnFire()) { + fireBuildings.add(entity); + } + } + for (StandardEntity entity : fireBuildings) { + if (agents.isEmpty()) { + break; + } else if (agents.size() == 1) { + if (agents.get(0).getID().getValue() == me.getID().getValue()) { + return entity.getID(); + } + break; + } + agents.sort(new DistanceSorter(this.worldInfo, entity)); + StandardEntity a0 = agents.get(0); + StandardEntity a1 = agents.get(1); + + if (me.getID().getValue() == a0.getID().getValue() + || me.getID().getValue() == a1.getID().getValue()) { + return entity.getID(); + } else { + agents.remove(a0); + agents.remove(a1); + } + } + return null; + } + + + private EntityID calcTargetInWorld() { + Collection entities = this.worldInfo.getEntitiesOfType( + StandardEntityURN.BUILDING, StandardEntityURN.GAS_STATION, + StandardEntityURN.AMBULANCE_CENTRE, StandardEntityURN.FIRE_STATION, + StandardEntityURN.POLICE_OFFICE); + StandardEntity me = this.agentInfo.me(); + List agents = new ArrayList<>( + worldInfo.getEntitiesOfType(StandardEntityURN.FIRE_BRIGADE)); + Set fireBuildings = new HashSet<>(); + for (StandardEntity entity : entities) { + if (((Building) entity).isOnFire()) { + fireBuildings.add(entity); + } + } + for (StandardEntity entity : fireBuildings) { + if (agents.isEmpty()) { + break; + } else if (agents.size() == 1) { + if (agents.get(0).getID().getValue() == me.getID().getValue()) { + return entity.getID(); + } + break; + } + agents.sort(new DistanceSorter(this.worldInfo, entity)); + StandardEntity a0 = agents.get(0); + StandardEntity a1 = agents.get(1); + + if (me.getID().getValue() == a0.getID().getValue() + || me.getID().getValue() == a1.getID().getValue()) { + return entity.getID(); + } else { + agents.remove(a0); + agents.remove(a1); + } + } + return null; + } + + + @Override + public EntityID getTarget() { + return this.result; + } + + + @Override + public BuildingDetector precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + if (this.getCountPrecompute() >= 2) { + return this; + } + return this; + } + + + @Override + public BuildingDetector resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + if (this.getCountPrecompute() >= 2) { + return this; + } + return this; + } + + + @Override + public BuildingDetector preparate() { + super.preparate(); + if (this.getCountPrecompute() >= 2) { + return this; + } + return this; + } + + + @SuppressWarnings("unused") + private double getAngle(Vector2D v1, Vector2D v2) { + double flag = (v1.getX() * v2.getY()) - (v1.getY() * v2.getX()); + double angle = Math.acos(((v1.getX() * v2.getX()) + (v1.getY() * v2.getY())) + / (v1.getLength() * v2.getLength())); + if (flag > 0) { + return angle; + } + if (flag < 0) { + return -1 * angle; + } + return 0.0D; + } + + private class DistanceSorter implements Comparator { + + private StandardEntity reference; + private WorldInfo worldInfo; + + DistanceSorter(WorldInfo wi, StandardEntity reference) { + this.reference = reference; + this.worldInfo = wi; + } + + + public int compare(StandardEntity a, StandardEntity b) { + int d1 = this.worldInfo.getDistance(this.reference, a); + int d2 = this.worldInfo.getDistance(this.reference, b); + return d1 - d2; + } + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultFireTargetAllocator.java b/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultFireTargetAllocator.java new file mode 100644 index 0000000..1d3224f --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultFireTargetAllocator.java @@ -0,0 +1,345 @@ +package adf_core_python.impl.module.complex; + +import adf.core.agent.communication.standard.bundle.MessageUtil; +import adf.core.agent.communication.standard.bundle.centralized.CommandFire; +import adf.core.agent.communication.standard.bundle.centralized.MessageReport; +import adf.core.agent.communication.standard.bundle.information.MessageCivilian; +import adf.core.agent.communication.standard.bundle.information.MessageFireBrigade; +import adf.core.agent.communication.standard.bundle.information.MessagePoliceForce; +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf.core.component.communication.CommunicationMessage; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.complex.FireTargetAllocator; +import rescuecore2.standard.entities.*; +import rescuecore2.worldmodel.EntityID; + +import java.util.*; + +import static rescuecore2.standard.entities.StandardEntityURN.REFUGE; + +public class DefaultFireTargetAllocator extends FireTargetAllocator { + + private Collection priorityHumans; + private Collection targetHumans; + + private Map fireBrigadeInfoMap; + + public DefaultFireTargetAllocator(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + this.priorityHumans = new HashSet<>(); + this.targetHumans = new HashSet<>(); + this.fireBrigadeInfoMap = new HashMap<>(); + } + + + @Override + public FireTargetAllocator resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + if (this.getCountResume() >= 2) { + return this; + } + for (EntityID id : this.worldInfo + .getEntityIDsOfType(StandardEntityURN.FIRE_BRIGADE)) { + this.fireBrigadeInfoMap.put(id, new FireBrigadeInfo(id)); + } + return this; + } + + + @Override + public FireTargetAllocator preparate() { + super.preparate(); + if (this.getCountPrecompute() >= 2) { + return this; + } + for (EntityID id : this.worldInfo + .getEntityIDsOfType(StandardEntityURN.AMBULANCE_TEAM)) { + this.fireBrigadeInfoMap.put(id, new FireBrigadeInfo(id)); + } + return this; + } + + + @Override + public Map getResult() { + return this.convert(this.fireBrigadeInfoMap); + } + + + @Override + public FireTargetAllocator calc() { + List agents = this.getActionAgents(this.fireBrigadeInfoMap); + Collection removes = new ArrayList<>(); + int currentTime = this.agentInfo.getTime(); + for (EntityID target : this.priorityHumans) { + if (agents.size() > 0) { + StandardEntity targetEntity = this.worldInfo.getEntity(target); + if (targetEntity != null && targetEntity instanceof Human + && ((Human) targetEntity).isPositionDefined()) { + agents.sort(new DistanceSorter(this.worldInfo, targetEntity)); + StandardEntity result = agents.get(0); + agents.remove(0); + FireBrigadeInfo info = this.fireBrigadeInfoMap.get(result.getID()); + if (info != null) { + info.canNewAction = false; + info.target = target; + info.commandTime = currentTime; + this.fireBrigadeInfoMap.put(result.getID(), info); + removes.add(target); + } + } + } + } + this.priorityHumans.removeAll(removes); + removes.clear(); + for (EntityID target : this.targetHumans) { + if (agents.size() > 0) { + StandardEntity targetEntity = this.worldInfo.getEntity(target); + if (targetEntity != null && targetEntity instanceof Human + && ((Human) targetEntity).isPositionDefined()) { + agents.sort(new DistanceSorter(this.worldInfo, targetEntity)); + StandardEntity result = agents.get(0); + agents.remove(0); + FireBrigadeInfo info = this.fireBrigadeInfoMap.get(result.getID()); + if (info != null) { + info.canNewAction = false; + info.target = target; + info.commandTime = currentTime; + this.fireBrigadeInfoMap.put(result.getID(), info); + removes.add(target); + } + } + } + } + this.targetHumans.removeAll(removes); + return this; + } + + + @Override + public FireTargetAllocator updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + if (this.getCountUpdateInfo() >= 2) { + return this; + } + int currentTime = this.agentInfo.getTime(); + for (CommunicationMessage message : messageManager + .getReceivedMessageList()) { + Class messageClass = message.getClass(); + if (messageClass == MessageCivilian.class) { + MessageCivilian mc = (MessageCivilian) message; + MessageUtil.reflectMessage(this.worldInfo, mc); + if (mc.isBuriednessDefined() && mc.getBuriedness() > 0) { + this.targetHumans.add(mc.getAgentID()); + } else { + this.priorityHumans.remove(mc.getAgentID()); + this.targetHumans.remove(mc.getAgentID()); + } + } else if (messageClass == MessageFireBrigade.class) { + MessageFireBrigade mfb = (MessageFireBrigade) message; + MessageUtil.reflectMessage(this.worldInfo, mfb); + if (mfb.isBuriednessDefined() && mfb.getBuriedness() > 0) { + this.priorityHumans.add(mfb.getAgentID()); + } else { + this.priorityHumans.remove(mfb.getAgentID()); + this.targetHumans.remove(mfb.getAgentID()); + } + } else if (messageClass == MessagePoliceForce.class) { + MessagePoliceForce mpf = (MessagePoliceForce) message; + MessageUtil.reflectMessage(this.worldInfo, mpf); + if (mpf.isBuriednessDefined() && mpf.getBuriedness() > 0) { + this.priorityHumans.add(mpf.getAgentID()); + } else { + this.priorityHumans.remove(mpf.getAgentID()); + this.targetHumans.remove(mpf.getAgentID()); + } + } + } + for (CommunicationMessage message : messageManager + .getReceivedMessageList(MessageFireBrigade.class)) { + MessageFireBrigade mat = (MessageFireBrigade) message; + MessageUtil.reflectMessage(this.worldInfo, mat); + if (mat.isBuriednessDefined() && mat.getBuriedness() > 0) { + this.priorityHumans.add(mat.getAgentID()); + } else { + this.priorityHumans.remove(mat.getAgentID()); + this.targetHumans.remove(mat.getAgentID()); + } + FireBrigadeInfo info = this.fireBrigadeInfoMap.get(mat.getAgentID()); + if (info == null) { + info = new FireBrigadeInfo(mat.getAgentID()); + } + if (currentTime >= info.commandTime + 2) { + this.fireBrigadeInfoMap.put(mat.getAgentID(), this.update(info, mat)); + } + } + for (CommunicationMessage message : messageManager + .getReceivedMessageList(CommandFire.class)) { + CommandFire command = (CommandFire) message; + if (command.getAction() == CommandFire.ACTION_RESCUE + && command.isBroadcast()) { + this.priorityHumans.add(command.getTargetID()); + this.targetHumans.add(command.getTargetID()); + } + } + for (CommunicationMessage message : messageManager + .getReceivedMessageList(MessageReport.class)) { + MessageReport report = (MessageReport) message; + FireBrigadeInfo info = this.fireBrigadeInfoMap.get(report.getSenderID()); + if (info != null && report.isDone()) { + info.canNewAction = true; + this.priorityHumans.remove(info.target); + this.targetHumans.remove(info.target); + info.target = null; + this.fireBrigadeInfoMap.put(info.agentID, info); + } + } + return this; + } + + + private Map convert(Map map) { + Map result = new HashMap<>(); + for (EntityID id : map.keySet()) { + FireBrigadeInfo info = map.get(id); + if (info != null && info.target != null) { + result.put(id, info.target); + } + } + return result; + } + + + private List + getActionAgents(Map map) { + List result = new ArrayList<>(); + for (StandardEntity entity : this.worldInfo + .getEntitiesOfType(StandardEntityURN.POLICE_FORCE)) { + FireBrigadeInfo info = map.get(entity.getID()); + if (info != null && info.canNewAction + && ((FireBrigade) entity).isPositionDefined()) { + result.add(entity); + } + } + return result; + } + + + private FireBrigadeInfo update(FireBrigadeInfo info, + MessageFireBrigade message) { + if (message.isBuriednessDefined() && message.getBuriedness() > 0) { + info.canNewAction = false; + if (info.target != null) { + this.targetHumans.add(info.target); + info.target = null; + } + return info; + } + if (message.getAction() == MessageFireBrigade.ACTION_REST) { + info.canNewAction = true; + if (info.target != null) { + this.targetHumans.add(info.target); + info.target = null; + } + } else if (message.getAction() == MessageFireBrigade.ACTION_MOVE) { + if (message.getTargetID() != null) { + StandardEntity entity = this.worldInfo.getEntity(message.getTargetID()); + if (entity != null) { + if (entity instanceof Area) { + if (entity.getStandardURN() == REFUGE) { + info.canNewAction = false; + return info; + } + StandardEntity targetEntity = this.worldInfo.getEntity(info.target); + if (targetEntity != null) { + if (targetEntity instanceof Human) { + targetEntity = this.worldInfo.getPosition((Human) targetEntity); + if (targetEntity == null) { + this.priorityHumans.remove(info.target); + this.targetHumans.remove(info.target); + info.canNewAction = true; + info.target = null; + return info; + } + } + if (targetEntity.getID().getValue() == entity.getID() + .getValue()) { + info.canNewAction = false; + } else { + info.canNewAction = true; + if (info.target != null) { + this.targetHumans.add(info.target); + info.target = null; + } + } + } else { + info.canNewAction = true; + info.target = null; + } + return info; + } else if (entity instanceof Human) { + if (entity.getID().getValue() == info.target.getValue()) { + info.canNewAction = false; + } else { + info.canNewAction = true; + this.targetHumans.add(info.target); + this.targetHumans.add(entity.getID()); + info.target = null; + } + return info; + } + } + } + info.canNewAction = true; + if (info.target != null) { + this.targetHumans.add(info.target); + info.target = null; + } + } else if (message.getAction() == MessageFireBrigade.ACTION_RESCUE) { + info.canNewAction = true; + if (info.target != null) { + this.targetHumans.add(info.target); + info.target = null; + } + } + return info; + } + + private class FireBrigadeInfo { + + EntityID agentID; + EntityID target; + boolean canNewAction; + int commandTime; + + FireBrigadeInfo(EntityID id) { + agentID = id; + target = null; + canNewAction = true; + commandTime = -1; + } + } + + private class DistanceSorter implements Comparator { + + private StandardEntity reference; + private WorldInfo worldInfo; + + DistanceSorter(WorldInfo wi, StandardEntity reference) { + this.reference = reference; + this.worldInfo = wi; + } + + + public int compare(StandardEntity a, StandardEntity b) { + int d1 = this.worldInfo.getDistance(this.reference, a); + int d2 = this.worldInfo.getDistance(this.reference, b); + return d1 - d2; + } + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultHumanDetector.java b/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultHumanDetector.java new file mode 100644 index 0000000..21d0262 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultHumanDetector.java @@ -0,0 +1,252 @@ +package adf_core_python.impl.module.complex; + +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.algorithm.Clustering; +import adf_core_python.core.component.module.complex.HumanDetector; +import rescuecore2.standard.entities.Area; +import rescuecore2.standard.entities.Human; +import rescuecore2.standard.entities.StandardEntity; +import rescuecore2.standard.entities.StandardEntityURN; +import rescuecore2.worldmodel.EntityID; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; + +import static rescuecore2.standard.entities.StandardEntityURN.*; + +public class DefaultHumanDetector extends HumanDetector { + + private Clustering clustering; + + private EntityID result; + + public DefaultHumanDetector(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + + this.result = null; + + switch (scenarioInfo.getMode()) { + case PRECOMPUTATION_PHASE: + case PRECOMPUTED: + case NON_PRECOMPUTE: + this.clustering = moduleManager.getModule( + "DefaultHumanDetector.Clustering", + "adf_core_python.impl.module.algorithm.KMeansClustering"); + break; + } + registerModule(this.clustering); + } + + + @Override + public HumanDetector updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + if (this.getCountUpdateInfo() > 1) { + return this; + } + + return this; + } + + + @Override + public HumanDetector calc() { + Human transportHuman = this.agentInfo.someoneOnBoard(); + if (transportHuman != null) { + this.result = transportHuman.getID(); + return this; + } + if (this.result != null) { + Human target = (Human) this.worldInfo.getEntity(this.result); + if (target != null) { + if (!target.isHPDefined() || target.getHP() == 0) { + this.result = null; + } else if (!target.isPositionDefined()) { + this.result = null; + } else { + StandardEntity position = this.worldInfo.getPosition(target); + if (position != null) { + StandardEntityURN positionURN = position.getStandardURN(); + if (positionURN == REFUGE || positionURN == AMBULANCE_TEAM) { + this.result = null; + } + } + } + } + } + if (this.result == null) { + if (clustering == null) { + this.result = this.calcTargetInWorld(); + return this; + } + this.result = this.calcTargetInCluster(clustering); + if (this.result == null) { + this.result = this.calcTargetInWorld(); + } + } + return this; + } + + + private EntityID calcTargetInCluster(Clustering clustering) { + int clusterIndex = clustering.getClusterIndex(this.agentInfo.getID()); + Collection< + StandardEntity> elements = clustering.getClusterEntities(clusterIndex); + if (elements == null || elements.isEmpty()) { + return null; + } + + List rescueTargets = new ArrayList<>(); + List loadTargets = new ArrayList<>(); + for (StandardEntity next : this.worldInfo.getEntitiesOfType(AMBULANCE_TEAM, + FIRE_BRIGADE, POLICE_FORCE)) { + Human h = (Human) next; + if (this.agentInfo.getID().getValue() == h.getID().getValue()) { + continue; + } + StandardEntity positionEntity = this.worldInfo.getPosition(h); + if (positionEntity != null && elements.contains(positionEntity) + || elements.contains(h)) { + if (h.isHPDefined() && h.isBuriednessDefined() && h.getHP() > 0 + && h.getBuriedness() > 0) { + rescueTargets.add(h); + } + } + } + for (StandardEntity next : this.worldInfo.getEntitiesOfType(CIVILIAN)) { + Human h = (Human) next; + StandardEntity positionEntity = this.worldInfo.getPosition(h); + if (positionEntity != null && positionEntity instanceof Area) { + if (elements.contains(positionEntity)) { + if (h.isHPDefined() && h.getHP() > 0) { + if (h.isBuriednessDefined() && h.getBuriedness() > 0) { + rescueTargets.add(h); + } else { + if (h.isDamageDefined() && h.getDamage() > 0 + && positionEntity.getStandardURN() != REFUGE) { + loadTargets.add(h); + } + } + } + } + } + } + if (rescueTargets.size() > 0) { + rescueTargets + .sort(new DistanceSorter(this.worldInfo, this.agentInfo.me())); + return rescueTargets.get(0).getID(); + } + if (loadTargets.size() > 0) { + loadTargets.sort(new DistanceSorter(this.worldInfo, this.agentInfo.me())); + return loadTargets.get(0).getID(); + } + return null; + } + + + private EntityID calcTargetInWorld() { + List rescueTargets = new ArrayList<>(); + List loadTargets = new ArrayList<>(); + for (StandardEntity next : this.worldInfo.getEntitiesOfType(AMBULANCE_TEAM, + FIRE_BRIGADE, POLICE_FORCE)) { + Human h = (Human) next; + if (this.agentInfo.getID().getValue() != h.getID().getValue()) { + StandardEntity positionEntity = this.worldInfo.getPosition(h); + if (positionEntity != null && h.isHPDefined() + && h.isBuriednessDefined()) { + if (h.getHP() > 0 && h.getBuriedness() > 0) { + rescueTargets.add(h); + } + } + } + } + for (StandardEntity next : this.worldInfo.getEntitiesOfType(CIVILIAN)) { + Human h = (Human) next; + StandardEntity positionEntity = this.worldInfo.getPosition(h); + if (positionEntity != null && positionEntity instanceof Area) { + if (h.isHPDefined() && h.getHP() > 0) { + if (h.isBuriednessDefined() && h.getBuriedness() > 0) { + rescueTargets.add(h); + } else { + if (h.isDamageDefined() && h.getDamage() > 0 + && positionEntity.getStandardURN() != REFUGE) { + loadTargets.add(h); + } + } + } + } + } + if (rescueTargets.size() > 0) { + rescueTargets + .sort(new DistanceSorter(this.worldInfo, this.agentInfo.me())); + return rescueTargets.get(0).getID(); + } + if (loadTargets.size() > 0) { + loadTargets.sort(new DistanceSorter(this.worldInfo, this.agentInfo.me())); + return loadTargets.get(0).getID(); + } + return null; + } + + + @Override + public EntityID getTarget() { + return this.result; + } + + + @Override + public HumanDetector precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + if (this.getCountPrecompute() >= 2) { + return this; + } + return this; + } + + + @Override + public HumanDetector resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + if (this.getCountResume() >= 2) { + return this; + } + return this; + } + + + @Override + public HumanDetector preparate() { + super.preparate(); + if (this.getCountPreparate() >= 2) { + return this; + } + return this; + } + + private class DistanceSorter implements Comparator { + + private StandardEntity reference; + private WorldInfo worldInfo; + + DistanceSorter(WorldInfo wi, StandardEntity reference) { + this.reference = reference; + this.worldInfo = wi; + } + + + public int compare(StandardEntity a, StandardEntity b) { + int d1 = this.worldInfo.getDistance(this.reference, a); + int d2 = this.worldInfo.getDistance(this.reference, b); + return d1 - d2; + } + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultPoliceTargetAllocator.java b/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultPoliceTargetAllocator.java new file mode 100644 index 0000000..688c510 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultPoliceTargetAllocator.java @@ -0,0 +1,323 @@ +package adf_core_python.impl.module.complex; + +import adf.core.agent.communication.standard.bundle.MessageUtil; +import adf.core.agent.communication.standard.bundle.centralized.CommandPolice; +import adf.core.agent.communication.standard.bundle.centralized.MessageReport; +import adf.core.agent.communication.standard.bundle.information.MessagePoliceForce; +import adf.core.agent.communication.standard.bundle.information.MessageRoad; +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf.core.component.communication.CommunicationMessage; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.complex.PoliceTargetAllocator; +import rescuecore2.standard.entities.*; +import rescuecore2.worldmodel.EntityID; + +import java.util.*; + +import static rescuecore2.standard.entities.StandardEntityURN.*; + +public class DefaultPoliceTargetAllocator extends PoliceTargetAllocator { + + private Collection priorityAreas; + private Collection targetAreas; + + private Map agentInfoMap; + + public DefaultPoliceTargetAllocator(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + this.priorityAreas = new HashSet<>(); + this.targetAreas = new HashSet<>(); + this.agentInfoMap = new HashMap<>(); + } + + + @Override + public PoliceTargetAllocator resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + if (this.getCountResume() >= 2) { + return this; + } + for (EntityID id : this.worldInfo + .getEntityIDsOfType(StandardEntityURN.POLICE_FORCE)) { + this.agentInfoMap.put(id, new PoliceForceInfo(id)); + } + for (StandardEntity e : this.worldInfo.getEntitiesOfType(REFUGE, BUILDING, + GAS_STATION)) { + for (EntityID id : ((Building) e).getNeighbours()) { + StandardEntity neighbour = this.worldInfo.getEntity(id); + if (neighbour instanceof Road) { + this.targetAreas.add(id); + } + } + } + for (StandardEntity e : this.worldInfo.getEntitiesOfType(REFUGE)) { + for (EntityID id : ((Building) e).getNeighbours()) { + StandardEntity neighbour = this.worldInfo.getEntity(id); + if (neighbour instanceof Road) { + this.priorityAreas.add(id); + } + } + } + return this; + } + + + @Override + public PoliceTargetAllocator preparate() { + super.preparate(); + if (this.getCountPrecompute() >= 2) { + return this; + } + for (EntityID id : this.worldInfo + .getEntityIDsOfType(StandardEntityURN.POLICE_FORCE)) { + this.agentInfoMap.put(id, new PoliceForceInfo(id)); + } + for (StandardEntity e : this.worldInfo.getEntitiesOfType(REFUGE, BUILDING, + GAS_STATION)) { + for (EntityID id : ((Building) e).getNeighbours()) { + StandardEntity neighbour = this.worldInfo.getEntity(id); + if (neighbour instanceof Road) { + this.targetAreas.add(id); + } + } + } + for (StandardEntity e : this.worldInfo.getEntitiesOfType(REFUGE)) { + for (EntityID id : ((Building) e).getNeighbours()) { + StandardEntity neighbour = this.worldInfo.getEntity(id); + if (neighbour instanceof Road) { + this.priorityAreas.add(id); + } + } + } + return this; + } + + + @Override + public Map getResult() { + return this.convert(this.agentInfoMap); + } + + + @Override + public PoliceTargetAllocator calc() { + List agents = this.getActionAgents(this.agentInfoMap); + Collection removes = new ArrayList<>(); + int currentTime = this.agentInfo.getTime(); + for (EntityID target : this.priorityAreas) { + if (agents.size() > 0) { + StandardEntity targetEntity = this.worldInfo.getEntity(target); + if (targetEntity != null) { + agents.sort(new DistanceSorter(this.worldInfo, targetEntity)); + StandardEntity result = agents.get(0); + agents.remove(0); + PoliceForceInfo info = this.agentInfoMap.get(result.getID()); + if (info != null) { + info.canNewAction = false; + info.target = target; + info.commandTime = currentTime; + this.agentInfoMap.put(result.getID(), info); + removes.add(target); + } + } + } + } + this.priorityAreas.removeAll(removes); + List areas = new ArrayList<>(); + for (EntityID target : this.targetAreas) { + StandardEntity targetEntity = this.worldInfo.getEntity(target); + if (targetEntity != null) { + areas.add(targetEntity); + } + } + for (StandardEntity agent : agents) { + if (areas.size() > 0) { + areas.sort(new DistanceSorter(this.worldInfo, agent)); + StandardEntity result = areas.get(0); + areas.remove(0); + this.targetAreas.remove(result.getID()); + PoliceForceInfo info = this.agentInfoMap.get(agent.getID()); + if (info != null) { + info.canNewAction = false; + info.target = result.getID(); + info.commandTime = currentTime; + this.agentInfoMap.put(agent.getID(), info); + } + } + } + return this; + } + + + @Override + public PoliceTargetAllocator updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + if (this.getCountUpdateInfo() >= 2) { + return this; + } + int currentTime = this.agentInfo.getTime(); + for (CommunicationMessage message : messageManager + .getReceivedMessageList(MessageRoad.class)) { + MessageRoad mpf = (MessageRoad) message; + MessageUtil.reflectMessage(this.worldInfo, mpf); + } + for (CommunicationMessage message : messageManager + .getReceivedMessageList(MessagePoliceForce.class)) { + MessagePoliceForce mpf = (MessagePoliceForce) message; + MessageUtil.reflectMessage(this.worldInfo, mpf); + PoliceForceInfo info = this.agentInfoMap.get(mpf.getAgentID()); + if (info == null) { + info = new PoliceForceInfo(mpf.getAgentID()); + } + if (currentTime >= info.commandTime + 2) { + this.agentInfoMap.put(mpf.getAgentID(), this.update(info, mpf)); + } + } + for (CommunicationMessage message : messageManager + .getReceivedMessageList(CommandPolice.class)) { + CommandPolice command = (CommandPolice) message; + if (command.getAction() == CommandPolice.ACTION_CLEAR + && command.isBroadcast()) { + this.priorityAreas.add(command.getTargetID()); + this.targetAreas.add(command.getTargetID()); + } + } + for (CommunicationMessage message : messageManager + .getReceivedMessageList(MessageReport.class)) { + MessageReport report = (MessageReport) message; + PoliceForceInfo info = this.agentInfoMap.get(report.getSenderID()); + if (info != null && report.isDone()) { + info.canNewAction = true; + this.priorityAreas.remove(info.target); + this.targetAreas.remove(info.target); + info.target = null; + this.agentInfoMap.put(info.agentID, info); + } + } + return this; + } + + + private PoliceForceInfo update(PoliceForceInfo info, + MessagePoliceForce message) { + if (message.isBuriednessDefined() && message.getBuriedness() > 0) { + info.canNewAction = false; + if (info.target != null) { + this.targetAreas.add(info.target); + info.target = null; + } + return info; + } + if (message.getAction() == MessagePoliceForce.ACTION_REST) { + info.canNewAction = true; + if (info.target != null) { + this.targetAreas.add(info.target); + info.target = null; + } + } else if (message.getAction() == MessagePoliceForce.ACTION_MOVE) { + if (message.getTargetID() != null) { + StandardEntity entity = this.worldInfo.getEntity(message.getTargetID()); + if (entity != null && entity instanceof Area) { + if (info.target != null) { + StandardEntity targetEntity = this.worldInfo.getEntity(info.target); + if (targetEntity != null && targetEntity instanceof Area) { + if (message.getTargetID().getValue() == info.target.getValue()) { + info.canNewAction = false; + } else { + info.canNewAction = true; + this.targetAreas.add(info.target); + info.target = null; + } + } else { + info.canNewAction = true; + info.target = null; + } + } else { + info.canNewAction = true; + } + } else { + info.canNewAction = true; + if (info.target != null) { + this.targetAreas.add(info.target); + info.target = null; + } + } + } else { + info.canNewAction = true; + if (info.target != null) { + this.targetAreas.add(info.target); + info.target = null; + } + } + } else if (message.getAction() == MessagePoliceForce.ACTION_CLEAR) { + info.canNewAction = false; + } + return info; + } + + + private List + getActionAgents(Map infoMap) { + List result = new ArrayList<>(); + for (StandardEntity entity : this.worldInfo + .getEntitiesOfType(StandardEntityURN.POLICE_FORCE)) { + PoliceForceInfo info = infoMap.get(entity.getID()); + if (info != null && info.canNewAction + && ((PoliceForce) entity).isPositionDefined()) { + result.add(entity); + } + } + return result; + } + + + private Map + convert(Map infoMap) { + Map result = new HashMap<>(); + for (EntityID id : infoMap.keySet()) { + PoliceForceInfo info = infoMap.get(id); + if (info != null && info.target != null) { + result.put(id, info.target); + } + } + return result; + } + + private class PoliceForceInfo { + + EntityID agentID; + EntityID target; + boolean canNewAction; + int commandTime; + + PoliceForceInfo(EntityID id) { + agentID = id; + target = null; + canNewAction = true; + commandTime = -1; + } + } + + private class DistanceSorter implements Comparator { + + private StandardEntity reference; + private WorldInfo worldInfo; + + DistanceSorter(WorldInfo wi, StandardEntity reference) { + this.reference = reference; + this.worldInfo = wi; + } + + + public int compare(StandardEntity a, StandardEntity b) { + int d1 = this.worldInfo.getDistance(this.reference, a); + int d2 = this.worldInfo.getDistance(this.reference, b); + return d1 - d2; + } + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultRoadDetector.java b/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultRoadDetector.java new file mode 100644 index 0000000..277342c --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultRoadDetector.java @@ -0,0 +1,362 @@ +package adf_core_python.impl.module.complex; + +import adf.core.agent.communication.standard.bundle.MessageUtil; +import adf.core.agent.communication.standard.bundle.centralized.CommandPolice; +import adf.core.agent.communication.standard.bundle.information.MessageAmbulanceTeam; +import adf.core.agent.communication.standard.bundle.information.MessageFireBrigade; +import adf.core.agent.communication.standard.bundle.information.MessagePoliceForce; +import adf.core.agent.communication.standard.bundle.information.MessageRoad; +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf.core.component.communication.CommunicationMessage; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.algorithm.PathPlanning; +import adf_core_python.core.component.module.complex.RoadDetector; +import rescuecore2.standard.entities.*; +import rescuecore2.worldmodel.EntityID; + +import java.util.*; + +import static rescuecore2.standard.entities.StandardEntityURN.*; + +public class DefaultRoadDetector extends RoadDetector { + + private Set targetAreas; + private Set priorityRoads; + + private PathPlanning pathPlanning; + + private EntityID result; + + public DefaultRoadDetector(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + switch (scenarioInfo.getMode()) { + case PRECOMPUTATION_PHASE: + case PRECOMPUTED: + case NON_PRECOMPUTE: + this.pathPlanning = moduleManager.getModule( + "DefaultRoadDetector.PathPlanning", + "adf_core_python_core_python.impl.module.algorithm.DijkstraPathPlanning"); + break; + } + registerModule(this.pathPlanning); + this.result = null; + } + + + @Override + public RoadDetector calc() { + if (this.result == null) { + EntityID positionID = this.agentInfo.getPosition(); + if (this.targetAreas.contains(positionID)) { + this.result = positionID; + return this; + } + List removeList = new ArrayList<>(this.priorityRoads.size()); + for (EntityID id : this.priorityRoads) { + if (!this.targetAreas.contains(id)) { + removeList.add(id); + } + } + this.priorityRoads.removeAll(removeList); + if (this.priorityRoads.size() > 0) { + this.pathPlanning.setFrom(positionID); + this.pathPlanning.setDestination(this.targetAreas); + List path = this.pathPlanning.calc().getResult(); + if (path != null && path.size() > 0) { + this.result = path.get(path.size() - 1); + } + return this; + } + + this.pathPlanning.setFrom(positionID); + this.pathPlanning.setDestination(this.targetAreas); + List path = this.pathPlanning.calc().getResult(); + if (path != null && path.size() > 0) { + this.result = path.get(path.size() - 1); + } + } + return this; + } + + + @Override + public EntityID getTarget() { + return this.result; + } + + + @Override + public RoadDetector precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + if (this.getCountPrecompute() >= 2) { + return this; + } + return this; + } + + + @Override + public RoadDetector resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + if (this.getCountResume() >= 2) { + return this; + } + this.targetAreas = new HashSet<>(); + for (StandardEntity e : this.worldInfo.getEntitiesOfType(REFUGE, BUILDING, + GAS_STATION)) { + for (EntityID id : ((Building) e).getNeighbours()) { + StandardEntity neighbour = this.worldInfo.getEntity(id); + if (neighbour instanceof Road) { + this.targetAreas.add(id); + } + } + } + this.priorityRoads = new HashSet<>(); + for (StandardEntity e : this.worldInfo.getEntitiesOfType(REFUGE)) { + for (EntityID id : ((Building) e).getNeighbours()) { + StandardEntity neighbour = this.worldInfo.getEntity(id); + if (neighbour instanceof Road) { + this.priorityRoads.add(id); + } + } + } + return this; + } + + + @Override + public RoadDetector preparate() { + super.preparate(); + if (this.getCountPreparate() >= 2) { + return this; + } + this.targetAreas = new HashSet<>(); + for (StandardEntity e : this.worldInfo.getEntitiesOfType(REFUGE, BUILDING, + GAS_STATION)) { + for (EntityID id : ((Building) e).getNeighbours()) { + StandardEntity neighbour = this.worldInfo.getEntity(id); + if (neighbour instanceof Road) { + this.targetAreas.add(id); + } + } + } + this.priorityRoads = new HashSet<>(); + for (StandardEntity e : this.worldInfo.getEntitiesOfType(REFUGE)) { + for (EntityID id : ((Building) e).getNeighbours()) { + StandardEntity neighbour = this.worldInfo.getEntity(id); + if (neighbour instanceof Road) { + this.priorityRoads.add(id); + } + } + } + return this; + } + + + @Override + public RoadDetector updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + if (this.getCountUpdateInfo() >= 2) { + return this; + } + if (this.result != null) { + if (this.agentInfo.getPosition().equals(this.result)) { + StandardEntity entity = this.worldInfo.getEntity(this.result); + if (entity instanceof Building) { + this.result = null; + } else if (entity instanceof Road) { + Road road = (Road) entity; + if (!road.isBlockadesDefined() || road.getBlockades().isEmpty()) { + this.targetAreas.remove(this.result); + this.result = null; + } + } + } + } + Set changedEntities = this.worldInfo.getChanged() + .getChangedEntities(); + for (CommunicationMessage message : messageManager + .getReceivedMessageList()) { + Class messageClass = message.getClass(); + if (messageClass == MessageAmbulanceTeam.class) { + this.reflectMessage((MessageAmbulanceTeam) message); + } else if (messageClass == MessageFireBrigade.class) { + this.reflectMessage((MessageFireBrigade) message); + } else if (messageClass == MessageRoad.class) { + this.reflectMessage((MessageRoad) message, changedEntities); + } else if (messageClass == MessagePoliceForce.class) { + this.reflectMessage((MessagePoliceForce) message); + } else if (messageClass == CommandPolice.class) { + this.reflectMessage((CommandPolice) message); + } + } + for (EntityID id : this.worldInfo.getChanged().getChangedEntities()) { + StandardEntity entity = this.worldInfo.getEntity(id); + if (entity instanceof Road) { + Road road = (Road) entity; + if (!road.isBlockadesDefined() || road.getBlockades().isEmpty()) { + this.targetAreas.remove(id); + } + } + } + return this; + } + + + private void reflectMessage(MessageRoad messageRoad, + Collection changedEntities) { + if (messageRoad.isBlockadeDefined() + && !changedEntities.contains(messageRoad.getBlockadeID())) { + MessageUtil.reflectMessage(this.worldInfo, messageRoad); + } + if (messageRoad.isPassable()) { + this.targetAreas.remove(messageRoad.getRoadID()); + } + } + + + private void reflectMessage(MessageAmbulanceTeam messageAmbulanceTeam) { + if (messageAmbulanceTeam.getPosition() == null) { + return; + } + if (messageAmbulanceTeam + .getAction() == MessageAmbulanceTeam.ACTION_RESCUE) { + StandardEntity position = this.worldInfo + .getEntity(messageAmbulanceTeam.getPosition()); + if (position != null && position instanceof Building) { + this.targetAreas.removeAll(((Building) position).getNeighbours()); + } + } else if (messageAmbulanceTeam + .getAction() == MessageAmbulanceTeam.ACTION_LOAD) { + StandardEntity position = this.worldInfo + .getEntity(messageAmbulanceTeam.getPosition()); + if (position != null && position instanceof Building) { + this.targetAreas.removeAll(((Building) position).getNeighbours()); + } + } else if (messageAmbulanceTeam + .getAction() == MessageAmbulanceTeam.ACTION_MOVE) { + if (messageAmbulanceTeam.getTargetID() == null) { + return; + } + StandardEntity target = this.worldInfo + .getEntity(messageAmbulanceTeam.getTargetID()); + if (target instanceof Building) { + for (EntityID id : ((Building) target).getNeighbours()) { + StandardEntity neighbour = this.worldInfo.getEntity(id); + if (neighbour instanceof Road) { + this.priorityRoads.add(id); + } + } + } else if (target instanceof Human) { + Human human = (Human) target; + if (human.isPositionDefined()) { + StandardEntity position = this.worldInfo.getPosition(human); + if (position instanceof Building) { + for (EntityID id : ((Building) position).getNeighbours()) { + StandardEntity neighbour = this.worldInfo.getEntity(id); + if (neighbour instanceof Road) { + this.priorityRoads.add(id); + } + } + } + } + } + } + } + + + private void reflectMessage(MessageFireBrigade messageFireBrigade) { + if (messageFireBrigade.getTargetID() == null) { + return; + } + if (messageFireBrigade.getAction() == MessageFireBrigade.ACTION_REFILL) { + StandardEntity target = this.worldInfo + .getEntity(messageFireBrigade.getTargetID()); + if (target instanceof Building) { + for (EntityID id : ((Building) target).getNeighbours()) { + StandardEntity neighbour = this.worldInfo.getEntity(id); + if (neighbour instanceof Road) { + this.priorityRoads.add(id); + } + } + } else if (target.getStandardURN() == HYDRANT) { + this.priorityRoads.add(target.getID()); + this.targetAreas.add(target.getID()); + } + } + } + + + private void reflectMessage(MessagePoliceForce messagePoliceForce) { + if (messagePoliceForce.getAction() == MessagePoliceForce.ACTION_CLEAR) { + if (messagePoliceForce.getAgentID().getValue() != this.agentInfo.getID() + .getValue()) { + if (messagePoliceForce.isTargetDefined()) { + EntityID targetID = messagePoliceForce.getTargetID(); + if (targetID == null) { + return; + } + StandardEntity entity = this.worldInfo.getEntity(targetID); + if (entity == null) { + return; + } + + if (entity instanceof Area) { + this.targetAreas.remove(targetID); + if (this.result != null + && this.result.getValue() == targetID.getValue()) { + if (this.agentInfo.getID().getValue() < messagePoliceForce + .getAgentID().getValue()) { + this.result = null; + } + } + } else if (entity.getStandardURN() == BLOCKADE) { + EntityID position = ((Blockade) entity).getPosition(); + this.targetAreas.remove(position); + if (this.result != null + && this.result.getValue() == position.getValue()) { + if (this.agentInfo.getID().getValue() < messagePoliceForce + .getAgentID().getValue()) { + this.result = null; + } + } + } + + } + } + } + } + + + private void reflectMessage(CommandPolice commandPolice) { + boolean flag = false; + if (commandPolice.isToIDDefined() && this.agentInfo.getID() + .getValue() == commandPolice.getToID().getValue()) { + flag = true; + } else if (commandPolice.isBroadcast()) { + flag = true; + } + if (flag && commandPolice.getAction() == CommandPolice.ACTION_CLEAR) { + if (commandPolice.getTargetID() == null) { + return; + } + StandardEntity target = this.worldInfo + .getEntity(commandPolice.getTargetID()); + if (target instanceof Area) { + this.priorityRoads.add(target.getID()); + this.targetAreas.add(target.getID()); + } else if (target.getStandardURN() == BLOCKADE) { + Blockade blockade = (Blockade) target; + if (blockade.isPositionDefined()) { + this.priorityRoads.add(blockade.getPosition()); + this.targetAreas.add(blockade.getPosition()); + } + } + } + } +} diff --git a/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultSearch.java b/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultSearch.java new file mode 100644 index 0000000..c75c7ed --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/impl/module/complex/DefaultSearch.java @@ -0,0 +1,164 @@ +package adf_core_python.impl.module.complex; + +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.core.agent.communication.MessageManager; +import adf_core_python.core.agent.info.AgentInfo; +import adf_core_python.core.agent.module.ModuleManager; +import adf_core_python.core.agent.precompute.PrecomputeData; +import adf_core_python.core.component.module.algorithm.Clustering; +import adf_core_python.core.component.module.algorithm.PathPlanning; +import adf_core_python.core.component.module.complex.Search; +import rescuecore2.standard.entities.Building; +import rescuecore2.standard.entities.StandardEntity; +import rescuecore2.standard.entities.StandardEntityURN; +import rescuecore2.worldmodel.EntityID; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; + +import static rescuecore2.standard.entities.StandardEntityURN.*; + +public class DefaultSearch extends Search { + + private PathPlanning pathPlanning; + private Clustering clustering; + + private EntityID result; + private Collection unsearchedBuildingIDs; + + public DefaultSearch(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + + this.unsearchedBuildingIDs = new HashSet<>(); + + StandardEntityURN agentURN = ai.me().getStandardURN(); + switch (si.getMode()) { + case PRECOMPUTATION_PHASE: + case PRECOMPUTED: + case NON_PRECOMPUTE: + if (agentURN == AMBULANCE_TEAM) { + this.pathPlanning = moduleManager.getModule( + "DefaultSearch.PathPlanning.Ambulance", + "adf_core_python_core_python.impl.module.algorithm.DijkstraPathPlanning"); + this.clustering = moduleManager.getModule( + "DefaultSearch.Clustering.Ambulance", + "adf_core_python.impl.module.algorithm.KMeansClustering"); + } else if (agentURN == FIRE_BRIGADE) { + this.pathPlanning = moduleManager.getModule( + "DefaultSearch.PathPlanning.Fire", + "adf_core_python.impl.module.algorithm.DijkstraPathPlanning"); + this.clustering = moduleManager.getModule( + "DefaultSearch.Clustering.Fire", + "adf_core_python.impl.module.algorithm.KMeansClustering"); + } else if (agentURN == POLICE_FORCE) { + this.pathPlanning = moduleManager.getModule( + "DefaultSearch.PathPlanning.Police", + "adf_core_python.impl.module.algorithm.DijkstraPathPlanning"); + this.clustering = moduleManager.getModule( + "DefaultSearch.Clustering.Police", + "adf_core_python.impl.module.algorithm.KMeansClustering"); + } + break; + } + + registerModule(this.pathPlanning); + registerModule(this.clustering); + } + + + @Override + public Search updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + if (this.getCountUpdateInfo() >= 2) { + return this; + } + + this.unsearchedBuildingIDs + .removeAll(this.worldInfo.getChanged().getChangedEntities()); + + if (this.unsearchedBuildingIDs.isEmpty()) { + this.reset(); + this.unsearchedBuildingIDs + .removeAll(this.worldInfo.getChanged().getChangedEntities()); + } + return this; + } + + + @Override + public Search calc() { + this.result = null; + this.pathPlanning.setFrom(this.agentInfo.getPosition()); + this.pathPlanning.setDestination(this.unsearchedBuildingIDs); + List path = this.pathPlanning.calc().getResult(); + if (path != null && path.size() > 0) { + this.result = path.get(path.size() - 1); + } + return this; + } + + + private void reset() { + this.unsearchedBuildingIDs.clear(); + + Collection clusterEntities = null; + if (this.clustering != null) { + int clusterIndex = this.clustering + .getClusterIndex(this.agentInfo.getID()); + clusterEntities = this.clustering.getClusterEntities(clusterIndex); + + } + if (clusterEntities != null && clusterEntities.size() > 0) { + for (StandardEntity entity : clusterEntities) { + if (entity instanceof Building && entity.getStandardURN() != REFUGE) { + this.unsearchedBuildingIDs.add(entity.getID()); + } + } + } else { + this.unsearchedBuildingIDs + .addAll(this.worldInfo.getEntityIDsOfType(BUILDING, GAS_STATION, + AMBULANCE_CENTRE, FIRE_STATION, POLICE_OFFICE)); + } + } + + + @Override + public EntityID getTarget() { + return this.result; + } + + + @Override + public Search precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + if (this.getCountPrecompute() >= 2) { + return this; + } + return this; + } + + + @Override + public Search resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + if (this.getCountResume() >= 2) { + return this; + } + this.worldInfo.requestRollback(); + return this; + } + + + @Override + public Search preparate() { + super.preparate(); + if (this.getCountPreparate() >= 2) { + return this; + } + this.worldInfo.requestRollback(); + return this; + } +} From fba0179ce7ebe72c0bb94cf2de6ae3f70147ff65 Mon Sep 17 00:00:00 2001 From: harrki Date: Wed, 19 Mar 2025 00:54:52 +0900 Subject: [PATCH 220/249] fix: Add precompute files --- .gitignore | 1 + .../core/agent/precompute/PreData.java | 56 +++ .../core/agent/precompute/PrecomputeData.java | 400 ++++++++++++++++++ 3 files changed, 457 insertions(+) create mode 100644 java/lib/src/main/java/adf_core_python/core/agent/precompute/PreData.java create mode 100644 java/lib/src/main/java/adf_core_python/core/agent/precompute/PrecomputeData.java diff --git a/.gitignore b/.gitignore index 6119e70..41060c9 100644 --- a/.gitignore +++ b/.gitignore @@ -173,3 +173,4 @@ cython_debug/ # ADF agent.log* precompute +!java/lib/src/main/java/adf_core_python/core/agent/precompute diff --git a/java/lib/src/main/java/adf_core_python/core/agent/precompute/PreData.java b/java/lib/src/main/java/adf_core_python/core/agent/precompute/PreData.java new file mode 100644 index 0000000..aea850f --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/core/agent/precompute/PreData.java @@ -0,0 +1,56 @@ +package adf_core_python.core.agent.precompute; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class PreData { + + public Map intValues; + public Map doubleValues; + public Map stringValues; + public Map idValues; + public Map boolValues; + + public Map> intLists; + public Map> doubleLists; + public Map> stringLists; + public Map> idLists; + public Map> boolLists; + + public boolean isReady; + public String readyID; + + public PreData() { + this.intValues = new HashMap<>(); + this.doubleValues = new HashMap<>(); + this.stringValues = new HashMap<>(); + this.idValues = new HashMap<>(); + this.boolValues = new HashMap<>(); + this.intLists = new HashMap<>(); + this.doubleLists = new HashMap<>(); + this.stringLists = new HashMap<>(); + this.idLists = new HashMap<>(); + this.boolLists = new HashMap<>(); + this.isReady = false; + this.readyID = ""; + } + + + public PreData copy() { + PreData preData = new PreData(); + preData.intValues = new HashMap<>(this.intValues); + preData.doubleValues = new HashMap<>(this.doubleValues); + preData.stringValues = new HashMap<>(this.stringValues); + preData.idValues = new HashMap<>(this.idValues); + preData.boolValues = new HashMap<>(this.boolValues); + preData.intLists = new HashMap<>(this.intLists); + preData.doubleLists = new HashMap<>(this.doubleLists); + preData.stringLists = new HashMap<>(this.stringLists); + preData.idLists = new HashMap<>(this.idLists); + preData.boolLists = new HashMap<>(this.boolLists); + preData.isReady = this.isReady; + preData.readyID = this.readyID; + return preData; + } +} diff --git a/java/lib/src/main/java/adf_core_python/core/agent/precompute/PrecomputeData.java b/java/lib/src/main/java/adf_core_python/core/agent/precompute/PrecomputeData.java new file mode 100644 index 0000000..583d401 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/core/agent/precompute/PrecomputeData.java @@ -0,0 +1,400 @@ +package adf_core_python.core.agent.precompute; + +import adf.core.agent.info.WorldInfo; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.msgpack.jackson.dataformat.MessagePackFactory; +import rescuecore2.worldmodel.EntityID; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public final class PrecomputeData { + + public static final String DEFAULT_FILE_NAME = "data.bin"; + public static final File PRECOMP_DATA_DIR = new File("precomp_data"); + + private final String fileName; + + private PreData data; + + public PrecomputeData() { + this(DEFAULT_FILE_NAME); + } + + + public PrecomputeData(String name) { + this.fileName = name; + this.init(); + } + + + private PrecomputeData(String name, PreData precomputeDatas) { + this.fileName = name; + this.data = precomputeDatas; + } + + + public static void removeData(String name) { + if (!PRECOMP_DATA_DIR.exists()) { + return; + } + + File file = new File(PRECOMP_DATA_DIR, name); + if (!file.exists()) { + return; + } + + file.delete(); + } + + + public static void removeData() { + removeData(DEFAULT_FILE_NAME); + } + + + public PrecomputeData copy() { + return new PrecomputeData(this.fileName, this.data.copy()); + } + + + private void init() { + this.data = this.read(this.fileName); + if (this.data == null) { + this.data = new PreData(); + } + } + + + private PreData read(String name) { + try { + if (!PRECOMP_DATA_DIR.exists()) { + if (!PRECOMP_DATA_DIR.mkdir()) { + return null; + } + } + + File readFile = new File(PRECOMP_DATA_DIR, name); + if (!readFile.exists()) { + return null; + } + + FileInputStream fis = new FileInputStream(readFile); + BufferedInputStream bis = new BufferedInputStream(fis); + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + byte[] binary = new byte[1024]; + while (true) { + int len = bis.read(binary); + if (len < 0) { + break; + } + bout.write(binary, 0, len); + } + + binary = bout.toByteArray(); + ObjectMapper om = new ObjectMapper(new MessagePackFactory()); + PreData ds = om.readValue(binary, PreData.class); + bis.close(); + fis.close(); + return ds; + } catch (IOException e) { + return null; + } + } + + + public boolean write() { + try { + if (!PRECOMP_DATA_DIR.exists()) { + if (!PRECOMP_DATA_DIR.mkdir()) { + return false; + } + } + ObjectMapper om = new ObjectMapper(new MessagePackFactory()); + byte[] binary = om.writeValueAsBytes(this.data); + FileOutputStream fos = new FileOutputStream( + new File(PRECOMP_DATA_DIR, this.fileName)); + fos.write(binary); + fos.close(); + return true; + } catch (IOException e) { + e.printStackTrace(); + ; + return false; + } + } + + + public Integer setInteger(String name, int value) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.intValues.put(callClassName + ":" + name, value); + } + + + public Double setDouble(String name, double value) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.doubleValues.put(callClassName + ":" + name, value); + } + + + public Boolean setBoolean(String name, boolean value) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.boolValues.put(callClassName + ":" + name, value); + } + + + public String setString(String name, String value) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.stringValues.put(callClassName + ":" + name, value); + } + + + public EntityID setEntityID(String name, EntityID value) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + Integer id = this.data.idValues.put(callClassName + ":" + name, + value.getValue()); + return id == null ? null : new EntityID(id); + } + + + public List setIntegerList(String name, List list) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.intLists.put(callClassName + ":" + name, list); + } + + + public List setDoubleList(String name, List list) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.doubleLists.put(callClassName + ":" + name, list); + } + + + public List setStringList(String name, List list) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.stringLists.put(callClassName + ":" + name, list); + } + + + public List setEntityIDList(String name, List list) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + List cvtList = new ArrayList<>(); + for (EntityID id : list) { + cvtList.add(id.getValue()); + } + + cvtList = this.data.idLists.put(callClassName + ":" + name, cvtList); + return cvtList == null ? null + : cvtList.stream().map(EntityID::new).collect(Collectors.toList()); + } + + + public List setBooleanList(String name, List list) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.boolLists.put(callClassName + ":" + name, list); + } + + + public boolean setReady(boolean isReady, WorldInfo worldInfo) { + this.data.isReady = isReady; + this.data.readyID = makeReadyID(worldInfo); + return (this.data.isReady + && this.data.readyID.equals(this.makeReadyID(worldInfo))); + } + + + public Integer getInteger(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.intValues.get(callClassName + ":" + name); + } + + + public Double getDouble(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.doubleValues.get(callClassName + ":" + name); + } + + + public Boolean getBoolean(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.boolValues.get(callClassName + ":" + name); + } + + + public String getString(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.stringValues.get(callClassName + ":" + name); + } + + + public EntityID getEntityID(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + Integer id = this.data.idValues.get(callClassName + ":" + name); + return id == null ? null : new EntityID(id); + } + + + public List getIntegerList(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.intLists.get(callClassName + ":" + name); + } + + + public List getDoubleList(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.doubleLists.get(callClassName + ":" + name); + } + + + public List getStringList(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.stringLists.get(callClassName + ":" + name); + } + + + public List getEntityIDList(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + List cvtList = this.data.idLists.get(callClassName + ":" + name); + return cvtList == null ? null + : cvtList.stream().map(EntityID::new).collect(Collectors.toList()); + } + + + public List getBooleanList(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.boolLists.get(callClassName + ":" + name); + } + + + public boolean isReady(WorldInfo worldInfo) { + return (this.data.isReady + && this.data.readyID.equals(this.makeReadyID(worldInfo))); + } + + + private String makeReadyID(WorldInfo worldInfo) { + return "" + worldInfo.getBounds().getX() + worldInfo.getBounds().getY() + + worldInfo.getAllEntities().size(); + } +} From 1785a95e405347506123107bb6004598fe02e111 Mon Sep 17 00:00:00 2001 From: harrki Date: Wed, 19 Mar 2025 10:31:41 +0900 Subject: [PATCH 221/249] feat: Add jitpack --- jitpack.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 jitpack.yml diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 0000000..7b797cc --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,5 @@ +jdk: + - openjdk17 + +before_install: + - cd java \ No newline at end of file From f822458f4e2b6e2d91dc6924f9271f31539c01f3 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 11 Apr 2025 19:11:18 +0900 Subject: [PATCH 222/249] =?UTF-8?q?fix:=20=E3=83=91=E3=82=B9=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cli/template/config/module.yaml | 59 +++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/adf_core_python/cli/template/config/module.yaml b/adf_core_python/cli/template/config/module.yaml index efec4c6..4ee5b0d 100644 --- a/adf_core_python/cli/template/config/module.yaml +++ b/adf_core_python/cli/template/config/module.yaml @@ -3,45 +3,45 @@ DefaultTacticsAmbulanceTeam: Search: src.team_name.module.complex.sample_search.SampleSearch ExtendActionTransport: adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove - CommandExecutorAmbulance: adf_core_python.implement.centralized.DefaultCommandExecutorAmbulance - CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout + CommandExecutorAmbulance: adf_core_python.implement.centralized.default_command_executor_ambulance.DefaultCommandExecutorAmbulance + CommandExecutorScout: adf_core_python.implement.centralized.default_command_executor_scout.DefaultCommandExecutorScout DefaultTacticsFireBrigade: HumanDetector: src.team_name.module.complex.sample_human_detector.SampleHumanDetector Search: src.team_name.module.complex.sample_search.SampleSearch ExtendActionRescue: adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove - CommandExecutorFire: adf_core_python.implement.centralized.DefaultCommandExecutorFire - CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout + CommandExecutorFire: adf_core_python.implement.centralized.default_command_executor_fire.DefaultCommandExecutorFire + CommandExecutorScout: adf_core_python.implement.centralized.default_command_executor_scout.DefaultCommandExecutorScout DefaultTacticsPoliceForce: RoadDetector: src.team_name.module.complex.sample_road_detector.SampleRoadDetector Search: src.team_name.module.complex.sample_search.SampleSearch ExtendActionClear: adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove - CommandExecutorPolice: adf_core_python.implement.centralized.DefaultCommandExecutorPolice - CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScoutPolice + CommandExecutorPolice: adf_core_python.implement.centralized.default_command_executor_police.DefaultCommandExecutorPolice + CommandExecutorScout: adf_core_python.implement.centralized.default_command_executor_scout.DefaultCommandExecutorScout DefaultTacticsAmbulanceCenter: TargetAllocator: adf_core_python.implement.module.complex.default_ambulance_target_allocator.DefaultAmbulanceTargetAllocator - # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerAmbulance + CommandPicker: adf_core_python.implement.centralized.default_command_picker_ambulance.DefaultCommandPickerAmbulance DefaultTacticsFireStation: TargetAllocator: adf_core_python.implement.module.complex.default_fire_target_allocator.DefaultFireTargetAllocator - # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerFire + CommandPicker: adf_core_python.implement.centralized.default_command_picker_fire.DefaultCommandPickerFire DefaultTacticsPoliceOffice: TargetAllocator: adf_core_python.implement.module.complex.default_police_target_allocator.DefaultPoliceTargetAllocator - # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerPolice + CommandPicker: adf_core_python.implement.centralized.default_command_picker_police.DefaultCommandPickerPolice -SampleSearch: +DefaultSearch: PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning Clustering: adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering -SampleRoadDetector: +DefaultRoadDetector: PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -SampleHumanDetector: +DefaultHumanDetector: Clustering: adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering DefaultExtendActionClear: @@ -56,28 +56,27 @@ DefaultExtendActionMove: DefaultExtendActionTransport: PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# DefaultCommandExecutorAmbulance: -# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ExtendActionTransport: adf_core_python.implement.action.DefaultExtendActionTransport -# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove +DefaultCommandExecutorAmbulance: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning + ExtendActionTransport: adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport + ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove -# DefaultCommandExecutorFire: -# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# EtxActionFireRescue: adf_core_python.implement.action.DefaultExtendActionRescue -# EtxActionFireFighting: adf_core_python.implement.action.DefaultExtendActionFireFighting -# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove +DefaultCommandExecutorFire: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning + ExtendActionFireRescue: adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue + ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove -# DefaultCommandExecutorPolice: -# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear -# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove +DefaultCommandExecutorPolice: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning + ExtendActionClear: adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear + ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove -# DefaultCommandExecutorScout: -# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning +DefaultCommandExecutorScout: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# DefaultCommandExecutorScoutPolice: -# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear +DefaultCommandExecutorScoutPolice: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning + ExtendActionClear: adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear MessageManager: PlatoonChannelSubscriber: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber From 13060ae9d8568405ad31ed9fa4cb4d3b72057983 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 11 Apr 2025 19:15:22 +0900 Subject: [PATCH 223/249] =?UTF-8?q?feat:=20=E3=83=87=E3=83=95=E3=82=A9?= =?UTF-8?q?=E3=83=AB=E3=83=88=E8=A8=AD=E5=AE=9A=E3=82=92=E3=82=B5=E3=83=B3?= =?UTF-8?q?=E3=83=97=E3=83=AB=E8=A8=AD=E5=AE=9A=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adf_core_python/cli/template/config/module.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adf_core_python/cli/template/config/module.yaml b/adf_core_python/cli/template/config/module.yaml index 4ee5b0d..0079ab9 100644 --- a/adf_core_python/cli/template/config/module.yaml +++ b/adf_core_python/cli/template/config/module.yaml @@ -34,14 +34,14 @@ DefaultTacticsPoliceOffice: TargetAllocator: adf_core_python.implement.module.complex.default_police_target_allocator.DefaultPoliceTargetAllocator CommandPicker: adf_core_python.implement.centralized.default_command_picker_police.DefaultCommandPickerPolice -DefaultSearch: +SampleSearch: PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning Clustering: adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering -DefaultRoadDetector: +SampleRoadDetector: PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -DefaultHumanDetector: +SampleHumanDetector: Clustering: adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering DefaultExtendActionClear: From d34de0b6bea89533c0c20ebb9cde7b8da4ef31c1 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 11 Apr 2025 19:18:00 +0900 Subject: [PATCH 224/249] =?UTF-8?q?feat:=20=E3=83=90=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=82=920.1.2=E3=81=AB=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3f3522a..9ae40d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "adf_core_python" -version = "0.1.1" +version = "0.1.2" description = "Agent Development Framework for Python" authors = [ "Haruki Uehara ", From edcaf71881b36501d0580922d5064f7093036e19 Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 15 Apr 2025 00:50:40 +0900 Subject: [PATCH 225/249] =?UTF-8?q?feat:=20=E3=82=AB=E3=83=BC=E3=83=8D?= =?UTF-8?q?=E3=83=AB=E6=8E=A5=E7=B6=9A=E3=81=AE=E7=A2=BA=E8=AA=8D=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97=E3=80=81=E3=82=BF?= =?UTF-8?q?=E3=82=A4=E3=83=A0=E3=82=A2=E3=82=A6=E3=83=88=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=82=92=E5=B0=8E=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/launcher/agent_launcher.py | 32 +++++++++++-- adf_core_python/core/launcher/config_key.py | 1 + .../launcher/connect/component_launcher.py | 46 +++++++++++++++++++ adf_core_python/launcher.py | 15 ++++-- 4 files changed, 87 insertions(+), 7 deletions(-) diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index 63f31bc..94fe7a8 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -1,4 +1,5 @@ import importlib +import socket import threading from typing import Optional @@ -59,13 +60,27 @@ def init_connector(self) -> None: def launch(self) -> None: kernel_host: str = self.config.get_value(ConfigKey.KEY_KERNEL_HOST, "localhost") kernel_port: int = self.config.get_value(ConfigKey.KEY_KERNEL_PORT, 27931) - self.logger.info( - f"Start agent launcher (host: {kernel_host}, port: {kernel_port})" - ) component_launcher: ComponentLauncher = ComponentLauncher( kernel_host, kernel_port, self.logger ) + timeout: int = self.config.get_value( + ConfigKey.KEY_KERNEL_TIMEOUT, + 30, + ) + if component_launcher.check_kernel_connection(timeout=timeout): + self.logger.info( + f"Kernel is running (host: {kernel_host}, port: {kernel_port})" + ) + else: + self.logger.error( + f"Kernel is not running (host: {kernel_host}, port: {kernel_port})" + ) + return + + self.logger.info( + f"Start agent launcher (host: {kernel_host}, port: {kernel_port})" + ) gateway_launcher: Optional[GatewayLauncher] = None gateway_flag: bool = self.config.get_value(ConfigKey.KEY_GATEWAY_FLAG, False) @@ -104,3 +119,14 @@ def connect() -> None: for thread in self.agent_thread_list: thread.join() + + def check_kernel_connection(self, host: str, port: int, timeout: int = 5) -> bool: + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + result = sock.connect_ex((host, port)) + sock.close() + return result == 0 + except Exception as e: + self.logger.error(f"カーネルへの接続確認中にエラーが発生しました: {e}") + return False diff --git a/adf_core_python/core/launcher/config_key.py b/adf_core_python/core/launcher/config_key.py index 8253f47..2f7f9a0 100644 --- a/adf_core_python/core/launcher/config_key.py +++ b/adf_core_python/core/launcher/config_key.py @@ -6,6 +6,7 @@ class ConfigKey: KEY_LOADER_CLASS: Final[str] = "adf_core_python.launcher.loader" KEY_KERNEL_HOST: Final[str] = "kernel.host" KEY_KERNEL_PORT: Final[str] = "kernel.port" + KEY_KERNEL_TIMEOUT: Final[str] = "kernel.timeout" KEY_TEAM_NAME: Final[str] = "team.name" KEY_DEBUG_FLAG: Final[str] = "adf.debug.flag" KEY_DEVELOP_FLAG: Final[str] = "adf.develop.flag" diff --git a/adf_core_python/core/launcher/connect/component_launcher.py b/adf_core_python/core/launcher/connect/component_launcher.py index 7b60008..9a684fa 100644 --- a/adf_core_python/core/launcher/connect/component_launcher.py +++ b/adf_core_python/core/launcher/connect/component_launcher.py @@ -1,4 +1,5 @@ import socket +import time from structlog import BoundLogger @@ -53,3 +54,48 @@ def connect(self, agent: Agent, _request_id: int) -> None: def generate_request_id(self) -> int: self.request_id += 1 return self.request_id + + def check_kernel_connection( + self, timeout: int = 30, retry_interval: float = 5.0 + ) -> bool: + """Attempts to connect to the kernel multiple times within the specified timeout period. + + Args: + timeout (int): Total timeout duration in seconds + retry_interval (float): Interval between retry attempts in seconds + + Returns: + bool: True if connection successful, False otherwise + """ + start_time = time.time() + attempt = 1 + + while True: + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(retry_interval) + result = sock.connect_ex((self.host, self.port)) + sock.close() + + if result == 0: + self.logger.info( + f"Successfully connected to kernel (attempt: {attempt})" + ) + return True + + elapsed_time = time.time() - start_time + if elapsed_time >= timeout: + self.logger.error( + f"Timeout: Could not connect to kernel within {timeout} seconds (attempts: {attempt})" + ) + return False + + self.logger.debug( + f"Connection attempt {attempt} failed - retrying in {retry_interval} seconds" + ) + time.sleep(retry_interval) + attempt += 1 + + except Exception as e: + self.logger.error(f"Error while checking kernel connection: {e}") + return False diff --git a/adf_core_python/launcher.py b/adf_core_python/launcher.py index 94d0c6e..5d04a89 100644 --- a/adf_core_python/launcher.py +++ b/adf_core_python/launcher.py @@ -12,11 +12,8 @@ def __init__( self, launcher_config_file: str, ) -> None: - resource.setrlimit(resource.RLIMIT_NOFILE, (8192, 9223372036854775807)) + resource.setrlimit(resource.RLIMIT_NOFILE, (8192, 1048576)) - configure_logger() - - self.logger = get_logger(__name__) self.launcher_config = Config(launcher_config_file) parser = argparse.ArgumentParser(description="Agent Launcher") @@ -80,6 +77,12 @@ def __init__( action="store_true", help="precompute flag", ) + parser.add_argument( + "--timeout", + type=int, + help="timeout in seconds", + metavar="", + ) parser.add_argument("--debug", action="store_true", help="debug flag") parser.add_argument( "--java", @@ -98,6 +101,7 @@ def __init__( ConfigKey.KEY_FIRE_STATION_COUNT: args.firestation, ConfigKey.KEY_POLICE_OFFICE_COUNT: args.policeoffice, ConfigKey.KEY_PRECOMPUTE: args.precompute, + ConfigKey.KEY_KERNEL_TIMEOUT: args.timeout, ConfigKey.KEY_DEBUG_FLAG: args.debug, ConfigKey.KEY_GATEWAY_FLAG: args.java, } @@ -106,6 +110,9 @@ def __init__( if value is not None: self.launcher_config.set_value(key, value) + configure_logger() + self.logger = get_logger(__name__) + self.logger.debug(f"launcher_config: {self.launcher_config}") def launch(self) -> None: From 02afafe1706dd9254cbca499582eac44a724507f Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 22 Apr 2025 15:31:22 +0900 Subject: [PATCH 226/249] =?UTF-8?q?feat:=20=E3=83=AD=E3=82=B0=E3=83=95?= =?UTF-8?q?=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=AE=E3=83=AD=E3=83=BC=E3=83=86?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=82=92=E5=89=8A=E9=99=A4=E3=81=97=E3=80=81?= =?UTF-8?q?=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E9=80=81=E4=BF=A1?= =?UTF-8?q?=E3=81=AE=E3=83=87=E3=83=90=E3=83=83=E3=82=B0=E3=83=AD=E3=82=B0?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adf_core_python/core/agent/agent.py | 7 +------ adf_core_python/core/logger/logger.py | 16 +++++++++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index 6c8275b..b18fb67 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -24,10 +24,10 @@ from rcrs_core.connection.URN import Entity as EntityURN from rcrs_core.messages.AKAcknowledge import AKAcknowledge from rcrs_core.messages.AKConnect import AKConnect +from rcrs_core.messages.controlMessageFactory import ControlMessageFactory from rcrs_core.messages.KAConnectError import KAConnectError from rcrs_core.messages.KAConnectOK import KAConnectOK from rcrs_core.messages.KASense import KASense -from rcrs_core.messages.controlMessageFactory import ControlMessageFactory from rcrs_core.worldmodel.changeSet import ChangeSet from rcrs_core.worldmodel.entityID import EntityID from rcrs_core.worldmodel.worldmodel import WorldModel @@ -214,11 +214,6 @@ def update_step_info( self.think() - self.logger.debug( - f"send messages: {self._message_manager.get_send_message_list()}", - message_manager=self._message_manager, - ) - self._message_manager.coordinate_message( self._agent_info, self._world_info, self._scenario_info ) diff --git a/adf_core_python/core/logger/logger.py b/adf_core_python/core/logger/logger.py index 766e283..16797e8 100644 --- a/adf_core_python/core/logger/logger.py +++ b/adf_core_python/core/logger/logger.py @@ -1,6 +1,7 @@ import logging +import os import sys -from logging.handlers import RotatingFileHandler +from datetime import datetime import structlog from structlog.dev import ConsoleRenderer @@ -51,6 +52,13 @@ def get_agent_logger(name: str, agent_info: AgentInfo) -> structlog.BoundLogger: def configure_logger() -> None: + # 既存のログファイルが存在する場合、日付付きでバックアップする + log_file = "agent.log" + if os.path.exists(log_file): + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_file = f"agent_{timestamp}.log" + os.rename(log_file, backup_file) + structlog.configure( processors=[ structlog.stdlib.add_log_level, @@ -65,16 +73,14 @@ def configure_logger() -> None: wrapper_class=structlog.stdlib.BoundLogger, cache_logger_on_first_use=True, ) + handler_stdout = logging.StreamHandler(sys.stdout) handler_stdout.setFormatter( structlog.stdlib.ProcessorFormatter(processor=ConsoleRenderer()) ) handler_stdout.setLevel(logging.INFO) - handler_file = RotatingFileHandler( - "agent.log", maxBytes=1024 * 1024 * 1024, backupCount=5 - ) - handler_file.doRollover() + handler_file = logging.FileHandler(log_file) handler_file.setFormatter( structlog.stdlib.ProcessorFormatter(processor=JSONRenderer()) ) From fb480f66de4d1041acd21dbf0b3e7006eeaa16d9 Mon Sep 17 00:00:00 2001 From: shima004 Date: Tue, 22 Apr 2025 15:38:54 +0900 Subject: [PATCH 227/249] =?UTF-8?q?feat:=20=E3=83=90=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=82=920.1.3=E3=81=AB=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9ae40d5..c62e989 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "adf_core_python" -version = "0.1.2" +version = "0.1.3" description = "Agent Development Framework for Python" authors = [ "Haruki Uehara ", From 095fa6ebaf807c89d0da0caadae6f8f8137adb92 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 1 May 2025 14:30:52 +0900 Subject: [PATCH 228/249] =?UTF-8?q?feat:=20=E3=83=AA=E3=82=BD=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E5=88=B6=E9=99=90=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=81=AB?= =?UTF-8?q?=E5=A4=B1=E6=95=97=E3=81=97=E3=81=9F=E5=A0=B4=E5=90=88=E3=81=AE?= =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97=E3=80=81=E3=83=90?= =?UTF-8?q?=E3=83=BC=E3=82=B8=E3=83=A7=E3=83=B3=E3=82=920.1.4=E3=81=AB?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adf_core_python/launcher.py | 8 +++++++- pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/adf_core_python/launcher.py b/adf_core_python/launcher.py index 5d04a89..0982f70 100644 --- a/adf_core_python/launcher.py +++ b/adf_core_python/launcher.py @@ -12,7 +12,13 @@ def __init__( self, launcher_config_file: str, ) -> None: - resource.setrlimit(resource.RLIMIT_NOFILE, (8192, 1048576)) + try: + resource.setrlimit(resource.RLIMIT_NOFILE, (8192, 1048576)) + except Exception as e: + print( + f"Failed to set resource limit: {e}. " + "This may cause issues with the number of open files." + ) self.launcher_config = Config(launcher_config_file) diff --git a/pyproject.toml b/pyproject.toml index c62e989..ea94664 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "adf_core_python" -version = "0.1.3" +version = "0.1.4" description = "Agent Development Framework for Python" authors = [ "Haruki Uehara ", From 2f8d61a946fd60af3b6f3c492145fa7c0f479581 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 21:19:13 +0000 Subject: [PATCH 229/249] chore(deps-dev): bump setuptools from 75.6.0 to 78.1.1 Bumps [setuptools](https://github.com/pypa/setuptools) from 75.6.0 to 78.1.1. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v75.6.0...v78.1.1) --- updated-dependencies: - dependency-name: setuptools dependency-version: 78.1.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8f98a4f..610bd29 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1149,24 +1149,24 @@ test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.3 [[package]] name = "setuptools" -version = "75.6.0" +version = "78.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"}, - {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"}, + {file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"}, + {file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.7.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (>=1.12,<1.14)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "shapely" From 83c4d9b554d41f39b8b8e35f6720961fdcb9d24a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jun 2025 10:14:11 +0000 Subject: [PATCH 230/249] chore(deps-dev): bump requests from 2.32.3 to 2.32.4 Bumps [requests](https://github.com/psf/requests) from 2.32.3 to 2.32.4. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.3...v2.32.4) --- updated-dependencies: - dependency-name: requests dependency-version: 2.32.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8f98a4f..0ea77c2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -978,19 +978,19 @@ resolved_reference = "42a20e312de20ea46f1e0622a82f41e81fc3514f" [[package]] name = "requests" -version = "2.32.3" +version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, + {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, + {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, ] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" +charset_normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" From 5661994c17f9f8c67493f4e347ac9539f2aa5888 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 10:52:47 +0900 Subject: [PATCH 231/249] Specify version tag for rcrs_core dependency in pyproject.toml --- poetry.lock | 1276 +++++++++++++++++++++++++----------------------- pyproject.toml | 2 +- 2 files changed, 669 insertions(+), 609 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6326ce2..0dc5c07 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "accessible-pygments" @@ -33,33 +33,34 @@ files = [ [[package]] name = "babel" -version = "2.16.0" +version = "2.17.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, - {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, + {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, + {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, ] [package.extras] -dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] [[package]] name = "beautifulsoup4" -version = "4.12.3" +version = "4.13.4" description = "Screen-scraping library" optional = false -python-versions = ">=3.6.0" +python-versions = ">=3.7.0" groups = ["dev"] files = [ - {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, - {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, + {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, + {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, ] [package.dependencies] soupsieve = ">1.2" +typing-extensions = ">=4.0.0" [package.extras] cchardet = ["cchardet"] @@ -70,288 +71,272 @@ lxml = ["lxml"] [[package]] name = "bitarray" -version = "3.0.0" +version = "3.6.0" description = "efficient arrays of booleans -- C extension" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "bitarray-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5ddbf71a97ad1d6252e6e93d2d703b624d0a5b77c153b12f9ea87d83e1250e0c"}, - {file = "bitarray-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0e7f24a0b01e6e6a0191c50b06ca8edfdec1988d9d2b264d669d2487f4f4680"}, - {file = "bitarray-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:150b7b29c36d9f1a24779aea723fdfc73d1c1c161dc0ea14990da27d4e947092"}, - {file = "bitarray-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8330912be6cb8e2fbfe8eb69f82dee139d605730cadf8d50882103af9ac83bb4"}, - {file = "bitarray-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e56ba8be5f17dee0ffa6d6ce85251e062ded2faa3cbd2558659c671e6c3bf96d"}, - {file = "bitarray-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffd94b4803811c738e504a4b499fb2f848b2f7412d71e6b517508217c1d7929d"}, - {file = "bitarray-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0255bd05ec7165e512c115423a5255a3f301417973d20a80fc5bfc3f3640bcb"}, - {file = "bitarray-3.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe606e728842389943a939258809dc5db2de831b1d2e0118515059e87f7bbc1a"}, - {file = "bitarray-3.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e89ea59a3ed86a6eb150d016ed28b1bedf892802d0ed32b5659d3199440f3ced"}, - {file = "bitarray-3.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cf0cc2e91dd38122dec2e6541efa99aafb0a62e118179218181eff720b4b8153"}, - {file = "bitarray-3.0.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:2d9fe3ee51afeb909b68f97e14c6539ace3f4faa99b21012e610bbe7315c388d"}, - {file = "bitarray-3.0.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:37be5482b9df3105bad00fdf7dc65244e449b130867c3879c9db1db7d72e508b"}, - {file = "bitarray-3.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0027b8f3bb2bba914c79115e96a59b9924aafa1a578223a7c4f0a7242d349842"}, - {file = "bitarray-3.0.0-cp310-cp310-win32.whl", hash = "sha256:628f93e9c2c23930bd1cfe21c634d6c84ec30f45f23e69aefe1fcd262186d7bb"}, - {file = "bitarray-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:0b655c3110e315219e266b2732609fddb0857bc69593de29f3c2ba74b7d3f51a"}, - {file = "bitarray-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:44c3e78b60070389b824d5a654afa1c893df723153c81904088d4922c3cfb6ac"}, - {file = "bitarray-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:545d36332de81e4742a845a80df89530ff193213a50b4cbef937ed5a44c0e5e5"}, - {file = "bitarray-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a9eb510cde3fa78c2e302bece510bf5ed494ec40e6b082dec753d6e22d5d1b1"}, - {file = "bitarray-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e3727ab63dfb6bde00b281934e2212bb7529ea3006c0031a556a84d2268bea5"}, - {file = "bitarray-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2055206ed653bee0b56628f6a4d248d53e5660228d355bbec0014bdfa27050ae"}, - {file = "bitarray-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:147542299f458bdb177f798726e5f7d39ab8491de4182c3c6d9885ed275a3c2b"}, - {file = "bitarray-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f761184b93092077c7f6b7dad7bd4e671c1620404a76620da7872ceb576a94"}, - {file = "bitarray-3.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e008b7b4ce6c7f7a54b250c45c28d4243cc2a3bbfd5298fa7dac92afda229842"}, - {file = "bitarray-3.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dfea514e665af278b2e1d4deb542de1cd4f77413bee83dd15ae16175976ea8d5"}, - {file = "bitarray-3.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:66d6134b7bb737b88f1d16478ad0927c571387f6054f4afa5557825a4c1b78e2"}, - {file = "bitarray-3.0.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3cd565253889940b4ec4768d24f101d9fe111cad4606fdb203ea16f9797cf9ed"}, - {file = "bitarray-3.0.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4800c91a14656789d2e67d9513359e23e8a534c8ee1482bb9b517a4cfc845200"}, - {file = "bitarray-3.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c2945e0390d1329c585c584c6b6d78be017d9c6a1288f9c92006fe907f69cc28"}, - {file = "bitarray-3.0.0-cp311-cp311-win32.whl", hash = "sha256:c23286abba0cb509733c6ce8f4013cd951672c332b2e184dbefbd7331cd234c8"}, - {file = "bitarray-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:ca79f02a98cbda1472449d440592a2fe2ad96fe55515a0447fa8864a38017cf8"}, - {file = "bitarray-3.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:184972c96e1c7e691be60c3792ca1a51dd22b7f25d96ebea502fe3c9b554f25d"}, - {file = "bitarray-3.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:787db8da5e9e29be712f7a6bce153c7bc8697ccc2c38633e347bb9c82475d5c9"}, - {file = "bitarray-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2da91ab3633c66999c2a352f0ca9ae064f553e5fc0eca231d28e7e305b83e942"}, - {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7edb83089acbf2c86c8002b96599071931dc4ea5e1513e08306f6f7df879a48b"}, - {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996d1b83eb904589f40974538223eaed1ab0f62be8a5105c280b9bd849e685c4"}, - {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4817d73d995bd2b977d9cde6050be8d407791cf1f84c8047fa0bea88c1b815bc"}, - {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d47bc4ff9b0e1624d613563c6fa7b80aebe7863c56c3df5ab238bb7134e8755"}, - {file = "bitarray-3.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aca0a9cd376beaccd9f504961de83e776dd209c2de5a4c78dc87a78edf61839b"}, - {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:572a61fba7e3a710a8324771322fba8488d134034d349dcd036a7aef74723a80"}, - {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a817ad70c1aff217530576b4f037dd9b539eb2926603354fcac605d824082ad1"}, - {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2ac67b658fa5426503e9581a3fb44a26a3b346c1abd17105735f07db572195b3"}, - {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:12f19ede03e685c5c588ab5ed63167999295ffab5e1126c5fe97d12c0718c18f"}, - {file = "bitarray-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcef31b062f756ba7eebcd7890c5d5de84b9d64ee877325257bcc9782288564a"}, - {file = "bitarray-3.0.0-cp312-cp312-win32.whl", hash = "sha256:656db7bdf1d81ec3b57b3cad7ec7276765964bcfd0eb81c5d1331f385298169c"}, - {file = "bitarray-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f785af6b7cb07a9b1e5db0dea9ef9e3e8bb3d74874a0a61303eab9c16acc1999"}, - {file = "bitarray-3.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7cb885c043000924554fe2124d13084c8fdae03aec52c4086915cd4cb87fe8be"}, - {file = "bitarray-3.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7814c9924a0b30ecd401f02f082d8697fc5a5be3f8d407efa6e34531ff3c306a"}, - {file = "bitarray-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bcf524a087b143ba736aebbb054bb399d49e77cf7c04ed24c728e411adc82bfa"}, - {file = "bitarray-3.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1d5abf1d6d910599ac16afdd9a0ed3e24f3b46af57f3070cf2792f236f36e0b"}, - {file = "bitarray-3.0.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9929051feeaf8d948cc0b1c9ce57748079a941a1a15c89f6014edf18adaade84"}, - {file = "bitarray-3.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96cf0898f8060b2d3ae491762ae871b071212ded97ff9e1e3a5229e9fefe544c"}, - {file = "bitarray-3.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab37da66a8736ad5a75a58034180e92c41e864da0152b84e71fcc253a2f69cd4"}, - {file = "bitarray-3.0.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beeb79e476d19b91fd6a3439853e4e5ba1b3b475920fa40d62bde719c8af786f"}, - {file = "bitarray-3.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f75fc0198c955d840b836059bd43e0993edbf119923029ca60c4fc017cefa54a"}, - {file = "bitarray-3.0.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f12cc7c7638074918cdcc7491aff897df921b092ffd877227892d2686e98f876"}, - {file = "bitarray-3.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dbe1084935b942fab206e609fa1ed3f46ad1f2612fb4833e177e9b2a5e006c96"}, - {file = "bitarray-3.0.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ac06dd72ee1e1b6e312504d06f75220b5894af1fb58f0c20643698f5122aea76"}, - {file = "bitarray-3.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00f9a88c56e373009ac3c73c55205cfbd9683fbd247e2f9a64bae3da78795252"}, - {file = "bitarray-3.0.0-cp313-cp313-win32.whl", hash = "sha256:9c6e52005e91803eb4e08c0a08a481fb55ddce97f926bae1f6fa61b3396b5b61"}, - {file = "bitarray-3.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:cb98d5b6eac4b2cf2a5a69f60a9c499844b8bea207059e9fc45c752436e6bb49"}, - {file = "bitarray-3.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:eb27c01b747649afd7e1c342961680893df6d8d81f832a6f04d8c8e03a8a54cc"}, - {file = "bitarray-3.0.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4683bff52f5a0fd523fb5d3138161ef87611e63968e1fcb6cf4b0c6a86970fe0"}, - {file = "bitarray-3.0.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb7302dbcfcb676f0b66f15891f091d0233c4fc23e1d4b9dc9b9e958156e347f"}, - {file = "bitarray-3.0.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:153d7c416a70951dcfa73487af05d2f49c632e95602f1620cd9a651fa2033695"}, - {file = "bitarray-3.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251cd5bd47f542893b2b61860eded54f34920ea47fd5bff038d85e7a2f7ae99b"}, - {file = "bitarray-3.0.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fa4b4d9fa90124b33b251ef74e44e737021f253dc7a9174e1b39f097451f7ca"}, - {file = "bitarray-3.0.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:18abdce7ab5d2104437c39670821cba0b32fdb9b2da9e6d17a4ff295362bd9dc"}, - {file = "bitarray-3.0.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:2855cc01ee370f7e6e3ec97eebe44b1453c83fb35080313145e2c8c3c5243afb"}, - {file = "bitarray-3.0.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:0cecaf2981c9cd2054547f651537b4f4939f9fe225d3fc2b77324b597c124e40"}, - {file = "bitarray-3.0.0-cp36-cp36m-musllinux_1_2_s390x.whl", hash = "sha256:22b00f65193fafb13aa644e16012c8b49e7d5cbb6bb72825105ff89aadaa01e3"}, - {file = "bitarray-3.0.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:20f30373f0af9cb583e4122348cefde93c82865dbcbccc4997108b3d575ece84"}, - {file = "bitarray-3.0.0-cp36-cp36m-win32.whl", hash = "sha256:aef404d5400d95c6ec86664df9924bde667c8865f8e33c9b7bd79823d53b3e5d"}, - {file = "bitarray-3.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:ec5b0f2d13da53e0975ac15ecbe8badb463bdb0bebaa09457f4df3320421915c"}, - {file = "bitarray-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:041c889e69c847b8a96346650e50f728b747ae176889199c49a3f31ae1de0e23"}, - {file = "bitarray-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc83ea003dd75e9ade3291ef0585577dd5524aec0c8c99305c0aaa2a7570d6db"}, - {file = "bitarray-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c33129b49196aa7965ac0f16fcde7b6ad8614b606caf01669a0277cef1afe1d"}, - {file = "bitarray-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ef5c787c8263c082a73219a69eb60a500e157a4ac69d1b8515ad836b0e71fb4"}, - {file = "bitarray-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e15c94d79810c5ab90ddf4d943f71f14332890417be896ca253f21fa3d78d2b1"}, - {file = "bitarray-3.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cd021ada988e73d649289cee00428b75564c46d55fbdcb0e3402e504b0ae5ea"}, - {file = "bitarray-3.0.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7f1c24be7519f16a47b7e2ad1a1ef73023d34d8cbe1a3a59b185fc14baabb132"}, - {file = "bitarray-3.0.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:000df24c183011b5d27c23d79970f49b6762e5bb5aacd25da9c3e9695c693222"}, - {file = "bitarray-3.0.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:42bf1b222c698b467097f58b9f59dc850dfa694dde4e08237407a6a103757aa3"}, - {file = "bitarray-3.0.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:648e7ce794928e8d11343b5da8ecc5b910af75a82ea1a4264d5d0a55c3785faa"}, - {file = "bitarray-3.0.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:f536fc4d1a683025f9caef0bebeafd60384054579ffe0825bb9bd8c59f8c55b8"}, - {file = "bitarray-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:a754c1464e7b946b1cac7300c582c6fba7d66e535cd1dab76d998ad285ac5a37"}, - {file = "bitarray-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e91d46d12781a14ccb8b284566b14933de4e3b29f8bc5e1c17de7a2001ad3b5b"}, - {file = "bitarray-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:904c1d5e3bd24f0c0d37a582d2461312033c91436a6a4f3bdeeceb4bea4a899d"}, - {file = "bitarray-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:47ccf9887bd595d4a0536f2310f0dcf89e17ab83b8befa7dc8727b8017120fda"}, - {file = "bitarray-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:71ad0139c95c9acf4fb62e203b428f9906157b15eecf3f30dc10b55919225896"}, - {file = "bitarray-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e002ac1073ac70e323a7a4bfa9ab95e7e1a85c79160799e265563f342b1557"}, - {file = "bitarray-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acc07211a59e2f245e9a06f28fa374d094fb0e71cf5366eef52abbb826ddc81e"}, - {file = "bitarray-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98a4070ddafabddaee70b2aa7cc6286cf73c37984169ab03af1782da2351059a"}, - {file = "bitarray-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7d09ef06ba57bea646144c29764bf6b870fb3c5558ca098191e07b6a1d40bf7"}, - {file = "bitarray-3.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce249ed981f428a8b61538ca82d3875847733d579dd40084ab8246549160f8a4"}, - {file = "bitarray-3.0.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ea40e98d751ed4b255db4a88fe8fb743374183f78470b9e9305aab186bf28ede"}, - {file = "bitarray-3.0.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:928b8b6dfcd015e1a81334cfdac02815da2a2407854492a80cf8a3a922b04052"}, - {file = "bitarray-3.0.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:fbb645477595ce2a0fbb678d1cfd08d3b896e5d56196d40fb9e114eeab9382b3"}, - {file = "bitarray-3.0.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:dc1937a0ff2671797d35243db4b596329842480d125a65e9fe964bcffaf16dfc"}, - {file = "bitarray-3.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a4f49ac31734fe654a68e2515c0da7f5bbdf2d52755ba09a42ac406f1f08c9d0"}, - {file = "bitarray-3.0.0-cp38-cp38-win32.whl", hash = "sha256:6d2a2ce73f9897268f58857ad6893a1a6680c5a6b28f79d21c7d33285a5ae646"}, - {file = "bitarray-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:b1047999f1797c3ea7b7c85261649249c243308dcf3632840d076d18fa72f142"}, - {file = "bitarray-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:39b38a3d45dac39d528c87b700b81dfd5e8dc8e9e1a102503336310ef837c3fd"}, - {file = "bitarray-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0e104f9399144fab6a892d379ba1bb4275e56272eb465059beef52a77b4e5ce6"}, - {file = "bitarray-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0879f839ec8f079fa60c3255966c2e1aa7196699a234d4e5b7898fbc321901b5"}, - {file = "bitarray-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9502c2230d59a4ace2fddfd770dad8e8b414cbd99517e7e56c55c20997c28b8d"}, - {file = "bitarray-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57d5ef854f8ec434f2ffd9ddcefc25a10848393fe2976e2be2c8c773cf5fef42"}, - {file = "bitarray-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3c36b2fcfebe15ad1c10a90c1d52a42bebe960adcbce340fef867203028fbe7"}, - {file = "bitarray-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66a33a537e781eac3a352397ce6b07eedf3a8380ef4a804f8844f3f45e335544"}, - {file = "bitarray-3.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa54c7e1da8cf4be0aab941ea284ec64033ede5d6de3fd47d75e77cafe986e9d"}, - {file = "bitarray-3.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a667ea05ba1ea81b722682276dbef1d36990f8908cf51e570099fd505a89f931"}, - {file = "bitarray-3.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d756bfeb62ca4fe65d2af7a39249d442c05070c047d03729ad6cd4c2e9b0f0bd"}, - {file = "bitarray-3.0.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c9e9fef0754867d88e948ce8351c9fd7e507d8514e0f242fd67c907b9cdf98b3"}, - {file = "bitarray-3.0.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:67a0b56dd02f2713f6f52cacb3f251afd67c94c5f0748026d307d87a81a8e15c"}, - {file = "bitarray-3.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d8c36ddc1923bcc4c11b9994c54eaae25034812a42400b7b8a86fe6d242166a2"}, - {file = "bitarray-3.0.0-cp39-cp39-win32.whl", hash = "sha256:1414a7102a3c4986f241480544f5c99f5d32258fb9b85c9c04e84e48c490ab35"}, - {file = "bitarray-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:8c9733d2ff9b7838ac04bf1048baea153174753e6a47312be14c83c6a395424b"}, - {file = "bitarray-3.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fef4e3b3f2084b4dae3e5316b44cda72587dcc81f68b4eb2dbda1b8d15261b61"}, - {file = "bitarray-3.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e9eee03f187cef1e54a4545124109ee0afc84398628b4b32ebb4852b4a66393"}, - {file = "bitarray-3.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cb5702dd667f4bb10fed056ffdc4ddaae8193a52cd74cb2cdb54e71f4ef2dd1"}, - {file = "bitarray-3.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:666e44b0458bb2894b64264a29f2cc7b5b2cbcc4c5e9cedfe1fdbde37a8e329a"}, - {file = "bitarray-3.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c756a92cf1c1abf01e56a4cc40cb89f0ff9147f2a0be5b557ec436a23ff464d8"}, - {file = "bitarray-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7e51e7f8289bf6bb631e1ef2a8f5e9ca287985ff518fe666abbdfdb6a848cb26"}, - {file = "bitarray-3.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fa5d8e4b28388b337face6ce4029be73585651a44866901513df44be9a491ab"}, - {file = "bitarray-3.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3963b80a68aedcd722a9978d261ae53cb9bb6a8129cc29790f0f10ce5aca287a"}, - {file = "bitarray-3.0.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b555006a7dea53f6bebc616a4d0249cecbf8f1fadf77860120a2e5dbdc2f167"}, - {file = "bitarray-3.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4ac2027ca650a7302864ed2528220d6cc6921501b383e9917afc7a2424a1e36d"}, - {file = "bitarray-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bf90aba4cff9e72e24ecdefe33bad608f147a23fa5c97790a5bab0e72fe62b6d"}, - {file = "bitarray-3.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a199e6d7c3bad5ba9d0e4dc00dde70ee7d111c9dfc521247fa646ef59fa57e"}, - {file = "bitarray-3.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43b6c7c4f4a7b80e86e24a76f4c6b9b67d03229ea16d7d403520616535c32196"}, - {file = "bitarray-3.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fc13da3518f14825b239374734fce93c1a9299ed7b558c3ec1d659ec7e4c70"}, - {file = "bitarray-3.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:369b6d457af94af901d632c7e625ca6caf0a7484110fc91c6290ce26bc4f1478"}, - {file = "bitarray-3.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ee040ad3b7dfa05e459713099f16373c1f2a6f68b43cb0575a66718e7a5daef4"}, - {file = "bitarray-3.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dad7ba2af80f9ec1dd988c3aca7992408ec0d0b4c215b65d353d95ab0070b10"}, - {file = "bitarray-3.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4839d3b64af51e4b8bb4a602563b98b9faeb34fd6c00ed23d7834e40a9d080fc"}, - {file = "bitarray-3.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f71f24b58e75a889b9915e3197865302467f13e7390efdea5b6afc7424b3a2ea"}, - {file = "bitarray-3.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:bcf0150ae0bcc4aa97bdfcb231b37bad1a59083c1b5012643b266012bf420e68"}, - {file = "bitarray-3.0.0.tar.gz", hash = "sha256:a2083dc20f0d828a7cdf7a16b20dae56aab0f43dc4f347a3b3039f6577992b03"}, + {file = "bitarray-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6841c08b51417f8ffe398b2828fc0593440c99525c868f640e0302476745320b"}, + {file = "bitarray-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a04b7a9017b8d0341ebbe77f61b74df1cf1b714f42b671a06f4912dc93d82597"}, + {file = "bitarray-3.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:664d462a4c0783fd755fe3440f07b7e46d149859c96caacadf3f28890f19a8de"}, + {file = "bitarray-3.6.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e997d22e0d1e08c8752f61675a75d93659f7aa4dbeaee54207f8d877817b4a0c"}, + {file = "bitarray-3.6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6755cfcfa7d8966e704d580c831e39818f85e7b2b7852ad22708973176f0009e"}, + {file = "bitarray-3.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4798f6744fa2633666e17b4ea8ff70250781b52a25afdbf5ffb5e176c58848f1"}, + {file = "bitarray-3.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:efa5834ba5e6c70b22afdca3894097e5a592d8d483c976359654ba990477799a"}, + {file = "bitarray-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d47e2bdeba4fb1986af2ba395ce51223f4d460e6e77119439e78f2b592cafade"}, + {file = "bitarray-3.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2a324e3007afb5c667026f5235b35efe3c4a95f1b83cd93aa9fce67b42f08e7c"}, + {file = "bitarray-3.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:080a7bf55c432abdae74f25dc3dbff407418346aeae1d43e31f65e8ef114f785"}, + {file = "bitarray-3.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3eb1390a8b062fe9125e5cc4c5eba990b5d383eec54f2b996e7ce73ac43150f9"}, + {file = "bitarray-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2020102a40edd094c0aa80e09203af71c533c41f76ce3237c99fd194a473ea33"}, + {file = "bitarray-3.6.0-cp310-cp310-win32.whl", hash = "sha256:01d6dc548e7fe5c66913c2274f44855b0f8474935acff7811e84fe1f4024c94f"}, + {file = "bitarray-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:8d759cecfa8aab4a1eb4e23b6420126b15c7743e85b33f389916bb98c4ecbb84"}, + {file = "bitarray-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7c20d6e6cafce5027e7092beb2ac6eec0d71045d6318b34f36e1387a8c8859a3"}, + {file = "bitarray-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cf36cadeb9c989f760a13058dbc455e5406ec3d2d247c705c8d4bc6dd1b0fcc6"}, + {file = "bitarray-3.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ba4fba3de1dca653de41c879349ec6ca521d85cff6a7ca5d2fdd8f76c93781"}, + {file = "bitarray-3.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77d2368a06a86a18919c05a9b4b0ee9869f770e6a5f414b0fecc911870fe3974"}, + {file = "bitarray-3.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a39be79a7c36e9a2e20376261c30deb3cdca86b50f7462ae9ff10a755c6720b9"}, + {file = "bitarray-3.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4695fcd37478988b1d0a16d5bc0df56dcb677fd5db37f1893d993fd3ebef914b"}, + {file = "bitarray-3.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52328192d454ca2ddad09fbc088872b014c74b22ecdd5164717dc7e6442014fa"}, + {file = "bitarray-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96117212229905da864794df9ea7bd54987c30a5dcbab3432edc3f344231adae"}, + {file = "bitarray-3.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:68f6e64d4867ee79e25c49d7f35b2b1f04a6d6f778176dcf5b759f3b17a02b2b"}, + {file = "bitarray-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:29ed022189a7997de46cb9bd4e2e49d6163d4f8d78dea72ac5a0e0293b856810"}, + {file = "bitarray-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e71c9dba78671d38a549e3b2d52514f50e199f9d7e18ed9b0180adeef0d04130"}, + {file = "bitarray-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ddb319f869d497ef2d3d56319360b61284a9a1d8b3de3bc936748698acfda6be"}, + {file = "bitarray-3.6.0-cp311-cp311-win32.whl", hash = "sha256:25060e7162e44242a449ed1a14a4e94b5aef340812754c443459f19c7954be91"}, + {file = "bitarray-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2d951002b11962b26afb31f758c18ad39771f287b100fa5adb1d09a47eaaf5b"}, + {file = "bitarray-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9616ea14917d06736339cf36bb9eaf4eb52110a74136b0dc5eff94e92417d22"}, + {file = "bitarray-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7e2e1ff784c2cdfd863bad31985851427f2d2796e445cec85080c7510cba4315"}, + {file = "bitarray-3.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:911b4a16dce370657e5b8d8b6ba0fbb50dd5e2b24c4416f4b9e664503d3f0502"}, + {file = "bitarray-3.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b47843f2f288fa746dead4394591a3432a358aaad48240283fa230d6e74b0e7"}, + {file = "bitarray-3.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8f95daf0ce2b24815ddf62667229ba5dfc0cfee43eb43b2549766170d0f24ae9"}, + {file = "bitarray-3.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15b9e37bbca59657e4dcc63ad068c821a4676def15f04742c406748a0a11b9c"}, + {file = "bitarray-3.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9d247fcc33c90f2758f4162693250341e3f38cd094f64390076ef33ad0887f9"}, + {file = "bitarray-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:84bb57010a1ab76cf880424a2e0bce8dd26989849d2122ff073aa11bfc271c27"}, + {file = "bitarray-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:27d13c7b886afc5d2fc49d6e92f9c96b1f0a14dc7b5502520c29f3da7550d401"}, + {file = "bitarray-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c4e75bbf9ade3d2cdf1b607a8b353b17d9b3cf54e88b2a5a773f50ae6f1bfbc"}, + {file = "bitarray-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:975a118aa019d745f1398613b27fd8789f60a8cea057a00cdc1abedee123ffe6"}, + {file = "bitarray-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9ed4a2852b3de7a64884afcc6936db771707943249a060aec8e551c16361d478"}, + {file = "bitarray-3.6.0-cp312-cp312-win32.whl", hash = "sha256:5dd9edcab8979a50c2c4dec6d5b66789fb6f630bb52ab90a4548111075a75e48"}, + {file = "bitarray-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:552a93be286ca485914777461b384761519db313e0a7f3012dca424c9610a4d5"}, + {file = "bitarray-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3f96f57cea35ba19fd23a20b38fa0dfa3d87d582507129b8c8e314aa298f59b"}, + {file = "bitarray-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:81e84054b22babcd6c5cc1eac0de2bfc1054ecdf742720cbfb36efbe89ec6c30"}, + {file = "bitarray-3.6.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca643295bf5441dd38dadf7571ca4b63961820eedbffbe46ceba0893bf226203"}, + {file = "bitarray-3.6.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139963494fc3dd5caee5e38c0a03783ef50be118565e94b1dbb0210770f0b32d"}, + {file = "bitarray-3.6.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:243825f56b58bef28bfc602992a8c6d09bbc625628c195498d6020120d632a09"}, + {file = "bitarray-3.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:583b46b3ba44121de5e87e95ae379932dc5fd2e37ebdf2c11a6d7975891425c1"}, + {file = "bitarray-3.6.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f0be27d06732e2833b672a8fcc32fa195bdb22161eb88f8890de15e30264a01"}, + {file = "bitarray-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:507e567aee4806576e20752f22533e8b7ec61e7e75062a7ce9222a0675aa0da6"}, + {file = "bitarray-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:22188943a29072b684cd7c99e0b2cfc0af317cea3366c583d820507e6d1f2ed4"}, + {file = "bitarray-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f92462ea3888c99439f58f7561ecd5dd4cf8b8b1b259ccf5376667b8c46ee747"}, + {file = "bitarray-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3800f3c8c9780f281cf590543fd4b3278fea6988202273a260ecc58136895efb"}, + {file = "bitarray-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a50a66fa34dd7f9dcdbc7602a1b7bf6f9ab030b4f43e892324193423d9ede180"}, + {file = "bitarray-3.6.0-cp313-cp313-win32.whl", hash = "sha256:afa24e5750c9b89ad5a7efef037efe49f4e339f20a94bf678c422c0c71e1207a"}, + {file = "bitarray-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4c5e7edf1e7bcbde3b52058f171a411e2a24a081b3e951d685dfea4c3c383d5"}, + {file = "bitarray-3.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fefd18b29f3b84a0cdea1d86340219d9871c3b0673a38e722a73a2c39591eaa7"}, + {file = "bitarray-3.6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:679856547f0b27b98811b73756bdf53769c23b045a6f95177cae634daabf1ddf"}, + {file = "bitarray-3.6.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51947a00ae9924584fb14c0c1b1f4c1fd916d9abd6f47582f318ab9c9cb9f3d0"}, + {file = "bitarray-3.6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0956322bf4d5e2293e57600aa929c241edf1e209e94e12483bf58c5c691432db"}, + {file = "bitarray-3.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3b521e117ab991d6b3b830656f464b354a42dbea2ca16a0e7d93d573f7ab7ff"}, + {file = "bitarray-3.6.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27eeee915258b105a21a4b0f8aebc5f77bb4dc4fb4063a09dd329fa1fdcbd234"}, + {file = "bitarray-3.6.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:f738051052abc95dc17f9a4c92044294a263fb7f762efdb13e528d419005c0e4"}, + {file = "bitarray-3.6.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:1971050b447023288a2b694a03b400bd5163829cd67b10f19e757fe87cd1161e"}, + {file = "bitarray-3.6.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:a290a417608f50137bec731d1f22ff3efebac72845530807a8433b2db9358c95"}, + {file = "bitarray-3.6.0-cp36-cp36m-musllinux_1_2_s390x.whl", hash = "sha256:8ef3f0977c21190f949d5cfd71ded09de87d330c6d98bd5ecb5bb1135d666d0d"}, + {file = "bitarray-3.6.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:357e07c827bad01f98d0bd0dfdc722f483febeed39140fd75ffd016a451b60b9"}, + {file = "bitarray-3.6.0-cp36-cp36m-win32.whl", hash = "sha256:bdd6412c1f38da7565126b174f4e644f362e317ef0560fac1fb9d0c70900ff4d"}, + {file = "bitarray-3.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a1b3c4ca3bec8e0ad9d32ce62444c5f3913588124a922629aa7d39357b2adf3f"}, + {file = "bitarray-3.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:531e6dfec8058fcf5d69e863b61e6b28e3749b615a4dcc0ab8ad36307c4017fc"}, + {file = "bitarray-3.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6f9e897907757e9c2d722ae6c203d48a04826a14e1495e33935c8583c163a9"}, + {file = "bitarray-3.6.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c9f36055a89b9517db66eb8e80137126bf629c767ceeade4d004e40bc8bcd99"}, + {file = "bitarray-3.6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d6f3a94abd8b44b2bf346ca81ab2ff41ab9146c53905eedf5178b19d9fe53bf"}, + {file = "bitarray-3.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79db23eda81627132327ed292bd813a9af64399b98aaac3d42ad8deeed24cd5e"}, + {file = "bitarray-3.6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d6c9bc14bacdfbfd51fed85f0576973eaaa7b30d81ef93264f8e22b86a9c9f7"}, + {file = "bitarray-3.6.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:60408ec9c0bd76f1fa00d28034429a0316246d31069b982a86aec8d5c99e910a"}, + {file = "bitarray-3.6.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:870ed23361e2918ab1ffc23fe0ab293abf3c372a68ee7387456d13da3e213008"}, + {file = "bitarray-3.6.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:c677849947d523a082be6e0b5c9137f443a54e951a1711ef003ec52910c41ece"}, + {file = "bitarray-3.6.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:a5ce1bdee102f7e60c075274df10b892d9ff5183ad6f5f515973eda8903dfe4c"}, + {file = "bitarray-3.6.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:a33f7c5acf44961f29018b13f0b5f5e1589ac0cfdf75a97c9774cf7ec84d09e0"}, + {file = "bitarray-3.6.0-cp37-cp37m-win32.whl", hash = "sha256:16d0edab54bb9d214319418f65bd15cfc4210ec41a16c3dd0b71e626c803212d"}, + {file = "bitarray-3.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f76784355060999c36fa807b59faecb38f5769ae58283d00270835773f95e35b"}, + {file = "bitarray-3.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cc060bc17b9de27874997d612e37d52f72092f9b59d1e04284a90ed8113cadca"}, + {file = "bitarray-3.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfc417e58f277e949ed662d9cd050ddbb00c0dea8a828abaccc93dc357b7a6d1"}, + {file = "bitarray-3.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:129165b68a3e0c2a633ed0d8557cf5ade24a0b37ca97d7805fa6fc5fb73c19d5"}, + {file = "bitarray-3.6.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50d702149747852923be60cae125285eca8d189d4c7d8832c0c958d4071a0f78"}, + {file = "bitarray-3.6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccf4a73e07bfbd790443d6b3c1f1447ffda23cc9391e40c035d9b7d3514b57b8"}, + {file = "bitarray-3.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7d2dbe628f3db935622a5b80a5c4d95665cdefc4904372aa3c4d786289477f"}, + {file = "bitarray-3.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5da4939e241301f5e1d18118695e8d2c300be90431b66bd43a00376acec45e1e"}, + {file = "bitarray-3.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9930853d451086c4c084d83a87294bdb0c5bc0fa4105a26c487ac09ea62e565b"}, + {file = "bitarray-3.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:e0e4fdeae6c0a9d886749780ec5dcf469e98f27b312efa93008d03eaa2426fd5"}, + {file = "bitarray-3.6.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:79ab1c5f26f23e51d4a44c4397c8a3bf56c306c125dfab6b3eebdfa13d1dca6f"}, + {file = "bitarray-3.6.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b9a03767c937b621ee267507bc394df97fb2f8f61130f39f2033f3c6c191f124"}, + {file = "bitarray-3.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:54bd71f14a5fa9bae73ef92f2e2be894dc36c7a6d1c4962e5969bd8a9aa39325"}, + {file = "bitarray-3.6.0-cp38-cp38-win32.whl", hash = "sha256:7e0851a985a7b10f634188117c825ef99d63402555cca5bc32c7bfc5adaf0d6f"}, + {file = "bitarray-3.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:00628196dd3592972a5183194ab1475dadf9ef2a4cf3fd8c7c184a94934012e8"}, + {file = "bitarray-3.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:69d2d507c1174330c71c834b5d65e66181ad7b42b0d88b5b31804ee9b4f5dae7"}, + {file = "bitarray-3.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:727f7a969416f02ef5c1256541e06f0836fb615022699fa8e2591e85296c5570"}, + {file = "bitarray-3.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42622c42c159ea4535bba7e1e3c97f1fec79505bc6873ae657dc0a8f861c60de"}, + {file = "bitarray-3.6.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f8b12424f8fdf29d1c0749c628bd1530cecfc77374935d096cccc0e4eada232"}, + {file = "bitarray-3.6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:963cbcf296943f7017470d0b705e63e908f32b4f7dbe43f72c22f6fe1bd9ef66"}, + {file = "bitarray-3.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e304f94c0353f6ae5711533b5793b3a45b17aa2c5b07e656649b0af4e0939b5"}, + {file = "bitarray-3.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c533c828d0007fac27cf45e5c1a711e5914dd469db5fe6be5f4e606bf2d7f63"}, + {file = "bitarray-3.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:220d4b8649ef54ac98e5e0e3dd92230247f67270d1524a8b31aa9859007affb0"}, + {file = "bitarray-3.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:407920e9318d94cc6c9611aaa5b5e5963a09f1cbfa17b16b66edea453b3754f4"}, + {file = "bitarray-3.6.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:056fe779f01a867d572e071c0944ac2f3bf58d8bced326040f0bd060af33a209"}, + {file = "bitarray-3.6.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ca87f639094c72268e17bc7f57c1225cc38f9e191a489a0134762e3fec402c1a"}, + {file = "bitarray-3.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b5ad8261f47c2a72d0f676bc40f752db8cfdcab911e970753343836e41d5a9a7"}, + {file = "bitarray-3.6.0-cp39-cp39-win32.whl", hash = "sha256:a773199dc42b5d02fcd46c8add552da2c4725ce2caa069527c7e27b5b6089e85"}, + {file = "bitarray-3.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:157313a124287cbc8a11b55a75def0dd59e68badbc82c2dc2d204dc852742874"}, + {file = "bitarray-3.6.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a763dd33d6e27c9b4db3f8089a5fa39179a8a3cf48ce702b24a857d7c621333c"}, + {file = "bitarray-3.6.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8cf44b012e7493127ce7ca6e469138ac96b3295a117877d5469aabe7c8728d87"}, + {file = "bitarray-3.6.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e297fd2e58afe17e33dd80c231c3a9d850279a2a8625aed1d39f9be9534809e"}, + {file = "bitarray-3.6.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fc8bc65f964c7278deb1b7a69379dab3ecc90095f252deb17365637ebb274d"}, + {file = "bitarray-3.6.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa3c925502bd0b957a96a5619134bcdc0382ef73cffd40bad218ced3586bcf8d"}, + {file = "bitarray-3.6.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9f7796959c9c036a115d34696563f75d4a2912d3b97c15c15f2a36bdd5496ce9"}, + {file = "bitarray-3.6.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b02cc1cac9099c0ec72da09593e7fadb1b6cf88a101acc8153592c700d732d80"}, + {file = "bitarray-3.6.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26691454a6770628882b68fe74e9f84ca2a51512edd49cbb025b14df5a9dd85a"}, + {file = "bitarray-3.6.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a5b0d277087a5bf261a607fc6ff4aaffcf80b300cd19b7a1e9754a4649f5fd4"}, + {file = "bitarray-3.6.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af670708e145b048ead87375b899229443f2d0b4af2d1450d7701c74cd932b03"}, + {file = "bitarray-3.6.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:aeb6db2f4ab54ac21a3851d05130a2aa78a6f6a5f14003f9ae3114fb8b210850"}, + {file = "bitarray-3.6.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:99d16862e802e7c50c3b6cdd1bf041b6142335c9c2b426631f731257adfe5a15"}, + {file = "bitarray-3.6.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:66d8b7a89fac6042f7df9ea97d97ed0f5e404281110a882e3babd909161f85b6"}, + {file = "bitarray-3.6.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62f71b268f14ee6cc3045b95441bfe0518cef1d0b2ffbc6f3e9758f786ff5a03"}, + {file = "bitarray-3.6.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72760411d60d8d76979a20ed3f15586d824db04668b581b86e61158c2b616db0"}, + {file = "bitarray-3.6.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc5029c61f9ebb2d4c247f13584a0ef0e8e49abb13e56460310821aca3ffcaf"}, + {file = "bitarray-3.6.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0ac446f557eb28e3f7c65372608810ff073840627e9037e22ed10bd081793a34"}, + {file = "bitarray-3.6.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b9ae0008cff25e154ef1e3975a1705d344e844ffdeb34c25b007fd48c876e95d"}, + {file = "bitarray-3.6.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:db78cc5c03b446a43413165aa873e2f408e9fd5ddb45533e7bd3b638bace867c"}, + {file = "bitarray-3.6.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cfbdccddaa0ff07789e9e180db127906c676e479e05c04830cd458945de3511"}, + {file = "bitarray-3.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:222cb27ff05bc0aec72498d075dba1facec49a76a7da45740690cebbe3e81e43"}, + {file = "bitarray-3.6.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b58a672ec448fb36839a5fc7bf2b2f60df9a97b872d8bd6ca1a28da6126f5c7"}, + {file = "bitarray-3.6.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b37c9ea942395de029be270f0eca8c141eb14e8455941495cd3b6f95bbe465f4"}, + {file = "bitarray-3.6.0.tar.gz", hash = "sha256:20febc849a1f858e6a57a7d47b323fe9e727c579ddd526d317ad8831748a66a8"}, ] [[package]] name = "certifi" -version = "2024.8.30" +version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, ] [[package]] name = "charset-normalizer" -version = "3.4.0" +version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, + {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, + {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, ] [[package]] name = "click" -version = "8.1.7" +version = "8.2.1" description = "Composable command line interface toolkit" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, + {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, ] [package.dependencies] @@ -411,14 +396,14 @@ files = [ [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] @@ -441,14 +426,14 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "joblib" -version = "1.4.2" +version = "1.5.1" description = "Lightweight pipelining with Python functions" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, - {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, + {file = "joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a"}, + {file = "joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444"}, ] [[package]] @@ -594,49 +579,56 @@ files = [ [[package]] name = "mypy" -version = "1.13.0" +version = "1.17.1" description = "Optional static typing for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, - {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, - {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, - {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, - {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, - {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, - {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, - {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, - {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, - {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, - {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, - {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, - {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, - {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, - {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, - {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, - {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, - {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, - {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, - {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, - {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, - {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, + {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, + {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, + {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, + {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, + {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, + {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, + {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, + {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, + {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, + {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"}, + {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"}, + {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"}, + {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, + {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, ] [package.dependencies] -mypy-extensions = ">=1.0.0" -typing-extensions = ">=4.6.0" +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" +typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -647,26 +639,26 @@ reports = ["lxml"] [[package]] name = "mypy-extensions" -version = "1.0.0" +version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] [[package]] name = "myst-parser" -version = "4.0.0" +version = "4.0.1" description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "myst_parser-4.0.0-py3-none-any.whl", hash = "sha256:b9317997552424448c6096c2558872fdb6f81d3ecb3a40ce84a7518798f3f28d"}, - {file = "myst_parser-4.0.0.tar.gz", hash = "sha256:851c9dfb44e36e56d15d05e72f02b80da21a9e0d07cba96baf5e2d476bb91531"}, + {file = "myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d"}, + {file = "myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4"}, ] [package.dependencies] @@ -678,167 +670,196 @@ pyyaml = "*" sphinx = ">=7,<9" [package.extras] -code-style = ["pre-commit (>=3.0,<4.0)"] +code-style = ["pre-commit (>=4.0,<5.0)"] linkify = ["linkify-it-py (>=2.0,<3.0)"] rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] -testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] +testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pygments (<2.19)", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] [[package]] name = "numpy" -version = "2.1.3" +version = "2.3.2" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.10" +python-versions = ">=3.11" groups = ["main"] files = [ - {file = "numpy-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff"}, - {file = "numpy-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5"}, - {file = "numpy-2.1.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1"}, - {file = "numpy-2.1.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd"}, - {file = "numpy-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3"}, - {file = "numpy-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098"}, - {file = "numpy-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c"}, - {file = "numpy-2.1.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4"}, - {file = "numpy-2.1.3-cp310-cp310-win32.whl", hash = "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23"}, - {file = "numpy-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0"}, - {file = "numpy-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d"}, - {file = "numpy-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41"}, - {file = "numpy-2.1.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9"}, - {file = "numpy-2.1.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09"}, - {file = "numpy-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a"}, - {file = "numpy-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b"}, - {file = "numpy-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee"}, - {file = "numpy-2.1.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0"}, - {file = "numpy-2.1.3-cp311-cp311-win32.whl", hash = "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9"}, - {file = "numpy-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2"}, - {file = "numpy-2.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e"}, - {file = "numpy-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958"}, - {file = "numpy-2.1.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8"}, - {file = "numpy-2.1.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564"}, - {file = "numpy-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512"}, - {file = "numpy-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b"}, - {file = "numpy-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc"}, - {file = "numpy-2.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0"}, - {file = "numpy-2.1.3-cp312-cp312-win32.whl", hash = "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9"}, - {file = "numpy-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a"}, - {file = "numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f"}, - {file = "numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598"}, - {file = "numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57"}, - {file = "numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe"}, - {file = "numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43"}, - {file = "numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56"}, - {file = "numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a"}, - {file = "numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef"}, - {file = "numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f"}, - {file = "numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed"}, - {file = "numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f"}, - {file = "numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4"}, - {file = "numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e"}, - {file = "numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0"}, - {file = "numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408"}, - {file = "numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6"}, - {file = "numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f"}, - {file = "numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17"}, - {file = "numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48"}, - {file = "numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4"}, - {file = "numpy-2.1.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f"}, - {file = "numpy-2.1.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4"}, - {file = "numpy-2.1.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d"}, - {file = "numpy-2.1.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb"}, - {file = "numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8"}, + {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d"}, + {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3"}, + {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f"}, + {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097"}, + {file = "numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220"}, + {file = "numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170"}, + {file = "numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b"}, + {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370"}, + {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73"}, + {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc"}, + {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be"}, + {file = "numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036"}, + {file = "numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f"}, + {file = "numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089"}, + {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2"}, + {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f"}, + {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee"}, + {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6"}, + {file = "numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b"}, + {file = "numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56"}, + {file = "numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286"}, + {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8"}, + {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a"}, + {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91"}, + {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5"}, + {file = "numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5"}, + {file = "numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450"}, + {file = "numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19"}, + {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f"}, + {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5"}, + {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58"}, + {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0"}, + {file = "numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2"}, + {file = "numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b"}, + {file = "numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2"}, + {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0"}, + {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0"}, + {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2"}, + {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf"}, + {file = "numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1"}, + {file = "numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b"}, + {file = "numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619"}, + {file = "numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48"}, ] [[package]] name = "packaging" -version = "24.2" +version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "protobuf" -version = "5.29.0" +version = "6.31.1" description = "" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "protobuf-5.29.0-cp310-abi3-win32.whl", hash = "sha256:ea7fb379b257911c8c020688d455e8f74efd2f734b72dc1ea4b4d7e9fd1326f2"}, - {file = "protobuf-5.29.0-cp310-abi3-win_amd64.whl", hash = "sha256:34a90cf30c908f47f40ebea7811f743d360e202b6f10d40c02529ebd84afc069"}, - {file = "protobuf-5.29.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:c931c61d0cc143a2e756b1e7f8197a508de5365efd40f83c907a9febf36e6b43"}, - {file = "protobuf-5.29.0-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:85286a47caf63b34fa92fdc1fd98b649a8895db595cfa746c5286eeae890a0b1"}, - {file = "protobuf-5.29.0-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:0d10091d6d03537c3f902279fcf11e95372bdd36a79556311da0487455791b20"}, - {file = "protobuf-5.29.0-cp38-cp38-win32.whl", hash = "sha256:0cd67a1e5c2d88930aa767f702773b2d054e29957432d7c6a18f8be02a07719a"}, - {file = "protobuf-5.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:e467f81fdd12ded9655cea3e9b83dc319d93b394ce810b556fb0f421d8613e86"}, - {file = "protobuf-5.29.0-cp39-cp39-win32.whl", hash = "sha256:17d128eebbd5d8aee80300aed7a43a48a25170af3337f6f1333d1fac2c6839ac"}, - {file = "protobuf-5.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:6c3009e22717c6cc9e6594bb11ef9f15f669b19957ad4087214d69e08a213368"}, - {file = "protobuf-5.29.0-py3-none-any.whl", hash = "sha256:88c4af76a73183e21061881360240c0cdd3c39d263b4e8fb570aaf83348d608f"}, - {file = "protobuf-5.29.0.tar.gz", hash = "sha256:445a0c02483869ed8513a585d80020d012c6dc60075f96fa0563a724987b1001"}, + {file = "protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9"}, + {file = "protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447"}, + {file = "protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402"}, + {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39"}, + {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6"}, + {file = "protobuf-6.31.1-cp39-cp39-win32.whl", hash = "sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16"}, + {file = "protobuf-6.31.1-cp39-cp39-win_amd64.whl", hash = "sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9"}, + {file = "protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e"}, + {file = "protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a"}, ] [[package]] name = "psutil" -version = "6.1.0" +version = "6.1.1" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" groups = ["dev"] files = [ - {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, - {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, - {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:000d1d1ebd634b4efb383f4034437384e44a6d455260aaee2eca1e9c1b55f047"}, - {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5cd2bcdc75b452ba2e10f0e8ecc0b57b827dd5d7aaffbc6821b2a9a242823a76"}, - {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:045f00a43c737f960d273a83973b2511430d61f283a44c96bf13a6e829ba8fdc"}, - {file = "psutil-6.1.0-cp27-none-win32.whl", hash = "sha256:9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e"}, - {file = "psutil-6.1.0-cp27-none-win_amd64.whl", hash = "sha256:a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85"}, - {file = "psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"}, - {file = "psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"}, - {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"}, - {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"}, - {file = "psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"}, - {file = "psutil-6.1.0-cp36-cp36m-win32.whl", hash = "sha256:6d3fbbc8d23fcdcb500d2c9f94e07b1342df8ed71b948a2649b5cb060a7c94ca"}, - {file = "psutil-6.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1209036fbd0421afde505a4879dee3b2fd7b1e14fee81c0069807adcbbcca747"}, - {file = "psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"}, - {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, - {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, + {file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"}, + {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"}, + {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8df0178ba8a9e5bc84fed9cfa61d54601b371fbec5c8eebad27575f1e105c0d4"}, + {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:1924e659d6c19c647e763e78670a05dbb7feaf44a0e9c94bf9e14dfc6ba50468"}, + {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:018aeae2af92d943fdf1da6b58665124897cfc94faa2ca92098838f83e1b1bca"}, + {file = "psutil-6.1.1-cp27-none-win32.whl", hash = "sha256:6d4281f5bbca041e2292be3380ec56a9413b790579b8e593b1784499d0005dac"}, + {file = "psutil-6.1.1-cp27-none-win_amd64.whl", hash = "sha256:c777eb75bb33c47377c9af68f30e9f11bc78e0f07fbf907be4a5d70b2fe5f030"}, + {file = "psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8"}, + {file = "psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377"}, + {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003"}, + {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160"}, + {file = "psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3"}, + {file = "psutil-6.1.1-cp36-cp36m-win32.whl", hash = "sha256:384636b1a64b47814437d1173be1427a7c83681b17a450bfc309a1953e329603"}, + {file = "psutil-6.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8be07491f6ebe1a693f17d4f11e69d0dc1811fa082736500f649f79df7735303"}, + {file = "psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53"}, + {file = "psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649"}, + {file = "psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5"}, ] [package.extras] -dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] +dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] test = ["pytest", "pytest-xdist", "setuptools"] [[package]] name = "pydata-sphinx-theme" -version = "0.16.0" +version = "0.15.4" description = "Bootstrap-based Sphinx theme from the PyData community" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pydata_sphinx_theme-0.16.0-py3-none-any.whl", hash = "sha256:18c810ee4e67e05281e371e156c1fb5bb0fa1f2747240461b225272f7d8d57d8"}, - {file = "pydata_sphinx_theme-0.16.0.tar.gz", hash = "sha256:721dd26e05fa8b992d66ef545536e6cbe0110afb9865820a08894af1ad6f7707"}, + {file = "pydata_sphinx_theme-0.15.4-py3-none-any.whl", hash = "sha256:2136ad0e9500d0949f96167e63f3e298620040aea8f9c74621959eda5d4cf8e6"}, + {file = "pydata_sphinx_theme-0.15.4.tar.gz", hash = "sha256:7762ec0ac59df3acecf49fd2f889e1b4565dbce8b88b2e29ee06fdd90645a06d"}, ] [package.dependencies] @@ -846,8 +867,9 @@ accessible-pygments = "*" Babel = "*" beautifulsoup4 = "*" docutils = "!=0.17.0" +packaging = "*" pygments = ">=2.7" -sphinx = ">=6.1" +sphinx = ">=5" typing-extensions = "*" [package.extras] @@ -859,14 +881,14 @@ test = ["pytest", "pytest-cov", "pytest-regressions", "sphinx[test]"] [[package]] name = "pygments" -version = "2.18.0" +version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] [package.extras] @@ -874,24 +896,25 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" -version = "8.3.3" +version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, - {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, + {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, + {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +iniconfig = ">=1" +packaging = ">=20" pluggy = ">=1.5,<2" +pygments = ">=2.7.2" [package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "pyyaml" @@ -973,7 +996,7 @@ rtree = "*" [package.source] type = "git" url = "https://github.com/adf-python/rcrs-core-python" -reference = "HEAD" +reference = "v0.1.0" resolved_reference = "42a20e312de20ea46f1e0622a82f41e81fc3514f" [[package]] @@ -998,24 +1021,40 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "roman-numerals-py" +version = "3.1.0" +description = "Manipulate well-formed Roman numerals" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c"}, + {file = "roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d"}, +] + +[package.extras] +lint = ["mypy (==1.15.0)", "pyright (==1.1.394)", "ruff (==0.9.7)"] +test = ["pytest (>=8)"] + [[package]] name = "rtree" -version = "1.3.0" +version = "1.4.0" description = "R-Tree spatial index for Python GIS" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "Rtree-1.3.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:80879d9db282a2273ca3a0d896c84583940e9777477727a277624ebfd424c517"}, - {file = "Rtree-1.3.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4328e9e421797c347e6eb08efbbade962fe3664ebd60c1dffe82c40911b1e125"}, - {file = "Rtree-1.3.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:037130d3ce1fc029de81941ec416ba5546f66228380ba19bb41f2ea1294e8423"}, - {file = "Rtree-1.3.0-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:864a05d0c3b7ce6c5e34378b7ab630057603b79179368bc50624258bdf2ff631"}, - {file = "Rtree-1.3.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ec2ed6d1635753dab966e68f592a9c4896f3f4ec6ad2b09b776d592eacd883a9"}, - {file = "Rtree-1.3.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b4485fb3e5c5e85b94a95f0a930a3848e040d2699cfb012940ba5b0130f1e09a"}, - {file = "Rtree-1.3.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7e2e9211f4fb404c06a08fd2cbebb03234214f73c51913bb371c3d9954e99cc9"}, - {file = "Rtree-1.3.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c021f4772b25cc24915da8073e553ded6fa8d0b317caa4202255ed26b2344c1c"}, - {file = "Rtree-1.3.0-py3-none-win_amd64.whl", hash = "sha256:97f835801d24c10bbf02381abe5e327345c8296ec711dde7658792376abafc66"}, - {file = "rtree-1.3.0.tar.gz", hash = "sha256:b36e9dd2dc60ffe3d02e367242d2c26f7281b00e1aaf0c39590442edaaadd916"}, + {file = "rtree-1.4.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:4d1bebc418101480aabf41767e772dd2155d3b27b1376cccbd93e4509485e091"}, + {file = "rtree-1.4.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:997f8c38d5dffa3949ea8adb4c8b291ea5cd4ef5ee69455d642dd171baf9991d"}, + {file = "rtree-1.4.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0133d9c54ab3ffe874ba6d411dbe0254765c5e68d92da5b91362c370f16fd997"}, + {file = "rtree-1.4.0-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d3b7bf1fe6463139377995ebe22a01a7005d134707f43672a3c09305e12f5f43"}, + {file = "rtree-1.4.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:27e4a6d617d63dcb82fcd4c2856134b8a3741bd1af3b1a0d98e886054f394da5"}, + {file = "rtree-1.4.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5258e826064eab82439760201e9421ce6d4340789d6d080c1b49367ddd03f61f"}, + {file = "rtree-1.4.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:20d5b3f9cf8bbbcc9fec42ab837c603c5dd86103ef29134300c8da2495c1248b"}, + {file = "rtree-1.4.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a67bee1233370a4c72c0969a96d2a1df1ba404ddd9f146849c53ab420eab361b"}, + {file = "rtree-1.4.0-py3-none-win_amd64.whl", hash = "sha256:ba83efc7b7563905b1bfdfc14490c4bfb59e92e5e6156bdeb6ec5df5117252f4"}, + {file = "rtree-1.4.0.tar.gz", hash = "sha256:9d97c7c5dcf25f6c0599c76d9933368c6a8d7238f2c1d00e76f1a69369ca82a0"}, ] [[package]] @@ -1047,116 +1086,138 @@ files = [ [[package]] name = "scikit-learn" -version = "1.5.2" +version = "1.7.1" description = "A set of python modules for machine learning and data mining" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "scikit_learn-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:299406827fb9a4f862626d0fe6c122f5f87f8910b86fe5daa4c32dcd742139b6"}, - {file = "scikit_learn-1.5.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:2d4cad1119c77930b235579ad0dc25e65c917e756fe80cab96aa3b9428bd3fb0"}, - {file = "scikit_learn-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c412ccc2ad9bf3755915e3908e677b367ebc8d010acbb3f182814524f2e5540"}, - {file = "scikit_learn-1.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a686885a4b3818d9e62904d91b57fa757fc2bed3e465c8b177be652f4dd37c8"}, - {file = "scikit_learn-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:c15b1ca23d7c5f33cc2cb0a0d6aaacf893792271cddff0edbd6a40e8319bc113"}, - {file = "scikit_learn-1.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03b6158efa3faaf1feea3faa884c840ebd61b6484167c711548fce208ea09445"}, - {file = "scikit_learn-1.5.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1ff45e26928d3b4eb767a8f14a9a6efbf1cbff7c05d1fb0f95f211a89fd4f5de"}, - {file = "scikit_learn-1.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f763897fe92d0e903aa4847b0aec0e68cadfff77e8a0687cabd946c89d17e675"}, - {file = "scikit_learn-1.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8b0ccd4a902836493e026c03256e8b206656f91fbcc4fde28c57a5b752561f1"}, - {file = "scikit_learn-1.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6c16d84a0d45e4894832b3c4d0bf73050939e21b99b01b6fd59cbb0cf39163b6"}, - {file = "scikit_learn-1.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f932a02c3f4956dfb981391ab24bda1dbd90fe3d628e4b42caef3e041c67707a"}, - {file = "scikit_learn-1.5.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3b923d119d65b7bd555c73be5423bf06c0105678ce7e1f558cb4b40b0a5502b1"}, - {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f60021ec1574e56632be2a36b946f8143bf4e5e6af4a06d85281adc22938e0dd"}, - {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:394397841449853c2290a32050382edaec3da89e35b3e03d6cc966aebc6a8ae6"}, - {file = "scikit_learn-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:57cc1786cfd6bd118220a92ede80270132aa353647684efa385a74244a41e3b1"}, - {file = "scikit_learn-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9a702e2de732bbb20d3bad29ebd77fc05a6b427dc49964300340e4c9328b3f5"}, - {file = "scikit_learn-1.5.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:b0768ad641981f5d3a198430a1d31c3e044ed2e8a6f22166b4d546a5116d7908"}, - {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:178ddd0a5cb0044464fc1bfc4cca5b1833bfc7bb022d70b05db8530da4bb3dd3"}, - {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7284ade780084d94505632241bf78c44ab3b6f1e8ccab3d2af58e0e950f9c12"}, - {file = "scikit_learn-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:b7b0f9a0b1040830d38c39b91b3a44e1b643f4b36e36567b80b7c6bd2202a27f"}, - {file = "scikit_learn-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:757c7d514ddb00ae249832fe87100d9c73c6ea91423802872d9e74970a0e40b9"}, - {file = "scikit_learn-1.5.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:52788f48b5d8bca5c0736c175fa6bdaab2ef00a8f536cda698db61bd89c551c1"}, - {file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:643964678f4b5fbdc95cbf8aec638acc7aa70f5f79ee2cdad1eec3df4ba6ead8"}, - {file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca64b3089a6d9b9363cd3546f8978229dcbb737aceb2c12144ee3f70f95684b7"}, - {file = "scikit_learn-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:3bed4909ba187aca80580fe2ef370d9180dcf18e621a27c4cf2ef10d279a7efe"}, - {file = "scikit_learn-1.5.2.tar.gz", hash = "sha256:b4237ed7b3fdd0a4882792e68ef2545d5baa50aca3bb45aa7df468138ad8f94d"}, + {file = "scikit_learn-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:406204dd4004f0517f0b23cf4b28c6245cbd51ab1b6b78153bc784def214946d"}, + {file = "scikit_learn-1.7.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:16af2e44164f05d04337fd1fc3ae7c4ea61fd9b0d527e22665346336920fe0e1"}, + {file = "scikit_learn-1.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2f2e78e56a40c7587dea9a28dc4a49500fa2ead366869418c66f0fd75b80885c"}, + {file = "scikit_learn-1.7.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b62b76ad408a821475b43b7bb90a9b1c9a4d8d125d505c2df0539f06d6e631b1"}, + {file = "scikit_learn-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:9963b065677a4ce295e8ccdee80a1dd62b37249e667095039adcd5bce6e90deb"}, + {file = "scikit_learn-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90c8494ea23e24c0fb371afc474618c1019dc152ce4a10e4607e62196113851b"}, + {file = "scikit_learn-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bb870c0daf3bf3be145ec51df8ac84720d9972170786601039f024bf6d61a518"}, + {file = "scikit_learn-1.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40daccd1b5623f39e8943ab39735cadf0bdce80e67cdca2adcb5426e987320a8"}, + {file = "scikit_learn-1.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30d1f413cfc0aa5a99132a554f1d80517563c34a9d3e7c118fde2d273c6fe0f7"}, + {file = "scikit_learn-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c711d652829a1805a95d7fe96654604a8f16eab5a9e9ad87b3e60173415cb650"}, + {file = "scikit_learn-1.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3cee419b49b5bbae8796ecd690f97aa412ef1674410c23fc3257c6b8b85b8087"}, + {file = "scikit_learn-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2fd8b8d35817b0d9ebf0b576f7d5ffbbabdb55536b0655a8aaae629d7ffd2e1f"}, + {file = "scikit_learn-1.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:588410fa19a96a69763202f1d6b7b91d5d7a5d73be36e189bc6396bfb355bd87"}, + {file = "scikit_learn-1.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3142f0abe1ad1d1c31a2ae987621e41f6b578144a911ff4ac94781a583adad7"}, + {file = "scikit_learn-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ddd9092c1bd469acab337d87930067c87eac6bd544f8d5027430983f1e1ae88"}, + {file = "scikit_learn-1.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b7839687fa46d02e01035ad775982f2470be2668e13ddd151f0f55a5bf123bae"}, + {file = "scikit_learn-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a10f276639195a96c86aa572ee0698ad64ee939a7b042060b98bd1930c261d10"}, + {file = "scikit_learn-1.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13679981fdaebc10cc4c13c43344416a86fcbc61449cb3e6517e1df9d12c8309"}, + {file = "scikit_learn-1.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f1262883c6a63f067a980a8cdd2d2e7f2513dddcef6a9eaada6416a7a7cbe43"}, + {file = "scikit_learn-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca6d31fb10e04d50bfd2b50d66744729dbb512d4efd0223b864e2fdbfc4cee11"}, + {file = "scikit_learn-1.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:781674d096303cfe3d351ae6963ff7c958db61cde3421cd490e3a5a58f2a94ae"}, + {file = "scikit_learn-1.7.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:10679f7f125fe7ecd5fad37dd1aa2daae7e3ad8df7f3eefa08901b8254b3e12c"}, + {file = "scikit_learn-1.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f812729e38c8cb37f760dce71a9b83ccfb04f59b3dca7c6079dcdc60544fa9e"}, + {file = "scikit_learn-1.7.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88e1a20131cf741b84b89567e1717f27a2ced228e0f29103426102bc2e3b8ef7"}, + {file = "scikit_learn-1.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b1bd1d919210b6a10b7554b717c9000b5485aa95a1d0f177ae0d7ee8ec750da5"}, + {file = "scikit_learn-1.7.1.tar.gz", hash = "sha256:24b3f1e976a4665aa74ee0fcaac2b8fccc6ae77c8e07ab25da3ba6d3292b9802"}, ] [package.dependencies] joblib = ">=1.2.0" -numpy = ">=1.19.5" -scipy = ">=1.6.0" +numpy = ">=1.22.0" +scipy = ">=1.8.0" threadpoolctl = ">=3.1.0" [package.extras] -benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"] -build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"] -docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-design (>=0.6.0)", "sphinx-gallery (>=0.16.0)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)"] -examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] -install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"] -maintenance = ["conda-lock (==2.5.6)"] -tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.2.1)", "scikit-image (>=0.17.2)"] +benchmark = ["matplotlib (>=3.5.0)", "memory_profiler (>=0.57.0)", "pandas (>=1.4.0)"] +build = ["cython (>=3.0.10)", "meson-python (>=0.17.1)", "numpy (>=1.22.0)", "scipy (>=1.8.0)"] +docs = ["Pillow (>=8.4.0)", "matplotlib (>=3.5.0)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.4.0)", "plotly (>=5.14.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.19.0)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-design (>=0.6.0)", "sphinx-gallery (>=0.17.1)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)", "towncrier (>=24.8.0)"] +examples = ["matplotlib (>=3.5.0)", "pandas (>=1.4.0)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.19.0)", "seaborn (>=0.9.0)"] +install = ["joblib (>=1.2.0)", "numpy (>=1.22.0)", "scipy (>=1.8.0)", "threadpoolctl (>=3.1.0)"] +maintenance = ["conda-lock (==3.0.1)"] +tests = ["matplotlib (>=3.5.0)", "mypy (>=1.15)", "numpydoc (>=1.2.0)", "pandas (>=1.4.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pyamg (>=4.2.1)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.11.7)", "scikit-image (>=0.19.0)"] [[package]] name = "scipy" -version = "1.14.1" +version = "1.16.1" description = "Fundamental algorithms for scientific computing in Python" optional = false -python-versions = ">=3.10" +python-versions = ">=3.11" groups = ["main"] files = [ - {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, - {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, - {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"}, - {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"}, - {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"}, - {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"}, - {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"}, - {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"}, - {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"}, - {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"}, - {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"}, - {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"}, - {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"}, - {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"}, - {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"}, - {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"}, - {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"}, - {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"}, - {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"}, - {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"}, - {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"}, + {file = "scipy-1.16.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030"}, + {file = "scipy-1.16.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7"}, + {file = "scipy-1.16.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77"}, + {file = "scipy-1.16.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe"}, + {file = "scipy-1.16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b"}, + {file = "scipy-1.16.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7"}, + {file = "scipy-1.16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958"}, + {file = "scipy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39"}, + {file = "scipy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596"}, + {file = "scipy-1.16.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c"}, + {file = "scipy-1.16.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04"}, + {file = "scipy-1.16.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919"}, + {file = "scipy-1.16.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921"}, + {file = "scipy-1.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725"}, + {file = "scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618"}, + {file = "scipy-1.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d"}, + {file = "scipy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119"}, + {file = "scipy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a"}, + {file = "scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f"}, + {file = "scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb"}, + {file = "scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c"}, + {file = "scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608"}, + {file = "scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f"}, + {file = "scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b"}, + {file = "scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45"}, + {file = "scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65"}, + {file = "scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab"}, + {file = "scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6"}, + {file = "scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27"}, + {file = "scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7"}, + {file = "scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6"}, + {file = "scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4"}, + {file = "scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3"}, + {file = "scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7"}, + {file = "scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc"}, + {file = "scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39"}, + {file = "scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318"}, + {file = "scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc"}, + {file = "scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8"}, + {file = "scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e"}, + {file = "scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0"}, + {file = "scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b"}, + {file = "scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731"}, + {file = "scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3"}, + {file = "scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19"}, + {file = "scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65"}, + {file = "scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2"}, + {file = "scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d"}, + {file = "scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695"}, + {file = "scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86"}, + {file = "scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff"}, + {file = "scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4"}, + {file = "scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3"}, + {file = "scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998"}, + {file = "scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3"}, ] [package.dependencies] -numpy = ">=1.23.5,<2.3" +numpy = ">=1.25.2,<2.6" [package.extras] dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] -doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "linkify-it-py", "matplotlib (>=3.5)", "myst-nb (>=1.2.0)", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.2.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.3.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "setuptools" -version = "78.1.1" +version = "80.9.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"}, - {file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"}, + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, ] [package.extras] @@ -1170,97 +1231,96 @@ type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.deve [[package]] name = "shapely" -version = "2.0.6" +version = "2.1.1" description = "Manipulation and analysis of geometric objects" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "shapely-2.0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29a34e068da2d321e926b5073539fd2a1d4429a2c656bd63f0bd4c8f5b236d0b"}, - {file = "shapely-2.0.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c84c3f53144febf6af909d6b581bc05e8785d57e27f35ebaa5c1ab9baba13b"}, - {file = "shapely-2.0.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad2fae12dca8d2b727fa12b007e46fbc522148a584f5d6546c539f3464dccde"}, - {file = "shapely-2.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3304883bd82d44be1b27a9d17f1167fda8c7f5a02a897958d86c59ec69b705e"}, - {file = "shapely-2.0.6-cp310-cp310-win32.whl", hash = "sha256:3ec3a0eab496b5e04633a39fa3d5eb5454628228201fb24903d38174ee34565e"}, - {file = "shapely-2.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:28f87cdf5308a514763a5c38de295544cb27429cfa655d50ed8431a4796090c4"}, - {file = "shapely-2.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aeb0f51a9db176da9a30cb2f4329b6fbd1e26d359012bb0ac3d3c7781667a9e"}, - {file = "shapely-2.0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a7a78b0d51257a367ee115f4d41ca4d46edbd0dd280f697a8092dd3989867b2"}, - {file = "shapely-2.0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f32c23d2f43d54029f986479f7c1f6e09c6b3a19353a3833c2ffb226fb63a855"}, - {file = "shapely-2.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3dc9fb0eb56498912025f5eb352b5126f04801ed0e8bdbd867d21bdbfd7cbd0"}, - {file = "shapely-2.0.6-cp311-cp311-win32.whl", hash = "sha256:d93b7e0e71c9f095e09454bf18dad5ea716fb6ced5df3cb044564a00723f339d"}, - {file = "shapely-2.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:c02eb6bf4cfb9fe6568502e85bb2647921ee49171bcd2d4116c7b3109724ef9b"}, - {file = "shapely-2.0.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cec9193519940e9d1b86a3b4f5af9eb6910197d24af02f247afbfb47bcb3fab0"}, - {file = "shapely-2.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83b94a44ab04a90e88be69e7ddcc6f332da7c0a0ebb1156e1c4f568bbec983c3"}, - {file = "shapely-2.0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:537c4b2716d22c92036d00b34aac9d3775e3691f80c7aa517c2c290351f42cd8"}, - {file = "shapely-2.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fea108334be345c283ce74bf064fa00cfdd718048a8af7343c59eb40f59726"}, - {file = "shapely-2.0.6-cp312-cp312-win32.whl", hash = "sha256:42fd4cd4834747e4990227e4cbafb02242c0cffe9ce7ef9971f53ac52d80d55f"}, - {file = "shapely-2.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:665990c84aece05efb68a21b3523a6b2057e84a1afbef426ad287f0796ef8a48"}, - {file = "shapely-2.0.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:42805ef90783ce689a4dde2b6b2f261e2c52609226a0438d882e3ced40bb3013"}, - {file = "shapely-2.0.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6d2cb146191a47bd0cee8ff5f90b47547b82b6345c0d02dd8b25b88b68af62d7"}, - {file = "shapely-2.0.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3fdef0a1794a8fe70dc1f514440aa34426cc0ae98d9a1027fb299d45741c381"}, - {file = "shapely-2.0.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c665a0301c645615a107ff7f52adafa2153beab51daf34587170d85e8ba6805"}, - {file = "shapely-2.0.6-cp313-cp313-win32.whl", hash = "sha256:0334bd51828f68cd54b87d80b3e7cee93f249d82ae55a0faf3ea21c9be7b323a"}, - {file = "shapely-2.0.6-cp313-cp313-win_amd64.whl", hash = "sha256:d37d070da9e0e0f0a530a621e17c0b8c3c9d04105655132a87cfff8bd77cc4c2"}, - {file = "shapely-2.0.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fa7468e4f5b92049c0f36d63c3e309f85f2775752e076378e36c6387245c5462"}, - {file = "shapely-2.0.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed5867e598a9e8ac3291da6cc9baa62ca25706eea186117034e8ec0ea4355653"}, - {file = "shapely-2.0.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81d9dfe155f371f78c8d895a7b7f323bb241fb148d848a2bf2244f79213123fe"}, - {file = "shapely-2.0.6-cp37-cp37m-win32.whl", hash = "sha256:fbb7bf02a7542dba55129062570211cfb0defa05386409b3e306c39612e7fbcc"}, - {file = "shapely-2.0.6-cp37-cp37m-win_amd64.whl", hash = "sha256:837d395fac58aa01aa544495b97940995211e3e25f9aaf87bc3ba5b3a8cd1ac7"}, - {file = "shapely-2.0.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c6d88ade96bf02f6bfd667ddd3626913098e243e419a0325ebef2bbd481d1eb6"}, - {file = "shapely-2.0.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8b3b818c4407eaa0b4cb376fd2305e20ff6df757bf1356651589eadc14aab41b"}, - {file = "shapely-2.0.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbc783529a21f2bd50c79cef90761f72d41c45622b3e57acf78d984c50a5d13"}, - {file = "shapely-2.0.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2423f6c0903ebe5df6d32e0066b3d94029aab18425ad4b07bf98c3972a6e25a1"}, - {file = "shapely-2.0.6-cp38-cp38-win32.whl", hash = "sha256:2de00c3bfa80d6750832bde1d9487e302a6dd21d90cb2f210515cefdb616e5f5"}, - {file = "shapely-2.0.6-cp38-cp38-win_amd64.whl", hash = "sha256:3a82d58a1134d5e975f19268710e53bddd9c473743356c90d97ce04b73e101ee"}, - {file = "shapely-2.0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:392f66f458a0a2c706254f473290418236e52aa4c9b476a072539d63a2460595"}, - {file = "shapely-2.0.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eba5bae271d523c938274c61658ebc34de6c4b33fdf43ef7e938b5776388c1be"}, - {file = "shapely-2.0.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7060566bc4888b0c8ed14b5d57df8a0ead5c28f9b69fb6bed4476df31c51b0af"}, - {file = "shapely-2.0.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b02154b3e9d076a29a8513dffcb80f047a5ea63c897c0cd3d3679f29363cf7e5"}, - {file = "shapely-2.0.6-cp39-cp39-win32.whl", hash = "sha256:44246d30124a4f1a638a7d5419149959532b99dfa25b54393512e6acc9c211ac"}, - {file = "shapely-2.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:2b542d7f1dbb89192d3512c52b679c822ba916f93479fa5d4fc2fe4fa0b3c9e8"}, - {file = "shapely-2.0.6.tar.gz", hash = "sha256:997f6159b1484059ec239cacaa53467fd8b5564dabe186cd84ac2944663b0bf6"}, + {file = "shapely-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8ccc872a632acb7bdcb69e5e78df27213f7efd195882668ffba5405497337c6"}, + {file = "shapely-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f24f2ecda1e6c091da64bcbef8dd121380948074875bd1b247b3d17e99407099"}, + {file = "shapely-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45112a5be0b745b49e50f8829ce490eb67fefb0cea8d4f8ac5764bfedaa83d2d"}, + {file = "shapely-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c10ce6f11904d65e9bbb3e41e774903c944e20b3f0b282559885302f52f224a"}, + {file = "shapely-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:61168010dfe4e45f956ffbbaf080c88afce199ea81eb1f0ac43230065df320bd"}, + {file = "shapely-2.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cacf067cdff741cd5c56a21c52f54ece4e4dad9d311130493a791997da4a886b"}, + {file = "shapely-2.1.1-cp310-cp310-win32.whl", hash = "sha256:23b8772c3b815e7790fb2eab75a0b3951f435bc0fce7bb146cb064f17d35ab4f"}, + {file = "shapely-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:2c7b2b6143abf4fa77851cef8ef690e03feade9a0d48acd6dc41d9e0e78d7ca6"}, + {file = "shapely-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587a1aa72bc858fab9b8c20427b5f6027b7cbc92743b8e2c73b9de55aa71c7a7"}, + {file = "shapely-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fa5c53b0791a4b998f9ad84aad456c988600757a96b0a05e14bba10cebaaaea"}, + {file = "shapely-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aabecd038841ab5310d23495253f01c2a82a3aedae5ab9ca489be214aa458aa7"}, + {file = "shapely-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586f6aee1edec04e16227517a866df3e9a2e43c1f635efc32978bb3dc9c63753"}, + {file = "shapely-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b9878b9e37ad26c72aada8de0c9cfe418d9e2ff36992a1693b7f65a075b28647"}, + {file = "shapely-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9a531c48f289ba355e37b134e98e28c557ff13965d4653a5228d0f42a09aed0"}, + {file = "shapely-2.1.1-cp311-cp311-win32.whl", hash = "sha256:4866de2673a971820c75c0167b1f1cd8fb76f2d641101c23d3ca021ad0449bab"}, + {file = "shapely-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:20a9d79958b3d6c70d8a886b250047ea32ff40489d7abb47d01498c704557a93"}, + {file = "shapely-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2827365b58bf98efb60affc94a8e01c56dd1995a80aabe4b701465d86dcbba43"}, + {file = "shapely-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c551f7fa7f1e917af2347fe983f21f212863f1d04f08eece01e9c275903fad"}, + {file = "shapely-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78dec4d4fbe7b1db8dc36de3031767e7ece5911fb7782bc9e95c5cdec58fb1e9"}, + {file = "shapely-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:872d3c0a7b8b37da0e23d80496ec5973c4692920b90de9f502b5beb994bbaaef"}, + {file = "shapely-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e2b9125ebfbc28ecf5353511de62f75a8515ae9470521c9a693e4bb9fbe0cf1"}, + {file = "shapely-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4b96cea171b3d7f6786976a0520f178c42792897653ecca0c5422fb1e6946e6d"}, + {file = "shapely-2.1.1-cp312-cp312-win32.whl", hash = "sha256:39dca52201e02996df02e447f729da97cfb6ff41a03cb50f5547f19d02905af8"}, + {file = "shapely-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:13d643256f81d55a50013eff6321142781cf777eb6a9e207c2c9e6315ba6044a"}, + {file = "shapely-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3004a644d9e89e26c20286d5fdc10f41b1744c48ce910bd1867fdff963fe6c48"}, + {file = "shapely-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1415146fa12d80a47d13cfad5310b3c8b9c2aa8c14a0c845c9d3d75e77cb54f6"}, + {file = "shapely-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21fcab88b7520820ec16d09d6bea68652ca13993c84dffc6129dc3607c95594c"}, + {file = "shapely-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ce6a5cc52c974b291237a96c08c5592e50f066871704fb5b12be2639d9026a"}, + {file = "shapely-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:04e4c12a45a1d70aeb266618d8cf81a2de9c4df511b63e105b90bfdfb52146de"}, + {file = "shapely-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6ca74d851ca5264aae16c2b47e96735579686cb69fa93c4078070a0ec845b8d8"}, + {file = "shapely-2.1.1-cp313-cp313-win32.whl", hash = "sha256:fd9130501bf42ffb7e0695b9ea17a27ae8ce68d50b56b6941c7f9b3d3453bc52"}, + {file = "shapely-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:ab8d878687b438a2f4c138ed1a80941c6ab0029e0f4c785ecfe114413b498a97"}, + {file = "shapely-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c062384316a47f776305ed2fa22182717508ffdeb4a56d0ff4087a77b2a0f6d"}, + {file = "shapely-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4ecf6c196b896e8f1360cc219ed4eee1c1e5f5883e505d449f263bd053fb8c05"}, + {file = "shapely-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb00070b4c4860f6743c600285109c273cca5241e970ad56bb87bef0be1ea3a0"}, + {file = "shapely-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14a9afa5fa980fbe7bf63706fdfb8ff588f638f145a1d9dbc18374b5b7de913"}, + {file = "shapely-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b640e390dabde790e3fb947198b466e63223e0a9ccd787da5f07bcb14756c28d"}, + {file = "shapely-2.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:69e08bf9697c1b73ec6aa70437db922bafcea7baca131c90c26d59491a9760f9"}, + {file = "shapely-2.1.1-cp313-cp313t-win32.whl", hash = "sha256:ef2d09d5a964cc90c2c18b03566cf918a61c248596998a0301d5b632beadb9db"}, + {file = "shapely-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7"}, + {file = "shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772"}, ] [package.dependencies] -numpy = ">=1.14,<3" +numpy = ">=1.21" [package.extras] docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] -test = ["pytest", "pytest-cov"] +test = ["pytest", "pytest-cov", "scipy-doctest"] [[package]] name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +version = "3.0.1" +description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." optional = false -python-versions = "*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*" groups = ["dev"] files = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, + {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, + {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, ] [[package]] name = "soupsieve" -version = "2.6" +version = "2.7" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, - {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, + {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, + {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, ] [[package]] name = "sphinx" -version = "8.1.3" +version = "8.2.3" description = "Python documentation generator" optional = false -python-versions = ">=3.10" +python-versions = ">=3.11" groups = ["dev"] files = [ - {file = "sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2"}, - {file = "sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927"}, + {file = "sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3"}, + {file = "sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348"}, ] [package.dependencies] @@ -1273,6 +1333,7 @@ Jinja2 = ">=3.1" packaging = ">=23.0" Pygments = ">=2.17" requests = ">=2.30.0" +roman-numerals-py = ">=1.0.0" snowballstemmer = ">=2.2" sphinxcontrib-applehelp = ">=1.0.7" sphinxcontrib-devhelp = ">=1.0.6" @@ -1283,24 +1344,24 @@ sphinxcontrib-serializinghtml = ">=1.1.9" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=6.0)", "mypy (==1.11.1)", "pyright (==1.1.384)", "pytest (>=6.0)", "ruff (==0.6.9)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241005)", "types-requests (==2.32.0.20240914)", "types-urllib3 (==1.26.25.14)"] -test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] +lint = ["betterproto (==2.0.0b6)", "mypy (==1.15.0)", "pypi-attestations (==0.0.21)", "pyright (==1.1.395)", "pytest (>=8.0)", "ruff (==0.9.9)", "sphinx-lint (>=0.9)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.19.0.20250219)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241128)", "types-requests (==2.32.0.20241016)", "types-urllib3 (==1.26.25.14)"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "pytest-xdist[psutil] (>=3.4)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] [[package]] name = "sphinx-book-theme" -version = "1.1.3" +version = "1.1.4" description = "A clean book theme for scientific explanations and documentation with Sphinx" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "sphinx_book_theme-1.1.3-py3-none-any.whl", hash = "sha256:a554a9a7ac3881979a87a2b10f633aa2a5706e72218a10f71be38b3c9e831ae9"}, - {file = "sphinx_book_theme-1.1.3.tar.gz", hash = "sha256:1f25483b1846cb3d353a6bc61b3b45b031f4acf845665d7da90e01ae0aef5b4d"}, + {file = "sphinx_book_theme-1.1.4-py3-none-any.whl", hash = "sha256:843b3f5c8684640f4a2d01abd298beb66452d1b2394cd9ef5be5ebd5640ea0e1"}, + {file = "sphinx_book_theme-1.1.4.tar.gz", hash = "sha256:73efe28af871d0a89bd05856d300e61edce0d5b2fbb7984e84454be0fedfe9ed"}, ] [package.dependencies] -pydata-sphinx-theme = ">=0.15.2" -sphinx = ">=5" +pydata-sphinx-theme = "0.15.4" +sphinx = ">=6.1" [package.extras] code-style = ["pre-commit"] @@ -1328,24 +1389,23 @@ rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] [[package]] name = "sphinx-intl" -version = "2.3.1" +version = "2.3.2" description = "Sphinx utility that make it easy to translate and to apply translation." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "sphinx_intl-2.3.1-py3-none-any.whl", hash = "sha256:e3f4be80743e0e4c778fbc07e72b062c13c88c50645a69b62557a3f57a24b7fc"}, - {file = "sphinx_intl-2.3.1.tar.gz", hash = "sha256:e2d04b907f57a029d46b4da344fd791e660d935eab6ced8a274fc65446389af1"}, + {file = "sphinx_intl-2.3.2-py3-none-any.whl", hash = "sha256:f0082f9383066bab8406129a2ed531d21c38706d08467bf5ca3714e8914bb2be"}, + {file = "sphinx_intl-2.3.2.tar.gz", hash = "sha256:04b0d8ea04d111a7ba278b17b7b3fe9625c58b6f8ffb78bb8a1dd1288d88c1c7"}, ] [package.dependencies] -babel = "*" -click = "*" -setuptools = "*" +babel = ">=2.9.0" +click = ">=8.0.0" sphinx = "*" [package.extras] -test = ["pytest"] +test = ["pytest (>=8.3.5)"] [[package]] name = "sphinx-rtd-theme" @@ -1560,14 +1620,14 @@ tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version >= \"3.7\" and py [[package]] name = "threadpoolctl" -version = "3.5.0" +version = "3.6.0" description = "threadpoolctl" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"}, - {file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"}, + {file = "threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb"}, + {file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"}, ] [[package]] @@ -1626,38 +1686,38 @@ files = [ [[package]] name = "types-pyyaml" -version = "6.0.12.20240917" +version = "6.0.12.20250516" description = "Typing stubs for PyYAML" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"}, - {file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"}, + {file = "types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530"}, + {file = "types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba"}, ] [[package]] name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, ] [[package]] name = "urllib3" -version = "2.2.3" +version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] @@ -1684,4 +1744,4 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [metadata] lock-version = "2.1" python-versions = "^3.12" -content-hash = "39cb164105bc42d0880084e72c11894dfd5381d9f39a17beeda770b54e881949" +content-hash = "ca8c4ce733e53da7e9b9208c3341bcc8043863f16f8e4300e5cf2212495690c2" diff --git a/pyproject.toml b/pyproject.toml index ea94664..506d7f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ package-mode = true [tool.poetry.dependencies] python = "^3.12" -rcrs_core = { git = "https://github.com/adf-python/rcrs-core-python" } +rcrs_core = { git = "https://github.com/adf-python/rcrs-core-python", tag = "v0.1.0" } pyyaml = "^6.0.2" types-pyyaml = "^6.0.12.20240808" scikit-learn = "^1.5.2" From 1ddc2e837a144db283a3a37c19319806f1a09373 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 10:53:16 +0900 Subject: [PATCH 232/249] =?UTF-8?q?feat:=20=E3=83=90=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=82=920.1.4=E3=81=8B=E3=82=890.1.5?= =?UTF-8?q?=E3=81=AB=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 506d7f0..e2bb97b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "adf_core_python" -version = "0.1.4" +version = "0.1.5" description = "Agent Development Framework for Python" authors = [ "Haruki Uehara ", From 9477e587a22cf1a8204488fd89e4b5d80c1c7845 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 00:52:08 +0900 Subject: [PATCH 233/249] Update Python version and rcrs_core dependency in pyproject.toml - Bump Python version from 3.12 to 3.13 - Change rcrs_core dependency to point to the "improve" branch of the repository --- .../module/complex/sample_human_detector.py | 28 ++-- .../module/complex/sample_road_detector.py | 14 +- .../team_name/module/complex/sample_search.py | 7 +- adf_core_python/core/agent/action/action.py | 4 +- .../agent/action/ambulance/action_load.py | 11 +- .../agent/action/ambulance/action_rescue.py | 11 +- .../agent/action/ambulance/action_unload.py | 11 +- .../core/agent/action/common/action_move.py | 18 +-- .../core/agent/action/common/action_rest.py | 11 +- .../agent/action/fire/action_extinguish.py | 14 +- .../core/agent/action/fire/action_refill.py | 11 +- .../core/agent/action/fire/action_rescue.py | 11 +- .../core/agent/action/police/action_clear.py | 14 +- .../agent/action/police/action_clear_area.py | 11 +- adf_core_python/core/agent/agent.py | 138 ++++++++++-------- .../bundle/centralized/command_ambulance.py | 2 +- .../bundle/centralized/command_fire.py | 2 +- .../bundle/centralized/command_police.py | 2 +- .../bundle/centralized/command_scout.py | 2 +- .../bundle/centralized/message_report.py | 2 +- .../information/message_ambulance_team.py | 7 +- .../bundle/information/message_building.py | 6 +- .../bundle/information/message_civilian.py | 6 +- .../information/message_fire_brigade.py | 5 +- .../information/message_police_force.py | 5 +- .../bundle/information/message_road.py | 12 +- .../standard/bundle/standard_message.py | 2 +- .../standard/standard_communication_module.py | 4 +- .../standard/utility/apply_to_world_info.py | 20 +-- .../core/agent/config/module_config.py | 2 +- adf_core_python/core/agent/info/agent_info.py | 22 +-- adf_core_python/core/agent/info/world_info.py | 28 ++-- adf_core_python/core/agent/office/office.py | 2 +- .../core/agent/office/office_ambulance.py | 4 +- .../core/agent/office/office_fire.py | 2 +- .../core/agent/office/office_police.py | 2 +- adf_core_python/core/agent/platoon/platoon.py | 2 +- .../core/agent/platoon/platoon_ambulance.py | 2 +- .../core/agent/platoon/platoon_fire.py | 2 +- .../core/agent/platoon/platoon_police.py | 2 +- .../core/component/action/extend_action.py | 2 +- .../component/centralized/command_picker.py | 2 +- .../component/module/algorithm/clustering.py | 4 +- .../module/algorithm/path_planning.py | 2 +- .../complex/ambulance_target_allocator.py | 2 +- .../module/complex/fire_target_allocator.py | 2 +- .../module/complex/human_detector.py | 2 +- .../module/complex/police_target_allocator.py | 2 +- .../component/module/complex/road_detector.py | 2 +- .../core/component/module/complex/search.py | 2 +- .../module/complex/target_allocator.py | 2 +- .../module/complex/target_detector.py | 4 +- .../module/algorithm/gateway_clustering.py | 12 +- .../module/algorithm/gateway_path_planning.py | 8 +- .../module/complex/gateway_human_detector.py | 2 +- .../module/complex/gateway_road_detector.py | 2 +- .../complex/gateway_target_allocator.py | 2 +- .../module/complex/gateway_target_detector.py | 8 +- adf_core_python/core/gateway/gateway_agent.py | 9 +- .../core/gateway/gateway_module.py | 2 +- .../core/gateway/message/am_agent.py | 31 ++-- .../core/gateway/message/am_exec.py | 25 ++-- .../core/gateway/message/am_module.py | 23 ++- .../core/gateway/message/am_update.py | 31 ++-- .../core/gateway/message/ma_exec_response.py | 33 ++--- .../gateway/message/ma_module_response.py | 30 ++-- .../gateway/message/moduleMessageFactory.py | 4 +- adf_core_python/core/logger/logger.py | 6 +- .../action/default_extend_action_clear.py | 69 +++++---- .../action/default_extend_action_move.py | 14 +- .../action/default_extend_action_rescue.py | 25 ++-- .../action/default_extend_action_transport.py | 27 ++-- .../default_command_executor_ambulance.py | 7 +- .../default_command_executor_fire.py | 6 +- .../default_command_executor_police.py | 7 +- .../default_command_executor_scout.py | 14 +- .../default_command_executor_scout_police.py | 14 +- .../default_command_picker_ambulance.py | 9 +- .../default_command_picker_fire.py | 9 +- .../default_command_picker_police.py | 8 +- .../module/algorithm/a_star_path_planning.py | 8 +- .../algorithm/dijkstra_path_planning.py | 7 +- .../module/algorithm/k_means_clustering.py | 36 +++-- .../default_channel_subscriber.py | 6 +- .../default_message_coordinator.py | 4 +- .../default_ambulance_target_allocator.py | 19 +-- .../complex/default_fire_target_allocator.py | 19 +-- .../module/complex/default_human_detector.py | 26 ++-- .../default_police_target_allocator.py | 42 +++--- .../module/complex/default_road_detector.py | 14 +- .../module/complex/default_search.py | 7 +- .../default_tactics_ambulance_center.py | 2 +- .../tactics/default_tactics_ambulance_team.py | 2 +- .../tactics/default_tactics_fire_brigade.py | 2 +- .../tactics/default_tactics_fire_station.py | 2 +- .../tactics/default_tactics_police_force.py | 2 +- .../tactics/default_tactics_police_office.py | 2 +- poetry.lock | 18 +-- pyproject.toml | 4 +- 99 files changed, 533 insertions(+), 615 deletions(-) diff --git a/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py b/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py index 8b44545..1768cfc 100644 --- a/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py @@ -1,10 +1,7 @@ from typing import Optional, cast -from rcrs_core.connection.URN import Entity as EntityURN -from rcrs_core.entities.civilian import Civilian -from rcrs_core.entities.entity import Entity -from rcrs_core.entities.human import Human -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Civilian, Entity, EntityID, Human +from rcrscore.urn import EntityURN from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -46,7 +43,7 @@ def __init__( def calculate(self) -> HumanDetector: transport_human: Optional[Human] = self._agent_info.some_one_on_board() if transport_human is not None: - self._result = transport_human.get_id() + self._result = transport_human.get_entity_id() return self if self._result is not None: @@ -72,44 +69,45 @@ def _select_target(self) -> Optional[EntityID]: cluster_valid_human_entities: list[Entity] = [ entity for entity in cluster_entities - if self._is_valid_human(entity.get_id()) and isinstance(entity, Civilian) + if self._is_valid_human(entity.get_entity_id()) + and isinstance(entity, Civilian) ] if len(cluster_valid_human_entities) != 0: nearest_human_entity = cluster_valid_human_entities[0] nearest_distance = self._world_info.get_distance( self._agent_info.get_entity_id(), - nearest_human_entity.get_id(), + nearest_human_entity.get_entity_id(), ) for entity in cluster_valid_human_entities: distance = self._world_info.get_distance( self._agent_info.get_entity_id(), - entity.get_id(), + entity.get_entity_id(), ) if distance < nearest_distance: nearest_distance = distance nearest_human_entity = entity - return nearest_human_entity.get_id() + return nearest_human_entity.get_entity_id() world_valid_human_entities: list[Entity] = [ entity for entity in self._world_info.get_entities_of_types([Civilian]) - if self._is_valid_human(entity.get_id()) + if self._is_valid_human(entity.get_entity_id()) ] if len(world_valid_human_entities) != 0: nearest_human_entity = world_valid_human_entities[0] nearest_distance = self._world_info.get_distance( self._agent_info.get_entity_id(), - nearest_human_entity.get_id(), + nearest_human_entity.get_entity_id(), ) for entity in world_valid_human_entities: distance = self._world_info.get_distance( self._agent_info.get_entity_id(), - entity.get_id(), + entity.get_entity_id(), ) if distance < nearest_distance: nearest_distance = distance nearest_human_entity = entity - return nearest_human_entity.get_id() + return nearest_human_entity.get_entity_id() return None @@ -126,6 +124,8 @@ def _is_valid_human(self, target_entity_id: EntityID) -> bool: if buriedness is None: return False myself = self._agent_info.get_myself() + if myself is None: + return False if myself.get_urn() == EntityURN.FIRE_BRIGADE and buriedness == 0: return False if myself.get_urn() == EntityURN.AMBULANCE_TEAM and buriedness > 0: diff --git a/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py b/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py index 2f554db..87e188a 100644 --- a/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py @@ -1,10 +1,6 @@ from typing import Optional, cast -from rcrs_core.entities.building import Building -from rcrs_core.entities.gasStation import GasStation -from rcrs_core.entities.refuge import Refuge -from rcrs_core.entities.road import Road -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Building, EntityID, GasStation, Refuge, Road from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -59,7 +55,7 @@ def resume(self, precompute_data: PrecomputeData) -> RoadDetector: for entity in entities: if not isinstance(entity, Building): continue - for entity_id in entity.get_neighbours(): + for entity_id in entity.get_neighbors(): neighbor = self._world_info.get_entity(entity_id) if isinstance(neighbor, Road): self._target_areas.add(entity_id) @@ -68,7 +64,7 @@ def resume(self, precompute_data: PrecomputeData) -> RoadDetector: for entity in self._world_info.get_entities_of_types([Refuge]): if not isinstance(entity, Building): continue - for entity_id in entity.get_neighbours(): + for entity_id in entity.get_neighbors(): neighbor = self._world_info.get_entity(entity_id) if isinstance(neighbor, Road): self._priority_roads.add(entity_id) @@ -86,7 +82,7 @@ def prepare(self) -> RoadDetector: ) for entity in entities: building: Building = cast(Building, entity) - for entity_id in building.get_neighbours(): + for entity_id in building.get_neighbors(): neighbor = self._world_info.get_entity(entity_id) if isinstance(neighbor, Road): self._target_areas.add(entity_id) @@ -94,7 +90,7 @@ def prepare(self) -> RoadDetector: self._priority_roads = set() for entity in self._world_info.get_entities_of_types([Refuge]): refuge: Refuge = cast(Refuge, entity) - for entity_id in refuge.get_neighbours(): + for entity_id in refuge.get_neighbors(): neighbor = self._world_info.get_entity(entity_id) if isinstance(neighbor, Road): self._priority_roads.add(entity_id) diff --git a/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py b/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py index d495c49..a441a07 100644 --- a/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py @@ -1,9 +1,6 @@ from typing import Optional, cast -from rcrs_core.entities.building import Building -from rcrs_core.entities.entity import Entity -from rcrs_core.entities.refuge import Refuge -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Building, Entity, EntityID, Refuge from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -98,7 +95,7 @@ def _get_search_targets(self) -> set[EntityID]: cluster_index ) building_entity_ids: list[EntityID] = [ - entity.get_id() + entity.get_entity_id() for entity in cluster_entities if isinstance(entity, Building) and not isinstance(entity, Refuge) ] diff --git a/adf_core_python/core/agent/action/action.py b/adf_core_python/core/agent/action/action.py index c7ab198..911b028 100644 --- a/adf_core_python/core/agent/action/action.py +++ b/adf_core_python/core/agent/action/action.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.commands import Command +from rcrscore.entities import EntityID class Action(ABC): diff --git a/adf_core_python/core/agent/action/ambulance/action_load.py b/adf_core_python/core/agent/action/ambulance/action_load.py index c7ee299..1e9eee5 100644 --- a/adf_core_python/core/agent/action/ambulance/action_load.py +++ b/adf_core_python/core/agent/action/ambulance/action_load.py @@ -1,15 +1,8 @@ -from typing import TYPE_CHECKING - -from rcrs_core.commands.AKLoad import AKLoad -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.commands import AKLoad, Command +from rcrscore.entities import EntityID from adf_core_python.core.agent.action.action import Action -if TYPE_CHECKING: - from rcrs_core.commands.Command import Command - from rcrs_core.worldmodel.entityID import EntityID - class ActionLoad(Action): def __init__(self, target_id: EntityID) -> None: diff --git a/adf_core_python/core/agent/action/ambulance/action_rescue.py b/adf_core_python/core/agent/action/ambulance/action_rescue.py index e4bfb1d..e3ad8e1 100644 --- a/adf_core_python/core/agent/action/ambulance/action_rescue.py +++ b/adf_core_python/core/agent/action/ambulance/action_rescue.py @@ -1,15 +1,8 @@ -from typing import TYPE_CHECKING - -from rcrs_core.commands.AKRescue import AKRescue -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.commands import AKRescue, Command +from rcrscore.entities import EntityID from adf_core_python.core.agent.action.action import Action -if TYPE_CHECKING: - from rcrs_core.commands.Command import Command - from rcrs_core.worldmodel.entityID import EntityID - class ActionRescue(Action): def __init__(self, target_id: EntityID) -> None: diff --git a/adf_core_python/core/agent/action/ambulance/action_unload.py b/adf_core_python/core/agent/action/ambulance/action_unload.py index afe3f1a..2f725cb 100644 --- a/adf_core_python/core/agent/action/ambulance/action_unload.py +++ b/adf_core_python/core/agent/action/ambulance/action_unload.py @@ -1,15 +1,8 @@ -from typing import TYPE_CHECKING - -from rcrs_core.commands.AKUnload import AKUnload -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.commands import AKUnload, Command +from rcrscore.entities import EntityID from adf_core_python.core.agent.action.action import Action -if TYPE_CHECKING: - from rcrs_core.commands.Command import Command - from rcrs_core.worldmodel.entityID import EntityID - class ActionUnload(Action): def get_command(self, agent_id: EntityID, time: int) -> Command: diff --git a/adf_core_python/core/agent/action/common/action_move.py b/adf_core_python/core/agent/action/common/action_move.py index 2baaba4..c2829a8 100644 --- a/adf_core_python/core/agent/action/common/action_move.py +++ b/adf_core_python/core/agent/action/common/action_move.py @@ -1,15 +1,10 @@ -from typing import TYPE_CHECKING, Optional +from typing import Optional -from rcrs_core.commands.AKMove import AKMove -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.commands import AKMove, Command +from rcrscore.entities import EntityID from adf_core_python.core.agent.action.action import Action -if TYPE_CHECKING: - from rcrs_core.commands.Command import Command - from rcrs_core.worldmodel.entityID import EntityID - class ActionMove(Action): def __init__( @@ -32,11 +27,12 @@ def get_destination_y(self) -> Optional[int]: return self.destination_y def get_command(self, agent_id: EntityID, time: int) -> Command: - path: list[int] = [p.get_value() for p in self.path] if self.destination_x is not None and self.destination_y is not None: - return AKMove(agent_id, time, path, self.destination_x, self.destination_y) + return AKMove( + agent_id, time, self.path, self.destination_x, self.destination_y + ) else: - return AKMove(agent_id, time, path) + return AKMove(agent_id, time, self.path) def __str__(self) -> str: path: str = ", ".join([str(p) for p in self.path]) diff --git a/adf_core_python/core/agent/action/common/action_rest.py b/adf_core_python/core/agent/action/common/action_rest.py index ab86c82..94bfaea 100644 --- a/adf_core_python/core/agent/action/common/action_rest.py +++ b/adf_core_python/core/agent/action/common/action_rest.py @@ -1,15 +1,8 @@ -from typing import TYPE_CHECKING - -from rcrs_core.commands.AKRest import AKRest -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.commands import AKRest, Command +from rcrscore.entities import EntityID from adf_core_python.core.agent.action.action import Action -if TYPE_CHECKING: - from rcrs_core.commands.Command import Command - from rcrs_core.worldmodel.entityID import EntityID - class ActionRest(Action): def get_command(self, agent_id: EntityID, time: int) -> Command: diff --git a/adf_core_python/core/agent/action/fire/action_extinguish.py b/adf_core_python/core/agent/action/fire/action_extinguish.py index 296bef6..64713a9 100644 --- a/adf_core_python/core/agent/action/fire/action_extinguish.py +++ b/adf_core_python/core/agent/action/fire/action_extinguish.py @@ -1,15 +1,8 @@ -from typing import TYPE_CHECKING - -from rcrs_core.commands.AKExtinguish import AKExtinguish -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.commands import AKExtinguish, Command +from rcrscore.entities import EntityID from adf_core_python.core.agent.action.action import Action -if TYPE_CHECKING: - from rcrs_core.commands.Command import Command - from rcrs_core.worldmodel.entityID import EntityID - class ActionExtinguish(Action): def __init__(self, target_id: EntityID, max_power: int) -> None: @@ -17,8 +10,7 @@ def __init__(self, target_id: EntityID, max_power: int) -> None: self.max_power = max_power def get_command(self, agent_id: EntityID, time: int) -> Command: - # TODO: Implement AKEExtinguish - return AKExtinguish() + return AKExtinguish(agent_id, time, self.target_id, self.max_power) def __str__(self) -> str: return ( diff --git a/adf_core_python/core/agent/action/fire/action_refill.py b/adf_core_python/core/agent/action/fire/action_refill.py index bae2cc8..15c0242 100644 --- a/adf_core_python/core/agent/action/fire/action_refill.py +++ b/adf_core_python/core/agent/action/fire/action_refill.py @@ -1,15 +1,8 @@ -from typing import TYPE_CHECKING - -from rcrs_core.commands.AKRest import AKRest -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.commands import AKRest, Command +from rcrscore.entities import EntityID from adf_core_python.core.agent.action.action import Action -if TYPE_CHECKING: - from rcrs_core.commands.Command import Command - from rcrs_core.worldmodel.entityID import EntityID - class ActionRefill(Action): def get_command(self, agent_id: EntityID, time: int) -> Command: diff --git a/adf_core_python/core/agent/action/fire/action_rescue.py b/adf_core_python/core/agent/action/fire/action_rescue.py index e4bfb1d..e3ad8e1 100644 --- a/adf_core_python/core/agent/action/fire/action_rescue.py +++ b/adf_core_python/core/agent/action/fire/action_rescue.py @@ -1,15 +1,8 @@ -from typing import TYPE_CHECKING - -from rcrs_core.commands.AKRescue import AKRescue -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.commands import AKRescue, Command +from rcrscore.entities import EntityID from adf_core_python.core.agent.action.action import Action -if TYPE_CHECKING: - from rcrs_core.commands.Command import Command - from rcrs_core.worldmodel.entityID import EntityID - class ActionRescue(Action): def __init__(self, target_id: EntityID) -> None: diff --git a/adf_core_python/core/agent/action/police/action_clear.py b/adf_core_python/core/agent/action/police/action_clear.py index b343336..6ba35d3 100644 --- a/adf_core_python/core/agent/action/police/action_clear.py +++ b/adf_core_python/core/agent/action/police/action_clear.py @@ -1,23 +1,15 @@ -from typing import TYPE_CHECKING - -from rcrs_core.commands.AKClear import AKClear -from rcrs_core.commands.Command import Command -from rcrs_core.entities.blockade import Blockade -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.commands import AKClear, Command +from rcrscore.entities import Blockade, EntityID from adf_core_python.core.agent.action.action import Action -if TYPE_CHECKING: - from rcrs_core.commands.Command import Command - from rcrs_core.worldmodel.entityID import EntityID - class ActionClear(Action): def __init__(self, blockade: Blockade) -> None: self.blockade = blockade def get_command(self, agent_id: EntityID, time: int) -> Command: - return AKClear(agent_id, time, self.blockade.get_id()) + return AKClear(agent_id, time, self.blockade.get_entity_id()) def __str__(self) -> str: return f"ActionClear(blockade={self.blockade})" diff --git a/adf_core_python/core/agent/action/police/action_clear_area.py b/adf_core_python/core/agent/action/police/action_clear_area.py index 2531dd5..be92c8a 100644 --- a/adf_core_python/core/agent/action/police/action_clear_area.py +++ b/adf_core_python/core/agent/action/police/action_clear_area.py @@ -1,15 +1,8 @@ -from typing import TYPE_CHECKING - -from rcrs_core.commands.AKClearArea import AKClearArea -from rcrs_core.commands.Command import Command -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.commands import AKClearArea, Command +from rcrscore.entities import EntityID from adf_core_python.core.agent.action.action import Action -if TYPE_CHECKING: - from rcrs_core.commands.Command import Command - from rcrs_core.worldmodel.entityID import EntityID - class ActionClearArea(Action): def __init__(self, position_x: int, position_y: int) -> None: diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index b18fb67..e3409c4 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -5,32 +5,49 @@ from typing import Any, Callable, NoReturn, Optional from bitarray import bitarray -from rcrs_core.commands.AKClear import AKClear -from rcrs_core.commands.AKClearArea import AKClearArea -from rcrs_core.commands.AKLoad import AKLoad -from rcrs_core.commands.AKMove import AKMove -from rcrs_core.commands.AKRescue import AKRescue -from rcrs_core.commands.AKRest import AKRest -from rcrs_core.commands.AKSay import AKSay -from rcrs_core.commands.AKSpeak import AKSpeak -from rcrs_core.commands.AKSubscribe import AKSubscribe -from rcrs_core.commands.AKTell import AKTell -from rcrs_core.commands.AKUnload import AKUnload -from rcrs_core.commands.Command import Command -from rcrs_core.config.config import Config as RCRSConfig -from rcrs_core.connection.URN import Command as CommandURN -from rcrs_core.connection.URN import ComponentCommand as ComponentCommandMessageID -from rcrs_core.connection.URN import ComponentControlMSG as ComponentControlMessageID -from rcrs_core.connection.URN import Entity as EntityURN -from rcrs_core.messages.AKAcknowledge import AKAcknowledge -from rcrs_core.messages.AKConnect import AKConnect -from rcrs_core.messages.controlMessageFactory import ControlMessageFactory -from rcrs_core.messages.KAConnectError import KAConnectError -from rcrs_core.messages.KAConnectOK import KAConnectOK -from rcrs_core.messages.KASense import KASense -from rcrs_core.worldmodel.changeSet import ChangeSet -from rcrs_core.worldmodel.entityID import EntityID -from rcrs_core.worldmodel.worldmodel import WorldModel +from rcrscore.commands import ( + AKClear, + AKClearArea, + AKLoad, + AKMove, + AKRescue, + AKRest, + AKSay, + AKSpeak, + AKSubscribe, + AKTell, + AKUnload, + Command, +) +from rcrscore.config.config import Config as RCRSConfig + +# from rcrscore.connection.URN import Command as CommandURN +# from rcrscore.connection.URN import ComponentCommand as ComponentCommandMessageID +# from rcrscore.connection.URN import ComponentControlMSG as ComponentControlMessageID +# from rcrscore.connection.URN import Entity as EntityURN +from rcrscore.entities import EntityID + +# from rcrscore.messages.AKAcknowledge import AKAcknowledge +# from rcrscore.messages.AKConnect import AKConnect +# from rcrscore.messages.controlMessageFactory import ControlMessageFactory +# from rcrscore.messages.KAConnectError import KAConnectError +# from rcrscore.messages.KAConnectOK import KAConnectOK +# from rcrscore.messages.KASense import KASense +from rcrscore.messages import ( + AKAcknowledge, + AKConnect, + ControlMessageFactory, + KAConnectError, + KAConnectOK, + KASense, +) +from rcrscore.urn import ( + CommandURN, + ComponentCommandURN, + ComponentControlMessageURN, + EntityURN, +) +from rcrscore.worldmodel import ChangeSet, WorldModel from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( @@ -229,7 +246,9 @@ def get_requested_entities(self) -> list[EntityURN]: def start_up(self, request_id: int) -> None: ak_connect = AKConnect() - self.send_msg(ak_connect.write(request_id, self)) + self.send_msg( + ak_connect.write(request_id, self.name, self.get_requested_entities()) + ) def message_received(self, msg: Any) -> None: c_msg = ControlMessageFactory().make_message(msg) @@ -264,14 +283,7 @@ def handle_connect_ok(self, msg: Any) -> None: if config is not None: for key, value in config.data.items(): self.config.set_value(key, value) - for key, value in config.int_data.items(): - self.config.set_value(key, value) - for key, value in config.float_data.items(): - self.config.set_value(key, value) - for key, value in config.boolean_data.items(): - self.config.set_value(key, value) - for key, value in config.array_data.items(): - self.config.set_value(key, value) + self.send_acknowledge(msg.request_id) self.post_connect() self.logger.info( @@ -284,8 +296,8 @@ def handle_connect_ok(self, msg: Any) -> None: self.finish_post_connect_event.set() - def handler_sense(self, msg: Any) -> None: - _id = EntityID(msg.agent_id) + def handler_sense(self, msg: KASense) -> None: + _id = msg.agent_id time = msg.time change_set = msg.change_set heard = msg.hear.commands @@ -295,24 +307,20 @@ def handler_sense(self, msg: Any) -> None: return heard_commands: list[Command] = [] - for herad_command in heard: - if herad_command.urn == CommandURN.AK_SPEAK: + for heard_command in heard: + if heard_command.urn == CommandURN.AK_SPEAK: heard_commands.append( AKSpeak( EntityID( - herad_command.components[ - ComponentControlMessageID.AgentID + heard_command.components[ + ComponentControlMessageURN.AgentID ].entityID ), - herad_command.components[ - ComponentControlMessageID.Time - ].intValue, - herad_command.components[ - ComponentCommandMessageID.Message - ].rawData, - herad_command.components[ - ComponentCommandMessageID.Channel + heard_command.components[ + ComponentControlMessageURN.Time ].intValue, + heard_command.components[ComponentCommandURN.Message].rawData, + heard_command.components[ComponentCommandURN.Channel].intValue, ) ) self.world_model.merge(change_set) @@ -328,55 +336,57 @@ def send_acknowledge(self, request_id: int) -> None: def send_clear(self, time: int, target: EntityID) -> None: cmd = AKClear(self.get_entity_id(), time, target) - msg = cmd.prepare_cmd() + msg = cmd.to_message_proto() self.send_msg(msg) def send_clear_area(self, time: int, x: int = -1, y: int = -1) -> None: cmd = AKClearArea(self.get_entity_id(), time, x, y) - msg = cmd.prepare_cmd() + msg = cmd.to_message_proto() self.send_msg(msg) def send_load(self, time: int, target: EntityID) -> None: cmd = AKLoad(self.get_entity_id(), time, target) - msg = cmd.prepare_cmd() + msg = cmd.to_message_proto() self.send_msg(msg) - def send_move(self, time: int, path: list[int], x: int = -1, y: int = -1) -> None: - cmd = AKMove(self.get_entity_id(), time, path[:], x, y) - msg = cmd.prepare_cmd() + def send_move( + self, time: int, path: list[EntityID], x: int = -1, y: int = -1 + ) -> None: + cmd = AKMove(self.get_entity_id(), time, path, x, y) + msg = cmd.to_message_proto() self.send_msg(msg) def send_rescue(self, time: int, target: EntityID) -> None: cmd = AKRescue(self.get_entity_id(), time, target) - msg = cmd.prepare_cmd() + msg = cmd.to_message_proto() self.send_msg(msg) def send_rest(self, time: int) -> None: cmd = AKRest(self.get_entity_id(), time) - msg = cmd.prepare_cmd() + msg = cmd.to_message_proto() self.send_msg(msg) - def send_say(self, time_step: int, message: str) -> None: + def send_say(self, time_step: int, message: bytes) -> None: cmd = AKSay(self.get_entity_id(), time_step, message) - msg = cmd.prepare_cmd() + msg = cmd.to_message_proto() self.send_msg(msg) def send_speak(self, time_step: int, message: bitarray, channel: int) -> None: cmd = AKSpeak(self.get_entity_id(), time_step, bytes(message), channel) # type: ignore - msg = cmd.prepare_cmd() + msg = cmd.to_message_proto() self.send_msg(msg) def send_subscribe(self, time: int, channels: list[int]) -> None: cmd = AKSubscribe(self.get_entity_id(), time, channels) - msg = cmd.prepare_cmd() + msg = cmd.to_message_proto() self.send_msg(msg) - def send_tell(self, time: int, message: str) -> None: + def send_tell(self, time: int, message: bytes) -> None: cmd = AKTell(self.get_entity_id(), time, message) - msg = cmd.prepare_cmd() + msg = cmd.to_message_proto() self.send_msg(msg) def send_unload(self, time: int) -> None: cmd = AKUnload(self.get_entity_id(), time) - msg = cmd.prepare_cmd() + msg = cmd.to_message_proto() self.send_msg(msg) diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py index a5d6984..1a0a2f0 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py +++ b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py @@ -3,7 +3,7 @@ from typing import Optional from bitarray import bitarray -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( StandardMessage, diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py index cf61131..a4bd8ae 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py +++ b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py @@ -3,7 +3,7 @@ from typing import Optional from bitarray import bitarray -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( StandardMessage, diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py index 64b8e34..c57f2fb 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py +++ b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py @@ -3,7 +3,7 @@ from typing import Optional from bitarray import bitarray -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( StandardMessage, diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py index 138a1dd..4d82478 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py +++ b/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py @@ -3,7 +3,7 @@ from typing import Optional from bitarray import bitarray -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( StandardMessage, diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py b/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py index 55f8907..99cf589 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py +++ b/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py @@ -1,7 +1,7 @@ from __future__ import annotations from bitarray import bitarray -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( StandardMessage, diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py index 3a74aba..2fbe0ea 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py @@ -3,8 +3,7 @@ from typing import Optional from bitarray import bitarray -from rcrs_core.entities.ambulanceTeam import AmbulanceTeam -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import AmbulanceTeam, EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( StandardMessage, @@ -44,7 +43,9 @@ def __init__( ttl: Optional[int] = None, ): super().__init__(is_wireless_message, priority, sender_entity_id, ttl) - self._ambulance_team_entity_id: Optional[EntityID] = ambulance_team.get_id() + self._ambulance_team_entity_id: Optional[EntityID] = ( + ambulance_team.get_entity_id() + ) self._ambulance_team_hp: Optional[int] = ambulance_team.get_hp() or None self._ambulance_team_buriedness: Optional[int] = ( ambulance_team.get_buriedness() or None diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py index be9f911..586f836 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py @@ -3,8 +3,8 @@ from typing import Optional from bitarray import bitarray -from rcrs_core.entities.building import Building -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID +from rcrscore.entities.building import Building from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( StandardMessage, @@ -33,7 +33,7 @@ def __init__( ttl: Optional[int] = None, ): super().__init__(is_wireless_message, priority, sender_entity_id, ttl) - self._building_entity_id: Optional[EntityID] = building.get_id() + self._building_entity_id: Optional[EntityID] = building.get_entity_id() self._building_brokenness: Optional[int] = building.get_brokenness() or None self._building_fireyness: Optional[int] = building.get_fieryness() or None self._building_temperature: Optional[int] = building.get_temperature() or None diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py index a5a62b4..79b4ae7 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py @@ -3,8 +3,8 @@ from typing import Optional from bitarray import bitarray -from rcrs_core.entities.civilian import Civilian -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID +from rcrscore.entities.civilian import Civilian from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( StandardMessage, @@ -34,7 +34,7 @@ def __init__( ttl: Optional[int] = None, ): super().__init__(is_wireless_message, priority, sender_entity_id, ttl) - self._civilian_entity_id: Optional[EntityID] = civilian.get_id() + self._civilian_entity_id: Optional[EntityID] = civilian.get_entity_id() self._civilian_hp: Optional[int] = civilian.get_hp() or None self._civilian_buriedness: Optional[int] = civilian.get_buriedness() or None self._civilian_damage: Optional[int] = civilian.get_damage() or None diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py index feefd51..0f41702 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py @@ -3,8 +3,7 @@ from typing import Optional from bitarray import bitarray -from rcrs_core.entities.fireBrigade import FireBrigade -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID, FireBrigade from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( StandardMessage, @@ -45,7 +44,7 @@ def __init__( ttl: Optional[int] = None, ): super().__init__(is_wireless_message, priority, sender_entity_id, ttl) - self._fire_brigade_entity_id: Optional[EntityID] = fire_brigade.get_id() + self._fire_brigade_entity_id: Optional[EntityID] = fire_brigade.get_entity_id() self._fire_brigade_hp: Optional[int] = fire_brigade.get_hp() or None self._fire_brigade_buriedness: Optional[int] = ( fire_brigade.get_buriedness() or None diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py index f37432d..f4b72d0 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py @@ -3,8 +3,7 @@ from typing import Optional from bitarray import bitarray -from rcrs_core.entities.policeForce import PoliceForce -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID, PoliceForce from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( StandardMessage, @@ -42,7 +41,7 @@ def __init__( ttl: Optional[int] = None, ): super().__init__(is_wireless_message, priority, sender_entity_id, ttl) - self._police_force_entity_id: Optional[EntityID] = police_force.get_id() + self._police_force_entity_id: Optional[EntityID] = police_force.get_entity_id() self._police_force_hp: Optional[int] = police_force.get_hp() or None self._police_force_buriedness: Optional[int] = ( police_force.get_buriedness() or None diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py b/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py index 474421c..747bfb5 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py +++ b/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py @@ -3,9 +3,7 @@ from typing import Optional from bitarray import bitarray -from rcrs_core.entities.blockade import Blockade -from rcrs_core.entities.road import Road -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Blockade, EntityID, Road from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( StandardMessage, @@ -43,15 +41,15 @@ def __init__( ttl: Optional[int] = None, ): super().__init__(is_wireless_message, priority, sender_entity_id, ttl) - self._road_entity_id: Optional[EntityID] = road.get_id() + self._road_entity_id: Optional[EntityID] = road.get_entity_id() self._road_blockade_entity_id: Optional[EntityID] = None self._road_blockade_repair_cost: Optional[int] = None self._road_blockade_x: Optional[int] = None self._road_blockade_y: Optional[int] = None if blockade: - self._road_blockade_entity_id = blockade.get_id() - self._road_blockade_repair_cost = blockade.get_repaire_cost() + self._road_blockade_entity_id = blockade.get_entity_id() + self._road_blockade_repair_cost = blockade.get_repair_cost() if is_send_blockade_location: self._road_blockade_x = blockade.get_x() or None self._road_blockade_y = blockade.get_y() or None @@ -146,7 +144,7 @@ def from_bits( ) road = Road(road_id or -1) blockade = Blockade(road_blockade_id or -1) - blockade.set_repaire_cost(road_blockade_repair_cost) + blockade.set_repair_cost(road_blockade_repair_cost) blockade.set_x(road_blockade_x) blockade.set_y(road_blockade_y) return MessageRoad( diff --git a/adf_core_python/core/agent/communication/standard/bundle/standard_message.py b/adf_core_python/core/agent/communication/standard/bundle/standard_message.py index bdec11b..34ec8f9 100644 --- a/adf_core_python/core/agent/communication/standard/bundle/standard_message.py +++ b/adf_core_python/core/agent/communication/standard/bundle/standard_message.py @@ -3,7 +3,7 @@ from typing import Optional from bitarray import bitarray -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( StandardMessagePriority, diff --git a/adf_core_python/core/agent/communication/standard/standard_communication_module.py b/adf_core_python/core/agent/communication/standard/standard_communication_module.py index 3fdffb3..f502651 100644 --- a/adf_core_python/core/agent/communication/standard/standard_communication_module.py +++ b/adf_core_python/core/agent/communication/standard/standard_communication_module.py @@ -3,8 +3,8 @@ from typing import TYPE_CHECKING from bitarray import bitarray -from rcrs_core.commands.AKSpeak import AKSpeak -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.commands import AKSpeak +from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( diff --git a/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py b/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py index c1e7020..56b1f8f 100644 --- a/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py +++ b/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py @@ -1,10 +1,12 @@ -from rcrs_core.entities.ambulanceTeam import AmbulanceTeam -from rcrs_core.entities.blockade import Blockade -from rcrs_core.entities.building import Building -from rcrs_core.entities.civilian import Civilian -from rcrs_core.entities.fireBrigade import FireBrigade -from rcrs_core.entities.policeForce import PoliceForce -from rcrs_core.entities.road import Road +from rcrscore.entities import ( + AmbulanceTeam, + Blockade, + Building, + Civilian, + FireBrigade, + PoliceForce, + Road, +) from adf_core_python.core.agent.communication.standard.bundle.information.message_ambulance_team import ( MessageAmbulanceTeam, @@ -317,7 +319,7 @@ def _apply_to_world_info_road( if blockade is None: road_blockade = Blockade(blockade_entity_id.get_value()) if (repair_cost := message_road.get_road_blockade_repair_cost()) is not None: - road_blockade.set_repaire_cost(repair_cost) + road_blockade.set_repair_cost(repair_cost) if (x := message_road.get_road_blockade_x()) is not None: road_blockade.set_x(x) if (y := message_road.get_road_blockade_y()) is not None: @@ -328,7 +330,7 @@ def _apply_to_world_info_road( if ( repair_cost := message_road.get_road_blockade_repair_cost() ) is not None: - blockade.set_repaire_cost(repair_cost) + blockade.set_repair_cost(repair_cost) if (x := message_road.get_road_blockade_x()) is not None: blockade.set_x(x) if (y := message_road.get_road_blockade_y()) is not None: diff --git a/adf_core_python/core/agent/config/module_config.py b/adf_core_python/core/agent/config/module_config.py index 72a292a..7c65649 100644 --- a/adf_core_python/core/agent/config/module_config.py +++ b/adf_core_python/core/agent/config/module_config.py @@ -1,6 +1,6 @@ from typing import Any, Final -from rcrs_core.config.config import Config +from rcrscore.config.config import Config from yaml import safe_load diff --git a/adf_core_python/core/agent/info/agent_info.py b/adf_core_python/core/agent/info/agent_info.py index 23f6657..5dea6a5 100644 --- a/adf_core_python/core/agent/info/agent_info.py +++ b/adf_core_python/core/agent/info/agent_info.py @@ -3,13 +3,12 @@ from time import time from typing import TYPE_CHECKING -from rcrs_core.commands.Command import Command -from rcrs_core.entities.civilian import Civilian -from rcrs_core.entities.entity import Entity -from rcrs_core.entities.human import Human -from rcrs_core.worldmodel.changeSet import ChangeSet -from rcrs_core.worldmodel.entityID import EntityID -from rcrs_core.worldmodel.worldmodel import WorldModel +from rcrscore.commands import Command +from rcrscore.entities import EntityID +from rcrscore.entities.civilian import Civilian +from rcrscore.entities.entity import Entity +from rcrscore.entities.human import Human +from rcrscore.worldmodel import ChangeSet, WorldModel from adf_core_python.core.agent.action.action import Action @@ -83,7 +82,7 @@ def get_entity_id(self) -> EntityID: # TODO: Agent class should return EntityID instead of EntityID | None return self._agent.get_entity_id() - def get_myself(self) -> Entity: + def get_myself(self) -> Entity | None: """ Get the entity of the agent @@ -94,7 +93,7 @@ def get_myself(self) -> Entity: """ return self._world_model.get_entity(self.get_entity_id()) - def get_position_entity_id(self) -> EntityID: + def get_position_entity_id(self) -> EntityID | None: """ Get the position entity ID of the agent @@ -104,10 +103,13 @@ def get_position_entity_id(self) -> EntityID: Position entity ID of the agent """ entity = self._world_model.get_entity(self.get_entity_id()) + if entity is None: + return None + if isinstance(entity, Human): return entity.get_position() else: - return entity.get_id() + return entity.get_entity_id() def set_change_set(self, change_set: ChangeSet) -> None: """ diff --git a/adf_core_python/core/agent/info/world_info.py b/adf_core_python/core/agent/info/world_info.py index c4dec84..8ac55c9 100644 --- a/adf_core_python/core/agent/info/world_info.py +++ b/adf_core_python/core/agent/info/world_info.py @@ -1,12 +1,11 @@ from typing import Any, Optional, cast -from rcrs_core.entities.area import Area -from rcrs_core.entities.blockade import Blockade -from rcrs_core.entities.entity import Entity -from rcrs_core.entities.human import Human -from rcrs_core.worldmodel.changeSet import ChangeSet -from rcrs_core.worldmodel.entityID import EntityID -from rcrs_core.worldmodel.worldmodel import WorldModel +from rcrscore.entities import EntityID +from rcrscore.entities.area import Area +from rcrscore.entities.blockade import Blockade +from rcrscore.entities.entity import Entity +from rcrscore.entities.human import Human +from rcrscore.worldmodel import ChangeSet, WorldModel class WorldInfo: @@ -74,7 +73,7 @@ def get_entity_ids_of_types( entity_ids: list[EntityID] = [] for entity in self._world_model.get_entities(): if any(isinstance(entity, entity_type) for entity_type in entity_types): - entity_ids.append(entity.get_id()) + entity_ids.append(entity.get_entity_id()) return entity_ids @@ -145,7 +144,7 @@ def get_distance(self, entity_id1: EntityID, entity_id2: EntityID) -> float: return distance - def get_entity_position(self, entity_id: EntityID) -> EntityID: + def get_entity_position(self, entity_id: EntityID) -> EntityID | None: """ Get the entity position @@ -168,7 +167,7 @@ def get_entity_position(self, entity_id: EntityID) -> EntityID: if entity is None: raise ValueError(f"Invalid entity: entity_id={entity_id}, entity={entity}") if isinstance(entity, Area): - return entity.get_id() + return entity.get_entity_id() if isinstance(entity, Human): return entity.get_position() if isinstance(entity, Blockade): @@ -196,10 +195,11 @@ def get_blockades(self, area: Area) -> set[Blockade]: Blockade """ blockades = set() - for blockade_entity_id in area.get_blockades(): - blockades_entity = self.get_entity(blockade_entity_id) - if isinstance(blockades_entity, Blockade): - blockades.add(cast(Blockade, blockades_entity)) + if blockade_entity_ids := area.get_blockades(): + for blockade_entity_id in blockade_entity_ids: + blockades_entity = self.get_entity(blockade_entity_id) + if isinstance(blockades_entity, Blockade): + blockades.add(cast(Blockade, blockades_entity)) return blockades def add_entity(self, entity: Entity) -> None: diff --git a/adf_core_python/core/agent/office/office.py b/adf_core_python/core/agent/office/office.py index ae06c87..ea9a3b8 100644 --- a/adf_core_python/core/agent/office/office.py +++ b/adf_core_python/core/agent/office/office.py @@ -129,5 +129,5 @@ def think(self) -> None: self.send_msg( ActionRest() .get_command(self.agent_id, self._agent_info.get_time()) - .prepare_cmd() + .to_message_proto() ) diff --git a/adf_core_python/core/agent/office/office_ambulance.py b/adf_core_python/core/agent/office/office_ambulance.py index f746fd5..1df7e5e 100644 --- a/adf_core_python/core/agent/office/office_ambulance.py +++ b/adf_core_python/core/agent/office/office_ambulance.py @@ -1,7 +1,7 @@ from threading import Event from typing import Optional -from rcrs_core.connection.URN import Entity as EntityURN +from rcrscore.urn import EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -39,4 +39,4 @@ def precompute(self) -> None: pass def get_requested_entities(self) -> list[EntityURN]: - return [EntityURN.AMBULANCE_CENTRE] + return [EntityURN.AMBULANCE_CENTER] diff --git a/adf_core_python/core/agent/office/office_fire.py b/adf_core_python/core/agent/office/office_fire.py index 6128edd..766e35a 100644 --- a/adf_core_python/core/agent/office/office_fire.py +++ b/adf_core_python/core/agent/office/office_fire.py @@ -1,7 +1,7 @@ from threading import Event from typing import Optional -from rcrs_core.connection.URN import Entity as EntityURN +from rcrscore.urn import EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/adf_core_python/core/agent/office/office_police.py b/adf_core_python/core/agent/office/office_police.py index 9717ac2..67d4cc3 100644 --- a/adf_core_python/core/agent/office/office_police.py +++ b/adf_core_python/core/agent/office/office_police.py @@ -1,7 +1,7 @@ from threading import Event from typing import Optional -from rcrs_core.connection.URN import Entity as EntityURN +from rcrscore.urn import EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py index 2f2781c..8e9788c 100644 --- a/adf_core_python/core/agent/platoon/platoon.py +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -133,5 +133,5 @@ def think(self) -> None: self.send_msg( action.get_command( self.agent_id, self._agent_info.get_time() - ).prepare_cmd() + ).to_message_proto() ) diff --git a/adf_core_python/core/agent/platoon/platoon_ambulance.py b/adf_core_python/core/agent/platoon/platoon_ambulance.py index 0daad4a..16a1127 100644 --- a/adf_core_python/core/agent/platoon/platoon_ambulance.py +++ b/adf_core_python/core/agent/platoon/platoon_ambulance.py @@ -1,7 +1,7 @@ from threading import Event from typing import Optional -from rcrs_core.connection.URN import Entity as EntityURN +from rcrscore.urn import EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/adf_core_python/core/agent/platoon/platoon_fire.py b/adf_core_python/core/agent/platoon/platoon_fire.py index 8debf99..13db7e0 100644 --- a/adf_core_python/core/agent/platoon/platoon_fire.py +++ b/adf_core_python/core/agent/platoon/platoon_fire.py @@ -1,7 +1,7 @@ from threading import Event from typing import Optional -from rcrs_core.connection.URN import Entity as EntityURN +from rcrscore.urn import EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/adf_core_python/core/agent/platoon/platoon_police.py b/adf_core_python/core/agent/platoon/platoon_police.py index 441602f..8f9c33a 100644 --- a/adf_core_python/core/agent/platoon/platoon_police.py +++ b/adf_core_python/core/agent/platoon/platoon_police.py @@ -1,7 +1,7 @@ from threading import Event from typing import Optional -from rcrs_core.connection.URN import Entity as EntityURN +from rcrscore.urn import EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/adf_core_python/core/component/action/extend_action.py b/adf_core_python/core/component/action/extend_action.py index fece605..1688e62 100644 --- a/adf_core_python/core/component/action/extend_action.py +++ b/adf_core_python/core/component/action/extend_action.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: - from rcrs_core.worldmodel.entityID import EntityID + from rcrscore.entities import EntityID from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.communication.message_manager import MessageManager diff --git a/adf_core_python/core/component/centralized/command_picker.py b/adf_core_python/core/component/centralized/command_picker.py index 19c869a..8fcea2f 100644 --- a/adf_core_python/core/component/centralized/command_picker.py +++ b/adf_core_python/core/component/centralized/command_picker.py @@ -3,7 +3,7 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID if TYPE_CHECKING: from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/adf_core_python/core/component/module/algorithm/clustering.py b/adf_core_python/core/component/module/algorithm/clustering.py index 0a6bfdc..2594cb9 100644 --- a/adf_core_python/core/component/module/algorithm/clustering.py +++ b/adf_core_python/core/component/module/algorithm/clustering.py @@ -6,8 +6,8 @@ from adf_core_python.core.component.module.abstract_module import AbstractModule if TYPE_CHECKING: - from rcrs_core.entities.entity import Entity - from rcrs_core.worldmodel.entityID import EntityID + from rcrscore.entities import EntityID + from rcrscore.entities.entity import Entity from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/adf_core_python/core/component/module/algorithm/path_planning.py b/adf_core_python/core/component/module/algorithm/path_planning.py index 6445ac0..d7318f1 100644 --- a/adf_core_python/core/component/module/algorithm/path_planning.py +++ b/adf_core_python/core/component/module/algorithm/path_planning.py @@ -6,7 +6,7 @@ from adf_core_python.core.component.module.abstract_module import AbstractModule if TYPE_CHECKING: - from rcrs_core.worldmodel.entityID import EntityID + from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/adf_core_python/core/component/module/complex/ambulance_target_allocator.py b/adf_core_python/core/component/module/complex/ambulance_target_allocator.py index 7359e68..63e5a5f 100644 --- a/adf_core_python/core/component/module/complex/ambulance_target_allocator.py +++ b/adf_core_python/core/component/module/complex/ambulance_target_allocator.py @@ -8,7 +8,7 @@ ) if TYPE_CHECKING: - from rcrs_core.worldmodel.entityID import EntityID + from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/adf_core_python/core/component/module/complex/fire_target_allocator.py b/adf_core_python/core/component/module/complex/fire_target_allocator.py index 9e35373..2cf3ec2 100644 --- a/adf_core_python/core/component/module/complex/fire_target_allocator.py +++ b/adf_core_python/core/component/module/complex/fire_target_allocator.py @@ -8,7 +8,7 @@ ) if TYPE_CHECKING: - from rcrs_core.worldmodel.entityID import EntityID + from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/adf_core_python/core/component/module/complex/human_detector.py b/adf_core_python/core/component/module/complex/human_detector.py index 10a31e2..0e7ae6c 100644 --- a/adf_core_python/core/component/module/complex/human_detector.py +++ b/adf_core_python/core/component/module/complex/human_detector.py @@ -3,7 +3,7 @@ from abc import abstractmethod from typing import TYPE_CHECKING -from rcrs_core.entities.human import Human +from rcrscore.entities.human import Human from adf_core_python.core.component.module.complex.target_detector import ( TargetDetector, diff --git a/adf_core_python/core/component/module/complex/police_target_allocator.py b/adf_core_python/core/component/module/complex/police_target_allocator.py index bf31d15..a066304 100644 --- a/adf_core_python/core/component/module/complex/police_target_allocator.py +++ b/adf_core_python/core/component/module/complex/police_target_allocator.py @@ -8,7 +8,7 @@ ) if TYPE_CHECKING: - from rcrs_core.worldmodel.entityID import EntityID + from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/adf_core_python/core/component/module/complex/road_detector.py b/adf_core_python/core/component/module/complex/road_detector.py index 08b552d..888ea96 100644 --- a/adf_core_python/core/component/module/complex/road_detector.py +++ b/adf_core_python/core/component/module/complex/road_detector.py @@ -3,7 +3,7 @@ from abc import abstractmethod from typing import TYPE_CHECKING -from rcrs_core.entities.road import Road +from rcrscore.entities.road import Road from adf_core_python.core.component.module.complex.target_detector import ( TargetDetector, diff --git a/adf_core_python/core/component/module/complex/search.py b/adf_core_python/core/component/module/complex/search.py index 7715831..bfeddef 100644 --- a/adf_core_python/core/component/module/complex/search.py +++ b/adf_core_python/core/component/module/complex/search.py @@ -3,7 +3,7 @@ from abc import abstractmethod from typing import TYPE_CHECKING -from rcrs_core.entities.area import Area +from rcrscore.entities.area import Area from adf_core_python.core.component.module.complex.target_detector import ( TargetDetector, diff --git a/adf_core_python/core/component/module/complex/target_allocator.py b/adf_core_python/core/component/module/complex/target_allocator.py index 55869c9..88ef50f 100644 --- a/adf_core_python/core/component/module/complex/target_allocator.py +++ b/adf_core_python/core/component/module/complex/target_allocator.py @@ -6,7 +6,7 @@ from adf_core_python.core.component.module.abstract_module import AbstractModule if TYPE_CHECKING: - from rcrs_core.worldmodel.entityID import EntityID + from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/adf_core_python/core/component/module/complex/target_detector.py b/adf_core_python/core/component/module/complex/target_detector.py index 29fb4df..21a79e4 100644 --- a/adf_core_python/core/component/module/complex/target_detector.py +++ b/adf_core_python/core/component/module/complex/target_detector.py @@ -3,12 +3,12 @@ from abc import abstractmethod from typing import TYPE_CHECKING, Generic, Optional, TypeVar -from rcrs_core.entities.entity import Entity +from rcrscore.entities.entity import Entity from adf_core_python.core.component.module.abstract_module import AbstractModule if TYPE_CHECKING: - from rcrs_core.worldmodel.entityID import EntityID + from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py b/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py index 6518770..ade93a0 100644 --- a/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py +++ b/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py @@ -3,7 +3,7 @@ import json from typing import TYPE_CHECKING -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID from adf_core_python.core.component.module.algorithm.clustering import Clustering from adf_core_python.core.gateway.component.module.gateway_abstract_module import ( @@ -11,7 +11,7 @@ ) if TYPE_CHECKING: - from rcrs_core.entities.entity import Entity + from rcrscore.entities.entity import Entity from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -64,17 +64,17 @@ def calculate(self) -> GatewayClustering: def get_cluster_number(self) -> int: result = self._gateway_module.execute("getClusterNumber") - return int(result.get_value_or_default("ClusterNumber", "0")) + return int(result.get_value("ClusterNumber") or 0) def get_cluster_index(self, entity_id: EntityID) -> int: arguments: dict[str, str] = {"EntityID": str(entity_id.get_value())} result = self._gateway_module.execute("getClusterIndex(EntityID)", arguments) - return int(result.get_value_or_default("ClusterIndex", "0")) + return int(result.get_value("ClusterIndex") or 0) def get_cluster_entities(self, cluster_index: int) -> list[Entity]: arguments: dict[str, str] = {"Index": str(cluster_index)} result = self._gateway_module.execute("getClusterEntities(int)", arguments) - json_str = result.get_value_or_default("EntityIDs", "[]") + json_str = result.get_value("EntityIDs") or "[]" entity_ids: list[int] = json.loads(json_str) entities: list[Entity] = [] for entity_id in entity_ids: @@ -84,7 +84,7 @@ def get_cluster_entities(self, cluster_index: int) -> list[Entity]: def get_cluster_entity_ids(self, cluster_index: int) -> list[EntityID]: arguments: dict[str, str] = {"Index": str(cluster_index)} result = self._gateway_module.execute("getClusterEntityIDs(int)", arguments) - json_str = result.get_value_or_default("EntityIDs", "[]") + json_str = result.get_value("EntityIDs") or "[]" raw_entity_ids: list[int] = json.loads(json_str) entity_ids: list[EntityID] = [] for entity_id in raw_entity_ids: diff --git a/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py b/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py index 6a2bc4e..ef4df09 100644 --- a/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py +++ b/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py @@ -3,7 +3,7 @@ import json from typing import TYPE_CHECKING -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning from adf_core_python.core.gateway.component.module.gateway_abstract_module import ( @@ -70,7 +70,7 @@ def get_path( result = self._gateway_module.execute( "getResult(EntityID, EntityID)", arguments ) - json_str = result.get_value_or_default("Result", "[]") + json_str = result.get_value("Result") or "[]" raw_entity_ids: list[int] = json.loads(json_str) entity_ids: list[EntityID] = [] for entity_id in raw_entity_ids: @@ -89,7 +89,7 @@ def get_path_to_multiple_destinations( result = self._gateway_module.execute( "getResult(EntityID, List[EntityID])", arguments ) - json_str = result.get_value_or_default("Result", "[]") + json_str = result.get_value("Result") or "[]" raw_entity_ids: list[int] = json.loads(json_str) entity_ids: list[EntityID] = [] for entity_id in raw_entity_ids: @@ -104,4 +104,4 @@ def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> floa result = self._gateway_module.execute( "getDistance(EntityID, EntityID)", arguments ) - return float(result.get_value_or_default("Result", "0.0")) + return float(result.get_value("Result") or 0.0) diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py b/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py index 7fc6fe7..7692f07 100644 --- a/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py +++ b/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from rcrs_core.entities.human import Human +from rcrscore.entities.human import Human from adf_core_python.core.component.module.complex.human_detector import ( HumanDetector, diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py b/adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py index da275b9..bfceb81 100644 --- a/adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py +++ b/adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from rcrs_core.entities.road import Road +from rcrscore.entities.road import Road from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.component.module.complex.road_detector import RoadDetector diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py b/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py index 98649a4..1f7a57f 100644 --- a/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py +++ b/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID from adf_core_python.core.component.module.complex.target_allocator import ( TargetAllocator, diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py b/adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py index a58b25f..1f50383 100644 --- a/adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py +++ b/adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py @@ -1,9 +1,9 @@ from __future__ import annotations -from typing import Optional, Generic, TypeVar, TYPE_CHECKING +from typing import TYPE_CHECKING, Generic, Optional, TypeVar -from rcrs_core.entities.entity import Entity -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID +from rcrscore.entities.entity import Entity from adf_core_python.core.component.module.complex.target_detector import TargetDetector from adf_core_python.core.gateway.component.module.gateway_abstract_module import ( @@ -64,7 +64,7 @@ def calculate(self) -> GatewayTargetDetector[T]: def get_target_entity_id(self) -> Optional[EntityID]: result = self._gateway_module.execute("getTarget") - entity_id_str = result.get_value_or_default("EntityID", "-1") + entity_id_str = result.get_value("EntityID") or "-1" if entity_id_str == "-1": return None return EntityID(int(entity_id_str)) diff --git a/adf_core_python/core/gateway/gateway_agent.py b/adf_core_python/core/gateway/gateway_agent.py index 31008bb..7f04e19 100644 --- a/adf_core_python/core/gateway/gateway_agent.py +++ b/adf_core_python/core/gateway/gateway_agent.py @@ -1,8 +1,9 @@ from __future__ import annotations -from typing import Optional, TYPE_CHECKING, Callable +from typing import TYPE_CHECKING, Callable, Optional -from rcrs_core.connection import RCRSProto_pb2, URN +from rcrscore.proto import RCRSProto_pb2 +from rcrscore.urn import CommandURN from adf_core_python.core.agent.info.agent_info import AgentInfo from adf_core_python.core.agent.info.scenario_info import ScenarioInfo @@ -95,7 +96,7 @@ def update(self) -> None: def set_send_msg(self, connection_send_func: Callable) -> None: self.send_msg = connection_send_func - def message_received(self, msg: RCRSProto_pb2) -> None: + def message_received(self, msg: RCRSProto_pb2.MessageProto) -> None: c_msg = ModuleMessageFactory().make_message(msg) if isinstance(c_msg, MAModuleResponse): if c_msg.module_id is None or c_msg.class_name is None: @@ -112,7 +113,7 @@ def message_received(self, msg: RCRSProto_pb2) -> None: self._gateway_modules[c_msg.module_id].set_execute_response(c_msg.result) self._gateway_modules[c_msg.module_id].set_is_executed(True) - if msg.urn == URN.Command.AK_SPEAK: + if msg.urn == CommandURN.AK_SPEAK: if self.send_msg is None: raise RuntimeError("send_msg is None") self.send_msg(msg) diff --git a/adf_core_python/core/gateway/gateway_module.py b/adf_core_python/core/gateway/gateway_module.py index e79b239..99f9115 100644 --- a/adf_core_python/core/gateway/gateway_module.py +++ b/adf_core_python/core/gateway/gateway_module.py @@ -1,7 +1,7 @@ import uuid from typing import Optional -from rcrs_core.config.config import Config +from rcrscore.config.config import Config from adf_core_python.core.gateway.gateway_agent import GatewayAgent from adf_core_python.core.gateway.message.am_exec import AMExec diff --git a/adf_core_python/core/gateway/message/am_agent.py b/adf_core_python/core/gateway/message/am_agent.py index 3d6bc8d..aac4850 100644 --- a/adf_core_python/core/gateway/message/am_agent.py +++ b/adf_core_python/core/gateway/message/am_agent.py @@ -1,35 +1,30 @@ from typing import Any -from rcrs_core.connection import RCRSProto_pb2 -from rcrs_core.entities.entity import Entity -from rcrs_core.messages.message import Message -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID +from rcrscore.entities.entity import Entity +from rcrscore.messages import AKControlMessage +from rcrscore.proto import RCRSProto_pb2 +from rcrscore.urn.control_message import ControlMessageURN from adf_core_python.core.gateway.message.urn.urn import ( - ModuleMSG, ComponentModuleMSG, + ModuleMSG, ) -class AMAgent(Message): - def __init__(self) -> None: - super().__init__(ModuleMSG.AM_AGENT) - - def read(self) -> None: - pass - +class AMAgent(AKControlMessage): + @staticmethod def write( - self, agent_id: EntityID, entities: list[Entity], config: dict[str, Any], mode: int, - ) -> Any: + ) -> RCRSProto_pb2.MessageProto: entity_proto_list = [] for entity in entities: entity_proto = RCRSProto_pb2.EntityProto() entity_proto.urn = entity.get_urn() - entity_proto.entityID = entity.get_id().get_value() + entity_proto.entityID = entity.get_entity_id().get_value() property_proto_list = [] for k, v in entity.get_properties().items(): @@ -45,7 +40,7 @@ def write( config_proto.data[str(key)] = str(value) msg = RCRSProto_pb2.MessageProto() - msg.urn = self.get_urn() + msg.urn = AMAgent.get_urn() msg.components[ComponentModuleMSG.AgentID].entityID = agent_id.get_value() msg.components[ComponentModuleMSG.Entities].entityList.CopyFrom( entity_list_proto @@ -53,3 +48,7 @@ def write( msg.components[ComponentModuleMSG.Config].config.CopyFrom(config_proto) msg.components[ComponentModuleMSG.Mode].intValue = mode return msg + + @staticmethod + def get_urn() -> ControlMessageURN: + return ModuleMSG.AM_AGENT # type: ignore diff --git a/adf_core_python/core/gateway/message/am_exec.py b/adf_core_python/core/gateway/message/am_exec.py index 9bd36be..dd8508b 100644 --- a/adf_core_python/core/gateway/message/am_exec.py +++ b/adf_core_python/core/gateway/message/am_exec.py @@ -1,25 +1,20 @@ -from abc import ABC from typing import Any -from rcrs_core.connection import RCRSProto_pb2 -from rcrs_core.messages.message import Message +from rcrscore.messages import AKControlMessage +from rcrscore.proto import RCRSProto_pb2 +from rcrscore.urn.control_message import ControlMessageURN from adf_core_python.core.gateway.message.urn.urn import ( - ModuleMSG, ComponentModuleMSG, + ModuleMSG, ) -class AMExec(Message, ABC): - def __init__(self) -> None: - super().__init__(ModuleMSG.AM_EXEC) - - def read(self) -> None: - pass - - def write(self, module_id: str, method_name: str, arguments: dict[str, str]) -> Any: +class AMExec(AKControlMessage): + @staticmethod + def write(module_id: str, method_name: str, arguments: dict[str, str]) -> Any: msg = RCRSProto_pb2.MessageProto() - msg.urn = self.get_urn() + msg.urn = AMExec.get_urn() msg.components[ComponentModuleMSG.ModuleID].stringValue = module_id msg.components[ComponentModuleMSG.MethodName].stringValue = method_name config_proto = RCRSProto_pb2.ConfigProto() @@ -28,3 +23,7 @@ def write(self, module_id: str, method_name: str, arguments: dict[str, str]) -> msg.components[ComponentModuleMSG.Arguments].config.CopyFrom(config_proto) return msg + + @staticmethod + def get_urn() -> ControlMessageURN: + return ModuleMSG.AM_EXEC # type: ignore diff --git a/adf_core_python/core/gateway/message/am_module.py b/adf_core_python/core/gateway/message/am_module.py index 0dd26bc..6b31662 100644 --- a/adf_core_python/core/gateway/message/am_module.py +++ b/adf_core_python/core/gateway/message/am_module.py @@ -1,29 +1,24 @@ from typing import Any -from rcrs_core.connection import RCRSProto_pb2 -from rcrs_core.messages.message import Message +from rcrscore.messages import AKControlMessage +from rcrscore.proto import RCRSProto_pb2 +from rcrscore.urn.control_message import ControlMessageURN from adf_core_python.core.gateway.message.urn.urn import ( - ModuleMSG, ComponentModuleMSG, + ModuleMSG, ) -class AMModule(Message): - def __init__(self) -> None: - super().__init__(ModuleMSG.AM_MODULE) - - def read(self) -> None: - pass - +class AMModule(AKControlMessage): + @staticmethod def write( - self, module_id: str, module_name: str, default_class_name: str, ) -> Any: msg = RCRSProto_pb2.MessageProto() - msg.urn = self.get_urn() + msg.urn = AMModule.get_urn() msg.components[ComponentModuleMSG.ModuleID].stringValue = module_id msg.components[ComponentModuleMSG.ModuleName].stringValue = module_name msg.components[ @@ -31,3 +26,7 @@ def write( ].stringValue = default_class_name return msg + + @staticmethod + def get_urn() -> ControlMessageURN: + return ModuleMSG.AM_MODULE # type: ignore diff --git a/adf_core_python/core/gateway/message/am_update.py b/adf_core_python/core/gateway/message/am_update.py index 0988920..e6baa79 100644 --- a/adf_core_python/core/gateway/message/am_update.py +++ b/adf_core_python/core/gateway/message/am_update.py @@ -1,27 +1,22 @@ -from abc import ABC from typing import Any -from rcrs_core.commands.Command import Command -from rcrs_core.connection import RCRSProto_pb2 -from rcrs_core.messages.message import Message -from rcrs_core.worldmodel.changeSet import ChangeSet +from rcrscore.commands import Command +from rcrscore.messages import AKControlMessage +from rcrscore.proto import RCRSProto_pb2 +from rcrscore.urn.control_message import ControlMessageURN +from rcrscore.worldmodel import ChangeSet from adf_core_python.core.gateway.message.urn.urn import ( - ModuleMSG, ComponentModuleMSG, + ModuleMSG, ) -class AMUpdate(Message, ABC): - def __init__(self) -> None: - super().__init__(ModuleMSG.AM_UPDATE) - - def read(self) -> None: - pass - - def write(self, time: int, changed: ChangeSet, heard: list[Command]) -> Any: +class AMUpdate(AKControlMessage): + @staticmethod + def write(time: int, changed: ChangeSet, heard: list[Command]) -> Any: msg = RCRSProto_pb2.MessageProto() - msg.urn = self.get_urn() + msg.urn = AMUpdate.get_urn() msg.components[ComponentModuleMSG.Time].intValue = time msg.components[ComponentModuleMSG.Changed].changeSet.CopyFrom( changed.to_change_set_proto() @@ -30,9 +25,13 @@ def write(self, time: int, changed: ChangeSet, heard: list[Command]) -> Any: message_proto_list = [] if heard is not None: for h in heard: - message_proto_list.append(h.prepare_cmd()) + message_proto_list.append(h.to_message_proto()) message_list_proto.commands.extend(message_proto_list) msg.components[ComponentModuleMSG.Heard].commandList.CopyFrom( message_list_proto ) return msg + + @staticmethod + def get_urn() -> ControlMessageURN: + return ModuleMSG.AM_UPDATE # type: ignore diff --git a/adf_core_python/core/gateway/message/ma_exec_response.py b/adf_core_python/core/gateway/message/ma_exec_response.py index 8c68fbc..1e84021 100644 --- a/adf_core_python/core/gateway/message/ma_exec_response.py +++ b/adf_core_python/core/gateway/message/ma_exec_response.py @@ -1,28 +1,27 @@ -from abc import ABC - -from rcrs_core.config.config import Config -from rcrs_core.connection import RCRSProto_pb2 -from rcrs_core.messages.message import Message +from rcrscore.config.config import Config +from rcrscore.messages import KAControlMessage +from rcrscore.proto import RCRSProto_pb2 +from rcrscore.urn.control_message import ControlMessageURN from adf_core_python.core.gateway.message.urn.urn import ( - ModuleMSG, ComponentModuleMSG, + ModuleMSG, ) -class MAExecResponse(Message, ABC): - def __init__(self, data: RCRSProto_pb2) -> None: - super().__init__(ModuleMSG.MA_EXEC_RESPONSE) - self.module_id = None +class MAExecResponse(KAControlMessage): + def __init__(self, message_proto: RCRSProto_pb2.MessageProto) -> None: self.result = Config() - self.data = data - self.read() + self.read(message_proto) - def read(self) -> None: - self.module_id = self.data.components[ComponentModuleMSG.ModuleID].stringValue - result = self.data.components[ComponentModuleMSG.Result].config + def read(self, message_proto: RCRSProto_pb2.MessageProto) -> None: + self.module_id: str = message_proto.components[ + ComponentModuleMSG.ModuleID + ].stringValue + result = message_proto.components[ComponentModuleMSG.Result].config for key, value in result.data.items(): self.result.set_value(key, value) - def write(self) -> None: - pass + @staticmethod + def get_urn() -> ControlMessageURN: + return ModuleMSG.MA_EXEC_RESPONSE # type: ignore diff --git a/adf_core_python/core/gateway/message/ma_module_response.py b/adf_core_python/core/gateway/message/ma_module_response.py index c680419..8e879f3 100644 --- a/adf_core_python/core/gateway/message/ma_module_response.py +++ b/adf_core_python/core/gateway/message/ma_module_response.py @@ -1,26 +1,30 @@ from abc import ABC from typing import Optional -from rcrs_core.connection import RCRSProto_pb2 -from rcrs_core.messages.message import Message +from rcrscore.messages import KAControlMessage +from rcrscore.proto import RCRSProto_pb2 +from rcrscore.urn.control_message import ControlMessageURN from adf_core_python.core.gateway.message.urn.urn import ( - ModuleMSG, ComponentModuleMSG, + ModuleMSG, ) -class MAModuleResponse(Message, ABC): - def __init__(self, data: RCRSProto_pb2) -> None: - super().__init__(ModuleMSG.MA_MODULE_RESPONSE) +class MAModuleResponse(KAControlMessage, ABC): + def __init__(self, message_proto: RCRSProto_pb2.MessageProto) -> None: self.module_id: Optional[str] = None self.class_name: Optional[str] = None - self.data = data - self.read() + self.read(message_proto) - def read(self) -> None: - self.module_id = self.data.components[ComponentModuleMSG.ModuleID].stringValue - self.class_name = self.data.components[ComponentModuleMSG.ClassName].stringValue + def read(self, message_proto: RCRSProto_pb2.MessageProto) -> None: + self.module_id = message_proto.components[ + ComponentModuleMSG.ModuleID + ].stringValue + self.class_name = message_proto.components[ + ComponentModuleMSG.ClassName + ].stringValue - def write(self) -> None: - pass + @staticmethod + def get_urn() -> ControlMessageURN: + return ModuleMSG.MA_MODULE_RESPONSE # type: ignore diff --git a/adf_core_python/core/gateway/message/moduleMessageFactory.py b/adf_core_python/core/gateway/message/moduleMessageFactory.py index 6fac9d8..3874b25 100644 --- a/adf_core_python/core/gateway/message/moduleMessageFactory.py +++ b/adf_core_python/core/gateway/message/moduleMessageFactory.py @@ -1,6 +1,6 @@ from typing import Optional -from rcrs_core.connection import RCRSProto_pb2 +from rcrscore.proto import RCRSProto_pb2 from adf_core_python.core.gateway.message.ma_exec_response import MAExecResponse from adf_core_python.core.gateway.message.ma_module_response import ( @@ -14,7 +14,7 @@ def __init__(self) -> None: pass def make_message( - self, msg: RCRSProto_pb2 + self, msg: RCRSProto_pb2.MessageProto ) -> Optional[MAModuleResponse | MAExecResponse]: if msg.urn == ModuleMSG.MA_MODULE_RESPONSE: return MAModuleResponse(msg) diff --git a/adf_core_python/core/logger/logger.py b/adf_core_python/core/logger/logger.py index 16797e8..8b8f2f6 100644 --- a/adf_core_python/core/logger/logger.py +++ b/adf_core_python/core/logger/logger.py @@ -45,9 +45,13 @@ def get_agent_logger(name: str, agent_info: AgentInfo) -> structlog.BoundLogger: structlog.BoundLogger The logger with the given name and agent information. """ + agent = agent_info.get_myself() + if agent is None: + raise ValueError("Agent information is not available") + return structlog.get_logger(name).bind( agent_id=str(agent_info.get_entity_id()), - agent_type=str(agent_info.get_myself().get_urn().name), + agent_type=str(agent.get_urn().name), ) diff --git a/adf_core_python/implement/action/default_extend_action_clear.py b/adf_core_python/implement/action/default_extend_action_clear.py index d0a1912..879cbdf 100644 --- a/adf_core_python/implement/action/default_extend_action_clear.py +++ b/adf_core_python/implement/action/default_extend_action_clear.py @@ -2,16 +2,18 @@ import sys from typing import Optional, cast -from rcrs_core.entities.ambulanceTeam import AmbulanceTeam -from rcrs_core.entities.area import Area -from rcrs_core.entities.blockade import Blockade -from rcrs_core.entities.building import Building -from rcrs_core.entities.fireBrigade import FireBrigade -from rcrs_core.entities.human import Human -from rcrs_core.entities.policeForce import PoliceForce -from rcrs_core.entities.refuge import Refuge -from rcrs_core.entities.road import Road -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import ( + AmbulanceTeam, + Area, + Blockade, + Building, + EntityID, + FireBrigade, + Human, + PoliceForce, + Refuge, + Road, +) from shapely import LineString, Point, Polygon from adf_core_python.core.agent.action.action import Action @@ -261,7 +263,7 @@ def _get_rescue_action( for agent_entity in agent_entities: human = cast(Human, agent_entity) - if human.get_position().get_value() != road.get_id().get_value(): + if human.get_position().get_value() != road.get_entity_id().get_value(): continue human_x = human.get_x() @@ -294,10 +296,11 @@ def _get_rescue_action( return ActionClear(clear_blockade) another_distance = self.world_info.get_distance( - police_entity.get_id(), clear_blockade.get_id() + police_entity.get_entity_id(), + clear_blockade.get_entity_id(), ) blockade_distance = self.world_info.get_distance( - police_entity.get_id(), blockade.get_id() + police_entity.get_entity_id(), blockade.get_entity_id() ) if blockade_distance < another_distance: return action @@ -334,10 +337,12 @@ def _get_rescue_action( return ActionClear(clear_blockade) distance1 = self.world_info.get_distance( - police_entity.get_id(), clear_blockade.get_id() + police_entity.get_entity_id(), + clear_blockade.get_entity_id(), ) distance2 = self.world_info.get_distance( - police_entity.get_id(), blockade.get_id() + police_entity.get_entity_id(), + blockade.get_entity_id(), ) if distance1 > distance2: return ActionClearArea(clear_x, clear_y) @@ -346,7 +351,9 @@ def _get_rescue_action( elif distance < min_distance: min_distance = distance - move_action = ActionMove([road.get_id()], human_x, human_y) + move_action = ActionMove( + [road.get_entity_id()], human_x, human_y + ) if action_clear is not None: return action_clear @@ -412,7 +419,7 @@ def _get_intersect_edge_action( if best_point is not None: bp_x, bp_y = best_point if road.get_blockades() is None: - return ActionMove([road.get_id()], int(bp_x), int(bp_y)) + return ActionMove([road.get_entity_id()], int(bp_x), int(bp_y)) action_clear: Optional[ActionClearArea] = None clear_blockade: Optional[Blockade] = None @@ -446,7 +453,9 @@ def _get_intersect_edge_action( return ActionClear(clear_blockade) return action_clear elif action_move is None: - action_move = ActionMove([road.get_id()], int(bp_x), int(bp_y)) + action_move = ActionMove( + [road.get_entity_id()], int(bp_x), int(bp_y) + ) if action_clear is not None: return action_clear @@ -457,12 +466,12 @@ def _get_intersect_edge_action( cast(PoliceForce, self.agent_info.get_myself()), road ) if action is None: - action = ActionMove([road.get_id()], int(point_x), int(point_y)) + action = ActionMove([road.get_entity_id()], int(point_x), int(point_y)) return action def _get_move_points(self, road: Road) -> set[tuple[float, float]]: points: Optional[set[tuple[float, float]]] = self._move_point_cache.get( - road.get_id() + road.get_entity_id() ) if points is None: points = set() @@ -480,7 +489,7 @@ def _get_move_points(self, road: Road) -> set[tuple[float, float]]: if (mid_x, mid_y) in points: points.remove((mid_x, mid_y)) - self._move_point_cache[road.get_id()] = points + self._move_point_cache[road.get_entity_id()] = points return points @@ -568,10 +577,10 @@ def _get_area_clear_action( if self._is_intersecting_blockades(blockade, another): distance1 = self.world_info.get_distance( - police_entity.get_id(), blockade.get_id() + police_entity.get_entity_id(), blockade.get_entity_id() ) distance2 = self.world_info.get_distance( - police_entity.get_id(), another.get_id() + police_entity.get_entity_id(), another.get_entity_id() ) if distance1 <= distance2 and distance1 < min_distance: min_distance = distance1 @@ -632,7 +641,7 @@ def _get_neighbour_position_action( if position is None: return None - edge = target.get_edge_to(position.get_id()) + edge = target.get_edge_to(position.get_entity_id()) if edge is None: return None @@ -680,7 +689,9 @@ def _get_neighbour_position_action( if self.count >= self._forced_move: self.count = 0 return ActionMove( - [road.get_id()], int(clear_x), int(clear_y) + [road.get_entity_id()], + int(clear_x), + int(clear_y), ) self.count += 1 @@ -696,7 +707,7 @@ def _get_neighbour_position_action( return action_clear elif action_move is None: action_move = ActionMove( - [road.get_id()], int(mid_x), int(mid_y) + [road.get_entity_id()], int(mid_x), int(mid_y) ) if action_clear is not None: @@ -707,7 +718,7 @@ def _get_neighbour_position_action( if isinstance(target, Road): road = cast(Road, target) if road.get_blockades() == []: - return ActionMove([position.get_id(), target.get_id()]) + return ActionMove([position.get_entity_id(), target.get_entity_id()]) target_blockade: Optional[Blockade] = None min_point_distance = sys.float_info.max @@ -739,11 +750,11 @@ def _get_neighbour_position_action( ): if self.count >= self._forced_move: self.count = 0 - return ActionMove([road.get_id()], clear_x, clear_y) + return ActionMove([road.get_entity_id()], clear_x, clear_y) self.count += 1 self._old_clear_x = clear_x self._old_clear_y = clear_y return ActionClearArea(clear_x, clear_y) - return ActionMove([position.get_id(), target.get_id()]) + return ActionMove([position.get_entity_id(), target.get_entity_id()]) diff --git a/adf_core_python/implement/action/default_extend_action_move.py b/adf_core_python/implement/action/default_extend_action_move.py index e4936a4..b14baf3 100644 --- a/adf_core_python/implement/action/default_extend_action_move.py +++ b/adf_core_python/implement/action/default_extend_action_move.py @@ -1,10 +1,6 @@ from typing import Optional, cast -from rcrs_core.entities.area import Area -from rcrs_core.entities.blockade import Blockade -from rcrs_core.entities.entity import Entity -from rcrs_core.entities.human import Human -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Area, Blockade, Entity, EntityID, Human from adf_core_python.core.agent.action.common.action_move import ActionMove from adf_core_python.core.agent.communication.message_manager import MessageManager @@ -83,7 +79,7 @@ def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: entity = self.world_info.get_entity(position) if entity is not None and isinstance(entity, Area): - self._target_entity_id = entity.get_id() + self._target_entity_id = entity.get_entity_id() return self @@ -94,8 +90,12 @@ def calculate(self) -> ExtendAction: if self._target_entity_id is None: return self + agent_position = agent.get_position() + if agent_position is None: + return self + path: list[EntityID] = self._path_planning.get_path( - agent.get_position(), self._target_entity_id + agent_position, self._target_entity_id ) if path is not None and len(path) != 0: diff --git a/adf_core_python/implement/action/default_extend_action_rescue.py b/adf_core_python/implement/action/default_extend_action_rescue.py index e76e96a..df1e067 100644 --- a/adf_core_python/implement/action/default_extend_action_rescue.py +++ b/adf_core_python/implement/action/default_extend_action_rescue.py @@ -1,10 +1,6 @@ from typing import Optional, cast -from rcrs_core.entities.area import Area -from rcrs_core.entities.blockade import Blockade -from rcrs_core.entities.fireBrigade import FireBrigade -from rcrs_core.entities.human import Human -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Area, Blockade, EntityID, FireBrigade, Human from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.action.ambulance.action_rescue import ActionRescue @@ -117,16 +113,19 @@ def _calc_rescue( return None agent_position_entity_id = agent.get_position() + if agent_position_entity_id is None: + return None + if isinstance(target_entity, Human): human = cast(Human, target_entity) if human.get_hp() == 0: return None target_position_entity_id = human.get_position() - if ( - agent_position_entity_id.get_value() - == target_position_entity_id.get_value() - ): + if target_position_entity_id is None: + return None + + if agent_position_entity_id == target_position_entity_id: buriedness = human.get_buriedness() if buriedness is not None and buriedness > 0: return ActionRescue(target_entity_id) @@ -141,10 +140,14 @@ def _calc_rescue( if isinstance(target_entity, Blockade): blockade = cast(Blockade, target_entity) - target_entity = self.world_info.get_entity(blockade.get_position()) + blockade_position = blockade.get_position() + if blockade_position is None: + return None + + target_entity = self.world_info.get_entity(blockade_position) if isinstance(target_entity, Area): path = self._path_planning.get_path( - agent_position_entity_id, target_entity.get_id() + agent_position_entity_id, target_entity.get_entity_id() ) if path != []: return ActionMove(path) diff --git a/adf_core_python/implement/action/default_extend_action_transport.py b/adf_core_python/implement/action/default_extend_action_transport.py index b1c9629..a64b23c 100644 --- a/adf_core_python/implement/action/default_extend_action_transport.py +++ b/adf_core_python/implement/action/default_extend_action_transport.py @@ -1,12 +1,14 @@ from typing import Optional, Union, cast -from rcrs_core.entities.ambulanceTeam import AmbulanceTeam -from rcrs_core.entities.area import Area -from rcrs_core.entities.civilian import Civilian -from rcrs_core.entities.entity import Entity -from rcrs_core.entities.human import Human -from rcrs_core.entities.refuge import Refuge -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import ( + AmbulanceTeam, + Area, + Civilian, + Entity, + EntityID, + Human, + Refuge, +) from adf_core_python.core.agent.action.ambulance.action_load import ActionLoad from adf_core_python.core.agent.action.ambulance.action_unload import ActionUnload @@ -98,7 +100,7 @@ def calculate(self) -> ExtendAction: agent: AmbulanceTeam = cast(AmbulanceTeam, self.agent_info.get_myself()) transport_human: Optional[Human] = self.agent_info.some_one_on_board() if transport_human is not None: - self._logger.debug(f"transport_human: {transport_human.get_id()}") + self._logger.debug(f"transport_human: {transport_human.get_entity_id()}") self.result = self.calc_unload( agent, self._path_planning, transport_human, self._target_entity_id ) @@ -133,7 +135,7 @@ def calc_rescue( target_position = human.get_position() if agent_position == target_position: if isinstance(human, Civilian) and ((human.get_buriedness() or 0) == 0): - return ActionLoad(human.get_id()) + return ActionLoad(human.get_entity_id()) else: path = path_planning.get_path(agent_position, target_position) if path is not None and len(path) > 0: @@ -141,7 +143,7 @@ def calc_rescue( return None if isinstance(target_entity, Area): - path = path_planning.get_path(agent_position, target_entity.get_id()) + path = path_planning.get_path(agent_position, target_entity.get_entity_id()) if path is not None and len(path) > 0: return ActionMove(path) @@ -161,10 +163,7 @@ def calc_unload( return ActionUnload() agent_position = agent.get_position() - if ( - target_id is None - or transport_human.get_id().get_value() == target_id.get_value() - ): + if target_id is None or transport_human.get_entity_id() == target_id: position = self.world_info.get_entity(agent_position) if position is None: return None diff --git a/adf_core_python/implement/centralized/default_command_executor_ambulance.py b/adf_core_python/implement/centralized/default_command_executor_ambulance.py index adfb80b..aa6bf81 100644 --- a/adf_core_python/implement/centralized/default_command_executor_ambulance.py +++ b/adf_core_python/implement/centralized/default_command_executor_ambulance.py @@ -1,11 +1,6 @@ from typing import Optional, cast -from rcrs_core.entities.ambulanceTeam import AmbulanceTeam -from rcrs_core.entities.area import Area -from rcrs_core.entities.civilian import Civilian -from rcrs_core.entities.human import Human -from rcrs_core.entities.refuge import Refuge -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import AmbulanceTeam, Area, Civilian, EntityID, Human, Refuge from adf_core_python.core.agent.action.common.action_move import ActionMove from adf_core_python.core.agent.action.common.action_rest import ActionRest diff --git a/adf_core_python/implement/centralized/default_command_executor_fire.py b/adf_core_python/implement/centralized/default_command_executor_fire.py index c4fdf64..7def460 100644 --- a/adf_core_python/implement/centralized/default_command_executor_fire.py +++ b/adf_core_python/implement/centralized/default_command_executor_fire.py @@ -1,10 +1,6 @@ from typing import Optional, cast -from rcrs_core.entities.area import Area -from rcrs_core.entities.civilian import Civilian -from rcrs_core.entities.human import Human -from rcrs_core.entities.refuge import Refuge -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Area, Civilian, EntityID, Human, Refuge from adf_core_python.core.agent.action.common.action_move import ActionMove from adf_core_python.core.agent.action.common.action_rest import ActionRest diff --git a/adf_core_python/implement/centralized/default_command_executor_police.py b/adf_core_python/implement/centralized/default_command_executor_police.py index b137337..c5d756b 100644 --- a/adf_core_python/implement/centralized/default_command_executor_police.py +++ b/adf_core_python/implement/centralized/default_command_executor_police.py @@ -1,11 +1,6 @@ from typing import Optional, cast -from rcrs_core.entities.area import Area -from rcrs_core.entities.blockade import Blockade -from rcrs_core.entities.human import Human -from rcrs_core.entities.refuge import Refuge -from rcrs_core.entities.road import Road -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Area, Blockade, EntityID, Human, Refuge, Road from adf_core_python.core.agent.action.common.action_move import ActionMove from adf_core_python.core.agent.action.common.action_rest import ActionRest diff --git a/adf_core_python/implement/centralized/default_command_executor_scout.py b/adf_core_python/implement/centralized/default_command_executor_scout.py index 28fdd78..0ff628b 100644 --- a/adf_core_python/implement/centralized/default_command_executor_scout.py +++ b/adf_core_python/implement/centralized/default_command_executor_scout.py @@ -1,9 +1,6 @@ from typing import Optional, cast -from rcrs_core.entities.area import Area -from rcrs_core.entities.human import Human -from rcrs_core.entities.refuge import Refuge -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Area, EntityID, Human, Refuge from adf_core_python.core.agent.action.common.action_move import ActionMove from adf_core_python.core.agent.communication.message_manager import MessageManager @@ -72,8 +69,11 @@ def set_command(self, command: CommandScout) -> CommandExecutor: for entity in self._world_info.get_entities_of_types([Area]): if isinstance(entity, Refuge): continue - if self._world_info.get_distance(target, entity.get_id()) <= scout_distance: - self._targets.append(entity.get_id()) + if ( + self._world_info.get_distance(target, entity.get_entity_id()) + <= scout_distance + ): + self._targets.append(entity.get_entity_id()) return self @@ -151,7 +151,7 @@ def _is_command_completed(self) -> bool: case self.ACTION_SCOUT: if len(self._targets) != 0: for entity in self._world_info.get_entities_of_types([Area]): - self._targets.remove(entity.get_id()) + self._targets.remove(entity.get_entity_id()) return len(self._targets) == 0 case _: return True diff --git a/adf_core_python/implement/centralized/default_command_executor_scout_police.py b/adf_core_python/implement/centralized/default_command_executor_scout_police.py index e0345a5..c80d68a 100644 --- a/adf_core_python/implement/centralized/default_command_executor_scout_police.py +++ b/adf_core_python/implement/centralized/default_command_executor_scout_police.py @@ -1,9 +1,6 @@ from typing import Optional, cast -from rcrs_core.entities.area import Area -from rcrs_core.entities.human import Human -from rcrs_core.entities.refuge import Refuge -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Area, EntityID, Human, Refuge from adf_core_python.core.agent.action.common.action_move import ActionMove from adf_core_python.core.agent.communication.message_manager import MessageManager @@ -77,8 +74,11 @@ def set_command(self, command: CommandScout) -> CommandExecutor: for entity in self._world_info.get_entities_of_types([Area]): if isinstance(entity, Refuge): continue - if self._world_info.get_distance(target, entity.get_id()) <= scout_distance: - self._targets.append(entity.get_id()) + if ( + self._world_info.get_distance(target, entity.get_entity_id()) + <= scout_distance + ): + self._targets.append(entity.get_entity_id()) return self @@ -163,7 +163,7 @@ def _is_command_completed(self) -> bool: case self.ACTION_SCOUT: if len(self._targets) != 0: for entity in self._world_info.get_entities_of_types([Area]): - self._targets.remove(entity.get_id()) + self._targets.remove(entity.get_entity_id()) return len(self._targets) == 0 case _: return True diff --git a/adf_core_python/implement/centralized/default_command_picker_ambulance.py b/adf_core_python/implement/centralized/default_command_picker_ambulance.py index b2a507d..edddf4e 100644 --- a/adf_core_python/implement/centralized/default_command_picker_ambulance.py +++ b/adf_core_python/implement/centralized/default_command_picker_ambulance.py @@ -1,9 +1,6 @@ from __future__ import annotations -from rcrs_core.entities.ambulanceTeam import AmbulanceTeam -from rcrs_core.entities.area import Area -from rcrs_core.entities.human import Human -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import AmbulanceTeam, Area, EntityID, Human from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( CommandAmbulance, @@ -69,7 +66,7 @@ def calculate(self) -> CommandPicker: self._agent_info.get_entity_id(), CommandAmbulance.ACTION_RESCUE, StandardMessagePriority.NORMAL, - target.get_id(), + target.get_entity_id(), ) self.messages.append(command) @@ -80,7 +77,7 @@ def calculate(self) -> CommandPicker: self._agent_info.get_entity_id(), self.scout_distance, StandardMessagePriority.NORMAL, - target.get_id(), + target.get_entity_id(), ) self.messages.append(command) return self diff --git a/adf_core_python/implement/centralized/default_command_picker_fire.py b/adf_core_python/implement/centralized/default_command_picker_fire.py index bacb996..a5c8017 100644 --- a/adf_core_python/implement/centralized/default_command_picker_fire.py +++ b/adf_core_python/implement/centralized/default_command_picker_fire.py @@ -1,9 +1,6 @@ from __future__ import annotations -from rcrs_core.entities.area import Area -from rcrs_core.entities.fireBrigade import FireBrigade -from rcrs_core.entities.human import Human -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Area, EntityID, FireBrigade, Human from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( CommandAmbulance, @@ -69,7 +66,7 @@ def calculate(self) -> CommandPicker: self._agent_info.get_entity_id(), CommandAmbulance.ACTION_RESCUE, StandardMessagePriority.NORMAL, - target.get_id(), + target.get_entity_id(), ) self.messages.append(command) @@ -80,7 +77,7 @@ def calculate(self) -> CommandPicker: self._agent_info.get_entity_id(), self.scout_distance, StandardMessagePriority.NORMAL, - target.get_id(), + target.get_entity_id(), ) self.messages.append(command) return self diff --git a/adf_core_python/implement/centralized/default_command_picker_police.py b/adf_core_python/implement/centralized/default_command_picker_police.py index bbda78e..0dbde23 100644 --- a/adf_core_python/implement/centralized/default_command_picker_police.py +++ b/adf_core_python/implement/centralized/default_command_picker_police.py @@ -1,8 +1,6 @@ from __future__ import annotations -from rcrs_core.entities.area import Area -from rcrs_core.entities.policeForce import PoliceForce -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Area, EntityID, PoliceForce from adf_core_python.core.agent.communication.standard.bundle.centralized.command_police import ( CommandPolice, @@ -59,11 +57,11 @@ def calculate(self) -> CommandPicker: if isinstance(target, Area): command = CommandPolice( True, - agent.get_id(), + agent.get_entity_id(), self._agent_info.get_entity_id(), CommandPolice.ACTION_AUTONOMY, StandardMessagePriority.NORMAL, - target.get_id(), + target.get_entity_id(), ) self.messages.append(command) return self diff --git a/adf_core_python/implement/module/algorithm/a_star_path_planning.py b/adf_core_python/implement/module/algorithm/a_star_path_planning.py index 6f2fce5..5a80339 100644 --- a/adf_core_python/implement/module/algorithm/a_star_path_planning.py +++ b/adf_core_python/implement/module/algorithm/a_star_path_planning.py @@ -1,8 +1,6 @@ from __future__ import annotations -from rcrs_core.entities.area import Area -from rcrs_core.entities.entity import Entity -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Area, Entity, EntityID from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -30,9 +28,9 @@ def __init__( self._graph: dict[EntityID, set[EntityID]] = {} for entity in entities: if isinstance(entity, Area): - self._graph[entity.get_id()] = set( + self._graph[entity.get_entity_id()] = set( neighbor - for neighbor in entity.get_neighbours() # TODO: Fix rcrs_core typo + for neighbor in entity.get_neighbors() if neighbor != EntityID(0) ) diff --git a/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py b/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py index e8697d6..1a592a8 100644 --- a/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py +++ b/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py @@ -3,8 +3,7 @@ import heapq from typing import Optional -from rcrs_core.entities.area import Area -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Area, EntityID from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -31,9 +30,9 @@ def __init__( for area in self._world_info.get_entities_of_types([Area]): if not isinstance(area, Area): continue - if (neighbors := area.get_neighbours()) is None: + if (neighbors := area.get_neighbors()) is None: continue - area_id = area.get_id() + area_id = area.get_entity_id() self.graph[area_id] = [ ( neighbor, diff --git a/adf_core_python/implement/module/algorithm/k_means_clustering.py b/adf_core_python/implement/module/algorithm/k_means_clustering.py index 57228b7..9fe0517 100644 --- a/adf_core_python/implement/module/algorithm/k_means_clustering.py +++ b/adf_core_python/implement/module/algorithm/k_means_clustering.py @@ -1,15 +1,17 @@ import numpy as np -from rcrs_core.connection.URN import Entity as EntityURN -from rcrs_core.entities.ambulanceCenter import AmbulanceCentre -from rcrs_core.entities.building import Building -from rcrs_core.entities.entity import Entity -from rcrs_core.entities.fireStation import FireStation -from rcrs_core.entities.gasStation import GasStation -from rcrs_core.entities.hydrant import Hydrant -from rcrs_core.entities.policeOffice import PoliceOffice -from rcrs_core.entities.refuge import Refuge -from rcrs_core.entities.road import Road -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import ( + AmbulanceCenter, + Building, + Entity, + EntityID, + FireStation, + GasStation, + Hydrant, + PoliceOffice, + Refuge, + Road, +) +from rcrscore.urn import EntityURN from sklearn.cluster import KMeans from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -64,16 +66,16 @@ def __init__( agent_info.get_myself().__class__, ] ), - key=lambda entity: entity.get_id().get_value(), + key=lambda entity: entity.get_entity_id().get_value(), ) self.entity_cluster_indices = { - entity.get_id(): idx for idx, entity in enumerate(sorted_entities) + entity.get_entity_id(): idx for idx, entity in enumerate(sorted_entities) } self.cluster_entities: list[list[Entity]] = [] self.entities: list[Entity] = world_info.get_entities_of_types( [ - AmbulanceCentre, + AmbulanceCenter, FireStation, GasStation, Hydrant, @@ -92,7 +94,7 @@ def precompute(self, precompute_data: PrecomputeData) -> Clustering: precompute_data.write_json_data( { "cluster_entities": [ - [entity.get_id().get_value() for entity in cluster] + [entity.get_entity_id().get_value() for entity in cluster] for cluster in cluster_entities ] }, @@ -127,7 +129,9 @@ def get_cluster_entities(self, cluster_index: int) -> list[Entity]: def get_cluster_entity_ids(self, cluster_index: int) -> list[EntityID]: if cluster_index >= len(self.cluster_entities): return [] - return [entity.get_id() for entity in self.cluster_entities[cluster_index]] + return [ + entity.get_entity_id() for entity in self.cluster_entities[cluster_index] + ] def prepare(self) -> Clustering: super().prepare() diff --git a/adf_core_python/implement/module/communication/default_channel_subscriber.py b/adf_core_python/implement/module/communication/default_channel_subscriber.py index d195c9a..398b720 100644 --- a/adf_core_python/implement/module/communication/default_channel_subscriber.py +++ b/adf_core_python/implement/module/communication/default_channel_subscriber.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from rcrs_core.connection.URN import Entity as EntityURN +from rcrscore.urn import EntityURN from adf_core_python.core.agent.info.scenario_info import ScenarioInfoKeys from adf_core_python.core.component.communication.channel_subscriber import ( @@ -69,7 +69,7 @@ def get_channel_number( agent_index = 2 elif ( agent_type == EntityURN.AMBULANCE_TEAM - or agent_type == EntityURN.AMBULANCE_CENTRE + or agent_type == EntityURN.AMBULANCE_CENTER ): agent_index = 3 @@ -97,5 +97,5 @@ def get_channel_number( for i in range(max_channels): print( - f"AMB-{i}: {DefaultChannelSubscriber.get_channel_number(EntityURN.AMBULANCE_CENTRE, i, num_channels)}" + f"AMB-{i}: {DefaultChannelSubscriber.get_channel_number(EntityURN.AMBULANCE_CENTER, i, num_channels)}" ) diff --git a/adf_core_python/implement/module/communication/default_message_coordinator.py b/adf_core_python/implement/module/communication/default_message_coordinator.py index 92e1555..28c8490 100644 --- a/adf_core_python/implement/module/communication/default_message_coordinator.py +++ b/adf_core_python/implement/module/communication/default_message_coordinator.py @@ -1,6 +1,6 @@ from typing import Optional -from rcrs_core.connection.URN import Entity as EntityURN +from rcrscore.urn import EntityURN from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( @@ -96,7 +96,7 @@ def coordinate( fire_brigade_messages.append(msg) elif agent_type == EntityURN.POLICE_OFFICE: police_messages.append(msg) - elif agent_type == EntityURN.AMBULANCE_CENTRE: + elif agent_type == EntityURN.AMBULANCE_CENTER: ambulance_messages.append(msg) elif isinstance(msg, MessageReport): if agent_type == EntityURN.FIRE_BRIGADE: diff --git a/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py b/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py index 912638b..50f0f35 100644 --- a/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py +++ b/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py @@ -1,10 +1,7 @@ from functools import cmp_to_key from typing import Callable, Optional -from rcrs_core.entities.ambulanceTeam import AmbulanceTeam -from rcrs_core.entities.entity import Entity -from rcrs_core.entities.human import Human -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import AmbulanceTeam, Entity, EntityID, Human from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -70,12 +67,12 @@ def calculate(self) -> AmbulanceTargetAllocator: agents, key=cmp_to_key(self._compare_by_distance(target_entity)) ) result = agents.pop(0) - info = self._ambulance_team_info_map[result.get_id()] + info = self._ambulance_team_info_map[result.get_entity_id()] if info is not None: info._can_new_action = False info._target = target info.command_time = current_time - self._ambulance_team_info_map[result.get_id()] = info + self._ambulance_team_info_map[result.get_entity_id()] = info removes.append(target) for r in removes: @@ -90,12 +87,12 @@ def calculate(self) -> AmbulanceTargetAllocator: agents, key=cmp_to_key(self._compare_by_distance(target_entity)) ) result = agents.pop(0) - info = self._ambulance_team_info_map[result.get_id()] + info = self._ambulance_team_info_map[result.get_entity_id()] if info is not None: info._can_new_action = False info._target = target info.command_time = current_time - self._ambulance_team_info_map[result.get_id()] = info + self._ambulance_team_info_map[result.get_entity_id()] = info removes.append(target) for r in removes: @@ -112,7 +109,7 @@ def _get_action_agents( ) -> list[AmbulanceTeam]: result = [] for entity in self._world_info.get_entities_of_types([AmbulanceTeam]): - info = info_map[entity.get_id()] + info = info_map[entity.get_entity_id()] if info is not None and info._can_new_action: result.append(entity) return result @@ -122,10 +119,10 @@ def _compare_by_distance( ) -> Callable[[Entity, Entity], int]: def _cmp_func(entity_a: Entity, entity_b: Entity) -> int: distance_a = self._world_info.get_distance( - target_entity.get_id(), entity_a.get_id() + target_entity.get_entity_id(), entity_a.get_entity_id() ) distance_b = self._world_info.get_distance( - target_entity.get_id(), entity_b.get_id() + target_entity.get_entity_id(), entity_b.get_entity_id() ) if distance_a < distance_b: return -1 diff --git a/adf_core_python/implement/module/complex/default_fire_target_allocator.py b/adf_core_python/implement/module/complex/default_fire_target_allocator.py index 56711c1..c32e716 100644 --- a/adf_core_python/implement/module/complex/default_fire_target_allocator.py +++ b/adf_core_python/implement/module/complex/default_fire_target_allocator.py @@ -1,10 +1,7 @@ from functools import cmp_to_key from typing import Callable, Optional -from rcrs_core.entities.entity import Entity -from rcrs_core.entities.fireBrigade import FireBrigade -from rcrs_core.entities.human import Human -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Entity, EntityID, FireBrigade, Human from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -70,12 +67,12 @@ def calculate(self) -> FireTargetAllocator: agents, key=cmp_to_key(self._compare_by_distance(target_entity)) ) result = agents.pop(0) - info = self._fire_brigade_info_map[result.get_id()] + info = self._fire_brigade_info_map[result.get_entity_id()] if info is not None: info._can_new_action = False info._target = target info.command_time = current_time - self._fire_brigade_info_map[result.get_id()] = info + self._fire_brigade_info_map[result.get_entity_id()] = info removes.append(target) for r in removes: @@ -90,12 +87,12 @@ def calculate(self) -> FireTargetAllocator: agents, key=cmp_to_key(self._compare_by_distance(target_entity)) ) result = agents.pop(0) - info = self._fire_brigade_info_map[result.get_id()] + info = self._fire_brigade_info_map[result.get_entity_id()] if info is not None: info._can_new_action = False info._target = target info.command_time = current_time - self._fire_brigade_info_map[result.get_id()] = info + self._fire_brigade_info_map[result.get_entity_id()] = info removes.append(target) for r in removes: @@ -111,7 +108,7 @@ def _get_action_agents( ) -> list[FireBrigade]: result = [] for entity in self._world_info.get_entities_of_types([FireBrigade]): - info = info_map[entity.get_id()] + info = info_map[entity.get_entity_id()] if info is not None and info._can_new_action: result.append(entity) return result @@ -121,10 +118,10 @@ def _compare_by_distance( ) -> Callable[[Entity, Entity], int]: def _cmp_func(entity_a: Entity, entity_b: Entity) -> int: distance_a = self._world_info.get_distance( - target_entity.get_id(), entity_a.get_id() + target_entity.get_entity_id(), entity_a.get_entity_id() ) distance_b = self._world_info.get_distance( - target_entity.get_id(), entity_b.get_id() + target_entity.get_entity_id(), entity_b.get_entity_id() ) if distance_a < distance_b: return -1 diff --git a/adf_core_python/implement/module/complex/default_human_detector.py b/adf_core_python/implement/module/complex/default_human_detector.py index 4720226..a667524 100644 --- a/adf_core_python/implement/module/complex/default_human_detector.py +++ b/adf_core_python/implement/module/complex/default_human_detector.py @@ -1,10 +1,7 @@ from typing import Optional, cast -from rcrs_core.connection.URN import Entity as EntityURN -from rcrs_core.entities.civilian import Civilian -from rcrs_core.entities.entity import Entity -from rcrs_core.entities.human import Human -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Civilian, Entity, EntityID, Human +from rcrscore.urn import EntityURN from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -46,7 +43,7 @@ def __init__( def calculate(self) -> HumanDetector: transport_human: Optional[Human] = self._agent_info.some_one_on_board() if transport_human is not None: - self._result = transport_human.get_id() + self._result = transport_human.get_entity_id() return self if self._result is not None: @@ -72,44 +69,45 @@ def _select_target(self) -> Optional[EntityID]: cluster_valid_human_entities: list[Entity] = [ entity for entity in cluster_entities - if self._is_valid_human(entity.get_id()) and isinstance(entity, Civilian) + if self._is_valid_human(entity.get_entity_id()) + and isinstance(entity, Civilian) ] if len(cluster_valid_human_entities) != 0: nearest_human_entity = cluster_valid_human_entities[0] nearest_distance = self._world_info.get_distance( self._agent_info.get_entity_id(), - nearest_human_entity.get_id(), + nearest_human_entity.get_entity_id(), ) for entity in cluster_valid_human_entities: distance = self._world_info.get_distance( self._agent_info.get_entity_id(), - entity.get_id(), + entity.get_entity_id(), ) if distance < nearest_distance: nearest_distance = distance nearest_human_entity = entity - return nearest_human_entity.get_id() + return nearest_human_entity.get_entity_id() world_valid_human_entities: list[Entity] = [ entity for entity in self._world_info.get_entities_of_types([Civilian]) - if self._is_valid_human(entity.get_id()) + if self._is_valid_human(entity.get_entity_id()) ] if len(world_valid_human_entities) != 0: nearest_human_entity = world_valid_human_entities[0] nearest_distance = self._world_info.get_distance( self._agent_info.get_entity_id(), - nearest_human_entity.get_id(), + nearest_human_entity.get_entity_id(), ) for entity in world_valid_human_entities: distance = self._world_info.get_distance( self._agent_info.get_entity_id(), - entity.get_id(), + entity.get_entity_id(), ) if distance < nearest_distance: nearest_distance = distance nearest_human_entity = entity - return nearest_human_entity.get_id() + return nearest_human_entity.get_entity_id() return None diff --git a/adf_core_python/implement/module/complex/default_police_target_allocator.py b/adf_core_python/implement/module/complex/default_police_target_allocator.py index 00d6702..0de8b3f 100644 --- a/adf_core_python/implement/module/complex/default_police_target_allocator.py +++ b/adf_core_python/implement/module/complex/default_police_target_allocator.py @@ -1,13 +1,15 @@ from functools import cmp_to_key from typing import Callable, Optional, cast -from rcrs_core.entities.building import Building -from rcrs_core.entities.entity import Entity -from rcrs_core.entities.gasStation import GasStation -from rcrs_core.entities.policeForce import PoliceForce -from rcrs_core.entities.refuge import Refuge -from rcrs_core.entities.road import Road -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import ( + Building, + Entity, + EntityID, + GasStation, + PoliceForce, + Refuge, + Road, +) from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -50,14 +52,14 @@ def resume(self, precompute_data: PrecomputeData) -> PoliceTargetAllocator: [Refuge, Building, GasStation] ): building: Building = cast(Building, entity) - for entity_id in building.get_neighbours(): + for entity_id in building.get_neighbors(): neighbor = self._world_info.get_entity(entity_id) if isinstance(neighbor, Road): self._target_areas.add(entity_id) for entity in self._world_info.get_entities_of_types([Refuge]): refuge: Refuge = cast(Refuge, entity) - for entity_id in refuge.get_neighbours(): + for entity_id in refuge.get_neighbors(): neighbor = self._world_info.get_entity(entity_id) if isinstance(neighbor, Road): self._priority_areas.add(entity_id) @@ -75,14 +77,14 @@ def prepare(self) -> PoliceTargetAllocator: [Refuge, Building, GasStation] ): building: Building = cast(Building, entity) - for entity_id in building.get_neighbours(): + for entity_id in building.get_neighbors(): neighbor = self._world_info.get_entity(entity_id) if isinstance(neighbor, Road): self._target_areas.add(entity_id) for entity in self._world_info.get_entities_of_types([Refuge]): refuge: Refuge = cast(Refuge, entity) - for entity_id in refuge.get_neighbours(): + for entity_id in refuge.get_neighbors(): neighbor = self._world_info.get_entity(entity_id) if isinstance(neighbor, Road): self._priority_areas.add(entity_id) @@ -107,12 +109,12 @@ def calculate(self) -> PoliceTargetAllocator: agents, key=cmp_to_key(self._compare_by_distance(target_entity)) ) result = agents.pop(0) - info = self._agent_info_map[result.get_id()] + info = self._agent_info_map[result.get_entity_id()] if info is not None: info._can_new_action = False info._target = target info.command_time = current_time - self._agent_info_map[result.get_id()] = info + self._agent_info_map[result.get_entity_id()] = info removes.append(target) for r in removes: @@ -128,13 +130,13 @@ def calculate(self) -> PoliceTargetAllocator: if len(areas) > 0: areas.sort(key=cmp_to_key(self._compare_by_distance(agent))) result = areas.pop(0) - self._target_areas.remove(result.get_id()) - info = self._agent_info_map[agent.get_id()] + self._target_areas.remove(result.get_entity_id()) + info = self._agent_info_map[agent.get_entity_id()] if info is not None: info._can_new_action = False - info._target = result.get_id() + info._target = result.get_entity_id() info.command_time = current_time - self._agent_info_map[agent.get_id()] = info + self._agent_info_map[agent.get_entity_id()] = info return self @@ -146,7 +148,7 @@ def _get_action_agents( ) -> list[PoliceForce]: result = [] for entity in self._world_info.get_entities_of_types([PoliceForce]): - info = info_map[entity.get_id()] + info = info_map[entity.get_entity_id()] if info is not None and info._can_new_action: result.append(entity) return result @@ -156,10 +158,10 @@ def _compare_by_distance( ) -> Callable[[Entity, Entity], int]: def _cmp_func(entity_a: Entity, entity_b: Entity) -> int: distance_a = self._world_info.get_distance( - target_entity.get_id(), entity_a.get_id() + target_entity.get_entity_id(), entity_a.get_entity_id() ) distance_b = self._world_info.get_distance( - target_entity.get_id(), entity_b.get_id() + target_entity.get_entity_id(), entity_b.get_entity_id() ) if distance_a < distance_b: return -1 diff --git a/adf_core_python/implement/module/complex/default_road_detector.py b/adf_core_python/implement/module/complex/default_road_detector.py index 035cc61..ad837a4 100644 --- a/adf_core_python/implement/module/complex/default_road_detector.py +++ b/adf_core_python/implement/module/complex/default_road_detector.py @@ -1,10 +1,6 @@ from typing import Optional, cast -from rcrs_core.entities.building import Building -from rcrs_core.entities.gasStation import GasStation -from rcrs_core.entities.refuge import Refuge -from rcrs_core.entities.road import Road -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Building, EntityID, GasStation, Refuge, Road from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -59,7 +55,7 @@ def resume(self, precompute_data: PrecomputeData) -> RoadDetector: for entity in entities: if not isinstance(entity, Building): continue - for entity_id in entity.get_neighbours(): + for entity_id in entity.get_neighbors(): neighbor = self._world_info.get_entity(entity_id) if isinstance(neighbor, Road): self._target_areas.add(entity_id) @@ -68,7 +64,7 @@ def resume(self, precompute_data: PrecomputeData) -> RoadDetector: for entity in self._world_info.get_entities_of_types([Refuge]): if not isinstance(entity, Building): continue - for entity_id in entity.get_neighbours(): + for entity_id in entity.get_neighbors(): neighbor = self._world_info.get_entity(entity_id) if isinstance(neighbor, Road): self._priority_roads.add(entity_id) @@ -86,7 +82,7 @@ def prepare(self) -> RoadDetector: ) for entity in entities: building: Building = cast(Building, entity) - for entity_id in building.get_neighbours(): + for entity_id in building.get_neighbors(): neighbor = self._world_info.get_entity(entity_id) if isinstance(neighbor, Road): self._target_areas.add(entity_id) @@ -94,7 +90,7 @@ def prepare(self) -> RoadDetector: self._priority_roads = set() for entity in self._world_info.get_entities_of_types([Refuge]): refuge: Refuge = cast(Refuge, entity) - for entity_id in refuge.get_neighbours(): + for entity_id in refuge.get_neighbors(): neighbor = self._world_info.get_entity(entity_id) if isinstance(neighbor, Road): self._priority_roads.add(entity_id) diff --git a/adf_core_python/implement/module/complex/default_search.py b/adf_core_python/implement/module/complex/default_search.py index 05d7c88..0d5c714 100644 --- a/adf_core_python/implement/module/complex/default_search.py +++ b/adf_core_python/implement/module/complex/default_search.py @@ -1,9 +1,6 @@ from typing import Optional, cast -from rcrs_core.entities.building import Building -from rcrs_core.entities.entity import Entity -from rcrs_core.entities.refuge import Refuge -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import Building, Entity, EntityID, Refuge from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -98,7 +95,7 @@ def _get_search_targets(self) -> set[EntityID]: cluster_index ) building_entity_ids: list[EntityID] = [ - entity.get_id() + entity.get_entity_id() for entity in cluster_entities if isinstance(entity, Building) and not isinstance(entity, Refuge) ] diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py b/adf_core_python/implement/tactics/default_tactics_ambulance_center.py index 23a9d85..68c9ff8 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_center.py @@ -1,6 +1,6 @@ from typing import cast -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 76b6c47..688c845 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -1,6 +1,6 @@ from typing import Optional, cast -from rcrs_core.entities.ambulanceTeam import AmbulanceTeam +from rcrscore.entities import AmbulanceTeam from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.action.common.action_rest import ActionRest diff --git a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py index 0d9740e..de4f706 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py @@ -1,6 +1,6 @@ from typing import Optional, cast -from rcrs_core.entities.fireBrigade import FireBrigade +from rcrscore.entities import FireBrigade from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.action.common.action_rest import ActionRest diff --git a/adf_core_python/implement/tactics/default_tactics_fire_station.py b/adf_core_python/implement/tactics/default_tactics_fire_station.py index 0b15a3e..ef6425d 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_station.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_station.py @@ -1,6 +1,6 @@ from typing import cast -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py index 6a1f2f6..8b75a07 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -1,6 +1,6 @@ from typing import Optional, cast -from rcrs_core.entities.policeForce import PoliceForce +from rcrscore.entities import PoliceForce from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.action.common.action_rest import ActionRest diff --git a/adf_core_python/implement/tactics/default_tactics_police_office.py b/adf_core_python/implement/tactics/default_tactics_police_office.py index df095cc..d605244 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_office.py +++ b/adf_core_python/implement/tactics/default_tactics_police_office.py @@ -1,6 +1,6 @@ from typing import cast -from rcrs_core.worldmodel.entityID import EntityID +from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData diff --git a/poetry.lock b/poetry.lock index 0dc5c07..371973e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -980,24 +980,24 @@ files = [ ] [[package]] -name = "rcrs_core" +name = "rcrscore" version = "0.1.0" -description = "RoboCup Rescue Simulation(RCRS Agent Development Library)" +description = "Add your description here" optional = false -python-versions = "*" +python-versions = ">=3.13" groups = ["main"] files = [] develop = false [package.dependencies] -protobuf = ">=5.28" -rtree = "*" +protobuf = ">=6.31.1" +rtree = ">=1.4.0" [package.source] type = "git" url = "https://github.com/adf-python/rcrs-core-python" -reference = "v0.1.0" -resolved_reference = "42a20e312de20ea46f1e0622a82f41e81fc3514f" +reference = "improve" +resolved_reference = "288c666003039b77c09e91d0067f61ad2b125ce4" [[package]] name = "requests" @@ -1743,5 +1743,5 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [metadata] lock-version = "2.1" -python-versions = "^3.12" -content-hash = "ca8c4ce733e53da7e9b9208c3341bcc8043863f16f8e4300e5cf2212495690c2" +python-versions = "^3.13" +content-hash = "0f25d8ec7e7a27c7ea9006b97135f44ede3114ec3a9a6f19702345464d0d7b51" diff --git a/pyproject.toml b/pyproject.toml index e2bb97b..080aba5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,8 +10,8 @@ readme = "README.md" package-mode = true [tool.poetry.dependencies] -python = "^3.12" -rcrs_core = { git = "https://github.com/adf-python/rcrs-core-python", tag = "v0.1.0" } +python = "^3.13" +rcrscore = { git = "https://github.com/adf-python/rcrs-core-python", branch = "improve" } pyyaml = "^6.0.2" types-pyyaml = "^6.0.12.20240808" scikit-learn = "^1.5.2" From 58a94b739d6abb1b940443f99bc0d387556640c4 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 02:58:30 +0900 Subject: [PATCH 234/249] Refactor code for improved type safety and null handling --- .../module/complex/sample_road_detector.py | 21 ++-- .../team_name/module/complex/sample_search.py | 3 +- adf_core_python/core/agent/info/world_info.py | 4 +- .../module/algorithm/gateway_clustering.py | 4 +- .../complex/gateway_target_allocator.py | 5 +- .../core/gateway/gateway_module.py | 2 + .../action/default_extend_action_clear.py | 97 +++++++++++++------ .../action/default_extend_action_rescue.py | 4 +- .../action/default_extend_action_transport.py | 19 +++- .../default_command_executor_ambulance.py | 4 +- .../default_command_executor_police.py | 19 ++-- .../default_command_executor_scout.py | 17 ++-- .../default_command_executor_scout_police.py | 17 ++-- .../module/algorithm/a_star_path_planning.py | 6 +- .../algorithm/dijkstra_path_planning.py | 12 +-- .../module/algorithm/k_means_clustering.py | 7 +- .../default_ambulance_target_allocator.py | 7 +- .../complex/default_fire_target_allocator.py | 7 +- .../module/complex/default_human_detector.py | 2 + .../default_police_target_allocator.py | 19 ++-- .../module/complex/default_road_detector.py | 21 ++-- .../module/complex/default_search.py | 3 +- 22 files changed, 197 insertions(+), 103 deletions(-) diff --git a/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py b/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py index 87e188a..2e71d61 100644 --- a/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py @@ -37,7 +37,7 @@ def __init__( ) self.register_sub_module(self._path_planning) - self._result = None + self._result: Optional[EntityID] = None def precompute(self, precompute_data: PrecomputeData) -> RoadDetector: super().precompute(precompute_data) @@ -108,7 +108,7 @@ def update_info(self, message_manager: MessageManager) -> RoadDetector: if isinstance(entity, Building): self._result = None elif isinstance(entity, Road): - road: Road = cast(Road, entity) + road = entity if road.get_blockades() == []: self._target_areas.remove(self._result) self._result = None @@ -117,7 +117,9 @@ def update_info(self, message_manager: MessageManager) -> RoadDetector: def calculate(self) -> RoadDetector: if self._result is None: - position_entity_id: EntityID = self._agent_info.get_position_entity_id() + position_entity_id = self._agent_info.get_position_entity_id() + if position_entity_id is None: + return self if position_entity_id in self._target_areas: self._result = position_entity_id return self @@ -128,21 +130,22 @@ def calculate(self) -> RoadDetector: self._priority_roads = self._priority_roads - set(remove_list) if len(self._priority_roads) > 0: - _nearest_target_area = self._agent_info.get_position_entity_id() + agent_position = self._agent_info.get_position_entity_id() + if agent_position is None: + return self + _nearest_target_area = agent_position _nearest_distance = float("inf") for target_area in self._target_areas: if ( - self._world_info.get_distance( - self._agent_info.get_position_entity_id(), target_area - ) + self._world_info.get_distance(agent_position, target_area) < _nearest_distance ): _nearest_target_area = target_area _nearest_distance = self._world_info.get_distance( - self._agent_info.get_position_entity_id(), target_area + agent_position, target_area ) path: list[EntityID] = self._path_planning.get_path( - self._agent_info.get_position_entity_id(), _nearest_target_area + agent_position, _nearest_target_area ) if path is not None and len(path) > 0: self._result = path[-1] diff --git a/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py b/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py index a441a07..888f657 100644 --- a/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py @@ -64,7 +64,8 @@ def update_info(self, message_manager: MessageManager) -> Search: ) searched_building_id = self._agent_info.get_position_entity_id() - self._unreached_building_ids.discard(searched_building_id) + if searched_building_id is not None: + self._unreached_building_ids.discard(searched_building_id) if len(self._unreached_building_ids) == 0: self._unreached_building_ids = self._get_search_targets() diff --git a/adf_core_python/core/agent/info/world_info.py b/adf_core_python/core/agent/info/world_info.py index 8ac55c9..acea5da 100644 --- a/adf_core_python/core/agent/info/world_info.py +++ b/adf_core_python/core/agent/info/world_info.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, cast +from typing import Any, Optional from rcrscore.entities import EntityID from rcrscore.entities.area import Area @@ -199,7 +199,7 @@ def get_blockades(self, area: Area) -> set[Blockade]: for blockade_entity_id in blockade_entity_ids: blockades_entity = self.get_entity(blockade_entity_id) if isinstance(blockades_entity, Blockade): - blockades.add(cast(Blockade, blockades_entity)) + blockades.add(blockades_entity) return blockades def add_entity(self, entity: Entity) -> None: diff --git a/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py b/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py index ade93a0..c01047f 100644 --- a/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py +++ b/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py @@ -78,7 +78,9 @@ def get_cluster_entities(self, cluster_index: int) -> list[Entity]: entity_ids: list[int] = json.loads(json_str) entities: list[Entity] = [] for entity_id in entity_ids: - entities.append(self._world_info.get_entity(EntityID(entity_id))) + entity = self._world_info.get_entity(EntityID(entity_id)) + if entity is not None: + entities.append(entity) return entities def get_cluster_entity_ids(self, cluster_index: int) -> list[EntityID]: diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py b/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py index 1f7a57f..df35491 100644 --- a/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py +++ b/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py @@ -63,11 +63,12 @@ def calculate(self) -> GatewayTargetAllocator: def get_result(self) -> dict[EntityID, EntityID]: response = self._gateway_module.execute("getResult") - response_keys = response.get_all_keys() + response_keys = response.data.keys() result: dict[EntityID, EntityID] = {} for key in response_keys: + value = response.get_value(key) result[EntityID(int(key))] = EntityID( - int(response.get_value_or_default(key, "-1")) + int(value if value is not None else "-1") ) return result diff --git a/adf_core_python/core/gateway/gateway_module.py b/adf_core_python/core/gateway/gateway_module.py index 99f9115..68bb446 100644 --- a/adf_core_python/core/gateway/gateway_module.py +++ b/adf_core_python/core/gateway/gateway_module.py @@ -54,6 +54,8 @@ def initialize(self, module_name: str, default_class_name: str) -> str: return self.get_gateway_class_name() def get_execute_response(self) -> Config: + if self._result is None: + raise RuntimeError("No execution result available") return self._result def set_execute_response(self, result: Config) -> None: diff --git a/adf_core_python/implement/action/default_extend_action_clear.py b/adf_core_python/implement/action/default_extend_action_clear.py index 879cbdf..7e09b41 100644 --- a/adf_core_python/implement/action/default_extend_action_clear.py +++ b/adf_core_python/implement/action/default_extend_action_clear.py @@ -59,7 +59,7 @@ def __init__( ) ) - self._target_entity_id = None + self._target_entity_id: Optional[EntityID] = None self._move_point_cache: dict[EntityID, Optional[set[tuple[float, float]]]] = {} self._old_clear_x = 0 self._old_clear_y = 0 @@ -135,14 +135,14 @@ def calculate(self) -> ExtendAction: return self agent_position_entity_id = police_force.get_position() + if agent_position_entity_id is None: + return self target_entity = self.world_info.get_entity(self._target_entity_id) position_entity = self.world_info.get_entity(agent_position_entity_id) if target_entity is None or isinstance(target_entity, Area) is False: return self if isinstance(position_entity, Road): - self.result = self._get_rescue_action( - police_force, cast(Road, position_entity) - ) + self.result = self._get_rescue_action(police_force, position_entity) if self.result is not None: return self @@ -180,7 +180,7 @@ def calculate(self) -> ExtendAction: police_force, cast(Area, entity) ) if self.result is not None and isinstance(self.result, ActionMove): - action_move = cast(ActionMove, self.result) + action_move = self.result if action_move.is_destination_defined(): self.result = None @@ -193,15 +193,16 @@ def _need_rest(self, police_force: PoliceForce) -> bool: hp = police_force.get_hp() damage = police_force.get_damage() - if hp == 0 or damage == 0: + if hp is None or damage is None or hp == 0 or damage == 0: return False active_time = (hp / damage) + (1 if (hp % damage) != 0 else 0) if self._kernel_time == -1: self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) - return damage >= self._threshold_rest or ( - active_time + self.agent_info.get_time() < self._kernel_time + return damage is not None and ( + damage >= self._threshold_rest + or (active_time + self.agent_info.get_time() < self._kernel_time) ) def _calc_rest( @@ -211,6 +212,8 @@ def _calc_rest( target_entity_ids: list[EntityID], ) -> Optional[Action]: position_entity_id = police_force.get_position() + if position_entity_id is None: + return None refuges = self.world_info.get_entity_ids_of_types([Refuge]) current_size = len(refuges) if position_entity_id in refuges: @@ -244,12 +247,13 @@ def _calc_rest( def _get_rescue_action( self, police_entity: PoliceForce, road: Road ) -> Optional[Action]: + road_blockades = road.get_blockades() blockades = set( [] - if road.get_blockades() is None + if road_blockades is None else [ cast(Blockade, self.world_info.get_entity(blockade_entity_id)) - for blockade_entity_id in road.get_blockades() + for blockade_entity_id in road_blockades ] ) agent_entities = set( @@ -263,15 +267,30 @@ def _get_rescue_action( for agent_entity in agent_entities: human = cast(Human, agent_entity) - if human.get_position().get_value() != road.get_entity_id().get_value(): + human_position = human.get_position() + if ( + human_position is None + or human_position.get_value() != road.get_entity_id().get_value() + ): continue human_x = human.get_x() human_y = human.get_y() + if ( + human_x is None + or human_y is None + or police_x is None + or police_y is None + ): + continue + action_clear: Optional[ActionClear | ActionClearArea] = None clear_blockade: Optional[Blockade] = None for blockade in blockades: - if not self._is_inside(human_x, human_y, blockade.get_apexes()): + blockade_apexes = blockade.get_apexes() + if blockade_apexes is None or not self._is_inside( + human_x, human_y, blockade_apexes + ): continue distance = self._get_distance(police_x, police_y, human_x, human_y) @@ -373,7 +392,10 @@ def _get_distance(self, x1: float, y1: float, x2: float, y2: float) -> float: def _is_intersecting_area( self, agent_x: float, agent_y: float, point_x: float, point_y: float, area: Area ) -> bool: - for edge in area.get_edges(): + edges = area.get_edges() + if edges is None: + return False + for edge in edges: start_x = edge.get_start_x() start_y = edge.get_start_y() end_x = edge.get_end_x() @@ -483,11 +505,13 @@ def _get_move_points(self, road: Road) -> set[tuple[float, float]]: if self._is_inside(mid_x, mid_y, apex): points.add((mid_x, mid_y)) - for edge in road.get_edges(): - mid_x = (edge.get_start_x() + edge.get_end_x()) / 2.0 - mid_y = (edge.get_start_y() + edge.get_end_y()) / 2.0 - if (mid_x, mid_y) in points: - points.remove((mid_x, mid_y)) + edges = road.get_edges() + if edges is not None: + for edge in edges: + mid_x = (edge.get_start_x() + edge.get_end_x()) / 2.0 + mid_y = (edge.get_start_y() + edge.get_end_y()) / 2.0 + if (mid_x, mid_y) in points: + points.remove((mid_x, mid_y)) self._move_point_cache[road.get_entity_id()] = points @@ -518,6 +542,8 @@ def _is_intersecting_blockade( blockade: Blockade, ) -> bool: apexes = blockade.get_apexes() + if apexes is None or len(apexes) < 4: + return False for i in range(0, len(apexes) - 3, 2): line1 = LineString( [(apexes[i], apexes[i + 1]), (apexes[i + 2], apexes[i + 3])] @@ -532,6 +558,8 @@ def _is_intersecting_blockades( ) -> bool: apexes1 = blockade1.get_apexes() apexes2 = blockade2.get_apexes() + if apexes1 is None or apexes2 is None or len(apexes1) < 4 or len(apexes2) < 4: + return False for i in range(0, len(apexes1) - 2, 2): for j in range(0, len(apexes2) - 2, 2): line1 = LineString( @@ -593,20 +621,26 @@ def _get_area_clear_action( if min_distance < self._clear_distance: return ActionClear(clear_blockade) else: - return ActionMove( - [police_entity.get_position()], - clear_blockade.get_x(), - clear_blockade.get_y(), - ) + position = police_entity.get_position() + if position is not None: + return ActionMove( + [position], + clear_blockade.get_x(), + clear_blockade.get_y(), + ) agent_x = police_entity.get_x() agent_y = police_entity.get_y() + if agent_x is None or agent_y is None: + return None clear_blockade = None min_point_distance = sys.float_info.max clear_x = 0 clear_y = 0 for blockade in blockades: apexes = blockade.get_apexes() + if apexes is None or len(apexes) < 4: + continue for i in range(0, len(apexes) - 2, 2): distance = self._get_distance( agent_x, agent_y, apexes[i], apexes[i + 1] @@ -625,7 +659,9 @@ def _get_area_clear_action( clear_x = int(agent_x + vector[0]) clear_y = int(agent_y + vector[1]) return ActionClearArea(clear_x, clear_y) - return ActionMove([police_entity.get_position()], clear_x, clear_y) + position = police_entity.get_position() + if position is not None: + return ActionMove([position], clear_x, clear_y) return None @@ -637,7 +673,12 @@ def _get_neighbour_position_action( ) -> Optional[Action]: agent_x = police_entity.get_x() agent_y = police_entity.get_y() - position = self.world_info.get_entity(police_entity.get_position()) + if agent_x is None or agent_y is None: + return None + position_id = police_entity.get_position() + if position_id is None: + return None + position = self.world_info.get_entity(position_id) if position is None: return None @@ -646,7 +687,7 @@ def _get_neighbour_position_action( return None if isinstance(position, Road): - road = cast(Road, position) + road = position if road.get_blockades() != []: mid_x = (edge.get_start_x() + edge.get_end_x()) / 2.0 mid_y = (edge.get_start_y() + edge.get_end_y()) / 2.0 @@ -716,7 +757,7 @@ def _get_neighbour_position_action( return action_move if isinstance(target, Road): - road = cast(Road, target) + road = target if road.get_blockades() == []: return ActionMove([position.get_entity_id(), target.get_entity_id()]) @@ -726,6 +767,8 @@ def _get_neighbour_position_action( clear_y = 0 for blockade in self.world_info.get_blockades(road): apexes = blockade.get_apexes() + if apexes is None or len(apexes) < 4: + continue for i in range(0, len(apexes) - 2, 2): distance = self._get_distance( agent_x, agent_y, apexes[i], apexes[i + 1] diff --git a/adf_core_python/implement/action/default_extend_action_rescue.py b/adf_core_python/implement/action/default_extend_action_rescue.py index df1e067..b4624bb 100644 --- a/adf_core_python/implement/action/default_extend_action_rescue.py +++ b/adf_core_python/implement/action/default_extend_action_rescue.py @@ -117,7 +117,7 @@ def _calc_rescue( return None if isinstance(target_entity, Human): - human = cast(Human, target_entity) + human = target_entity if human.get_hp() == 0: return None @@ -139,7 +139,7 @@ def _calc_rescue( return None if isinstance(target_entity, Blockade): - blockade = cast(Blockade, target_entity) + blockade = target_entity blockade_position = blockade.get_position() if blockade_position is None: return None diff --git a/adf_core_python/implement/action/default_extend_action_transport.py b/adf_core_python/implement/action/default_extend_action_transport.py index a64b23c..5fe0b9d 100644 --- a/adf_core_python/implement/action/default_extend_action_transport.py +++ b/adf_core_python/implement/action/default_extend_action_transport.py @@ -125,6 +125,8 @@ def calc_rescue( return None agent_position = agent.get_position() + if agent_position is None: + return None if isinstance(target_entity, Human): human = target_entity if human.get_position() is None: @@ -133,6 +135,8 @@ def calc_rescue( return None target_position = human.get_position() + if target_position is None: + return None if agent_position == target_position: if isinstance(human, Civilian) and ((human.get_buriedness() or 0) == 0): return ActionLoad(human.get_entity_id()) @@ -143,6 +147,8 @@ def calc_rescue( return None if isinstance(target_entity, Area): + if agent_position is None: + return None path = path_planning.get_path(agent_position, target_entity.get_entity_id()) if path is not None and len(path) > 0: return ActionMove(path) @@ -163,6 +169,8 @@ def calc_unload( return ActionUnload() agent_position = agent.get_position() + if agent_position is None: + return None if target_id is None or transport_human.get_entity_id() == target_id: position = self.world_info.get_entity(agent_position) if position is None: @@ -181,10 +189,11 @@ def calc_unload( target_entity = self.world_info.get_entity(target_id) if isinstance(target_entity, Human): - human = cast(Human, target_entity) - if human.get_position() is not None: + human = target_entity + human_position = human.get_position() + if human_position is not None: return self.calc_refuge_action( - agent, path_planning, human.get_position(), True + agent, path_planning, human_position, True ) path = self.get_nearest_refuge_path(agent, path_planning) if path is not None and len(path) > 0: @@ -200,6 +209,8 @@ def calc_refuge_action( is_unload: bool, ) -> Optional[ActionMove | ActionUnload | ActionRest]: position = human.get_position() + if position is None: + return None refuges = self.world_info.get_entity_ids_of_types([Refuge]) size = len(refuges) @@ -235,6 +246,8 @@ def get_nearest_refuge_path( self, human: Human, path_planning: PathPlanning ) -> list[EntityID]: position = human.get_position() + if position is None: + return [] refuges = self.world_info.get_entity_ids_of_types([Refuge]) nearest_path = None diff --git a/adf_core_python/implement/centralized/default_command_executor_ambulance.py b/adf_core_python/implement/centralized/default_command_executor_ambulance.py index aa6bf81..d4783c6 100644 --- a/adf_core_python/implement/centralized/default_command_executor_ambulance.py +++ b/adf_core_python/implement/centralized/default_command_executor_ambulance.py @@ -254,8 +254,8 @@ def _is_command_completed(self) -> bool: if isinstance(human, Civilian): self._command_type = self.ACTION_RESCUE return self._is_command_completed() - if human.get_position() is not None: - position = human.get_position() + position = human.get_position() + if position is not None: if position in self._world_info.get_entity_ids_of_types( [AmbulanceTeam] ): diff --git a/adf_core_python/implement/centralized/default_command_executor_police.py b/adf_core_python/implement/centralized/default_command_executor_police.py index c5d756b..abfb51f 100644 --- a/adf_core_python/implement/centralized/default_command_executor_police.py +++ b/adf_core_python/implement/centralized/default_command_executor_police.py @@ -202,11 +202,13 @@ def _is_command_completed(self) -> bool: match self._command_type: case self.ACTION_REST: if self._target is None: - return agent.get_damage() == 0 + damage = agent.get_damage() + return damage is not None and damage == 0 if (target_entity := self._world_info.get_entity(self._target)) is None: return False if isinstance(target_entity, Refuge): - return agent.get_damage() == 0 + damage = agent.get_damage() + return damage is not None and damage == 0 return False case self.ACTION_MOVE: return ( @@ -218,17 +220,19 @@ def _is_command_completed(self) -> bool: return True entity = self._world_info.get_entity(self._target) if isinstance(entity, Road): - if entity.get_blockades is not None: - return len(entity.get_blockades()) == 0 + blockades = entity.get_blockades() + if blockades is not None: + return len(blockades) == 0 return self._agent_info.get_position_entity_id() == self._target return True case self.ACTION_AUTONOMY: if self._target is not None: target_entity = self._world_info.get_entity(self._target) if isinstance(target_entity, Refuge): + damage = agent.get_damage() self._command_type = ( self.ACTION_REST - if agent.get_damage() > 0 + if damage is not None and damage > 0 else self.ACTION_CLEAR ) return self._is_command_completed() @@ -238,8 +242,9 @@ def _is_command_completed(self) -> bool: elif isinstance(target_entity, Human): if target_entity.get_hp() == 0: return True - if target_entity.get_position() is not None and isinstance( - self._world_info.get_entity(target_entity.get_position()), + position = target_entity.get_position() + if position is not None and isinstance( + self._world_info.get_entity(position), Area, ): self._target = target_entity.get_position() diff --git a/adf_core_python/implement/centralized/default_command_executor_scout.py b/adf_core_python/implement/centralized/default_command_executor_scout.py index 0ff628b..7941ed5 100644 --- a/adf_core_python/implement/centralized/default_command_executor_scout.py +++ b/adf_core_python/implement/centralized/default_command_executor_scout.py @@ -1,6 +1,6 @@ from typing import Optional, cast -from rcrscore.entities import Area, EntityID, Human, Refuge +from rcrscore.entities import Building, EntityID, Human, Refuge, Road from adf_core_python.core.agent.action.common.action_move import ActionMove from adf_core_python.core.agent.communication.message_manager import MessageManager @@ -59,6 +59,8 @@ def set_command(self, command: CommandScout) -> CommandExecutor: target = command.get_command_target_entity_id() if target is None: target = self._agent_info.get_position_entity_id() + if target is None: + return self self._command_type = self.ACTION_SCOUT self._commander = command.get_sender_entity_id() @@ -66,7 +68,7 @@ def set_command(self, command: CommandScout) -> CommandExecutor: if (scout_distance := command.get_scout_range()) is None: return self - for entity in self._world_info.get_entities_of_types([Area]): + for entity in self._world_info.get_entities_of_types([Road, Building]): if isinstance(entity, Refuge): continue if ( @@ -83,9 +85,10 @@ def calculate(self) -> CommandExecutor: case self.ACTION_SCOUT: if len(self._targets) == 0: return self - path = self._path_planning.get_path( - self._agent_info.get_position_entity_id(), self._targets[0] - ) + agent_position = self._agent_info.get_position_entity_id() + if agent_position is None: + return self + path = self._path_planning.get_path(agent_position, self._targets[0]) if path is None: return self self._result = ActionMove(path) @@ -150,7 +153,9 @@ def _is_command_completed(self) -> bool: match self._command_type: case self.ACTION_SCOUT: if len(self._targets) != 0: - for entity in self._world_info.get_entities_of_types([Area]): + for entity in self._world_info.get_entities_of_types( + [Road, Building] + ): self._targets.remove(entity.get_entity_id()) return len(self._targets) == 0 case _: diff --git a/adf_core_python/implement/centralized/default_command_executor_scout_police.py b/adf_core_python/implement/centralized/default_command_executor_scout_police.py index c80d68a..f1806ad 100644 --- a/adf_core_python/implement/centralized/default_command_executor_scout_police.py +++ b/adf_core_python/implement/centralized/default_command_executor_scout_police.py @@ -1,6 +1,6 @@ from typing import Optional, cast -from rcrscore.entities import Area, EntityID, Human, Refuge +from rcrscore.entities import Building, EntityID, Human, Refuge, Road from adf_core_python.core.agent.action.common.action_move import ActionMove from adf_core_python.core.agent.communication.message_manager import MessageManager @@ -64,6 +64,8 @@ def set_command(self, command: CommandScout) -> CommandExecutor: target = command.get_command_target_entity_id() if target is None: target = self._agent_info.get_position_entity_id() + if target is None: + return self self._command_type = self.ACTION_SCOUT self._commander = command.get_sender_entity_id() @@ -71,7 +73,7 @@ def set_command(self, command: CommandScout) -> CommandExecutor: if (scout_distance := command.get_scout_range()) is None: return self - for entity in self._world_info.get_entities_of_types([Area]): + for entity in self._world_info.get_entities_of_types([Road, Building, Refuge]): if isinstance(entity, Refuge): continue if ( @@ -88,9 +90,10 @@ def calculate(self) -> CommandExecutor: case self.ACTION_SCOUT: if len(self._targets) == 0: return self - path = self._path_planning.get_path( - self._agent_info.get_position_entity_id(), self._targets[0] - ) + agent_position = self._agent_info.get_position_entity_id() + if agent_position is None: + return self + path = self._path_planning.get_path(agent_position, self._targets[0]) if path is None: return self action = ( @@ -162,7 +165,9 @@ def _is_command_completed(self) -> bool: match self._command_type: case self.ACTION_SCOUT: if len(self._targets) != 0: - for entity in self._world_info.get_entities_of_types([Area]): + for entity in self._world_info.get_entities_of_types( + [Road, Building, Refuge] + ): self._targets.remove(entity.get_entity_id()) return len(self._targets) == 0 case _: diff --git a/adf_core_python/implement/module/algorithm/a_star_path_planning.py b/adf_core_python/implement/module/algorithm/a_star_path_planning.py index 5a80339..e31cef3 100644 --- a/adf_core_python/implement/module/algorithm/a_star_path_planning.py +++ b/adf_core_python/implement/module/algorithm/a_star_path_planning.py @@ -1,6 +1,6 @@ from __future__ import annotations -from rcrscore.entities import Area, Entity, EntityID +from rcrscore.entities import Area, Building, Entity, EntityID, Road from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -24,7 +24,9 @@ def __init__( super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - entities: list[Entity] = self._world_info.get_entities_of_types([Area]) + entities: list[Entity] = self._world_info.get_entities_of_types( + [Building, Road] + ) self._graph: dict[EntityID, set[EntityID]] = {} for entity in entities: if isinstance(entity, Area): diff --git a/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py b/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py index 1a592a8..dac1549 100644 --- a/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py +++ b/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py @@ -3,7 +3,7 @@ import heapq from typing import Optional -from rcrscore.entities import Area, EntityID +from rcrscore.entities import Area, Building, EntityID, Road from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -27,7 +27,7 @@ def __init__( ) self.graph: dict[EntityID, list[tuple[EntityID, float]]] = {} # グラフの構築 - for area in self._world_info.get_entities_of_types([Area]): + for area in self._world_info.get_entities_of_types([Road, Building]): if not isinstance(area, Area): continue if (neighbors := area.get_neighbors()) is None: @@ -71,10 +71,10 @@ def get_path( previous[neighbor] = current_node path: list[EntityID] = [] - current_node = to_entity_id - while current_node is not None: - path.append(current_node) - current_node = previous[current_node] + current_path_node: Optional[EntityID] = to_entity_id + while current_path_node is not None: + path.append(current_path_node) + current_path_node = previous.get(current_path_node) return path[::-1] diff --git a/adf_core_python/implement/module/algorithm/k_means_clustering.py b/adf_core_python/implement/module/algorithm/k_means_clustering.py index 9fe0517..a567abe 100644 --- a/adf_core_python/implement/module/algorithm/k_means_clustering.py +++ b/adf_core_python/implement/module/algorithm/k_means_clustering.py @@ -35,7 +35,10 @@ def __init__( super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - match agent_info.get_myself().get_urn(): + myself = agent_info.get_myself() + if myself is None: + raise RuntimeError("Could not get agent entity") + match myself.get_urn(): case EntityURN.AMBULANCE_TEAM: self._cluster_number = int( scenario_info.get_value( @@ -63,7 +66,7 @@ def __init__( sorted_entities = sorted( world_info.get_entities_of_types( [ - agent_info.get_myself().__class__, + myself.__class__, ] ), key=lambda entity: entity.get_entity_id().get_value(), diff --git a/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py b/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py index 50f0f35..bdeb89a 100644 --- a/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py +++ b/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py @@ -109,9 +109,10 @@ def _get_action_agents( ) -> list[AmbulanceTeam]: result = [] for entity in self._world_info.get_entities_of_types([AmbulanceTeam]): - info = info_map[entity.get_entity_id()] - if info is not None and info._can_new_action: - result.append(entity) + if isinstance(entity, AmbulanceTeam): + info = info_map[entity.get_entity_id()] + if info is not None and info._can_new_action: + result.append(entity) return result def _compare_by_distance( diff --git a/adf_core_python/implement/module/complex/default_fire_target_allocator.py b/adf_core_python/implement/module/complex/default_fire_target_allocator.py index c32e716..4373d5d 100644 --- a/adf_core_python/implement/module/complex/default_fire_target_allocator.py +++ b/adf_core_python/implement/module/complex/default_fire_target_allocator.py @@ -108,9 +108,10 @@ def _get_action_agents( ) -> list[FireBrigade]: result = [] for entity in self._world_info.get_entities_of_types([FireBrigade]): - info = info_map[entity.get_entity_id()] - if info is not None and info._can_new_action: - result.append(entity) + if isinstance(entity, FireBrigade): + info = info_map[entity.get_entity_id()] + if info is not None and info._can_new_action: + result.append(entity) return result def _compare_by_distance( diff --git a/adf_core_python/implement/module/complex/default_human_detector.py b/adf_core_python/implement/module/complex/default_human_detector.py index a667524..8f5e860 100644 --- a/adf_core_python/implement/module/complex/default_human_detector.py +++ b/adf_core_python/implement/module/complex/default_human_detector.py @@ -124,6 +124,8 @@ def _is_valid_human(self, target_entity_id: EntityID) -> bool: if buriedness is None: return False myself = self._agent_info.get_myself() + if myself is None: + return False if myself.get_urn() == EntityURN.FIRE_BRIGADE and buriedness == 0: return False if myself.get_urn() == EntityURN.AMBULANCE_TEAM and buriedness > 0: diff --git a/adf_core_python/implement/module/complex/default_police_target_allocator.py b/adf_core_python/implement/module/complex/default_police_target_allocator.py index 0de8b3f..9499ebb 100644 --- a/adf_core_python/implement/module/complex/default_police_target_allocator.py +++ b/adf_core_python/implement/module/complex/default_police_target_allocator.py @@ -108,13 +108,13 @@ def calculate(self) -> PoliceTargetAllocator: agents = sorted( agents, key=cmp_to_key(self._compare_by_distance(target_entity)) ) - result = agents.pop(0) - info = self._agent_info_map[result.get_entity_id()] + selected_agent = agents.pop(0) + info = self._agent_info_map[selected_agent.get_entity_id()] if info is not None: info._can_new_action = False info._target = target info.command_time = current_time - self._agent_info_map[result.get_entity_id()] = info + self._agent_info_map[selected_agent.get_entity_id()] = info removes.append(target) for r in removes: @@ -129,12 +129,12 @@ def calculate(self) -> PoliceTargetAllocator: for agent in agents: if len(areas) > 0: areas.sort(key=cmp_to_key(self._compare_by_distance(agent))) - result = areas.pop(0) - self._target_areas.remove(result.get_entity_id()) + target_area: Entity = areas.pop(0) + self._target_areas.remove(target_area.get_entity_id()) info = self._agent_info_map[agent.get_entity_id()] if info is not None: info._can_new_action = False - info._target = result.get_entity_id() + info._target = target_area.get_entity_id() info.command_time = current_time self._agent_info_map[agent.get_entity_id()] = info @@ -148,9 +148,10 @@ def _get_action_agents( ) -> list[PoliceForce]: result = [] for entity in self._world_info.get_entities_of_types([PoliceForce]): - info = info_map[entity.get_entity_id()] - if info is not None and info._can_new_action: - result.append(entity) + if isinstance(entity, PoliceForce): + info = info_map[entity.get_entity_id()] + if info is not None and info._can_new_action: + result.append(entity) return result def _compare_by_distance( diff --git a/adf_core_python/implement/module/complex/default_road_detector.py b/adf_core_python/implement/module/complex/default_road_detector.py index ad837a4..ed7a02d 100644 --- a/adf_core_python/implement/module/complex/default_road_detector.py +++ b/adf_core_python/implement/module/complex/default_road_detector.py @@ -37,7 +37,7 @@ def __init__( ) self.register_sub_module(self._path_planning) - self._result = None + self._result: Optional[EntityID] = None def precompute(self, precompute_data: PrecomputeData) -> RoadDetector: super().precompute(precompute_data) @@ -108,7 +108,7 @@ def update_info(self, message_manager: MessageManager) -> RoadDetector: if isinstance(entity, Building): self._result = None elif isinstance(entity, Road): - road: Road = cast(Road, entity) + road = entity if road.get_blockades() == []: self._target_areas.remove(self._result) self._result = None @@ -117,7 +117,9 @@ def update_info(self, message_manager: MessageManager) -> RoadDetector: def calculate(self) -> RoadDetector: if self._result is None: - position_entity_id: EntityID = self._agent_info.get_position_entity_id() + position_entity_id = self._agent_info.get_position_entity_id() + if position_entity_id is None: + return self if position_entity_id in self._target_areas: self._result = position_entity_id return self @@ -128,21 +130,22 @@ def calculate(self) -> RoadDetector: self._priority_roads = self._priority_roads - set(remove_list) if len(self._priority_roads) > 0: - _nearest_target_area = self._agent_info.get_position_entity_id() + agent_position = self._agent_info.get_position_entity_id() + if agent_position is None: + return self + _nearest_target_area = agent_position _nearest_distance = float("inf") for target_area in self._target_areas: if ( - self._world_info.get_distance( - self._agent_info.get_position_entity_id(), target_area - ) + self._world_info.get_distance(agent_position, target_area) < _nearest_distance ): _nearest_target_area = target_area _nearest_distance = self._world_info.get_distance( - self._agent_info.get_position_entity_id(), target_area + agent_position, target_area ) path: list[EntityID] = self._path_planning.get_path( - self._agent_info.get_position_entity_id(), _nearest_target_area + agent_position, _nearest_target_area ) if path is not None and len(path) > 0: self._result = path[-1] diff --git a/adf_core_python/implement/module/complex/default_search.py b/adf_core_python/implement/module/complex/default_search.py index 0d5c714..1050029 100644 --- a/adf_core_python/implement/module/complex/default_search.py +++ b/adf_core_python/implement/module/complex/default_search.py @@ -64,7 +64,8 @@ def update_info(self, message_manager: MessageManager) -> Search: ) searched_building_id = self._agent_info.get_position_entity_id() - self._unreached_building_ids.discard(searched_building_id) + if searched_building_id is not None: + self._unreached_building_ids.discard(searched_building_id) if len(self._unreached_building_ids) == 0: self._unreached_building_ids = self._get_search_targets() From 44d72c52ffc62006d414411f74a4d67fac7be99d Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 15:05:48 +0900 Subject: [PATCH 235/249] move: add src file --- .../core/agent/precompute/precompute_data.py | 75 ------------------- adf_core_python/implement/tactics/__init__.py | 0 .../adf_core_python}/__init__.py | 0 .../adf_core_python}/cli/__init__.py | 0 .../adf_core_python}/cli/cli.py | 0 .../cli/template/config/development.json | 0 .../cli/template/config/launcher.yaml | 0 .../cli/template/config/module.yaml | 0 .../adf_core_python}/cli/template/main.py | 0 .../cli/template/src/team_name/__init__.py | 0 .../template/src/team_name/module/__init__.py | 0 .../src/team_name/module/complex/__init__.py | 0 .../module/complex/sample_human_detector.py | 0 .../module/complex/sample_road_detector.py | 0 .../team_name/module/complex/sample_search.py | 0 .../adf_core_python}/core/__init__.py | 0 .../adf_core_python}/core/agent/__init__.py | 0 .../core/agent/action/__init__.py | 0 .../core/agent/action/action.py | 0 .../core/agent/action/ambulance/__init__.py | 0 .../agent/action/ambulance/action_load.py | 0 .../agent/action/ambulance/action_rescue.py | 0 .../agent/action/ambulance/action_unload.py | 0 .../core/agent/action/common/__init__.py | 0 .../core/agent/action/common/action_move.py | 0 .../core/agent/action/common/action_rest.py | 0 .../core/agent/action/fire/__init__.py | 0 .../agent/action/fire/action_extinguish.py | 0 .../core/agent/action/fire/action_refill.py | 0 .../core/agent/action/fire/action_rescue.py | 0 .../core/agent/action/police/__init__.py | 0 .../core/agent/action/police/action_clear.py | 0 .../agent/action/police/action_clear_area.py | 0 .../adf_core_python}/core/agent/agent.py | 0 .../core/agent/communication/__init__.py | 0 .../agent/communication/message_manager.py | 0 .../agent/communication/standard/__init__.py | 0 .../communication/standard/bundle/__init__.py | 0 .../standard/bundle/centralized/__init__.py | 0 .../bundle/centralized/command_ambulance.py | 0 .../bundle/centralized/command_fire.py | 0 .../bundle/centralized/command_police.py | 0 .../bundle/centralized/command_scout.py | 0 .../bundle/centralized/message_report.py | 0 .../standard/bundle/information/__init__.py | 0 .../information/message_ambulance_team.py | 0 .../bundle/information/message_building.py | 0 .../bundle/information/message_civilian.py | 0 .../information/message_fire_brigade.py | 0 .../information/message_police_force.py | 0 .../bundle/information/message_road.py | 0 .../standard/bundle/standard_message.py | 0 .../bundle/standard_message_priority.py | 0 .../standard/standard_communication_module.py | 0 .../standard/utility/__init__.py | 0 .../standard/utility/apply_to_world_info.py | 0 .../utility/bitarray_with_exits_flag.py | 0 .../core/agent/config/__init__.py | 0 .../core/agent/config/module_config.py | 0 .../core/agent/develop/__init__.py | 0 .../core/agent/develop/develop_data.py | 0 .../core/agent/info/__init__.py | 0 .../core/agent/info/agent_info.py | 0 .../core/agent/info/scenario_info.py | 0 .../core/agent/info/world_info.py | 0 .../core/agent/module/__init__.py | 0 .../core/agent/module/module_manager.py | 0 .../core/agent/office/office.py | 0 .../core/agent/office/office_ambulance.py | 0 .../core/agent/office/office_fire.py | 0 .../core/agent/office/office_police.py | 0 .../core/agent/platoon/__init__.py | 0 .../core/agent/platoon/platoon.py | 0 .../core/agent/platoon/platoon_ambulance.py | 0 .../core/agent/platoon/platoon_fire.py | 0 .../core/agent/platoon/platoon_police.py | 0 .../core/component}/__init__.py | 0 .../core/component/abstract_loader.py | 0 .../core/component/action}/__init__.py | 0 .../core/component/action/extend_action.py | 0 .../component/centralized/command_executor.py | 0 .../component/centralized/command_picker.py | 0 .../core/component/communication}/__init__.py | 0 .../communication/channel_subscriber.py | 0 .../communication/communication_message.py | 0 .../communication/communication_module.py | 0 .../communication/message_coordinator.py | 0 .../core/component/module}/__init__.py | 0 .../core/component/module/abstract_module.py | 0 .../component/module/algorithm}/__init__.py | 0 .../component/module/algorithm/clustering.py | 0 .../module/algorithm/path_planning.py | 0 .../component/module/complex}/__init__.py | 0 .../complex/ambulance_target_allocator.py | 0 .../module/complex/fire_target_allocator.py | 0 .../module/complex/human_detector.py | 0 .../module/complex/police_target_allocator.py | 0 .../component/module/complex/road_detector.py | 0 .../core/component/module/complex/search.py | 0 .../module/complex/target_allocator.py | 0 .../module/complex/target_detector.py | 0 .../core/component/tactics}/__init__.py | 0 .../core/component/tactics/tactics_agent.py | 0 .../tactics/tactics_ambulance_center.py | 0 .../tactics/tactics_ambulance_team.py | 0 .../core/component/tactics/tactics_center.py | 0 .../component/tactics/tactics_fire_brigade.py | 0 .../component/tactics/tactics_fire_station.py | 0 .../component/tactics/tactics_police_force.py | 0 .../tactics/tactics_police_office.py | 0 .../adf_core_python/core/config}/__init__.py | 0 .../adf_core_python}/core/config/config.py | 0 .../adf_core_python/core/gateway}/__init__.py | 0 .../core/gateway/component}/__init__.py | 0 .../gateway/component/module}/__init__.py | 0 .../component/module/algorithm}/__init__.py | 0 .../module/algorithm/gateway_clustering.py | 0 .../module/algorithm/gateway_path_planning.py | 0 .../component/module/complex}/__init__.py | 0 .../gateway_ambulance_target_allocator.py | 0 .../complex/gateway_fire_target_allocator.py | 0 .../module/complex/gateway_human_detector.py | 0 .../gateway_police_target_allocator.py | 0 .../module/complex/gateway_road_detector.py | 0 .../module/complex/gateway_search.py | 0 .../complex/gateway_target_allocator.py | 0 .../module/complex/gateway_target_detector.py | 0 .../module/gateway_abstract_module.py | 0 .../core/gateway/gateway_agent.py | 0 .../core/gateway/gateway_launcher.py | 0 .../core/gateway/gateway_module.py | 0 .../core/gateway/message}/__init__.py | 0 .../core/gateway/message/am_agent.py | 0 .../core/gateway/message/am_exec.py | 0 .../core/gateway/message/am_module.py | 0 .../core/gateway/message/am_update.py | 0 .../core/gateway/message/ma_exec_response.py | 0 .../gateway/message/ma_module_response.py | 0 .../gateway/message/moduleMessageFactory.py | 0 .../core/gateway/message/urn}/__init__.py | 0 .../core/gateway/message/urn/urn.py | 0 .../core/gateway/module_dict.py | 0 .../core/launcher}/__init__.py | 0 .../core/launcher/agent_launcher.py | 0 .../core/launcher/config_key.py | 0 .../core/launcher/connect}/__init__.py | 0 .../launcher/connect/component_launcher.py | 0 .../core/launcher/connect/connection.py | 0 .../core/launcher/connect/connector.py | 0 .../connect/connector_ambulance_center.py | 0 .../connect/connector_ambulance_team.py | 0 .../connect/connector_fire_brigade.py | 0 .../connect/connector_fire_station.py | 0 .../connect/connector_police_force.py | 0 .../connect/connector_police_office.py | 0 .../launcher/connect/error/agent_error.py | 0 .../launcher/connect/error/server_error.py | 0 .../adf_core_python/core/logger}/__init__.py | 0 .../adf_core_python}/core/logger/logger.py | 0 .../adf_core_python/implement}/__init__.py | 0 .../implement/action}/__init__.py | 0 .../action/default_extend_action_clear.py | 0 .../action/default_extend_action_move.py | 0 .../action/default_extend_action_rescue.py | 0 .../action/default_extend_action_transport.py | 0 .../default_command_executor_ambulance.py | 0 .../default_command_executor_fire.py | 0 .../default_command_executor_police.py | 0 .../default_command_executor_scout.py | 0 .../default_command_executor_scout_police.py | 0 .../default_command_picker_ambulance.py | 0 .../default_command_picker_fire.py | 0 .../default_command_picker_police.py | 0 .../implement/default_loader.py | 0 .../implement/module}/__init__.py | 0 .../implement/module/algorithm}/__init__.py | 0 .../module/algorithm/a_star_path_planning.py | 0 .../algorithm/dijkstra_path_planning.py | 0 .../module/algorithm/k_means_clustering.py | 0 .../module/communication}/__init__.py | 0 .../default_channel_subscriber.py | 0 .../default_message_coordinator.py | 0 .../implement/module/complex}/__init__.py | 0 .../default_ambulance_target_allocator.py | 0 .../complex/default_fire_target_allocator.py | 0 .../module/complex/default_human_detector.py | 0 .../default_police_target_allocator.py | 0 .../module/complex/default_road_detector.py | 0 .../module/complex/default_search.py | 0 .../implement/tactics}/__init__.py | 0 .../default_tactics_ambulance_center.py | 0 .../tactics/default_tactics_ambulance_team.py | 0 .../tactics/default_tactics_fire_brigade.py | 0 .../tactics/default_tactics_fire_station.py | 0 .../tactics/default_tactics_police_force.py | 0 .../tactics/default_tactics_police_office.py | 0 .../adf_core_python}/launcher.py | 0 197 files changed, 75 deletions(-) delete mode 100644 adf_core_python/core/agent/precompute/precompute_data.py delete mode 100644 adf_core_python/implement/tactics/__init__.py rename {adf_core_python => src/adf_core_python}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/cli/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/cli/cli.py (100%) rename {adf_core_python => src/adf_core_python}/cli/template/config/development.json (100%) rename {adf_core_python => src/adf_core_python}/cli/template/config/launcher.yaml (100%) rename {adf_core_python => src/adf_core_python}/cli/template/config/module.yaml (100%) rename {adf_core_python => src/adf_core_python}/cli/template/main.py (100%) rename {adf_core_python => src/adf_core_python}/cli/template/src/team_name/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/cli/template/src/team_name/module/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/cli/template/src/team_name/module/complex/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/cli/template/src/team_name/module/complex/sample_human_detector.py (100%) rename {adf_core_python => src/adf_core_python}/cli/template/src/team_name/module/complex/sample_road_detector.py (100%) rename {adf_core_python => src/adf_core_python}/cli/template/src/team_name/module/complex/sample_search.py (100%) rename {adf_core_python => src/adf_core_python}/core/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/action.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/ambulance/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/ambulance/action_load.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/ambulance/action_rescue.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/ambulance/action_unload.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/common/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/common/action_move.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/common/action_rest.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/fire/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/fire/action_extinguish.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/fire/action_refill.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/fire/action_rescue.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/police/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/police/action_clear.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/action/police/action_clear_area.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/agent.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/message_manager.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/centralized/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/centralized/command_ambulance.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/centralized/command_fire.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/centralized/command_police.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/centralized/command_scout.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/centralized/message_report.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/information/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/information/message_ambulance_team.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/information/message_building.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/information/message_civilian.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/information/message_fire_brigade.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/information/message_police_force.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/information/message_road.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/standard_message.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/bundle/standard_message_priority.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/standard_communication_module.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/utility/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/utility/apply_to_world_info.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/communication/standard/utility/bitarray_with_exits_flag.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/config/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/config/module_config.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/develop/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/develop/develop_data.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/info/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/info/agent_info.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/info/scenario_info.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/info/world_info.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/module/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/module/module_manager.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/office/office.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/office/office_ambulance.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/office/office_fire.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/office/office_police.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/platoon/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/platoon/platoon.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/platoon/platoon_ambulance.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/platoon/platoon_fire.py (100%) rename {adf_core_python => src/adf_core_python}/core/agent/platoon/platoon_police.py (100%) rename {adf_core_python/core/agent/precompute => src/adf_core_python/core/component}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/abstract_loader.py (100%) rename {adf_core_python/core/component => src/adf_core_python/core/component/action}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/action/extend_action.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/centralized/command_executor.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/centralized/command_picker.py (100%) rename {adf_core_python/core/component/action => src/adf_core_python/core/component/communication}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/communication/channel_subscriber.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/communication/communication_message.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/communication/communication_module.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/communication/message_coordinator.py (100%) rename {adf_core_python/core/component/communication => src/adf_core_python/core/component/module}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/module/abstract_module.py (100%) rename {adf_core_python/core/component/module => src/adf_core_python/core/component/module/algorithm}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/module/algorithm/clustering.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/module/algorithm/path_planning.py (100%) rename {adf_core_python/core/component/module/algorithm => src/adf_core_python/core/component/module/complex}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/module/complex/ambulance_target_allocator.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/module/complex/fire_target_allocator.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/module/complex/human_detector.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/module/complex/police_target_allocator.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/module/complex/road_detector.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/module/complex/search.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/module/complex/target_allocator.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/module/complex/target_detector.py (100%) rename {adf_core_python/core/component/module/complex => src/adf_core_python/core/component/tactics}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/tactics/tactics_agent.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/tactics/tactics_ambulance_center.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/tactics/tactics_ambulance_team.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/tactics/tactics_center.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/tactics/tactics_fire_brigade.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/tactics/tactics_fire_station.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/tactics/tactics_police_force.py (100%) rename {adf_core_python => src/adf_core_python}/core/component/tactics/tactics_police_office.py (100%) rename {adf_core_python/core/component/tactics => src/adf_core_python/core/config}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/config/config.py (100%) rename {adf_core_python/core/config => src/adf_core_python/core/gateway}/__init__.py (100%) rename {adf_core_python/core/gateway => src/adf_core_python/core/gateway/component}/__init__.py (100%) rename {adf_core_python/core/gateway/component => src/adf_core_python/core/gateway/component/module}/__init__.py (100%) rename {adf_core_python/core/gateway/component/module => src/adf_core_python/core/gateway/component/module/algorithm}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/component/module/algorithm/gateway_clustering.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/component/module/algorithm/gateway_path_planning.py (100%) rename {adf_core_python/core/gateway/component/module/algorithm => src/adf_core_python/core/gateway/component/module/complex}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/component/module/complex/gateway_ambulance_target_allocator.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/component/module/complex/gateway_fire_target_allocator.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/component/module/complex/gateway_human_detector.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/component/module/complex/gateway_police_target_allocator.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/component/module/complex/gateway_road_detector.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/component/module/complex/gateway_search.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/component/module/complex/gateway_target_allocator.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/component/module/complex/gateway_target_detector.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/component/module/gateway_abstract_module.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/gateway_agent.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/gateway_launcher.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/gateway_module.py (100%) rename {adf_core_python/core/gateway/component/module/complex => src/adf_core_python/core/gateway/message}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/message/am_agent.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/message/am_exec.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/message/am_module.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/message/am_update.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/message/ma_exec_response.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/message/ma_module_response.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/message/moduleMessageFactory.py (100%) rename {adf_core_python/core/gateway/message => src/adf_core_python/core/gateway/message/urn}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/message/urn/urn.py (100%) rename {adf_core_python => src/adf_core_python}/core/gateway/module_dict.py (100%) rename {adf_core_python/core/gateway/message/urn => src/adf_core_python/core/launcher}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/launcher/agent_launcher.py (100%) rename {adf_core_python => src/adf_core_python}/core/launcher/config_key.py (100%) rename {adf_core_python/core/launcher => src/adf_core_python/core/launcher/connect}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/launcher/connect/component_launcher.py (100%) rename {adf_core_python => src/adf_core_python}/core/launcher/connect/connection.py (100%) rename {adf_core_python => src/adf_core_python}/core/launcher/connect/connector.py (100%) rename {adf_core_python => src/adf_core_python}/core/launcher/connect/connector_ambulance_center.py (100%) rename {adf_core_python => src/adf_core_python}/core/launcher/connect/connector_ambulance_team.py (100%) rename {adf_core_python => src/adf_core_python}/core/launcher/connect/connector_fire_brigade.py (100%) rename {adf_core_python => src/adf_core_python}/core/launcher/connect/connector_fire_station.py (100%) rename {adf_core_python => src/adf_core_python}/core/launcher/connect/connector_police_force.py (100%) rename {adf_core_python => src/adf_core_python}/core/launcher/connect/connector_police_office.py (100%) rename {adf_core_python => src/adf_core_python}/core/launcher/connect/error/agent_error.py (100%) rename {adf_core_python => src/adf_core_python}/core/launcher/connect/error/server_error.py (100%) rename {adf_core_python/core/launcher/connect => src/adf_core_python/core/logger}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/core/logger/logger.py (100%) rename {adf_core_python/core/logger => src/adf_core_python/implement}/__init__.py (100%) rename {adf_core_python/implement => src/adf_core_python/implement/action}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/implement/action/default_extend_action_clear.py (100%) rename {adf_core_python => src/adf_core_python}/implement/action/default_extend_action_move.py (100%) rename {adf_core_python => src/adf_core_python}/implement/action/default_extend_action_rescue.py (100%) rename {adf_core_python => src/adf_core_python}/implement/action/default_extend_action_transport.py (100%) rename {adf_core_python => src/adf_core_python}/implement/centralized/default_command_executor_ambulance.py (100%) rename {adf_core_python => src/adf_core_python}/implement/centralized/default_command_executor_fire.py (100%) rename {adf_core_python => src/adf_core_python}/implement/centralized/default_command_executor_police.py (100%) rename {adf_core_python => src/adf_core_python}/implement/centralized/default_command_executor_scout.py (100%) rename {adf_core_python => src/adf_core_python}/implement/centralized/default_command_executor_scout_police.py (100%) rename {adf_core_python => src/adf_core_python}/implement/centralized/default_command_picker_ambulance.py (100%) rename {adf_core_python => src/adf_core_python}/implement/centralized/default_command_picker_fire.py (100%) rename {adf_core_python => src/adf_core_python}/implement/centralized/default_command_picker_police.py (100%) rename {adf_core_python => src/adf_core_python}/implement/default_loader.py (100%) rename {adf_core_python/implement/action => src/adf_core_python/implement/module}/__init__.py (100%) rename {adf_core_python/implement/module => src/adf_core_python/implement/module/algorithm}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/implement/module/algorithm/a_star_path_planning.py (100%) rename {adf_core_python => src/adf_core_python}/implement/module/algorithm/dijkstra_path_planning.py (100%) rename {adf_core_python => src/adf_core_python}/implement/module/algorithm/k_means_clustering.py (100%) rename {adf_core_python/implement/module/algorithm => src/adf_core_python/implement/module/communication}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/implement/module/communication/default_channel_subscriber.py (100%) rename {adf_core_python => src/adf_core_python}/implement/module/communication/default_message_coordinator.py (100%) rename {adf_core_python/implement/module/communication => src/adf_core_python/implement/module/complex}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/implement/module/complex/default_ambulance_target_allocator.py (100%) rename {adf_core_python => src/adf_core_python}/implement/module/complex/default_fire_target_allocator.py (100%) rename {adf_core_python => src/adf_core_python}/implement/module/complex/default_human_detector.py (100%) rename {adf_core_python => src/adf_core_python}/implement/module/complex/default_police_target_allocator.py (100%) rename {adf_core_python => src/adf_core_python}/implement/module/complex/default_road_detector.py (100%) rename {adf_core_python => src/adf_core_python}/implement/module/complex/default_search.py (100%) rename {adf_core_python/implement/module/complex => src/adf_core_python/implement/tactics}/__init__.py (100%) rename {adf_core_python => src/adf_core_python}/implement/tactics/default_tactics_ambulance_center.py (100%) rename {adf_core_python => src/adf_core_python}/implement/tactics/default_tactics_ambulance_team.py (100%) rename {adf_core_python => src/adf_core_python}/implement/tactics/default_tactics_fire_brigade.py (100%) rename {adf_core_python => src/adf_core_python}/implement/tactics/default_tactics_fire_station.py (100%) rename {adf_core_python => src/adf_core_python}/implement/tactics/default_tactics_police_force.py (100%) rename {adf_core_python => src/adf_core_python}/implement/tactics/default_tactics_police_office.py (100%) rename {adf_core_python => src/adf_core_python}/launcher.py (100%) diff --git a/adf_core_python/core/agent/precompute/precompute_data.py b/adf_core_python/core/agent/precompute/precompute_data.py deleted file mode 100644 index 41b2029..0000000 --- a/adf_core_python/core/agent/precompute/precompute_data.py +++ /dev/null @@ -1,75 +0,0 @@ -import json -import os - -ENCODE = "utf-8" - - -class PrecomputeData: - def __init__(self, dir_path: str) -> None: - """ - Initialize the PrecomputeData object. - - Parameters - ---------- - dir_path : str - The directory path to save the precompute data. - - Raises - ------ - Exception - """ - self._dir_path = dir_path - - def read_json_data(self, module_name: str) -> dict: - """ - Read the precompute data from the file. - - Returns - ------- - dict - The precompute data. - - Raises - ------ - Exception - """ - - with open(f"{self._dir_path}/{module_name}.json", "r", encoding=ENCODE) as file: - return json.load(file) - - def write_json_data(self, data: dict, module_name: str) -> None: - """ - Write the precompute data to the file. - - Parameters - ---------- - data : dict - The data to write. - - Raises - ------ - Exception - """ - if not os.path.exists(self._dir_path): - os.makedirs(self._dir_path) - - with open(f"{self._dir_path}/{module_name}.json", "w", encoding=ENCODE) as file: - json.dump(data, file, indent=4) - - def remove_precompute_data(self) -> None: - """ - Remove the precompute data file. - """ - if os.path.exists(self._dir_path): - os.remove(self._dir_path) - - def is_available(self) -> bool: - """ - Check if the precompute data is available. - - Returns - ------- - bool - True if the precompute data is available, False otherwise. - """ - return os.path.exists(self._dir_path) diff --git a/adf_core_python/implement/tactics/__init__.py b/adf_core_python/implement/tactics/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/adf_core_python/__init__.py b/src/adf_core_python/__init__.py similarity index 100% rename from adf_core_python/__init__.py rename to src/adf_core_python/__init__.py diff --git a/adf_core_python/cli/__init__.py b/src/adf_core_python/cli/__init__.py similarity index 100% rename from adf_core_python/cli/__init__.py rename to src/adf_core_python/cli/__init__.py diff --git a/adf_core_python/cli/cli.py b/src/adf_core_python/cli/cli.py similarity index 100% rename from adf_core_python/cli/cli.py rename to src/adf_core_python/cli/cli.py diff --git a/adf_core_python/cli/template/config/development.json b/src/adf_core_python/cli/template/config/development.json similarity index 100% rename from adf_core_python/cli/template/config/development.json rename to src/adf_core_python/cli/template/config/development.json diff --git a/adf_core_python/cli/template/config/launcher.yaml b/src/adf_core_python/cli/template/config/launcher.yaml similarity index 100% rename from adf_core_python/cli/template/config/launcher.yaml rename to src/adf_core_python/cli/template/config/launcher.yaml diff --git a/adf_core_python/cli/template/config/module.yaml b/src/adf_core_python/cli/template/config/module.yaml similarity index 100% rename from adf_core_python/cli/template/config/module.yaml rename to src/adf_core_python/cli/template/config/module.yaml diff --git a/adf_core_python/cli/template/main.py b/src/adf_core_python/cli/template/main.py similarity index 100% rename from adf_core_python/cli/template/main.py rename to src/adf_core_python/cli/template/main.py diff --git a/adf_core_python/cli/template/src/team_name/__init__.py b/src/adf_core_python/cli/template/src/team_name/__init__.py similarity index 100% rename from adf_core_python/cli/template/src/team_name/__init__.py rename to src/adf_core_python/cli/template/src/team_name/__init__.py diff --git a/adf_core_python/cli/template/src/team_name/module/__init__.py b/src/adf_core_python/cli/template/src/team_name/module/__init__.py similarity index 100% rename from adf_core_python/cli/template/src/team_name/module/__init__.py rename to src/adf_core_python/cli/template/src/team_name/module/__init__.py diff --git a/adf_core_python/cli/template/src/team_name/module/complex/__init__.py b/src/adf_core_python/cli/template/src/team_name/module/complex/__init__.py similarity index 100% rename from adf_core_python/cli/template/src/team_name/module/complex/__init__.py rename to src/adf_core_python/cli/template/src/team_name/module/complex/__init__.py diff --git a/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py b/src/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py similarity index 100% rename from adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py rename to src/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py diff --git a/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py b/src/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py similarity index 100% rename from adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py rename to src/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py diff --git a/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py b/src/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py similarity index 100% rename from adf_core_python/cli/template/src/team_name/module/complex/sample_search.py rename to src/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py diff --git a/adf_core_python/core/__init__.py b/src/adf_core_python/core/__init__.py similarity index 100% rename from adf_core_python/core/__init__.py rename to src/adf_core_python/core/__init__.py diff --git a/adf_core_python/core/agent/__init__.py b/src/adf_core_python/core/agent/__init__.py similarity index 100% rename from adf_core_python/core/agent/__init__.py rename to src/adf_core_python/core/agent/__init__.py diff --git a/adf_core_python/core/agent/action/__init__.py b/src/adf_core_python/core/agent/action/__init__.py similarity index 100% rename from adf_core_python/core/agent/action/__init__.py rename to src/adf_core_python/core/agent/action/__init__.py diff --git a/adf_core_python/core/agent/action/action.py b/src/adf_core_python/core/agent/action/action.py similarity index 100% rename from adf_core_python/core/agent/action/action.py rename to src/adf_core_python/core/agent/action/action.py diff --git a/adf_core_python/core/agent/action/ambulance/__init__.py b/src/adf_core_python/core/agent/action/ambulance/__init__.py similarity index 100% rename from adf_core_python/core/agent/action/ambulance/__init__.py rename to src/adf_core_python/core/agent/action/ambulance/__init__.py diff --git a/adf_core_python/core/agent/action/ambulance/action_load.py b/src/adf_core_python/core/agent/action/ambulance/action_load.py similarity index 100% rename from adf_core_python/core/agent/action/ambulance/action_load.py rename to src/adf_core_python/core/agent/action/ambulance/action_load.py diff --git a/adf_core_python/core/agent/action/ambulance/action_rescue.py b/src/adf_core_python/core/agent/action/ambulance/action_rescue.py similarity index 100% rename from adf_core_python/core/agent/action/ambulance/action_rescue.py rename to src/adf_core_python/core/agent/action/ambulance/action_rescue.py diff --git a/adf_core_python/core/agent/action/ambulance/action_unload.py b/src/adf_core_python/core/agent/action/ambulance/action_unload.py similarity index 100% rename from adf_core_python/core/agent/action/ambulance/action_unload.py rename to src/adf_core_python/core/agent/action/ambulance/action_unload.py diff --git a/adf_core_python/core/agent/action/common/__init__.py b/src/adf_core_python/core/agent/action/common/__init__.py similarity index 100% rename from adf_core_python/core/agent/action/common/__init__.py rename to src/adf_core_python/core/agent/action/common/__init__.py diff --git a/adf_core_python/core/agent/action/common/action_move.py b/src/adf_core_python/core/agent/action/common/action_move.py similarity index 100% rename from adf_core_python/core/agent/action/common/action_move.py rename to src/adf_core_python/core/agent/action/common/action_move.py diff --git a/adf_core_python/core/agent/action/common/action_rest.py b/src/adf_core_python/core/agent/action/common/action_rest.py similarity index 100% rename from adf_core_python/core/agent/action/common/action_rest.py rename to src/adf_core_python/core/agent/action/common/action_rest.py diff --git a/adf_core_python/core/agent/action/fire/__init__.py b/src/adf_core_python/core/agent/action/fire/__init__.py similarity index 100% rename from adf_core_python/core/agent/action/fire/__init__.py rename to src/adf_core_python/core/agent/action/fire/__init__.py diff --git a/adf_core_python/core/agent/action/fire/action_extinguish.py b/src/adf_core_python/core/agent/action/fire/action_extinguish.py similarity index 100% rename from adf_core_python/core/agent/action/fire/action_extinguish.py rename to src/adf_core_python/core/agent/action/fire/action_extinguish.py diff --git a/adf_core_python/core/agent/action/fire/action_refill.py b/src/adf_core_python/core/agent/action/fire/action_refill.py similarity index 100% rename from adf_core_python/core/agent/action/fire/action_refill.py rename to src/adf_core_python/core/agent/action/fire/action_refill.py diff --git a/adf_core_python/core/agent/action/fire/action_rescue.py b/src/adf_core_python/core/agent/action/fire/action_rescue.py similarity index 100% rename from adf_core_python/core/agent/action/fire/action_rescue.py rename to src/adf_core_python/core/agent/action/fire/action_rescue.py diff --git a/adf_core_python/core/agent/action/police/__init__.py b/src/adf_core_python/core/agent/action/police/__init__.py similarity index 100% rename from adf_core_python/core/agent/action/police/__init__.py rename to src/adf_core_python/core/agent/action/police/__init__.py diff --git a/adf_core_python/core/agent/action/police/action_clear.py b/src/adf_core_python/core/agent/action/police/action_clear.py similarity index 100% rename from adf_core_python/core/agent/action/police/action_clear.py rename to src/adf_core_python/core/agent/action/police/action_clear.py diff --git a/adf_core_python/core/agent/action/police/action_clear_area.py b/src/adf_core_python/core/agent/action/police/action_clear_area.py similarity index 100% rename from adf_core_python/core/agent/action/police/action_clear_area.py rename to src/adf_core_python/core/agent/action/police/action_clear_area.py diff --git a/adf_core_python/core/agent/agent.py b/src/adf_core_python/core/agent/agent.py similarity index 100% rename from adf_core_python/core/agent/agent.py rename to src/adf_core_python/core/agent/agent.py diff --git a/adf_core_python/core/agent/communication/__init__.py b/src/adf_core_python/core/agent/communication/__init__.py similarity index 100% rename from adf_core_python/core/agent/communication/__init__.py rename to src/adf_core_python/core/agent/communication/__init__.py diff --git a/adf_core_python/core/agent/communication/message_manager.py b/src/adf_core_python/core/agent/communication/message_manager.py similarity index 100% rename from adf_core_python/core/agent/communication/message_manager.py rename to src/adf_core_python/core/agent/communication/message_manager.py diff --git a/adf_core_python/core/agent/communication/standard/__init__.py b/src/adf_core_python/core/agent/communication/standard/__init__.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/__init__.py rename to src/adf_core_python/core/agent/communication/standard/__init__.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/__init__.py b/src/adf_core_python/core/agent/communication/standard/bundle/__init__.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/__init__.py rename to src/adf_core_python/core/agent/communication/standard/bundle/__init__.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/__init__.py b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/__init__.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/centralized/__init__.py rename to src/adf_core_python/core/agent/communication/standard/bundle/centralized/__init__.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py rename to src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py rename to src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py rename to src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py rename to src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py rename to src/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/__init__.py b/src/adf_core_python/core/agent/communication/standard/bundle/information/__init__.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/information/__init__.py rename to src/adf_core_python/core/agent/communication/standard/bundle/information/__init__.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py rename to src/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/information/message_building.py rename to src/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py rename to src/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py rename to src/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py rename to src/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/information/message_road.py rename to src/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/standard_message.py b/src/adf_core_python/core/agent/communication/standard/bundle/standard_message.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/standard_message.py rename to src/adf_core_python/core/agent/communication/standard/bundle/standard_message.py diff --git a/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py b/src/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py rename to src/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py diff --git a/adf_core_python/core/agent/communication/standard/standard_communication_module.py b/src/adf_core_python/core/agent/communication/standard/standard_communication_module.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/standard_communication_module.py rename to src/adf_core_python/core/agent/communication/standard/standard_communication_module.py diff --git a/adf_core_python/core/agent/communication/standard/utility/__init__.py b/src/adf_core_python/core/agent/communication/standard/utility/__init__.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/utility/__init__.py rename to src/adf_core_python/core/agent/communication/standard/utility/__init__.py diff --git a/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py b/src/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py rename to src/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py diff --git a/adf_core_python/core/agent/communication/standard/utility/bitarray_with_exits_flag.py b/src/adf_core_python/core/agent/communication/standard/utility/bitarray_with_exits_flag.py similarity index 100% rename from adf_core_python/core/agent/communication/standard/utility/bitarray_with_exits_flag.py rename to src/adf_core_python/core/agent/communication/standard/utility/bitarray_with_exits_flag.py diff --git a/adf_core_python/core/agent/config/__init__.py b/src/adf_core_python/core/agent/config/__init__.py similarity index 100% rename from adf_core_python/core/agent/config/__init__.py rename to src/adf_core_python/core/agent/config/__init__.py diff --git a/adf_core_python/core/agent/config/module_config.py b/src/adf_core_python/core/agent/config/module_config.py similarity index 100% rename from adf_core_python/core/agent/config/module_config.py rename to src/adf_core_python/core/agent/config/module_config.py diff --git a/adf_core_python/core/agent/develop/__init__.py b/src/adf_core_python/core/agent/develop/__init__.py similarity index 100% rename from adf_core_python/core/agent/develop/__init__.py rename to src/adf_core_python/core/agent/develop/__init__.py diff --git a/adf_core_python/core/agent/develop/develop_data.py b/src/adf_core_python/core/agent/develop/develop_data.py similarity index 100% rename from adf_core_python/core/agent/develop/develop_data.py rename to src/adf_core_python/core/agent/develop/develop_data.py diff --git a/adf_core_python/core/agent/info/__init__.py b/src/adf_core_python/core/agent/info/__init__.py similarity index 100% rename from adf_core_python/core/agent/info/__init__.py rename to src/adf_core_python/core/agent/info/__init__.py diff --git a/adf_core_python/core/agent/info/agent_info.py b/src/adf_core_python/core/agent/info/agent_info.py similarity index 100% rename from adf_core_python/core/agent/info/agent_info.py rename to src/adf_core_python/core/agent/info/agent_info.py diff --git a/adf_core_python/core/agent/info/scenario_info.py b/src/adf_core_python/core/agent/info/scenario_info.py similarity index 100% rename from adf_core_python/core/agent/info/scenario_info.py rename to src/adf_core_python/core/agent/info/scenario_info.py diff --git a/adf_core_python/core/agent/info/world_info.py b/src/adf_core_python/core/agent/info/world_info.py similarity index 100% rename from adf_core_python/core/agent/info/world_info.py rename to src/adf_core_python/core/agent/info/world_info.py diff --git a/adf_core_python/core/agent/module/__init__.py b/src/adf_core_python/core/agent/module/__init__.py similarity index 100% rename from adf_core_python/core/agent/module/__init__.py rename to src/adf_core_python/core/agent/module/__init__.py diff --git a/adf_core_python/core/agent/module/module_manager.py b/src/adf_core_python/core/agent/module/module_manager.py similarity index 100% rename from adf_core_python/core/agent/module/module_manager.py rename to src/adf_core_python/core/agent/module/module_manager.py diff --git a/adf_core_python/core/agent/office/office.py b/src/adf_core_python/core/agent/office/office.py similarity index 100% rename from adf_core_python/core/agent/office/office.py rename to src/adf_core_python/core/agent/office/office.py diff --git a/adf_core_python/core/agent/office/office_ambulance.py b/src/adf_core_python/core/agent/office/office_ambulance.py similarity index 100% rename from adf_core_python/core/agent/office/office_ambulance.py rename to src/adf_core_python/core/agent/office/office_ambulance.py diff --git a/adf_core_python/core/agent/office/office_fire.py b/src/adf_core_python/core/agent/office/office_fire.py similarity index 100% rename from adf_core_python/core/agent/office/office_fire.py rename to src/adf_core_python/core/agent/office/office_fire.py diff --git a/adf_core_python/core/agent/office/office_police.py b/src/adf_core_python/core/agent/office/office_police.py similarity index 100% rename from adf_core_python/core/agent/office/office_police.py rename to src/adf_core_python/core/agent/office/office_police.py diff --git a/adf_core_python/core/agent/platoon/__init__.py b/src/adf_core_python/core/agent/platoon/__init__.py similarity index 100% rename from adf_core_python/core/agent/platoon/__init__.py rename to src/adf_core_python/core/agent/platoon/__init__.py diff --git a/adf_core_python/core/agent/platoon/platoon.py b/src/adf_core_python/core/agent/platoon/platoon.py similarity index 100% rename from adf_core_python/core/agent/platoon/platoon.py rename to src/adf_core_python/core/agent/platoon/platoon.py diff --git a/adf_core_python/core/agent/platoon/platoon_ambulance.py b/src/adf_core_python/core/agent/platoon/platoon_ambulance.py similarity index 100% rename from adf_core_python/core/agent/platoon/platoon_ambulance.py rename to src/adf_core_python/core/agent/platoon/platoon_ambulance.py diff --git a/adf_core_python/core/agent/platoon/platoon_fire.py b/src/adf_core_python/core/agent/platoon/platoon_fire.py similarity index 100% rename from adf_core_python/core/agent/platoon/platoon_fire.py rename to src/adf_core_python/core/agent/platoon/platoon_fire.py diff --git a/adf_core_python/core/agent/platoon/platoon_police.py b/src/adf_core_python/core/agent/platoon/platoon_police.py similarity index 100% rename from adf_core_python/core/agent/platoon/platoon_police.py rename to src/adf_core_python/core/agent/platoon/platoon_police.py diff --git a/adf_core_python/core/agent/precompute/__init__.py b/src/adf_core_python/core/component/__init__.py similarity index 100% rename from adf_core_python/core/agent/precompute/__init__.py rename to src/adf_core_python/core/component/__init__.py diff --git a/adf_core_python/core/component/abstract_loader.py b/src/adf_core_python/core/component/abstract_loader.py similarity index 100% rename from adf_core_python/core/component/abstract_loader.py rename to src/adf_core_python/core/component/abstract_loader.py diff --git a/adf_core_python/core/component/__init__.py b/src/adf_core_python/core/component/action/__init__.py similarity index 100% rename from adf_core_python/core/component/__init__.py rename to src/adf_core_python/core/component/action/__init__.py diff --git a/adf_core_python/core/component/action/extend_action.py b/src/adf_core_python/core/component/action/extend_action.py similarity index 100% rename from adf_core_python/core/component/action/extend_action.py rename to src/adf_core_python/core/component/action/extend_action.py diff --git a/adf_core_python/core/component/centralized/command_executor.py b/src/adf_core_python/core/component/centralized/command_executor.py similarity index 100% rename from adf_core_python/core/component/centralized/command_executor.py rename to src/adf_core_python/core/component/centralized/command_executor.py diff --git a/adf_core_python/core/component/centralized/command_picker.py b/src/adf_core_python/core/component/centralized/command_picker.py similarity index 100% rename from adf_core_python/core/component/centralized/command_picker.py rename to src/adf_core_python/core/component/centralized/command_picker.py diff --git a/adf_core_python/core/component/action/__init__.py b/src/adf_core_python/core/component/communication/__init__.py similarity index 100% rename from adf_core_python/core/component/action/__init__.py rename to src/adf_core_python/core/component/communication/__init__.py diff --git a/adf_core_python/core/component/communication/channel_subscriber.py b/src/adf_core_python/core/component/communication/channel_subscriber.py similarity index 100% rename from adf_core_python/core/component/communication/channel_subscriber.py rename to src/adf_core_python/core/component/communication/channel_subscriber.py diff --git a/adf_core_python/core/component/communication/communication_message.py b/src/adf_core_python/core/component/communication/communication_message.py similarity index 100% rename from adf_core_python/core/component/communication/communication_message.py rename to src/adf_core_python/core/component/communication/communication_message.py diff --git a/adf_core_python/core/component/communication/communication_module.py b/src/adf_core_python/core/component/communication/communication_module.py similarity index 100% rename from adf_core_python/core/component/communication/communication_module.py rename to src/adf_core_python/core/component/communication/communication_module.py diff --git a/adf_core_python/core/component/communication/message_coordinator.py b/src/adf_core_python/core/component/communication/message_coordinator.py similarity index 100% rename from adf_core_python/core/component/communication/message_coordinator.py rename to src/adf_core_python/core/component/communication/message_coordinator.py diff --git a/adf_core_python/core/component/communication/__init__.py b/src/adf_core_python/core/component/module/__init__.py similarity index 100% rename from adf_core_python/core/component/communication/__init__.py rename to src/adf_core_python/core/component/module/__init__.py diff --git a/adf_core_python/core/component/module/abstract_module.py b/src/adf_core_python/core/component/module/abstract_module.py similarity index 100% rename from adf_core_python/core/component/module/abstract_module.py rename to src/adf_core_python/core/component/module/abstract_module.py diff --git a/adf_core_python/core/component/module/__init__.py b/src/adf_core_python/core/component/module/algorithm/__init__.py similarity index 100% rename from adf_core_python/core/component/module/__init__.py rename to src/adf_core_python/core/component/module/algorithm/__init__.py diff --git a/adf_core_python/core/component/module/algorithm/clustering.py b/src/adf_core_python/core/component/module/algorithm/clustering.py similarity index 100% rename from adf_core_python/core/component/module/algorithm/clustering.py rename to src/adf_core_python/core/component/module/algorithm/clustering.py diff --git a/adf_core_python/core/component/module/algorithm/path_planning.py b/src/adf_core_python/core/component/module/algorithm/path_planning.py similarity index 100% rename from adf_core_python/core/component/module/algorithm/path_planning.py rename to src/adf_core_python/core/component/module/algorithm/path_planning.py diff --git a/adf_core_python/core/component/module/algorithm/__init__.py b/src/adf_core_python/core/component/module/complex/__init__.py similarity index 100% rename from adf_core_python/core/component/module/algorithm/__init__.py rename to src/adf_core_python/core/component/module/complex/__init__.py diff --git a/adf_core_python/core/component/module/complex/ambulance_target_allocator.py b/src/adf_core_python/core/component/module/complex/ambulance_target_allocator.py similarity index 100% rename from adf_core_python/core/component/module/complex/ambulance_target_allocator.py rename to src/adf_core_python/core/component/module/complex/ambulance_target_allocator.py diff --git a/adf_core_python/core/component/module/complex/fire_target_allocator.py b/src/adf_core_python/core/component/module/complex/fire_target_allocator.py similarity index 100% rename from adf_core_python/core/component/module/complex/fire_target_allocator.py rename to src/adf_core_python/core/component/module/complex/fire_target_allocator.py diff --git a/adf_core_python/core/component/module/complex/human_detector.py b/src/adf_core_python/core/component/module/complex/human_detector.py similarity index 100% rename from adf_core_python/core/component/module/complex/human_detector.py rename to src/adf_core_python/core/component/module/complex/human_detector.py diff --git a/adf_core_python/core/component/module/complex/police_target_allocator.py b/src/adf_core_python/core/component/module/complex/police_target_allocator.py similarity index 100% rename from adf_core_python/core/component/module/complex/police_target_allocator.py rename to src/adf_core_python/core/component/module/complex/police_target_allocator.py diff --git a/adf_core_python/core/component/module/complex/road_detector.py b/src/adf_core_python/core/component/module/complex/road_detector.py similarity index 100% rename from adf_core_python/core/component/module/complex/road_detector.py rename to src/adf_core_python/core/component/module/complex/road_detector.py diff --git a/adf_core_python/core/component/module/complex/search.py b/src/adf_core_python/core/component/module/complex/search.py similarity index 100% rename from adf_core_python/core/component/module/complex/search.py rename to src/adf_core_python/core/component/module/complex/search.py diff --git a/adf_core_python/core/component/module/complex/target_allocator.py b/src/adf_core_python/core/component/module/complex/target_allocator.py similarity index 100% rename from adf_core_python/core/component/module/complex/target_allocator.py rename to src/adf_core_python/core/component/module/complex/target_allocator.py diff --git a/adf_core_python/core/component/module/complex/target_detector.py b/src/adf_core_python/core/component/module/complex/target_detector.py similarity index 100% rename from adf_core_python/core/component/module/complex/target_detector.py rename to src/adf_core_python/core/component/module/complex/target_detector.py diff --git a/adf_core_python/core/component/module/complex/__init__.py b/src/adf_core_python/core/component/tactics/__init__.py similarity index 100% rename from adf_core_python/core/component/module/complex/__init__.py rename to src/adf_core_python/core/component/tactics/__init__.py diff --git a/adf_core_python/core/component/tactics/tactics_agent.py b/src/adf_core_python/core/component/tactics/tactics_agent.py similarity index 100% rename from adf_core_python/core/component/tactics/tactics_agent.py rename to src/adf_core_python/core/component/tactics/tactics_agent.py diff --git a/adf_core_python/core/component/tactics/tactics_ambulance_center.py b/src/adf_core_python/core/component/tactics/tactics_ambulance_center.py similarity index 100% rename from adf_core_python/core/component/tactics/tactics_ambulance_center.py rename to src/adf_core_python/core/component/tactics/tactics_ambulance_center.py diff --git a/adf_core_python/core/component/tactics/tactics_ambulance_team.py b/src/adf_core_python/core/component/tactics/tactics_ambulance_team.py similarity index 100% rename from adf_core_python/core/component/tactics/tactics_ambulance_team.py rename to src/adf_core_python/core/component/tactics/tactics_ambulance_team.py diff --git a/adf_core_python/core/component/tactics/tactics_center.py b/src/adf_core_python/core/component/tactics/tactics_center.py similarity index 100% rename from adf_core_python/core/component/tactics/tactics_center.py rename to src/adf_core_python/core/component/tactics/tactics_center.py diff --git a/adf_core_python/core/component/tactics/tactics_fire_brigade.py b/src/adf_core_python/core/component/tactics/tactics_fire_brigade.py similarity index 100% rename from adf_core_python/core/component/tactics/tactics_fire_brigade.py rename to src/adf_core_python/core/component/tactics/tactics_fire_brigade.py diff --git a/adf_core_python/core/component/tactics/tactics_fire_station.py b/src/adf_core_python/core/component/tactics/tactics_fire_station.py similarity index 100% rename from adf_core_python/core/component/tactics/tactics_fire_station.py rename to src/adf_core_python/core/component/tactics/tactics_fire_station.py diff --git a/adf_core_python/core/component/tactics/tactics_police_force.py b/src/adf_core_python/core/component/tactics/tactics_police_force.py similarity index 100% rename from adf_core_python/core/component/tactics/tactics_police_force.py rename to src/adf_core_python/core/component/tactics/tactics_police_force.py diff --git a/adf_core_python/core/component/tactics/tactics_police_office.py b/src/adf_core_python/core/component/tactics/tactics_police_office.py similarity index 100% rename from adf_core_python/core/component/tactics/tactics_police_office.py rename to src/adf_core_python/core/component/tactics/tactics_police_office.py diff --git a/adf_core_python/core/component/tactics/__init__.py b/src/adf_core_python/core/config/__init__.py similarity index 100% rename from adf_core_python/core/component/tactics/__init__.py rename to src/adf_core_python/core/config/__init__.py diff --git a/adf_core_python/core/config/config.py b/src/adf_core_python/core/config/config.py similarity index 100% rename from adf_core_python/core/config/config.py rename to src/adf_core_python/core/config/config.py diff --git a/adf_core_python/core/config/__init__.py b/src/adf_core_python/core/gateway/__init__.py similarity index 100% rename from adf_core_python/core/config/__init__.py rename to src/adf_core_python/core/gateway/__init__.py diff --git a/adf_core_python/core/gateway/__init__.py b/src/adf_core_python/core/gateway/component/__init__.py similarity index 100% rename from adf_core_python/core/gateway/__init__.py rename to src/adf_core_python/core/gateway/component/__init__.py diff --git a/adf_core_python/core/gateway/component/__init__.py b/src/adf_core_python/core/gateway/component/module/__init__.py similarity index 100% rename from adf_core_python/core/gateway/component/__init__.py rename to src/adf_core_python/core/gateway/component/module/__init__.py diff --git a/adf_core_python/core/gateway/component/module/__init__.py b/src/adf_core_python/core/gateway/component/module/algorithm/__init__.py similarity index 100% rename from adf_core_python/core/gateway/component/module/__init__.py rename to src/adf_core_python/core/gateway/component/module/algorithm/__init__.py diff --git a/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py b/src/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py similarity index 100% rename from adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py rename to src/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py diff --git a/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py b/src/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py similarity index 100% rename from adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py rename to src/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py diff --git a/adf_core_python/core/gateway/component/module/algorithm/__init__.py b/src/adf_core_python/core/gateway/component/module/complex/__init__.py similarity index 100% rename from adf_core_python/core/gateway/component/module/algorithm/__init__.py rename to src/adf_core_python/core/gateway/component/module/complex/__init__.py diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_ambulance_target_allocator.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_ambulance_target_allocator.py similarity index 100% rename from adf_core_python/core/gateway/component/module/complex/gateway_ambulance_target_allocator.py rename to src/adf_core_python/core/gateway/component/module/complex/gateway_ambulance_target_allocator.py diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_fire_target_allocator.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_fire_target_allocator.py similarity index 100% rename from adf_core_python/core/gateway/component/module/complex/gateway_fire_target_allocator.py rename to src/adf_core_python/core/gateway/component/module/complex/gateway_fire_target_allocator.py diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py similarity index 100% rename from adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py rename to src/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_police_target_allocator.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_police_target_allocator.py similarity index 100% rename from adf_core_python/core/gateway/component/module/complex/gateway_police_target_allocator.py rename to src/adf_core_python/core/gateway/component/module/complex/gateway_police_target_allocator.py diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py similarity index 100% rename from adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py rename to src/adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_search.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_search.py similarity index 100% rename from adf_core_python/core/gateway/component/module/complex/gateway_search.py rename to src/adf_core_python/core/gateway/component/module/complex/gateway_search.py diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py similarity index 100% rename from adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py rename to src/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py similarity index 100% rename from adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py rename to src/adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py diff --git a/adf_core_python/core/gateway/component/module/gateway_abstract_module.py b/src/adf_core_python/core/gateway/component/module/gateway_abstract_module.py similarity index 100% rename from adf_core_python/core/gateway/component/module/gateway_abstract_module.py rename to src/adf_core_python/core/gateway/component/module/gateway_abstract_module.py diff --git a/adf_core_python/core/gateway/gateway_agent.py b/src/adf_core_python/core/gateway/gateway_agent.py similarity index 100% rename from adf_core_python/core/gateway/gateway_agent.py rename to src/adf_core_python/core/gateway/gateway_agent.py diff --git a/adf_core_python/core/gateway/gateway_launcher.py b/src/adf_core_python/core/gateway/gateway_launcher.py similarity index 100% rename from adf_core_python/core/gateway/gateway_launcher.py rename to src/adf_core_python/core/gateway/gateway_launcher.py diff --git a/adf_core_python/core/gateway/gateway_module.py b/src/adf_core_python/core/gateway/gateway_module.py similarity index 100% rename from adf_core_python/core/gateway/gateway_module.py rename to src/adf_core_python/core/gateway/gateway_module.py diff --git a/adf_core_python/core/gateway/component/module/complex/__init__.py b/src/adf_core_python/core/gateway/message/__init__.py similarity index 100% rename from adf_core_python/core/gateway/component/module/complex/__init__.py rename to src/adf_core_python/core/gateway/message/__init__.py diff --git a/adf_core_python/core/gateway/message/am_agent.py b/src/adf_core_python/core/gateway/message/am_agent.py similarity index 100% rename from adf_core_python/core/gateway/message/am_agent.py rename to src/adf_core_python/core/gateway/message/am_agent.py diff --git a/adf_core_python/core/gateway/message/am_exec.py b/src/adf_core_python/core/gateway/message/am_exec.py similarity index 100% rename from adf_core_python/core/gateway/message/am_exec.py rename to src/adf_core_python/core/gateway/message/am_exec.py diff --git a/adf_core_python/core/gateway/message/am_module.py b/src/adf_core_python/core/gateway/message/am_module.py similarity index 100% rename from adf_core_python/core/gateway/message/am_module.py rename to src/adf_core_python/core/gateway/message/am_module.py diff --git a/adf_core_python/core/gateway/message/am_update.py b/src/adf_core_python/core/gateway/message/am_update.py similarity index 100% rename from adf_core_python/core/gateway/message/am_update.py rename to src/adf_core_python/core/gateway/message/am_update.py diff --git a/adf_core_python/core/gateway/message/ma_exec_response.py b/src/adf_core_python/core/gateway/message/ma_exec_response.py similarity index 100% rename from adf_core_python/core/gateway/message/ma_exec_response.py rename to src/adf_core_python/core/gateway/message/ma_exec_response.py diff --git a/adf_core_python/core/gateway/message/ma_module_response.py b/src/adf_core_python/core/gateway/message/ma_module_response.py similarity index 100% rename from adf_core_python/core/gateway/message/ma_module_response.py rename to src/adf_core_python/core/gateway/message/ma_module_response.py diff --git a/adf_core_python/core/gateway/message/moduleMessageFactory.py b/src/adf_core_python/core/gateway/message/moduleMessageFactory.py similarity index 100% rename from adf_core_python/core/gateway/message/moduleMessageFactory.py rename to src/adf_core_python/core/gateway/message/moduleMessageFactory.py diff --git a/adf_core_python/core/gateway/message/__init__.py b/src/adf_core_python/core/gateway/message/urn/__init__.py similarity index 100% rename from adf_core_python/core/gateway/message/__init__.py rename to src/adf_core_python/core/gateway/message/urn/__init__.py diff --git a/adf_core_python/core/gateway/message/urn/urn.py b/src/adf_core_python/core/gateway/message/urn/urn.py similarity index 100% rename from adf_core_python/core/gateway/message/urn/urn.py rename to src/adf_core_python/core/gateway/message/urn/urn.py diff --git a/adf_core_python/core/gateway/module_dict.py b/src/adf_core_python/core/gateway/module_dict.py similarity index 100% rename from adf_core_python/core/gateway/module_dict.py rename to src/adf_core_python/core/gateway/module_dict.py diff --git a/adf_core_python/core/gateway/message/urn/__init__.py b/src/adf_core_python/core/launcher/__init__.py similarity index 100% rename from adf_core_python/core/gateway/message/urn/__init__.py rename to src/adf_core_python/core/launcher/__init__.py diff --git a/adf_core_python/core/launcher/agent_launcher.py b/src/adf_core_python/core/launcher/agent_launcher.py similarity index 100% rename from adf_core_python/core/launcher/agent_launcher.py rename to src/adf_core_python/core/launcher/agent_launcher.py diff --git a/adf_core_python/core/launcher/config_key.py b/src/adf_core_python/core/launcher/config_key.py similarity index 100% rename from adf_core_python/core/launcher/config_key.py rename to src/adf_core_python/core/launcher/config_key.py diff --git a/adf_core_python/core/launcher/__init__.py b/src/adf_core_python/core/launcher/connect/__init__.py similarity index 100% rename from adf_core_python/core/launcher/__init__.py rename to src/adf_core_python/core/launcher/connect/__init__.py diff --git a/adf_core_python/core/launcher/connect/component_launcher.py b/src/adf_core_python/core/launcher/connect/component_launcher.py similarity index 100% rename from adf_core_python/core/launcher/connect/component_launcher.py rename to src/adf_core_python/core/launcher/connect/component_launcher.py diff --git a/adf_core_python/core/launcher/connect/connection.py b/src/adf_core_python/core/launcher/connect/connection.py similarity index 100% rename from adf_core_python/core/launcher/connect/connection.py rename to src/adf_core_python/core/launcher/connect/connection.py diff --git a/adf_core_python/core/launcher/connect/connector.py b/src/adf_core_python/core/launcher/connect/connector.py similarity index 100% rename from adf_core_python/core/launcher/connect/connector.py rename to src/adf_core_python/core/launcher/connect/connector.py diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_center.py b/src/adf_core_python/core/launcher/connect/connector_ambulance_center.py similarity index 100% rename from adf_core_python/core/launcher/connect/connector_ambulance_center.py rename to src/adf_core_python/core/launcher/connect/connector_ambulance_center.py diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/src/adf_core_python/core/launcher/connect/connector_ambulance_team.py similarity index 100% rename from adf_core_python/core/launcher/connect/connector_ambulance_team.py rename to src/adf_core_python/core/launcher/connect/connector_ambulance_team.py diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/src/adf_core_python/core/launcher/connect/connector_fire_brigade.py similarity index 100% rename from adf_core_python/core/launcher/connect/connector_fire_brigade.py rename to src/adf_core_python/core/launcher/connect/connector_fire_brigade.py diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/src/adf_core_python/core/launcher/connect/connector_fire_station.py similarity index 100% rename from adf_core_python/core/launcher/connect/connector_fire_station.py rename to src/adf_core_python/core/launcher/connect/connector_fire_station.py diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/src/adf_core_python/core/launcher/connect/connector_police_force.py similarity index 100% rename from adf_core_python/core/launcher/connect/connector_police_force.py rename to src/adf_core_python/core/launcher/connect/connector_police_force.py diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/src/adf_core_python/core/launcher/connect/connector_police_office.py similarity index 100% rename from adf_core_python/core/launcher/connect/connector_police_office.py rename to src/adf_core_python/core/launcher/connect/connector_police_office.py diff --git a/adf_core_python/core/launcher/connect/error/agent_error.py b/src/adf_core_python/core/launcher/connect/error/agent_error.py similarity index 100% rename from adf_core_python/core/launcher/connect/error/agent_error.py rename to src/adf_core_python/core/launcher/connect/error/agent_error.py diff --git a/adf_core_python/core/launcher/connect/error/server_error.py b/src/adf_core_python/core/launcher/connect/error/server_error.py similarity index 100% rename from adf_core_python/core/launcher/connect/error/server_error.py rename to src/adf_core_python/core/launcher/connect/error/server_error.py diff --git a/adf_core_python/core/launcher/connect/__init__.py b/src/adf_core_python/core/logger/__init__.py similarity index 100% rename from adf_core_python/core/launcher/connect/__init__.py rename to src/adf_core_python/core/logger/__init__.py diff --git a/adf_core_python/core/logger/logger.py b/src/adf_core_python/core/logger/logger.py similarity index 100% rename from adf_core_python/core/logger/logger.py rename to src/adf_core_python/core/logger/logger.py diff --git a/adf_core_python/core/logger/__init__.py b/src/adf_core_python/implement/__init__.py similarity index 100% rename from adf_core_python/core/logger/__init__.py rename to src/adf_core_python/implement/__init__.py diff --git a/adf_core_python/implement/__init__.py b/src/adf_core_python/implement/action/__init__.py similarity index 100% rename from adf_core_python/implement/__init__.py rename to src/adf_core_python/implement/action/__init__.py diff --git a/adf_core_python/implement/action/default_extend_action_clear.py b/src/adf_core_python/implement/action/default_extend_action_clear.py similarity index 100% rename from adf_core_python/implement/action/default_extend_action_clear.py rename to src/adf_core_python/implement/action/default_extend_action_clear.py diff --git a/adf_core_python/implement/action/default_extend_action_move.py b/src/adf_core_python/implement/action/default_extend_action_move.py similarity index 100% rename from adf_core_python/implement/action/default_extend_action_move.py rename to src/adf_core_python/implement/action/default_extend_action_move.py diff --git a/adf_core_python/implement/action/default_extend_action_rescue.py b/src/adf_core_python/implement/action/default_extend_action_rescue.py similarity index 100% rename from adf_core_python/implement/action/default_extend_action_rescue.py rename to src/adf_core_python/implement/action/default_extend_action_rescue.py diff --git a/adf_core_python/implement/action/default_extend_action_transport.py b/src/adf_core_python/implement/action/default_extend_action_transport.py similarity index 100% rename from adf_core_python/implement/action/default_extend_action_transport.py rename to src/adf_core_python/implement/action/default_extend_action_transport.py diff --git a/adf_core_python/implement/centralized/default_command_executor_ambulance.py b/src/adf_core_python/implement/centralized/default_command_executor_ambulance.py similarity index 100% rename from adf_core_python/implement/centralized/default_command_executor_ambulance.py rename to src/adf_core_python/implement/centralized/default_command_executor_ambulance.py diff --git a/adf_core_python/implement/centralized/default_command_executor_fire.py b/src/adf_core_python/implement/centralized/default_command_executor_fire.py similarity index 100% rename from adf_core_python/implement/centralized/default_command_executor_fire.py rename to src/adf_core_python/implement/centralized/default_command_executor_fire.py diff --git a/adf_core_python/implement/centralized/default_command_executor_police.py b/src/adf_core_python/implement/centralized/default_command_executor_police.py similarity index 100% rename from adf_core_python/implement/centralized/default_command_executor_police.py rename to src/adf_core_python/implement/centralized/default_command_executor_police.py diff --git a/adf_core_python/implement/centralized/default_command_executor_scout.py b/src/adf_core_python/implement/centralized/default_command_executor_scout.py similarity index 100% rename from adf_core_python/implement/centralized/default_command_executor_scout.py rename to src/adf_core_python/implement/centralized/default_command_executor_scout.py diff --git a/adf_core_python/implement/centralized/default_command_executor_scout_police.py b/src/adf_core_python/implement/centralized/default_command_executor_scout_police.py similarity index 100% rename from adf_core_python/implement/centralized/default_command_executor_scout_police.py rename to src/adf_core_python/implement/centralized/default_command_executor_scout_police.py diff --git a/adf_core_python/implement/centralized/default_command_picker_ambulance.py b/src/adf_core_python/implement/centralized/default_command_picker_ambulance.py similarity index 100% rename from adf_core_python/implement/centralized/default_command_picker_ambulance.py rename to src/adf_core_python/implement/centralized/default_command_picker_ambulance.py diff --git a/adf_core_python/implement/centralized/default_command_picker_fire.py b/src/adf_core_python/implement/centralized/default_command_picker_fire.py similarity index 100% rename from adf_core_python/implement/centralized/default_command_picker_fire.py rename to src/adf_core_python/implement/centralized/default_command_picker_fire.py diff --git a/adf_core_python/implement/centralized/default_command_picker_police.py b/src/adf_core_python/implement/centralized/default_command_picker_police.py similarity index 100% rename from adf_core_python/implement/centralized/default_command_picker_police.py rename to src/adf_core_python/implement/centralized/default_command_picker_police.py diff --git a/adf_core_python/implement/default_loader.py b/src/adf_core_python/implement/default_loader.py similarity index 100% rename from adf_core_python/implement/default_loader.py rename to src/adf_core_python/implement/default_loader.py diff --git a/adf_core_python/implement/action/__init__.py b/src/adf_core_python/implement/module/__init__.py similarity index 100% rename from adf_core_python/implement/action/__init__.py rename to src/adf_core_python/implement/module/__init__.py diff --git a/adf_core_python/implement/module/__init__.py b/src/adf_core_python/implement/module/algorithm/__init__.py similarity index 100% rename from adf_core_python/implement/module/__init__.py rename to src/adf_core_python/implement/module/algorithm/__init__.py diff --git a/adf_core_python/implement/module/algorithm/a_star_path_planning.py b/src/adf_core_python/implement/module/algorithm/a_star_path_planning.py similarity index 100% rename from adf_core_python/implement/module/algorithm/a_star_path_planning.py rename to src/adf_core_python/implement/module/algorithm/a_star_path_planning.py diff --git a/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py b/src/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py similarity index 100% rename from adf_core_python/implement/module/algorithm/dijkstra_path_planning.py rename to src/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py diff --git a/adf_core_python/implement/module/algorithm/k_means_clustering.py b/src/adf_core_python/implement/module/algorithm/k_means_clustering.py similarity index 100% rename from adf_core_python/implement/module/algorithm/k_means_clustering.py rename to src/adf_core_python/implement/module/algorithm/k_means_clustering.py diff --git a/adf_core_python/implement/module/algorithm/__init__.py b/src/adf_core_python/implement/module/communication/__init__.py similarity index 100% rename from adf_core_python/implement/module/algorithm/__init__.py rename to src/adf_core_python/implement/module/communication/__init__.py diff --git a/adf_core_python/implement/module/communication/default_channel_subscriber.py b/src/adf_core_python/implement/module/communication/default_channel_subscriber.py similarity index 100% rename from adf_core_python/implement/module/communication/default_channel_subscriber.py rename to src/adf_core_python/implement/module/communication/default_channel_subscriber.py diff --git a/adf_core_python/implement/module/communication/default_message_coordinator.py b/src/adf_core_python/implement/module/communication/default_message_coordinator.py similarity index 100% rename from adf_core_python/implement/module/communication/default_message_coordinator.py rename to src/adf_core_python/implement/module/communication/default_message_coordinator.py diff --git a/adf_core_python/implement/module/communication/__init__.py b/src/adf_core_python/implement/module/complex/__init__.py similarity index 100% rename from adf_core_python/implement/module/communication/__init__.py rename to src/adf_core_python/implement/module/complex/__init__.py diff --git a/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py b/src/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py similarity index 100% rename from adf_core_python/implement/module/complex/default_ambulance_target_allocator.py rename to src/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py diff --git a/adf_core_python/implement/module/complex/default_fire_target_allocator.py b/src/adf_core_python/implement/module/complex/default_fire_target_allocator.py similarity index 100% rename from adf_core_python/implement/module/complex/default_fire_target_allocator.py rename to src/adf_core_python/implement/module/complex/default_fire_target_allocator.py diff --git a/adf_core_python/implement/module/complex/default_human_detector.py b/src/adf_core_python/implement/module/complex/default_human_detector.py similarity index 100% rename from adf_core_python/implement/module/complex/default_human_detector.py rename to src/adf_core_python/implement/module/complex/default_human_detector.py diff --git a/adf_core_python/implement/module/complex/default_police_target_allocator.py b/src/adf_core_python/implement/module/complex/default_police_target_allocator.py similarity index 100% rename from adf_core_python/implement/module/complex/default_police_target_allocator.py rename to src/adf_core_python/implement/module/complex/default_police_target_allocator.py diff --git a/adf_core_python/implement/module/complex/default_road_detector.py b/src/adf_core_python/implement/module/complex/default_road_detector.py similarity index 100% rename from adf_core_python/implement/module/complex/default_road_detector.py rename to src/adf_core_python/implement/module/complex/default_road_detector.py diff --git a/adf_core_python/implement/module/complex/default_search.py b/src/adf_core_python/implement/module/complex/default_search.py similarity index 100% rename from adf_core_python/implement/module/complex/default_search.py rename to src/adf_core_python/implement/module/complex/default_search.py diff --git a/adf_core_python/implement/module/complex/__init__.py b/src/adf_core_python/implement/tactics/__init__.py similarity index 100% rename from adf_core_python/implement/module/complex/__init__.py rename to src/adf_core_python/implement/tactics/__init__.py diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py b/src/adf_core_python/implement/tactics/default_tactics_ambulance_center.py similarity index 100% rename from adf_core_python/implement/tactics/default_tactics_ambulance_center.py rename to src/adf_core_python/implement/tactics/default_tactics_ambulance_center.py diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/src/adf_core_python/implement/tactics/default_tactics_ambulance_team.py similarity index 100% rename from adf_core_python/implement/tactics/default_tactics_ambulance_team.py rename to src/adf_core_python/implement/tactics/default_tactics_ambulance_team.py diff --git a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py b/src/adf_core_python/implement/tactics/default_tactics_fire_brigade.py similarity index 100% rename from adf_core_python/implement/tactics/default_tactics_fire_brigade.py rename to src/adf_core_python/implement/tactics/default_tactics_fire_brigade.py diff --git a/adf_core_python/implement/tactics/default_tactics_fire_station.py b/src/adf_core_python/implement/tactics/default_tactics_fire_station.py similarity index 100% rename from adf_core_python/implement/tactics/default_tactics_fire_station.py rename to src/adf_core_python/implement/tactics/default_tactics_fire_station.py diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/src/adf_core_python/implement/tactics/default_tactics_police_force.py similarity index 100% rename from adf_core_python/implement/tactics/default_tactics_police_force.py rename to src/adf_core_python/implement/tactics/default_tactics_police_force.py diff --git a/adf_core_python/implement/tactics/default_tactics_police_office.py b/src/adf_core_python/implement/tactics/default_tactics_police_office.py similarity index 100% rename from adf_core_python/implement/tactics/default_tactics_police_office.py rename to src/adf_core_python/implement/tactics/default_tactics_police_office.py diff --git a/adf_core_python/launcher.py b/src/adf_core_python/launcher.py similarity index 100% rename from adf_core_python/launcher.py rename to src/adf_core_python/launcher.py From 016423596410e9c9fea97c3b01342f5fee35daf3 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 15:06:13 +0900 Subject: [PATCH 236/249] feat: poetry to uv --- .github/workflows/ci.yaml | 14 +- .github/workflows/document.yaml | 6 +- .python-version | 1 + poetry.lock | 1747 ------------------------------- poetry.toml | 2 - pyproject.toml | 101 +- uv.lock | 947 +++++++++++++++++ 7 files changed, 1006 insertions(+), 1812 deletions(-) create mode 100644 .python-version delete mode 100644 poetry.lock delete mode 100644 poetry.toml create mode 100644 uv.lock diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b10f9d2..24ebbf5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,7 +27,7 @@ jobs: uses: actions/cache@v4 with: path: .venv - key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} + key: ${{ runner.os }}-venv-${{ hashFiles('**/uv.lock') }} - name: Install dependencies if: steps.cached-virtualenv.outputs.cache-hit != 'true' @@ -35,8 +35,8 @@ jobs: python -m venv .venv source .venv/bin/activate pip install --upgrade pip - pip install poetry - poetry install + pip install uv + uv sync ruff-format: needs: setup @@ -49,7 +49,7 @@ jobs: uses: actions/cache@v4 with: path: .venv - key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} + key: ${{ runner.os }}-venv-${{ hashFiles('**/uv.lock') }} - name: Set path run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH @@ -132,12 +132,12 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: - java-version: '17' - distribution: 'temurin' + java-version: "17" + distribution: "temurin" - name: Setup Gradle uses: gradle/actions/setup-gradle@v3 - name: Publish package run: cd java && ./gradlew publish env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/document.yaml b/.github/workflows/document.yaml index 986bc11..a70bca9 100644 --- a/.github/workflows/document.yaml +++ b/.github/workflows/document.yaml @@ -40,7 +40,7 @@ jobs: uses: actions/cache@v4 with: path: .venv - key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} + key: ${{ runner.os }}-venv-${{ hashFiles('**/uv.lock') }} - name: Install dependencies if: steps.cached-virtualenv.outputs.cache-hit != 'true' @@ -48,8 +48,8 @@ jobs: python -m venv .venv source .venv/bin/activate pip install --upgrade pip - pip install poetry - poetry install + pip install uv + uv sync - name: Build documentation run: | diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 371973e..0000000 --- a/poetry.lock +++ /dev/null @@ -1,1747 +0,0 @@ -# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. - -[[package]] -name = "accessible-pygments" -version = "0.0.5" -description = "A collection of accessible pygments styles" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7"}, - {file = "accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872"}, -] - -[package.dependencies] -pygments = ">=1.5" - -[package.extras] -dev = ["pillow", "pkginfo (>=1.10)", "playwright", "pre-commit", "setuptools", "twine (>=5.0)"] -tests = ["hypothesis", "pytest"] - -[[package]] -name = "alabaster" -version = "1.0.0" -description = "A light, configurable Sphinx theme" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"}, - {file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"}, -] - -[[package]] -name = "babel" -version = "2.17.0" -description = "Internationalization utilities" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, - {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, -] - -[package.extras] -dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] - -[[package]] -name = "beautifulsoup4" -version = "4.13.4" -description = "Screen-scraping library" -optional = false -python-versions = ">=3.7.0" -groups = ["dev"] -files = [ - {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, - {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, -] - -[package.dependencies] -soupsieve = ">1.2" -typing-extensions = ">=4.0.0" - -[package.extras] -cchardet = ["cchardet"] -chardet = ["chardet"] -charset-normalizer = ["charset-normalizer"] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -name = "bitarray" -version = "3.6.0" -description = "efficient arrays of booleans -- C extension" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "bitarray-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6841c08b51417f8ffe398b2828fc0593440c99525c868f640e0302476745320b"}, - {file = "bitarray-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a04b7a9017b8d0341ebbe77f61b74df1cf1b714f42b671a06f4912dc93d82597"}, - {file = "bitarray-3.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:664d462a4c0783fd755fe3440f07b7e46d149859c96caacadf3f28890f19a8de"}, - {file = "bitarray-3.6.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e997d22e0d1e08c8752f61675a75d93659f7aa4dbeaee54207f8d877817b4a0c"}, - {file = "bitarray-3.6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6755cfcfa7d8966e704d580c831e39818f85e7b2b7852ad22708973176f0009e"}, - {file = "bitarray-3.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4798f6744fa2633666e17b4ea8ff70250781b52a25afdbf5ffb5e176c58848f1"}, - {file = "bitarray-3.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:efa5834ba5e6c70b22afdca3894097e5a592d8d483c976359654ba990477799a"}, - {file = "bitarray-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d47e2bdeba4fb1986af2ba395ce51223f4d460e6e77119439e78f2b592cafade"}, - {file = "bitarray-3.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2a324e3007afb5c667026f5235b35efe3c4a95f1b83cd93aa9fce67b42f08e7c"}, - {file = "bitarray-3.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:080a7bf55c432abdae74f25dc3dbff407418346aeae1d43e31f65e8ef114f785"}, - {file = "bitarray-3.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3eb1390a8b062fe9125e5cc4c5eba990b5d383eec54f2b996e7ce73ac43150f9"}, - {file = "bitarray-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2020102a40edd094c0aa80e09203af71c533c41f76ce3237c99fd194a473ea33"}, - {file = "bitarray-3.6.0-cp310-cp310-win32.whl", hash = "sha256:01d6dc548e7fe5c66913c2274f44855b0f8474935acff7811e84fe1f4024c94f"}, - {file = "bitarray-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:8d759cecfa8aab4a1eb4e23b6420126b15c7743e85b33f389916bb98c4ecbb84"}, - {file = "bitarray-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7c20d6e6cafce5027e7092beb2ac6eec0d71045d6318b34f36e1387a8c8859a3"}, - {file = "bitarray-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cf36cadeb9c989f760a13058dbc455e5406ec3d2d247c705c8d4bc6dd1b0fcc6"}, - {file = "bitarray-3.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ba4fba3de1dca653de41c879349ec6ca521d85cff6a7ca5d2fdd8f76c93781"}, - {file = "bitarray-3.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77d2368a06a86a18919c05a9b4b0ee9869f770e6a5f414b0fecc911870fe3974"}, - {file = "bitarray-3.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a39be79a7c36e9a2e20376261c30deb3cdca86b50f7462ae9ff10a755c6720b9"}, - {file = "bitarray-3.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4695fcd37478988b1d0a16d5bc0df56dcb677fd5db37f1893d993fd3ebef914b"}, - {file = "bitarray-3.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52328192d454ca2ddad09fbc088872b014c74b22ecdd5164717dc7e6442014fa"}, - {file = "bitarray-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96117212229905da864794df9ea7bd54987c30a5dcbab3432edc3f344231adae"}, - {file = "bitarray-3.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:68f6e64d4867ee79e25c49d7f35b2b1f04a6d6f778176dcf5b759f3b17a02b2b"}, - {file = "bitarray-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:29ed022189a7997de46cb9bd4e2e49d6163d4f8d78dea72ac5a0e0293b856810"}, - {file = "bitarray-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e71c9dba78671d38a549e3b2d52514f50e199f9d7e18ed9b0180adeef0d04130"}, - {file = "bitarray-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ddb319f869d497ef2d3d56319360b61284a9a1d8b3de3bc936748698acfda6be"}, - {file = "bitarray-3.6.0-cp311-cp311-win32.whl", hash = "sha256:25060e7162e44242a449ed1a14a4e94b5aef340812754c443459f19c7954be91"}, - {file = "bitarray-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2d951002b11962b26afb31f758c18ad39771f287b100fa5adb1d09a47eaaf5b"}, - {file = "bitarray-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9616ea14917d06736339cf36bb9eaf4eb52110a74136b0dc5eff94e92417d22"}, - {file = "bitarray-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7e2e1ff784c2cdfd863bad31985851427f2d2796e445cec85080c7510cba4315"}, - {file = "bitarray-3.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:911b4a16dce370657e5b8d8b6ba0fbb50dd5e2b24c4416f4b9e664503d3f0502"}, - {file = "bitarray-3.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b47843f2f288fa746dead4394591a3432a358aaad48240283fa230d6e74b0e7"}, - {file = "bitarray-3.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8f95daf0ce2b24815ddf62667229ba5dfc0cfee43eb43b2549766170d0f24ae9"}, - {file = "bitarray-3.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15b9e37bbca59657e4dcc63ad068c821a4676def15f04742c406748a0a11b9c"}, - {file = "bitarray-3.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9d247fcc33c90f2758f4162693250341e3f38cd094f64390076ef33ad0887f9"}, - {file = "bitarray-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:84bb57010a1ab76cf880424a2e0bce8dd26989849d2122ff073aa11bfc271c27"}, - {file = "bitarray-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:27d13c7b886afc5d2fc49d6e92f9c96b1f0a14dc7b5502520c29f3da7550d401"}, - {file = "bitarray-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c4e75bbf9ade3d2cdf1b607a8b353b17d9b3cf54e88b2a5a773f50ae6f1bfbc"}, - {file = "bitarray-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:975a118aa019d745f1398613b27fd8789f60a8cea057a00cdc1abedee123ffe6"}, - {file = "bitarray-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9ed4a2852b3de7a64884afcc6936db771707943249a060aec8e551c16361d478"}, - {file = "bitarray-3.6.0-cp312-cp312-win32.whl", hash = "sha256:5dd9edcab8979a50c2c4dec6d5b66789fb6f630bb52ab90a4548111075a75e48"}, - {file = "bitarray-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:552a93be286ca485914777461b384761519db313e0a7f3012dca424c9610a4d5"}, - {file = "bitarray-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3f96f57cea35ba19fd23a20b38fa0dfa3d87d582507129b8c8e314aa298f59b"}, - {file = "bitarray-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:81e84054b22babcd6c5cc1eac0de2bfc1054ecdf742720cbfb36efbe89ec6c30"}, - {file = "bitarray-3.6.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca643295bf5441dd38dadf7571ca4b63961820eedbffbe46ceba0893bf226203"}, - {file = "bitarray-3.6.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139963494fc3dd5caee5e38c0a03783ef50be118565e94b1dbb0210770f0b32d"}, - {file = "bitarray-3.6.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:243825f56b58bef28bfc602992a8c6d09bbc625628c195498d6020120d632a09"}, - {file = "bitarray-3.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:583b46b3ba44121de5e87e95ae379932dc5fd2e37ebdf2c11a6d7975891425c1"}, - {file = "bitarray-3.6.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f0be27d06732e2833b672a8fcc32fa195bdb22161eb88f8890de15e30264a01"}, - {file = "bitarray-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:507e567aee4806576e20752f22533e8b7ec61e7e75062a7ce9222a0675aa0da6"}, - {file = "bitarray-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:22188943a29072b684cd7c99e0b2cfc0af317cea3366c583d820507e6d1f2ed4"}, - {file = "bitarray-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f92462ea3888c99439f58f7561ecd5dd4cf8b8b1b259ccf5376667b8c46ee747"}, - {file = "bitarray-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3800f3c8c9780f281cf590543fd4b3278fea6988202273a260ecc58136895efb"}, - {file = "bitarray-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a50a66fa34dd7f9dcdbc7602a1b7bf6f9ab030b4f43e892324193423d9ede180"}, - {file = "bitarray-3.6.0-cp313-cp313-win32.whl", hash = "sha256:afa24e5750c9b89ad5a7efef037efe49f4e339f20a94bf678c422c0c71e1207a"}, - {file = "bitarray-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4c5e7edf1e7bcbde3b52058f171a411e2a24a081b3e951d685dfea4c3c383d5"}, - {file = "bitarray-3.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fefd18b29f3b84a0cdea1d86340219d9871c3b0673a38e722a73a2c39591eaa7"}, - {file = "bitarray-3.6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:679856547f0b27b98811b73756bdf53769c23b045a6f95177cae634daabf1ddf"}, - {file = "bitarray-3.6.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51947a00ae9924584fb14c0c1b1f4c1fd916d9abd6f47582f318ab9c9cb9f3d0"}, - {file = "bitarray-3.6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0956322bf4d5e2293e57600aa929c241edf1e209e94e12483bf58c5c691432db"}, - {file = "bitarray-3.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3b521e117ab991d6b3b830656f464b354a42dbea2ca16a0e7d93d573f7ab7ff"}, - {file = "bitarray-3.6.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27eeee915258b105a21a4b0f8aebc5f77bb4dc4fb4063a09dd329fa1fdcbd234"}, - {file = "bitarray-3.6.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:f738051052abc95dc17f9a4c92044294a263fb7f762efdb13e528d419005c0e4"}, - {file = "bitarray-3.6.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:1971050b447023288a2b694a03b400bd5163829cd67b10f19e757fe87cd1161e"}, - {file = "bitarray-3.6.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:a290a417608f50137bec731d1f22ff3efebac72845530807a8433b2db9358c95"}, - {file = "bitarray-3.6.0-cp36-cp36m-musllinux_1_2_s390x.whl", hash = "sha256:8ef3f0977c21190f949d5cfd71ded09de87d330c6d98bd5ecb5bb1135d666d0d"}, - {file = "bitarray-3.6.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:357e07c827bad01f98d0bd0dfdc722f483febeed39140fd75ffd016a451b60b9"}, - {file = "bitarray-3.6.0-cp36-cp36m-win32.whl", hash = "sha256:bdd6412c1f38da7565126b174f4e644f362e317ef0560fac1fb9d0c70900ff4d"}, - {file = "bitarray-3.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a1b3c4ca3bec8e0ad9d32ce62444c5f3913588124a922629aa7d39357b2adf3f"}, - {file = "bitarray-3.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:531e6dfec8058fcf5d69e863b61e6b28e3749b615a4dcc0ab8ad36307c4017fc"}, - {file = "bitarray-3.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6f9e897907757e9c2d722ae6c203d48a04826a14e1495e33935c8583c163a9"}, - {file = "bitarray-3.6.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c9f36055a89b9517db66eb8e80137126bf629c767ceeade4d004e40bc8bcd99"}, - {file = "bitarray-3.6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d6f3a94abd8b44b2bf346ca81ab2ff41ab9146c53905eedf5178b19d9fe53bf"}, - {file = "bitarray-3.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79db23eda81627132327ed292bd813a9af64399b98aaac3d42ad8deeed24cd5e"}, - {file = "bitarray-3.6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d6c9bc14bacdfbfd51fed85f0576973eaaa7b30d81ef93264f8e22b86a9c9f7"}, - {file = "bitarray-3.6.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:60408ec9c0bd76f1fa00d28034429a0316246d31069b982a86aec8d5c99e910a"}, - {file = "bitarray-3.6.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:870ed23361e2918ab1ffc23fe0ab293abf3c372a68ee7387456d13da3e213008"}, - {file = "bitarray-3.6.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:c677849947d523a082be6e0b5c9137f443a54e951a1711ef003ec52910c41ece"}, - {file = "bitarray-3.6.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:a5ce1bdee102f7e60c075274df10b892d9ff5183ad6f5f515973eda8903dfe4c"}, - {file = "bitarray-3.6.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:a33f7c5acf44961f29018b13f0b5f5e1589ac0cfdf75a97c9774cf7ec84d09e0"}, - {file = "bitarray-3.6.0-cp37-cp37m-win32.whl", hash = "sha256:16d0edab54bb9d214319418f65bd15cfc4210ec41a16c3dd0b71e626c803212d"}, - {file = "bitarray-3.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f76784355060999c36fa807b59faecb38f5769ae58283d00270835773f95e35b"}, - {file = "bitarray-3.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cc060bc17b9de27874997d612e37d52f72092f9b59d1e04284a90ed8113cadca"}, - {file = "bitarray-3.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfc417e58f277e949ed662d9cd050ddbb00c0dea8a828abaccc93dc357b7a6d1"}, - {file = "bitarray-3.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:129165b68a3e0c2a633ed0d8557cf5ade24a0b37ca97d7805fa6fc5fb73c19d5"}, - {file = "bitarray-3.6.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50d702149747852923be60cae125285eca8d189d4c7d8832c0c958d4071a0f78"}, - {file = "bitarray-3.6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccf4a73e07bfbd790443d6b3c1f1447ffda23cc9391e40c035d9b7d3514b57b8"}, - {file = "bitarray-3.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7d2dbe628f3db935622a5b80a5c4d95665cdefc4904372aa3c4d786289477f"}, - {file = "bitarray-3.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5da4939e241301f5e1d18118695e8d2c300be90431b66bd43a00376acec45e1e"}, - {file = "bitarray-3.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9930853d451086c4c084d83a87294bdb0c5bc0fa4105a26c487ac09ea62e565b"}, - {file = "bitarray-3.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:e0e4fdeae6c0a9d886749780ec5dcf469e98f27b312efa93008d03eaa2426fd5"}, - {file = "bitarray-3.6.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:79ab1c5f26f23e51d4a44c4397c8a3bf56c306c125dfab6b3eebdfa13d1dca6f"}, - {file = "bitarray-3.6.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b9a03767c937b621ee267507bc394df97fb2f8f61130f39f2033f3c6c191f124"}, - {file = "bitarray-3.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:54bd71f14a5fa9bae73ef92f2e2be894dc36c7a6d1c4962e5969bd8a9aa39325"}, - {file = "bitarray-3.6.0-cp38-cp38-win32.whl", hash = "sha256:7e0851a985a7b10f634188117c825ef99d63402555cca5bc32c7bfc5adaf0d6f"}, - {file = "bitarray-3.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:00628196dd3592972a5183194ab1475dadf9ef2a4cf3fd8c7c184a94934012e8"}, - {file = "bitarray-3.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:69d2d507c1174330c71c834b5d65e66181ad7b42b0d88b5b31804ee9b4f5dae7"}, - {file = "bitarray-3.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:727f7a969416f02ef5c1256541e06f0836fb615022699fa8e2591e85296c5570"}, - {file = "bitarray-3.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42622c42c159ea4535bba7e1e3c97f1fec79505bc6873ae657dc0a8f861c60de"}, - {file = "bitarray-3.6.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f8b12424f8fdf29d1c0749c628bd1530cecfc77374935d096cccc0e4eada232"}, - {file = "bitarray-3.6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:963cbcf296943f7017470d0b705e63e908f32b4f7dbe43f72c22f6fe1bd9ef66"}, - {file = "bitarray-3.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e304f94c0353f6ae5711533b5793b3a45b17aa2c5b07e656649b0af4e0939b5"}, - {file = "bitarray-3.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c533c828d0007fac27cf45e5c1a711e5914dd469db5fe6be5f4e606bf2d7f63"}, - {file = "bitarray-3.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:220d4b8649ef54ac98e5e0e3dd92230247f67270d1524a8b31aa9859007affb0"}, - {file = "bitarray-3.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:407920e9318d94cc6c9611aaa5b5e5963a09f1cbfa17b16b66edea453b3754f4"}, - {file = "bitarray-3.6.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:056fe779f01a867d572e071c0944ac2f3bf58d8bced326040f0bd060af33a209"}, - {file = "bitarray-3.6.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ca87f639094c72268e17bc7f57c1225cc38f9e191a489a0134762e3fec402c1a"}, - {file = "bitarray-3.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b5ad8261f47c2a72d0f676bc40f752db8cfdcab911e970753343836e41d5a9a7"}, - {file = "bitarray-3.6.0-cp39-cp39-win32.whl", hash = "sha256:a773199dc42b5d02fcd46c8add552da2c4725ce2caa069527c7e27b5b6089e85"}, - {file = "bitarray-3.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:157313a124287cbc8a11b55a75def0dd59e68badbc82c2dc2d204dc852742874"}, - {file = "bitarray-3.6.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a763dd33d6e27c9b4db3f8089a5fa39179a8a3cf48ce702b24a857d7c621333c"}, - {file = "bitarray-3.6.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8cf44b012e7493127ce7ca6e469138ac96b3295a117877d5469aabe7c8728d87"}, - {file = "bitarray-3.6.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e297fd2e58afe17e33dd80c231c3a9d850279a2a8625aed1d39f9be9534809e"}, - {file = "bitarray-3.6.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fc8bc65f964c7278deb1b7a69379dab3ecc90095f252deb17365637ebb274d"}, - {file = "bitarray-3.6.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa3c925502bd0b957a96a5619134bcdc0382ef73cffd40bad218ced3586bcf8d"}, - {file = "bitarray-3.6.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9f7796959c9c036a115d34696563f75d4a2912d3b97c15c15f2a36bdd5496ce9"}, - {file = "bitarray-3.6.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b02cc1cac9099c0ec72da09593e7fadb1b6cf88a101acc8153592c700d732d80"}, - {file = "bitarray-3.6.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26691454a6770628882b68fe74e9f84ca2a51512edd49cbb025b14df5a9dd85a"}, - {file = "bitarray-3.6.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a5b0d277087a5bf261a607fc6ff4aaffcf80b300cd19b7a1e9754a4649f5fd4"}, - {file = "bitarray-3.6.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af670708e145b048ead87375b899229443f2d0b4af2d1450d7701c74cd932b03"}, - {file = "bitarray-3.6.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:aeb6db2f4ab54ac21a3851d05130a2aa78a6f6a5f14003f9ae3114fb8b210850"}, - {file = "bitarray-3.6.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:99d16862e802e7c50c3b6cdd1bf041b6142335c9c2b426631f731257adfe5a15"}, - {file = "bitarray-3.6.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:66d8b7a89fac6042f7df9ea97d97ed0f5e404281110a882e3babd909161f85b6"}, - {file = "bitarray-3.6.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62f71b268f14ee6cc3045b95441bfe0518cef1d0b2ffbc6f3e9758f786ff5a03"}, - {file = "bitarray-3.6.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72760411d60d8d76979a20ed3f15586d824db04668b581b86e61158c2b616db0"}, - {file = "bitarray-3.6.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc5029c61f9ebb2d4c247f13584a0ef0e8e49abb13e56460310821aca3ffcaf"}, - {file = "bitarray-3.6.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0ac446f557eb28e3f7c65372608810ff073840627e9037e22ed10bd081793a34"}, - {file = "bitarray-3.6.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b9ae0008cff25e154ef1e3975a1705d344e844ffdeb34c25b007fd48c876e95d"}, - {file = "bitarray-3.6.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:db78cc5c03b446a43413165aa873e2f408e9fd5ddb45533e7bd3b638bace867c"}, - {file = "bitarray-3.6.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cfbdccddaa0ff07789e9e180db127906c676e479e05c04830cd458945de3511"}, - {file = "bitarray-3.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:222cb27ff05bc0aec72498d075dba1facec49a76a7da45740690cebbe3e81e43"}, - {file = "bitarray-3.6.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b58a672ec448fb36839a5fc7bf2b2f60df9a97b872d8bd6ca1a28da6126f5c7"}, - {file = "bitarray-3.6.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b37c9ea942395de029be270f0eca8c141eb14e8455941495cd3b6f95bbe465f4"}, - {file = "bitarray-3.6.0.tar.gz", hash = "sha256:20febc849a1f858e6a57a7d47b323fe9e727c579ddd526d317ad8831748a66a8"}, -] - -[[package]] -name = "certifi" -version = "2025.8.3" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, - {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, - {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, - {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, -] - -[[package]] -name = "click" -version = "8.2.1" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.10" -groups = ["main", "dev"] -files = [ - {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, - {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -markers = {main = "platform_system == \"Windows\""} - -[[package]] -name = "docutils" -version = "0.21.2" -description = "Docutils -- Python Documentation Utilities" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, - {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, -] - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "imagesize" -version = "1.4.1" -description = "Getting image size from png/jpeg/jpeg2000/gif file" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["dev"] -files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] - -[[package]] -name = "iniconfig" -version = "2.1.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, - {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, - {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "joblib" -version = "1.5.1" -description = "Lightweight pipelining with Python functions" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a"}, - {file = "joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444"}, -] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "3.0.2" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, -] - -[[package]] -name = "mdit-py-plugins" -version = "0.4.2" -description = "Collection of plugins for markdown-it-py" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, - {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, -] - -[package.dependencies] -markdown-it-py = ">=1.0.0,<4.0.0" - -[package.extras] -code-style = ["pre-commit"] -rtd = ["myst-parser", "sphinx-book-theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "mslex" -version = "1.3.0" -description = "shlex for windows" -optional = false -python-versions = ">=3.5" -groups = ["dev"] -markers = "sys_platform == \"win32\"" -files = [ - {file = "mslex-1.3.0-py3-none-any.whl", hash = "sha256:c7074b347201b3466fc077c5692fbce9b5f62a63a51f537a53fbbd02eff2eea4"}, - {file = "mslex-1.3.0.tar.gz", hash = "sha256:641c887d1d3db610eee2af37a8e5abda3f70b3006cdfd2d0d29dc0d1ae28a85d"}, -] - -[[package]] -name = "mypy" -version = "1.17.1" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, - {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, - {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, - {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, - {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, - {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, - {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, - {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, - {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, - {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, - {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, - {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, - {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, - {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, - {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, - {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, - {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, - {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, - {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, - {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, - {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, - {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, - {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, - {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, - {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, - {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, - {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, - {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, - {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, - {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, - {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"}, - {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"}, - {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"}, - {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"}, - {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"}, - {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"}, - {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, - {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, -] - -[package.dependencies] -mypy_extensions = ">=1.0.0" -pathspec = ">=0.9.0" -typing_extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, - {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, -] - -[[package]] -name = "myst-parser" -version = "4.0.1" -description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d"}, - {file = "myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4"}, -] - -[package.dependencies] -docutils = ">=0.19,<0.22" -jinja2 = "*" -markdown-it-py = ">=3.0,<4.0" -mdit-py-plugins = ">=0.4.1,<1.0" -pyyaml = "*" -sphinx = ">=7,<9" - -[package.extras] -code-style = ["pre-commit (>=4.0,<5.0)"] -linkify = ["linkify-it-py (>=2.0,<3.0)"] -rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] -testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pygments (<2.19)", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] -testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] - -[[package]] -name = "numpy" -version = "2.3.2" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.11" -groups = ["main"] -files = [ - {file = "numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9"}, - {file = "numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168"}, - {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b"}, - {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8"}, - {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d"}, - {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3"}, - {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f"}, - {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097"}, - {file = "numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220"}, - {file = "numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170"}, - {file = "numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89"}, - {file = "numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b"}, - {file = "numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f"}, - {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0"}, - {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b"}, - {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370"}, - {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73"}, - {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc"}, - {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be"}, - {file = "numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036"}, - {file = "numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f"}, - {file = "numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07"}, - {file = "numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3"}, - {file = "numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b"}, - {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6"}, - {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089"}, - {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2"}, - {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f"}, - {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee"}, - {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6"}, - {file = "numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b"}, - {file = "numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56"}, - {file = "numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2"}, - {file = "numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab"}, - {file = "numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2"}, - {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a"}, - {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286"}, - {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8"}, - {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a"}, - {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91"}, - {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5"}, - {file = "numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5"}, - {file = "numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450"}, - {file = "numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a"}, - {file = "numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a"}, - {file = "numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b"}, - {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125"}, - {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19"}, - {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f"}, - {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5"}, - {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58"}, - {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0"}, - {file = "numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2"}, - {file = "numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b"}, - {file = "numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910"}, - {file = "numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e"}, - {file = "numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45"}, - {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b"}, - {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2"}, - {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0"}, - {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0"}, - {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2"}, - {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf"}, - {file = "numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1"}, - {file = "numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b"}, - {file = "numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619"}, - {file = "numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48"}, -] - -[[package]] -name = "packaging" -version = "25.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, -] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, - {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["coverage", "pytest", "pytest-benchmark"] - -[[package]] -name = "protobuf" -version = "6.31.1" -description = "" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9"}, - {file = "protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447"}, - {file = "protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402"}, - {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39"}, - {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6"}, - {file = "protobuf-6.31.1-cp39-cp39-win32.whl", hash = "sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16"}, - {file = "protobuf-6.31.1-cp39-cp39-win_amd64.whl", hash = "sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9"}, - {file = "protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e"}, - {file = "protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a"}, -] - -[[package]] -name = "psutil" -version = "6.1.1" -description = "Cross-platform lib for process and system monitoring in Python." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -groups = ["dev"] -files = [ - {file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"}, - {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"}, - {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8df0178ba8a9e5bc84fed9cfa61d54601b371fbec5c8eebad27575f1e105c0d4"}, - {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:1924e659d6c19c647e763e78670a05dbb7feaf44a0e9c94bf9e14dfc6ba50468"}, - {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:018aeae2af92d943fdf1da6b58665124897cfc94faa2ca92098838f83e1b1bca"}, - {file = "psutil-6.1.1-cp27-none-win32.whl", hash = "sha256:6d4281f5bbca041e2292be3380ec56a9413b790579b8e593b1784499d0005dac"}, - {file = "psutil-6.1.1-cp27-none-win_amd64.whl", hash = "sha256:c777eb75bb33c47377c9af68f30e9f11bc78e0f07fbf907be4a5d70b2fe5f030"}, - {file = "psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8"}, - {file = "psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377"}, - {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003"}, - {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160"}, - {file = "psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3"}, - {file = "psutil-6.1.1-cp36-cp36m-win32.whl", hash = "sha256:384636b1a64b47814437d1173be1427a7c83681b17a450bfc309a1953e329603"}, - {file = "psutil-6.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8be07491f6ebe1a693f17d4f11e69d0dc1811fa082736500f649f79df7735303"}, - {file = "psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53"}, - {file = "psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649"}, - {file = "psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5"}, -] - -[package.extras] -dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] -test = ["pytest", "pytest-xdist", "setuptools"] - -[[package]] -name = "pydata-sphinx-theme" -version = "0.15.4" -description = "Bootstrap-based Sphinx theme from the PyData community" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pydata_sphinx_theme-0.15.4-py3-none-any.whl", hash = "sha256:2136ad0e9500d0949f96167e63f3e298620040aea8f9c74621959eda5d4cf8e6"}, - {file = "pydata_sphinx_theme-0.15.4.tar.gz", hash = "sha256:7762ec0ac59df3acecf49fd2f889e1b4565dbce8b88b2e29ee06fdd90645a06d"}, -] - -[package.dependencies] -accessible-pygments = "*" -Babel = "*" -beautifulsoup4 = "*" -docutils = "!=0.17.0" -packaging = "*" -pygments = ">=2.7" -sphinx = ">=5" -typing-extensions = "*" - -[package.extras] -a11y = ["pytest-playwright"] -dev = ["pandoc", "pre-commit", "pydata-sphinx-theme[doc,test]", "pyyaml", "sphinx-theme-builder[cli]", "tox"] -doc = ["ablog (>=0.11.8)", "colorama", "graphviz", "ipykernel", "ipyleaflet", "ipywidgets", "jupyter_sphinx", "jupyterlite-sphinx", "linkify-it-py", "matplotlib", "myst-parser", "nbsphinx", "numpy", "numpydoc", "pandas", "plotly", "rich", "sphinx-autoapi (>=3.0.0)", "sphinx-copybutton", "sphinx-design", "sphinx-favicon (>=1.0.1)", "sphinx-sitemap", "sphinx-togglebutton", "sphinxcontrib-youtube (>=1.4.1)", "sphinxext-rediraffe", "xarray"] -i18n = ["Babel", "jinja2"] -test = ["pytest", "pytest-cov", "pytest-regressions", "sphinx[test]"] - -[[package]] -name = "pygments" -version = "2.19.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pytest" -version = "8.4.1" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, - {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, -] - -[package.dependencies] -colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -iniconfig = ">=1" -packaging = ">=20" -pluggy = ">=1.5,<2" -pygments = ">=2.7.2" - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "rcrscore" -version = "0.1.0" -description = "Add your description here" -optional = false -python-versions = ">=3.13" -groups = ["main"] -files = [] -develop = false - -[package.dependencies] -protobuf = ">=6.31.1" -rtree = ">=1.4.0" - -[package.source] -type = "git" -url = "https://github.com/adf-python/rcrs-core-python" -reference = "improve" -resolved_reference = "288c666003039b77c09e91d0067f61ad2b125ce4" - -[[package]] -name = "requests" -version = "2.32.4" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, - {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset_normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "roman-numerals-py" -version = "3.1.0" -description = "Manipulate well-formed Roman numerals" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c"}, - {file = "roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d"}, -] - -[package.extras] -lint = ["mypy (==1.15.0)", "pyright (==1.1.394)", "ruff (==0.9.7)"] -test = ["pytest (>=8)"] - -[[package]] -name = "rtree" -version = "1.4.0" -description = "R-Tree spatial index for Python GIS" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "rtree-1.4.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:4d1bebc418101480aabf41767e772dd2155d3b27b1376cccbd93e4509485e091"}, - {file = "rtree-1.4.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:997f8c38d5dffa3949ea8adb4c8b291ea5cd4ef5ee69455d642dd171baf9991d"}, - {file = "rtree-1.4.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0133d9c54ab3ffe874ba6d411dbe0254765c5e68d92da5b91362c370f16fd997"}, - {file = "rtree-1.4.0-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d3b7bf1fe6463139377995ebe22a01a7005d134707f43672a3c09305e12f5f43"}, - {file = "rtree-1.4.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:27e4a6d617d63dcb82fcd4c2856134b8a3741bd1af3b1a0d98e886054f394da5"}, - {file = "rtree-1.4.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5258e826064eab82439760201e9421ce6d4340789d6d080c1b49367ddd03f61f"}, - {file = "rtree-1.4.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:20d5b3f9cf8bbbcc9fec42ab837c603c5dd86103ef29134300c8da2495c1248b"}, - {file = "rtree-1.4.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a67bee1233370a4c72c0969a96d2a1df1ba404ddd9f146849c53ab420eab361b"}, - {file = "rtree-1.4.0-py3-none-win_amd64.whl", hash = "sha256:ba83efc7b7563905b1bfdfc14490c4bfb59e92e5e6156bdeb6ec5df5117252f4"}, - {file = "rtree-1.4.0.tar.gz", hash = "sha256:9d97c7c5dcf25f6c0599c76d9933368c6a8d7238f2c1d00e76f1a69369ca82a0"}, -] - -[[package]] -name = "ruff" -version = "0.4.10" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"}, - {file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695"}, - {file = "ruff-0.4.10-py3-none-win32.whl", hash = "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca"}, - {file = "ruff-0.4.10-py3-none-win_amd64.whl", hash = "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7"}, - {file = "ruff-0.4.10-py3-none-win_arm64.whl", hash = "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0"}, - {file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"}, -] - -[[package]] -name = "scikit-learn" -version = "1.7.1" -description = "A set of python modules for machine learning and data mining" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "scikit_learn-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:406204dd4004f0517f0b23cf4b28c6245cbd51ab1b6b78153bc784def214946d"}, - {file = "scikit_learn-1.7.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:16af2e44164f05d04337fd1fc3ae7c4ea61fd9b0d527e22665346336920fe0e1"}, - {file = "scikit_learn-1.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2f2e78e56a40c7587dea9a28dc4a49500fa2ead366869418c66f0fd75b80885c"}, - {file = "scikit_learn-1.7.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b62b76ad408a821475b43b7bb90a9b1c9a4d8d125d505c2df0539f06d6e631b1"}, - {file = "scikit_learn-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:9963b065677a4ce295e8ccdee80a1dd62b37249e667095039adcd5bce6e90deb"}, - {file = "scikit_learn-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90c8494ea23e24c0fb371afc474618c1019dc152ce4a10e4607e62196113851b"}, - {file = "scikit_learn-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bb870c0daf3bf3be145ec51df8ac84720d9972170786601039f024bf6d61a518"}, - {file = "scikit_learn-1.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40daccd1b5623f39e8943ab39735cadf0bdce80e67cdca2adcb5426e987320a8"}, - {file = "scikit_learn-1.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30d1f413cfc0aa5a99132a554f1d80517563c34a9d3e7c118fde2d273c6fe0f7"}, - {file = "scikit_learn-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c711d652829a1805a95d7fe96654604a8f16eab5a9e9ad87b3e60173415cb650"}, - {file = "scikit_learn-1.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3cee419b49b5bbae8796ecd690f97aa412ef1674410c23fc3257c6b8b85b8087"}, - {file = "scikit_learn-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2fd8b8d35817b0d9ebf0b576f7d5ffbbabdb55536b0655a8aaae629d7ffd2e1f"}, - {file = "scikit_learn-1.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:588410fa19a96a69763202f1d6b7b91d5d7a5d73be36e189bc6396bfb355bd87"}, - {file = "scikit_learn-1.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3142f0abe1ad1d1c31a2ae987621e41f6b578144a911ff4ac94781a583adad7"}, - {file = "scikit_learn-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ddd9092c1bd469acab337d87930067c87eac6bd544f8d5027430983f1e1ae88"}, - {file = "scikit_learn-1.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b7839687fa46d02e01035ad775982f2470be2668e13ddd151f0f55a5bf123bae"}, - {file = "scikit_learn-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a10f276639195a96c86aa572ee0698ad64ee939a7b042060b98bd1930c261d10"}, - {file = "scikit_learn-1.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13679981fdaebc10cc4c13c43344416a86fcbc61449cb3e6517e1df9d12c8309"}, - {file = "scikit_learn-1.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f1262883c6a63f067a980a8cdd2d2e7f2513dddcef6a9eaada6416a7a7cbe43"}, - {file = "scikit_learn-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca6d31fb10e04d50bfd2b50d66744729dbb512d4efd0223b864e2fdbfc4cee11"}, - {file = "scikit_learn-1.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:781674d096303cfe3d351ae6963ff7c958db61cde3421cd490e3a5a58f2a94ae"}, - {file = "scikit_learn-1.7.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:10679f7f125fe7ecd5fad37dd1aa2daae7e3ad8df7f3eefa08901b8254b3e12c"}, - {file = "scikit_learn-1.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f812729e38c8cb37f760dce71a9b83ccfb04f59b3dca7c6079dcdc60544fa9e"}, - {file = "scikit_learn-1.7.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88e1a20131cf741b84b89567e1717f27a2ced228e0f29103426102bc2e3b8ef7"}, - {file = "scikit_learn-1.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b1bd1d919210b6a10b7554b717c9000b5485aa95a1d0f177ae0d7ee8ec750da5"}, - {file = "scikit_learn-1.7.1.tar.gz", hash = "sha256:24b3f1e976a4665aa74ee0fcaac2b8fccc6ae77c8e07ab25da3ba6d3292b9802"}, -] - -[package.dependencies] -joblib = ">=1.2.0" -numpy = ">=1.22.0" -scipy = ">=1.8.0" -threadpoolctl = ">=3.1.0" - -[package.extras] -benchmark = ["matplotlib (>=3.5.0)", "memory_profiler (>=0.57.0)", "pandas (>=1.4.0)"] -build = ["cython (>=3.0.10)", "meson-python (>=0.17.1)", "numpy (>=1.22.0)", "scipy (>=1.8.0)"] -docs = ["Pillow (>=8.4.0)", "matplotlib (>=3.5.0)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.4.0)", "plotly (>=5.14.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.19.0)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-design (>=0.6.0)", "sphinx-gallery (>=0.17.1)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)", "towncrier (>=24.8.0)"] -examples = ["matplotlib (>=3.5.0)", "pandas (>=1.4.0)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.19.0)", "seaborn (>=0.9.0)"] -install = ["joblib (>=1.2.0)", "numpy (>=1.22.0)", "scipy (>=1.8.0)", "threadpoolctl (>=3.1.0)"] -maintenance = ["conda-lock (==3.0.1)"] -tests = ["matplotlib (>=3.5.0)", "mypy (>=1.15)", "numpydoc (>=1.2.0)", "pandas (>=1.4.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pyamg (>=4.2.1)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.11.7)", "scikit-image (>=0.19.0)"] - -[[package]] -name = "scipy" -version = "1.16.1" -description = "Fundamental algorithms for scientific computing in Python" -optional = false -python-versions = ">=3.11" -groups = ["main"] -files = [ - {file = "scipy-1.16.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030"}, - {file = "scipy-1.16.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7"}, - {file = "scipy-1.16.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77"}, - {file = "scipy-1.16.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe"}, - {file = "scipy-1.16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b"}, - {file = "scipy-1.16.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7"}, - {file = "scipy-1.16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958"}, - {file = "scipy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39"}, - {file = "scipy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596"}, - {file = "scipy-1.16.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c"}, - {file = "scipy-1.16.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04"}, - {file = "scipy-1.16.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919"}, - {file = "scipy-1.16.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921"}, - {file = "scipy-1.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725"}, - {file = "scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618"}, - {file = "scipy-1.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d"}, - {file = "scipy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119"}, - {file = "scipy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a"}, - {file = "scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f"}, - {file = "scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb"}, - {file = "scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c"}, - {file = "scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608"}, - {file = "scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f"}, - {file = "scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b"}, - {file = "scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45"}, - {file = "scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65"}, - {file = "scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab"}, - {file = "scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6"}, - {file = "scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27"}, - {file = "scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7"}, - {file = "scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6"}, - {file = "scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4"}, - {file = "scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3"}, - {file = "scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7"}, - {file = "scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc"}, - {file = "scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39"}, - {file = "scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318"}, - {file = "scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc"}, - {file = "scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8"}, - {file = "scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e"}, - {file = "scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0"}, - {file = "scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b"}, - {file = "scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731"}, - {file = "scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3"}, - {file = "scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19"}, - {file = "scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65"}, - {file = "scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2"}, - {file = "scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d"}, - {file = "scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695"}, - {file = "scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86"}, - {file = "scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff"}, - {file = "scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4"}, - {file = "scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3"}, - {file = "scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998"}, - {file = "scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3"}, -] - -[package.dependencies] -numpy = ">=1.25.2,<2.6" - -[package.extras] -dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] -doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "linkify-it-py", "matplotlib (>=3.5)", "myst-nb (>=1.2.0)", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.2.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict (>=2.3.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] - -[[package]] -name = "setuptools" -version = "80.9.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, - {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] - -[[package]] -name = "shapely" -version = "2.1.1" -description = "Manipulation and analysis of geometric objects" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "shapely-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8ccc872a632acb7bdcb69e5e78df27213f7efd195882668ffba5405497337c6"}, - {file = "shapely-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f24f2ecda1e6c091da64bcbef8dd121380948074875bd1b247b3d17e99407099"}, - {file = "shapely-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45112a5be0b745b49e50f8829ce490eb67fefb0cea8d4f8ac5764bfedaa83d2d"}, - {file = "shapely-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c10ce6f11904d65e9bbb3e41e774903c944e20b3f0b282559885302f52f224a"}, - {file = "shapely-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:61168010dfe4e45f956ffbbaf080c88afce199ea81eb1f0ac43230065df320bd"}, - {file = "shapely-2.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cacf067cdff741cd5c56a21c52f54ece4e4dad9d311130493a791997da4a886b"}, - {file = "shapely-2.1.1-cp310-cp310-win32.whl", hash = "sha256:23b8772c3b815e7790fb2eab75a0b3951f435bc0fce7bb146cb064f17d35ab4f"}, - {file = "shapely-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:2c7b2b6143abf4fa77851cef8ef690e03feade9a0d48acd6dc41d9e0e78d7ca6"}, - {file = "shapely-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587a1aa72bc858fab9b8c20427b5f6027b7cbc92743b8e2c73b9de55aa71c7a7"}, - {file = "shapely-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fa5c53b0791a4b998f9ad84aad456c988600757a96b0a05e14bba10cebaaaea"}, - {file = "shapely-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aabecd038841ab5310d23495253f01c2a82a3aedae5ab9ca489be214aa458aa7"}, - {file = "shapely-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586f6aee1edec04e16227517a866df3e9a2e43c1f635efc32978bb3dc9c63753"}, - {file = "shapely-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b9878b9e37ad26c72aada8de0c9cfe418d9e2ff36992a1693b7f65a075b28647"}, - {file = "shapely-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9a531c48f289ba355e37b134e98e28c557ff13965d4653a5228d0f42a09aed0"}, - {file = "shapely-2.1.1-cp311-cp311-win32.whl", hash = "sha256:4866de2673a971820c75c0167b1f1cd8fb76f2d641101c23d3ca021ad0449bab"}, - {file = "shapely-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:20a9d79958b3d6c70d8a886b250047ea32ff40489d7abb47d01498c704557a93"}, - {file = "shapely-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2827365b58bf98efb60affc94a8e01c56dd1995a80aabe4b701465d86dcbba43"}, - {file = "shapely-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c551f7fa7f1e917af2347fe983f21f212863f1d04f08eece01e9c275903fad"}, - {file = "shapely-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78dec4d4fbe7b1db8dc36de3031767e7ece5911fb7782bc9e95c5cdec58fb1e9"}, - {file = "shapely-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:872d3c0a7b8b37da0e23d80496ec5973c4692920b90de9f502b5beb994bbaaef"}, - {file = "shapely-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e2b9125ebfbc28ecf5353511de62f75a8515ae9470521c9a693e4bb9fbe0cf1"}, - {file = "shapely-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4b96cea171b3d7f6786976a0520f178c42792897653ecca0c5422fb1e6946e6d"}, - {file = "shapely-2.1.1-cp312-cp312-win32.whl", hash = "sha256:39dca52201e02996df02e447f729da97cfb6ff41a03cb50f5547f19d02905af8"}, - {file = "shapely-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:13d643256f81d55a50013eff6321142781cf777eb6a9e207c2c9e6315ba6044a"}, - {file = "shapely-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3004a644d9e89e26c20286d5fdc10f41b1744c48ce910bd1867fdff963fe6c48"}, - {file = "shapely-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1415146fa12d80a47d13cfad5310b3c8b9c2aa8c14a0c845c9d3d75e77cb54f6"}, - {file = "shapely-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21fcab88b7520820ec16d09d6bea68652ca13993c84dffc6129dc3607c95594c"}, - {file = "shapely-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ce6a5cc52c974b291237a96c08c5592e50f066871704fb5b12be2639d9026a"}, - {file = "shapely-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:04e4c12a45a1d70aeb266618d8cf81a2de9c4df511b63e105b90bfdfb52146de"}, - {file = "shapely-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6ca74d851ca5264aae16c2b47e96735579686cb69fa93c4078070a0ec845b8d8"}, - {file = "shapely-2.1.1-cp313-cp313-win32.whl", hash = "sha256:fd9130501bf42ffb7e0695b9ea17a27ae8ce68d50b56b6941c7f9b3d3453bc52"}, - {file = "shapely-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:ab8d878687b438a2f4c138ed1a80941c6ab0029e0f4c785ecfe114413b498a97"}, - {file = "shapely-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c062384316a47f776305ed2fa22182717508ffdeb4a56d0ff4087a77b2a0f6d"}, - {file = "shapely-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4ecf6c196b896e8f1360cc219ed4eee1c1e5f5883e505d449f263bd053fb8c05"}, - {file = "shapely-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb00070b4c4860f6743c600285109c273cca5241e970ad56bb87bef0be1ea3a0"}, - {file = "shapely-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14a9afa5fa980fbe7bf63706fdfb8ff588f638f145a1d9dbc18374b5b7de913"}, - {file = "shapely-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b640e390dabde790e3fb947198b466e63223e0a9ccd787da5f07bcb14756c28d"}, - {file = "shapely-2.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:69e08bf9697c1b73ec6aa70437db922bafcea7baca131c90c26d59491a9760f9"}, - {file = "shapely-2.1.1-cp313-cp313t-win32.whl", hash = "sha256:ef2d09d5a964cc90c2c18b03566cf918a61c248596998a0301d5b632beadb9db"}, - {file = "shapely-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7"}, - {file = "shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772"}, -] - -[package.dependencies] -numpy = ">=1.21" - -[package.extras] -docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] -test = ["pytest", "pytest-cov", "scipy-doctest"] - -[[package]] -name = "snowballstemmer" -version = "3.0.1" -description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*" -groups = ["dev"] -files = [ - {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, - {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, -] - -[[package]] -name = "soupsieve" -version = "2.7" -description = "A modern CSS selector implementation for Beautiful Soup." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, - {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, -] - -[[package]] -name = "sphinx" -version = "8.2.3" -description = "Python documentation generator" -optional = false -python-versions = ">=3.11" -groups = ["dev"] -files = [ - {file = "sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3"}, - {file = "sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348"}, -] - -[package.dependencies] -alabaster = ">=0.7.14" -babel = ">=2.13" -colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} -docutils = ">=0.20,<0.22" -imagesize = ">=1.3" -Jinja2 = ">=3.1" -packaging = ">=23.0" -Pygments = ">=2.17" -requests = ">=2.30.0" -roman-numerals-py = ">=1.0.0" -snowballstemmer = ">=2.2" -sphinxcontrib-applehelp = ">=1.0.7" -sphinxcontrib-devhelp = ">=1.0.6" -sphinxcontrib-htmlhelp = ">=2.0.6" -sphinxcontrib-jsmath = ">=1.0.1" -sphinxcontrib-qthelp = ">=1.0.6" -sphinxcontrib-serializinghtml = ">=1.1.9" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["betterproto (==2.0.0b6)", "mypy (==1.15.0)", "pypi-attestations (==0.0.21)", "pyright (==1.1.395)", "pytest (>=8.0)", "ruff (==0.9.9)", "sphinx-lint (>=0.9)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.19.0.20250219)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241128)", "types-requests (==2.32.0.20241016)", "types-urllib3 (==1.26.25.14)"] -test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "pytest-xdist[psutil] (>=3.4)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] - -[[package]] -name = "sphinx-book-theme" -version = "1.1.4" -description = "A clean book theme for scientific explanations and documentation with Sphinx" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "sphinx_book_theme-1.1.4-py3-none-any.whl", hash = "sha256:843b3f5c8684640f4a2d01abd298beb66452d1b2394cd9ef5be5ebd5640ea0e1"}, - {file = "sphinx_book_theme-1.1.4.tar.gz", hash = "sha256:73efe28af871d0a89bd05856d300e61edce0d5b2fbb7984e84454be0fedfe9ed"}, -] - -[package.dependencies] -pydata-sphinx-theme = "0.15.4" -sphinx = ">=6.1" - -[package.extras] -code-style = ["pre-commit"] -doc = ["ablog", "folium", "ipywidgets", "matplotlib", "myst-nb", "nbclient", "numpy", "numpydoc", "pandas", "plotly", "sphinx-copybutton", "sphinx-design", "sphinx-examples", "sphinx-tabs", "sphinx-thebe", "sphinx-togglebutton", "sphinxcontrib-bibtex", "sphinxcontrib-youtube", "sphinxext-opengraph"] -test = ["beautifulsoup4", "coverage", "defusedxml", "myst-nb", "pytest", "pytest-cov", "pytest-regressions", "sphinx_thebe"] - -[[package]] -name = "sphinx-copybutton" -version = "0.5.2" -description = "Add a copy button to each of your code cells." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"}, - {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"}, -] - -[package.dependencies] -sphinx = ">=1.8" - -[package.extras] -code-style = ["pre-commit (==2.12.1)"] -rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] - -[[package]] -name = "sphinx-intl" -version = "2.3.2" -description = "Sphinx utility that make it easy to translate and to apply translation." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "sphinx_intl-2.3.2-py3-none-any.whl", hash = "sha256:f0082f9383066bab8406129a2ed531d21c38706d08467bf5ca3714e8914bb2be"}, - {file = "sphinx_intl-2.3.2.tar.gz", hash = "sha256:04b0d8ea04d111a7ba278b17b7b3fe9625c58b6f8ffb78bb8a1dd1288d88c1c7"}, -] - -[package.dependencies] -babel = ">=2.9.0" -click = ">=8.0.0" -sphinx = "*" - -[package.extras] -test = ["pytest (>=8.3.5)"] - -[[package]] -name = "sphinx-rtd-theme" -version = "3.0.2" -description = "Read the Docs theme for Sphinx" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13"}, - {file = "sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85"}, -] - -[package.dependencies] -docutils = ">0.18,<0.22" -sphinx = ">=6,<9" -sphinxcontrib-jquery = ">=4,<5" - -[package.extras] -dev = ["bump2version", "transifex-client", "twine", "wheel"] - -[[package]] -name = "sphinx-togglebutton" -version = "0.3.2" -description = "Toggle page content and collapse admonitions in Sphinx." -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "sphinx-togglebutton-0.3.2.tar.gz", hash = "sha256:ab0c8b366427b01e4c89802d5d078472c427fa6e9d12d521c34fa0442559dc7a"}, - {file = "sphinx_togglebutton-0.3.2-py3-none-any.whl", hash = "sha256:9647ba7874b7d1e2d43413d8497153a85edc6ac95a3fea9a75ef9c1e08aaae2b"}, -] - -[package.dependencies] -docutils = "*" -setuptools = "*" -sphinx = "*" -wheel = "*" - -[package.extras] -sphinx = ["matplotlib", "myst-nb", "numpy", "sphinx-book-theme", "sphinx-design", "sphinx-examples"] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "2.0.0" -description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, - {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "2.0.0" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, - {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.1.0" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, - {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["html5lib", "pytest"] - -[[package]] -name = "sphinxcontrib-jquery" -version = "4.1" -description = "Extension to include jQuery on newer Sphinx releases" -optional = false -python-versions = ">=2.7" -groups = ["dev"] -files = [ - {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, - {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, -] - -[package.dependencies] -Sphinx = ">=1.8" - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -description = "A sphinx extension which renders display math in HTML via JavaScript" -optional = false -python-versions = ">=3.5" -groups = ["dev"] -files = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] - -[package.extras] -test = ["flake8", "mypy", "pytest"] - -[[package]] -name = "sphinxcontrib-mermaid" -version = "1.0.0" -description = "Mermaid diagrams in yours Sphinx powered docs" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "sphinxcontrib_mermaid-1.0.0-py3-none-any.whl", hash = "sha256:60b72710ea02087f212028feb09711225fbc2e343a10d34822fe787510e1caa3"}, - {file = "sphinxcontrib_mermaid-1.0.0.tar.gz", hash = "sha256:2e8ab67d3e1e2816663f9347d026a8dee4a858acdd4ad32dd1c808893db88146"}, -] - -[package.dependencies] -pyyaml = "*" -sphinx = "*" - -[package.extras] -test = ["defusedxml", "myst-parser", "pytest", "ruff", "sphinx"] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "2.0.0" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, - {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["defusedxml (>=0.7.1)", "pytest"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "2.0.0" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, - {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "structlog" -version = "24.4.0" -description = "Structured Logging for Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "structlog-24.4.0-py3-none-any.whl", hash = "sha256:597f61e80a91cc0749a9fd2a098ed76715a1c8a01f73e336b746504d1aad7610"}, - {file = "structlog-24.4.0.tar.gz", hash = "sha256:b27bfecede327a6d2da5fbc96bd859f114ecc398a6389d664f62085ee7ae6fc4"}, -] - -[package.extras] -dev = ["freezegun (>=0.2.8)", "mypy (>=1.4)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "rich", "simplejson", "twisted"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "sphinxext-opengraph", "twisted"] -tests = ["freezegun (>=0.2.8)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "simplejson"] -typing = ["mypy (>=1.4)", "rich", "twisted"] - -[[package]] -name = "taskipy" -version = "1.14.1" -description = "tasks runner for python projects" -optional = false -python-versions = "<4.0,>=3.6" -groups = ["dev"] -files = [ - {file = "taskipy-1.14.1-py3-none-any.whl", hash = "sha256:6e361520f29a0fd2159848e953599f9c75b1d0b047461e4965069caeb94908f1"}, - {file = "taskipy-1.14.1.tar.gz", hash = "sha256:410fbcf89692dfd4b9f39c2b49e1750b0a7b81affd0e2d7ea8c35f9d6a4774ed"}, -] - -[package.dependencies] -colorama = ">=0.4.4,<0.5.0" -mslex = {version = ">=1.1.0,<2.0.0", markers = "sys_platform == \"win32\""} -psutil = ">=5.7.2,<7" -tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} - -[[package]] -name = "threadpoolctl" -version = "3.6.0" -description = "threadpoolctl" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb"}, - {file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"}, -] - -[[package]] -name = "tomli" -version = "2.2.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, - {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, - {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, - {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, - {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, - {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, - {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, - {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, - {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, - {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, -] - -[[package]] -name = "types-protobuf" -version = "4.25.0.20240417" -description = "Typing stubs for protobuf" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "types-protobuf-4.25.0.20240417.tar.gz", hash = "sha256:c34eff17b9b3a0adb6830622f0f302484e4c089f533a46e3f147568313544352"}, - {file = "types_protobuf-4.25.0.20240417-py3-none-any.whl", hash = "sha256:e9b613227c2127e3d4881d75d93c93b4d6fd97b5f6a099a0b654a05351c8685d"}, -] - -[[package]] -name = "types-pyyaml" -version = "6.0.12.20250516" -description = "Typing stubs for PyYAML" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530"}, - {file = "types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba"}, -] - -[[package]] -name = "typing-extensions" -version = "4.14.1" -description = "Backported and Experimental Type Hints for Python 3.9+" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, - {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, -] - -[[package]] -name = "urllib3" -version = "2.5.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "wheel" -version = "0.45.1" -description = "A built-package format for Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248"}, - {file = "wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729"}, -] - -[package.extras] -test = ["pytest (>=6.0.0)", "setuptools (>=65)"] - -[metadata] -lock-version = "2.1" -python-versions = "^3.13" -content-hash = "0f25d8ec7e7a27c7ea9006b97135f44ede3114ec3a9a6f19702345464d0d7b51" diff --git a/poetry.toml b/poetry.toml deleted file mode 100644 index ab1033b..0000000 --- a/poetry.toml +++ /dev/null @@ -1,2 +0,0 @@ -[virtualenvs] -in-project = true diff --git a/pyproject.toml b/pyproject.toml index 080aba5..0e429bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,55 +1,47 @@ -[tool.poetry] +[project] name = "adf_core_python" -version = "0.1.5" +version = "0.2.0" description = "Agent Development Framework for Python" +readme = "README.md" authors = [ - "Haruki Uehara ", - "Yuki Shimada ", + { name = "Haruki Uehara", email = "k19016kk@maslab.aitech.ac.jp"}, + { name = "Yuki Shimada", email = "shimapaca@maslab.aitech.ac.jp" } +] +requires-python = ">=3.13" +dependencies = [ + "bitarray>=3.6.0", + "click>=8.2.1", + "jinja2>=3.1.6", + "protobuf>=6.31.1", + "pyyaml>=6.0.2", + "rcrscore", + "rtree>=1.4.0", + "scikit-learn>=1.7.1", + "shapely>=2.1.1", + "structlog>=25.4.0", + "types-pyyaml>=6.0.12.20250516", ] -readme = "README.md" -package-mode = true - -[tool.poetry.dependencies] -python = "^3.13" -rcrscore = { git = "https://github.com/adf-python/rcrs-core-python", branch = "improve" } -pyyaml = "^6.0.2" -types-pyyaml = "^6.0.12.20240808" -scikit-learn = "^1.5.2" -structlog = "^24.4.0" -bitarray = "^3.0.0" -shapely = "^2.0.6" -click = "^8.1.7" -jinja2 = "3.1.6" - - -[tool.poetry.group.dev.dependencies] -taskipy = "^1.12.2" -pytest = "^8.3.2" -mypy = "^1.9.0" -types-protobuf = "^4.25.0.20240410" -ruff = "^0.4.4" -sphinx = "^8.1.3" -myst-parser = "^4.0.0" -sphinx-book-theme = "^1.1.3" -sphinx-copybutton = "^0.5.2" -sphinxcontrib-mermaid = "^1.0.0" -sphinx-togglebutton = "^0.3.2" -sphinx-intl = "^2.3.1" -sphinx-rtd-theme = "^3.0.2" [build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" - -[tool.poetry.scripts] -adf-core-python = "adf_core_python.cli.cli:cli" +requires = ["uv_build>=0.8.2,<0.9.0"] +build-backend = "uv_build" + +[dependency-groups] +dev = [ + "mypy>=1.17.1", + "myst-parser>=4.0.1", + "pytest>=8.4.1", + "ruff>=0.12.5", + "sphinx>=8.2.3", + "sphinx-book-theme>=1.1.4", + "sphinx-copybutton>=0.5.2", + "sphinx-intl>=2.3.2", + "sphinx-rtd-theme>=3.0.2", + "sphinx-togglebutton>=0.3.2", + "sphinxcontrib-mermaid>=1.0.0", + "types-protobuf>=6.30.2.20250703", +] -[tool.taskipy.tasks] -format = "ruff format ." -lint = "ruff check ." -typecheck = "mypy ." -test = "pytest" -precommit = "task format && task lint && task typecheck && task test" [tool.ruff] # Exclude a variety of commonly ignored directories. @@ -82,11 +74,12 @@ exclude = [ "venv", "pb2", "docs", + "proto", ] # Same as Black. line-length = 88 -indent-width = 4 +indent-width = 2 # Assume Python 3.12 target-version = "py312" @@ -134,10 +127,12 @@ docstring-code-line-length = "dynamic" [tool.mypy] ignore_missing_imports = true -show_error_context = true # エラー時のメッセージを詳細表示 -show_column_numbers = true # エラー発生箇所の行数/列数を表示 -disallow_untyped_defs = true # 関数定義の引数/戻り値に型アノテーション必須 -no_implicit_optional = true # デフォルト引数に None を取る場合型アノテーションに Optional 必須 -check_untyped_defs = true # 型注釈がない関数やメソッドに対して型チェックを行う -warn_redundant_casts = true # 冗長なキャストに警告 -exclude = ["docs/*"] +show_error_context = true +show_column_numbers = true +disallow_untyped_defs = true +no_implicit_optional = true +check_untyped_defs = true +warn_redundant_casts = true + +[tool.uv.sources] +rcrscore = { git = "https://github.com/adf-python/rcrs-core-python" , tag = "v0.2.0" } diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..8d71127 --- /dev/null +++ b/uv.lock @@ -0,0 +1,947 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "accessible-pygments" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, +] + +[[package]] +name = "adf-core-python" +version = "0.2.0" +source = { editable = "." } +dependencies = [ + { name = "bitarray" }, + { name = "click" }, + { name = "jinja2" }, + { name = "protobuf" }, + { name = "pyyaml" }, + { name = "rcrscore" }, + { name = "rtree" }, + { name = "scikit-learn" }, + { name = "shapely" }, + { name = "structlog" }, + { name = "types-pyyaml" }, +] + +[package.dev-dependencies] +dev = [ + { name = "mypy" }, + { name = "myst-parser" }, + { name = "pytest" }, + { name = "ruff" }, + { name = "sphinx" }, + { name = "sphinx-book-theme" }, + { name = "sphinx-copybutton" }, + { name = "sphinx-intl" }, + { name = "sphinx-rtd-theme" }, + { name = "sphinx-togglebutton" }, + { name = "sphinxcontrib-mermaid" }, + { name = "types-protobuf" }, +] + +[package.metadata] +requires-dist = [ + { name = "bitarray", specifier = ">=3.6.0" }, + { name = "click", specifier = ">=8.2.1" }, + { name = "jinja2", specifier = ">=3.1.6" }, + { name = "protobuf", specifier = ">=6.31.1" }, + { name = "pyyaml", specifier = ">=6.0.2" }, + { name = "rcrscore", git = "https://github.com/adf-python/rcrs-core-python?tag=v0.2.0" }, + { name = "rtree", specifier = ">=1.4.0" }, + { name = "scikit-learn", specifier = ">=1.7.1" }, + { name = "shapely", specifier = ">=2.1.1" }, + { name = "structlog", specifier = ">=25.4.0" }, + { name = "types-pyyaml", specifier = ">=6.0.12.20250516" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "mypy", specifier = ">=1.17.1" }, + { name = "myst-parser", specifier = ">=4.0.1" }, + { name = "pytest", specifier = ">=8.4.1" }, + { name = "ruff", specifier = ">=0.12.5" }, + { name = "sphinx", specifier = ">=8.2.3" }, + { name = "sphinx-book-theme", specifier = ">=1.1.4" }, + { name = "sphinx-copybutton", specifier = ">=0.5.2" }, + { name = "sphinx-intl", specifier = ">=2.3.2" }, + { name = "sphinx-rtd-theme", specifier = ">=3.0.2" }, + { name = "sphinx-togglebutton", specifier = ">=0.3.2" }, + { name = "sphinxcontrib-mermaid", specifier = ">=1.0.0" }, + { name = "types-protobuf", specifier = ">=6.30.2.20250703" }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" }, +] + +[[package]] +name = "bitarray" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/ee/3b2fcbac3a4192e5d079aaa1850dff2f9ac625861c4c644819c2b34292ec/bitarray-3.6.0.tar.gz", hash = "sha256:20febc849a1f858e6a57a7d47b323fe9e727c579ddd526d317ad8831748a66a8", size = 147946, upload-time = "2025-07-29T18:03:56.681Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/2c/21066c7a97b2c88037b0fc04480fa13b0031c30c6f70452dc9c84fb2b087/bitarray-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3f96f57cea35ba19fd23a20b38fa0dfa3d87d582507129b8c8e314aa298f59b", size = 144156, upload-time = "2025-07-29T18:01:35.58Z" }, + { url = "https://files.pythonhosted.org/packages/34/a5/9cc42ea0c440ac1c2a65375688ac5891da12b3820f4a32440791d25ed668/bitarray-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:81e84054b22babcd6c5cc1eac0de2bfc1054ecdf742720cbfb36efbe89ec6c30", size = 140916, upload-time = "2025-07-29T18:01:36.67Z" }, + { url = "https://files.pythonhosted.org/packages/d7/66/709d259d855528213b1099facddb08d6108cb0074cf88dc357cdd07bacff/bitarray-3.6.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca643295bf5441dd38dadf7571ca4b63961820eedbffbe46ceba0893bf226203", size = 324713, upload-time = "2025-07-29T18:01:37.925Z" }, + { url = "https://files.pythonhosted.org/packages/6c/67/831e366ea4f0d52d622482b8475f87040cbc210d8f5f383935a4cc6363fe/bitarray-3.6.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139963494fc3dd5caee5e38c0a03783ef50be118565e94b1dbb0210770f0b32d", size = 341300, upload-time = "2025-07-29T18:01:39.56Z" }, + { url = "https://files.pythonhosted.org/packages/66/c9/197375b63ca768ac8b1e624f27dc0eccdd451f94c6b9bf8950500d8da134/bitarray-3.6.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:243825f56b58bef28bfc602992a8c6d09bbc625628c195498d6020120d632a09", size = 333724, upload-time = "2025-07-29T18:01:40.861Z" }, + { url = "https://files.pythonhosted.org/packages/e1/23/96c882d798b8bc9d5354ad1fba18ad3ad4f3c0a661a296c8e51ca2941e0f/bitarray-3.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:583b46b3ba44121de5e87e95ae379932dc5fd2e37ebdf2c11a6d7975891425c1", size = 327276, upload-time = "2025-07-29T18:01:42.039Z" }, + { url = "https://files.pythonhosted.org/packages/20/8e/51751fe0e6f9fe7980b0467b471ba9ab8d1713a2a6576980d18143511656/bitarray-3.6.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f0be27d06732e2833b672a8fcc32fa195bdb22161eb88f8890de15e30264a01", size = 314903, upload-time = "2025-07-29T18:01:43.302Z" }, + { url = "https://files.pythonhosted.org/packages/49/7a/e4db9876e6e8bb261e64a384d3adb4372f13099b356e559cec85d022b897/bitarray-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:507e567aee4806576e20752f22533e8b7ec61e7e75062a7ce9222a0675aa0da6", size = 322551, upload-time = "2025-07-29T18:01:44.548Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5a/9460070e6cb671067cc2e115a82da6fc9ef0958542b98b07a5ed4a05a97b/bitarray-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:22188943a29072b684cd7c99e0b2cfc0af317cea3366c583d820507e6d1f2ed4", size = 316128, upload-time = "2025-07-29T18:01:45.789Z" }, + { url = "https://files.pythonhosted.org/packages/34/6f/f5d78c8e908750b9c3d5839eca2af5f6e99d6c7fe8a0498ef79a1af90bd8/bitarray-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f92462ea3888c99439f58f7561ecd5dd4cf8b8b1b259ccf5376667b8c46ee747", size = 339337, upload-time = "2025-07-29T18:01:47.684Z" }, + { url = "https://files.pythonhosted.org/packages/0d/d3/f740b601eae4e28e22d8560877fe9881f1b7a96fcb23b186e8580d328929/bitarray-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3800f3c8c9780f281cf590543fd4b3278fea6988202273a260ecc58136895efb", size = 338607, upload-time = "2025-07-29T18:01:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/4e/81/b9451089eea0ef66996852d2694b0f5afc0a76b1bc45c9a4f8204ae8674d/bitarray-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a50a66fa34dd7f9dcdbc7602a1b7bf6f9ab030b4f43e892324193423d9ede180", size = 324788, upload-time = "2025-07-29T18:01:51.454Z" }, + { url = "https://files.pythonhosted.org/packages/82/e8/80620fc60ad34bff647881a4f25c15b992c524e0f7af9c7c6c573b03556e/bitarray-3.6.0-cp313-cp313-win32.whl", hash = "sha256:afa24e5750c9b89ad5a7efef037efe49f4e339f20a94bf678c422c0c71e1207a", size = 137841, upload-time = "2025-07-29T18:01:52.95Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ee/303be88b847da29a067babc690e231d7838520dc1af57d14dad5a7ca095c/bitarray-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4c5e7edf1e7bcbde3b52058f171a411e2a24a081b3e951d685dfea4c3c383d5", size = 144820, upload-time = "2025-07-29T18:01:54.137Z" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/fe/0f5a938c54105553436dbff7a61dc4fed4b1b2c98852f8833beaf4d5968f/joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444", size = 330475, upload-time = "2025-05-23T12:04:37.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/4f/1195bbac8e0c2acc5f740661631d8d750dc38d4a32b23ee5df3cde6f4e0d/joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a", size = 307746, upload-time = "2025-05-23T12:04:35.124Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542, upload-time = "2024-09-09T20:27:49.564Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316, upload-time = "2024-09-09T20:27:48.397Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mypy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338, upload-time = "2025-07-31T07:53:38.873Z" }, + { url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066, upload-time = "2025-07-31T07:54:14.707Z" }, + { url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473, upload-time = "2025-07-31T07:53:14.504Z" }, + { url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296, upload-time = "2025-07-31T07:53:03.896Z" }, + { url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657, upload-time = "2025-07-31T07:54:08.576Z" }, + { url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320, upload-time = "2025-07-31T07:53:01.341Z" }, + { url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037, upload-time = "2025-07-31T07:54:10.942Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550, upload-time = "2025-07-31T07:53:41.307Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963, upload-time = "2025-07-31T07:53:16.878Z" }, + { url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189, upload-time = "2025-07-31T07:54:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322, upload-time = "2025-07-31T07:53:10.551Z" }, + { url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879, upload-time = "2025-07-31T07:52:56.683Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "myst-parser" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985, upload-time = "2025-02-12T10:53:03.833Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579, upload-time = "2025-02-12T10:53:02.078Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/c0/c6bb172c916b00700ed3bf71cb56175fd1f7dbecebf8353545d0b5519f6c/numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3", size = 20949074, upload-time = "2025-07-24T20:43:07.813Z" }, + { url = "https://files.pythonhosted.org/packages/20/4e/c116466d22acaf4573e58421c956c6076dc526e24a6be0903219775d862e/numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b", size = 14177311, upload-time = "2025-07-24T20:43:29.335Z" }, + { url = "https://files.pythonhosted.org/packages/78/45/d4698c182895af189c463fc91d70805d455a227261d950e4e0f1310c2550/numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6", size = 5106022, upload-time = "2025-07-24T20:43:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/9f/76/3e6880fef4420179309dba72a8c11f6166c431cf6dee54c577af8906f914/numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089", size = 6640135, upload-time = "2025-07-24T20:43:49.28Z" }, + { url = "https://files.pythonhosted.org/packages/34/fa/87ff7f25b3c4ce9085a62554460b7db686fef1e0207e8977795c7b7d7ba1/numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2", size = 14278147, upload-time = "2025-07-24T20:44:10.328Z" }, + { url = "https://files.pythonhosted.org/packages/1d/0f/571b2c7a3833ae419fe69ff7b479a78d313581785203cc70a8db90121b9a/numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f", size = 16635989, upload-time = "2025-07-24T20:44:34.88Z" }, + { url = "https://files.pythonhosted.org/packages/24/5a/84ae8dca9c9a4c592fe11340b36a86ffa9fd3e40513198daf8a97839345c/numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee", size = 16053052, upload-time = "2025-07-24T20:44:58.872Z" }, + { url = "https://files.pythonhosted.org/packages/57/7c/e5725d99a9133b9813fcf148d3f858df98511686e853169dbaf63aec6097/numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6", size = 18577955, upload-time = "2025-07-24T20:45:26.714Z" }, + { url = "https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b", size = 6311843, upload-time = "2025-07-24T20:49:24.444Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6f/a428fd1cb7ed39b4280d057720fed5121b0d7754fd2a9768640160f5517b/numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56", size = 12782876, upload-time = "2025-07-24T20:49:43.227Z" }, + { url = "https://files.pythonhosted.org/packages/65/85/4ea455c9040a12595fb6c43f2c217257c7b52dd0ba332c6a6c1d28b289fe/numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2", size = 10192786, upload-time = "2025-07-24T20:49:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/80/23/8278f40282d10c3f258ec3ff1b103d4994bcad78b0cba9208317f6bb73da/numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab", size = 21047395, upload-time = "2025-07-24T20:45:58.821Z" }, + { url = "https://files.pythonhosted.org/packages/1f/2d/624f2ce4a5df52628b4ccd16a4f9437b37c35f4f8a50d00e962aae6efd7a/numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2", size = 14300374, upload-time = "2025-07-24T20:46:20.207Z" }, + { url = "https://files.pythonhosted.org/packages/f6/62/ff1e512cdbb829b80a6bd08318a58698867bca0ca2499d101b4af063ee97/numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a", size = 5228864, upload-time = "2025-07-24T20:46:30.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8e/74bc18078fff03192d4032cfa99d5a5ca937807136d6f5790ce07ca53515/numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286", size = 6737533, upload-time = "2025-07-24T20:46:46.111Z" }, + { url = "https://files.pythonhosted.org/packages/19/ea/0731efe2c9073ccca5698ef6a8c3667c4cf4eea53fcdcd0b50140aba03bc/numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8", size = 14352007, upload-time = "2025-07-24T20:47:07.1Z" }, + { url = "https://files.pythonhosted.org/packages/cf/90/36be0865f16dfed20f4bc7f75235b963d5939707d4b591f086777412ff7b/numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a", size = 16701914, upload-time = "2025-07-24T20:47:32.459Z" }, + { url = "https://files.pythonhosted.org/packages/94/30/06cd055e24cb6c38e5989a9e747042b4e723535758e6153f11afea88c01b/numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91", size = 16132708, upload-time = "2025-07-24T20:47:58.129Z" }, + { url = "https://files.pythonhosted.org/packages/9a/14/ecede608ea73e58267fd7cb78f42341b3b37ba576e778a1a06baffbe585c/numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5", size = 18651678, upload-time = "2025-07-24T20:48:25.402Z" }, + { url = "https://files.pythonhosted.org/packages/40/f3/2fe6066b8d07c3685509bc24d56386534c008b462a488b7f503ba82b8923/numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5", size = 6441832, upload-time = "2025-07-24T20:48:37.181Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ba/0937d66d05204d8f28630c9c60bc3eda68824abde4cf756c4d6aad03b0c6/numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450", size = 12927049, upload-time = "2025-07-24T20:48:56.24Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ed/13542dd59c104d5e654dfa2ac282c199ba64846a74c2c4bcdbc3a0f75df1/numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a", size = 10262935, upload-time = "2025-07-24T20:49:13.136Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7c/7659048aaf498f7611b783e000c7268fcc4dcf0ce21cd10aad7b2e8f9591/numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a", size = 20950906, upload-time = "2025-07-24T20:50:30.346Z" }, + { url = "https://files.pythonhosted.org/packages/80/db/984bea9d4ddf7112a04cfdfb22b1050af5757864cfffe8e09e44b7f11a10/numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b", size = 14185607, upload-time = "2025-07-24T20:50:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/e4/76/b3d6f414f4eca568f469ac112a3b510938d892bc5a6c190cb883af080b77/numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125", size = 5114110, upload-time = "2025-07-24T20:51:01.041Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d2/6f5e6826abd6bca52392ed88fe44a4b52aacb60567ac3bc86c67834c3a56/numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19", size = 6642050, upload-time = "2025-07-24T20:51:11.64Z" }, + { url = "https://files.pythonhosted.org/packages/c4/43/f12b2ade99199e39c73ad182f103f9d9791f48d885c600c8e05927865baf/numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f", size = 14296292, upload-time = "2025-07-24T20:51:33.488Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f9/77c07d94bf110a916b17210fac38680ed8734c236bfed9982fd8524a7b47/numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5", size = 16638913, upload-time = "2025-07-24T20:51:58.517Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d1/9d9f2c8ea399cc05cfff8a7437453bd4e7d894373a93cdc46361bbb49a7d/numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58", size = 16071180, upload-time = "2025-07-24T20:52:22.827Z" }, + { url = "https://files.pythonhosted.org/packages/4c/41/82e2c68aff2a0c9bf315e47d61951099fed65d8cb2c8d9dc388cb87e947e/numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0", size = 18576809, upload-time = "2025-07-24T20:52:51.015Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/4b4fd3efb0837ed252d0f583c5c35a75121038a8c4e065f2c259be06d2d8/numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2", size = 6366410, upload-time = "2025-07-24T20:56:44.949Z" }, + { url = "https://files.pythonhosted.org/packages/11/9e/b4c24a6b8467b61aced5c8dc7dcfce23621baa2e17f661edb2444a418040/numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b", size = 12918821, upload-time = "2025-07-24T20:57:06.479Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0f/0dc44007c70b1007c1cef86b06986a3812dd7106d8f946c09cfa75782556/numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910", size = 10477303, upload-time = "2025-07-24T20:57:22.879Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3e/075752b79140b78ddfc9c0a1634d234cfdbc6f9bbbfa6b7504e445ad7d19/numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e", size = 21047524, upload-time = "2025-07-24T20:53:22.086Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6d/60e8247564a72426570d0e0ea1151b95ce5bd2f1597bb878a18d32aec855/numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45", size = 14300519, upload-time = "2025-07-24T20:53:44.053Z" }, + { url = "https://files.pythonhosted.org/packages/4d/73/d8326c442cd428d47a067070c3ac6cc3b651a6e53613a1668342a12d4479/numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b", size = 5228972, upload-time = "2025-07-24T20:53:53.81Z" }, + { url = "https://files.pythonhosted.org/packages/34/2e/e71b2d6dad075271e7079db776196829019b90ce3ece5c69639e4f6fdc44/numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2", size = 6737439, upload-time = "2025-07-24T20:54:04.742Z" }, + { url = "https://files.pythonhosted.org/packages/15/b0/d004bcd56c2c5e0500ffc65385eb6d569ffd3363cb5e593ae742749b2daa/numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0", size = 14352479, upload-time = "2025-07-24T20:54:25.819Z" }, + { url = "https://files.pythonhosted.org/packages/11/e3/285142fcff8721e0c99b51686426165059874c150ea9ab898e12a492e291/numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0", size = 16702805, upload-time = "2025-07-24T20:54:50.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/c3/33b56b0e47e604af2c7cd065edca892d180f5899599b76830652875249a3/numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2", size = 16133830, upload-time = "2025-07-24T20:55:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ae/7b1476a1f4d6a48bc669b8deb09939c56dd2a439db1ab03017844374fb67/numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf", size = 18652665, upload-time = "2025-07-24T20:55:46.665Z" }, + { url = "https://files.pythonhosted.org/packages/14/ba/5b5c9978c4bb161034148ade2de9db44ec316fab89ce8c400db0e0c81f86/numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1", size = 6514777, upload-time = "2025-07-24T20:55:57.66Z" }, + { url = "https://files.pythonhosted.org/packages/eb/46/3dbaf0ae7c17cdc46b9f662c56da2054887b8d9e737c1476f335c83d33db/numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b", size = 13111856, upload-time = "2025-07-24T20:56:17.318Z" }, + { url = "https://files.pythonhosted.org/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226, upload-time = "2025-07-24T20:56:34.509Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "protobuf" +version = "6.31.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/f3/b9655a711b32c19720253f6f06326faf90580834e2e83f840472d752bc8b/protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a", size = 441797, upload-time = "2025-05-28T19:25:54.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/6f/6ab8e4bf962fd5570d3deaa2d5c38f0a363f57b4501047b5ebeb83ab1125/protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9", size = 423603, upload-time = "2025-05-28T19:25:41.198Z" }, + { url = "https://files.pythonhosted.org/packages/44/3a/b15c4347dd4bf3a1b0ee882f384623e2063bb5cf9fa9d57990a4f7df2fb6/protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447", size = 435283, upload-time = "2025-05-28T19:25:44.275Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c9/b9689a2a250264a84e66c46d8862ba788ee7a641cdca39bccf64f59284b7/protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402", size = 425604, upload-time = "2025-05-28T19:25:45.702Z" }, + { url = "https://files.pythonhosted.org/packages/76/a1/7a5a94032c83375e4fe7e7f56e3976ea6ac90c5e85fac8576409e25c39c3/protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39", size = 322115, upload-time = "2025-05-28T19:25:47.128Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/b59d405d64d31999244643d88c45c8241c58f17cc887e73bcb90602327f8/protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6", size = 321070, upload-time = "2025-05-28T19:25:50.036Z" }, + { url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724, upload-time = "2025-05-28T19:25:53.926Z" }, +] + +[[package]] +name = "pydata-sphinx-theme" +version = "0.15.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accessible-pygments" }, + { name = "babel" }, + { name = "beautifulsoup4" }, + { name = "docutils" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "sphinx" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/ea/3ab478cccacc2e8ef69892c42c44ae547bae089f356c4b47caf61730958d/pydata_sphinx_theme-0.15.4.tar.gz", hash = "sha256:7762ec0ac59df3acecf49fd2f889e1b4565dbce8b88b2e29ee06fdd90645a06d", size = 2400673, upload-time = "2024-06-25T19:28:45.041Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/d3/c622950d87a2ffd1654208733b5bd1c5645930014abed8f4c0d74863988b/pydata_sphinx_theme-0.15.4-py3-none-any.whl", hash = "sha256:2136ad0e9500d0949f96167e63f3e298620040aea8f9c74621959eda5d4cf8e6", size = 4640157, upload-time = "2024-06-25T19:28:42.383Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "rcrscore" +version = "0.1.0" +source = { git = "https://github.com/adf-python/rcrs-core-python?tag=v0.2.0#d8227cfcfb479c020fd0354a923aefcb6f2573df" } +dependencies = [ + { name = "protobuf" }, + { name = "rtree" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[[package]] +name = "roman-numerals-py" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, +] + +[[package]] +name = "rtree" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/b8/0091f020acafcb034daa5b062f0626f6a73c7e0d64826af23861390a9585/rtree-1.4.0.tar.gz", hash = "sha256:9d97c7c5dcf25f6c0599c76d9933368c6a8d7238f2c1d00e76f1a69369ca82a0", size = 50789, upload-time = "2025-03-05T23:31:45.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/4c/8d54d6dc5ff8ba8ced1fad9378f89f9dd60addcc4cf0e525ee0e67b1769f/rtree-1.4.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:4d1bebc418101480aabf41767e772dd2155d3b27b1376cccbd93e4509485e091", size = 482755, upload-time = "2025-03-05T23:31:29.884Z" }, + { url = "https://files.pythonhosted.org/packages/20/29/045e700d2135e9a67896086c831fde80fd4105971b443d5727a4093fcbf1/rtree-1.4.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:997f8c38d5dffa3949ea8adb4c8b291ea5cd4ef5ee69455d642dd171baf9991d", size = 439796, upload-time = "2025-03-05T23:31:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fc/c3bd8cd67b10a12a6b9e2d06796779128c3e6968922dbf29fcd53af68d81/rtree-1.4.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0133d9c54ab3ffe874ba6d411dbe0254765c5e68d92da5b91362c370f16fd997", size = 497549, upload-time = "2025-03-05T23:31:33.722Z" }, + { url = "https://files.pythonhosted.org/packages/a0/dd/49dc9ab037d0cb288ed40f8b7f498f69d44243e4745e241c05d5e457ea8b/rtree-1.4.0-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d3b7bf1fe6463139377995ebe22a01a7005d134707f43672a3c09305e12f5f43", size = 568787, upload-time = "2025-03-05T23:31:35.478Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e7/57737dff73ce789bdadd916d48ac12e977d8578176e1e890b1b8d89b9dbf/rtree-1.4.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:27e4a6d617d63dcb82fcd4c2856134b8a3741bd1af3b1a0d98e886054f394da5", size = 541090, upload-time = "2025-03-05T23:31:37.712Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8f/1f3f716c4e8388670cfd5d0a3578e2354a1e6a3403648e234e1540e3e3bd/rtree-1.4.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5258e826064eab82439760201e9421ce6d4340789d6d080c1b49367ddd03f61f", size = 1454194, upload-time = "2025-03-05T23:31:39.851Z" }, + { url = "https://files.pythonhosted.org/packages/22/ec/b42052b10e63a1c5d5d61ce234332f689736053644ba1756f7a632ea7659/rtree-1.4.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:20d5b3f9cf8bbbcc9fec42ab837c603c5dd86103ef29134300c8da2495c1248b", size = 1692814, upload-time = "2025-03-05T23:31:41.617Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5b/a9920e9a2dc43b066ff13b7fde2e7bffcca315cfa43ae6f4cc15970e39eb/rtree-1.4.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a67bee1233370a4c72c0969a96d2a1df1ba404ddd9f146849c53ab420eab361b", size = 1554860, upload-time = "2025-03-05T23:31:43.091Z" }, + { url = "https://files.pythonhosted.org/packages/ce/c2/362f2cc36a7a57b47380061c23fc109c7222c1a544ffd24cda289ba19673/rtree-1.4.0-py3-none-win_amd64.whl", hash = "sha256:ba83efc7b7563905b1bfdfc14490c4bfb59e92e5e6156bdeb6ec5df5117252f4", size = 385221, upload-time = "2025-03-05T23:31:44.537Z" }, +] + +[[package]] +name = "ruff" +version = "0.12.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/81/0bd3594fa0f690466e41bd033bdcdf86cba8288345ac77ad4afbe5ec743a/ruff-0.12.7.tar.gz", hash = "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", size = 5197814, upload-time = "2025-07-29T22:32:35.877Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/d2/6cb35e9c85e7a91e8d22ab32ae07ac39cc34a71f1009a6f9e4a2a019e602/ruff-0.12.7-py3-none-linux_armv6l.whl", hash = "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", size = 11852189, upload-time = "2025-07-29T22:31:41.281Z" }, + { url = "https://files.pythonhosted.org/packages/63/5b/a4136b9921aa84638f1a6be7fb086f8cad0fde538ba76bda3682f2599a2f/ruff-0.12.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", size = 12519389, upload-time = "2025-07-29T22:31:54.265Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c9/3e24a8472484269b6b1821794141f879c54645a111ded4b6f58f9ab0705f/ruff-0.12.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", size = 11743384, upload-time = "2025-07-29T22:31:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/26/7c/458dd25deeb3452c43eaee853c0b17a1e84169f8021a26d500ead77964fd/ruff-0.12.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", size = 11943759, upload-time = "2025-07-29T22:32:01.95Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8b/658798472ef260ca050e400ab96ef7e85c366c39cf3dfbef4d0a46a528b6/ruff-0.12.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", size = 11654028, upload-time = "2025-07-29T22:32:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/a8/86/9c2336f13b2a3326d06d39178fd3448dcc7025f82514d1b15816fe42bfe8/ruff-0.12.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", size = 13225209, upload-time = "2025-07-29T22:32:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/76/69/df73f65f53d6c463b19b6b312fd2391dc36425d926ec237a7ed028a90fc1/ruff-0.12.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", size = 14182353, upload-time = "2025-07-29T22:32:10.053Z" }, + { url = "https://files.pythonhosted.org/packages/58/1e/de6cda406d99fea84b66811c189b5ea139814b98125b052424b55d28a41c/ruff-0.12.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", size = 13631555, upload-time = "2025-07-29T22:32:12.644Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ae/625d46d5164a6cc9261945a5e89df24457dc8262539ace3ac36c40f0b51e/ruff-0.12.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", size = 12667556, upload-time = "2025-07-29T22:32:15.312Z" }, + { url = "https://files.pythonhosted.org/packages/55/bf/9cb1ea5e3066779e42ade8d0cd3d3b0582a5720a814ae1586f85014656b6/ruff-0.12.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", size = 12939784, upload-time = "2025-07-29T22:32:17.69Z" }, + { url = "https://files.pythonhosted.org/packages/55/7f/7ead2663be5627c04be83754c4f3096603bf5e99ed856c7cd29618c691bd/ruff-0.12.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8", size = 11771356, upload-time = "2025-07-29T22:32:20.134Z" }, + { url = "https://files.pythonhosted.org/packages/17/40/a95352ea16edf78cd3a938085dccc55df692a4d8ba1b3af7accbe2c806b0/ruff-0.12.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", size = 11612124, upload-time = "2025-07-29T22:32:22.645Z" }, + { url = "https://files.pythonhosted.org/packages/4d/74/633b04871c669e23b8917877e812376827c06df866e1677f15abfadc95cb/ruff-0.12.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", size = 12479945, upload-time = "2025-07-29T22:32:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/be/34/c3ef2d7799c9778b835a76189c6f53c179d3bdebc8c65288c29032e03613/ruff-0.12.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", size = 12998677, upload-time = "2025-07-29T22:32:27.022Z" }, + { url = "https://files.pythonhosted.org/packages/77/ab/aca2e756ad7b09b3d662a41773f3edcbd262872a4fc81f920dc1ffa44541/ruff-0.12.7-py3-none-win32.whl", hash = "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", size = 11756687, upload-time = "2025-07-29T22:32:29.381Z" }, + { url = "https://files.pythonhosted.org/packages/b4/71/26d45a5042bc71db22ddd8252ca9d01e9ca454f230e2996bb04f16d72799/ruff-0.12.7-py3-none-win_amd64.whl", hash = "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", size = 12912365, upload-time = "2025-07-29T22:32:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9b/0b8aa09817b63e78d94b4977f18b1fcaead3165a5ee49251c5d5c245bb2d/ruff-0.12.7-py3-none-win_arm64.whl", hash = "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", size = 11982083, upload-time = "2025-07-29T22:32:33.881Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/84/5f4af978fff619706b8961accac84780a6d298d82a8873446f72edb4ead0/scikit_learn-1.7.1.tar.gz", hash = "sha256:24b3f1e976a4665aa74ee0fcaac2b8fccc6ae77c8e07ab25da3ba6d3292b9802", size = 7190445, upload-time = "2025-07-18T08:01:54.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/f8/e0533303f318a0f37b88300d21f79b6ac067188d4824f1047a37214ab718/scikit_learn-1.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b7839687fa46d02e01035ad775982f2470be2668e13ddd151f0f55a5bf123bae", size = 9213143, upload-time = "2025-07-18T08:01:32.942Z" }, + { url = "https://files.pythonhosted.org/packages/71/f3/f1df377d1bdfc3e3e2adc9c119c238b182293e6740df4cbeac6de2cc3e23/scikit_learn-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a10f276639195a96c86aa572ee0698ad64ee939a7b042060b98bd1930c261d10", size = 8591977, upload-time = "2025-07-18T08:01:34.967Z" }, + { url = "https://files.pythonhosted.org/packages/99/72/c86a4cd867816350fe8dee13f30222340b9cd6b96173955819a5561810c5/scikit_learn-1.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13679981fdaebc10cc4c13c43344416a86fcbc61449cb3e6517e1df9d12c8309", size = 9436142, upload-time = "2025-07-18T08:01:37.397Z" }, + { url = "https://files.pythonhosted.org/packages/e8/66/277967b29bd297538dc7a6ecfb1a7dce751beabd0d7f7a2233be7a4f7832/scikit_learn-1.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f1262883c6a63f067a980a8cdd2d2e7f2513dddcef6a9eaada6416a7a7cbe43", size = 9282996, upload-time = "2025-07-18T08:01:39.721Z" }, + { url = "https://files.pythonhosted.org/packages/e2/47/9291cfa1db1dae9880420d1e07dbc7e8dd4a7cdbc42eaba22512e6bde958/scikit_learn-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca6d31fb10e04d50bfd2b50d66744729dbb512d4efd0223b864e2fdbfc4cee11", size = 8707418, upload-time = "2025-07-18T08:01:42.124Z" }, + { url = "https://files.pythonhosted.org/packages/61/95/45726819beccdaa34d3362ea9b2ff9f2b5d3b8bf721bd632675870308ceb/scikit_learn-1.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:781674d096303cfe3d351ae6963ff7c958db61cde3421cd490e3a5a58f2a94ae", size = 9561466, upload-time = "2025-07-18T08:01:44.195Z" }, + { url = "https://files.pythonhosted.org/packages/ee/1c/6f4b3344805de783d20a51eb24d4c9ad4b11a7f75c1801e6ec6d777361fd/scikit_learn-1.7.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:10679f7f125fe7ecd5fad37dd1aa2daae7e3ad8df7f3eefa08901b8254b3e12c", size = 9040467, upload-time = "2025-07-18T08:01:46.671Z" }, + { url = "https://files.pythonhosted.org/packages/6f/80/abe18fe471af9f1d181904203d62697998b27d9b62124cd281d740ded2f9/scikit_learn-1.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f812729e38c8cb37f760dce71a9b83ccfb04f59b3dca7c6079dcdc60544fa9e", size = 9532052, upload-time = "2025-07-18T08:01:48.676Z" }, + { url = "https://files.pythonhosted.org/packages/14/82/b21aa1e0c4cee7e74864d3a5a721ab8fcae5ca55033cb6263dca297ed35b/scikit_learn-1.7.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88e1a20131cf741b84b89567e1717f27a2ced228e0f29103426102bc2e3b8ef7", size = 9361575, upload-time = "2025-07-18T08:01:50.639Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/f4777fcd5627dc6695fa6b92179d0edb7a3ac1b91bcd9a1c7f64fa7ade23/scikit_learn-1.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b1bd1d919210b6a10b7554b717c9000b5485aa95a1d0f177ae0d7ee8ec750da5", size = 9277310, upload-time = "2025-07-18T08:01:52.547Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/4a/b927028464795439faec8eaf0b03b011005c487bb2d07409f28bf30879c4/scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3", size = 30580861, upload-time = "2025-07-27T16:33:30.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/0b/b5c99382b839854a71ca9482c684e3472badc62620287cbbdab499b75ce6/scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f", size = 36533717, upload-time = "2025-07-27T16:28:51.706Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e5/69ab2771062c91e23e07c12e7d5033a6b9b80b0903ee709c3c36b3eb520c/scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb", size = 28570009, upload-time = "2025-07-27T16:28:57.017Z" }, + { url = "https://files.pythonhosted.org/packages/f4/69/bd75dbfdd3cf524f4d753484d723594aed62cfaac510123e91a6686d520b/scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c", size = 20841942, upload-time = "2025-07-27T16:29:01.152Z" }, + { url = "https://files.pythonhosted.org/packages/ea/74/add181c87663f178ba7d6144b370243a87af8476664d5435e57d599e6874/scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608", size = 23498507, upload-time = "2025-07-27T16:29:05.202Z" }, + { url = "https://files.pythonhosted.org/packages/1d/74/ece2e582a0d9550cee33e2e416cc96737dce423a994d12bbe59716f47ff1/scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f", size = 33286040, upload-time = "2025-07-27T16:29:10.201Z" }, + { url = "https://files.pythonhosted.org/packages/e4/82/08e4076df538fb56caa1d489588d880ec7c52d8273a606bb54d660528f7c/scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b", size = 35176096, upload-time = "2025-07-27T16:29:17.091Z" }, + { url = "https://files.pythonhosted.org/packages/fa/79/cd710aab8c921375711a8321c6be696e705a120e3011a643efbbcdeeabcc/scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45", size = 35490328, upload-time = "2025-07-27T16:29:22.928Z" }, + { url = "https://files.pythonhosted.org/packages/71/73/e9cc3d35ee4526d784520d4494a3e1ca969b071fb5ae5910c036a375ceec/scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65", size = 37939921, upload-time = "2025-07-27T16:29:29.108Z" }, + { url = "https://files.pythonhosted.org/packages/21/12/c0efd2941f01940119b5305c375ae5c0fcb7ec193f806bd8f158b73a1782/scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab", size = 38479462, upload-time = "2025-07-27T16:30:24.078Z" }, + { url = "https://files.pythonhosted.org/packages/7a/19/c3d08b675260046a991040e1ea5d65f91f40c7df1045fffff412dcfc6765/scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6", size = 36938832, upload-time = "2025-07-27T16:29:35.057Z" }, + { url = "https://files.pythonhosted.org/packages/81/f2/ce53db652c033a414a5b34598dba6b95f3d38153a2417c5a3883da429029/scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27", size = 29093084, upload-time = "2025-07-27T16:29:40.201Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ae/7a10ff04a7dc15f9057d05b33737ade244e4bd195caa3f7cc04d77b9e214/scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7", size = 21365098, upload-time = "2025-07-27T16:29:44.295Z" }, + { url = "https://files.pythonhosted.org/packages/36/ac/029ff710959932ad3c2a98721b20b405f05f752f07344622fd61a47c5197/scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6", size = 23896858, upload-time = "2025-07-27T16:29:48.784Z" }, + { url = "https://files.pythonhosted.org/packages/71/13/d1ef77b6bd7898720e1f0b6b3743cb945f6c3cafa7718eaac8841035ab60/scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4", size = 33438311, upload-time = "2025-07-27T16:29:54.164Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e0/e64a6821ffbb00b4c5b05169f1c1fddb4800e9307efe3db3788995a82a2c/scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3", size = 35279542, upload-time = "2025-07-27T16:30:00.249Z" }, + { url = "https://files.pythonhosted.org/packages/57/59/0dc3c8b43e118f1e4ee2b798dcc96ac21bb20014e5f1f7a8e85cc0653bdb/scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7", size = 35667665, upload-time = "2025-07-27T16:30:05.916Z" }, + { url = "https://files.pythonhosted.org/packages/45/5f/844ee26e34e2f3f9f8febb9343748e72daeaec64fe0c70e9bf1ff84ec955/scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc", size = 38045210, upload-time = "2025-07-27T16:30:11.655Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d7/210f2b45290f444f1de64bc7353aa598ece9f0e90c384b4a156f9b1a5063/scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39", size = 38593661, upload-time = "2025-07-27T16:30:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/81/ea/84d481a5237ed223bd3d32d6e82d7a6a96e34756492666c260cef16011d1/scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318", size = 36525921, upload-time = "2025-07-27T16:30:30.081Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9f/d9edbdeff9f3a664807ae3aea383e10afaa247e8e6255e6d2aa4515e8863/scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc", size = 28564152, upload-time = "2025-07-27T16:30:35.336Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/8125bcb1fe04bc267d103e76516243e8d5e11229e6b306bda1024a5423d1/scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8", size = 20836028, upload-time = "2025-07-27T16:30:39.421Z" }, + { url = "https://files.pythonhosted.org/packages/77/9c/bf92e215701fc70bbcd3d14d86337cf56a9b912a804b9c776a269524a9e9/scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e", size = 23489666, upload-time = "2025-07-27T16:30:43.663Z" }, + { url = "https://files.pythonhosted.org/packages/5e/00/5e941d397d9adac41b02839011594620d54d99488d1be5be755c00cde9ee/scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0", size = 33358318, upload-time = "2025-07-27T16:30:48.982Z" }, + { url = "https://files.pythonhosted.org/packages/0e/87/8db3aa10dde6e3e8e7eb0133f24baa011377d543f5b19c71469cf2648026/scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b", size = 35185724, upload-time = "2025-07-27T16:30:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/89/b4/6ab9ae443216807622bcff02690262d8184078ea467efee2f8c93288a3b1/scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731", size = 35554335, upload-time = "2025-07-27T16:30:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/9c/9a/d0e9dc03c5269a1afb60661118296a32ed5d2c24298af61b676c11e05e56/scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3", size = 37960310, upload-time = "2025-07-27T16:31:06.151Z" }, + { url = "https://files.pythonhosted.org/packages/5e/00/c8f3130a50521a7977874817ca89e0599b1b4ee8e938bad8ae798a0e1f0d/scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19", size = 39319239, upload-time = "2025-07-27T16:31:59.942Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f2/1ca3eda54c3a7e4c92f6acef7db7b3a057deb135540d23aa6343ef8ad333/scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65", size = 36939460, upload-time = "2025-07-27T16:31:11.865Z" }, + { url = "https://files.pythonhosted.org/packages/80/30/98c2840b293a132400c0940bb9e140171dcb8189588619048f42b2ce7b4f/scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2", size = 29093322, upload-time = "2025-07-27T16:31:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e6/1e6e006e850622cf2a039b62d1a6ddc4497d4851e58b68008526f04a9a00/scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d", size = 21365329, upload-time = "2025-07-27T16:31:21.188Z" }, + { url = "https://files.pythonhosted.org/packages/8e/02/72a5aa5b820589dda9a25e329ca752842bfbbaf635e36bc7065a9b42216e/scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695", size = 23897544, upload-time = "2025-07-27T16:31:25.408Z" }, + { url = "https://files.pythonhosted.org/packages/2b/dc/7122d806a6f9eb8a33532982234bed91f90272e990f414f2830cfe656e0b/scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86", size = 33442112, upload-time = "2025-07-27T16:31:30.62Z" }, + { url = "https://files.pythonhosted.org/packages/24/39/e383af23564daa1021a5b3afbe0d8d6a68ec639b943661841f44ac92de85/scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff", size = 35286594, upload-time = "2025-07-27T16:31:36.112Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/1a0b0aff40c3056d955f38b0df5d178350c3d74734ec54f9c68d23910be5/scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4", size = 35665080, upload-time = "2025-07-27T16:31:42.025Z" }, + { url = "https://files.pythonhosted.org/packages/64/df/ce88803e9ed6e27fe9b9abefa157cf2c80e4fa527cf17ee14be41f790ad4/scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3", size = 38050306, upload-time = "2025-07-27T16:31:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6c/a76329897a7cae4937d403e623aa6aaea616a0bb5b36588f0b9d1c9a3739/scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998", size = 39427705, upload-time = "2025-07-27T16:31:53.96Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "shapely" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/3c/2da625233f4e605155926566c0e7ea8dda361877f48e8b1655e53456f252/shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772", size = 315422, upload-time = "2025-05-19T11:04:41.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/8e/2bc836437f4b84d62efc1faddce0d4e023a5d990bbddd3c78b2004ebc246/shapely-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3004a644d9e89e26c20286d5fdc10f41b1744c48ce910bd1867fdff963fe6c48", size = 1832107, upload-time = "2025-05-19T11:04:19.736Z" }, + { url = "https://files.pythonhosted.org/packages/12/a2/12c7cae5b62d5d851c2db836eadd0986f63918a91976495861f7c492f4a9/shapely-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1415146fa12d80a47d13cfad5310b3c8b9c2aa8c14a0c845c9d3d75e77cb54f6", size = 1642355, upload-time = "2025-05-19T11:04:21.035Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/6d28b43d53fea56de69c744e34c2b999ed4042f7a811dc1bceb876071c95/shapely-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21fcab88b7520820ec16d09d6bea68652ca13993c84dffc6129dc3607c95594c", size = 2968871, upload-time = "2025-05-19T11:04:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/dd/87/1017c31e52370b2b79e4d29e07cbb590ab9e5e58cf7e2bdfe363765d6251/shapely-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ce6a5cc52c974b291237a96c08c5592e50f066871704fb5b12be2639d9026a", size = 3080830, upload-time = "2025-05-19T11:04:23.997Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fe/f4a03d81abd96a6ce31c49cd8aaba970eaaa98e191bd1e4d43041e57ae5a/shapely-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:04e4c12a45a1d70aeb266618d8cf81a2de9c4df511b63e105b90bfdfb52146de", size = 3908961, upload-time = "2025-05-19T11:04:25.702Z" }, + { url = "https://files.pythonhosted.org/packages/ef/59/7605289a95a6844056a2017ab36d9b0cb9d6a3c3b5317c1f968c193031c9/shapely-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6ca74d851ca5264aae16c2b47e96735579686cb69fa93c4078070a0ec845b8d8", size = 4079623, upload-time = "2025-05-19T11:04:27.171Z" }, + { url = "https://files.pythonhosted.org/packages/bc/4d/9fea036eff2ef4059d30247128b2d67aaa5f0b25e9fc27e1d15cc1b84704/shapely-2.1.1-cp313-cp313-win32.whl", hash = "sha256:fd9130501bf42ffb7e0695b9ea17a27ae8ce68d50b56b6941c7f9b3d3453bc52", size = 1521916, upload-time = "2025-05-19T11:04:28.405Z" }, + { url = "https://files.pythonhosted.org/packages/12/d9/6d13b8957a17c95794f0c4dfb65ecd0957e6c7131a56ce18d135c1107a52/shapely-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:ab8d878687b438a2f4c138ed1a80941c6ab0029e0f4c785ecfe114413b498a97", size = 1702746, upload-time = "2025-05-19T11:04:29.643Z" }, + { url = "https://files.pythonhosted.org/packages/60/36/b1452e3e7f35f5f6454d96f3be6e2bb87082720ff6c9437ecc215fa79be0/shapely-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c062384316a47f776305ed2fa22182717508ffdeb4a56d0ff4087a77b2a0f6d", size = 1833482, upload-time = "2025-05-19T11:04:30.852Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ca/8e6f59be0718893eb3e478141285796a923636dc8f086f83e5b0ec0036d0/shapely-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4ecf6c196b896e8f1360cc219ed4eee1c1e5f5883e505d449f263bd053fb8c05", size = 1642256, upload-time = "2025-05-19T11:04:32.068Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/0053aea449bb1d4503999525fec6232f049abcdc8df60d290416110de943/shapely-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb00070b4c4860f6743c600285109c273cca5241e970ad56bb87bef0be1ea3a0", size = 3016614, upload-time = "2025-05-19T11:04:33.7Z" }, + { url = "https://files.pythonhosted.org/packages/ee/53/36f1b1de1dfafd1b457dcbafa785b298ce1b8a3e7026b79619e708a245d5/shapely-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14a9afa5fa980fbe7bf63706fdfb8ff588f638f145a1d9dbc18374b5b7de913", size = 3093542, upload-time = "2025-05-19T11:04:34.952Z" }, + { url = "https://files.pythonhosted.org/packages/b9/bf/0619f37ceec6b924d84427c88835b61f27f43560239936ff88915c37da19/shapely-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b640e390dabde790e3fb947198b466e63223e0a9ccd787da5f07bcb14756c28d", size = 3945961, upload-time = "2025-05-19T11:04:36.32Z" }, + { url = "https://files.pythonhosted.org/packages/93/c9/20ca4afeb572763b07a7997f00854cb9499df6af85929e93012b189d8917/shapely-2.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:69e08bf9697c1b73ec6aa70437db922bafcea7baca131c90c26d59491a9760f9", size = 4089514, upload-time = "2025-05-19T11:04:37.683Z" }, + { url = "https://files.pythonhosted.org/packages/33/6a/27036a5a560b80012a544366bceafd491e8abb94a8db14047b5346b5a749/shapely-2.1.1-cp313-cp313t-win32.whl", hash = "sha256:ef2d09d5a964cc90c2c18b03566cf918a61c248596998a0301d5b632beadb9db", size = 1540607, upload-time = "2025-05-19T11:04:38.925Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f1/5e9b3ba5c7aa7ebfaf269657e728067d16a7c99401c7973ddf5f0cf121bd/shapely-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7", size = 1723061, upload-time = "2025-05-19T11:04:40.082Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" }, +] + +[[package]] +name = "sphinx" +version = "8.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals-py" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, +] + +[[package]] +name = "sphinx-book-theme" +version = "1.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydata-sphinx-theme" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/19/d002ed96bdc7738c15847c730e1e88282d738263deac705d5713b4d8fa94/sphinx_book_theme-1.1.4.tar.gz", hash = "sha256:73efe28af871d0a89bd05856d300e61edce0d5b2fbb7984e84454be0fedfe9ed", size = 439188, upload-time = "2025-02-20T16:32:32.581Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/9e/c41d68be04eef5b6202b468e0f90faf0c469f3a03353f2a218fd78279710/sphinx_book_theme-1.1.4-py3-none-any.whl", hash = "sha256:843b3f5c8684640f4a2d01abd298beb66452d1b2394cd9ef5be5ebd5640ea0e1", size = 433952, upload-time = "2025-02-20T16:32:31.009Z" }, +] + +[[package]] +name = "sphinx-copybutton" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039, upload-time = "2023-04-14T08:10:22.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, +] + +[[package]] +name = "sphinx-intl" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "click" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/21/eb12016ecb0b52861762b0d227dff75622988f238776a5ee4c75bade507e/sphinx_intl-2.3.2.tar.gz", hash = "sha256:04b0d8ea04d111a7ba278b17b7b3fe9625c58b6f8ffb78bb8a1dd1288d88c1c7", size = 27921, upload-time = "2025-08-02T04:53:01.891Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/3b/156032fa29a87e9eba9182b8e893a7e88c1d98907a078a371d69be432e52/sphinx_intl-2.3.2-py3-none-any.whl", hash = "sha256:f0082f9383066bab8406129a2ed531d21c38706d08467bf5ca3714e8914bb2be", size = 12899, upload-time = "2025-08-02T04:53:00.353Z" }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "sphinx" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/44/c97faec644d29a5ceddd3020ae2edffa69e7d00054a8c7a6021e82f20335/sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85", size = 7620463, upload-time = "2024-11-13T11:06:04.545Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/77/46e3bac77b82b4df5bb5b61f2de98637724f246b4966cfc34bc5895d852a/sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13", size = 7655561, upload-time = "2024-11-13T11:06:02.094Z" }, +] + +[[package]] +name = "sphinx-togglebutton" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "setuptools" }, + { name = "sphinx" }, + { name = "wheel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/df/d151dfbbe588116e450ca7e898750cb218dca6b2e557ced8de6f9bd7242b/sphinx-togglebutton-0.3.2.tar.gz", hash = "sha256:ab0c8b366427b01e4c89802d5d078472c427fa6e9d12d521c34fa0442559dc7a", size = 8324, upload-time = "2022-07-15T12:08:50.286Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/18/267ce39f29d26cdc7177231428ba823fe5ca94db8c56d1bed69033b364c8/sphinx_togglebutton-0.3.2-py3-none-any.whl", hash = "sha256:9647ba7874b7d1e2d43413d8497153a85edc6ac95a3fea9a75ef9c1e08aaae2b", size = 8249, upload-time = "2022-07-15T12:08:48.8Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-mermaid" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/69/bf039237ad260073e8c02f820b3e00dc34f3a2de20aff7861e6b19d2f8c5/sphinxcontrib_mermaid-1.0.0.tar.gz", hash = "sha256:2e8ab67d3e1e2816663f9347d026a8dee4a858acdd4ad32dd1c808893db88146", size = 15153, upload-time = "2024-10-12T16:33:03.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/c8/784b9ac6ea08aa594c1a4becbd0dbe77186785362e31fd633b8c6ae0197a/sphinxcontrib_mermaid-1.0.0-py3-none-any.whl", hash = "sha256:60b72710ea02087f212028feb09711225fbc2e343a10d34822fe787510e1caa3", size = 9597, upload-time = "2024-10-12T16:33:02.303Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "structlog" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/b9/6e672db4fec07349e7a8a8172c1a6ae235c58679ca29c3f86a61b5e59ff3/structlog-25.4.0.tar.gz", hash = "sha256:186cd1b0a8ae762e29417095664adf1d6a31702160a46dacb7796ea82f7409e4", size = 1369138, upload-time = "2025-06-02T08:21:12.971Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/4a/97ee6973e3a73c74c8120d59829c3861ea52210667ec3e7a16045c62b64d/structlog-25.4.0-py3-none-any.whl", hash = "sha256:fe809ff5c27e557d14e613f45ca441aabda051d119ee5a0102aaba6ce40eed2c", size = 68720, upload-time = "2025-06-02T08:21:11.43Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "types-protobuf" +version = "6.30.2.20250703" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/54/d63ce1eee8e93c4d710bbe2c663ec68e3672cf4f2fca26eecd20981c0c5d/types_protobuf-6.30.2.20250703.tar.gz", hash = "sha256:609a974754bbb71fa178fc641f51050395e8e1849f49d0420a6281ed8d1ddf46", size = 62300, upload-time = "2025-07-03T03:14:05.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/2b/5d0377c3d6e0f49d4847ad2c40629593fee4a5c9ec56eba26a15c708fbc0/types_protobuf-6.30.2.20250703-py3-none-any.whl", hash = "sha256:fa5aff9036e9ef432d703abbdd801b436a249b6802e4df5ef74513e272434e57", size = 76489, upload-time = "2025-07-03T03:14:04.453Z" }, +] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20250516" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/22/59e2aeb48ceeee1f7cd4537db9568df80d62bdb44a7f9e743502ea8aab9c/types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba", size = 17378, upload-time = "2025-05-16T03:08:04.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5f/e0af6f7f6a260d9af67e1db4f54d732abad514252a7a378a6c4d17dd1036/types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530", size = 20312, upload-time = "2025-05-16T03:08:04.019Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "wheel" +version = "0.45.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545, upload-time = "2024-11-23T00:18:23.513Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494, upload-time = "2024-11-23T00:18:21.207Z" }, +] From 622574f24c46332b4850cdc064224d5f510d66df Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 15:15:15 +0900 Subject: [PATCH 237/249] chore: format --- src/adf_core_python/cli/cli.py | 72 +- src/adf_core_python/cli/template/main.py | 4 +- .../module/complex/sample_human_detector.py | 261 ++- .../module/complex/sample_road_detector.py | 264 ++- .../team_name/module/complex/sample_search.py | 174 +- .../core/agent/action/action.py | 10 +- .../agent/action/ambulance/action_load.py | 12 +- .../agent/action/ambulance/action_rescue.py | 12 +- .../agent/action/ambulance/action_unload.py | 8 +- .../core/agent/action/common/action_move.py | 58 +- .../core/agent/action/common/action_rest.py | 8 +- .../agent/action/fire/action_extinguish.py | 16 +- .../core/agent/action/fire/action_refill.py | 8 +- .../core/agent/action/fire/action_rescue.py | 12 +- .../core/agent/action/police/action_clear.py | 12 +- .../agent/action/police/action_clear_area.py | 24 +- src/adf_core_python/core/agent/agent.py | 638 ++++---- .../agent/communication/message_manager.py | 724 ++++----- .../bundle/centralized/command_ambulance.py | 236 ++- .../bundle/centralized/command_fire.py | 238 ++- .../bundle/centralized/command_police.py | 234 ++- .../bundle/centralized/command_scout.py | 224 ++- .../bundle/centralized/message_report.py | 130 +- .../information/message_ambulance_team.py | 336 ++-- .../bundle/information/message_building.py | 184 +-- .../bundle/information/message_civilian.py | 232 ++- .../information/message_fire_brigade.py | 352 ++-- .../information/message_police_force.py | 316 ++-- .../bundle/information/message_road.py | 314 ++-- .../standard/bundle/standard_message.py | 92 +- .../bundle/standard_message_priority.py | 32 +- .../standard/standard_communication_module.py | 298 ++-- .../standard/utility/apply_to_world_info.py | 542 +++---- .../utility/bitarray_with_exits_flag.py | 108 +- .../core/agent/config/module_config.py | 144 +- .../core/agent/develop/develop_data.py | 108 +- .../core/agent/info/agent_info.py | 346 ++-- .../core/agent/info/scenario_info.py | 204 +-- .../core/agent/info/world_info.py | 404 +++-- .../core/agent/module/module_manager.py | 470 +++--- .../core/agent/office/office.py | 218 +-- .../core/agent/office/office_ambulance.py | 54 +- .../core/agent/office/office_fire.py | 50 +- .../core/agent/office/office_police.py | 50 +- .../core/agent/platoon/platoon.py | 226 +-- .../core/agent/platoon/platoon_ambulance.py | 50 +- .../core/agent/platoon/platoon_fire.py | 50 +- .../core/agent/platoon/platoon_police.py | 50 +- .../core/component/abstract_loader.py | 56 +- .../core/component/action/extend_action.py | 158 +- .../component/centralized/command_executor.py | 164 +- .../component/centralized/command_picker.py | 168 +- .../communication/channel_subscriber.py | 52 +- .../communication/communication_message.py | 26 +- .../communication/communication_module.py | 16 +- .../communication/message_coordinator.py | 36 +- .../core/component/module/abstract_module.py | 186 +-- .../component/module/algorithm/clustering.py | 94 +- .../module/algorithm/path_planning.py | 94 +- .../complex/ambulance_target_allocator.py | 88 +- .../module/complex/fire_target_allocator.py | 88 +- .../module/complex/human_detector.py | 68 +- .../module/complex/police_target_allocator.py | 88 +- .../component/module/complex/road_detector.py | 68 +- .../core/component/module/complex/search.py | 68 +- .../module/complex/target_allocator.py | 86 +- .../module/complex/target_detector.py | 86 +- .../core/component/tactics/tactics_agent.py | 318 ++-- .../tactics/tactics_ambulance_center.py | 4 +- .../tactics/tactics_ambulance_team.py | 4 +- .../core/component/tactics/tactics_center.py | 230 +-- .../component/tactics/tactics_fire_brigade.py | 4 +- .../component/tactics/tactics_fire_station.py | 4 +- .../component/tactics/tactics_police_force.py | 4 +- .../tactics/tactics_police_office.py | 4 +- src/adf_core_python/core/config/config.py | 74 +- .../module/algorithm/gateway_clustering.py | 156 +- .../module/algorithm/gateway_path_planning.py | 164 +- .../gateway_ambulance_target_allocator.py | 94 +- .../complex/gateway_fire_target_allocator.py | 96 +- .../module/complex/gateway_human_detector.py | 94 +- .../gateway_police_target_allocator.py | 100 +- .../module/complex/gateway_road_detector.py | 90 +- .../module/complex/gateway_search.py | 92 +- .../complex/gateway_target_allocator.py | 102 +- .../module/complex/gateway_target_detector.py | 94 +- .../module/gateway_abstract_module.py | 90 +- .../core/gateway/gateway_agent.py | 190 ++- .../core/gateway/gateway_launcher.py | 68 +- .../core/gateway/gateway_module.py | 146 +- .../core/gateway/message/am_agent.py | 80 +- .../core/gateway/message/am_exec.py | 32 +- .../core/gateway/message/am_module.py | 36 +- .../core/gateway/message/am_update.py | 44 +- .../core/gateway/message/ma_exec_response.py | 30 +- .../gateway/message/ma_module_response.py | 28 +- .../gateway/message/moduleMessageFactory.py | 22 +- .../core/gateway/message/urn/urn.py | 40 +- .../core/gateway/module_dict.py | 52 +- .../core/launcher/agent_launcher.py | 204 ++- .../core/launcher/config_key.py | 54 +- .../launcher/connect/component_launcher.py | 174 +- .../core/launcher/connect/connection.py | 149 +- .../core/launcher/connect/connector.py | 26 +- .../connect/connector_ambulance_center.py | 132 +- .../connect/connector_ambulance_team.py | 130 +- .../connect/connector_fire_brigade.py | 128 +- .../connect/connector_fire_station.py | 128 +- .../connect/connector_police_force.py | 128 +- .../connect/connector_police_office.py | 130 +- .../launcher/connect/error/agent_error.py | 2 +- .../launcher/connect/error/server_error.py | 2 +- src/adf_core_python/core/logger/logger.py | 150 +- .../action/default_extend_action_clear.py | 1445 ++++++++--------- .../action/default_extend_action_move.py | 156 +- .../action/default_extend_action_rescue.py | 254 +-- .../action/default_extend_action_transport.py | 456 +++--- .../default_command_executor_ambulance.py | 502 +++--- .../default_command_executor_fire.py | 414 ++--- .../default_command_executor_police.py | 438 ++--- .../default_command_executor_scout.py | 257 ++- .../default_command_executor_scout_police.py | 281 ++-- .../default_command_picker_ambulance.py | 116 +- .../default_command_picker_fire.py | 116 +- .../default_command_picker_police.py | 90 +- .../implement/default_loader.py | 48 +- .../module/algorithm/a_star_path_planning.py | 146 +- .../algorithm/dijkstra_path_planning.py | 230 ++- .../module/algorithm/k_means_clustering.py | 279 ++-- .../default_channel_subscriber.py | 161 +- .../default_message_coordinator.py | 397 +++-- .../default_ambulance_target_allocator.py | 272 ++-- .../complex/default_fire_target_allocator.py | 268 +-- .../module/complex/default_human_detector.py | 261 ++- .../default_police_target_allocator.py | 346 ++-- .../module/complex/default_road_detector.py | 264 ++- .../module/complex/default_search.py | 174 +- .../default_tactics_ambulance_center.py | 152 +- .../tactics/default_tactics_ambulance_team.py | 346 ++-- .../tactics/default_tactics_fire_brigade.py | 338 ++-- .../tactics/default_tactics_fire_station.py | 152 +- .../tactics/default_tactics_police_force.py | 346 ++-- .../tactics/default_tactics_police_office.py | 152 +- src/adf_core_python/launcher.py | 244 +-- tests/core/agent/config/test_module_config.py | 78 +- tests/core/agent/develop/test_develop.py | 24 +- .../core/agent/module/test_module_manager.py | 42 +- 147 files changed, 11722 insertions(+), 11945 deletions(-) diff --git a/src/adf_core_python/cli/cli.py b/src/adf_core_python/cli/cli.py index b14a1c7..abaf673 100644 --- a/src/adf_core_python/cli/cli.py +++ b/src/adf_core_python/cli/cli.py @@ -8,49 +8,49 @@ @click.command() @click.option( - "--name", prompt="Your agent team name", help="The name of your agent team" + "--name", prompt="Your agent team name", help="The name of your agent team" ) def cli(name: str) -> None: - # load template dir and create a new agent team - click.echo(f"Creating a new agent team with name: {name}") - # 自身がいるディレクトリを取得 - template_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "template") - # コマンドラインのカレントディレクトリを取得 - current_dir = os.getcwd() + # load template dir and create a new agent team + click.echo(f"Creating a new agent team with name: {name}") + # 自身がいるディレクトリを取得 + template_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "template") + # コマンドラインのカレントディレクトリを取得 + current_dir = os.getcwd() - _copy_template( - template_dir, - current_dir, - name, - ) + _copy_template( + template_dir, + current_dir, + name, + ) def _copy_template( - src: str, - dest: str, - name: str, + src: str, + dest: str, + name: str, ) -> None: - dest = os.path.join(dest, name) - shutil.copytree(src, dest) - - # dest以下のファイル内のNAME_PLACEHOLDERをnameに置換 - for root, dirs, files in os.walk(dest): - for file in files: - file_path = os.path.join(root, file) - with open(file_path, "r") as f: - if not file_path.endswith((".py", ".yaml", ".json")): - continue - content = f.read() - with open(file_path, "w") as f: - f.write(content.replace(NAME_PLACEHOLDER, name)) - - # ディレクトリ名のNAME_PLACEHOLDERをnameに置換 - for root, dirs, files in os.walk(dest): - for dir in dirs: - dir_path = os.path.join(root, dir) - new_dir_path = dir_path.replace(NAME_PLACEHOLDER, name) - os.rename(dir_path, new_dir_path) + dest = os.path.join(dest, name) + shutil.copytree(src, dest) + + # dest以下のファイル内のNAME_PLACEHOLDERをnameに置換 + for root, dirs, files in os.walk(dest): + for file in files: + file_path = os.path.join(root, file) + with open(file_path, "r") as f: + if not file_path.endswith((".py", ".yaml", ".json")): + continue + content = f.read() + with open(file_path, "w") as f: + f.write(content.replace(NAME_PLACEHOLDER, name)) + + # ディレクトリ名のNAME_PLACEHOLDERをnameに置換 + for root, dirs, files in os.walk(dest): + for dir in dirs: + dir_path = os.path.join(root, dir) + new_dir_path = dir_path.replace(NAME_PLACEHOLDER, name) + os.rename(dir_path, new_dir_path) if __name__ == "__main__": - cli() + cli() diff --git a/src/adf_core_python/cli/template/main.py b/src/adf_core_python/cli/template/main.py index 82ce430..8630da1 100644 --- a/src/adf_core_python/cli/template/main.py +++ b/src/adf_core_python/cli/template/main.py @@ -1,5 +1,5 @@ from adf_core_python.launcher import Launcher if __name__ == "__main__": - launcher = Launcher("./config/launcher.yaml") - launcher.launch() + launcher = Launcher("./config/launcher.yaml") + launcher.launch() diff --git a/src/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py b/src/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py index 1768cfc..b782c9d 100644 --- a/src/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py +++ b/src/adf_core_python/cli/template/src/team_name/module/complex/sample_human_detector.py @@ -14,136 +14,135 @@ class SampleHumanDetector(HumanDetector): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._clustering: Clustering = cast( + Clustering, + module_manager.get_module( + "SampleHumanDetector.Clustering", + "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", + ), + ) + self.register_sub_module(self._clustering) + + self._result: Optional[EntityID] = None + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + + def calculate(self) -> HumanDetector: + transport_human: Optional[Human] = self._agent_info.some_one_on_board() + if transport_human is not None: + self._result = transport_human.get_entity_id() + return self + + if self._result is not None: + if not self._is_valid_human(self._result): + self._result = None + + if self._result is None: + self._result = self._select_target() + + return self + + def _select_target(self) -> Optional[EntityID]: + if self._result is not None and self._is_valid_human(self._result): + return self._result + + cluster_index: int = self._clustering.get_cluster_index( + self._agent_info.get_entity_id() + ) + cluster_entities: list[Entity] = self._clustering.get_cluster_entities( + cluster_index + ) + + cluster_valid_human_entities: list[Entity] = [ + entity + for entity in cluster_entities + if self._is_valid_human(entity.get_entity_id()) and isinstance(entity, Civilian) + ] + if len(cluster_valid_human_entities) != 0: + nearest_human_entity = cluster_valid_human_entities[0] + nearest_distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), + nearest_human_entity.get_entity_id(), + ) + for entity in cluster_valid_human_entities: + distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), + entity.get_entity_id(), ) - self._clustering: Clustering = cast( - Clustering, - module_manager.get_module( - "SampleHumanDetector.Clustering", - "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", - ), + if distance < nearest_distance: + nearest_distance = distance + nearest_human_entity = entity + return nearest_human_entity.get_entity_id() + + world_valid_human_entities: list[Entity] = [ + entity + for entity in self._world_info.get_entities_of_types([Civilian]) + if self._is_valid_human(entity.get_entity_id()) + ] + if len(world_valid_human_entities) != 0: + nearest_human_entity = world_valid_human_entities[0] + nearest_distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), + nearest_human_entity.get_entity_id(), + ) + for entity in world_valid_human_entities: + distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), + entity.get_entity_id(), ) - self.register_sub_module(self._clustering) - - self._result: Optional[EntityID] = None - self._logger = get_agent_logger( - f"{self.__class__.__module__}.{self.__class__.__qualname__}", - self._agent_info, - ) - - def calculate(self) -> HumanDetector: - transport_human: Optional[Human] = self._agent_info.some_one_on_board() - if transport_human is not None: - self._result = transport_human.get_entity_id() - return self - - if self._result is not None: - if not self._is_valid_human(self._result): - self._result = None - - if self._result is None: - self._result = self._select_target() - - return self - - def _select_target(self) -> Optional[EntityID]: - if self._result is not None and self._is_valid_human(self._result): - return self._result - - cluster_index: int = self._clustering.get_cluster_index( - self._agent_info.get_entity_id() - ) - cluster_entities: list[Entity] = self._clustering.get_cluster_entities( - cluster_index - ) - - cluster_valid_human_entities: list[Entity] = [ - entity - for entity in cluster_entities - if self._is_valid_human(entity.get_entity_id()) - and isinstance(entity, Civilian) - ] - if len(cluster_valid_human_entities) != 0: - nearest_human_entity = cluster_valid_human_entities[0] - nearest_distance = self._world_info.get_distance( - self._agent_info.get_entity_id(), - nearest_human_entity.get_entity_id(), - ) - for entity in cluster_valid_human_entities: - distance = self._world_info.get_distance( - self._agent_info.get_entity_id(), - entity.get_entity_id(), - ) - if distance < nearest_distance: - nearest_distance = distance - nearest_human_entity = entity - return nearest_human_entity.get_entity_id() - - world_valid_human_entities: list[Entity] = [ - entity - for entity in self._world_info.get_entities_of_types([Civilian]) - if self._is_valid_human(entity.get_entity_id()) - ] - if len(world_valid_human_entities) != 0: - nearest_human_entity = world_valid_human_entities[0] - nearest_distance = self._world_info.get_distance( - self._agent_info.get_entity_id(), - nearest_human_entity.get_entity_id(), - ) - for entity in world_valid_human_entities: - distance = self._world_info.get_distance( - self._agent_info.get_entity_id(), - entity.get_entity_id(), - ) - if distance < nearest_distance: - nearest_distance = distance - nearest_human_entity = entity - return nearest_human_entity.get_entity_id() - - return None - - def _is_valid_human(self, target_entity_id: EntityID) -> bool: - target: Optional[Entity] = self._world_info.get_entity(target_entity_id) - if target is None: - return False - if not isinstance(target, Human): - return False - hp: Optional[int] = target.get_hp() - if hp is None or hp <= 0: - return False - buriedness: Optional[int] = target.get_buriedness() - if buriedness is None: - return False - myself = self._agent_info.get_myself() - if myself is None: - return False - if myself.get_urn() == EntityURN.FIRE_BRIGADE and buriedness == 0: - return False - if myself.get_urn() == EntityURN.AMBULANCE_TEAM and buriedness > 0: - return False - damage: Optional[int] = target.get_damage() - if damage is None or damage == 0: - return False - position_entity_id: Optional[EntityID] = target.get_position() - if position_entity_id is None: - return False - position: Optional[Entity] = self._world_info.get_entity(position_entity_id) - if position is None: - return False - urn: EntityURN = position.get_urn() - if urn == EntityURN.REFUGE or urn == EntityURN.AMBULANCE_TEAM: - return False - - return True - - def get_target_entity_id(self) -> Optional[EntityID]: - return self._result + if distance < nearest_distance: + nearest_distance = distance + nearest_human_entity = entity + return nearest_human_entity.get_entity_id() + + return None + + def _is_valid_human(self, target_entity_id: EntityID) -> bool: + target: Optional[Entity] = self._world_info.get_entity(target_entity_id) + if target is None: + return False + if not isinstance(target, Human): + return False + hp: Optional[int] = target.get_hp() + if hp is None or hp <= 0: + return False + buriedness: Optional[int] = target.get_buriedness() + if buriedness is None: + return False + myself = self._agent_info.get_myself() + if myself is None: + return False + if myself.get_urn() == EntityURN.FIRE_BRIGADE and buriedness == 0: + return False + if myself.get_urn() == EntityURN.AMBULANCE_TEAM and buriedness > 0: + return False + damage: Optional[int] = target.get_damage() + if damage is None or damage == 0: + return False + position_entity_id: Optional[EntityID] = target.get_position() + if position_entity_id is None: + return False + position: Optional[Entity] = self._world_info.get_entity(position_entity_id) + if position is None: + return False + urn: EntityURN = position.get_urn() + if urn == EntityURN.REFUGE or urn == EntityURN.AMBULANCE_TEAM: + return False + + return True + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._result diff --git a/src/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py b/src/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py index 2e71d61..4ec1295 100644 --- a/src/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py +++ b/src/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py @@ -10,147 +10,143 @@ from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.module.algorithm.path_planning import ( - PathPlanning, + PathPlanning, ) from adf_core_python.core.component.module.complex.road_detector import RoadDetector class SampleRoadDetector(RoadDetector): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - self._path_planning: PathPlanning = cast( - PathPlanning, - module_manager.get_module( - "SampleRoadDetector.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - - self.register_sub_module(self._path_planning) - self._result: Optional[EntityID] = None - - def precompute(self, precompute_data: PrecomputeData) -> RoadDetector: - super().precompute(precompute_data) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "SampleRoadDetector.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + + self.register_sub_module(self._path_planning) + self._result: Optional[EntityID] = None + + def precompute(self, precompute_data: PrecomputeData) -> RoadDetector: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> RoadDetector: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + + self._target_areas: set[EntityID] = set() + entities = self._world_info.get_entities_of_types([Refuge, Building, GasStation]) + for entity in entities: + if not isinstance(entity, Building): + continue + for entity_id in entity.get_neighbors(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._target_areas.add(entity_id) + + self._priority_roads = set() + for entity in self._world_info.get_entities_of_types([Refuge]): + if not isinstance(entity, Building): + continue + for entity_id in entity.get_neighbors(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._priority_roads.add(entity_id) + + return self + + def prepare(self) -> RoadDetector: + super().prepare() + if self.get_count_prepare() >= 2: + return self + + self._target_areas = set() + entities = self._world_info.get_entities_of_types([Refuge, Building, GasStation]) + for entity in entities: + building: Building = cast(Building, entity) + for entity_id in building.get_neighbors(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._target_areas.add(entity_id) + + self._priority_roads = set() + for entity in self._world_info.get_entities_of_types([Refuge]): + refuge: Refuge = cast(Refuge, entity) + for entity_id in refuge.get_neighbors(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._priority_roads.add(entity_id) + + return self + + def update_info(self, message_manager: MessageManager) -> RoadDetector: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self + + if self._result is not None: + if self._agent_info.get_position_entity_id == self._result: + entity = self._world_info.get_entity(self._result) + if isinstance(entity, Building): + self._result = None + elif isinstance(entity, Road): + road = entity + if road.get_blockades() == []: + self._target_areas.remove(self._result) + self._result = None + + return self + + def calculate(self) -> RoadDetector: + if self._result is None: + position_entity_id = self._agent_info.get_position_entity_id() + if position_entity_id is None: return self - - def resume(self, precompute_data: PrecomputeData) -> RoadDetector: - super().resume(precompute_data) - if self.get_count_resume() >= 2: - return self - - self._target_areas: set[EntityID] = set() - entities = self._world_info.get_entities_of_types( - [Refuge, Building, GasStation] - ) - for entity in entities: - if not isinstance(entity, Building): - continue - for entity_id in entity.get_neighbors(): - neighbor = self._world_info.get_entity(entity_id) - if isinstance(neighbor, Road): - self._target_areas.add(entity_id) - - self._priority_roads = set() - for entity in self._world_info.get_entities_of_types([Refuge]): - if not isinstance(entity, Building): - continue - for entity_id in entity.get_neighbors(): - neighbor = self._world_info.get_entity(entity_id) - if isinstance(neighbor, Road): - self._priority_roads.add(entity_id) - + if position_entity_id in self._target_areas: + self._result = position_entity_id return self - - def prepare(self) -> RoadDetector: - super().prepare() - if self.get_count_prepare() >= 2: - return self - - self._target_areas = set() - entities = self._world_info.get_entities_of_types( - [Refuge, Building, GasStation] + remove_list = [] + for entity_id in self._priority_roads: + if entity_id not in self._target_areas: + remove_list.append(entity_id) + + self._priority_roads = self._priority_roads - set(remove_list) + if len(self._priority_roads) > 0: + agent_position = self._agent_info.get_position_entity_id() + if agent_position is None: + return self + _nearest_target_area = agent_position + _nearest_distance = float("inf") + for target_area in self._target_areas: + if ( + self._world_info.get_distance(agent_position, target_area) + < _nearest_distance + ): + _nearest_target_area = target_area + _nearest_distance = self._world_info.get_distance( + agent_position, target_area + ) + path: list[EntityID] = self._path_planning.get_path( + agent_position, _nearest_target_area ) - for entity in entities: - building: Building = cast(Building, entity) - for entity_id in building.get_neighbors(): - neighbor = self._world_info.get_entity(entity_id) - if isinstance(neighbor, Road): - self._target_areas.add(entity_id) - - self._priority_roads = set() - for entity in self._world_info.get_entities_of_types([Refuge]): - refuge: Refuge = cast(Refuge, entity) - for entity_id in refuge.get_neighbors(): - neighbor = self._world_info.get_entity(entity_id) - if isinstance(neighbor, Road): - self._priority_roads.add(entity_id) - - return self - - def update_info(self, message_manager: MessageManager) -> RoadDetector: - super().update_info(message_manager) - if self.get_count_update_info() >= 2: - return self - - if self._result is not None: - if self._agent_info.get_position_entity_id == self._result: - entity = self._world_info.get_entity(self._result) - if isinstance(entity, Building): - self._result = None - elif isinstance(entity, Road): - road = entity - if road.get_blockades() == []: - self._target_areas.remove(self._result) - self._result = None + if path is not None and len(path) > 0: + self._result = path[-1] - return self - - def calculate(self) -> RoadDetector: - if self._result is None: - position_entity_id = self._agent_info.get_position_entity_id() - if position_entity_id is None: - return self - if position_entity_id in self._target_areas: - self._result = position_entity_id - return self - remove_list = [] - for entity_id in self._priority_roads: - if entity_id not in self._target_areas: - remove_list.append(entity_id) - - self._priority_roads = self._priority_roads - set(remove_list) - if len(self._priority_roads) > 0: - agent_position = self._agent_info.get_position_entity_id() - if agent_position is None: - return self - _nearest_target_area = agent_position - _nearest_distance = float("inf") - for target_area in self._target_areas: - if ( - self._world_info.get_distance(agent_position, target_area) - < _nearest_distance - ): - _nearest_target_area = target_area - _nearest_distance = self._world_info.get_distance( - agent_position, target_area - ) - path: list[EntityID] = self._path_planning.get_path( - agent_position, _nearest_target_area - ) - if path is not None and len(path) > 0: - self._result = path[-1] - - return self + return self - def get_target_entity_id(self) -> Optional[EntityID]: - return self._result + def get_target_entity_id(self) -> Optional[EntityID]: + return self._result diff --git a/src/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py b/src/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py index 888f657..9da8af3 100644 --- a/src/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py +++ b/src/adf_core_python/cli/template/src/team_name/module/complex/sample_search.py @@ -15,90 +15,90 @@ class SampleSearch(Search): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - self._unreached_building_ids: set[EntityID] = set() - self._result: Optional[EntityID] = None - - self._clustering: Clustering = cast( - Clustering, - module_manager.get_module( - "SampleSearch.Clustering", - "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", - ), - ) - - self._path_planning: PathPlanning = cast( - PathPlanning, - module_manager.get_module( - "SampleSearch.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - - self._logger = get_agent_logger( - f"{self.__class__.__module__}.{self.__class__.__qualname__}", - self._agent_info, - ) - - self.register_sub_module(self._clustering) - self.register_sub_module(self._path_planning) - - def update_info(self, message_manager: MessageManager) -> Search: - super().update_info(message_manager) - if self.get_count_update_info() > 1: - return self - - self._logger.debug( - f"unreached_building_ids: {[str(id) for id in self._unreached_building_ids]}" - ) - - searched_building_id = self._agent_info.get_position_entity_id() - if searched_building_id is not None: - self._unreached_building_ids.discard(searched_building_id) - - if len(self._unreached_building_ids) == 0: - self._unreached_building_ids = self._get_search_targets() - - return self - - def calculate(self) -> Search: - nearest_building_id: Optional[EntityID] = None - nearest_distance: Optional[float] = None - for building_id in self._unreached_building_ids: - distance = self._world_info.get_distance( - self._agent_info.get_entity_id(), building_id - ) - if nearest_distance is None or distance < nearest_distance: - nearest_building_id = building_id - nearest_distance = distance - self._result = nearest_building_id - return self - - def get_target_entity_id(self) -> Optional[EntityID]: - return self._result - - def _get_search_targets(self) -> set[EntityID]: - cluster_index: int = self._clustering.get_cluster_index( - self._agent_info.get_entity_id() - ) - cluster_entities: list[Entity] = self._clustering.get_cluster_entities( - cluster_index - ) - building_entity_ids: list[EntityID] = [ - entity.get_entity_id() - for entity in cluster_entities - if isinstance(entity, Building) and not isinstance(entity, Refuge) - ] - - return set(building_entity_ids) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + self._unreached_building_ids: set[EntityID] = set() + self._result: Optional[EntityID] = None + + self._clustering: Clustering = cast( + Clustering, + module_manager.get_module( + "SampleSearch.Clustering", + "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", + ), + ) + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "SampleSearch.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + + self.register_sub_module(self._clustering) + self.register_sub_module(self._path_planning) + + def update_info(self, message_manager: MessageManager) -> Search: + super().update_info(message_manager) + if self.get_count_update_info() > 1: + return self + + self._logger.debug( + f"unreached_building_ids: {[str(id) for id in self._unreached_building_ids]}" + ) + + searched_building_id = self._agent_info.get_position_entity_id() + if searched_building_id is not None: + self._unreached_building_ids.discard(searched_building_id) + + if len(self._unreached_building_ids) == 0: + self._unreached_building_ids = self._get_search_targets() + + return self + + def calculate(self) -> Search: + nearest_building_id: Optional[EntityID] = None + nearest_distance: Optional[float] = None + for building_id in self._unreached_building_ids: + distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), building_id + ) + if nearest_distance is None or distance < nearest_distance: + nearest_building_id = building_id + nearest_distance = distance + self._result = nearest_building_id + return self + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._result + + def _get_search_targets(self) -> set[EntityID]: + cluster_index: int = self._clustering.get_cluster_index( + self._agent_info.get_entity_id() + ) + cluster_entities: list[Entity] = self._clustering.get_cluster_entities( + cluster_index + ) + building_entity_ids: list[EntityID] = [ + entity.get_entity_id() + for entity in cluster_entities + if isinstance(entity, Building) and not isinstance(entity, Refuge) + ] + + return set(building_entity_ids) diff --git a/src/adf_core_python/core/agent/action/action.py b/src/adf_core_python/core/agent/action/action.py index 911b028..551d18e 100644 --- a/src/adf_core_python/core/agent/action/action.py +++ b/src/adf_core_python/core/agent/action/action.py @@ -5,9 +5,9 @@ class Action(ABC): - def __init__(self) -> None: - pass + def __init__(self) -> None: + pass - @abstractmethod - def get_command(self, agent_id: EntityID, time: int) -> Command: - raise NotImplementedError + @abstractmethod + def get_command(self, agent_id: EntityID, time: int) -> Command: + raise NotImplementedError diff --git a/src/adf_core_python/core/agent/action/ambulance/action_load.py b/src/adf_core_python/core/agent/action/ambulance/action_load.py index 1e9eee5..572b50b 100644 --- a/src/adf_core_python/core/agent/action/ambulance/action_load.py +++ b/src/adf_core_python/core/agent/action/ambulance/action_load.py @@ -5,11 +5,11 @@ class ActionLoad(Action): - def __init__(self, target_id: EntityID) -> None: - self.target_id = target_id + def __init__(self, target_id: EntityID) -> None: + self.target_id = target_id - def get_command(self, agent_id: EntityID, time: int) -> Command: - return AKLoad(agent_id, time, self.target_id) + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKLoad(agent_id, time, self.target_id) - def __str__(self) -> str: - return f"ActionLoad(target_id={self.target_id})" + def __str__(self) -> str: + return f"ActionLoad(target_id={self.target_id})" diff --git a/src/adf_core_python/core/agent/action/ambulance/action_rescue.py b/src/adf_core_python/core/agent/action/ambulance/action_rescue.py index e3ad8e1..8e3ce84 100644 --- a/src/adf_core_python/core/agent/action/ambulance/action_rescue.py +++ b/src/adf_core_python/core/agent/action/ambulance/action_rescue.py @@ -5,11 +5,11 @@ class ActionRescue(Action): - def __init__(self, target_id: EntityID) -> None: - self.target_id = target_id + def __init__(self, target_id: EntityID) -> None: + self.target_id = target_id - def get_command(self, agent_id: EntityID, time: int) -> Command: - return AKRescue(agent_id, time, self.target_id) + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKRescue(agent_id, time, self.target_id) - def __str__(self) -> str: - return f"ActionRescue(target_id={self.target_id})" + def __str__(self) -> str: + return f"ActionRescue(target_id={self.target_id})" diff --git a/src/adf_core_python/core/agent/action/ambulance/action_unload.py b/src/adf_core_python/core/agent/action/ambulance/action_unload.py index 2f725cb..4c46458 100644 --- a/src/adf_core_python/core/agent/action/ambulance/action_unload.py +++ b/src/adf_core_python/core/agent/action/ambulance/action_unload.py @@ -5,8 +5,8 @@ class ActionUnload(Action): - def get_command(self, agent_id: EntityID, time: int) -> Command: - return AKUnload(agent_id, time) + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKUnload(agent_id, time) - def __str__(self) -> str: - return "ActionUnload()" + def __str__(self) -> str: + return "ActionUnload()" diff --git a/src/adf_core_python/core/agent/action/common/action_move.py b/src/adf_core_python/core/agent/action/common/action_move.py index c2829a8..b6ddac0 100644 --- a/src/adf_core_python/core/agent/action/common/action_move.py +++ b/src/adf_core_python/core/agent/action/common/action_move.py @@ -7,33 +7,31 @@ class ActionMove(Action): - def __init__( - self, - path: list[EntityID], - destination_x: Optional[int] = None, - destination_y: Optional[int] = None, - ) -> None: - self.path = path - self.destination_x = destination_x - self.destination_y = destination_y - - def is_destination_defined(self) -> bool: - return self.destination_x is not None and self.destination_y is not None - - def get_destination_x(self) -> Optional[int]: - return self.destination_x - - def get_destination_y(self) -> Optional[int]: - return self.destination_y - - def get_command(self, agent_id: EntityID, time: int) -> Command: - if self.destination_x is not None and self.destination_y is not None: - return AKMove( - agent_id, time, self.path, self.destination_x, self.destination_y - ) - else: - return AKMove(agent_id, time, self.path) - - def __str__(self) -> str: - path: str = ", ".join([str(p) for p in self.path]) - return f"ActionMove(path={path}, destination_x={self.destination_x}, destination_y={self.destination_y})" + def __init__( + self, + path: list[EntityID], + destination_x: Optional[int] = None, + destination_y: Optional[int] = None, + ) -> None: + self.path = path + self.destination_x = destination_x + self.destination_y = destination_y + + def is_destination_defined(self) -> bool: + return self.destination_x is not None and self.destination_y is not None + + def get_destination_x(self) -> Optional[int]: + return self.destination_x + + def get_destination_y(self) -> Optional[int]: + return self.destination_y + + def get_command(self, agent_id: EntityID, time: int) -> Command: + if self.destination_x is not None and self.destination_y is not None: + return AKMove(agent_id, time, self.path, self.destination_x, self.destination_y) + else: + return AKMove(agent_id, time, self.path) + + def __str__(self) -> str: + path: str = ", ".join([str(p) for p in self.path]) + return f"ActionMove(path={path}, destination_x={self.destination_x}, destination_y={self.destination_y})" diff --git a/src/adf_core_python/core/agent/action/common/action_rest.py b/src/adf_core_python/core/agent/action/common/action_rest.py index 94bfaea..1d61001 100644 --- a/src/adf_core_python/core/agent/action/common/action_rest.py +++ b/src/adf_core_python/core/agent/action/common/action_rest.py @@ -5,8 +5,8 @@ class ActionRest(Action): - def get_command(self, agent_id: EntityID, time: int) -> Command: - return AKRest(agent_id, time) + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKRest(agent_id, time) - def __str__(self) -> str: - return "ActionRest()" + def __str__(self) -> str: + return "ActionRest()" diff --git a/src/adf_core_python/core/agent/action/fire/action_extinguish.py b/src/adf_core_python/core/agent/action/fire/action_extinguish.py index 64713a9..e14a663 100644 --- a/src/adf_core_python/core/agent/action/fire/action_extinguish.py +++ b/src/adf_core_python/core/agent/action/fire/action_extinguish.py @@ -5,14 +5,12 @@ class ActionExtinguish(Action): - def __init__(self, target_id: EntityID, max_power: int) -> None: - self.target_id = target_id - self.max_power = max_power + def __init__(self, target_id: EntityID, max_power: int) -> None: + self.target_id = target_id + self.max_power = max_power - def get_command(self, agent_id: EntityID, time: int) -> Command: - return AKExtinguish(agent_id, time, self.target_id, self.max_power) + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKExtinguish(agent_id, time, self.target_id, self.max_power) - def __str__(self) -> str: - return ( - f"ActionExtinguish(target_id={self.target_id}, max_power={self.max_power})" - ) + def __str__(self) -> str: + return f"ActionExtinguish(target_id={self.target_id}, max_power={self.max_power})" diff --git a/src/adf_core_python/core/agent/action/fire/action_refill.py b/src/adf_core_python/core/agent/action/fire/action_refill.py index 15c0242..8d11578 100644 --- a/src/adf_core_python/core/agent/action/fire/action_refill.py +++ b/src/adf_core_python/core/agent/action/fire/action_refill.py @@ -5,8 +5,8 @@ class ActionRefill(Action): - def get_command(self, agent_id: EntityID, time: int) -> Command: - return AKRest(agent_id, time) + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKRest(agent_id, time) - def __str__(self) -> str: - return "ActionRefill()" + def __str__(self) -> str: + return "ActionRefill()" diff --git a/src/adf_core_python/core/agent/action/fire/action_rescue.py b/src/adf_core_python/core/agent/action/fire/action_rescue.py index e3ad8e1..8e3ce84 100644 --- a/src/adf_core_python/core/agent/action/fire/action_rescue.py +++ b/src/adf_core_python/core/agent/action/fire/action_rescue.py @@ -5,11 +5,11 @@ class ActionRescue(Action): - def __init__(self, target_id: EntityID) -> None: - self.target_id = target_id + def __init__(self, target_id: EntityID) -> None: + self.target_id = target_id - def get_command(self, agent_id: EntityID, time: int) -> Command: - return AKRescue(agent_id, time, self.target_id) + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKRescue(agent_id, time, self.target_id) - def __str__(self) -> str: - return f"ActionRescue(target_id={self.target_id})" + def __str__(self) -> str: + return f"ActionRescue(target_id={self.target_id})" diff --git a/src/adf_core_python/core/agent/action/police/action_clear.py b/src/adf_core_python/core/agent/action/police/action_clear.py index 6ba35d3..2709b72 100644 --- a/src/adf_core_python/core/agent/action/police/action_clear.py +++ b/src/adf_core_python/core/agent/action/police/action_clear.py @@ -5,11 +5,11 @@ class ActionClear(Action): - def __init__(self, blockade: Blockade) -> None: - self.blockade = blockade + def __init__(self, blockade: Blockade) -> None: + self.blockade = blockade - def get_command(self, agent_id: EntityID, time: int) -> Command: - return AKClear(agent_id, time, self.blockade.get_entity_id()) + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKClear(agent_id, time, self.blockade.get_entity_id()) - def __str__(self) -> str: - return f"ActionClear(blockade={self.blockade})" + def __str__(self) -> str: + return f"ActionClear(blockade={self.blockade})" diff --git a/src/adf_core_python/core/agent/action/police/action_clear_area.py b/src/adf_core_python/core/agent/action/police/action_clear_area.py index be92c8a..41dbc63 100644 --- a/src/adf_core_python/core/agent/action/police/action_clear_area.py +++ b/src/adf_core_python/core/agent/action/police/action_clear_area.py @@ -5,18 +5,20 @@ class ActionClearArea(Action): - def __init__(self, position_x: int, position_y: int) -> None: - self.position_x = position_x - self.position_y = position_y + def __init__(self, position_x: int, position_y: int) -> None: + self.position_x = position_x + self.position_y = position_y - def get_position_x(self) -> int: - return self.position_x + def get_position_x(self) -> int: + return self.position_x - def get_position_y(self) -> int: - return self.position_y + def get_position_y(self) -> int: + return self.position_y - def get_command(self, agent_id: EntityID, time: int) -> Command: - return AKClearArea(agent_id, time, self.position_x, self.position_y) + def get_command(self, agent_id: EntityID, time: int) -> Command: + return AKClearArea(agent_id, time, self.position_x, self.position_y) - def __str__(self) -> str: - return f"ActionClearArea(position_x={self.position_x}, position_y={self.position_y})" + def __str__(self) -> str: + return ( + f"ActionClearArea(position_x={self.position_x}, position_y={self.position_y})" + ) diff --git a/src/adf_core_python/core/agent/agent.py b/src/adf_core_python/core/agent/agent.py index e3409c4..f96dd58 100644 --- a/src/adf_core_python/core/agent/agent.py +++ b/src/adf_core_python/core/agent/agent.py @@ -6,18 +6,18 @@ from bitarray import bitarray from rcrscore.commands import ( - AKClear, - AKClearArea, - AKLoad, - AKMove, - AKRescue, - AKRest, - AKSay, - AKSpeak, - AKSubscribe, - AKTell, - AKUnload, - Command, + AKClear, + AKClearArea, + AKLoad, + AKMove, + AKRescue, + AKRest, + AKSay, + AKSpeak, + AKSubscribe, + AKTell, + AKUnload, + Command, ) from rcrscore.config.config import Config as RCRSConfig @@ -34,57 +34,57 @@ # from rcrscore.messages.KAConnectOK import KAConnectOK # from rcrscore.messages.KASense import KASense from rcrscore.messages import ( - AKAcknowledge, - AKConnect, - ControlMessageFactory, - KAConnectError, - KAConnectOK, - KASense, + AKAcknowledge, + AKConnect, + ControlMessageFactory, + KAConnectError, + KAConnectOK, + KASense, ) from rcrscore.urn import ( - CommandURN, - ComponentCommandURN, - ComponentControlMessageURN, - EntityURN, + CommandURN, + ComponentCommandURN, + ComponentControlMessageURN, + EntityURN, ) from rcrscore.worldmodel import ChangeSet, WorldModel from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( - CommandAmbulance, + CommandAmbulance, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.command_fire import ( - CommandFire, + CommandFire, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.command_police import ( - CommandPolice, + CommandPolice, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( - CommandScout, + CommandScout, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( - MessageReport, + MessageReport, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_ambulance_team import ( - MessageAmbulanceTeam, + MessageAmbulanceTeam, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_building import ( - MessageBuilding, + MessageBuilding, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_civilian import ( - MessageCivilian, + MessageCivilian, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_fire_brigade import ( - MessageFireBrigade, + MessageFireBrigade, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_police_force import ( - MessagePoliceForce, + MessagePoliceForce, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_road import ( - MessageRoad, + MessageRoad, ) from adf_core_python.core.agent.communication.standard.standard_communication_module import ( - StandardCommunicationModule, + StandardCommunicationModule, ) from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -93,7 +93,7 @@ from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.communication.communication_module import ( - CommunicationModule, + CommunicationModule, ) from adf_core_python.core.config.config import Config from adf_core_python.core.gateway.gateway_agent import GatewayAgent @@ -102,291 +102,285 @@ class Agent: - def __init__( - self, - is_precompute: bool, - name: str, - is_debug: bool, - team_name: str, - data_storage_name: str, - module_config: ModuleConfig, - develop_data: DevelopData, - finish_post_connect_event: Event, - gateway_agent: Optional[GatewayAgent], - ) -> None: - self.name = name - self.connect_request_id = None - self.world_model = WorldModel() - self.config: Config - self.random = None - self.agent_id: EntityID - self.precompute_flag = is_precompute - self.logger = get_logger( - f"{self.__class__.__module__}.{self.__class__.__qualname__}" + def __init__( + self, + is_precompute: bool, + name: str, + is_debug: bool, + team_name: str, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + finish_post_connect_event: Event, + gateway_agent: Optional[GatewayAgent], + ) -> None: + self.name = name + self.connect_request_id = None + self.world_model = WorldModel() + self.config: Config + self.random = None + self.agent_id: EntityID + self.precompute_flag = is_precompute + self.logger = get_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}" + ) + self.finish_post_connect_event = finish_post_connect_event + + self.team_name = team_name + self.is_debug = is_debug + self.is_precompute = is_precompute + + if is_precompute: + self._mode = Mode.PRECOMPUTATION + + try: + self._precompute_data = PrecomputeData(data_storage_name) + except Exception as _: + pass + + self._module_config = module_config + self._develop_data = develop_data + self._message_manager: MessageManager = MessageManager() + self._communication_module: CommunicationModule = StandardCommunicationModule() + + self._gateway_agent: Optional[GatewayAgent] = gateway_agent + + def get_entity_id(self) -> EntityID: + return self.agent_id + + def set_send_msg(self, connection_send_func: Callable) -> None: + self.send_msg = connection_send_func + + def post_connect(self) -> None: + if self.is_precompute: + self._mode = Mode.PRECOMPUTATION + else: + if self._precompute_data.is_available(): + self._mode = Mode.PRECOMPUTED + else: + self._mode = Mode.NON_PRECOMPUTE + + self.config.set_value(ConfigKey.KEY_DEBUG_FLAG, self.is_debug) + self.config.set_value( + ConfigKey.KEY_DEVELOP_FLAG, self._develop_data.is_develop_mode() + ) + self._ignore_time: int = int(self.config.get_value("kernel.agents.ignoreuntil", 3)) + + self._scenario_info: ScenarioInfo = ScenarioInfo(self.config, self._mode) + self._world_info: WorldInfo = WorldInfo(self.world_model) + self._agent_info = AgentInfo(self, self.world_model) + + if isinstance(self._gateway_agent, GatewayAgent): + self._gateway_agent.set_initialize_data( + self._agent_info, self._world_info, self._scenario_info + ) + + self.logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + self.logger.debug(f"agent_config: {self.config}") + + def update_step_info( + self, time: int, change_set: ChangeSet, hear: list[Command] + ) -> None: + self._agent_info.record_think_start_time() + self._agent_info.set_time(time) + + if time == 1: + self._message_manager.register_message_class(0, MessageAmbulanceTeam) + self._message_manager.register_message_class(1, MessageFireBrigade) + self._message_manager.register_message_class(2, MessagePoliceForce) + self._message_manager.register_message_class(3, MessageBuilding) + self._message_manager.register_message_class(4, MessageCivilian) + self._message_manager.register_message_class(5, MessageRoad) + self._message_manager.register_message_class(6, CommandAmbulance) + self._message_manager.register_message_class(7, CommandFire) + self._message_manager.register_message_class(8, CommandPolice) + self._message_manager.register_message_class(9, CommandScout) + self._message_manager.register_message_class(10, MessageReport) + + if time > self._ignore_time: + self._message_manager.subscribe( + self._agent_info, self._world_info, self._scenario_info + ) + if not self._message_manager.get_is_subscribed(): + subscribed_channels = self._message_manager.get_subscribed_channels() + if subscribed_channels: + self.logger.debug( + f"Subscribed channels: {subscribed_channels}", + message_manager=self._message_manager, + ) + self.send_subscribe(time, subscribed_channels) + self._message_manager.set_is_subscribed(True) + + self._agent_info.set_heard_commands(hear) + self._agent_info.set_change_set(change_set) + self._world_info.set_change_set(change_set) + + if ( + isinstance(self._gateway_agent, GatewayAgent) + and self._gateway_agent.is_initialized() + ): + self._gateway_agent.update() + + self._message_manager.refresh() + self._communication_module.receive(self, self._message_manager) + + self.think() + + self._message_manager.coordinate_message( + self._agent_info, self._world_info, self._scenario_info + ) + self._communication_module.send(self, self._message_manager) + + @abstractmethod + def think(self) -> None: + pass + + @abstractmethod + def get_requested_entities(self) -> list[EntityURN]: + pass + + def start_up(self, request_id: int) -> None: + ak_connect = AKConnect() + self.send_msg( + ak_connect.write(request_id, self.name, self.get_requested_entities()) + ) + + def message_received(self, msg: Any) -> None: + c_msg = ControlMessageFactory().make_message(msg) + if isinstance(c_msg, KASense): + self.handler_sense(c_msg) + elif isinstance(c_msg, KAConnectOK): + self.handle_connect_ok(c_msg) + elif isinstance(c_msg, KAConnectError): + self.handle_connect_error(c_msg) + + def handle_connect_error(self, msg: Any) -> NoReturn: + if msg.reason.startswith("No more agents"): + self.logger.debug( + "Agent already connected: %s(request_id: %s)", + msg.reason, + msg.request_id, + ) + self.finish_post_connect_event.set() + else: + self.logger.error( + "Failed to connect agent: %s(request_id: %s)", + msg.reason, + msg.request_id, + ) + sys.exit(1) + + def handle_connect_ok(self, msg: Any) -> None: + self.agent_id = EntityID(msg.agent_id) + self.world_model.add_entities(msg.world) + config: RCRSConfig = msg.config + self.config = Config() + if config is not None: + for key, value in config.data.items(): + self.config.set_value(key, value) + + self.send_acknowledge(msg.request_id) + self.post_connect() + self.logger.info( + f"Connected to kernel: {self.__class__.__qualname__} (request_id: {msg.request_id}, agent_id: {self.agent_id}, mode: {self._mode})", + request_id=msg.request_id, + ) + if self.is_precompute: + self.logger.info("Precompute finished") + exit(0) + + self.finish_post_connect_event.set() + + def handler_sense(self, msg: KASense) -> None: + _id = msg.agent_id + time = msg.time + change_set = msg.change_set + heard = msg.hear.commands + + if _id != self.get_entity_id(): + self.logger.error("Agent ID mismatch: %s != %s", _id, self.get_entity_id()) + return + + heard_commands: list[Command] = [] + for heard_command in heard: + if heard_command.urn == CommandURN.AK_SPEAK: + heard_commands.append( + AKSpeak( + EntityID( + heard_command.components[ComponentControlMessageURN.AgentID].entityID + ), + heard_command.components[ComponentControlMessageURN.Time].intValue, + heard_command.components[ComponentCommandURN.Message].rawData, + heard_command.components[ComponentCommandURN.Channel].intValue, + ) ) - self.finish_post_connect_event = finish_post_connect_event - - self.team_name = team_name - self.is_debug = is_debug - self.is_precompute = is_precompute - - if is_precompute: - self._mode = Mode.PRECOMPUTATION - - try: - self._precompute_data = PrecomputeData(data_storage_name) - except Exception as _: - pass - - self._module_config = module_config - self._develop_data = develop_data - self._message_manager: MessageManager = MessageManager() - self._communication_module: CommunicationModule = StandardCommunicationModule() - - self._gateway_agent: Optional[GatewayAgent] = gateway_agent - - def get_entity_id(self) -> EntityID: - return self.agent_id - - def set_send_msg(self, connection_send_func: Callable) -> None: - self.send_msg = connection_send_func - - def post_connect(self) -> None: - if self.is_precompute: - self._mode = Mode.PRECOMPUTATION - else: - if self._precompute_data.is_available(): - self._mode = Mode.PRECOMPUTED - else: - self._mode = Mode.NON_PRECOMPUTE - - self.config.set_value(ConfigKey.KEY_DEBUG_FLAG, self.is_debug) - self.config.set_value( - ConfigKey.KEY_DEVELOP_FLAG, self._develop_data.is_develop_mode() - ) - self._ignore_time: int = int( - self.config.get_value("kernel.agents.ignoreuntil", 3) - ) - - self._scenario_info: ScenarioInfo = ScenarioInfo(self.config, self._mode) - self._world_info: WorldInfo = WorldInfo(self.world_model) - self._agent_info = AgentInfo(self, self.world_model) - - if isinstance(self._gateway_agent, GatewayAgent): - self._gateway_agent.set_initialize_data( - self._agent_info, self._world_info, self._scenario_info - ) - - self.logger = get_agent_logger( - f"{self.__class__.__module__}.{self.__class__.__qualname__}", - self._agent_info, - ) - self.logger.debug(f"agent_config: {self.config}") - - def update_step_info( - self, time: int, change_set: ChangeSet, hear: list[Command] - ) -> None: - self._agent_info.record_think_start_time() - self._agent_info.set_time(time) - - if time == 1: - self._message_manager.register_message_class(0, MessageAmbulanceTeam) - self._message_manager.register_message_class(1, MessageFireBrigade) - self._message_manager.register_message_class(2, MessagePoliceForce) - self._message_manager.register_message_class(3, MessageBuilding) - self._message_manager.register_message_class(4, MessageCivilian) - self._message_manager.register_message_class(5, MessageRoad) - self._message_manager.register_message_class(6, CommandAmbulance) - self._message_manager.register_message_class(7, CommandFire) - self._message_manager.register_message_class(8, CommandPolice) - self._message_manager.register_message_class(9, CommandScout) - self._message_manager.register_message_class(10, MessageReport) - - if time > self._ignore_time: - self._message_manager.subscribe( - self._agent_info, self._world_info, self._scenario_info - ) - if not self._message_manager.get_is_subscribed(): - subscribed_channels = self._message_manager.get_subscribed_channels() - if subscribed_channels: - self.logger.debug( - f"Subscribed channels: {subscribed_channels}", - message_manager=self._message_manager, - ) - self.send_subscribe(time, subscribed_channels) - self._message_manager.set_is_subscribed(True) - - self._agent_info.set_heard_commands(hear) - self._agent_info.set_change_set(change_set) - self._world_info.set_change_set(change_set) - - if ( - isinstance(self._gateway_agent, GatewayAgent) - and self._gateway_agent.is_initialized() - ): - self._gateway_agent.update() - - self._message_manager.refresh() - self._communication_module.receive(self, self._message_manager) - - self.think() - - self._message_manager.coordinate_message( - self._agent_info, self._world_info, self._scenario_info - ) - self._communication_module.send(self, self._message_manager) - - @abstractmethod - def think(self) -> None: - pass - - @abstractmethod - def get_requested_entities(self) -> list[EntityURN]: - pass - - def start_up(self, request_id: int) -> None: - ak_connect = AKConnect() - self.send_msg( - ak_connect.write(request_id, self.name, self.get_requested_entities()) - ) - - def message_received(self, msg: Any) -> None: - c_msg = ControlMessageFactory().make_message(msg) - if isinstance(c_msg, KASense): - self.handler_sense(c_msg) - elif isinstance(c_msg, KAConnectOK): - self.handle_connect_ok(c_msg) - elif isinstance(c_msg, KAConnectError): - self.handle_connect_error(c_msg) - - def handle_connect_error(self, msg: Any) -> NoReturn: - if msg.reason.startswith("No more agents"): - self.logger.debug( - "Agent already connected: %s(request_id: %s)", - msg.reason, - msg.request_id, - ) - self.finish_post_connect_event.set() - else: - self.logger.error( - "Failed to connect agent: %s(request_id: %s)", - msg.reason, - msg.request_id, - ) - sys.exit(1) - - def handle_connect_ok(self, msg: Any) -> None: - self.agent_id = EntityID(msg.agent_id) - self.world_model.add_entities(msg.world) - config: RCRSConfig = msg.config - self.config = Config() - if config is not None: - for key, value in config.data.items(): - self.config.set_value(key, value) - - self.send_acknowledge(msg.request_id) - self.post_connect() - self.logger.info( - f"Connected to kernel: {self.__class__.__qualname__} (request_id: {msg.request_id}, agent_id: {self.agent_id}, mode: {self._mode})", - request_id=msg.request_id, - ) - if self.is_precompute: - self.logger.info("Precompute finished") - exit(0) - - self.finish_post_connect_event.set() - - def handler_sense(self, msg: KASense) -> None: - _id = msg.agent_id - time = msg.time - change_set = msg.change_set - heard = msg.hear.commands - - if _id != self.get_entity_id(): - self.logger.error("Agent ID mismatch: %s != %s", _id, self.get_entity_id()) - return - - heard_commands: list[Command] = [] - for heard_command in heard: - if heard_command.urn == CommandURN.AK_SPEAK: - heard_commands.append( - AKSpeak( - EntityID( - heard_command.components[ - ComponentControlMessageURN.AgentID - ].entityID - ), - heard_command.components[ - ComponentControlMessageURN.Time - ].intValue, - heard_command.components[ComponentCommandURN.Message].rawData, - heard_command.components[ComponentCommandURN.Channel].intValue, - ) - ) - self.world_model.merge(change_set) - start_update_info_time = _time.time() - self.update_step_info(time, change_set, heard_commands) - self.logger.debug( - f"{time} step calculation time: {_time.time() - start_update_info_time}" - ) - - def send_acknowledge(self, request_id: int) -> None: - ak_ack = AKAcknowledge() - self.send_msg(ak_ack.write(request_id, self.agent_id)) - - def send_clear(self, time: int, target: EntityID) -> None: - cmd = AKClear(self.get_entity_id(), time, target) - msg = cmd.to_message_proto() - self.send_msg(msg) - - def send_clear_area(self, time: int, x: int = -1, y: int = -1) -> None: - cmd = AKClearArea(self.get_entity_id(), time, x, y) - msg = cmd.to_message_proto() - self.send_msg(msg) - - def send_load(self, time: int, target: EntityID) -> None: - cmd = AKLoad(self.get_entity_id(), time, target) - msg = cmd.to_message_proto() - self.send_msg(msg) - - def send_move( - self, time: int, path: list[EntityID], x: int = -1, y: int = -1 - ) -> None: - cmd = AKMove(self.get_entity_id(), time, path, x, y) - msg = cmd.to_message_proto() - self.send_msg(msg) - - def send_rescue(self, time: int, target: EntityID) -> None: - cmd = AKRescue(self.get_entity_id(), time, target) - msg = cmd.to_message_proto() - self.send_msg(msg) - - def send_rest(self, time: int) -> None: - cmd = AKRest(self.get_entity_id(), time) - msg = cmd.to_message_proto() - self.send_msg(msg) - - def send_say(self, time_step: int, message: bytes) -> None: - cmd = AKSay(self.get_entity_id(), time_step, message) - msg = cmd.to_message_proto() - self.send_msg(msg) - - def send_speak(self, time_step: int, message: bitarray, channel: int) -> None: - cmd = AKSpeak(self.get_entity_id(), time_step, bytes(message), channel) # type: ignore - msg = cmd.to_message_proto() - self.send_msg(msg) - - def send_subscribe(self, time: int, channels: list[int]) -> None: - cmd = AKSubscribe(self.get_entity_id(), time, channels) - msg = cmd.to_message_proto() - self.send_msg(msg) - - def send_tell(self, time: int, message: bytes) -> None: - cmd = AKTell(self.get_entity_id(), time, message) - msg = cmd.to_message_proto() - self.send_msg(msg) - - def send_unload(self, time: int) -> None: - cmd = AKUnload(self.get_entity_id(), time) - msg = cmd.to_message_proto() - self.send_msg(msg) + self.world_model.merge(change_set) + start_update_info_time = _time.time() + self.update_step_info(time, change_set, heard_commands) + self.logger.debug( + f"{time} step calculation time: {_time.time() - start_update_info_time}" + ) + + def send_acknowledge(self, request_id: int) -> None: + ak_ack = AKAcknowledge() + self.send_msg(ak_ack.write(request_id, self.agent_id)) + + def send_clear(self, time: int, target: EntityID) -> None: + cmd = AKClear(self.get_entity_id(), time, target) + msg = cmd.to_message_proto() + self.send_msg(msg) + + def send_clear_area(self, time: int, x: int = -1, y: int = -1) -> None: + cmd = AKClearArea(self.get_entity_id(), time, x, y) + msg = cmd.to_message_proto() + self.send_msg(msg) + + def send_load(self, time: int, target: EntityID) -> None: + cmd = AKLoad(self.get_entity_id(), time, target) + msg = cmd.to_message_proto() + self.send_msg(msg) + + def send_move( + self, time: int, path: list[EntityID], x: int = -1, y: int = -1 + ) -> None: + cmd = AKMove(self.get_entity_id(), time, path, x, y) + msg = cmd.to_message_proto() + self.send_msg(msg) + + def send_rescue(self, time: int, target: EntityID) -> None: + cmd = AKRescue(self.get_entity_id(), time, target) + msg = cmd.to_message_proto() + self.send_msg(msg) + + def send_rest(self, time: int) -> None: + cmd = AKRest(self.get_entity_id(), time) + msg = cmd.to_message_proto() + self.send_msg(msg) + + def send_say(self, time_step: int, message: bytes) -> None: + cmd = AKSay(self.get_entity_id(), time_step, message) + msg = cmd.to_message_proto() + self.send_msg(msg) + + def send_speak(self, time_step: int, message: bitarray, channel: int) -> None: + cmd = AKSpeak(self.get_entity_id(), time_step, bytes(message), channel) # type: ignore + msg = cmd.to_message_proto() + self.send_msg(msg) + + def send_subscribe(self, time: int, channels: list[int]) -> None: + cmd = AKSubscribe(self.get_entity_id(), time, channels) + msg = cmd.to_message_proto() + self.send_msg(msg) + + def send_tell(self, time: int, message: bytes) -> None: + cmd = AKTell(self.get_entity_id(), time, message) + msg = cmd.to_message_proto() + self.send_msg(msg) + + def send_unload(self, time: int) -> None: + cmd = AKUnload(self.get_entity_id(), time) + msg = cmd.to_message_proto() + self.send_msg(msg) diff --git a/src/adf_core_python/core/agent/communication/message_manager.py b/src/adf_core_python/core/agent/communication/message_manager.py index fc16479..f66fc89 100644 --- a/src/adf_core_python/core/agent/communication/message_manager.py +++ b/src/adf_core_python/core/agent/communication/message_manager.py @@ -6,370 +6,366 @@ from adf_core_python.core.agent.info.world_info import WorldInfo if TYPE_CHECKING: - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.component.communication.channel_subscriber import ( - ChannelSubscriber, - ) - from adf_core_python.core.component.communication.communication_message import ( - CommunicationMessage, - ) - from adf_core_python.core.component.communication.message_coordinator import ( - MessageCoordinator, - ) + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.component.communication.channel_subscriber import ( + ChannelSubscriber, + ) + from adf_core_python.core.component.communication.communication_message import ( + CommunicationMessage, + ) + from adf_core_python.core.component.communication.message_coordinator import ( + MessageCoordinator, + ) class MessageManager: - MAX_MESSAGE_CLASS_COUNT = 32 - - def __init__(self) -> None: - """ - Initialize the MessageManager. - - Parameters - ---------- - __standard_message_class_count : int - The count of standard message classes. - __custom_message_class_count : int - The count of custom message classes. - __message_classes : dict[int, CommunicationMessage] - The message classes. - __subscribed_channels : list[int] - The subscribed channels. Default is [1]. - __is_subscribed : bool - The flag to indicate if the agent is subscribed to the channel. - """ - self.__standard_message_class_count = 0b0000_0001 - self.__custom_message_class_count = 0b0001_0000 - self.__message_classes: dict[int, type[CommunicationMessage]] = {} - self.__send_message_list: list[CommunicationMessage] = [] - self.__received_message_list: list[CommunicationMessage] = [] - self.__channel_send_message_list: list[list[CommunicationMessage]] = [] - self.__check_duplicate_cache: set[int] = set() - self.__message_coordinator: MessageCoordinator - self.__channel_subscriber: ChannelSubscriber - self.__heard_agent_help_message_count: int = 0 - self.__subscribed_channels: list[int] = [1] - self.__is_subscribed = False - - def set_subscribed_channels(self, subscribed_channels: list[int]) -> None: - """ - Set the subscribed channels. - - Parameters - ---------- - subscribed_channels : list[int] - The subscribed channels. - - """ - self.__subscribed_channels = subscribed_channels - self.__is_subscribed = False - - def get_subscribed_channels(self) -> list[int]: - """ - Get the subscribed channels. - - Returns - ------- - list[int] - The subscribed channels. - - """ - return self.__subscribed_channels - - def set_is_subscribed(self, is_subscribed: bool) -> None: - """ - Set the flag to indicate if the agent is subscribed to the channel. - - Parameters - ---------- - is_subscribed : bool - The flag to indicate if the agent is subscribed to the channel. - - """ - self.__is_subscribed = is_subscribed - - def get_is_subscribed(self) -> bool: - """ - Get the flag to indicate if the agent is subscribed to the channel. - - Returns - ------- - bool - The flag to indicate if the agent is subscribed to the channel. - - """ - return self.__is_subscribed - - def set_channel_subscriber(self, channel_subscriber: ChannelSubscriber) -> None: - """ - Set the channel subscriber. - - Parameters - ---------- - channel_subscriber : ChannelSubscriber - The channel subscriber. - - """ - self.__channel_subscriber = channel_subscriber - - def set_message_coordinator(self, message_coordinator: MessageCoordinator) -> None: - """ - Set the message coordinator. - - Parameters - ---------- - message_coordinator : MessageCoordinator - The message coordinator. - - """ - self.__message_coordinator = message_coordinator - - def get_channel_subscriber(self) -> ChannelSubscriber: - """ - Get the channel subscriber. - - Returns - ------- - ChannelSubscriber - The channel subscriber. - - """ - return self.__channel_subscriber - - def add_heard_agent_help_message_count(self) -> None: - """ - Add the heard agent help message count. - - """ - self.__heard_agent_help_message_count += 1 - - def get_heard_agent_help_message_count(self) -> int: - """ - Get the heard agent help message count. - - Returns - ------- - int - The heard agent help message count. - - """ - return self.__heard_agent_help_message_count - - def add_received_message(self, message: CommunicationMessage) -> None: - """ - Add the received message. - - Parameters - ---------- - message : CommunicationMessage - The received message. - - """ - self.__received_message_list.append(message) - - def get_received_message_list(self) -> list[CommunicationMessage]: - """ - Get the received message list. - - Returns - ------- - list[CommunicationMessage] - The received message list. - - """ - return self.__received_message_list - - def get_channel_send_message_list(self) -> list[list[CommunicationMessage]]: - """ - Get the channel send message list. - - Returns - ------- - list[list[CommunicationMessage]] - The channel send message list. - - """ - return self.__channel_send_message_list - - def subscribe( - self, agent_info: AgentInfo, world_info: WorldInfo, scenario_info: ScenarioInfo - ) -> None: - """ - Subscribe to the channel. - - Parameters - ---------- - agent_info : AgentInfo - The agent info. - world_info : WorldInfo - The world info. - scenario_info : ScenarioInfo - The scenario info. - - Throws - ------ - ValueError - If the ChannelSubscriber is not set. - - """ - if self.__channel_subscriber is None: - raise ValueError("ChannelSubscriber is not set.") - - if agent_info.get_time() == 1: - self.__subscribed_channels = self.__channel_subscriber.subscribe( - agent_info, world_info, scenario_info - ) - - def register_message_class( - self, index: int, message_class: type[CommunicationMessage] - ) -> None: - """ - Register the message class. - - Parameters - ---------- - message_class : type[CommunicationMessage] - The message class. - - """ - if index >= self.MAX_MESSAGE_CLASS_COUNT: - raise ValueError( - f"Possible index values are 0 to {self.MAX_MESSAGE_CLASS_COUNT - 1}" - ) - self.__message_classes[index] = message_class - - def get_message_class(self, index: int) -> type[CommunicationMessage]: - """ - Get the message class. - - Parameters - ---------- - index : int - The index. - - Returns - ------- - type[CommunicationMessage] - The message class. - - """ - return self.__message_classes[index] - - def get_message_class_index(self, message_class: type[CommunicationMessage]) -> int: - """ - Get the message class index. - - Parameters - ---------- - message_class : type[CommunicationMessage] - The message class. - - Returns - ------- - int - The message class index. - - """ - for index, cls in self.__message_classes.items(): - if cls == message_class: - return index - return -1 - - def add_message( - self, message: CommunicationMessage, check_duplicate: bool = True - ) -> None: - """ - Add the message. - - Parameters - ---------- - message : CommunicationMessage - The message. - - """ - check_key = message.__hash__() - # TODO:両方同じコードになっているが、なぜなのか調査する - if check_duplicate and check_key not in self.__check_duplicate_cache: - self.__send_message_list.append(message) - self.__check_duplicate_cache.add(check_key) - else: - self.__send_message_list.append(message) - self.__check_duplicate_cache.add(check_key) - - def get_send_message_list(self) -> list[CommunicationMessage]: - """ - Get the send message list. - - Returns - ------- - list[CommunicationMessage] - The send message list. - - """ - return self.__send_message_list - - def coordinate_message( - self, agent_info: AgentInfo, world_info: WorldInfo, scenario_info: ScenarioInfo - ) -> None: - """ - Coordinate the message. - - Parameters - ---------- - agent_info : AgentInfo - The agent info. - world_info : WorldInfo - The world info. - scenario_info : ScenarioInfo - The scenario info. - - """ - if self.__message_coordinator is None: - raise ValueError("MessageCoordinator is not set.") - - self.__channel_send_message_list = [ - [] - for _ in range( - int( - scenario_info.get_value( - ScenarioInfoKeys.COMMUNICATION_CHANNELS_COUNT, 1 - ) - ) - ) - ] - - self.__message_coordinator.coordinate( - agent_info, - world_info, - scenario_info, - self, - self.__send_message_list, - self.__channel_send_message_list, - ) - - def refresh(self) -> None: - """ - Refresh the message manager. - - """ - self.__send_message_list = [] - self.__received_message_list = [] - self.__check_duplicate_cache = set() - self.__heard_agent_help_message_count = 0 - - def __str__(self) -> str: - return ( - f"MessageManager(" - f"standard_message_class_count={self.__standard_message_class_count}, " - f"custom_message_class_count={self.__custom_message_class_count}, " - f"message_classes={self.__message_classes}, " - f"send_message_list={self.__send_message_list}, " - f"received_message_list={self.__received_message_list}, " - f"channel_send_message_list={self.__channel_send_message_list}, " - f"check_duplicate_cache={self.__check_duplicate_cache}, " - f"message_coordinator={self.__message_coordinator}, " - f"channel_subscriber={self.__channel_subscriber}, " - f"heard_agent_help_message_count={self.__heard_agent_help_message_count}, " - f"subscribed_channels={self.__subscribed_channels}, " - f"is_subscribed={self.__is_subscribed})" - ) + MAX_MESSAGE_CLASS_COUNT = 32 + + def __init__(self) -> None: + """ + Initialize the MessageManager. + + Parameters + ---------- + __standard_message_class_count : int + The count of standard message classes. + __custom_message_class_count : int + The count of custom message classes. + __message_classes : dict[int, CommunicationMessage] + The message classes. + __subscribed_channels : list[int] + The subscribed channels. Default is [1]. + __is_subscribed : bool + The flag to indicate if the agent is subscribed to the channel. + """ + self.__standard_message_class_count = 0b0000_0001 + self.__custom_message_class_count = 0b0001_0000 + self.__message_classes: dict[int, type[CommunicationMessage]] = {} + self.__send_message_list: list[CommunicationMessage] = [] + self.__received_message_list: list[CommunicationMessage] = [] + self.__channel_send_message_list: list[list[CommunicationMessage]] = [] + self.__check_duplicate_cache: set[int] = set() + self.__message_coordinator: MessageCoordinator + self.__channel_subscriber: ChannelSubscriber + self.__heard_agent_help_message_count: int = 0 + self.__subscribed_channels: list[int] = [1] + self.__is_subscribed = False + + def set_subscribed_channels(self, subscribed_channels: list[int]) -> None: + """ + Set the subscribed channels. + + Parameters + ---------- + subscribed_channels : list[int] + The subscribed channels. + + """ + self.__subscribed_channels = subscribed_channels + self.__is_subscribed = False + + def get_subscribed_channels(self) -> list[int]: + """ + Get the subscribed channels. + + Returns + ------- + list[int] + The subscribed channels. + + """ + return self.__subscribed_channels + + def set_is_subscribed(self, is_subscribed: bool) -> None: + """ + Set the flag to indicate if the agent is subscribed to the channel. + + Parameters + ---------- + is_subscribed : bool + The flag to indicate if the agent is subscribed to the channel. + + """ + self.__is_subscribed = is_subscribed + + def get_is_subscribed(self) -> bool: + """ + Get the flag to indicate if the agent is subscribed to the channel. + + Returns + ------- + bool + The flag to indicate if the agent is subscribed to the channel. + + """ + return self.__is_subscribed + + def set_channel_subscriber(self, channel_subscriber: ChannelSubscriber) -> None: + """ + Set the channel subscriber. + + Parameters + ---------- + channel_subscriber : ChannelSubscriber + The channel subscriber. + + """ + self.__channel_subscriber = channel_subscriber + + def set_message_coordinator(self, message_coordinator: MessageCoordinator) -> None: + """ + Set the message coordinator. + + Parameters + ---------- + message_coordinator : MessageCoordinator + The message coordinator. + + """ + self.__message_coordinator = message_coordinator + + def get_channel_subscriber(self) -> ChannelSubscriber: + """ + Get the channel subscriber. + + Returns + ------- + ChannelSubscriber + The channel subscriber. + + """ + return self.__channel_subscriber + + def add_heard_agent_help_message_count(self) -> None: + """ + Add the heard agent help message count. + + """ + self.__heard_agent_help_message_count += 1 + + def get_heard_agent_help_message_count(self) -> int: + """ + Get the heard agent help message count. + + Returns + ------- + int + The heard agent help message count. + + """ + return self.__heard_agent_help_message_count + + def add_received_message(self, message: CommunicationMessage) -> None: + """ + Add the received message. + + Parameters + ---------- + message : CommunicationMessage + The received message. + + """ + self.__received_message_list.append(message) + + def get_received_message_list(self) -> list[CommunicationMessage]: + """ + Get the received message list. + + Returns + ------- + list[CommunicationMessage] + The received message list. + + """ + return self.__received_message_list + + def get_channel_send_message_list(self) -> list[list[CommunicationMessage]]: + """ + Get the channel send message list. + + Returns + ------- + list[list[CommunicationMessage]] + The channel send message list. + + """ + return self.__channel_send_message_list + + def subscribe( + self, agent_info: AgentInfo, world_info: WorldInfo, scenario_info: ScenarioInfo + ) -> None: + """ + Subscribe to the channel. + + Parameters + ---------- + agent_info : AgentInfo + The agent info. + world_info : WorldInfo + The world info. + scenario_info : ScenarioInfo + The scenario info. + + Throws + ------ + ValueError + If the ChannelSubscriber is not set. + + """ + if self.__channel_subscriber is None: + raise ValueError("ChannelSubscriber is not set.") + + if agent_info.get_time() == 1: + self.__subscribed_channels = self.__channel_subscriber.subscribe( + agent_info, world_info, scenario_info + ) + + def register_message_class( + self, index: int, message_class: type[CommunicationMessage] + ) -> None: + """ + Register the message class. + + Parameters + ---------- + message_class : type[CommunicationMessage] + The message class. + + """ + if index >= self.MAX_MESSAGE_CLASS_COUNT: + raise ValueError( + f"Possible index values are 0 to {self.MAX_MESSAGE_CLASS_COUNT - 1}" + ) + self.__message_classes[index] = message_class + + def get_message_class(self, index: int) -> type[CommunicationMessage]: + """ + Get the message class. + + Parameters + ---------- + index : int + The index. + + Returns + ------- + type[CommunicationMessage] + The message class. + + """ + return self.__message_classes[index] + + def get_message_class_index(self, message_class: type[CommunicationMessage]) -> int: + """ + Get the message class index. + + Parameters + ---------- + message_class : type[CommunicationMessage] + The message class. + + Returns + ------- + int + The message class index. + + """ + for index, cls in self.__message_classes.items(): + if cls == message_class: + return index + return -1 + + def add_message( + self, message: CommunicationMessage, check_duplicate: bool = True + ) -> None: + """ + Add the message. + + Parameters + ---------- + message : CommunicationMessage + The message. + + """ + check_key = message.__hash__() + # TODO:両方同じコードになっているが、なぜなのか調査する + if check_duplicate and check_key not in self.__check_duplicate_cache: + self.__send_message_list.append(message) + self.__check_duplicate_cache.add(check_key) + else: + self.__send_message_list.append(message) + self.__check_duplicate_cache.add(check_key) + + def get_send_message_list(self) -> list[CommunicationMessage]: + """ + Get the send message list. + + Returns + ------- + list[CommunicationMessage] + The send message list. + + """ + return self.__send_message_list + + def coordinate_message( + self, agent_info: AgentInfo, world_info: WorldInfo, scenario_info: ScenarioInfo + ) -> None: + """ + Coordinate the message. + + Parameters + ---------- + agent_info : AgentInfo + The agent info. + world_info : WorldInfo + The world info. + scenario_info : ScenarioInfo + The scenario info. + + """ + if self.__message_coordinator is None: + raise ValueError("MessageCoordinator is not set.") + + self.__channel_send_message_list = [ + [] + for _ in range( + int(scenario_info.get_value(ScenarioInfoKeys.COMMUNICATION_CHANNELS_COUNT, 1)) + ) + ] + + self.__message_coordinator.coordinate( + agent_info, + world_info, + scenario_info, + self, + self.__send_message_list, + self.__channel_send_message_list, + ) + + def refresh(self) -> None: + """ + Refresh the message manager. + + """ + self.__send_message_list = [] + self.__received_message_list = [] + self.__check_duplicate_cache = set() + self.__heard_agent_help_message_count = 0 + + def __str__(self) -> str: + return ( + f"MessageManager(" + f"standard_message_class_count={self.__standard_message_class_count}, " + f"custom_message_class_count={self.__custom_message_class_count}, " + f"message_classes={self.__message_classes}, " + f"send_message_list={self.__send_message_list}, " + f"received_message_list={self.__received_message_list}, " + f"channel_send_message_list={self.__channel_send_message_list}, " + f"check_duplicate_cache={self.__check_duplicate_cache}, " + f"message_coordinator={self.__message_coordinator}, " + f"channel_subscriber={self.__channel_subscriber}, " + f"heard_agent_help_message_count={self.__heard_agent_help_message_count}, " + f"subscribed_channels={self.__subscribed_channels}, " + f"is_subscribed={self.__is_subscribed})" + ) diff --git a/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py index 1a0a2f0..6398af7 100644 --- a/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py +++ b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_ambulance.py @@ -6,130 +6,128 @@ from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( - read_with_exist_flag, - write_with_exist_flag, + read_with_exist_flag, + write_with_exist_flag, ) class CommandAmbulance(StandardMessage): - ACTION_REST: int = 0 - ACTION_MOVE: int = 1 - ACTION_RESCUE: int = 2 - ACTION_LOAD: int = 3 - ACTION_UNLOAD: int = 4 - ACTION_AUTONOMY: int = 5 - - SIZE_AMBULANCE_TEAM_ENTITY_ID: int = 32 - SIZE_TARGET_ENTITY_ID: int = 32 - SIZE_ACTION: int = 4 - - def __init__( - self, - is_wireless_message: bool, - command_executor_agent_entity_id: EntityID, - sender_entity_id: EntityID, - execute_action: int, - priority: StandardMessagePriority, - command_target_entity_id: Optional[EntityID] = None, - ): - super().__init__(is_wireless_message, priority, sender_entity_id) - self._command_executor_agent_entity_id: Optional[EntityID] = ( - command_executor_agent_entity_id - ) - self._command_target_entity_id: Optional[EntityID] = command_target_entity_id - self._is_bloadcast: bool = command_target_entity_id is None - self._execute_action: Optional[int] = execute_action - - def get_command_executor_agent_entity_id(self) -> Optional[EntityID]: - return self._command_executor_agent_entity_id - - def get_command_target_entity_id(self) -> Optional[EntityID]: - return self._command_target_entity_id - - def get_execute_action(self) -> Optional[int]: - return self._execute_action - - def is_broadcast(self) -> bool: - return self._is_bloadcast - - def get_bit_size(self) -> int: - return self.to_bits().__len__() - - def to_bits(self) -> bitarray: - bit_array = super().to_bits() - raw_command_executor_agent_entity_id = ( - self._command_executor_agent_entity_id.get_value() - if self._command_executor_agent_entity_id is not None - else None - ) - write_with_exist_flag( - bit_array, - raw_command_executor_agent_entity_id, - self.SIZE_AMBULANCE_TEAM_ENTITY_ID, - ) - raw_command_target_entity_id = ( - self._command_target_entity_id.get_value() - if self._command_target_entity_id is not None - else None - ) - write_with_exist_flag( - bit_array, raw_command_target_entity_id, self.SIZE_TARGET_ENTITY_ID - ) - write_with_exist_flag(bit_array, self._execute_action, self.SIZE_ACTION) - return bit_array - - @classmethod - def from_bits( - cls, - bit_array: bitarray, - is_wireless_message: bool, - sender_entity_id: EntityID, - ) -> CommandAmbulance: - std_message = super().from_bits( - bit_array, is_wireless_message, sender_entity_id - ) - raw_command_executor_agent_entity_id = read_with_exist_flag( - bit_array, cls.SIZE_AMBULANCE_TEAM_ENTITY_ID - ) - command_executor_agent_id = ( - EntityID(raw_command_executor_agent_entity_id) - if raw_command_executor_agent_entity_id is not None - else None - ) - raw_command_target_entity_id = read_with_exist_flag( - bit_array, cls.SIZE_TARGET_ENTITY_ID - ) - command_target_id = ( - EntityID(raw_command_target_entity_id) - if raw_command_target_entity_id is not None - else None - ) - execute_action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) - return cls( - is_wireless_message, - command_executor_agent_id or EntityID(-1), - sender_entity_id, - execute_action if execute_action is not None else -1, - std_message.get_priority(), - command_target_id, - ) - - def __hash__(self) -> int: - h = super().__hash__() - return hash( - ( - h, - self._command_executor_agent_entity_id, - self._command_target_entity_id, - self._execute_action, - ) - ) - - def __str__(self) -> str: - return f"CommandAmbulance(executor={self._command_executor_agent_entity_id}, target={self._command_target_entity_id}, action={self._execute_action})" + ACTION_REST: int = 0 + ACTION_MOVE: int = 1 + ACTION_RESCUE: int = 2 + ACTION_LOAD: int = 3 + ACTION_UNLOAD: int = 4 + ACTION_AUTONOMY: int = 5 + + SIZE_AMBULANCE_TEAM_ENTITY_ID: int = 32 + SIZE_TARGET_ENTITY_ID: int = 32 + SIZE_ACTION: int = 4 + + def __init__( + self, + is_wireless_message: bool, + command_executor_agent_entity_id: EntityID, + sender_entity_id: EntityID, + execute_action: int, + priority: StandardMessagePriority, + command_target_entity_id: Optional[EntityID] = None, + ): + super().__init__(is_wireless_message, priority, sender_entity_id) + self._command_executor_agent_entity_id: Optional[EntityID] = ( + command_executor_agent_entity_id + ) + self._command_target_entity_id: Optional[EntityID] = command_target_entity_id + self._is_bloadcast: bool = command_target_entity_id is None + self._execute_action: Optional[int] = execute_action + + def get_command_executor_agent_entity_id(self) -> Optional[EntityID]: + return self._command_executor_agent_entity_id + + def get_command_target_entity_id(self) -> Optional[EntityID]: + return self._command_target_entity_id + + def get_execute_action(self) -> Optional[int]: + return self._execute_action + + def is_broadcast(self) -> bool: + return self._is_bloadcast + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + raw_command_executor_agent_entity_id = ( + self._command_executor_agent_entity_id.get_value() + if self._command_executor_agent_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, + raw_command_executor_agent_entity_id, + self.SIZE_AMBULANCE_TEAM_ENTITY_ID, + ) + raw_command_target_entity_id = ( + self._command_target_entity_id.get_value() + if self._command_target_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, raw_command_target_entity_id, self.SIZE_TARGET_ENTITY_ID + ) + write_with_exist_flag(bit_array, self._execute_action, self.SIZE_ACTION) + return bit_array + + @classmethod + def from_bits( + cls, + bit_array: bitarray, + is_wireless_message: bool, + sender_entity_id: EntityID, + ) -> CommandAmbulance: + std_message = super().from_bits(bit_array, is_wireless_message, sender_entity_id) + raw_command_executor_agent_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_AMBULANCE_TEAM_ENTITY_ID + ) + command_executor_agent_id = ( + EntityID(raw_command_executor_agent_entity_id) + if raw_command_executor_agent_entity_id is not None + else None + ) + raw_command_target_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_TARGET_ENTITY_ID + ) + command_target_id = ( + EntityID(raw_command_target_entity_id) + if raw_command_target_entity_id is not None + else None + ) + execute_action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) + return cls( + is_wireless_message, + command_executor_agent_id or EntityID(-1), + sender_entity_id, + execute_action if execute_action is not None else -1, + std_message.get_priority(), + command_target_id, + ) + + def __hash__(self) -> int: + h = super().__hash__() + return hash( + ( + h, + self._command_executor_agent_entity_id, + self._command_target_entity_id, + self._execute_action, + ) + ) + + def __str__(self) -> str: + return f"CommandAmbulance(executor={self._command_executor_agent_entity_id}, target={self._command_target_entity_id}, action={self._execute_action})" diff --git a/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py index a4bd8ae..3dc7d02 100644 --- a/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py +++ b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_fire.py @@ -6,131 +6,129 @@ from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( - read_with_exist_flag, - write_with_exist_flag, + read_with_exist_flag, + write_with_exist_flag, ) class CommandFire(StandardMessage): - ACTION_REST: int = 0 - ACTION_MOVE: int = 1 - ACTION_EXTINGUISH: int = 2 - ACTION_REFILL: int = 3 - ACTION_RESCUE: int = 4 - ACTION_AUTONOMY: int = 5 - - SIZE_FIRE_BRIGADE_ENTITY_ID: int = 32 - SIZE_TARGET_ENTITY_ID: int = 32 - SIZE_ACTION: int = 4 - - def __init__( - self, - is_wireless_message: bool, - command_executor_agent_entity_id: EntityID, - sender_entity_id: EntityID, - execute_action: int, - priority: StandardMessagePriority, - command_target_entity_id: Optional[EntityID] = None, - ): - super().__init__(is_wireless_message, priority, sender_entity_id) - self._command_executor_agent_entity_id: Optional[EntityID] = ( - command_executor_agent_entity_id - ) - self._command_target_entity_id: Optional[EntityID] = command_target_entity_id - self._is_bloadcast: bool = command_target_entity_id is None - self._execute_action: Optional[int] = execute_action - - def get_command_executor_agent_entity_id(self) -> Optional[EntityID]: - return self._command_executor_agent_entity_id - - def get_command_target_entity_id(self) -> Optional[EntityID]: - return self._command_target_entity_id - - def get_execute_action(self) -> Optional[int]: - return self._execute_action - - def is_broadcast(self) -> bool: - return self._is_bloadcast - - def get_bit_size(self) -> int: - return self.to_bits().__len__() - - def to_bits(self) -> bitarray: - bit_array = super().to_bits() - raw_command_executor_agent_entity_id = ( - self._command_executor_agent_entity_id.get_value() - if self._command_executor_agent_entity_id is not None - else None - ) - write_with_exist_flag( - bit_array, - raw_command_executor_agent_entity_id, - self.SIZE_FIRE_BRIGADE_ENTITY_ID, - ) - raw_command_target_entity_id = ( - self._command_target_entity_id.get_value() - if self._command_target_entity_id is not None - else None - ) - write_with_exist_flag( - bit_array, raw_command_target_entity_id, self.SIZE_TARGET_ENTITY_ID - ) - write_with_exist_flag(bit_array, self._execute_action, self.SIZE_ACTION) - - return bit_array - - @classmethod - def from_bits( - cls, - bit_array: bitarray, - is_wireless_message: bool, - sender_entity_id: EntityID, - ) -> CommandFire: - std_message = super().from_bits( - bit_array, is_wireless_message, sender_entity_id - ) - raw_command_executor_agent_entity_id = read_with_exist_flag( - bit_array, cls.SIZE_FIRE_BRIGADE_ENTITY_ID - ) - command_executor_agent_id = ( - EntityID(raw_command_executor_agent_entity_id) - if raw_command_executor_agent_entity_id is not None - else None - ) - raw_command_target_entity_id = read_with_exist_flag( - bit_array, cls.SIZE_TARGET_ENTITY_ID - ) - command_target_id = ( - EntityID(raw_command_target_entity_id) - if raw_command_target_entity_id is not None - else None - ) - execute_action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) - return cls( - is_wireless_message, - command_executor_agent_id or EntityID(-1), - sender_entity_id, - execute_action if execute_action is not None else -1, - std_message.get_priority(), - command_target_id, - ) - - def __hash__(self) -> int: - h = super().__hash__() - return hash( - ( - h, - self._command_executor_agent_entity_id, - self._command_target_entity_id, - self._execute_action, - ) - ) - - def __str__(self) -> str: - return f"CommandFire(executor={self._command_executor_agent_entity_id}, target={self._command_target_entity_id}, action={self._execute_action})" + ACTION_REST: int = 0 + ACTION_MOVE: int = 1 + ACTION_EXTINGUISH: int = 2 + ACTION_REFILL: int = 3 + ACTION_RESCUE: int = 4 + ACTION_AUTONOMY: int = 5 + + SIZE_FIRE_BRIGADE_ENTITY_ID: int = 32 + SIZE_TARGET_ENTITY_ID: int = 32 + SIZE_ACTION: int = 4 + + def __init__( + self, + is_wireless_message: bool, + command_executor_agent_entity_id: EntityID, + sender_entity_id: EntityID, + execute_action: int, + priority: StandardMessagePriority, + command_target_entity_id: Optional[EntityID] = None, + ): + super().__init__(is_wireless_message, priority, sender_entity_id) + self._command_executor_agent_entity_id: Optional[EntityID] = ( + command_executor_agent_entity_id + ) + self._command_target_entity_id: Optional[EntityID] = command_target_entity_id + self._is_bloadcast: bool = command_target_entity_id is None + self._execute_action: Optional[int] = execute_action + + def get_command_executor_agent_entity_id(self) -> Optional[EntityID]: + return self._command_executor_agent_entity_id + + def get_command_target_entity_id(self) -> Optional[EntityID]: + return self._command_target_entity_id + + def get_execute_action(self) -> Optional[int]: + return self._execute_action + + def is_broadcast(self) -> bool: + return self._is_bloadcast + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + raw_command_executor_agent_entity_id = ( + self._command_executor_agent_entity_id.get_value() + if self._command_executor_agent_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, + raw_command_executor_agent_entity_id, + self.SIZE_FIRE_BRIGADE_ENTITY_ID, + ) + raw_command_target_entity_id = ( + self._command_target_entity_id.get_value() + if self._command_target_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, raw_command_target_entity_id, self.SIZE_TARGET_ENTITY_ID + ) + write_with_exist_flag(bit_array, self._execute_action, self.SIZE_ACTION) + + return bit_array + + @classmethod + def from_bits( + cls, + bit_array: bitarray, + is_wireless_message: bool, + sender_entity_id: EntityID, + ) -> CommandFire: + std_message = super().from_bits(bit_array, is_wireless_message, sender_entity_id) + raw_command_executor_agent_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_FIRE_BRIGADE_ENTITY_ID + ) + command_executor_agent_id = ( + EntityID(raw_command_executor_agent_entity_id) + if raw_command_executor_agent_entity_id is not None + else None + ) + raw_command_target_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_TARGET_ENTITY_ID + ) + command_target_id = ( + EntityID(raw_command_target_entity_id) + if raw_command_target_entity_id is not None + else None + ) + execute_action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) + return cls( + is_wireless_message, + command_executor_agent_id or EntityID(-1), + sender_entity_id, + execute_action if execute_action is not None else -1, + std_message.get_priority(), + command_target_id, + ) + + def __hash__(self) -> int: + h = super().__hash__() + return hash( + ( + h, + self._command_executor_agent_entity_id, + self._command_target_entity_id, + self._execute_action, + ) + ) + + def __str__(self) -> str: + return f"CommandFire(executor={self._command_executor_agent_entity_id}, target={self._command_target_entity_id}, action={self._execute_action})" diff --git a/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py index c57f2fb..2c70726 100644 --- a/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py +++ b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_police.py @@ -6,129 +6,127 @@ from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( - read_with_exist_flag, - write_with_exist_flag, + read_with_exist_flag, + write_with_exist_flag, ) class CommandPolice(StandardMessage): - ACTION_REST: int = 0 - ACTION_MOVE: int = 1 - ACTION_CLEAR: int = 2 - ACTION_AUTONOMY: int = 3 - - SIZE_POLICE_FORCE_ENTITY_ID: int = 32 - SIZE_TARGET_ENTITY_ID: int = 32 - SIZE_ACTION: int = 4 - - def __init__( - self, - is_wireless_message: bool, - command_executor_agent_entity_id: EntityID, - sender_entity_id: EntityID, - execute_action: int, - priority: StandardMessagePriority, - command_target_entity_id: Optional[EntityID] = None, - ): - super().__init__(is_wireless_message, priority, sender_entity_id) - self._command_executor_agent_entity_id: Optional[EntityID] = ( - command_executor_agent_entity_id - ) - self._command_target_entity_id: Optional[EntityID] = command_target_entity_id - self._is_bloadcast: bool = command_target_entity_id is None - self._execute_action: Optional[int] = execute_action - - def get_command_executor_agent_entity_id(self) -> Optional[EntityID]: - return self._command_executor_agent_entity_id - - def get_command_target_entity_id(self) -> Optional[EntityID]: - return self._command_target_entity_id - - def get_execute_action(self) -> Optional[int]: - return self._execute_action - - def is_broadcast(self) -> bool: - return self._is_bloadcast - - def get_bit_size(self) -> int: - return self.to_bits().__len__() - - def to_bits(self) -> bitarray: - bit_array = super().to_bits() - raw_command_executor_agent_entity_id = ( - self._command_executor_agent_entity_id.get_value() - if self._command_executor_agent_entity_id is not None - else None - ) - write_with_exist_flag( - bit_array, - raw_command_executor_agent_entity_id, - self.SIZE_POLICE_FORCE_ENTITY_ID, - ) - raw_command_target_entity_id = ( - self._command_target_entity_id.get_value() - if self._command_target_entity_id is not None - else None - ) - write_with_exist_flag( - bit_array, raw_command_target_entity_id, self.SIZE_TARGET_ENTITY_ID - ) - write_with_exist_flag(bit_array, self._execute_action, self.SIZE_ACTION) - - return bit_array - - @classmethod - def from_bits( - cls, - bit_array: bitarray, - is_wireless_message: bool, - sender_entity_id: EntityID, - ) -> CommandPolice: - std_message = super().from_bits( - bit_array, is_wireless_message, sender_entity_id - ) - raw_command_executor_agent_entity_id = read_with_exist_flag( - bit_array, cls.SIZE_POLICE_FORCE_ENTITY_ID - ) - command_executor_agent_id = ( - EntityID(raw_command_executor_agent_entity_id) - if raw_command_executor_agent_entity_id is not None - else None - ) - raw_command_target_entity_id = read_with_exist_flag( - bit_array, cls.SIZE_TARGET_ENTITY_ID - ) - command_target_id = ( - EntityID(raw_command_target_entity_id) - if raw_command_target_entity_id is not None - else None - ) - execute_action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) - return cls( - is_wireless_message, - command_executor_agent_id or EntityID(-1), - sender_entity_id, - execute_action if execute_action is not None else -1, - std_message.get_priority(), - command_target_id, - ) - - def __hash__(self) -> int: - h = super().__hash__() - return hash( - ( - h, - self._command_executor_agent_entity_id, - self._command_target_entity_id, - self._execute_action, - ) - ) - - def __str__(self) -> str: - return f"CommandPolice(executor={self._command_executor_agent_entity_id}, target={self._command_target_entity_id}, action={self._execute_action})" + ACTION_REST: int = 0 + ACTION_MOVE: int = 1 + ACTION_CLEAR: int = 2 + ACTION_AUTONOMY: int = 3 + + SIZE_POLICE_FORCE_ENTITY_ID: int = 32 + SIZE_TARGET_ENTITY_ID: int = 32 + SIZE_ACTION: int = 4 + + def __init__( + self, + is_wireless_message: bool, + command_executor_agent_entity_id: EntityID, + sender_entity_id: EntityID, + execute_action: int, + priority: StandardMessagePriority, + command_target_entity_id: Optional[EntityID] = None, + ): + super().__init__(is_wireless_message, priority, sender_entity_id) + self._command_executor_agent_entity_id: Optional[EntityID] = ( + command_executor_agent_entity_id + ) + self._command_target_entity_id: Optional[EntityID] = command_target_entity_id + self._is_bloadcast: bool = command_target_entity_id is None + self._execute_action: Optional[int] = execute_action + + def get_command_executor_agent_entity_id(self) -> Optional[EntityID]: + return self._command_executor_agent_entity_id + + def get_command_target_entity_id(self) -> Optional[EntityID]: + return self._command_target_entity_id + + def get_execute_action(self) -> Optional[int]: + return self._execute_action + + def is_broadcast(self) -> bool: + return self._is_bloadcast + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + raw_command_executor_agent_entity_id = ( + self._command_executor_agent_entity_id.get_value() + if self._command_executor_agent_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, + raw_command_executor_agent_entity_id, + self.SIZE_POLICE_FORCE_ENTITY_ID, + ) + raw_command_target_entity_id = ( + self._command_target_entity_id.get_value() + if self._command_target_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, raw_command_target_entity_id, self.SIZE_TARGET_ENTITY_ID + ) + write_with_exist_flag(bit_array, self._execute_action, self.SIZE_ACTION) + + return bit_array + + @classmethod + def from_bits( + cls, + bit_array: bitarray, + is_wireless_message: bool, + sender_entity_id: EntityID, + ) -> CommandPolice: + std_message = super().from_bits(bit_array, is_wireless_message, sender_entity_id) + raw_command_executor_agent_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_POLICE_FORCE_ENTITY_ID + ) + command_executor_agent_id = ( + EntityID(raw_command_executor_agent_entity_id) + if raw_command_executor_agent_entity_id is not None + else None + ) + raw_command_target_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_TARGET_ENTITY_ID + ) + command_target_id = ( + EntityID(raw_command_target_entity_id) + if raw_command_target_entity_id is not None + else None + ) + execute_action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) + return cls( + is_wireless_message, + command_executor_agent_id or EntityID(-1), + sender_entity_id, + execute_action if execute_action is not None else -1, + std_message.get_priority(), + command_target_id, + ) + + def __hash__(self) -> int: + h = super().__hash__() + return hash( + ( + h, + self._command_executor_agent_entity_id, + self._command_target_entity_id, + self._execute_action, + ) + ) + + def __str__(self) -> str: + return f"CommandPolice(executor={self._command_executor_agent_entity_id}, target={self._command_target_entity_id}, action={self._execute_action})" diff --git a/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py index 4d82478..9c2ae38 100644 --- a/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py +++ b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/command_scout.py @@ -6,124 +6,122 @@ from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( - read_with_exist_flag, - write_with_exist_flag, + read_with_exist_flag, + write_with_exist_flag, ) class CommandScout(StandardMessage): - SIZE_AGENT_ENTITY_ID: int = 32 - SIZE_TARGET_ENTITY_ID: int = 32 - SIZE_SCOUT_RANGE: int = 32 - - def __init__( - self, - is_wireless_message: bool, - command_executor_agent_entity_id: EntityID, - sender_entity_id: EntityID, - scout_range: int, - priority: StandardMessagePriority, - command_target_entity_id: Optional[EntityID] = None, - ): - super().__init__(is_wireless_message, priority, sender_entity_id) - self._command_executor_agent_entity_id: Optional[EntityID] = ( - command_executor_agent_entity_id - ) - self._command_target_entity_id: Optional[EntityID] = command_target_entity_id - self._is_bloadcast: bool = command_target_entity_id is None - self._scout_range: Optional[int] = scout_range - - def get_command_executor_agent_entity_id(self) -> Optional[EntityID]: - return self._command_executor_agent_entity_id - - def get_command_target_entity_id(self) -> Optional[EntityID]: - return self._command_target_entity_id - - def get_scout_range(self) -> Optional[int]: - return self._scout_range - - def is_broadcast(self) -> bool: - return self._is_bloadcast - - def get_bit_size(self) -> int: - return self.to_bits().__len__() - - def to_bits(self) -> bitarray: - bit_array = super().to_bits() - raw_command_executor_agent_entity_id = ( - self._command_executor_agent_entity_id.get_value() - if self._command_executor_agent_entity_id is not None - else None - ) - write_with_exist_flag( - bit_array, - raw_command_executor_agent_entity_id, - self.SIZE_AGENT_ENTITY_ID, - ) - raw_command_target_entity_id = ( - self._command_target_entity_id.get_value() - if self._command_target_entity_id is not None - else None - ) - write_with_exist_flag( - bit_array, raw_command_target_entity_id, self.SIZE_TARGET_ENTITY_ID - ) - write_with_exist_flag(bit_array, self._scout_range, self.SIZE_SCOUT_RANGE) - - return bit_array - - @classmethod - def from_bits( - cls, - bit_array: bitarray, - is_wireless_message: bool, - sender_entity_id: EntityID, - ) -> CommandScout: - std_message = super().from_bits( - bit_array, is_wireless_message, sender_entity_id - ) - raw_command_executor_agent_entity_id = read_with_exist_flag( - bit_array, cls.SIZE_AGENT_ENTITY_ID - ) - command_executor_agent_id = ( - EntityID(raw_command_executor_agent_entity_id) - if raw_command_executor_agent_entity_id is not None - else None - ) - raw_command_target_entity_id = read_with_exist_flag( - bit_array, cls.SIZE_TARGET_ENTITY_ID - ) - command_target_id = ( - EntityID(raw_command_target_entity_id) - if raw_command_target_entity_id is not None - else None - ) - scout_range = read_with_exist_flag(bit_array, cls.SIZE_SCOUT_RANGE) - return cls( - is_wireless_message, - command_executor_agent_id or EntityID(-1), - sender_entity_id, - scout_range if scout_range is not None else -1, - std_message.get_priority(), - command_target_id, - ) - - def __hash__(self) -> int: - h = super().__hash__() - return hash( - ( - h, - self._command_executor_agent_entity_id, - self._command_target_entity_id, - self._scout_range, - ) - ) - - def __str__(self) -> str: - return f"CommandScout(executor={self._command_executor_agent_entity_id}, target={self._command_target_entity_id}, scout_range={self._scout_range})" + SIZE_AGENT_ENTITY_ID: int = 32 + SIZE_TARGET_ENTITY_ID: int = 32 + SIZE_SCOUT_RANGE: int = 32 + + def __init__( + self, + is_wireless_message: bool, + command_executor_agent_entity_id: EntityID, + sender_entity_id: EntityID, + scout_range: int, + priority: StandardMessagePriority, + command_target_entity_id: Optional[EntityID] = None, + ): + super().__init__(is_wireless_message, priority, sender_entity_id) + self._command_executor_agent_entity_id: Optional[EntityID] = ( + command_executor_agent_entity_id + ) + self._command_target_entity_id: Optional[EntityID] = command_target_entity_id + self._is_bloadcast: bool = command_target_entity_id is None + self._scout_range: Optional[int] = scout_range + + def get_command_executor_agent_entity_id(self) -> Optional[EntityID]: + return self._command_executor_agent_entity_id + + def get_command_target_entity_id(self) -> Optional[EntityID]: + return self._command_target_entity_id + + def get_scout_range(self) -> Optional[int]: + return self._scout_range + + def is_broadcast(self) -> bool: + return self._is_bloadcast + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + raw_command_executor_agent_entity_id = ( + self._command_executor_agent_entity_id.get_value() + if self._command_executor_agent_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, + raw_command_executor_agent_entity_id, + self.SIZE_AGENT_ENTITY_ID, + ) + raw_command_target_entity_id = ( + self._command_target_entity_id.get_value() + if self._command_target_entity_id is not None + else None + ) + write_with_exist_flag( + bit_array, raw_command_target_entity_id, self.SIZE_TARGET_ENTITY_ID + ) + write_with_exist_flag(bit_array, self._scout_range, self.SIZE_SCOUT_RANGE) + + return bit_array + + @classmethod + def from_bits( + cls, + bit_array: bitarray, + is_wireless_message: bool, + sender_entity_id: EntityID, + ) -> CommandScout: + std_message = super().from_bits(bit_array, is_wireless_message, sender_entity_id) + raw_command_executor_agent_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_AGENT_ENTITY_ID + ) + command_executor_agent_id = ( + EntityID(raw_command_executor_agent_entity_id) + if raw_command_executor_agent_entity_id is not None + else None + ) + raw_command_target_entity_id = read_with_exist_flag( + bit_array, cls.SIZE_TARGET_ENTITY_ID + ) + command_target_id = ( + EntityID(raw_command_target_entity_id) + if raw_command_target_entity_id is not None + else None + ) + scout_range = read_with_exist_flag(bit_array, cls.SIZE_SCOUT_RANGE) + return cls( + is_wireless_message, + command_executor_agent_id or EntityID(-1), + sender_entity_id, + scout_range if scout_range is not None else -1, + std_message.get_priority(), + command_target_id, + ) + + def __hash__(self) -> int: + h = super().__hash__() + return hash( + ( + h, + self._command_executor_agent_entity_id, + self._command_target_entity_id, + self._scout_range, + ) + ) + + def __str__(self) -> str: + return f"CommandScout(executor={self._command_executor_agent_entity_id}, target={self._command_target_entity_id}, scout_range={self._scout_range})" diff --git a/src/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py index 99cf589..46d2283 100644 --- a/src/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py +++ b/src/adf_core_python/core/agent/communication/standard/bundle/centralized/message_report.py @@ -4,85 +4,83 @@ from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( - read_with_exist_flag, - write_with_exist_flag, + read_with_exist_flag, + write_with_exist_flag, ) class MessageReport(StandardMessage): - SIZE_DONE: int = 1 - SIZE_BLOADCAST: int = 1 + SIZE_DONE: int = 1 + SIZE_BLOADCAST: int = 1 - def __init__( - self, - is_wireless_message: bool, - is_done: bool, - is_bloadcast: bool, - sender_entity_id: EntityID, - priority: StandardMessagePriority, - ): - super().__init__(is_wireless_message, priority, sender_entity_id) - self._is_done: bool = is_done - self._is_bloadcast: bool = is_bloadcast + def __init__( + self, + is_wireless_message: bool, + is_done: bool, + is_bloadcast: bool, + sender_entity_id: EntityID, + priority: StandardMessagePriority, + ): + super().__init__(is_wireless_message, priority, sender_entity_id) + self._is_done: bool = is_done + self._is_bloadcast: bool = is_bloadcast - def is_done(self) -> bool: - return self._is_done + def is_done(self) -> bool: + return self._is_done - def is_broadcast(self) -> bool: - return self._is_bloadcast + def is_broadcast(self) -> bool: + return self._is_bloadcast - def get_bit_size(self) -> int: - return self.to_bits().__len__() + def get_bit_size(self) -> int: + return self.to_bits().__len__() - def to_bits(self) -> bitarray: - bit_array = super().to_bits() - write_with_exist_flag( - bit_array, - self._is_done, - self.SIZE_DONE, - ) - write_with_exist_flag( - bit_array, - self._is_bloadcast, - self.SIZE_BLOADCAST, - ) - return bit_array + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + write_with_exist_flag( + bit_array, + self._is_done, + self.SIZE_DONE, + ) + write_with_exist_flag( + bit_array, + self._is_bloadcast, + self.SIZE_BLOADCAST, + ) + return bit_array - @classmethod - def from_bits( - cls, - bit_array: bitarray, - is_wireless_message: bool, - sender_entity_id: EntityID, - ) -> MessageReport: - std_message = super().from_bits( - bit_array, is_wireless_message, sender_entity_id - ) - is_done = read_with_exist_flag(bit_array, cls.SIZE_DONE) == 1 - is_bloadcast = read_with_exist_flag(bit_array, cls.SIZE_BLOADCAST) == 1 - return cls( - is_wireless_message, - is_done, - is_bloadcast, - std_message.get_sender_entity_id(), - std_message.get_priority(), - ) + @classmethod + def from_bits( + cls, + bit_array: bitarray, + is_wireless_message: bool, + sender_entity_id: EntityID, + ) -> MessageReport: + std_message = super().from_bits(bit_array, is_wireless_message, sender_entity_id) + is_done = read_with_exist_flag(bit_array, cls.SIZE_DONE) == 1 + is_bloadcast = read_with_exist_flag(bit_array, cls.SIZE_BLOADCAST) == 1 + return cls( + is_wireless_message, + is_done, + is_bloadcast, + std_message.get_sender_entity_id(), + std_message.get_priority(), + ) - def __hash__(self) -> int: - h = super().__hash__() - return hash( - ( - h, - self._is_done, - self._is_bloadcast, - ) - ) + def __hash__(self) -> int: + h = super().__hash__() + return hash( + ( + h, + self._is_done, + self._is_bloadcast, + ) + ) - def __str__(self) -> str: - return f"CommandReport(done={self._is_done}, broadcast={self._is_bloadcast})" + def __str__(self) -> str: + return f"CommandReport(done={self._is_done}, broadcast={self._is_bloadcast})" diff --git a/src/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py index 2fbe0ea..b649c83 100644 --- a/src/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py +++ b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_ambulance_team.py @@ -6,182 +6,176 @@ from rcrscore.entities import AmbulanceTeam, EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( - read_with_exist_flag, - write_with_exist_flag, + read_with_exist_flag, + write_with_exist_flag, ) class MessageAmbulanceTeam(StandardMessage): - ACTION_REST: int = 0 - ACTION_MOVE: int = 1 - ACTION_RESCUE: int = 2 - ACTION_LOAD: int = 3 - ACTION_UNLOAD: int = 4 - - SIZE_AMBULANCE_TEAM_ENTITY_ID: int = 32 - SIZE_AMBULANCE_TEAM_HP: int = 14 - SIZE_AMBULANCE_TEAM_BURIEDNESS: int = 13 - SIZE_AMBULANCE_TEAM_DAMAGE: int = 14 - SIZE_AMBULANCE_TEAM_POSITION: int = 32 - SIZE_TARGET_ENTITY_ID: int = 32 - SIZE_ACTION: int = 4 - - def __init__( - self, - is_wireless_message: bool, - ambulance_team: AmbulanceTeam, - action: int, - target_entity_id: EntityID, - priority: StandardMessagePriority, - sender_entity_id: EntityID, - ttl: Optional[int] = None, - ): - super().__init__(is_wireless_message, priority, sender_entity_id, ttl) - self._ambulance_team_entity_id: Optional[EntityID] = ( - ambulance_team.get_entity_id() - ) - self._ambulance_team_hp: Optional[int] = ambulance_team.get_hp() or None - self._ambulance_team_buriedness: Optional[int] = ( - ambulance_team.get_buriedness() or None - ) - self._ambulance_team_damage: Optional[int] = ambulance_team.get_damage() or None - self._ambulance_team_position: Optional[EntityID] = ( - ambulance_team.get_position() or None - ) - self._target_entity_id: Optional[EntityID] = target_entity_id - self._action: Optional[int] = action - - def get_ambulance_team_entity_id(self) -> Optional[EntityID]: - return self._ambulance_team_entity_id - - def get_ambulance_team_hp(self) -> Optional[int]: - return self._ambulance_team_hp - - def get_ambulance_team_buriedness(self) -> Optional[int]: - return self._ambulance_team_buriedness - - def get_ambulance_team_damage(self) -> Optional[int]: - return self._ambulance_team_damage - - def get_ambulance_team_position(self) -> Optional[EntityID]: - return self._ambulance_team_position - - def get_target_entity_id(self) -> Optional[EntityID]: - return self._target_entity_id - - def get_action(self) -> Optional[int]: - return self._action - - def get_bit_size(self) -> int: - return self.to_bits().__len__() - - def to_bits(self) -> bitarray: - bit_array = super().to_bits() - write_with_exist_flag( - bit_array, - self._ambulance_team_entity_id.get_value() - if self._ambulance_team_entity_id - else None, - self.SIZE_AMBULANCE_TEAM_ENTITY_ID, - ) - write_with_exist_flag( - bit_array, self._ambulance_team_hp, self.SIZE_AMBULANCE_TEAM_HP - ) - write_with_exist_flag( - bit_array, - self._ambulance_team_buriedness, - self.SIZE_AMBULANCE_TEAM_BURIEDNESS, - ) - write_with_exist_flag( - bit_array, self._ambulance_team_damage, self.SIZE_AMBULANCE_TEAM_DAMAGE - ) - write_with_exist_flag( - bit_array, - self._ambulance_team_position.get_value() - if self._ambulance_team_position - else None, - self.SIZE_AMBULANCE_TEAM_POSITION, - ) - write_with_exist_flag( - bit_array, - self._target_entity_id.get_value() if self._target_entity_id else None, - self.SIZE_TARGET_ENTITY_ID, - ) - write_with_exist_flag(bit_array, self._action, self.SIZE_ACTION) - return bit_array - - @classmethod - def from_bits( - cls, - bit_array: bitarray, - is_wireless_message: bool, - sender_entity_id: EntityID, - ) -> MessageAmbulanceTeam: - std_message = super().from_bits( - bit_array, is_wireless_message, sender_entity_id - ) - ambulance_team_id = read_with_exist_flag( - bit_array, cls.SIZE_AMBULANCE_TEAM_ENTITY_ID - ) - ambulance_team_hp = read_with_exist_flag(bit_array, cls.SIZE_AMBULANCE_TEAM_HP) - ambulance_team_buriedness = read_with_exist_flag( - bit_array, cls.SIZE_AMBULANCE_TEAM_BURIEDNESS - ) - ambulance_team_damage = read_with_exist_flag( - bit_array, cls.SIZE_AMBULANCE_TEAM_DAMAGE - ) - raw_ambulance_team_position = read_with_exist_flag( - bit_array, cls.SIZE_AMBULANCE_TEAM_POSITION - ) - ambulance_team_position = ( - EntityID(raw_ambulance_team_position) - if raw_ambulance_team_position is not None - else None - ) - raw_target_entity_id = read_with_exist_flag( - bit_array, cls.SIZE_TARGET_ENTITY_ID - ) - target_entity_id = ( - EntityID(raw_target_entity_id) - if raw_target_entity_id is not None - else EntityID(-1) - ) - action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) - ambulance_team = AmbulanceTeam(ambulance_team_id or -1) - ambulance_team.set_hp(ambulance_team_hp) - ambulance_team.set_buriedness(ambulance_team_buriedness) - ambulance_team.set_damage(ambulance_team_damage) - ambulance_team.set_position(ambulance_team_position) - return MessageAmbulanceTeam( - False, - ambulance_team, - action if action is not None else -1, - target_entity_id, - StandardMessagePriority.NORMAL, - sender_entity_id, - std_message.get_ttl(), - ) - - def __hash__(self) -> int: - h = super().__hash__() - return hash( - ( - h, - self._ambulance_team_entity_id, - self._ambulance_team_hp, - self._ambulance_team_buriedness, - self._ambulance_team_damage, - self._ambulance_team_position, - self._target_entity_id, - self._action, - ) - ) - - def __str__(self) -> str: - return f"MessageAmbulanceTeam(ambulance_team_entity_id={self._ambulance_team_entity_id}, ambulance_team_hp={self._ambulance_team_hp}, ambulance_team_buriedness={self._ambulance_team_buriedness}, ambulance_team_damage={self._ambulance_team_damage}, ambulance_team_position={self._ambulance_team_position}, target_entity_id={self._target_entity_id}, action={self._action}, ttl={self._ttl})" + ACTION_REST: int = 0 + ACTION_MOVE: int = 1 + ACTION_RESCUE: int = 2 + ACTION_LOAD: int = 3 + ACTION_UNLOAD: int = 4 + + SIZE_AMBULANCE_TEAM_ENTITY_ID: int = 32 + SIZE_AMBULANCE_TEAM_HP: int = 14 + SIZE_AMBULANCE_TEAM_BURIEDNESS: int = 13 + SIZE_AMBULANCE_TEAM_DAMAGE: int = 14 + SIZE_AMBULANCE_TEAM_POSITION: int = 32 + SIZE_TARGET_ENTITY_ID: int = 32 + SIZE_ACTION: int = 4 + + def __init__( + self, + is_wireless_message: bool, + ambulance_team: AmbulanceTeam, + action: int, + target_entity_id: EntityID, + priority: StandardMessagePriority, + sender_entity_id: EntityID, + ttl: Optional[int] = None, + ): + super().__init__(is_wireless_message, priority, sender_entity_id, ttl) + self._ambulance_team_entity_id: Optional[EntityID] = ambulance_team.get_entity_id() + self._ambulance_team_hp: Optional[int] = ambulance_team.get_hp() or None + self._ambulance_team_buriedness: Optional[int] = ( + ambulance_team.get_buriedness() or None + ) + self._ambulance_team_damage: Optional[int] = ambulance_team.get_damage() or None + self._ambulance_team_position: Optional[EntityID] = ( + ambulance_team.get_position() or None + ) + self._target_entity_id: Optional[EntityID] = target_entity_id + self._action: Optional[int] = action + + def get_ambulance_team_entity_id(self) -> Optional[EntityID]: + return self._ambulance_team_entity_id + + def get_ambulance_team_hp(self) -> Optional[int]: + return self._ambulance_team_hp + + def get_ambulance_team_buriedness(self) -> Optional[int]: + return self._ambulance_team_buriedness + + def get_ambulance_team_damage(self) -> Optional[int]: + return self._ambulance_team_damage + + def get_ambulance_team_position(self) -> Optional[EntityID]: + return self._ambulance_team_position + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._target_entity_id + + def get_action(self) -> Optional[int]: + return self._action + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + write_with_exist_flag( + bit_array, + self._ambulance_team_entity_id.get_value() + if self._ambulance_team_entity_id + else None, + self.SIZE_AMBULANCE_TEAM_ENTITY_ID, + ) + write_with_exist_flag( + bit_array, self._ambulance_team_hp, self.SIZE_AMBULANCE_TEAM_HP + ) + write_with_exist_flag( + bit_array, + self._ambulance_team_buriedness, + self.SIZE_AMBULANCE_TEAM_BURIEDNESS, + ) + write_with_exist_flag( + bit_array, self._ambulance_team_damage, self.SIZE_AMBULANCE_TEAM_DAMAGE + ) + write_with_exist_flag( + bit_array, + self._ambulance_team_position.get_value() + if self._ambulance_team_position + else None, + self.SIZE_AMBULANCE_TEAM_POSITION, + ) + write_with_exist_flag( + bit_array, + self._target_entity_id.get_value() if self._target_entity_id else None, + self.SIZE_TARGET_ENTITY_ID, + ) + write_with_exist_flag(bit_array, self._action, self.SIZE_ACTION) + return bit_array + + @classmethod + def from_bits( + cls, + bit_array: bitarray, + is_wireless_message: bool, + sender_entity_id: EntityID, + ) -> MessageAmbulanceTeam: + std_message = super().from_bits(bit_array, is_wireless_message, sender_entity_id) + ambulance_team_id = read_with_exist_flag( + bit_array, cls.SIZE_AMBULANCE_TEAM_ENTITY_ID + ) + ambulance_team_hp = read_with_exist_flag(bit_array, cls.SIZE_AMBULANCE_TEAM_HP) + ambulance_team_buriedness = read_with_exist_flag( + bit_array, cls.SIZE_AMBULANCE_TEAM_BURIEDNESS + ) + ambulance_team_damage = read_with_exist_flag( + bit_array, cls.SIZE_AMBULANCE_TEAM_DAMAGE + ) + raw_ambulance_team_position = read_with_exist_flag( + bit_array, cls.SIZE_AMBULANCE_TEAM_POSITION + ) + ambulance_team_position = ( + EntityID(raw_ambulance_team_position) + if raw_ambulance_team_position is not None + else None + ) + raw_target_entity_id = read_with_exist_flag(bit_array, cls.SIZE_TARGET_ENTITY_ID) + target_entity_id = ( + EntityID(raw_target_entity_id) + if raw_target_entity_id is not None + else EntityID(-1) + ) + action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) + ambulance_team = AmbulanceTeam(ambulance_team_id or -1) + ambulance_team.set_hp(ambulance_team_hp) + ambulance_team.set_buriedness(ambulance_team_buriedness) + ambulance_team.set_damage(ambulance_team_damage) + ambulance_team.set_position(ambulance_team_position) + return MessageAmbulanceTeam( + False, + ambulance_team, + action if action is not None else -1, + target_entity_id, + StandardMessagePriority.NORMAL, + sender_entity_id, + std_message.get_ttl(), + ) + + def __hash__(self) -> int: + h = super().__hash__() + return hash( + ( + h, + self._ambulance_team_entity_id, + self._ambulance_team_hp, + self._ambulance_team_buriedness, + self._ambulance_team_damage, + self._ambulance_team_position, + self._target_entity_id, + self._action, + ) + ) + + def __str__(self) -> str: + return f"MessageAmbulanceTeam(ambulance_team_entity_id={self._ambulance_team_entity_id}, ambulance_team_hp={self._ambulance_team_hp}, ambulance_team_buriedness={self._ambulance_team_buriedness}, ambulance_team_damage={self._ambulance_team_damage}, ambulance_team_position={self._ambulance_team_position}, target_entity_id={self._target_entity_id}, action={self._action}, ttl={self._ttl})" diff --git a/src/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py index 586f836..8a909a5 100644 --- a/src/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py +++ b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_building.py @@ -7,116 +7,110 @@ from rcrscore.entities.building import Building from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( - read_with_exist_flag, - write_with_exist_flag, + read_with_exist_flag, + write_with_exist_flag, ) class MessageBuilding(StandardMessage): - SIZE_BUILDING_ENTITY_ID: int = 32 - SIZE_BUILDING_BROKENNESS: int = 32 - SIZE_BUILDING_FIREYNESS: int = 32 - SIZE_BUILDING_TEMPERATURE: int = 32 + SIZE_BUILDING_ENTITY_ID: int = 32 + SIZE_BUILDING_BROKENNESS: int = 32 + SIZE_BUILDING_FIREYNESS: int = 32 + SIZE_BUILDING_TEMPERATURE: int = 32 - def __init__( - self, - is_wireless_message: bool, - building: Building, - priority: StandardMessagePriority, - sender_entity_id: EntityID, - ttl: Optional[int] = None, - ): - super().__init__(is_wireless_message, priority, sender_entity_id, ttl) - self._building_entity_id: Optional[EntityID] = building.get_entity_id() - self._building_brokenness: Optional[int] = building.get_brokenness() or None - self._building_fireyness: Optional[int] = building.get_fieryness() or None - self._building_temperature: Optional[int] = building.get_temperature() or None + def __init__( + self, + is_wireless_message: bool, + building: Building, + priority: StandardMessagePriority, + sender_entity_id: EntityID, + ttl: Optional[int] = None, + ): + super().__init__(is_wireless_message, priority, sender_entity_id, ttl) + self._building_entity_id: Optional[EntityID] = building.get_entity_id() + self._building_brokenness: Optional[int] = building.get_brokenness() or None + self._building_fireyness: Optional[int] = building.get_fieryness() or None + self._building_temperature: Optional[int] = building.get_temperature() or None - def get_building_entity_id(self) -> Optional[EntityID]: - return self._building_entity_id + def get_building_entity_id(self) -> Optional[EntityID]: + return self._building_entity_id - def get_building_brokenness(self) -> Optional[int]: - return self._building_brokenness + def get_building_brokenness(self) -> Optional[int]: + return self._building_brokenness - def get_building_fireyness(self) -> Optional[int]: - return self._building_fireyness + def get_building_fireyness(self) -> Optional[int]: + return self._building_fireyness - def get_building_temperature(self) -> Optional[int]: - return self._building_temperature + def get_building_temperature(self) -> Optional[int]: + return self._building_temperature - def get_bit_size(self) -> int: - return self.to_bits().__len__() + def get_bit_size(self) -> int: + return self.to_bits().__len__() - def to_bits(self) -> bitarray: - bit_array = super().to_bits() - write_with_exist_flag( - bit_array, - self._building_entity_id.get_value() if self._building_entity_id else None, - self.SIZE_BUILDING_ENTITY_ID, - ) - write_with_exist_flag( - bit_array, - self._building_brokenness, - self.SIZE_BUILDING_BROKENNESS, - ) - write_with_exist_flag( - bit_array, - self._building_fireyness, - self.SIZE_BUILDING_FIREYNESS, - ) - write_with_exist_flag( - bit_array, - self._building_temperature, - self.SIZE_BUILDING_TEMPERATURE, - ) - return bit_array + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + write_with_exist_flag( + bit_array, + self._building_entity_id.get_value() if self._building_entity_id else None, + self.SIZE_BUILDING_ENTITY_ID, + ) + write_with_exist_flag( + bit_array, + self._building_brokenness, + self.SIZE_BUILDING_BROKENNESS, + ) + write_with_exist_flag( + bit_array, + self._building_fireyness, + self.SIZE_BUILDING_FIREYNESS, + ) + write_with_exist_flag( + bit_array, + self._building_temperature, + self.SIZE_BUILDING_TEMPERATURE, + ) + return bit_array - @classmethod - def from_bits( - cls, bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID - ) -> MessageBuilding: - std_message = super().from_bits( - bit_array, is_wireless_message, sender_entity_id - ) - building_id = read_with_exist_flag(bit_array, cls.SIZE_BUILDING_ENTITY_ID) - building_brokenness = read_with_exist_flag( - bit_array, cls.SIZE_BUILDING_BROKENNESS - ) - building_fireyness = read_with_exist_flag( - bit_array, cls.SIZE_BUILDING_FIREYNESS - ) - building_temperature = read_with_exist_flag( - bit_array, cls.SIZE_BUILDING_TEMPERATURE - ) - building = Building(building_id or -1) - building.set_brokenness(building_brokenness) - building.set_fieryness(building_fireyness) - building.set_temperature(building_temperature) - return MessageBuilding( - False, - building, - StandardMessagePriority.NORMAL, - sender_entity_id, - std_message.get_ttl(), - ) + @classmethod + def from_bits( + cls, bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID + ) -> MessageBuilding: + std_message = super().from_bits(bit_array, is_wireless_message, sender_entity_id) + building_id = read_with_exist_flag(bit_array, cls.SIZE_BUILDING_ENTITY_ID) + building_brokenness = read_with_exist_flag(bit_array, cls.SIZE_BUILDING_BROKENNESS) + building_fireyness = read_with_exist_flag(bit_array, cls.SIZE_BUILDING_FIREYNESS) + building_temperature = read_with_exist_flag( + bit_array, cls.SIZE_BUILDING_TEMPERATURE + ) + building = Building(building_id or -1) + building.set_brokenness(building_brokenness) + building.set_fieryness(building_fireyness) + building.set_temperature(building_temperature) + return MessageBuilding( + False, + building, + StandardMessagePriority.NORMAL, + sender_entity_id, + std_message.get_ttl(), + ) - def __hash__(self) -> int: - h = super().__hash__() - return hash( - ( - h, - self._building_entity_id, - self._building_brokenness, - self._building_fireyness, - self._building_temperature, - ) - ) + def __hash__(self) -> int: + h = super().__hash__() + return hash( + ( + h, + self._building_entity_id, + self._building_brokenness, + self._building_fireyness, + self._building_temperature, + ) + ) - def __str__(self) -> str: - return f"MessageBuilding(building_entity_id={self._building_entity_id}, building_brokenness={self._building_brokenness}, building_fireyness={self._building_fireyness}, building_temperature={self._building_temperature})" + def __str__(self) -> str: + return f"MessageBuilding(building_entity_id={self._building_entity_id}, building_brokenness={self._building_brokenness}, building_fireyness={self._building_fireyness}, building_temperature={self._building_temperature})" diff --git a/src/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py index 79b4ae7..c1918c5 100644 --- a/src/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py +++ b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_civilian.py @@ -7,130 +7,124 @@ from rcrscore.entities.civilian import Civilian from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( - read_with_exist_flag, - write_with_exist_flag, + read_with_exist_flag, + write_with_exist_flag, ) class MessageCivilian(StandardMessage): - SIZE_CIVILIAN_ENTITY_ID: int = 32 - SIZE_CIVILIAN_HP: int = 14 - SIZE_CIVILIAN_BURIEDNESS: int = 13 - SIZE_CIVILIAN_DAMAGE: int = 14 - SIZE_CIVILIAN_POSITION: int = 32 - - def __init__( - self, - is_wireless_message: bool, - civilian: Civilian, - priority: StandardMessagePriority, - sender_entity_id: EntityID, - ttl: Optional[int] = None, - ): - super().__init__(is_wireless_message, priority, sender_entity_id, ttl) - self._civilian_entity_id: Optional[EntityID] = civilian.get_entity_id() - self._civilian_hp: Optional[int] = civilian.get_hp() or None - self._civilian_buriedness: Optional[int] = civilian.get_buriedness() or None - self._civilian_damage: Optional[int] = civilian.get_damage() or None - self._civilian_position: Optional[EntityID] = civilian.get_position() or None - - def get_civilian_entity_id(self) -> Optional[EntityID]: - return self._civilian_entity_id - - def get_civilian_hp(self) -> Optional[int]: - return self._civilian_hp - - def get_civilian_buriedness(self) -> Optional[int]: - return self._civilian_buriedness - - def get_civilian_damage(self) -> Optional[int]: - return self._civilian_damage - - def get_civilian_position(self) -> Optional[EntityID]: - return self._civilian_position - - def get_bit_size(self) -> int: - return self.to_bits().__len__() - - def to_bits(self) -> bitarray: - bit_array = super().to_bits() - write_with_exist_flag( - bit_array, - self._civilian_entity_id.get_value() if self._civilian_entity_id else None, - self.SIZE_CIVILIAN_ENTITY_ID, - ) - write_with_exist_flag( - bit_array, - self._civilian_hp, - self.SIZE_CIVILIAN_HP, - ) - write_with_exist_flag( - bit_array, - self._civilian_buriedness, - self.SIZE_CIVILIAN_BURIEDNESS, - ) - write_with_exist_flag( - bit_array, - self._civilian_damage, - self.SIZE_CIVILIAN_DAMAGE, - ) - write_with_exist_flag( - bit_array, - self._civilian_position.get_value() if self._civilian_position else None, - self.SIZE_CIVILIAN_POSITION, - ) - return bit_array - - @classmethod - def from_bits( - cls, bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID - ) -> MessageCivilian: - std_message = super().from_bits( - bit_array, is_wireless_message, sender_entity_id - ) - civilian_id = read_with_exist_flag(bit_array, cls.SIZE_CIVILIAN_ENTITY_ID) - civilian_hp = read_with_exist_flag(bit_array, cls.SIZE_CIVILIAN_HP) - civilian_buriedness = read_with_exist_flag( - bit_array, cls.SIZE_CIVILIAN_BURIEDNESS - ) - civilian_damage = read_with_exist_flag(bit_array, cls.SIZE_CIVILIAN_DAMAGE) - raw_civilian_position = read_with_exist_flag( - bit_array, cls.SIZE_CIVILIAN_POSITION - ) - civilian_position = ( - EntityID(raw_civilian_position) if raw_civilian_position else None - ) - civilian = Civilian(civilian_id or -1) - civilian.set_hp(civilian_hp) - civilian.set_buriedness(civilian_buriedness) - civilian.set_damage(civilian_damage) - civilian.set_position(civilian_position) - return MessageCivilian( - False, - civilian, - StandardMessagePriority.NORMAL, - sender_entity_id, - std_message.get_ttl(), - ) - - def __hash__(self) -> int: - h = super().__hash__() - return hash( - ( - h, - self._civilian_entity_id, - self._civilian_hp, - self._civilian_buriedness, - self._civilian_damage, - self._civilian_position, - ) - ) - - def __str__(self) -> str: - return f"MessageCivilian(civilian_entity_id={self._civilian_entity_id}, civilian_hp={self._civilian_hp}, civilian_buriedness={self._civilian_buriedness}, civilian_damage={self._civilian_damage}, civilian_position={self._civilian_position})" + SIZE_CIVILIAN_ENTITY_ID: int = 32 + SIZE_CIVILIAN_HP: int = 14 + SIZE_CIVILIAN_BURIEDNESS: int = 13 + SIZE_CIVILIAN_DAMAGE: int = 14 + SIZE_CIVILIAN_POSITION: int = 32 + + def __init__( + self, + is_wireless_message: bool, + civilian: Civilian, + priority: StandardMessagePriority, + sender_entity_id: EntityID, + ttl: Optional[int] = None, + ): + super().__init__(is_wireless_message, priority, sender_entity_id, ttl) + self._civilian_entity_id: Optional[EntityID] = civilian.get_entity_id() + self._civilian_hp: Optional[int] = civilian.get_hp() or None + self._civilian_buriedness: Optional[int] = civilian.get_buriedness() or None + self._civilian_damage: Optional[int] = civilian.get_damage() or None + self._civilian_position: Optional[EntityID] = civilian.get_position() or None + + def get_civilian_entity_id(self) -> Optional[EntityID]: + return self._civilian_entity_id + + def get_civilian_hp(self) -> Optional[int]: + return self._civilian_hp + + def get_civilian_buriedness(self) -> Optional[int]: + return self._civilian_buriedness + + def get_civilian_damage(self) -> Optional[int]: + return self._civilian_damage + + def get_civilian_position(self) -> Optional[EntityID]: + return self._civilian_position + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + write_with_exist_flag( + bit_array, + self._civilian_entity_id.get_value() if self._civilian_entity_id else None, + self.SIZE_CIVILIAN_ENTITY_ID, + ) + write_with_exist_flag( + bit_array, + self._civilian_hp, + self.SIZE_CIVILIAN_HP, + ) + write_with_exist_flag( + bit_array, + self._civilian_buriedness, + self.SIZE_CIVILIAN_BURIEDNESS, + ) + write_with_exist_flag( + bit_array, + self._civilian_damage, + self.SIZE_CIVILIAN_DAMAGE, + ) + write_with_exist_flag( + bit_array, + self._civilian_position.get_value() if self._civilian_position else None, + self.SIZE_CIVILIAN_POSITION, + ) + return bit_array + + @classmethod + def from_bits( + cls, bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID + ) -> MessageCivilian: + std_message = super().from_bits(bit_array, is_wireless_message, sender_entity_id) + civilian_id = read_with_exist_flag(bit_array, cls.SIZE_CIVILIAN_ENTITY_ID) + civilian_hp = read_with_exist_flag(bit_array, cls.SIZE_CIVILIAN_HP) + civilian_buriedness = read_with_exist_flag(bit_array, cls.SIZE_CIVILIAN_BURIEDNESS) + civilian_damage = read_with_exist_flag(bit_array, cls.SIZE_CIVILIAN_DAMAGE) + raw_civilian_position = read_with_exist_flag(bit_array, cls.SIZE_CIVILIAN_POSITION) + civilian_position = ( + EntityID(raw_civilian_position) if raw_civilian_position else None + ) + civilian = Civilian(civilian_id or -1) + civilian.set_hp(civilian_hp) + civilian.set_buriedness(civilian_buriedness) + civilian.set_damage(civilian_damage) + civilian.set_position(civilian_position) + return MessageCivilian( + False, + civilian, + StandardMessagePriority.NORMAL, + sender_entity_id, + std_message.get_ttl(), + ) + + def __hash__(self) -> int: + h = super().__hash__() + return hash( + ( + h, + self._civilian_entity_id, + self._civilian_hp, + self._civilian_buriedness, + self._civilian_damage, + self._civilian_position, + ) + ) + + def __str__(self) -> str: + return f"MessageCivilian(civilian_entity_id={self._civilian_entity_id}, civilian_hp={self._civilian_hp}, civilian_buriedness={self._civilian_buriedness}, civilian_damage={self._civilian_damage}, civilian_position={self._civilian_position})" diff --git a/src/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py index 0f41702..1670b13 100644 --- a/src/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py +++ b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_fire_brigade.py @@ -6,194 +6,180 @@ from rcrscore.entities import EntityID, FireBrigade from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( - read_with_exist_flag, - write_with_exist_flag, + read_with_exist_flag, + write_with_exist_flag, ) class MessageFireBrigade(StandardMessage): - ACTION_REST: int = 0 - ACTION_MOVE: int = 1 - ACTION_EXTINGUISH: int = 2 - ACTION_REFILL: int = 3 - ACTION_RESCUE: int = 4 - - SIZE_FIRE_BRIGADE_ENTITY_ID: int = 32 - SIZE_FIRE_BRIGADE_HP: int = 14 - SIZE_FIRE_BRIGADE_BURIEDNESS: int = 13 - SIZE_FIRE_BRIGADE_DAMAGE: int = 14 - SIZE_FIRE_BRIGADE_POSITION: int = 32 - SIZE_FIRE_BRIGADE_WATER: int = 14 - SIZE_TARGET_ENTITY_ID: int = 32 - SIZE_ACTION: int = 4 - - def __init__( - self, - is_wireless_message: bool, - fire_brigade: FireBrigade, - action: int, - target_entity_id: EntityID, - priority: StandardMessagePriority, - sender_entity_id: EntityID, - ttl: Optional[int] = None, - ): - super().__init__(is_wireless_message, priority, sender_entity_id, ttl) - self._fire_brigade_entity_id: Optional[EntityID] = fire_brigade.get_entity_id() - self._fire_brigade_hp: Optional[int] = fire_brigade.get_hp() or None - self._fire_brigade_buriedness: Optional[int] = ( - fire_brigade.get_buriedness() or None - ) - self._fire_brigade_damage: Optional[int] = fire_brigade.get_damage() or None - self._fire_brigade_position: Optional[EntityID] = ( - fire_brigade.get_position() or None - ) - self._fire_brigade_water: Optional[int] = fire_brigade.get_water() or None - self._target_entity_id: Optional[EntityID] = target_entity_id - self._action: Optional[int] = action - - def get_fire_brigade_entity_id(self) -> Optional[EntityID]: - return self._fire_brigade_entity_id - - def get_fire_brigade_hp(self) -> Optional[int]: - return self._fire_brigade_hp - - def get_fire_brigade_buriedness(self) -> Optional[int]: - return self._fire_brigade_buriedness - - def get_fire_brigade_damage(self) -> Optional[int]: - return self._fire_brigade_damage - - def get_fire_brigade_position(self) -> Optional[EntityID]: - return self._fire_brigade_position - - def get_fire_brigade_water(self) -> Optional[int]: - return self._fire_brigade_water - - def get_target_entity_id(self) -> Optional[EntityID]: - return self._target_entity_id - - def get_action(self) -> Optional[int]: - return self._action - - def get_bit_size(self) -> int: - return self.to_bits().__len__() - - def to_bits(self) -> bitarray: - bit_array = super().to_bits() - write_with_exist_flag( - bit_array, - self._fire_brigade_entity_id.get_value() - if self._fire_brigade_entity_id - else None, - self.SIZE_FIRE_BRIGADE_ENTITY_ID, - ) - write_with_exist_flag( - bit_array, - self._fire_brigade_hp, - self.SIZE_FIRE_BRIGADE_HP, - ) - write_with_exist_flag( - bit_array, - self._fire_brigade_buriedness, - self.SIZE_FIRE_BRIGADE_BURIEDNESS, - ) - write_with_exist_flag( - bit_array, - self._fire_brigade_damage, - self.SIZE_FIRE_BRIGADE_DAMAGE, - ) - write_with_exist_flag( - bit_array, - self._fire_brigade_position.get_value() - if self._fire_brigade_position - else None, - self.SIZE_FIRE_BRIGADE_POSITION, - ) - write_with_exist_flag( - bit_array, - self._fire_brigade_water, - self.SIZE_FIRE_BRIGADE_WATER, - ) - write_with_exist_flag( - bit_array, - self._target_entity_id.get_value() if self._target_entity_id else None, - self.SIZE_TARGET_ENTITY_ID, - ) - write_with_exist_flag(bit_array, self._action, self.SIZE_ACTION) - return bit_array - - @classmethod - def from_bits( - cls, bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID - ) -> MessageFireBrigade: - std_message = super().from_bits( - bit_array, is_wireless_message, sender_entity_id - ) - fire_brigade_id = read_with_exist_flag( - bit_array, cls.SIZE_FIRE_BRIGADE_ENTITY_ID - ) - fire_brigade_hp = read_with_exist_flag(bit_array, cls.SIZE_FIRE_BRIGADE_HP) - fire_brigade_buriedness = read_with_exist_flag( - bit_array, cls.SIZE_FIRE_BRIGADE_BURIEDNESS - ) - fire_brigade_damage = read_with_exist_flag( - bit_array, cls.SIZE_FIRE_BRIGADE_DAMAGE - ) - raw_fire_brigade_position = read_with_exist_flag( - bit_array, cls.SIZE_FIRE_BRIGADE_POSITION - ) - fire_brigade_position = ( - EntityID(raw_fire_brigade_position) if raw_fire_brigade_position else None - ) - fire_brigade_water = read_with_exist_flag( - bit_array, cls.SIZE_FIRE_BRIGADE_WATER - ) - raw_target_entity_id = read_with_exist_flag( - bit_array, cls.SIZE_TARGET_ENTITY_ID - ) - target_entity_id = ( - EntityID(raw_target_entity_id) if raw_target_entity_id else EntityID(-1) - ) - action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) - fire_brigade = FireBrigade( - fire_brigade_id or -1, - ) - fire_brigade.set_hp(fire_brigade_hp) - fire_brigade.set_buriedness(fire_brigade_buriedness) - fire_brigade.set_damage(fire_brigade_damage) - fire_brigade.set_position(fire_brigade_position) - fire_brigade.set_water(fire_brigade_water) - return MessageFireBrigade( - False, - fire_brigade, - action if action is not None else -1, - target_entity_id, - StandardMessagePriority.NORMAL, - sender_entity_id, - std_message.get_ttl(), - ) - - def __hash__(self) -> int: - h = super().__hash__() - return hash( - ( - h, - self._fire_brigade_entity_id, - self._fire_brigade_hp, - self._fire_brigade_buriedness, - self._fire_brigade_damage, - self._fire_brigade_position, - self._fire_brigade_water, - self._target_entity_id, - self._action, - ) - ) - - def __str__(self) -> str: - return f"MessageFireBrigade(fire_brigade_entity_id={self._fire_brigade_entity_id}, fire_brigade_hp={self._fire_brigade_hp}, fire_brigade_buriedness={self._fire_brigade_buriedness}, fire_brigade_damage={self._fire_brigade_damage}, fire_brigade_position={self._fire_brigade_position}, fire_brigade_water={self._fire_brigade_water}, target_entity_id={self._target_entity_id}, action={self._action})" + ACTION_REST: int = 0 + ACTION_MOVE: int = 1 + ACTION_EXTINGUISH: int = 2 + ACTION_REFILL: int = 3 + ACTION_RESCUE: int = 4 + + SIZE_FIRE_BRIGADE_ENTITY_ID: int = 32 + SIZE_FIRE_BRIGADE_HP: int = 14 + SIZE_FIRE_BRIGADE_BURIEDNESS: int = 13 + SIZE_FIRE_BRIGADE_DAMAGE: int = 14 + SIZE_FIRE_BRIGADE_POSITION: int = 32 + SIZE_FIRE_BRIGADE_WATER: int = 14 + SIZE_TARGET_ENTITY_ID: int = 32 + SIZE_ACTION: int = 4 + + def __init__( + self, + is_wireless_message: bool, + fire_brigade: FireBrigade, + action: int, + target_entity_id: EntityID, + priority: StandardMessagePriority, + sender_entity_id: EntityID, + ttl: Optional[int] = None, + ): + super().__init__(is_wireless_message, priority, sender_entity_id, ttl) + self._fire_brigade_entity_id: Optional[EntityID] = fire_brigade.get_entity_id() + self._fire_brigade_hp: Optional[int] = fire_brigade.get_hp() or None + self._fire_brigade_buriedness: Optional[int] = fire_brigade.get_buriedness() or None + self._fire_brigade_damage: Optional[int] = fire_brigade.get_damage() or None + self._fire_brigade_position: Optional[EntityID] = ( + fire_brigade.get_position() or None + ) + self._fire_brigade_water: Optional[int] = fire_brigade.get_water() or None + self._target_entity_id: Optional[EntityID] = target_entity_id + self._action: Optional[int] = action + + def get_fire_brigade_entity_id(self) -> Optional[EntityID]: + return self._fire_brigade_entity_id + + def get_fire_brigade_hp(self) -> Optional[int]: + return self._fire_brigade_hp + + def get_fire_brigade_buriedness(self) -> Optional[int]: + return self._fire_brigade_buriedness + + def get_fire_brigade_damage(self) -> Optional[int]: + return self._fire_brigade_damage + + def get_fire_brigade_position(self) -> Optional[EntityID]: + return self._fire_brigade_position + + def get_fire_brigade_water(self) -> Optional[int]: + return self._fire_brigade_water + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._target_entity_id + + def get_action(self) -> Optional[int]: + return self._action + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + write_with_exist_flag( + bit_array, + self._fire_brigade_entity_id.get_value() + if self._fire_brigade_entity_id + else None, + self.SIZE_FIRE_BRIGADE_ENTITY_ID, + ) + write_with_exist_flag( + bit_array, + self._fire_brigade_hp, + self.SIZE_FIRE_BRIGADE_HP, + ) + write_with_exist_flag( + bit_array, + self._fire_brigade_buriedness, + self.SIZE_FIRE_BRIGADE_BURIEDNESS, + ) + write_with_exist_flag( + bit_array, + self._fire_brigade_damage, + self.SIZE_FIRE_BRIGADE_DAMAGE, + ) + write_with_exist_flag( + bit_array, + self._fire_brigade_position.get_value() if self._fire_brigade_position else None, + self.SIZE_FIRE_BRIGADE_POSITION, + ) + write_with_exist_flag( + bit_array, + self._fire_brigade_water, + self.SIZE_FIRE_BRIGADE_WATER, + ) + write_with_exist_flag( + bit_array, + self._target_entity_id.get_value() if self._target_entity_id else None, + self.SIZE_TARGET_ENTITY_ID, + ) + write_with_exist_flag(bit_array, self._action, self.SIZE_ACTION) + return bit_array + + @classmethod + def from_bits( + cls, bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID + ) -> MessageFireBrigade: + std_message = super().from_bits(bit_array, is_wireless_message, sender_entity_id) + fire_brigade_id = read_with_exist_flag(bit_array, cls.SIZE_FIRE_BRIGADE_ENTITY_ID) + fire_brigade_hp = read_with_exist_flag(bit_array, cls.SIZE_FIRE_BRIGADE_HP) + fire_brigade_buriedness = read_with_exist_flag( + bit_array, cls.SIZE_FIRE_BRIGADE_BURIEDNESS + ) + fire_brigade_damage = read_with_exist_flag(bit_array, cls.SIZE_FIRE_BRIGADE_DAMAGE) + raw_fire_brigade_position = read_with_exist_flag( + bit_array, cls.SIZE_FIRE_BRIGADE_POSITION + ) + fire_brigade_position = ( + EntityID(raw_fire_brigade_position) if raw_fire_brigade_position else None + ) + fire_brigade_water = read_with_exist_flag(bit_array, cls.SIZE_FIRE_BRIGADE_WATER) + raw_target_entity_id = read_with_exist_flag(bit_array, cls.SIZE_TARGET_ENTITY_ID) + target_entity_id = ( + EntityID(raw_target_entity_id) if raw_target_entity_id else EntityID(-1) + ) + action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) + fire_brigade = FireBrigade( + fire_brigade_id or -1, + ) + fire_brigade.set_hp(fire_brigade_hp) + fire_brigade.set_buriedness(fire_brigade_buriedness) + fire_brigade.set_damage(fire_brigade_damage) + fire_brigade.set_position(fire_brigade_position) + fire_brigade.set_water(fire_brigade_water) + return MessageFireBrigade( + False, + fire_brigade, + action if action is not None else -1, + target_entity_id, + StandardMessagePriority.NORMAL, + sender_entity_id, + std_message.get_ttl(), + ) + + def __hash__(self) -> int: + h = super().__hash__() + return hash( + ( + h, + self._fire_brigade_entity_id, + self._fire_brigade_hp, + self._fire_brigade_buriedness, + self._fire_brigade_damage, + self._fire_brigade_position, + self._fire_brigade_water, + self._target_entity_id, + self._action, + ) + ) + + def __str__(self) -> str: + return f"MessageFireBrigade(fire_brigade_entity_id={self._fire_brigade_entity_id}, fire_brigade_hp={self._fire_brigade_hp}, fire_brigade_buriedness={self._fire_brigade_buriedness}, fire_brigade_damage={self._fire_brigade_damage}, fire_brigade_position={self._fire_brigade_position}, fire_brigade_water={self._fire_brigade_water}, target_entity_id={self._target_entity_id}, action={self._action})" diff --git a/src/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py index f4b72d0..d6f9682 100644 --- a/src/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py +++ b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_police_force.py @@ -6,175 +6,163 @@ from rcrscore.entities import EntityID, PoliceForce from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( - read_with_exist_flag, - write_with_exist_flag, + read_with_exist_flag, + write_with_exist_flag, ) class MessagePoliceForce(StandardMessage): - ACTION_REST: int = 0 - ACTION_MOVE: int = 1 - ACTION_CLEAR: int = 2 - - SIZE_POLICE_FORCE_ENTITY_ID: int = 32 - SIZE_POLICE_FORCE_HP: int = 14 - SIZE_POLICE_FORCE_BURIEDNESS: int = 13 - SIZE_POLICE_FORCE_DAMAGE: int = 14 - SIZE_POLICE_FORCE_POSITION: int = 32 - SIZE_TARGET_ENTITY_ID: int = 32 - SIZE_ACTION: int = 4 - - def __init__( - self, - is_wireless_message: bool, - police_force: PoliceForce, - action: int, - target_entity_id: EntityID, - priority: StandardMessagePriority, - sender_entity_id: EntityID, - ttl: Optional[int] = None, - ): - super().__init__(is_wireless_message, priority, sender_entity_id, ttl) - self._police_force_entity_id: Optional[EntityID] = police_force.get_entity_id() - self._police_force_hp: Optional[int] = police_force.get_hp() or None - self._police_force_buriedness: Optional[int] = ( - police_force.get_buriedness() or None - ) - self._police_force_damage: Optional[int] = police_force.get_damage() or None - self._police_force_position: Optional[EntityID] = ( - police_force.get_position() or None - ) - self._target_entity_id: Optional[EntityID] = target_entity_id - self._action: Optional[int] = action - - def get_police_force_entity_id(self) -> Optional[EntityID]: - return self._police_force_entity_id - - def get_police_force_hp(self) -> Optional[int]: - return self._police_force_hp - - def get_police_force_buriedness(self) -> Optional[int]: - return self._police_force_buriedness - - def get_police_force_damage(self) -> Optional[int]: - return self._police_force_damage - - def get_police_force_position(self) -> Optional[EntityID]: - return self._police_force_position - - def get_target_entity_id(self) -> Optional[EntityID]: - return self._target_entity_id - - def get_action(self) -> Optional[int]: - return self._action - - def get_bit_size(self) -> int: - return self.to_bits().__len__() - - def to_bits(self) -> bitarray: - bit_array = super().to_bits() - write_with_exist_flag( - bit_array, - self._police_force_entity_id.get_value() - if self._police_force_entity_id - else None, - self.SIZE_POLICE_FORCE_ENTITY_ID, - ) - write_with_exist_flag( - bit_array, - self._police_force_hp, - self.SIZE_POLICE_FORCE_HP, - ) - write_with_exist_flag( - bit_array, - self._police_force_buriedness, - self.SIZE_POLICE_FORCE_BURIEDNESS, - ) - write_with_exist_flag( - bit_array, - self._police_force_damage, - self.SIZE_POLICE_FORCE_DAMAGE, - ) - write_with_exist_flag( - bit_array, - self._police_force_position.get_value() - if self._police_force_position - else None, - self.SIZE_POLICE_FORCE_POSITION, - ) - write_with_exist_flag( - bit_array, - self._target_entity_id.get_value() if self._target_entity_id else None, - self.SIZE_TARGET_ENTITY_ID, - ) - write_with_exist_flag(bit_array, self._action, self.SIZE_ACTION) - return bit_array - - @classmethod - def from_bits( - cls, bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID - ) -> MessagePoliceForce: - std_message = super().from_bits( - bit_array, is_wireless_message, sender_entity_id - ) - police_force_id = read_with_exist_flag( - bit_array, cls.SIZE_POLICE_FORCE_ENTITY_ID - ) - police_force_hp = read_with_exist_flag(bit_array, cls.SIZE_POLICE_FORCE_HP) - police_force_buriedness = read_with_exist_flag( - bit_array, cls.SIZE_POLICE_FORCE_BURIEDNESS - ) - police_force_damage = read_with_exist_flag( - bit_array, cls.SIZE_POLICE_FORCE_DAMAGE - ) - raw_police_force_position = read_with_exist_flag( - bit_array, cls.SIZE_POLICE_FORCE_POSITION - ) - police_force_position = ( - EntityID(raw_police_force_position) if raw_police_force_position else None - ) - raw_target_entity_id = read_with_exist_flag( - bit_array, cls.SIZE_TARGET_ENTITY_ID - ) - target_entity_id = ( - EntityID(raw_target_entity_id) if raw_target_entity_id else EntityID(-1) - ) - action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) - police_force = PoliceForce(police_force_id or -1) - police_force.set_hp(police_force_hp) - police_force.set_buriedness(police_force_buriedness) - police_force.set_damage(police_force_damage) - police_force.set_position(police_force_position) - return MessagePoliceForce( - False, - police_force, - action if action is not None else -1, - target_entity_id, - StandardMessagePriority.NORMAL, - sender_entity_id, - std_message.get_ttl(), - ) - - def __hash__(self) -> int: - h = super().__hash__() - return hash( - ( - h, - self._police_force_entity_id, - self._police_force_hp, - self._police_force_buriedness, - self._police_force_damage, - self._police_force_position, - self._target_entity_id, - self._action, - ) - ) - - def __str__(self) -> str: - return f"MessagePoliceForce(police_force_entity_id={self._police_force_entity_id}, police_force_hp={self._police_force_hp}, police_force_buriedness={self._police_force_buriedness}, police_force_damage={self._police_force_damage}, police_force_position={self._police_force_position}, target_entity_id={self._target_entity_id}, action={self._action})" + ACTION_REST: int = 0 + ACTION_MOVE: int = 1 + ACTION_CLEAR: int = 2 + + SIZE_POLICE_FORCE_ENTITY_ID: int = 32 + SIZE_POLICE_FORCE_HP: int = 14 + SIZE_POLICE_FORCE_BURIEDNESS: int = 13 + SIZE_POLICE_FORCE_DAMAGE: int = 14 + SIZE_POLICE_FORCE_POSITION: int = 32 + SIZE_TARGET_ENTITY_ID: int = 32 + SIZE_ACTION: int = 4 + + def __init__( + self, + is_wireless_message: bool, + police_force: PoliceForce, + action: int, + target_entity_id: EntityID, + priority: StandardMessagePriority, + sender_entity_id: EntityID, + ttl: Optional[int] = None, + ): + super().__init__(is_wireless_message, priority, sender_entity_id, ttl) + self._police_force_entity_id: Optional[EntityID] = police_force.get_entity_id() + self._police_force_hp: Optional[int] = police_force.get_hp() or None + self._police_force_buriedness: Optional[int] = police_force.get_buriedness() or None + self._police_force_damage: Optional[int] = police_force.get_damage() or None + self._police_force_position: Optional[EntityID] = ( + police_force.get_position() or None + ) + self._target_entity_id: Optional[EntityID] = target_entity_id + self._action: Optional[int] = action + + def get_police_force_entity_id(self) -> Optional[EntityID]: + return self._police_force_entity_id + + def get_police_force_hp(self) -> Optional[int]: + return self._police_force_hp + + def get_police_force_buriedness(self) -> Optional[int]: + return self._police_force_buriedness + + def get_police_force_damage(self) -> Optional[int]: + return self._police_force_damage + + def get_police_force_position(self) -> Optional[EntityID]: + return self._police_force_position + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._target_entity_id + + def get_action(self) -> Optional[int]: + return self._action + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + write_with_exist_flag( + bit_array, + self._police_force_entity_id.get_value() + if self._police_force_entity_id + else None, + self.SIZE_POLICE_FORCE_ENTITY_ID, + ) + write_with_exist_flag( + bit_array, + self._police_force_hp, + self.SIZE_POLICE_FORCE_HP, + ) + write_with_exist_flag( + bit_array, + self._police_force_buriedness, + self.SIZE_POLICE_FORCE_BURIEDNESS, + ) + write_with_exist_flag( + bit_array, + self._police_force_damage, + self.SIZE_POLICE_FORCE_DAMAGE, + ) + write_with_exist_flag( + bit_array, + self._police_force_position.get_value() if self._police_force_position else None, + self.SIZE_POLICE_FORCE_POSITION, + ) + write_with_exist_flag( + bit_array, + self._target_entity_id.get_value() if self._target_entity_id else None, + self.SIZE_TARGET_ENTITY_ID, + ) + write_with_exist_flag(bit_array, self._action, self.SIZE_ACTION) + return bit_array + + @classmethod + def from_bits( + cls, bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID + ) -> MessagePoliceForce: + std_message = super().from_bits(bit_array, is_wireless_message, sender_entity_id) + police_force_id = read_with_exist_flag(bit_array, cls.SIZE_POLICE_FORCE_ENTITY_ID) + police_force_hp = read_with_exist_flag(bit_array, cls.SIZE_POLICE_FORCE_HP) + police_force_buriedness = read_with_exist_flag( + bit_array, cls.SIZE_POLICE_FORCE_BURIEDNESS + ) + police_force_damage = read_with_exist_flag(bit_array, cls.SIZE_POLICE_FORCE_DAMAGE) + raw_police_force_position = read_with_exist_flag( + bit_array, cls.SIZE_POLICE_FORCE_POSITION + ) + police_force_position = ( + EntityID(raw_police_force_position) if raw_police_force_position else None + ) + raw_target_entity_id = read_with_exist_flag(bit_array, cls.SIZE_TARGET_ENTITY_ID) + target_entity_id = ( + EntityID(raw_target_entity_id) if raw_target_entity_id else EntityID(-1) + ) + action = read_with_exist_flag(bit_array, cls.SIZE_ACTION) + police_force = PoliceForce(police_force_id or -1) + police_force.set_hp(police_force_hp) + police_force.set_buriedness(police_force_buriedness) + police_force.set_damage(police_force_damage) + police_force.set_position(police_force_position) + return MessagePoliceForce( + False, + police_force, + action if action is not None else -1, + target_entity_id, + StandardMessagePriority.NORMAL, + sender_entity_id, + std_message.get_ttl(), + ) + + def __hash__(self) -> int: + h = super().__hash__() + return hash( + ( + h, + self._police_force_entity_id, + self._police_force_hp, + self._police_force_buriedness, + self._police_force_damage, + self._police_force_position, + self._target_entity_id, + self._action, + ) + ) + + def __str__(self) -> str: + return f"MessagePoliceForce(police_force_entity_id={self._police_force_entity_id}, police_force_hp={self._police_force_hp}, police_force_buriedness={self._police_force_buriedness}, police_force_damage={self._police_force_damage}, police_force_position={self._police_force_position}, target_entity_id={self._target_entity_id}, action={self._action})" diff --git a/src/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py index 747bfb5..d43b9d4 100644 --- a/src/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py +++ b/src/adf_core_python/core/agent/communication/standard/bundle/information/message_road.py @@ -6,172 +6,164 @@ from rcrscore.entities import Blockade, EntityID, Road from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( - read_with_exist_flag, - write_with_exist_flag, + read_with_exist_flag, + write_with_exist_flag, ) class MessageRoad(StandardMessage): - ACTION_REST: int = 0 - ACTION_MOVE: int = 1 - ACTION_CLEAR: int = 2 - - SIZE_ROAD_ENTITY_ID: int = 32 - SIZE_ROAD_BLOCKADE_ENTITY_ID: int = 32 - SIZE_ROAD_BLOCKADE_REPAIR_COST: int = 32 - SIZE_ROAD_BLOCKADE_X: int = 32 - SIZE_ROAD_BLOCKADE_Y: int = 32 - SIZE_PASSABLE: int = 1 - - def __init__( - self, - is_wireless_message: bool, - road: Road, - is_send_blockade_location: bool, - is_passable: Optional[bool], - blockade: Optional[Blockade], - priority: StandardMessagePriority, - sender_entity_id: EntityID, - ttl: Optional[int] = None, - ): - super().__init__(is_wireless_message, priority, sender_entity_id, ttl) - self._road_entity_id: Optional[EntityID] = road.get_entity_id() - self._road_blockade_entity_id: Optional[EntityID] = None - self._road_blockade_repair_cost: Optional[int] = None - self._road_blockade_x: Optional[int] = None - self._road_blockade_y: Optional[int] = None - - if blockade: - self._road_blockade_entity_id = blockade.get_entity_id() - self._road_blockade_repair_cost = blockade.get_repair_cost() - if is_send_blockade_location: - self._road_blockade_x = blockade.get_x() or None - self._road_blockade_y = blockade.get_y() or None - - self._is_passable: Optional[bool] = is_passable - self._is_send_blockade_location: bool = is_send_blockade_location - - def get_road_entity_id(self) -> Optional[EntityID]: - return self._road_entity_id - - def get_road_blockade_entity_id(self) -> Optional[EntityID]: - return self._road_blockade_entity_id - - def get_road_blockade_repair_cost(self) -> Optional[int]: - return self._road_blockade_repair_cost - - def get_road_blockade_x(self) -> Optional[int]: - return self._road_blockade_x - - def get_road_blockade_y(self) -> Optional[int]: - return self._road_blockade_y - - def get_is_passable(self) -> Optional[bool]: - return self._is_passable - - def get_is_send_blockade_location(self) -> bool: - return self._is_send_blockade_location - - def get_bit_size(self) -> int: - return self.to_bits().__len__() - - def to_bits(self) -> bitarray: - bit_array = super().to_bits() - write_with_exist_flag( - bit_array, - self._road_entity_id.get_value() if self._road_entity_id else None, - self.SIZE_ROAD_ENTITY_ID, - ) - write_with_exist_flag( - bit_array, - self._road_blockade_entity_id.get_value() - if self._road_blockade_entity_id - else None, - self.SIZE_ROAD_BLOCKADE_ENTITY_ID, - ) - write_with_exist_flag( - bit_array, - self._road_blockade_repair_cost - if self._road_blockade_repair_cost - else None, - self.SIZE_ROAD_BLOCKADE_REPAIR_COST, - ) - if self._is_send_blockade_location: - write_with_exist_flag( - bit_array, - self._road_blockade_x if self._road_blockade_x else None, - self.SIZE_ROAD_BLOCKADE_X, - ) - write_with_exist_flag( - bit_array, - self._road_blockade_y if self._road_blockade_y else None, - self.SIZE_ROAD_BLOCKADE_Y, - ) - else: - write_with_exist_flag(bit_array, None, self.SIZE_ROAD_BLOCKADE_X) - write_with_exist_flag(bit_array, None, self.SIZE_ROAD_BLOCKADE_Y) - write_with_exist_flag( - bit_array, - self._is_passable if self._is_passable else None, - self.SIZE_PASSABLE, - ) - return bit_array - - @classmethod - def from_bits( - cls, bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID - ) -> MessageRoad: - std_message = super().from_bits( - bit_array, is_wireless_message, sender_entity_id - ) - road_id = read_with_exist_flag(bit_array, cls.SIZE_ROAD_ENTITY_ID) - road_blockade_id = read_with_exist_flag( - bit_array, cls.SIZE_ROAD_BLOCKADE_ENTITY_ID - ) - road_blockade_repair_cost = read_with_exist_flag( - bit_array, cls.SIZE_ROAD_BLOCKADE_REPAIR_COST - ) - road_blockade_x = read_with_exist_flag(bit_array, cls.SIZE_ROAD_BLOCKADE_X) - road_blockade_y = read_with_exist_flag(bit_array, cls.SIZE_ROAD_BLOCKADE_Y) - is_passable = ( - True if read_with_exist_flag(bit_array, cls.SIZE_PASSABLE) else False - ) - road = Road(road_id or -1) - blockade = Blockade(road_blockade_id or -1) - blockade.set_repair_cost(road_blockade_repair_cost) - blockade.set_x(road_blockade_x) - blockade.set_y(road_blockade_y) - return MessageRoad( - is_wireless_message, - road, - False, - is_passable, - blockade, - StandardMessagePriority.NORMAL, - sender_entity_id, - std_message.get_ttl(), - ) - - def __hash__(self) -> int: - h = super().__hash__() - return hash( - ( - h, - self._road_entity_id, - self._road_blockade_entity_id, - self._road_blockade_repair_cost, - self._road_blockade_x, - self._road_blockade_y, - self._is_passable, - self._is_send_blockade_location, - ) - ) - - def __str__(self) -> str: - return f"MessageRoad(road_entity_id={self._road_entity_id}, road_blockade_entity_id={self._road_blockade_entity_id}, road_blockade_repair_cost={self._road_blockade_repair_cost}, road_blockade_x={self._road_blockade_x}, road_blockade_y={self._road_blockade_y}, is_passable={self._is_passable}, is_send_blockade_location={self._is_send_blockade_location})" + ACTION_REST: int = 0 + ACTION_MOVE: int = 1 + ACTION_CLEAR: int = 2 + + SIZE_ROAD_ENTITY_ID: int = 32 + SIZE_ROAD_BLOCKADE_ENTITY_ID: int = 32 + SIZE_ROAD_BLOCKADE_REPAIR_COST: int = 32 + SIZE_ROAD_BLOCKADE_X: int = 32 + SIZE_ROAD_BLOCKADE_Y: int = 32 + SIZE_PASSABLE: int = 1 + + def __init__( + self, + is_wireless_message: bool, + road: Road, + is_send_blockade_location: bool, + is_passable: Optional[bool], + blockade: Optional[Blockade], + priority: StandardMessagePriority, + sender_entity_id: EntityID, + ttl: Optional[int] = None, + ): + super().__init__(is_wireless_message, priority, sender_entity_id, ttl) + self._road_entity_id: Optional[EntityID] = road.get_entity_id() + self._road_blockade_entity_id: Optional[EntityID] = None + self._road_blockade_repair_cost: Optional[int] = None + self._road_blockade_x: Optional[int] = None + self._road_blockade_y: Optional[int] = None + + if blockade: + self._road_blockade_entity_id = blockade.get_entity_id() + self._road_blockade_repair_cost = blockade.get_repair_cost() + if is_send_blockade_location: + self._road_blockade_x = blockade.get_x() or None + self._road_blockade_y = blockade.get_y() or None + + self._is_passable: Optional[bool] = is_passable + self._is_send_blockade_location: bool = is_send_blockade_location + + def get_road_entity_id(self) -> Optional[EntityID]: + return self._road_entity_id + + def get_road_blockade_entity_id(self) -> Optional[EntityID]: + return self._road_blockade_entity_id + + def get_road_blockade_repair_cost(self) -> Optional[int]: + return self._road_blockade_repair_cost + + def get_road_blockade_x(self) -> Optional[int]: + return self._road_blockade_x + + def get_road_blockade_y(self) -> Optional[int]: + return self._road_blockade_y + + def get_is_passable(self) -> Optional[bool]: + return self._is_passable + + def get_is_send_blockade_location(self) -> bool: + return self._is_send_blockade_location + + def get_bit_size(self) -> int: + return self.to_bits().__len__() + + def to_bits(self) -> bitarray: + bit_array = super().to_bits() + write_with_exist_flag( + bit_array, + self._road_entity_id.get_value() if self._road_entity_id else None, + self.SIZE_ROAD_ENTITY_ID, + ) + write_with_exist_flag( + bit_array, + self._road_blockade_entity_id.get_value() + if self._road_blockade_entity_id + else None, + self.SIZE_ROAD_BLOCKADE_ENTITY_ID, + ) + write_with_exist_flag( + bit_array, + self._road_blockade_repair_cost if self._road_blockade_repair_cost else None, + self.SIZE_ROAD_BLOCKADE_REPAIR_COST, + ) + if self._is_send_blockade_location: + write_with_exist_flag( + bit_array, + self._road_blockade_x if self._road_blockade_x else None, + self.SIZE_ROAD_BLOCKADE_X, + ) + write_with_exist_flag( + bit_array, + self._road_blockade_y if self._road_blockade_y else None, + self.SIZE_ROAD_BLOCKADE_Y, + ) + else: + write_with_exist_flag(bit_array, None, self.SIZE_ROAD_BLOCKADE_X) + write_with_exist_flag(bit_array, None, self.SIZE_ROAD_BLOCKADE_Y) + write_with_exist_flag( + bit_array, + self._is_passable if self._is_passable else None, + self.SIZE_PASSABLE, + ) + return bit_array + + @classmethod + def from_bits( + cls, bit_array: bitarray, is_wireless_message: bool, sender_entity_id: EntityID + ) -> MessageRoad: + std_message = super().from_bits(bit_array, is_wireless_message, sender_entity_id) + road_id = read_with_exist_flag(bit_array, cls.SIZE_ROAD_ENTITY_ID) + road_blockade_id = read_with_exist_flag(bit_array, cls.SIZE_ROAD_BLOCKADE_ENTITY_ID) + road_blockade_repair_cost = read_with_exist_flag( + bit_array, cls.SIZE_ROAD_BLOCKADE_REPAIR_COST + ) + road_blockade_x = read_with_exist_flag(bit_array, cls.SIZE_ROAD_BLOCKADE_X) + road_blockade_y = read_with_exist_flag(bit_array, cls.SIZE_ROAD_BLOCKADE_Y) + is_passable = True if read_with_exist_flag(bit_array, cls.SIZE_PASSABLE) else False + road = Road(road_id or -1) + blockade = Blockade(road_blockade_id or -1) + blockade.set_repair_cost(road_blockade_repair_cost) + blockade.set_x(road_blockade_x) + blockade.set_y(road_blockade_y) + return MessageRoad( + is_wireless_message, + road, + False, + is_passable, + blockade, + StandardMessagePriority.NORMAL, + sender_entity_id, + std_message.get_ttl(), + ) + + def __hash__(self) -> int: + h = super().__hash__() + return hash( + ( + h, + self._road_entity_id, + self._road_blockade_entity_id, + self._road_blockade_repair_cost, + self._road_blockade_x, + self._road_blockade_y, + self._is_passable, + self._is_send_blockade_location, + ) + ) + + def __str__(self) -> str: + return f"MessageRoad(road_entity_id={self._road_entity_id}, road_blockade_entity_id={self._road_blockade_entity_id}, road_blockade_repair_cost={self._road_blockade_repair_cost}, road_blockade_x={self._road_blockade_x}, road_blockade_y={self._road_blockade_y}, is_passable={self._is_passable}, is_send_blockade_location={self._is_send_blockade_location})" diff --git a/src/adf_core_python/core/agent/communication/standard/bundle/standard_message.py b/src/adf_core_python/core/agent/communication/standard/bundle/standard_message.py index 34ec8f9..296cfd3 100644 --- a/src/adf_core_python/core/agent/communication/standard/bundle/standard_message.py +++ b/src/adf_core_python/core/agent/communication/standard/bundle/standard_message.py @@ -6,66 +6,66 @@ from rcrscore.entities import EntityID from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( - read_with_exist_flag, - write_with_exist_flag, + read_with_exist_flag, + write_with_exist_flag, ) from adf_core_python.core.component.communication.communication_message import ( - CommunicationMessage, + CommunicationMessage, ) class StandardMessage(CommunicationMessage): - SIZE_TTL: int = 3 + SIZE_TTL: int = 3 - def __init__( - self, - is_wireless_message: bool, - priority: StandardMessagePriority, - sender_entity_id: EntityID, - ttl: Optional[int] = None, - ): - super().__init__(is_wireless_message) - self._priority = priority - self._sender_entity_id = sender_entity_id - self._ttl = ttl + def __init__( + self, + is_wireless_message: bool, + priority: StandardMessagePriority, + sender_entity_id: EntityID, + ttl: Optional[int] = None, + ): + super().__init__(is_wireless_message) + self._priority = priority + self._sender_entity_id = sender_entity_id + self._ttl = ttl - def get_sender_entity_id(self) -> EntityID: - return self._sender_entity_id + def get_sender_entity_id(self) -> EntityID: + return self._sender_entity_id - def get_priority(self) -> StandardMessagePriority: - return self._priority + def get_priority(self) -> StandardMessagePriority: + return self._priority - def get_ttl(self) -> Optional[int]: - return self._ttl + def get_ttl(self) -> Optional[int]: + return self._ttl - @classmethod - def from_bits( - cls, - bit_array: bitarray, - is_wireless_message: bool, - sender_entity_id: EntityID, - ) -> StandardMessage: - ttl = read_with_exist_flag(bit_array, cls.SIZE_TTL) - return StandardMessage( - is_wireless_message, - StandardMessagePriority.NORMAL, - sender_entity_id, - ttl, - ) + @classmethod + def from_bits( + cls, + bit_array: bitarray, + is_wireless_message: bool, + sender_entity_id: EntityID, + ) -> StandardMessage: + ttl = read_with_exist_flag(bit_array, cls.SIZE_TTL) + return StandardMessage( + is_wireless_message, + StandardMessagePriority.NORMAL, + sender_entity_id, + ttl, + ) - def to_bits(self) -> bitarray: - bit_array = bitarray() - write_with_exist_flag(bit_array, self._ttl, self.SIZE_TTL) - return bit_array + def to_bits(self) -> bitarray: + bit_array = bitarray() + write_with_exist_flag(bit_array, self._ttl, self.SIZE_TTL) + return bit_array - def get_bit_size(self) -> int: - raise NotImplementedError + def get_bit_size(self) -> int: + raise NotImplementedError - def __hash__(self) -> int: - return hash((self._sender_entity_id, self._priority, self._ttl)) + def __hash__(self) -> int: + return hash((self._sender_entity_id, self._priority, self._ttl)) - def __str__(self) -> str: - return f"StandardMessage(sender_entity_id={self._sender_entity_id}, priority={self._priority}, ttl={self._ttl})" + def __str__(self) -> str: + return f"StandardMessage(sender_entity_id={self._sender_entity_id}, priority={self._priority}, ttl={self._ttl})" diff --git a/src/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py b/src/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py index 9e45154..357a9ac 100644 --- a/src/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py +++ b/src/adf_core_python/core/agent/communication/standard/bundle/standard_message_priority.py @@ -2,23 +2,23 @@ class StandardMessagePriority(Enum): - """ - Standard message priorities. - """ + """ + Standard message priorities. + """ - LOW = 0 - NORMAL = 1 - HIGH = 2 + LOW = 0 + NORMAL = 1 + HIGH = 2 - def __str__(self) -> str: - return self.name.lower() + def __str__(self) -> str: + return self.name.lower() - def __lt__(self, other: object) -> bool: - if isinstance(other, StandardMessagePriority): - return self.value < other.value - return NotImplemented + def __lt__(self, other: object) -> bool: + if isinstance(other, StandardMessagePriority): + return self.value < other.value + return NotImplemented - def __le__(self, other: object) -> bool: - if isinstance(other, StandardMessagePriority): - return self.value <= other.value - return NotImplemented + def __le__(self, other: object) -> bool: + if isinstance(other, StandardMessagePriority): + return self.value <= other.value + return NotImplemented diff --git a/src/adf_core_python/core/agent/communication/standard/standard_communication_module.py b/src/adf_core_python/core/agent/communication/standard/standard_communication_module.py index f502651..1a91c03 100644 --- a/src/adf_core_python/core/agent/communication/standard/standard_communication_module.py +++ b/src/adf_core_python/core/agent/communication/standard/standard_communication_module.py @@ -8,169 +8,161 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.communication.standard.utility.bitarray_with_exits_flag import ( - read_with_exist_flag, - write_with_exist_flag, + read_with_exist_flag, + write_with_exist_flag, ) from adf_core_python.core.component.communication.communication_module import ( - CommunicationModule, + CommunicationModule, ) from adf_core_python.core.logger.logger import get_logger if TYPE_CHECKING: - from adf_core_python.core.agent.agent import Agent + from adf_core_python.core.agent.agent import Agent class StandardCommunicationModule(CommunicationModule): - ESCAPE_CHAR = bitarray("11111111") - SIZE_ID: int = 5 - SIZE_TTL: int = 3 - - def receive(self, agent: Agent, message_manager: MessageManager) -> None: - heard_commands = agent._agent_info.get_heard_commands() - for command in heard_commands: - if isinstance(command, AKSpeak): - sender_entity_id = command.agent_id - if sender_entity_id == agent.get_entity_id(): - continue - data = command.message - is_wireless_message = command.channel != 0 - - if len(data) == 0: - continue - - if is_wireless_message: - bit_array = bitarray() - bit_array.frombytes(data) - self.add_received_message( - message_manager, - is_wireless_message, - sender_entity_id, - bit_array, - ) - else: - try: - voice_message = data.decode("utf-8") - if voice_message.startswith("Help") or voice_message.startswith( - "Ouch" - ): - message_manager.add_heard_agent_help_message_count() - continue - except UnicodeDecodeError: - pass - - escape_char = self.ESCAPE_CHAR.tobytes()[0] - i = 0 - bit_array = bitarray() - a = bitarray() - a.frombytes(data) - while i < len(data): - if data[i] == escape_char: - if (i + 1) >= len(data): - self.add_received_message( - message_manager, - False, - sender_entity_id, - bit_array, - ) - break - elif data[i + 1] != escape_char: - self.add_received_message( - message_manager, - False, - sender_entity_id, - bit_array, - ) - bit_array.clear() - i += 1 # Skip the next character - continue - i += 1 # Skip the escaped character - bits = bitarray() - bits.frombytes(data[i].to_bytes(1, "big")) - bit_array.extend(bits) - i += 1 - - def send(self, agent: Agent, message_manager: MessageManager) -> None: - voice_message_limit_bytes = agent._scenario_info.get_value( - "comms.channels.0.messages.size", 256 - ) - left_voice_message_limit_bits = voice_message_limit_bytes * 8 - voice_message_bit_array = bitarray() - - send_messages = message_manager.get_channel_send_message_list() - - for channel in range(len(send_messages)): - for message in send_messages[channel]: - message_class_index = message_manager.get_message_class_index( - type(message) + ESCAPE_CHAR = bitarray("11111111") + SIZE_ID: int = 5 + SIZE_TTL: int = 3 + + def receive(self, agent: Agent, message_manager: MessageManager) -> None: + heard_commands = agent._agent_info.get_heard_commands() + for command in heard_commands: + if isinstance(command, AKSpeak): + sender_entity_id = command.agent_id + if sender_entity_id == agent.get_entity_id(): + continue + data = command.message + is_wireless_message = command.channel != 0 + + if len(data) == 0: + continue + + if is_wireless_message: + bit_array = bitarray() + bit_array.frombytes(data) + self.add_received_message( + message_manager, + is_wireless_message, + sender_entity_id, + bit_array, + ) + else: + try: + voice_message = data.decode("utf-8") + if voice_message.startswith("Help") or voice_message.startswith("Ouch"): + message_manager.add_heard_agent_help_message_count() + continue + except UnicodeDecodeError: + pass + + escape_char = self.ESCAPE_CHAR.tobytes()[0] + i = 0 + bit_array = bitarray() + a = bitarray() + a.frombytes(data) + while i < len(data): + if data[i] == escape_char: + if (i + 1) >= len(data): + self.add_received_message( + message_manager, + False, + sender_entity_id, + bit_array, + ) + break + elif data[i + 1] != escape_char: + self.add_received_message( + message_manager, + False, + sender_entity_id, + bit_array, ) - bit_array = bitarray() - - write_with_exist_flag(bit_array, message_class_index, self.SIZE_ID) - - bit_array.extend(message.to_bits()) - - if channel > 0: - agent.send_speak( - agent._agent_info.get_time(), - bit_array, - channel, - ) - else: - # bit_arrayを8bitごとに区切れるようにして、エスケープ処理を行う - if len(bit_array) % 8 != 0: - bit_array.extend([False] * (8 - len(bit_array) % 8)) - message_bit_size = len(bit_array) - if message_bit_size <= left_voice_message_limit_bits: - esceped_message_data = bitarray() - for i in range(len(bit_array) // 8): - if bit_array[(i * 8) : ((i + 1) * 8)] == self.ESCAPE_CHAR: - esceped_message_data.extend(self.ESCAPE_CHAR) - esceped_message_data.extend( - bit_array[(i * 8) : ((i + 1) * 8)] - ) - esceped_message_data.extend(self.ESCAPE_CHAR) - if len(esceped_message_data) <= voice_message_limit_bytes: - left_voice_message_limit_bits -= len(esceped_message_data) - voice_message_bit_array.extend(esceped_message_data) - - if len(voice_message_bit_array) > 0: - agent.send_speak( - agent._agent_info.get_time(), - voice_message_bit_array, - 0, - ) - - def add_received_message( - self, - message_manager: MessageManager, - is_wireless_message: bool, - sender_entity_id: EntityID, - data: bitarray, - ) -> None: - message_class_index = read_with_exist_flag(data, self.SIZE_ID) - if message_class_index is None or message_class_index < 0: - get_logger( - f"{self.__class__.__module__}.{self.__class__.__qualname__}" - ).warning(f"Invalid message class index: {message_class_index}") - return - - message = message_manager.get_message_class(message_class_index) - if message is None: - get_logger( - f"{self.__class__.__module__}.{self.__class__.__qualname__}" - ).warning(f"Invalid message class index: {message_class_index}") - return - - if issubclass(message, StandardMessage): - message_instance = message.from_bits( - data, is_wireless_message, sender_entity_id - ) - message_manager.add_received_message(message_instance) + bit_array.clear() + i += 1 # Skip the next character + continue + i += 1 # Skip the escaped character + bits = bitarray() + bits.frombytes(data[i].to_bytes(1, "big")) + bit_array.extend(bits) + i += 1 + + def send(self, agent: Agent, message_manager: MessageManager) -> None: + voice_message_limit_bytes = agent._scenario_info.get_value( + "comms.channels.0.messages.size", 256 + ) + left_voice_message_limit_bits = voice_message_limit_bytes * 8 + voice_message_bit_array = bitarray() + + send_messages = message_manager.get_channel_send_message_list() + + for channel in range(len(send_messages)): + for message in send_messages[channel]: + message_class_index = message_manager.get_message_class_index(type(message)) + bit_array = bitarray() + + write_with_exist_flag(bit_array, message_class_index, self.SIZE_ID) + + bit_array.extend(message.to_bits()) + + if channel > 0: + agent.send_speak( + agent._agent_info.get_time(), + bit_array, + channel, + ) else: - get_logger( - f"{self.__class__.__module__}.{self.__class__.__qualname__}" - ).warning(f"Invalid message class: {message}") - return + # bit_arrayを8bitごとに区切れるようにして、エスケープ処理を行う + if len(bit_array) % 8 != 0: + bit_array.extend([False] * (8 - len(bit_array) % 8)) + message_bit_size = len(bit_array) + if message_bit_size <= left_voice_message_limit_bits: + esceped_message_data = bitarray() + for i in range(len(bit_array) // 8): + if bit_array[(i * 8) : ((i + 1) * 8)] == self.ESCAPE_CHAR: + esceped_message_data.extend(self.ESCAPE_CHAR) + esceped_message_data.extend(bit_array[(i * 8) : ((i + 1) * 8)]) + esceped_message_data.extend(self.ESCAPE_CHAR) + if len(esceped_message_data) <= voice_message_limit_bytes: + left_voice_message_limit_bits -= len(esceped_message_data) + voice_message_bit_array.extend(esceped_message_data) + + if len(voice_message_bit_array) > 0: + agent.send_speak( + agent._agent_info.get_time(), + voice_message_bit_array, + 0, + ) + + def add_received_message( + self, + message_manager: MessageManager, + is_wireless_message: bool, + sender_entity_id: EntityID, + data: bitarray, + ) -> None: + message_class_index = read_with_exist_flag(data, self.SIZE_ID) + if message_class_index is None or message_class_index < 0: + get_logger(f"{self.__class__.__module__}.{self.__class__.__qualname__}").warning( + f"Invalid message class index: {message_class_index}" + ) + return + + message = message_manager.get_message_class(message_class_index) + if message is None: + get_logger(f"{self.__class__.__module__}.{self.__class__.__qualname__}").warning( + f"Invalid message class index: {message_class_index}" + ) + return + + if issubclass(message, StandardMessage): + message_instance = message.from_bits(data, is_wireless_message, sender_entity_id) + message_manager.add_received_message(message_instance) + else: + get_logger(f"{self.__class__.__module__}.{self.__class__.__qualname__}").warning( + f"Invalid message class: {message}" + ) + return diff --git a/src/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py b/src/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py index 56b1f8f..3c0efb9 100644 --- a/src/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py +++ b/src/adf_core_python/core/agent/communication/standard/utility/apply_to_world_info.py @@ -1,337 +1,317 @@ from rcrscore.entities import ( - AmbulanceTeam, - Blockade, - Building, - Civilian, - FireBrigade, - PoliceForce, - Road, + AmbulanceTeam, + Blockade, + Building, + Civilian, + FireBrigade, + PoliceForce, + Road, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_ambulance_team import ( - MessageAmbulanceTeam, + MessageAmbulanceTeam, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_building import ( - MessageBuilding, + MessageBuilding, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_civilian import ( - MessageCivilian, + MessageCivilian, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_fire_brigade import ( - MessageFireBrigade, + MessageFireBrigade, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_police_force import ( - MessagePoliceForce, + MessagePoliceForce, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_road import ( - MessageRoad, + MessageRoad, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.info.world_info import WorldInfo def apply_to_world_info( - world_info: WorldInfo, - standard_message: StandardMessage, + world_info: WorldInfo, + standard_message: StandardMessage, ) -> None: - """ - Apply to world info. + """ + Apply to world info. - PARAMETERS - ---------- - world_info: WorldInfo - The world info to apply to. - standard_message: StandardMessage - The standard message to apply to world info. - """ + PARAMETERS + ---------- + world_info: WorldInfo + The world info to apply to. + standard_message: StandardMessage + The standard message to apply to world info. + """ - if isinstance(standard_message, MessageAmbulanceTeam): - _apply_to_world_info_ambulance_team(world_info, standard_message) - elif isinstance(standard_message, MessageFireBrigade): - _apply_to_world_info_fire_brigade(world_info, standard_message) - elif isinstance(standard_message, MessagePoliceForce): - _apply_to_world_info_police_force(world_info, standard_message) - elif isinstance(standard_message, MessageCivilian): - _apply_to_world_info_civilian(world_info, standard_message) - elif isinstance(standard_message, MessageBuilding): - _apply_to_world_info_building(world_info, standard_message) - elif isinstance(standard_message, MessageRoad): - _apply_to_world_info_road(world_info, standard_message) - else: - return + if isinstance(standard_message, MessageAmbulanceTeam): + _apply_to_world_info_ambulance_team(world_info, standard_message) + elif isinstance(standard_message, MessageFireBrigade): + _apply_to_world_info_fire_brigade(world_info, standard_message) + elif isinstance(standard_message, MessagePoliceForce): + _apply_to_world_info_police_force(world_info, standard_message) + elif isinstance(standard_message, MessageCivilian): + _apply_to_world_info_civilian(world_info, standard_message) + elif isinstance(standard_message, MessageBuilding): + _apply_to_world_info_building(world_info, standard_message) + elif isinstance(standard_message, MessageRoad): + _apply_to_world_info_road(world_info, standard_message) + else: + return def _apply_to_world_info_ambulance_team( - world_info: WorldInfo, - message_ambulance_team: MessageAmbulanceTeam, + world_info: WorldInfo, + message_ambulance_team: MessageAmbulanceTeam, ) -> None: - """ - Apply to world info for ambulance team. + """ + Apply to world info for ambulance team. - PARAMETERS - ---------- - world_info: WorldInfo - The world info to apply to. - standard_message: StandardMessage - The standard message to apply to world info. - """ - entity_id = message_ambulance_team.get_ambulance_team_entity_id() - if entity_id is None: - return - entity = world_info.get_entity(entity_id) - if entity is None: - ambulance = AmbulanceTeam(entity_id.get_value()) - if (hp := message_ambulance_team.get_ambulance_team_hp()) is not None: - ambulance.set_hp(hp) - if (damege := message_ambulance_team.get_ambulance_team_damage()) is not None: - ambulance.set_damage(damege) - if ( - buriedness := message_ambulance_team.get_ambulance_team_buriedness() - ) is not None: - ambulance.set_buriedness(buriedness) - if ( - position := message_ambulance_team.get_ambulance_team_position() - ) is not None: - ambulance.set_position(position) - world_info.add_entity(ambulance) - else: - if isinstance(entity, AmbulanceTeam): - if (hp := message_ambulance_team.get_ambulance_team_hp()) is not None: - entity.set_hp(hp) - if ( - damege := message_ambulance_team.get_ambulance_team_damage() - ) is not None: - entity.set_damage(damege) - if ( - buriedness := message_ambulance_team.get_ambulance_team_buriedness() - ) is not None: - entity.set_buriedness(buriedness) - if ( - position := message_ambulance_team.get_ambulance_team_position() - ) is not None: - entity.set_position(position) + PARAMETERS + ---------- + world_info: WorldInfo + The world info to apply to. + standard_message: StandardMessage + The standard message to apply to world info. + """ + entity_id = message_ambulance_team.get_ambulance_team_entity_id() + if entity_id is None: + return + entity = world_info.get_entity(entity_id) + if entity is None: + ambulance = AmbulanceTeam(entity_id.get_value()) + if (hp := message_ambulance_team.get_ambulance_team_hp()) is not None: + ambulance.set_hp(hp) + if (damege := message_ambulance_team.get_ambulance_team_damage()) is not None: + ambulance.set_damage(damege) + if ( + buriedness := message_ambulance_team.get_ambulance_team_buriedness() + ) is not None: + ambulance.set_buriedness(buriedness) + if (position := message_ambulance_team.get_ambulance_team_position()) is not None: + ambulance.set_position(position) + world_info.add_entity(ambulance) + else: + if isinstance(entity, AmbulanceTeam): + if (hp := message_ambulance_team.get_ambulance_team_hp()) is not None: + entity.set_hp(hp) + if (damege := message_ambulance_team.get_ambulance_team_damage()) is not None: + entity.set_damage(damege) + if ( + buriedness := message_ambulance_team.get_ambulance_team_buriedness() + ) is not None: + entity.set_buriedness(buriedness) + if (position := message_ambulance_team.get_ambulance_team_position()) is not None: + entity.set_position(position) def _apply_to_world_info_fire_brigade( - world_info: WorldInfo, - message_fire_brigade: MessageFireBrigade, + world_info: WorldInfo, + message_fire_brigade: MessageFireBrigade, ) -> None: - """ - Apply to world info for fire brigade. + """ + Apply to world info for fire brigade. - PARAMETERS - ---------- - world_info: WorldInfo - The world info to apply to. - standard_message: StandardMessage - The standard message to apply to world info. - """ - entity_id = message_fire_brigade.get_fire_brigade_entity_id() - if entity_id is None: - return - entity = world_info.get_entity(entity_id) - if entity is None: - fire_brigade = FireBrigade(entity_id.get_value()) - if (hp := message_fire_brigade.get_fire_brigade_hp()) is not None: - fire_brigade.set_hp(hp) - if (damage := message_fire_brigade.get_fire_brigade_damage()) is not None: - fire_brigade.set_damage(damage) - if ( - buriedness := message_fire_brigade.get_fire_brigade_buriedness() - ) is not None: - fire_brigade.set_buriedness(buriedness) - if (position := message_fire_brigade.get_fire_brigade_position()) is not None: - fire_brigade.set_position(position) - if (water := message_fire_brigade.get_fire_brigade_water()) is not None: - fire_brigade.set_water(water) - world_info.add_entity(fire_brigade) - else: - if isinstance(entity, FireBrigade): - if (hp := message_fire_brigade.get_fire_brigade_hp()) is not None: - entity.set_hp(hp) - if (damage := message_fire_brigade.get_fire_brigade_damage()) is not None: - entity.set_damage(damage) - if ( - buriedness := message_fire_brigade.get_fire_brigade_buriedness() - ) is not None: - entity.set_buriedness(buriedness) - if ( - position := message_fire_brigade.get_fire_brigade_position() - ) is not None: - entity.set_position(position) - if (water := message_fire_brigade.get_fire_brigade_water()) is not None: - entity.set_water(water) + PARAMETERS + ---------- + world_info: WorldInfo + The world info to apply to. + standard_message: StandardMessage + The standard message to apply to world info. + """ + entity_id = message_fire_brigade.get_fire_brigade_entity_id() + if entity_id is None: + return + entity = world_info.get_entity(entity_id) + if entity is None: + fire_brigade = FireBrigade(entity_id.get_value()) + if (hp := message_fire_brigade.get_fire_brigade_hp()) is not None: + fire_brigade.set_hp(hp) + if (damage := message_fire_brigade.get_fire_brigade_damage()) is not None: + fire_brigade.set_damage(damage) + if (buriedness := message_fire_brigade.get_fire_brigade_buriedness()) is not None: + fire_brigade.set_buriedness(buriedness) + if (position := message_fire_brigade.get_fire_brigade_position()) is not None: + fire_brigade.set_position(position) + if (water := message_fire_brigade.get_fire_brigade_water()) is not None: + fire_brigade.set_water(water) + world_info.add_entity(fire_brigade) + else: + if isinstance(entity, FireBrigade): + if (hp := message_fire_brigade.get_fire_brigade_hp()) is not None: + entity.set_hp(hp) + if (damage := message_fire_brigade.get_fire_brigade_damage()) is not None: + entity.set_damage(damage) + if (buriedness := message_fire_brigade.get_fire_brigade_buriedness()) is not None: + entity.set_buriedness(buriedness) + if (position := message_fire_brigade.get_fire_brigade_position()) is not None: + entity.set_position(position) + if (water := message_fire_brigade.get_fire_brigade_water()) is not None: + entity.set_water(water) def _apply_to_world_info_police_force( - world_info: WorldInfo, - message_police_force: MessagePoliceForce, + world_info: WorldInfo, + message_police_force: MessagePoliceForce, ) -> None: - """ - Apply to world info for police force. + """ + Apply to world info for police force. - PARAMETERS - ---------- - world_info: WorldInfo - The world info to apply to. - standard_message: StandardMessage - The standard message to apply to world info. - """ - entity_id = message_police_force.get_police_force_entity_id() - if entity_id is None: - return - entity = world_info.get_entity(entity_id) - if entity is None: - police_force = PoliceForce(entity_id.get_value()) - if (hp := message_police_force.get_police_force_hp()) is not None: - police_force.set_hp(hp) - if (damage := message_police_force.get_police_force_damage()) is not None: - police_force.set_damage(damage) - if ( - buriedness := message_police_force.get_police_force_buriedness() - ) is not None: - police_force.set_buriedness(buriedness) - if (position := message_police_force.get_police_force_position()) is not None: - police_force.set_position(position) - world_info.add_entity(police_force) - else: - if isinstance(entity, PoliceForce): - if (hp := message_police_force.get_police_force_hp()) is not None: - entity.set_hp(hp) - if (damage := message_police_force.get_police_force_damage()) is not None: - entity.set_damage(damage) - if ( - buriedness := message_police_force.get_police_force_buriedness() - ) is not None: - entity.set_buriedness(buriedness) - if ( - position := message_police_force.get_police_force_position() - ) is not None: - entity.set_position(position) + PARAMETERS + ---------- + world_info: WorldInfo + The world info to apply to. + standard_message: StandardMessage + The standard message to apply to world info. + """ + entity_id = message_police_force.get_police_force_entity_id() + if entity_id is None: + return + entity = world_info.get_entity(entity_id) + if entity is None: + police_force = PoliceForce(entity_id.get_value()) + if (hp := message_police_force.get_police_force_hp()) is not None: + police_force.set_hp(hp) + if (damage := message_police_force.get_police_force_damage()) is not None: + police_force.set_damage(damage) + if (buriedness := message_police_force.get_police_force_buriedness()) is not None: + police_force.set_buriedness(buriedness) + if (position := message_police_force.get_police_force_position()) is not None: + police_force.set_position(position) + world_info.add_entity(police_force) + else: + if isinstance(entity, PoliceForce): + if (hp := message_police_force.get_police_force_hp()) is not None: + entity.set_hp(hp) + if (damage := message_police_force.get_police_force_damage()) is not None: + entity.set_damage(damage) + if (buriedness := message_police_force.get_police_force_buriedness()) is not None: + entity.set_buriedness(buriedness) + if (position := message_police_force.get_police_force_position()) is not None: + entity.set_position(position) def _apply_to_world_info_civilian( - world_info: WorldInfo, - message_civilian: MessageCivilian, + world_info: WorldInfo, + message_civilian: MessageCivilian, ) -> None: - """ - Apply to world info for civilian. + """ + Apply to world info for civilian. - PARAMETERS - ---------- - world_info: WorldInfo - The world info to apply to. - standard_message: StandardMessage - The standard message to apply to world info. - """ - entity_id = message_civilian.get_civilian_entity_id() - if entity_id is None: - return - entity = world_info.get_entity(entity_id) - if entity is None: - civilian = Civilian(entity_id.get_value()) - if (hp := message_civilian.get_civilian_hp()) is not None: - civilian.set_hp(hp) - if (damage := message_civilian.get_civilian_damage()) is not None: - civilian.set_damage(damage) - if (buriedness := message_civilian.get_civilian_buriedness()) is not None: - civilian.set_buriedness(buriedness) - if (position := message_civilian.get_civilian_position()) is not None: - civilian.set_position(position) - world_info.add_entity(civilian) - else: - if isinstance(entity, Civilian): - if (hp := message_civilian.get_civilian_hp()) is not None: - entity.set_hp(hp) - if (damage := message_civilian.get_civilian_damage()) is not None: - entity.set_damage(damage) - if (buriedness := message_civilian.get_civilian_buriedness()) is not None: - entity.set_buriedness(buriedness) - if (position := message_civilian.get_civilian_position()) is not None: - entity.set_position(position) + PARAMETERS + ---------- + world_info: WorldInfo + The world info to apply to. + standard_message: StandardMessage + The standard message to apply to world info. + """ + entity_id = message_civilian.get_civilian_entity_id() + if entity_id is None: + return + entity = world_info.get_entity(entity_id) + if entity is None: + civilian = Civilian(entity_id.get_value()) + if (hp := message_civilian.get_civilian_hp()) is not None: + civilian.set_hp(hp) + if (damage := message_civilian.get_civilian_damage()) is not None: + civilian.set_damage(damage) + if (buriedness := message_civilian.get_civilian_buriedness()) is not None: + civilian.set_buriedness(buriedness) + if (position := message_civilian.get_civilian_position()) is not None: + civilian.set_position(position) + world_info.add_entity(civilian) + else: + if isinstance(entity, Civilian): + if (hp := message_civilian.get_civilian_hp()) is not None: + entity.set_hp(hp) + if (damage := message_civilian.get_civilian_damage()) is not None: + entity.set_damage(damage) + if (buriedness := message_civilian.get_civilian_buriedness()) is not None: + entity.set_buriedness(buriedness) + if (position := message_civilian.get_civilian_position()) is not None: + entity.set_position(position) def _apply_to_world_info_building( - world_info: WorldInfo, - message_building: MessageBuilding, + world_info: WorldInfo, + message_building: MessageBuilding, ) -> None: - """ - Apply to world info for building. + """ + Apply to world info for building. - PARAMETERS - ---------- - world_info: WorldInfo - The world info to apply to. - standard_message: StandardMessage - The standard message to apply to world info. - """ - entity_id = message_building.get_building_entity_id() - if entity_id is None: - return - entity = world_info.get_entity(entity_id) - if entity is None: - building = Building(entity_id.get_value()) - if (fieryness := message_building.get_building_fireyness()) is not None: - building.set_fieryness(fieryness) - if (brokenness := message_building.get_building_brokenness()) is not None: - building.set_brokenness(brokenness) - if (temperature := message_building.get_building_temperature()) is not None: - building.set_temperature(temperature) - world_info.add_entity(building) - else: - if isinstance(entity, Building): - if (fieryness := message_building.get_building_fireyness()) is not None: - entity.set_fieryness(fieryness) - if (brokenness := message_building.get_building_brokenness()) is not None: - entity.set_brokenness(brokenness) - if (temperature := message_building.get_building_temperature()) is not None: - entity.set_temperature(temperature) + PARAMETERS + ---------- + world_info: WorldInfo + The world info to apply to. + standard_message: StandardMessage + The standard message to apply to world info. + """ + entity_id = message_building.get_building_entity_id() + if entity_id is None: + return + entity = world_info.get_entity(entity_id) + if entity is None: + building = Building(entity_id.get_value()) + if (fieryness := message_building.get_building_fireyness()) is not None: + building.set_fieryness(fieryness) + if (brokenness := message_building.get_building_brokenness()) is not None: + building.set_brokenness(brokenness) + if (temperature := message_building.get_building_temperature()) is not None: + building.set_temperature(temperature) + world_info.add_entity(building) + else: + if isinstance(entity, Building): + if (fieryness := message_building.get_building_fireyness()) is not None: + entity.set_fieryness(fieryness) + if (brokenness := message_building.get_building_brokenness()) is not None: + entity.set_brokenness(brokenness) + if (temperature := message_building.get_building_temperature()) is not None: + entity.set_temperature(temperature) def _apply_to_world_info_road( - world_info: WorldInfo, - message_road: MessageRoad, + world_info: WorldInfo, + message_road: MessageRoad, ) -> None: - """ - Apply to world info for road. + """ + Apply to world info for road. - PARAMETERS - ---------- - world_info: WorldInfo - The world info to apply to. - standard_message: StandardMessage - The standard message to apply to world info. - """ - entity_id = message_road.get_road_entity_id() - if entity_id is None: - return - entity = world_info.get_entity(entity_id) - if not isinstance(entity, Road): - return + PARAMETERS + ---------- + world_info: WorldInfo + The world info to apply to. + standard_message: StandardMessage + The standard message to apply to world info. + """ + entity_id = message_road.get_road_entity_id() + if entity_id is None: + return + entity = world_info.get_entity(entity_id) + if not isinstance(entity, Road): + return - blockade_entity_id = message_road.get_road_blockade_entity_id() - if blockade_entity_id is None: - return + blockade_entity_id = message_road.get_road_blockade_entity_id() + if blockade_entity_id is None: + return - blockade = world_info.get_entity(blockade_entity_id) - if blockade is None: - road_blockade = Blockade(blockade_entity_id.get_value()) - if (repair_cost := message_road.get_road_blockade_repair_cost()) is not None: - road_blockade.set_repair_cost(repair_cost) - if (x := message_road.get_road_blockade_x()) is not None: - road_blockade.set_x(x) - if (y := message_road.get_road_blockade_y()) is not None: - road_blockade.set_y(y) - world_info.add_entity(road_blockade) - else: - if isinstance(blockade, Blockade): - if ( - repair_cost := message_road.get_road_blockade_repair_cost() - ) is not None: - blockade.set_repair_cost(repair_cost) - if (x := message_road.get_road_blockade_x()) is not None: - blockade.set_x(x) - if (y := message_road.get_road_blockade_y()) is not None: - blockade.set_y(y) + blockade = world_info.get_entity(blockade_entity_id) + if blockade is None: + road_blockade = Blockade(blockade_entity_id.get_value()) + if (repair_cost := message_road.get_road_blockade_repair_cost()) is not None: + road_blockade.set_repair_cost(repair_cost) + if (x := message_road.get_road_blockade_x()) is not None: + road_blockade.set_x(x) + if (y := message_road.get_road_blockade_y()) is not None: + road_blockade.set_y(y) + world_info.add_entity(road_blockade) + else: + if isinstance(blockade, Blockade): + if (repair_cost := message_road.get_road_blockade_repair_cost()) is not None: + blockade.set_repair_cost(repair_cost) + if (x := message_road.get_road_blockade_x()) is not None: + blockade.set_x(x) + if (y := message_road.get_road_blockade_y()) is not None: + blockade.set_y(y) diff --git a/src/adf_core_python/core/agent/communication/standard/utility/bitarray_with_exits_flag.py b/src/adf_core_python/core/agent/communication/standard/utility/bitarray_with_exits_flag.py index 64ab01b..ec6b3b3 100644 --- a/src/adf_core_python/core/agent/communication/standard/utility/bitarray_with_exits_flag.py +++ b/src/adf_core_python/core/agent/communication/standard/utility/bitarray_with_exits_flag.py @@ -7,66 +7,66 @@ def write_with_exist_flag( - bit_array: bitarray, value: Optional[int], bit_size: int + bit_array: bitarray, value: Optional[int], bit_size: int ) -> None: - """ - Write value to bit_array with an exist flag. - If value is None, write IS_NOT_EXIST_FLAG to bit_array. - If value is not None, write IS_EXIST_FLAG to bit_array and then write value to bit_array. + """ + Write value to bit_array with an exist flag. + If value is None, write IS_NOT_EXIST_FLAG to bit_array. + If value is not None, write IS_EXIST_FLAG to bit_array and then write value to bit_array. - PARAMETERS - ---------- - bit_array: bitarray - The bitarray to write to. - value: Optional[int] - The value to write. - bit_size: int - The number of bits to use to write value. + PARAMETERS + ---------- + bit_array: bitarray + The bitarray to write to. + value: Optional[int] + The value to write. + bit_size: int + The number of bits to use to write value. - RAISES - ------ - ValueError - If value is too large to fit into bit_size bits. - """ - if value is None: - bit_array.extend([IS_NOT_EXIST_FLAG]) - else: - bit_array.extend([IS_EXIST_FLAG]) - bit_value = bitarray(f"{value:0{bit_size}b}") - if len(bit_value) > bit_size: - raise ValueError(f"Value {value} is too large to fit into {bit_size} bits") - bit_array.extend(bit_value) + RAISES + ------ + ValueError + If value is too large to fit into bit_size bits. + """ + if value is None: + bit_array.extend([IS_NOT_EXIST_FLAG]) + else: + bit_array.extend([IS_EXIST_FLAG]) + bit_value = bitarray(f"{value:0{bit_size}b}") + if len(bit_value) > bit_size: + raise ValueError(f"Value {value} is too large to fit into {bit_size} bits") + bit_array.extend(bit_value) def read_with_exist_flag(bit_array: bitarray, size: int) -> Optional[int]: - """ - Read value from bit_array with an exist flag. - If the first bit is IS_NOT_EXIST_FLAG, return None. - If the first bit is IS_EXIST_FLAG, read and return value from bit_array. + """ + Read value from bit_array with an exist flag. + If the first bit is IS_NOT_EXIST_FLAG, return None. + If the first bit is IS_EXIST_FLAG, read and return value from bit_array. - PARAMETERS - ---------- - bit_array: bitarray - The bitarray to read from. - size: int - The number of bits to read to get value. + PARAMETERS + ---------- + bit_array: bitarray + The bitarray to read from. + size: int + The number of bits to read to get value. - RETURNS - ------- - Optional[int] - The value read from bit_array. + RETURNS + ------- + Optional[int] + The value read from bit_array. - RAISES - ------ - ValueError - If the first bit is not IS_EXIST_FLAG or IS_NOT_EXIST_FLAG. - """ - exist_flag = bit_array.pop(0) - if exist_flag == IS_NOT_EXIST_FLAG: - return None - elif exist_flag == IS_EXIST_FLAG: - value = int(bit_array[:size].to01(), 2) - del bit_array[:size] - return value - else: - raise ValueError("Invalid exist flag") + RAISES + ------ + ValueError + If the first bit is not IS_EXIST_FLAG or IS_NOT_EXIST_FLAG. + """ + exist_flag = bit_array.pop(0) + if exist_flag == IS_NOT_EXIST_FLAG: + return None + elif exist_flag == IS_EXIST_FLAG: + value = int(bit_array[:size].to01(), 2) + del bit_array[:size] + return value + else: + raise ValueError("Invalid exist flag") diff --git a/src/adf_core_python/core/agent/config/module_config.py b/src/adf_core_python/core/agent/config/module_config.py index 7c65649..25ff869 100644 --- a/src/adf_core_python/core/agent/config/module_config.py +++ b/src/adf_core_python/core/agent/config/module_config.py @@ -5,86 +5,86 @@ class ModuleConfig(Config): - DEFAULT_CONFIG_FILE_NAME: Final[str] = "config/module.yaml" + DEFAULT_CONFIG_FILE_NAME: Final[str] = "config/module.yaml" - def __init__(self, config_file_name: str = DEFAULT_CONFIG_FILE_NAME): - """ - Constructor + def __init__(self, config_file_name: str = DEFAULT_CONFIG_FILE_NAME): + """ + Constructor - Parameters - ---------- - config_file_name : str, optional - Configuration file name, by default DEFAULT_CONFIG_FILE_NAME + Parameters + ---------- + config_file_name : str, optional + Configuration file name, by default DEFAULT_CONFIG_FILE_NAME - Raises - ------ - FileNotFoundError - If config file not found - Exception - If error reading config file + Raises + ------ + FileNotFoundError + If config file not found + Exception + If error reading config file - Examples - -------- - >>> config = ModuleConfig("config/module.yaml") - >>> config.get_value("DefaultTacticsPoliceOffice.TargetAllocator") - "sample_team.module.complex.SamplePoliceTargetAllocator" - """ - super().__init__() - data = self._read_from_yaml(config_file_name) - flatten_data = self._flatten(data) - for key, value in flatten_data.items(): - self.set_value(key, value) + Examples + -------- + >>> config = ModuleConfig("config/module.yaml") + >>> config.get_value("DefaultTacticsPoliceOffice.TargetAllocator") + "sample_team.module.complex.SamplePoliceTargetAllocator" + """ + super().__init__() + data = self._read_from_yaml(config_file_name) + flatten_data = self._flatten(data) + for key, value in flatten_data.items(): + self.set_value(key, value) - def _read_from_yaml(self, file_name: str) -> dict[str, Any]: - """ - Read configuration from yaml file + def _read_from_yaml(self, file_name: str) -> dict[str, Any]: + """ + Read configuration from yaml file - Parameters - ---------- - file_name : str - Configuration file name + Parameters + ---------- + file_name : str + Configuration file name - Returns - ------- - dict[str, Any] - Configuration data - """ - try: - with open(file_name, mode="r", encoding="utf-8") as file: - data = safe_load(file) - except FileNotFoundError: - raise FileNotFoundError(f"Config file not found: {file_name}") - except Exception as e: - raise Exception(f"Error reading config file: {file_name}, {e}") + Returns + ------- + dict[str, Any] + Configuration data + """ + try: + with open(file_name, mode="r", encoding="utf-8") as file: + data = safe_load(file) + except FileNotFoundError: + raise FileNotFoundError(f"Config file not found: {file_name}") + except Exception as e: + raise Exception(f"Error reading config file: {file_name}, {e}") - return data + return data - def _flatten( - self, data: dict[str, Any], parent_key: str = "", sep: str = "." - ) -> dict[str, Any]: - """ - Flatten nested dictionary to a single level dictionary + def _flatten( + self, data: dict[str, Any], parent_key: str = "", sep: str = "." + ) -> dict[str, Any]: + """ + Flatten nested dictionary to a single level dictionary - Parameters - ---------- - data : dict[str, Any] - Nested dictionary - parent_key : str, optional - Parent key, by default "" - sep : str, optional - Separator, by default "." + Parameters + ---------- + data : dict[str, Any] + Nested dictionary + parent_key : str, optional + Parent key, by default "" + sep : str, optional + Separator, by default "." - Returns - ------- - dict[str, Any] - Flattened dictionary - """ - flatten_data: dict[str, Any] = {} - for key, value in data.items(): - new_key = f"{parent_key}{sep}{key}" if parent_key else key - if isinstance(value, dict): - v: dict[str, Any] = value - flatten_data.update(self._flatten(v, new_key, sep=sep)) - else: - flatten_data[new_key] = value - return flatten_data + Returns + ------- + dict[str, Any] + Flattened dictionary + """ + flatten_data: dict[str, Any] = {} + for key, value in data.items(): + new_key = f"{parent_key}{sep}{key}" if parent_key else key + if isinstance(value, dict): + v: dict[str, Any] = value + flatten_data.update(self._flatten(v, new_key, sep=sep)) + else: + flatten_data[new_key] = value + return flatten_data diff --git a/src/adf_core_python/core/agent/develop/develop_data.py b/src/adf_core_python/core/agent/develop/develop_data.py index 7d7c551..35bcb13 100644 --- a/src/adf_core_python/core/agent/develop/develop_data.py +++ b/src/adf_core_python/core/agent/develop/develop_data.py @@ -3,68 +3,68 @@ class DevelopData: - DEFAULT_FILE_NAME: Final[str] = "config/develop.json" + DEFAULT_FILE_NAME: Final[str] = "config/develop.json" - def __init__( - self, is_develop_mode: bool = False, file_name: str = DEFAULT_FILE_NAME - ) -> None: - """ - Constructor - """ - self._develop_data: dict[str, Any] = self._set_data_from_json(file_name) - self._is_develop_mode: bool = is_develop_mode + def __init__( + self, is_develop_mode: bool = False, file_name: str = DEFAULT_FILE_NAME + ) -> None: + """ + Constructor + """ + self._develop_data: dict[str, Any] = self._set_data_from_json(file_name) + self._is_develop_mode: bool = is_develop_mode - self._set_data_from_json(file_name) + self._set_data_from_json(file_name) - def _set_data_from_json(self, file_name: str) -> dict[str, Any]: - """ - Set data from json + def _set_data_from_json(self, file_name: str) -> dict[str, Any]: + """ + Set data from json - Parameters - ---------- - file_name : str, optional - Develop file name, by default DEFAULT_FILE_NAME + Parameters + ---------- + file_name : str, optional + Develop file name, by default DEFAULT_FILE_NAME - Returns - ------- - dict[str, Any] - Develop data - """ - try: - with open(file_name, mode="r", encoding="utf-8") as file: - return json.load(file) - except FileNotFoundError: - raise FileNotFoundError(f"Develop file not found: {file_name}") - except Exception as e: - raise Exception(f"Error reading develop file: {file_name}, {e}") + Returns + ------- + dict[str, Any] + Develop data + """ + try: + with open(file_name, mode="r", encoding="utf-8") as file: + return json.load(file) + except FileNotFoundError: + raise FileNotFoundError(f"Develop file not found: {file_name}") + except Exception as e: + raise Exception(f"Error reading develop file: {file_name}, {e}") - def is_develop_mode(self) -> bool: - """ - Check if develop mode is enabled + def is_develop_mode(self) -> bool: + """ + Check if develop mode is enabled - Returns - ------- - bool - True if develop mode is enabled - """ - return self._is_develop_mode + Returns + ------- + bool + True if develop mode is enabled + """ + return self._is_develop_mode - def get_value(self, key: str, default_value: Any = None) -> Any: - """ - Get value from develop data - If develop mode is disabled, return default value + def get_value(self, key: str, default_value: Any = None) -> Any: + """ + Get value from develop data + If develop mode is disabled, return default value - Parameters - ---------- - key : str - Key + Parameters + ---------- + key : str + Key - Returns - ------- - Any - Value - """ - if not self._is_develop_mode: - return default_value + Returns + ------- + Any + Value + """ + if not self._is_develop_mode: + return default_value - return self._develop_data.get(key, default_value) + return self._develop_data.get(key, default_value) diff --git a/src/adf_core_python/core/agent/info/agent_info.py b/src/adf_core_python/core/agent/info/agent_info.py index 5dea6a5..20658bb 100644 --- a/src/adf_core_python/core/agent/info/agent_info.py +++ b/src/adf_core_python/core/agent/info/agent_info.py @@ -13,179 +13,179 @@ from adf_core_python.core.agent.action.action import Action if TYPE_CHECKING: - from adf_core_python.core.agent.agent import Agent + from adf_core_python.core.agent.agent import Agent class AgentInfo: - def __init__(self, agent: Agent, world_model: WorldModel): - self._agent: Agent = agent - self._world_model: WorldModel = world_model - self._time: int = 0 - self._action_history: dict[int, Action] = {} - self._heard_commands: list[Command] = [] - self._change_set: ChangeSet = ChangeSet() - self._start_think_time: float = 0.0 - - def set_time(self, time: int) -> None: - """ - Set the current time of the agent - - Parameters - ---------- - time : int - Current time - """ - self._time = time - - def get_time(self) -> int: - """ - Get the current time of the agent - - Returns - ------- - int - Current time - """ - return self._time - - def set_heard_commands(self, heard_commands: list[Command]) -> None: - """ - Set the heard commands - - Parameters - ---------- - heard_commands : list[Command] - Heard commands - """ - self._heard_commands = heard_commands - - def get_heard_commands(self) -> list[Command]: - """ - Get the heard commands - - Returns - ------- - list[Command] - Heard commands - """ - return self._heard_commands - - def get_entity_id(self) -> EntityID: - """ - Get the entity ID of the agent - - Returns - ------- - EntityID - Entity ID of the agent - """ - # TODO: Agent class should return EntityID instead of EntityID | None - return self._agent.get_entity_id() - - def get_myself(self) -> Entity | None: - """ - Get the entity of the agent - - Returns - ------- - Entity - Entity of the agent - """ - return self._world_model.get_entity(self.get_entity_id()) - - def get_position_entity_id(self) -> EntityID | None: - """ - Get the position entity ID of the agent - - Returns - ------- - EntityID - Position entity ID of the agent - """ - entity = self._world_model.get_entity(self.get_entity_id()) - if entity is None: - return None - - if isinstance(entity, Human): - return entity.get_position() - else: - return entity.get_entity_id() - - def set_change_set(self, change_set: ChangeSet) -> None: - """ - Set the change set - - Parameters - ---------- - change_set : ChangeSet - Change set - """ - self._change_set = change_set - - def get_change_set(self) -> ChangeSet: - """ - Get the change set - - Returns - ------- - ChangeSet - Change set - """ - return self._change_set - - def some_one_on_board(self) -> Human | None: - """ - Get the human if someone is on board - - Returns - ------- - Human | None - Human if someone is on board, None otherwise - """ - entity_id: EntityID = self.get_entity_id() - for entity in self._world_model.get_entities(): - if isinstance(entity, Civilian): - if entity.get_position() == entity_id: - return entity - return None - - def get_executed_action(self, time: int) -> Action | None: - """ - Get the executed action at the given time - - Parameters - ---------- - time : int - Time - """ - return self._action_history.get(time) - - def set_executed_action(self, time: int, action: Action) -> None: - """ - Set the executed action at the given time - - Parameters - ---------- - time : int - Time - action : Action - Executed action - """ - self._action_history[time] = action - - def record_think_start_time(self) -> None: - """ - Record the start time of thinking - """ - self._start_think_time = time() - - def get_think_time(self) -> float: - """ - Get the time taken for thinking - - Returns - ------- - float - Time taken for thinking - """ - return time() - self._start_think_time + def __init__(self, agent: Agent, world_model: WorldModel): + self._agent: Agent = agent + self._world_model: WorldModel = world_model + self._time: int = 0 + self._action_history: dict[int, Action] = {} + self._heard_commands: list[Command] = [] + self._change_set: ChangeSet = ChangeSet() + self._start_think_time: float = 0.0 + + def set_time(self, time: int) -> None: + """ + Set the current time of the agent + + Parameters + ---------- + time : int + Current time + """ + self._time = time + + def get_time(self) -> int: + """ + Get the current time of the agent + + Returns + ------- + int + Current time + """ + return self._time + + def set_heard_commands(self, heard_commands: list[Command]) -> None: + """ + Set the heard commands + + Parameters + ---------- + heard_commands : list[Command] + Heard commands + """ + self._heard_commands = heard_commands + + def get_heard_commands(self) -> list[Command]: + """ + Get the heard commands + + Returns + ------- + list[Command] + Heard commands + """ + return self._heard_commands + + def get_entity_id(self) -> EntityID: + """ + Get the entity ID of the agent + + Returns + ------- + EntityID + Entity ID of the agent + """ + # TODO: Agent class should return EntityID instead of EntityID | None + return self._agent.get_entity_id() + + def get_myself(self) -> Entity | None: + """ + Get the entity of the agent + + Returns + ------- + Entity + Entity of the agent + """ + return self._world_model.get_entity(self.get_entity_id()) + + def get_position_entity_id(self) -> EntityID | None: + """ + Get the position entity ID of the agent + + Returns + ------- + EntityID + Position entity ID of the agent + """ + entity = self._world_model.get_entity(self.get_entity_id()) + if entity is None: + return None + + if isinstance(entity, Human): + return entity.get_position() + else: + return entity.get_entity_id() + + def set_change_set(self, change_set: ChangeSet) -> None: + """ + Set the change set + + Parameters + ---------- + change_set : ChangeSet + Change set + """ + self._change_set = change_set + + def get_change_set(self) -> ChangeSet: + """ + Get the change set + + Returns + ------- + ChangeSet + Change set + """ + return self._change_set + + def some_one_on_board(self) -> Human | None: + """ + Get the human if someone is on board + + Returns + ------- + Human | None + Human if someone is on board, None otherwise + """ + entity_id: EntityID = self.get_entity_id() + for entity in self._world_model.get_entities(): + if isinstance(entity, Civilian): + if entity.get_position() == entity_id: + return entity + return None + + def get_executed_action(self, time: int) -> Action | None: + """ + Get the executed action at the given time + + Parameters + ---------- + time : int + Time + """ + return self._action_history.get(time) + + def set_executed_action(self, time: int, action: Action) -> None: + """ + Set the executed action at the given time + + Parameters + ---------- + time : int + Time + action : Action + Executed action + """ + self._action_history[time] = action + + def record_think_start_time(self) -> None: + """ + Record the start time of thinking + """ + self._start_think_time = time() + + def get_think_time(self) -> float: + """ + Get the time taken for thinking + + Returns + ------- + float + Time taken for thinking + """ + return time() - self._start_think_time diff --git a/src/adf_core_python/core/agent/info/scenario_info.py b/src/adf_core_python/core/agent/info/scenario_info.py index 29f4244..6e26568 100644 --- a/src/adf_core_python/core/agent/info/scenario_info.py +++ b/src/adf_core_python/core/agent/info/scenario_info.py @@ -7,111 +7,111 @@ class Mode(IntEnum): - NON_PRECOMPUTE = 0 - PRECOMPUTED = 1 - PRECOMPUTATION = 2 + NON_PRECOMPUTE = 0 + PRECOMPUTED = 1 + PRECOMPUTATION = 2 class ScenarioInfoKeys: - KERNEL_PERCEPTION = "kernel.perception" - PERCEPTION_LOS_PRECISION_HP = "perception.los.precision.hp" - CLEAR_REPAIR_RAD = "clear.repair.rad" - FIRE_TANK_REFILL_HYDRANT_RATE = "fire.tank.refill_hydrant_rate" - SCENARIO_AGENTS_PF = "scenario.agents.pf" - SCENARIO_AGENTS_FS = "scenario.agents.fs" - VOICE_MESSAGES_SIZE = "comms.channels.0.messages.size" - FIRE_TANK_REFILL_RATE = "fire.tank.refill_rate" - KERNEL_TIMESTEPS = "kernel.timesteps" - FIRE_EXTINGUISH_MAX_SUM = "fire.extinguish.max-sum" - COMMUNICATION_CHANNELS_MAX_PLATOON = "comms.channels.max.platoon" - KERNEL_AGENTS_THINK_TIME = "kernel.agents.think-time" - FIRE_TANK_MAXIMUM = "fire.tank.maximum" - CLEAR_REPAIR_RATE = "clear.repair.rate" - KERNEL_STARTUP_CONNECT_TIME = "kernel.startup.connect-time" - KERNEL_HOST = "kernel.host" - SCENARIO_AGENTS_AT = "scenario.agents.at" - PERCEPTION_LOS_MAX_DISTANCE = "perception.los.max-distance" - SCENARIO_AGENTS_FB = "scenario.agents.fb" - SCENARIO_AGENTS_PO = "scenario.agents.po" - KERNEL_COMMUNICATION_MODEL = "kernel.communication-model" - PERCEPTION_LOS_PRECISION_DAMAGE = "perception.los.precision.damage" - SCENARIO_AGENTS_AC = "scenario.agents.ac" - COMMUNICATION_CHANNELS_MAX_OFFICE = "comms.channels.max.centre" - FIRE_EXTINGUISH_MAX_DISTANCE = "fire.extinguish.max-distance" - KERNEL_AGENTS_IGNOREUNTIL = "kernel.agents.ignoreuntil" - CLEAR_REPAIR_DISTANCE = "clear.repair.distance" - COMMUNICATION_CHANNELS_COUNT = "comms.channels.count" + KERNEL_PERCEPTION = "kernel.perception" + PERCEPTION_LOS_PRECISION_HP = "perception.los.precision.hp" + CLEAR_REPAIR_RAD = "clear.repair.rad" + FIRE_TANK_REFILL_HYDRANT_RATE = "fire.tank.refill_hydrant_rate" + SCENARIO_AGENTS_PF = "scenario.agents.pf" + SCENARIO_AGENTS_FS = "scenario.agents.fs" + VOICE_MESSAGES_SIZE = "comms.channels.0.messages.size" + FIRE_TANK_REFILL_RATE = "fire.tank.refill_rate" + KERNEL_TIMESTEPS = "kernel.timesteps" + FIRE_EXTINGUISH_MAX_SUM = "fire.extinguish.max-sum" + COMMUNICATION_CHANNELS_MAX_PLATOON = "comms.channels.max.platoon" + KERNEL_AGENTS_THINK_TIME = "kernel.agents.think-time" + FIRE_TANK_MAXIMUM = "fire.tank.maximum" + CLEAR_REPAIR_RATE = "clear.repair.rate" + KERNEL_STARTUP_CONNECT_TIME = "kernel.startup.connect-time" + KERNEL_HOST = "kernel.host" + SCENARIO_AGENTS_AT = "scenario.agents.at" + PERCEPTION_LOS_MAX_DISTANCE = "perception.los.max-distance" + SCENARIO_AGENTS_FB = "scenario.agents.fb" + SCENARIO_AGENTS_PO = "scenario.agents.po" + KERNEL_COMMUNICATION_MODEL = "kernel.communication-model" + PERCEPTION_LOS_PRECISION_DAMAGE = "perception.los.precision.damage" + SCENARIO_AGENTS_AC = "scenario.agents.ac" + COMMUNICATION_CHANNELS_MAX_OFFICE = "comms.channels.max.centre" + FIRE_EXTINGUISH_MAX_DISTANCE = "fire.extinguish.max-distance" + KERNEL_AGENTS_IGNOREUNTIL = "kernel.agents.ignoreuntil" + CLEAR_REPAIR_DISTANCE = "clear.repair.distance" + COMMUNICATION_CHANNELS_COUNT = "comms.channels.count" class ScenarioInfo: - def __init__(self, config: Config, mode: Mode): - """ - Constructor - - Parameters - ---------- - config : Config - Configuration - mode : Mode - Mode of the scenario - """ - self._config: Config = config - self._mode: Mode = mode - - def set_config(self, config: Config) -> None: - """ - Set the configuration - - Parameters - ---------- - config : Config - Configuration - """ - self._config = config - - def get_config(self) -> Config: - """ - Get the configuration - - Returns - ------- - Config - Configuration - """ - return self._config - - def get_mode(self) -> Mode: - """ - Get the mode of the scenario - - Returns - ------- - Mode - Mode of the scenario - """ - return self._mode - - def get_value(self, key: str, default: T) -> T: - """ - Get the value of the configuration - - Parameters - ---------- - key : str - Key of the configuration - default : Any - Default value of the configuration - - Returns - ------- - Any - Value of the configuration - """ - value = self._config.get_value(key, default) - if not isinstance(value, type(default)): - try: - return type(default)(value) # type: ignore - except (ValueError, TypeError): - # 型変換に失敗した場合はそのままデフォルト値を返す - return default - return value + def __init__(self, config: Config, mode: Mode): + """ + Constructor + + Parameters + ---------- + config : Config + Configuration + mode : Mode + Mode of the scenario + """ + self._config: Config = config + self._mode: Mode = mode + + def set_config(self, config: Config) -> None: + """ + Set the configuration + + Parameters + ---------- + config : Config + Configuration + """ + self._config = config + + def get_config(self) -> Config: + """ + Get the configuration + + Returns + ------- + Config + Configuration + """ + return self._config + + def get_mode(self) -> Mode: + """ + Get the mode of the scenario + + Returns + ------- + Mode + Mode of the scenario + """ + return self._mode + + def get_value(self, key: str, default: T) -> T: + """ + Get the value of the configuration + + Parameters + ---------- + key : str + Key of the configuration + default : Any + Default value of the configuration + + Returns + ------- + Any + Value of the configuration + """ + value = self._config.get_value(key, default) + if not isinstance(value, type(default)): + try: + return type(default)(value) # type: ignore + except (ValueError, TypeError): + # 型変換に失敗した場合はそのままデフォルト値を返す + return default + return value diff --git a/src/adf_core_python/core/agent/info/world_info.py b/src/adf_core_python/core/agent/info/world_info.py index acea5da..0fce3a7 100644 --- a/src/adf_core_python/core/agent/info/world_info.py +++ b/src/adf_core_python/core/agent/info/world_info.py @@ -9,206 +9,204 @@ class WorldInfo: - def __init__(self, world_model: WorldModel): - self._world_model: WorldModel = world_model - self._time: int = 0 - self._is_run_rollback: bool = False - self._rollback: dict[EntityID, dict[int, dict[int, Any]]] = {} - self._change_set: ChangeSet - - def get_world_model(self) -> WorldModel: - """ - Get the world model - - Returns - ------- - WorldModel - World model - """ - return self._world_model - - def set_change_set(self, change_set: ChangeSet) -> None: - """ - Set the change set - - Parameters - ---------- - change_set : ChangeSet - Change set - """ - self._change_set = change_set - - def get_entity(self, entity_id: EntityID) -> Optional[Entity]: - """ - Get the entity - - Parameters - ---------- - entity_id : EntityID - Entity ID - - Returns - ------- - Optional[Entity] - Entity - """ - return self._world_model.get_entity(entity_id) - - def get_entity_ids_of_types( - self, entity_types: list[type[Entity]] - ) -> list[EntityID]: - """ - Get the entity IDs of the specified types - - Parameters - ---------- - entity_types : list[type[Entity]] - List of entity types - - Returns - ------- - list[EntityID] - Entity IDs - """ - entity_ids: list[EntityID] = [] - for entity in self._world_model.get_entities(): - if any(isinstance(entity, entity_type) for entity_type in entity_types): - entity_ids.append(entity.get_entity_id()) - - return entity_ids - - def get_entities_of_types(self, entity_types: list[type[Entity]]) -> list[Entity]: - """ - Get the entities of the specified types - - Parameters - ---------- - entity_types : list[type[Entity]] - List of entity types - - Returns - ------- - list[Entity] - Entities - """ - entities: list[Entity] = [] - for entity in self._world_model.get_entities(): - if any(isinstance(entity, entity_type) for entity_type in entity_types): - entities.append(entity) - - return entities - - def get_distance(self, entity_id1: EntityID, entity_id2: EntityID) -> float: - """ - Get the distance between two entities - - Parameters - ---------- - entity_id1 : EntityID - Entity ID 1 - entity_id2 : EntityID - Entity ID 2 - - Returns - ------- - float - Distance - - Raises - ------ - ValueError - If one or both entities are invalid or the location is invalid - """ - entity1: Optional[Entity] = self.get_entity(entity_id1) - entity2: Optional[Entity] = self.get_entity(entity_id2) - if entity1 is None or entity2 is None: - raise ValueError( - f"One or both entities are invalid: entity_id1={entity_id1}, entity_id2={entity_id2}, entity1={entity1}, entity2={entity2}" - ) - - location1_x, location1_y = entity1.get_x(), entity1.get_y() - location2_x, location2_y = entity2.get_x(), entity2.get_y() - if ( - location1_x is None - or location1_y is None - or location2_x is None - or location2_y is None - ): - raise ValueError( - f"Invalid location: entity_id1={entity_id1}, entity_id2={entity_id2}, location1_x={location1_x}, location1_y={location1_y}, location2_x={location2_x}, location2_y={location2_y}" - ) - - distance: float = ( - (location1_x - location2_x) ** 2 + (location1_y - location2_y) ** 2 - ) ** 0.5 - - return distance - - def get_entity_position(self, entity_id: EntityID) -> EntityID | None: - """ - Get the entity position - - Parameters - ---------- - entity_id : EntityID - Entity ID - - Returns - ------- - EntityID - Entity position - - Raises - ------ - ValueError - If the entity is invalid - """ - entity = self.get_entity(entity_id) - if entity is None: - raise ValueError(f"Invalid entity: entity_id={entity_id}, entity={entity}") - if isinstance(entity, Area): - return entity.get_entity_id() - if isinstance(entity, Human): - return entity.get_position() - if isinstance(entity, Blockade): - return entity.get_position() - raise ValueError(f"Invalid entity type: entity_id={entity_id}, entity={entity}") - - def get_change_set(self) -> ChangeSet: - """ - Get the change set - - Returns - ------- - ChangeSet - Change set - """ - return self._change_set - - def get_blockades(self, area: Area) -> set[Blockade]: - """ - Get the blockades in the area - - Returns - ------- - ChangeSet - Blockade - """ - blockades = set() - if blockade_entity_ids := area.get_blockades(): - for blockade_entity_id in blockade_entity_ids: - blockades_entity = self.get_entity(blockade_entity_id) - if isinstance(blockades_entity, Blockade): - blockades.add(blockades_entity) - return blockades - - def add_entity(self, entity: Entity) -> None: - """ - Add the entity - - Parameters - ---------- - entity : Entity - Entity - """ - self._world_model.add_entity(entity) + def __init__(self, world_model: WorldModel): + self._world_model: WorldModel = world_model + self._time: int = 0 + self._is_run_rollback: bool = False + self._rollback: dict[EntityID, dict[int, dict[int, Any]]] = {} + self._change_set: ChangeSet + + def get_world_model(self) -> WorldModel: + """ + Get the world model + + Returns + ------- + WorldModel + World model + """ + return self._world_model + + def set_change_set(self, change_set: ChangeSet) -> None: + """ + Set the change set + + Parameters + ---------- + change_set : ChangeSet + Change set + """ + self._change_set = change_set + + def get_entity(self, entity_id: EntityID) -> Optional[Entity]: + """ + Get the entity + + Parameters + ---------- + entity_id : EntityID + Entity ID + + Returns + ------- + Optional[Entity] + Entity + """ + return self._world_model.get_entity(entity_id) + + def get_entity_ids_of_types(self, entity_types: list[type[Entity]]) -> list[EntityID]: + """ + Get the entity IDs of the specified types + + Parameters + ---------- + entity_types : list[type[Entity]] + List of entity types + + Returns + ------- + list[EntityID] + Entity IDs + """ + entity_ids: list[EntityID] = [] + for entity in self._world_model.get_entities(): + if any(isinstance(entity, entity_type) for entity_type in entity_types): + entity_ids.append(entity.get_entity_id()) + + return entity_ids + + def get_entities_of_types(self, entity_types: list[type[Entity]]) -> list[Entity]: + """ + Get the entities of the specified types + + Parameters + ---------- + entity_types : list[type[Entity]] + List of entity types + + Returns + ------- + list[Entity] + Entities + """ + entities: list[Entity] = [] + for entity in self._world_model.get_entities(): + if any(isinstance(entity, entity_type) for entity_type in entity_types): + entities.append(entity) + + return entities + + def get_distance(self, entity_id1: EntityID, entity_id2: EntityID) -> float: + """ + Get the distance between two entities + + Parameters + ---------- + entity_id1 : EntityID + Entity ID 1 + entity_id2 : EntityID + Entity ID 2 + + Returns + ------- + float + Distance + + Raises + ------ + ValueError + If one or both entities are invalid or the location is invalid + """ + entity1: Optional[Entity] = self.get_entity(entity_id1) + entity2: Optional[Entity] = self.get_entity(entity_id2) + if entity1 is None or entity2 is None: + raise ValueError( + f"One or both entities are invalid: entity_id1={entity_id1}, entity_id2={entity_id2}, entity1={entity1}, entity2={entity2}" + ) + + location1_x, location1_y = entity1.get_x(), entity1.get_y() + location2_x, location2_y = entity2.get_x(), entity2.get_y() + if ( + location1_x is None + or location1_y is None + or location2_x is None + or location2_y is None + ): + raise ValueError( + f"Invalid location: entity_id1={entity_id1}, entity_id2={entity_id2}, location1_x={location1_x}, location1_y={location1_y}, location2_x={location2_x}, location2_y={location2_y}" + ) + + distance: float = ( + (location1_x - location2_x) ** 2 + (location1_y - location2_y) ** 2 + ) ** 0.5 + + return distance + + def get_entity_position(self, entity_id: EntityID) -> EntityID | None: + """ + Get the entity position + + Parameters + ---------- + entity_id : EntityID + Entity ID + + Returns + ------- + EntityID + Entity position + + Raises + ------ + ValueError + If the entity is invalid + """ + entity = self.get_entity(entity_id) + if entity is None: + raise ValueError(f"Invalid entity: entity_id={entity_id}, entity={entity}") + if isinstance(entity, Area): + return entity.get_entity_id() + if isinstance(entity, Human): + return entity.get_position() + if isinstance(entity, Blockade): + return entity.get_position() + raise ValueError(f"Invalid entity type: entity_id={entity_id}, entity={entity}") + + def get_change_set(self) -> ChangeSet: + """ + Get the change set + + Returns + ------- + ChangeSet + Change set + """ + return self._change_set + + def get_blockades(self, area: Area) -> set[Blockade]: + """ + Get the blockades in the area + + Returns + ------- + ChangeSet + Blockade + """ + blockades = set() + if blockade_entity_ids := area.get_blockades(): + for blockade_entity_id in blockade_entity_ids: + blockades_entity = self.get_entity(blockade_entity_id) + if isinstance(blockades_entity, Blockade): + blockades.add(blockades_entity) + return blockades + + def add_entity(self, entity: Entity) -> None: + """ + Add the entity + + Parameters + ---------- + entity : Entity + Entity + """ + self._world_model.add_entity(entity) diff --git a/src/adf_core_python/core/agent/module/module_manager.py b/src/adf_core_python/core/agent/module/module_manager.py index 96c506b..6eafb42 100644 --- a/src/adf_core_python/core/agent/module/module_manager.py +++ b/src/adf_core_python/core/agent/module/module_manager.py @@ -7,14 +7,14 @@ from adf_core_python.core.component.centralized.command_executor import CommandExecutor from adf_core_python.core.component.centralized.command_picker import CommandPicker from adf_core_python.core.component.communication.channel_subscriber import ( - ChannelSubscriber, + ChannelSubscriber, ) from adf_core_python.core.component.communication.message_coordinator import ( - MessageCoordinator, + MessageCoordinator, ) from adf_core_python.core.component.module.abstract_module import AbstractModule from adf_core_python.core.gateway.component.module.gateway_abstract_module import ( - GatewayAbstractModule, + GatewayAbstractModule, ) from adf_core_python.core.gateway.gateway_agent import GatewayAgent from adf_core_python.core.gateway.gateway_module import GatewayModule @@ -22,248 +22,248 @@ from adf_core_python.core.logger.logger import get_agent_logger if TYPE_CHECKING: - from adf_core_python.core.agent.config.module_config import ModuleConfig - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.config.module_config import ModuleConfig + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo class ModuleManager: - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_config: ModuleConfig, - develop_data: DevelopData, - gateway_agent: Optional[GatewayAgent] = None, - ) -> None: - self._agent_info = agent_info - self._world_info = world_info - self._scenario_info = scenario_info - self._module_config = module_config - self._develop_data = develop_data - self._gateway_agent = gateway_agent - self._modules: dict[str, AbstractModule] = {} - self._actions: dict[str, ExtendAction] = {} - - self._executors: dict[str, Any] = {} - self._pickers: dict[str, Any] = {} - self._channel_subscribers: dict[str, Any] = {} - self._message_coordinators: dict[str, Any] = {} - - self._module_dict: ModuleDict = ModuleDict() - - self._logger = get_agent_logger( - f"{self.__class__.__module__}.{self.__class__.__qualname__}", + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_config: ModuleConfig, + develop_data: DevelopData, + gateway_agent: Optional[GatewayAgent] = None, + ) -> None: + self._agent_info = agent_info + self._world_info = world_info + self._scenario_info = scenario_info + self._module_config = module_config + self._develop_data = develop_data + self._gateway_agent = gateway_agent + self._modules: dict[str, AbstractModule] = {} + self._actions: dict[str, ExtendAction] = {} + + self._executors: dict[str, Any] = {} + self._pickers: dict[str, Any] = {} + self._channel_subscribers: dict[str, Any] = {} + self._message_coordinators: dict[str, Any] = {} + + self._module_dict: ModuleDict = ModuleDict() + + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + + def get_module( + self, module_name: str, default_module_class_name: str + ) -> AbstractModule: + instance = self._modules.get(module_name) + if instance is not None: + return instance + + class_name = self._module_config.get_value(module_name) + if class_name is None: + self._logger.warning( + f"Module {module_name} not found in config. Using default module {default_module_class_name}" + ) + + if class_name is not None: + try: + module_class: type = self._load_module(class_name) + if issubclass(module_class, AbstractModule): + instance = module_class( self._agent_info, + self._world_info, + self._scenario_info, + self, + self._develop_data, + ) + self._modules[module_name] = instance + return instance + except ModuleNotFoundError: + self._logger.warning( + f"Module {module_name} not found in python. " + f"If gateway flag is active, using module {module_name} in java" ) - def get_module( - self, module_name: str, default_module_class_name: str - ) -> AbstractModule: - instance = self._modules.get(module_name) - if instance is not None: - return instance - - class_name = self._module_config.get_value(module_name) - if class_name is None: - self._logger.warning( - f"Module {module_name} not found in config. Using default module {default_module_class_name}" - ) - - if class_name is not None: - try: - module_class: type = self._load_module(class_name) - if issubclass(module_class, AbstractModule): - instance = module_class( - self._agent_info, - self._world_info, - self._scenario_info, - self, - self._develop_data, - ) - self._modules[module_name] = instance - return instance - except ModuleNotFoundError: - self._logger.warning( - f"Module {module_name} not found in python. " - f"If gateway flag is active, using module {module_name} in java" - ) - - if isinstance(self._gateway_agent, GatewayAgent): - gateway_module = GatewayModule(self._gateway_agent) - java_class_name = gateway_module.initialize(module_name, "") - class_name = self._module_dict[java_class_name] - if class_name is not None: - module_class = self._load_module(class_name) - if issubclass(module_class, GatewayAbstractModule): - instance = module_class( - self._agent_info, - self._world_info, - self._scenario_info, - self, - self._develop_data, - gateway_module, - ) - self._modules[module_name] = instance - return instance - - class_name = default_module_class_name + if isinstance(self._gateway_agent, GatewayAgent): + gateway_module = GatewayModule(self._gateway_agent) + java_class_name = gateway_module.initialize(module_name, "") + class_name = self._module_dict[java_class_name] if class_name is not None: - module_class = self._load_module(class_name) - if issubclass(module_class, AbstractModule): - instance = module_class( - self._agent_info, - self._world_info, - self._scenario_info, - self, - self._develop_data, - ) - self._modules[module_name] = instance - return instance - - raise RuntimeError(f"Module {class_name} is not a subclass of AbstractModule") - - def get_extend_action( - self, action_name: str, default_action_class_name: str - ) -> ExtendAction: - class_name = self._module_config.get_value(action_name) - if class_name is None: - self._logger.warning( - f"Action {action_name} not found in config. Using default action {default_action_class_name}" - ) - class_name = default_action_class_name - - action_class: type = self._load_module(class_name) - - instance = self._actions.get(action_name) - if instance is not None: - return instance - - if issubclass(action_class, ExtendAction): - instance = action_class( - self._agent_info, - self._world_info, - self._scenario_info, - self, - self._develop_data, - ) - self._actions[action_name] = instance - return instance - - raise RuntimeError(f"Action {class_name} is not a subclass of ExtendAction") - - def get_channel_subscriber( - self, channel_subscriber_name: str, default_channel_subscriber_name: str - ) -> ChannelSubscriber: - class_name = self._module_config.get_value(channel_subscriber_name) - if class_name is None: - self._logger.warning( - f"Channel subscriber {channel_subscriber_name} not found in config. Using default channel subscriber {default_channel_subscriber_name}" - ) - class_name = default_channel_subscriber_name - - channel_subscriber_class: type = self._load_module(class_name) - - instance = self._channel_subscribers.get(channel_subscriber_name) - if instance is not None: - return instance - - if issubclass(channel_subscriber_class, ChannelSubscriber): - instance = channel_subscriber_class() - self._channel_subscribers[channel_subscriber_name] = instance - return instance - - raise RuntimeError( - f"Channel subscriber {class_name} is not a subclass of ChannelSubscriber" - ) - - def get_message_coordinator( - self, message_coordinator_name: str, default_message_coordinator_name: str - ) -> MessageCoordinator: - class_name = self._module_config.get_value(message_coordinator_name) - if class_name is None: - self._logger.warning( - f"Channel subscriber {message_coordinator_name} not found in config. Using default channel subscriber {default_message_coordinator_name}" + module_class = self._load_module(class_name) + if issubclass(module_class, GatewayAbstractModule): + instance = module_class( + self._agent_info, + self._world_info, + self._scenario_info, + self, + self._develop_data, + gateway_module, ) - class_name = default_message_coordinator_name - - message_coordinator_class: type = self._load_module(class_name) - - instance = self._message_coordinators.get(message_coordinator_name) - if instance is not None: - return instance - - if issubclass(message_coordinator_class, MessageCoordinator): - instance = message_coordinator_class() - self._message_coordinators[message_coordinator_name] = instance + self._modules[module_name] = instance return instance - raise RuntimeError( - f"Message coordinator {class_name} is not a subclass of MessageCoordinator" + class_name = default_module_class_name + if class_name is not None: + module_class = self._load_module(class_name) + if issubclass(module_class, AbstractModule): + instance = module_class( + self._agent_info, + self._world_info, + self._scenario_info, + self, + self._develop_data, ) + self._modules[module_name] = instance + return instance + + raise RuntimeError(f"Module {class_name} is not a subclass of AbstractModule") + + def get_extend_action( + self, action_name: str, default_action_class_name: str + ) -> ExtendAction: + class_name = self._module_config.get_value(action_name) + if class_name is None: + self._logger.warning( + f"Action {action_name} not found in config. Using default action {default_action_class_name}" + ) + class_name = default_action_class_name + + action_class: type = self._load_module(class_name) + + instance = self._actions.get(action_name) + if instance is not None: + return instance + + if issubclass(action_class, ExtendAction): + instance = action_class( + self._agent_info, + self._world_info, + self._scenario_info, + self, + self._develop_data, + ) + self._actions[action_name] = instance + return instance + + raise RuntimeError(f"Action {class_name} is not a subclass of ExtendAction") + + def get_channel_subscriber( + self, channel_subscriber_name: str, default_channel_subscriber_name: str + ) -> ChannelSubscriber: + class_name = self._module_config.get_value(channel_subscriber_name) + if class_name is None: + self._logger.warning( + f"Channel subscriber {channel_subscriber_name} not found in config. Using default channel subscriber {default_channel_subscriber_name}" + ) + class_name = default_channel_subscriber_name + + channel_subscriber_class: type = self._load_module(class_name) + + instance = self._channel_subscribers.get(channel_subscriber_name) + if instance is not None: + return instance + + if issubclass(channel_subscriber_class, ChannelSubscriber): + instance = channel_subscriber_class() + self._channel_subscribers[channel_subscriber_name] = instance + return instance + + raise RuntimeError( + f"Channel subscriber {class_name} is not a subclass of ChannelSubscriber" + ) + + def get_message_coordinator( + self, message_coordinator_name: str, default_message_coordinator_name: str + ) -> MessageCoordinator: + class_name = self._module_config.get_value(message_coordinator_name) + if class_name is None: + self._logger.warning( + f"Channel subscriber {message_coordinator_name} not found in config. Using default channel subscriber {default_message_coordinator_name}" + ) + class_name = default_message_coordinator_name + + message_coordinator_class: type = self._load_module(class_name) + + instance = self._message_coordinators.get(message_coordinator_name) + if instance is not None: + return instance + + if issubclass(message_coordinator_class, MessageCoordinator): + instance = message_coordinator_class() + self._message_coordinators[message_coordinator_name] = instance + return instance + + raise RuntimeError( + f"Message coordinator {class_name} is not a subclass of MessageCoordinator" + ) + + def get_command_executor( + self, command_executor_name: str, default_command_executor_name: str + ) -> CommandExecutor: + class_name = self._module_config.get_value(command_executor_name) + if class_name is None: + self._logger.warning( + f"Command executor {command_executor_name} not found in config. Using default command executor {default_command_executor_name}" + ) + class_name = default_command_executor_name + + command_executor_class: type = self._load_module(class_name) + + instance = self._executors.get(command_executor_name) + if instance is not None: + return instance + + if issubclass(command_executor_class, CommandExecutor): + instance = command_executor_class( + self._agent_info, + self._world_info, + self._scenario_info, + self, + self._develop_data, + ) + self._executors[command_executor_name] = instance + return instance + + raise RuntimeError(f"Command executor {class_name} is not a subclass of object") + + def get_command_picker( + self, command_picker_name: str, default_command_picker_name: str + ) -> CommandPicker: + class_name = self._module_config.get_value(command_picker_name) + if class_name is None: + self._logger.warning( + f"Command picker {command_picker_name} not found in config. Using default command picker {default_command_picker_name}" + ) + class_name = default_command_picker_name + + command_picker_class: type = self._load_module(class_name) + + instance = self._pickers.get(command_picker_name) + if instance is not None: + return instance + + if issubclass(command_picker_class, CommandPicker): + instance = command_picker_class( + self._agent_info, + self._world_info, + self._scenario_info, + self, + self._develop_data, + ) + self._pickers[command_picker_name] = instance + return instance - def get_command_executor( - self, command_executor_name: str, default_command_executor_name: str - ) -> CommandExecutor: - class_name = self._module_config.get_value(command_executor_name) - if class_name is None: - self._logger.warning( - f"Command executor {command_executor_name} not found in config. Using default command executor {default_command_executor_name}" - ) - class_name = default_command_executor_name - - command_executor_class: type = self._load_module(class_name) - - instance = self._executors.get(command_executor_name) - if instance is not None: - return instance - - if issubclass(command_executor_class, CommandExecutor): - instance = command_executor_class( - self._agent_info, - self._world_info, - self._scenario_info, - self, - self._develop_data, - ) - self._executors[command_executor_name] = instance - return instance - - raise RuntimeError(f"Command executor {class_name} is not a subclass of object") - - def get_command_picker( - self, command_picker_name: str, default_command_picker_name: str - ) -> CommandPicker: - class_name = self._module_config.get_value(command_picker_name) - if class_name is None: - self._logger.warning( - f"Command picker {command_picker_name} not found in config. Using default command picker {default_command_picker_name}" - ) - class_name = default_command_picker_name - - command_picker_class: type = self._load_module(class_name) - - instance = self._pickers.get(command_picker_name) - if instance is not None: - return instance - - if issubclass(command_picker_class, CommandPicker): - instance = command_picker_class( - self._agent_info, - self._world_info, - self._scenario_info, - self, - self._develop_data, - ) - self._pickers[command_picker_name] = instance - return instance - - raise RuntimeError(f"Command picker {class_name} is not a subclass of object") + raise RuntimeError(f"Command picker {class_name} is not a subclass of object") - def _load_module(self, class_name: str) -> type: - module_name, module_class_name = class_name.rsplit(".", 1) - module = importlib.import_module(module_name) - return getattr(module, module_class_name) + def _load_module(self, class_name: str) -> type: + module_name, module_class_name = class_name.rsplit(".", 1) + module = importlib.import_module(module_name) + return getattr(module, module_class_name) diff --git a/src/adf_core_python/core/agent/office/office.py b/src/adf_core_python/core/agent/office/office.py index ea9a3b8..12b2a8f 100644 --- a/src/adf_core_python/core/agent/office/office.py +++ b/src/adf_core_python/core/agent/office/office.py @@ -14,120 +14,120 @@ class Office(Agent): - def __init__( - self, - tactics_center: TacticsCenter, - team_name: str, - is_precompute: bool, - is_debug: bool, - data_storage_name: str, - module_config: ModuleConfig, - develop_data: DevelopData, - finish_post_connect_event: Event, - gateway_agent: Optional[GatewayAgent], - ) -> None: - super().__init__( - is_precompute, - self.__class__.__qualname__, - is_debug, - team_name, - data_storage_name, - module_config, - develop_data, - finish_post_connect_event, - gateway_agent, - ) - self._tactics_center = tactics_center - self._team_name = team_name - self._is_precompute = is_precompute - self._is_debug = is_debug - self._data_storage_name = data_storage_name - self._module_config = module_config - self._develop_data = develop_data + def __init__( + self, + tactics_center: TacticsCenter, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + finish_post_connect_event: Event, + gateway_agent: Optional[GatewayAgent], + ) -> None: + super().__init__( + is_precompute, + self.__class__.__qualname__, + is_debug, + team_name, + data_storage_name, + module_config, + develop_data, + finish_post_connect_event, + gateway_agent, + ) + self._tactics_center = tactics_center + self._team_name = team_name + self._is_precompute = is_precompute + self._is_debug = is_debug + self._data_storage_name = data_storage_name + self._module_config = module_config + self._develop_data = develop_data - def post_connect(self) -> None: - super().post_connect() - self.precompute_data: PrecomputeData = PrecomputeData(self._data_storage_name) + def post_connect(self) -> None: + super().post_connect() + self.precompute_data: PrecomputeData = PrecomputeData(self._data_storage_name) - self._logger = get_agent_logger( - f"{self.__class__.__module__}.{self.__class__.__qualname__}", - self._agent_info, - ) + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) - self._module_manager: ModuleManager = ModuleManager( - self._agent_info, - self._world_info, - self._scenario_info, - self._module_config, - self._develop_data, - ) + self._module_manager: ModuleManager = ModuleManager( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_config, + self._develop_data, + ) - self._message_manager.set_channel_subscriber( - self._module_manager.get_channel_subscriber( - "MessageManager.PlatoonChannelSubscriber", - "adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber", - ) - ) - self._message_manager.set_message_coordinator( - self._module_manager.get_message_coordinator( - "MessageManager.PlatoonMessageCoordinator", - "adf_core_python.implement.module.communication.default_message_coordinator.DefaultMessageCoordinator", - ) - ) + self._message_manager.set_channel_subscriber( + self._module_manager.get_channel_subscriber( + "MessageManager.PlatoonChannelSubscriber", + "adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber", + ) + ) + self._message_manager.set_message_coordinator( + self._module_manager.get_message_coordinator( + "MessageManager.PlatoonMessageCoordinator", + "adf_core_python.implement.module.communication.default_message_coordinator.DefaultMessageCoordinator", + ) + ) - self._tactics_center.initialize( - self._agent_info, - self._world_info, - self._scenario_info, - self._module_manager, - self._precompute_data, - self._message_manager, - self._develop_data, - ) - - match self._scenario_info.get_mode(): - case Mode.PRECOMPUTATION: - self._tactics_center.precompute( - self._agent_info, - self._world_info, - self._scenario_info, - self._module_manager, - self._precompute_data, - self._message_manager, - self._develop_data, - ) - case Mode.PRECOMPUTED: - self._tactics_center.resume( - self._agent_info, - self._world_info, - self._scenario_info, - self._module_manager, - self._precompute_data, - self._message_manager, - self._develop_data, - ) - case Mode.NON_PRECOMPUTE: - self._tactics_center.prepare( - self._agent_info, - self._world_info, - self._scenario_info, - self._module_manager, - self.precompute_data, - self._develop_data, - ) + self._tactics_center.initialize( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + self._message_manager, + self._develop_data, + ) - def think(self) -> None: - self._tactics_center.think( - self._agent_info, - self._world_info, - self._scenario_info, - self._module_manager, - self._precompute_data, - self._message_manager, - self._develop_data, + match self._scenario_info.get_mode(): + case Mode.PRECOMPUTATION: + self._tactics_center.precompute( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + self._message_manager, + self._develop_data, ) - self.send_msg( - ActionRest() - .get_command(self.agent_id, self._agent_info.get_time()) - .to_message_proto() + case Mode.PRECOMPUTED: + self._tactics_center.resume( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + self._message_manager, + self._develop_data, ) + case Mode.NON_PRECOMPUTE: + self._tactics_center.prepare( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self.precompute_data, + self._develop_data, + ) + + def think(self) -> None: + self._tactics_center.think( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + self._message_manager, + self._develop_data, + ) + self.send_msg( + ActionRest() + .get_command(self.agent_id, self._agent_info.get_time()) + .to_message_proto() + ) diff --git a/src/adf_core_python/core/agent/office/office_ambulance.py b/src/adf_core_python/core/agent/office/office_ambulance.py index 1df7e5e..6653faa 100644 --- a/src/adf_core_python/core/agent/office/office_ambulance.py +++ b/src/adf_core_python/core/agent/office/office_ambulance.py @@ -11,32 +11,32 @@ class OfficeAmbulance(Office): - def __init__( - self, - tactics_center: TacticsCenter, - team_name: str, - is_precompute: bool, - is_debug: bool, - data_storage_name: str, - module_config: ModuleConfig, - develop_data: DevelopData, - finish_post_connect_event: Event, - gateway_agent: Optional[GatewayAgent], - ) -> None: - super().__init__( - tactics_center, - team_name, - is_precompute, - is_debug, - data_storage_name, - module_config, - develop_data, - finish_post_connect_event, - gateway_agent, - ) + def __init__( + self, + tactics_center: TacticsCenter, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + finish_post_connect_event: Event, + gateway_agent: Optional[GatewayAgent], + ) -> None: + super().__init__( + tactics_center, + team_name, + is_precompute, + is_debug, + data_storage_name, + module_config, + develop_data, + finish_post_connect_event, + gateway_agent, + ) - def precompute(self) -> None: - pass + def precompute(self) -> None: + pass - def get_requested_entities(self) -> list[EntityURN]: - return [EntityURN.AMBULANCE_CENTER] + def get_requested_entities(self) -> list[EntityURN]: + return [EntityURN.AMBULANCE_CENTER] diff --git a/src/adf_core_python/core/agent/office/office_fire.py b/src/adf_core_python/core/agent/office/office_fire.py index 766e35a..3ec3237 100644 --- a/src/adf_core_python/core/agent/office/office_fire.py +++ b/src/adf_core_python/core/agent/office/office_fire.py @@ -11,29 +11,29 @@ class OfficeFire(Office): - def __init__( - self, - tactics_center: TacticsCenter, - team_name: str, - is_precompute: bool, - is_debug: bool, - data_storage_name: str, - module_config: ModuleConfig, - develop_data: DevelopData, - finish_post_connect_event: Event, - gateway_agent: Optional[GatewayAgent], - ) -> None: - super().__init__( - tactics_center, - team_name, - is_precompute, - is_debug, - data_storage_name, - module_config, - develop_data, - finish_post_connect_event, - gateway_agent, - ) + def __init__( + self, + tactics_center: TacticsCenter, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + finish_post_connect_event: Event, + gateway_agent: Optional[GatewayAgent], + ) -> None: + super().__init__( + tactics_center, + team_name, + is_precompute, + is_debug, + data_storage_name, + module_config, + develop_data, + finish_post_connect_event, + gateway_agent, + ) - def get_requested_entities(self) -> list[EntityURN]: - return [EntityURN.FIRE_STATION] + def get_requested_entities(self) -> list[EntityURN]: + return [EntityURN.FIRE_STATION] diff --git a/src/adf_core_python/core/agent/office/office_police.py b/src/adf_core_python/core/agent/office/office_police.py index 67d4cc3..0aada9d 100644 --- a/src/adf_core_python/core/agent/office/office_police.py +++ b/src/adf_core_python/core/agent/office/office_police.py @@ -11,29 +11,29 @@ class OfficePolice(Office): - def __init__( - self, - tactics_center: TacticsCenter, - team_name: str, - is_precompute: bool, - is_debug: bool, - data_storage_name: str, - module_config: ModuleConfig, - develop_data: DevelopData, - finish_post_connect_event: Event, - gateway_agent: Optional[GatewayAgent], - ) -> None: - super().__init__( - tactics_center, - team_name, - is_precompute, - is_debug, - data_storage_name, - module_config, - develop_data, - finish_post_connect_event, - gateway_agent, - ) + def __init__( + self, + tactics_center: TacticsCenter, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + finish_post_connect_event: Event, + gateway_agent: Optional[GatewayAgent], + ) -> None: + super().__init__( + tactics_center, + team_name, + is_precompute, + is_debug, + data_storage_name, + module_config, + develop_data, + finish_post_connect_event, + gateway_agent, + ) - def get_requested_entities(self) -> list[EntityURN]: - return [EntityURN.POLICE_OFFICE] + def get_requested_entities(self) -> list[EntityURN]: + return [EntityURN.POLICE_OFFICE] diff --git a/src/adf_core_python/core/agent/platoon/platoon.py b/src/adf_core_python/core/agent/platoon/platoon.py index 8e9788c..0f56dfd 100644 --- a/src/adf_core_python/core/agent/platoon/platoon.py +++ b/src/adf_core_python/core/agent/platoon/platoon.py @@ -14,124 +14,124 @@ class Platoon(Agent): - def __init__( - self, - tactics_agent: TacticsAgent, - team_name: str, - is_precompute: bool, - is_debug: bool, - data_storage_name: str, - module_config: ModuleConfig, - develop_data: DevelopData, - finish_post_connect_event: Event, - gateway_agent: Optional[GatewayAgent], - ) -> None: - super().__init__( - is_precompute, - self.__class__.__qualname__, - is_debug, - team_name, - data_storage_name, - module_config, - develop_data, - finish_post_connect_event, - gateway_agent, - ) - self._tactics_agent = tactics_agent - self._team_name = team_name - self._is_precompute = is_precompute - self._is_debug = is_debug - self._data_storage_name = data_storage_name - self._module_config = module_config - self._develop_data = develop_data - self._gateway_agent = gateway_agent + def __init__( + self, + tactics_agent: TacticsAgent, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + finish_post_connect_event: Event, + gateway_agent: Optional[GatewayAgent], + ) -> None: + super().__init__( + is_precompute, + self.__class__.__qualname__, + is_debug, + team_name, + data_storage_name, + module_config, + develop_data, + finish_post_connect_event, + gateway_agent, + ) + self._tactics_agent = tactics_agent + self._team_name = team_name + self._is_precompute = is_precompute + self._is_debug = is_debug + self._data_storage_name = data_storage_name + self._module_config = module_config + self._develop_data = develop_data + self._gateway_agent = gateway_agent - def post_connect(self) -> None: - super().post_connect() - self.precompute_data: PrecomputeData = PrecomputeData(self._data_storage_name) + def post_connect(self) -> None: + super().post_connect() + self.precompute_data: PrecomputeData = PrecomputeData(self._data_storage_name) - self._logger = get_agent_logger( - f"{self.__class__.__module__}.{self.__class__.__qualname__}", - self._agent_info, - ) + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) - self._module_manager: ModuleManager = ModuleManager( - self._agent_info, - self._world_info, - self._scenario_info, - self._module_config, - self._develop_data, - self._gateway_agent, - ) + self._module_manager: ModuleManager = ModuleManager( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_config, + self._develop_data, + self._gateway_agent, + ) + + self._message_manager.set_channel_subscriber( + self._module_manager.get_channel_subscriber( + "MessageManager.PlatoonChannelSubscriber", + "adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber", + ) + ) + self._message_manager.set_message_coordinator( + self._module_manager.get_message_coordinator( + "MessageManager.PlatoonMessageCoordinator", + "adf_core_python.implement.module.communication.default_message_coordinator.DefaultMessageCoordinator", + ) + ) - self._message_manager.set_channel_subscriber( - self._module_manager.get_channel_subscriber( - "MessageManager.PlatoonChannelSubscriber", - "adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber", - ) + self._tactics_agent.initialize( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + self._message_manager, + self._develop_data, + ) + + match self._scenario_info.get_mode(): + case Mode.PRECOMPUTATION: + self._tactics_agent.precompute( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + self._message_manager, + self._develop_data, ) - self._message_manager.set_message_coordinator( - self._module_manager.get_message_coordinator( - "MessageManager.PlatoonMessageCoordinator", - "adf_core_python.implement.module.communication.default_message_coordinator.DefaultMessageCoordinator", - ) + case Mode.PRECOMPUTED: + self._tactics_agent.resume( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + self._message_manager, + self._develop_data, ) - - self._tactics_agent.initialize( - self._agent_info, - self._world_info, - self._scenario_info, - self._module_manager, - self._precompute_data, - self._message_manager, - self._develop_data, + case Mode.NON_PRECOMPUTE: + self._tactics_agent.prepare( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self.precompute_data, + self._develop_data, ) - match self._scenario_info.get_mode(): - case Mode.PRECOMPUTATION: - self._tactics_agent.precompute( - self._agent_info, - self._world_info, - self._scenario_info, - self._module_manager, - self._precompute_data, - self._message_manager, - self._develop_data, - ) - case Mode.PRECOMPUTED: - self._tactics_agent.resume( - self._agent_info, - self._world_info, - self._scenario_info, - self._module_manager, - self._precompute_data, - self._message_manager, - self._develop_data, - ) - case Mode.NON_PRECOMPUTE: - self._tactics_agent.prepare( - self._agent_info, - self._world_info, - self._scenario_info, - self._module_manager, - self.precompute_data, - self._develop_data, - ) - - def think(self) -> None: - action: Action = self._tactics_agent.think( - self._agent_info, - self._world_info, - self._scenario_info, - self._module_manager, - self._precompute_data, - self._message_manager, - self._develop_data, - ) - if action is not None and self.agent_id is not None: - self._agent_info.set_executed_action(self._agent_info.get_time(), action) - self.send_msg( - action.get_command( - self.agent_id, self._agent_info.get_time() - ).to_message_proto() - ) + def think(self) -> None: + action: Action = self._tactics_agent.think( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + self._message_manager, + self._develop_data, + ) + if action is not None and self.agent_id is not None: + self._agent_info.set_executed_action(self._agent_info.get_time(), action) + self.send_msg( + action.get_command( + self.agent_id, self._agent_info.get_time() + ).to_message_proto() + ) diff --git a/src/adf_core_python/core/agent/platoon/platoon_ambulance.py b/src/adf_core_python/core/agent/platoon/platoon_ambulance.py index 16a1127..e55de88 100644 --- a/src/adf_core_python/core/agent/platoon/platoon_ambulance.py +++ b/src/adf_core_python/core/agent/platoon/platoon_ambulance.py @@ -11,29 +11,29 @@ class PlatoonAmbulance(Platoon): - def __init__( - self, - tactics_agent: TacticsAgent, - team_name: str, - is_precompute: bool, - is_debug: bool, - data_storage_name: str, - module_config: ModuleConfig, - develop_data: DevelopData, - finish_post_connect_event: Event, - gateway_agent: Optional[GatewayAgent], - ): - super().__init__( - tactics_agent, - team_name, - is_precompute, - is_debug, - data_storage_name, - module_config, - develop_data, - finish_post_connect_event, - gateway_agent, - ) + def __init__( + self, + tactics_agent: TacticsAgent, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + finish_post_connect_event: Event, + gateway_agent: Optional[GatewayAgent], + ): + super().__init__( + tactics_agent, + team_name, + is_precompute, + is_debug, + data_storage_name, + module_config, + develop_data, + finish_post_connect_event, + gateway_agent, + ) - def get_requested_entities(self) -> list[EntityURN]: - return [EntityURN.AMBULANCE_TEAM] + def get_requested_entities(self) -> list[EntityURN]: + return [EntityURN.AMBULANCE_TEAM] diff --git a/src/adf_core_python/core/agent/platoon/platoon_fire.py b/src/adf_core_python/core/agent/platoon/platoon_fire.py index 13db7e0..b5cc288 100644 --- a/src/adf_core_python/core/agent/platoon/platoon_fire.py +++ b/src/adf_core_python/core/agent/platoon/platoon_fire.py @@ -11,29 +11,29 @@ class PlatoonFire(Platoon): - def __init__( - self, - tactics_agent: TacticsAgent, - team_name: str, - is_precompute: bool, - is_debug: bool, - data_storage_name: str, - module_config: ModuleConfig, - develop_data: DevelopData, - finish_post_connect_event: Event, - gateway_agent: Optional[GatewayAgent], - ): - super().__init__( - tactics_agent, - team_name, - is_precompute, - is_debug, - data_storage_name, - module_config, - develop_data, - finish_post_connect_event, - gateway_agent, - ) + def __init__( + self, + tactics_agent: TacticsAgent, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + finish_post_connect_event: Event, + gateway_agent: Optional[GatewayAgent], + ): + super().__init__( + tactics_agent, + team_name, + is_precompute, + is_debug, + data_storage_name, + module_config, + develop_data, + finish_post_connect_event, + gateway_agent, + ) - def get_requested_entities(self) -> list[EntityURN]: - return [EntityURN.FIRE_BRIGADE] + def get_requested_entities(self) -> list[EntityURN]: + return [EntityURN.FIRE_BRIGADE] diff --git a/src/adf_core_python/core/agent/platoon/platoon_police.py b/src/adf_core_python/core/agent/platoon/platoon_police.py index 8f9c33a..8a27047 100644 --- a/src/adf_core_python/core/agent/platoon/platoon_police.py +++ b/src/adf_core_python/core/agent/platoon/platoon_police.py @@ -11,29 +11,29 @@ class PlatoonPolice(Platoon): - def __init__( - self, - tactics_agent: TacticsAgent, - team_name: str, - is_precompute: bool, - is_debug: bool, - data_storage_name: str, - module_config: ModuleConfig, - develop_data: DevelopData, - finish_post_connect_event: Event, - gateway_agent: Optional[GatewayAgent], - ): - super().__init__( - tactics_agent, - team_name, - is_precompute, - is_debug, - data_storage_name, - module_config, - develop_data, - finish_post_connect_event, - gateway_agent, - ) + def __init__( + self, + tactics_agent: TacticsAgent, + team_name: str, + is_precompute: bool, + is_debug: bool, + data_storage_name: str, + module_config: ModuleConfig, + develop_data: DevelopData, + finish_post_connect_event: Event, + gateway_agent: Optional[GatewayAgent], + ): + super().__init__( + tactics_agent, + team_name, + is_precompute, + is_debug, + data_storage_name, + module_config, + develop_data, + finish_post_connect_event, + gateway_agent, + ) - def get_requested_entities(self) -> list[EntityURN]: - return [EntityURN.POLICE_FORCE] + def get_requested_entities(self) -> list[EntityURN]: + return [EntityURN.POLICE_FORCE] diff --git a/src/adf_core_python/core/component/abstract_loader.py b/src/adf_core_python/core/component/abstract_loader.py index dc0feae..4cc859a 100644 --- a/src/adf_core_python/core/component/abstract_loader.py +++ b/src/adf_core_python/core/component/abstract_loader.py @@ -2,52 +2,52 @@ from typing import Optional from adf_core_python.core.component.tactics.tactics_ambulance_center import ( - TacticsAmbulanceCenter, + TacticsAmbulanceCenter, ) from adf_core_python.core.component.tactics.tactics_ambulance_team import ( - TacticsAmbulanceTeam, + TacticsAmbulanceTeam, ) from adf_core_python.core.component.tactics.tactics_fire_brigade import ( - TacticsFireBrigade, + TacticsFireBrigade, ) from adf_core_python.core.component.tactics.tactics_fire_station import ( - TacticsFireStation, + TacticsFireStation, ) from adf_core_python.core.component.tactics.tactics_police_force import ( - TacticsPoliceForce, + TacticsPoliceForce, ) from adf_core_python.core.component.tactics.tactics_police_office import ( - TacticsPoliceOffice, + TacticsPoliceOffice, ) class AbstractLoader(ABC): - def __init__(self, team_name: Optional[str] = None): - self._team_name: str = "" if team_name is None else team_name + def __init__(self, team_name: Optional[str] = None): + self._team_name: str = "" if team_name is None else team_name - def get_team_name(self) -> str: - return self._team_name + def get_team_name(self) -> str: + return self._team_name - @abstractmethod - def get_tactics_ambulance_team(self) -> TacticsAmbulanceTeam: - raise NotImplementedError + @abstractmethod + def get_tactics_ambulance_team(self) -> TacticsAmbulanceTeam: + raise NotImplementedError - @abstractmethod - def get_tactics_fire_brigade(self) -> TacticsFireBrigade: - raise NotImplementedError + @abstractmethod + def get_tactics_fire_brigade(self) -> TacticsFireBrigade: + raise NotImplementedError - @abstractmethod - def get_tactics_police_force(self) -> TacticsPoliceForce: - raise NotImplementedError + @abstractmethod + def get_tactics_police_force(self) -> TacticsPoliceForce: + raise NotImplementedError - @abstractmethod - def get_tactics_ambulance_center(self) -> TacticsAmbulanceCenter: - raise NotImplementedError + @abstractmethod + def get_tactics_ambulance_center(self) -> TacticsAmbulanceCenter: + raise NotImplementedError - @abstractmethod - def get_tactics_fire_station(self) -> TacticsFireStation: - raise NotImplementedError + @abstractmethod + def get_tactics_fire_station(self) -> TacticsFireStation: + raise NotImplementedError - @abstractmethod - def get_tactics_police_office(self) -> TacticsPoliceOffice: - raise NotImplementedError + @abstractmethod + def get_tactics_police_office(self) -> TacticsPoliceOffice: + raise NotImplementedError diff --git a/src/adf_core_python/core/component/action/extend_action.py b/src/adf_core_python/core/component/action/extend_action.py index 1688e62..054f433 100644 --- a/src/adf_core_python/core/component/action/extend_action.py +++ b/src/adf_core_python/core/component/action/extend_action.py @@ -4,86 +4,86 @@ from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: - from rcrscore.entities import EntityID + from rcrscore.entities import EntityID - from adf_core_python.core.agent.action.action import Action - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.agent.action.action import Action + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData class ExtendAction(ABC): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ): - self.world_info = world_info - self.agent_info = agent_info - self.scenario_info = scenario_info - self.module_manager = module_manager - self.develop_data = develop_data - self.result: Optional[Action] = None - self.count_precompute: int = 0 - self.count_resume: int = 0 - self.count_prepare: int = 0 - self.count_update_info: int = 0 - self.count_update_info_current_time: int = 0 - - @abstractmethod - def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: - raise NotImplementedError - - @abstractmethod - def calculate(self) -> ExtendAction: - raise NotImplementedError - - def get_action(self) -> Optional[Action]: - return self.result - - def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: - self.count_precompute += 1 - return self - - def resume(self, precompute_data: PrecomputeData) -> ExtendAction: - self.count_resume += 1 - return self - - def prepare(self) -> ExtendAction: - self.count_prepare += 1 - return self - - def update_info(self, message_manager: MessageManager) -> ExtendAction: - self.count_update_info += 1 - return self - - def get_count_precompute(self) -> int: - return self.count_precompute - - def get_count_resume(self) -> int: - return self.count_resume - - def get_count_prepare(self) -> int: - return self.count_prepare - - def get_count_update_info(self) -> int: - return self.count_update_info - - def reset_count_precompute(self) -> None: - self.count_precompute = 0 - - def reset_count_resume(self) -> None: - self.count_resume = 0 - - def reset_count_prepare(self) -> None: - self.count_prepare = 0 - - def reset_count_update_info(self) -> None: - self.count_update_info = 0 + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ): + self.world_info = world_info + self.agent_info = agent_info + self.scenario_info = scenario_info + self.module_manager = module_manager + self.develop_data = develop_data + self.result: Optional[Action] = None + self.count_precompute: int = 0 + self.count_resume: int = 0 + self.count_prepare: int = 0 + self.count_update_info: int = 0 + self.count_update_info_current_time: int = 0 + + @abstractmethod + def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: + raise NotImplementedError + + @abstractmethod + def calculate(self) -> ExtendAction: + raise NotImplementedError + + def get_action(self) -> Optional[Action]: + return self.result + + def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: + self.count_precompute += 1 + return self + + def resume(self, precompute_data: PrecomputeData) -> ExtendAction: + self.count_resume += 1 + return self + + def prepare(self) -> ExtendAction: + self.count_prepare += 1 + return self + + def update_info(self, message_manager: MessageManager) -> ExtendAction: + self.count_update_info += 1 + return self + + def get_count_precompute(self) -> int: + return self.count_precompute + + def get_count_resume(self) -> int: + return self.count_resume + + def get_count_prepare(self) -> int: + return self.count_prepare + + def get_count_update_info(self) -> int: + return self.count_update_info + + def reset_count_precompute(self) -> None: + self.count_precompute = 0 + + def reset_count_resume(self) -> None: + self.count_resume = 0 + + def reset_count_prepare(self) -> None: + self.count_prepare = 0 + + def reset_count_update_info(self) -> None: + self.count_update_info = 0 diff --git a/src/adf_core_python/core/component/centralized/command_executor.py b/src/adf_core_python/core/component/centralized/command_executor.py index ebd501f..6b9bd8c 100644 --- a/src/adf_core_python/core/component/centralized/command_executor.py +++ b/src/adf_core_python/core/component/centralized/command_executor.py @@ -4,94 +4,94 @@ from typing import TYPE_CHECKING, Generic, Optional, TypeVar if TYPE_CHECKING: - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.agent.action.action import Action from adf_core_python.core.component.communication.communication_message import ( - CommunicationMessage, + CommunicationMessage, ) T = TypeVar("T", bound=CommunicationMessage) class CommandExecutor(ABC, Generic[T]): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - self._agent_info = agent_info - self._world_info = world_info - self._scenario_info = scenario_info - self._module_manager = module_manager - self._develop_data = develop_data - - self._result: Optional[Action] = None - - self._count_precompute: int = 0 - self._count_prepare: int = 0 - self._count_resume: int = 0 - self._count_update_info: int = 0 - self._count_update_info_current_time: int = 0 - - @abstractmethod - def set_command(self, command: T) -> CommandExecutor: - pass - - @abstractmethod - def calculate(self) -> CommandExecutor: - pass - - def get_action(self) -> Optional[Action]: - return self._result - - def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: - self._count_precompute += 1 - return self - - def prepare(self) -> CommandExecutor: - self._count_prepare += 1 - return self - - def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: - self._count_resume += 1 - return self - - def update_info(self, message_manager: MessageManager) -> CommandExecutor: - if self._count_update_info_current_time != self._agent_info.get_time(): - self._count_update_info_current_time = self._agent_info.get_time() - self._count_update_info += 1 - return self - - def get_count_precompute(self) -> int: - return self._count_precompute - - def get_count_prepare(self) -> int: - return self._count_prepare - - def get_count_resume(self) -> int: - return self._count_resume - - def get_count_update_info(self) -> int: - return self._count_update_info - - def reset_count_precompute(self) -> None: - self._count_precompute = 0 - - def reset_count_prepare(self) -> None: - self._count_prepare = 0 - - def reset_count_resume(self) -> None: - self._count_resume = 0 - - def reset_count_update_info(self) -> None: - self._count_update_info = 0 + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + self._agent_info = agent_info + self._world_info = world_info + self._scenario_info = scenario_info + self._module_manager = module_manager + self._develop_data = develop_data + + self._result: Optional[Action] = None + + self._count_precompute: int = 0 + self._count_prepare: int = 0 + self._count_resume: int = 0 + self._count_update_info: int = 0 + self._count_update_info_current_time: int = 0 + + @abstractmethod + def set_command(self, command: T) -> CommandExecutor: + pass + + @abstractmethod + def calculate(self) -> CommandExecutor: + pass + + def get_action(self) -> Optional[Action]: + return self._result + + def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: + self._count_precompute += 1 + return self + + def prepare(self) -> CommandExecutor: + self._count_prepare += 1 + return self + + def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: + self._count_resume += 1 + return self + + def update_info(self, message_manager: MessageManager) -> CommandExecutor: + if self._count_update_info_current_time != self._agent_info.get_time(): + self._count_update_info_current_time = self._agent_info.get_time() + self._count_update_info += 1 + return self + + def get_count_precompute(self) -> int: + return self._count_precompute + + def get_count_prepare(self) -> int: + return self._count_prepare + + def get_count_resume(self) -> int: + return self._count_resume + + def get_count_update_info(self) -> int: + return self._count_update_info + + def reset_count_precompute(self) -> None: + self._count_precompute = 0 + + def reset_count_prepare(self) -> None: + self._count_prepare = 0 + + def reset_count_resume(self) -> None: + self._count_resume = 0 + + def reset_count_update_info(self) -> None: + self._count_update_info = 0 diff --git a/src/adf_core_python/core/component/centralized/command_picker.py b/src/adf_core_python/core/component/centralized/command_picker.py index 8fcea2f..a78c05d 100644 --- a/src/adf_core_python/core/component/centralized/command_picker.py +++ b/src/adf_core_python/core/component/centralized/command_picker.py @@ -6,95 +6,95 @@ from rcrscore.entities import EntityID if TYPE_CHECKING: - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.communication.communication_message import ( - CommunicationMessage, + CommunicationMessage, ) class CommandPicker(ABC): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - self._agent_info = agent_info - self._world_info = world_info - self._scenario_info = scenario_info - self._module_manager = module_manager - self._develop_data = develop_data - self._count_precompute: int = 0 - self._count_prepare: int = 0 - self._count_resume: int = 0 - self._count_update_info: int = 0 - self._count_update_info_current_time: int = 0 - - @abstractmethod - def set_allocator_result( - self, allocation_data: dict[EntityID, EntityID] - ) -> CommandPicker: - pass - - @abstractmethod - def calculate(self) -> CommandPicker: - pass - - @abstractmethod - def get_result(self) -> list[CommunicationMessage]: - pass - - def precompute(self, precompute_data: PrecomputeData) -> CommandPicker: - self._count_precompute += 1 - return self - - def prepare(self) -> CommandPicker: - self._count_prepare += 1 - return self - - def resume(self, precompute_data: PrecomputeData) -> CommandPicker: - self._count_resume += 1 - return self - - def update_info(self, message_manager: MessageManager) -> CommandPicker: - if self._count_update_info_current_time != self._agent_info.get_time(): - self._count_update_info_current_time = self._agent_info.get_time() - self._count_update_info = 0 - self._count_update_info += 1 - return self - - def get_count_precompute(self) -> int: - return self._count_precompute - - def get_count_prepare(self) -> int: - return self._count_prepare - - def get_count_resume(self) -> int: - return self._count_resume - - def get_count_update_info(self) -> int: - return self._count_update_info - - def get_count_update_info_current_time(self) -> int: - return self._count_update_info_current_time - - def reset_count_precompute(self) -> None: - self._count_precompute = 0 - - def reset_count_prepare(self) -> None: - self._count_prepare = 0 - - def reset_count_resume(self) -> None: - self._count_resume = 0 - - def reset_count_update_info(self) -> None: - self._count_update_info = 0 + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + self._agent_info = agent_info + self._world_info = world_info + self._scenario_info = scenario_info + self._module_manager = module_manager + self._develop_data = develop_data + self._count_precompute: int = 0 + self._count_prepare: int = 0 + self._count_resume: int = 0 + self._count_update_info: int = 0 + self._count_update_info_current_time: int = 0 + + @abstractmethod + def set_allocator_result( + self, allocation_data: dict[EntityID, EntityID] + ) -> CommandPicker: + pass + + @abstractmethod + def calculate(self) -> CommandPicker: + pass + + @abstractmethod + def get_result(self) -> list[CommunicationMessage]: + pass + + def precompute(self, precompute_data: PrecomputeData) -> CommandPicker: + self._count_precompute += 1 + return self + + def prepare(self) -> CommandPicker: + self._count_prepare += 1 + return self + + def resume(self, precompute_data: PrecomputeData) -> CommandPicker: + self._count_resume += 1 + return self + + def update_info(self, message_manager: MessageManager) -> CommandPicker: + if self._count_update_info_current_time != self._agent_info.get_time(): + self._count_update_info_current_time = self._agent_info.get_time() + self._count_update_info = 0 + self._count_update_info += 1 + return self + + def get_count_precompute(self) -> int: + return self._count_precompute + + def get_count_prepare(self) -> int: + return self._count_prepare + + def get_count_resume(self) -> int: + return self._count_resume + + def get_count_update_info(self) -> int: + return self._count_update_info + + def get_count_update_info_current_time(self) -> int: + return self._count_update_info_current_time + + def reset_count_precompute(self) -> None: + self._count_precompute = 0 + + def reset_count_prepare(self) -> None: + self._count_prepare = 0 + + def reset_count_resume(self) -> None: + self._count_resume = 0 + + def reset_count_update_info(self) -> None: + self._count_update_info = 0 diff --git a/src/adf_core_python/core/component/communication/channel_subscriber.py b/src/adf_core_python/core/component/communication/channel_subscriber.py index d2da83f..9e1acd4 100644 --- a/src/adf_core_python/core/component/communication/channel_subscriber.py +++ b/src/adf_core_python/core/component/communication/channel_subscriber.py @@ -4,34 +4,34 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo class ChannelSubscriber(ABC): - @abstractmethod - def subscribe( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - ) -> list[int]: - """ - Subscribe to the channel. + @abstractmethod + def subscribe( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + ) -> list[int]: + """ + Subscribe to the channel. - Parameters - ---------- - agent_info : AgentInfo - The agent info. - world_info : WorldInfo - The world info. - scenario_info : ScenarioInfo - The scenario info. + Parameters + ---------- + agent_info : AgentInfo + The agent info. + world_info : WorldInfo + The world info. + scenario_info : ScenarioInfo + The scenario info. - Returns - ------- - list[int] - The list of subscribed channels. - """ - pass + Returns + ------- + list[int] + The list of subscribed channels. + """ + pass diff --git a/src/adf_core_python/core/component/communication/communication_message.py b/src/adf_core_python/core/component/communication/communication_message.py index 60972ff..5538b40 100644 --- a/src/adf_core_python/core/component/communication/communication_message.py +++ b/src/adf_core_python/core/component/communication/communication_message.py @@ -6,20 +6,20 @@ class CommunicationMessage(ABC): - def __init__(self, is_wireless_message: bool) -> None: - self._is_wireless_message = is_wireless_message + def __init__(self, is_wireless_message: bool) -> None: + self._is_wireless_message = is_wireless_message - def is_wireless_message(self) -> bool: - return self._is_wireless_message + def is_wireless_message(self) -> bool: + return self._is_wireless_message - @abstractmethod - def get_bit_size(self) -> int: - raise NotImplementedError + @abstractmethod + def get_bit_size(self) -> int: + raise NotImplementedError - @abstractmethod - def to_bits(self) -> bitarray: - raise NotImplementedError + @abstractmethod + def to_bits(self) -> bitarray: + raise NotImplementedError - @abstractmethod - def __hash__(self) -> int: - raise NotImplementedError + @abstractmethod + def __hash__(self) -> int: + raise NotImplementedError diff --git a/src/adf_core_python/core/component/communication/communication_module.py b/src/adf_core_python/core/component/communication/communication_module.py index 2e87e31..0959f6c 100644 --- a/src/adf_core_python/core/component/communication/communication_module.py +++ b/src/adf_core_python/core/component/communication/communication_module.py @@ -4,15 +4,15 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from adf_core_python.core.agent.agent import Agent - from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.agent import Agent + from adf_core_python.core.agent.communication.message_manager import MessageManager class CommunicationModule(ABC): - @abstractmethod - def receive(self, agent: Agent, message_manager: MessageManager) -> None: - pass + @abstractmethod + def receive(self, agent: Agent, message_manager: MessageManager) -> None: + pass - @abstractmethod - def send(self, agent: Agent, message_manager: MessageManager) -> None: - pass + @abstractmethod + def send(self, agent: Agent, message_manager: MessageManager) -> None: + pass diff --git a/src/adf_core_python/core/component/communication/message_coordinator.py b/src/adf_core_python/core/component/communication/message_coordinator.py index 9d8cb1b..86e93f1 100644 --- a/src/adf_core_python/core/component/communication/message_coordinator.py +++ b/src/adf_core_python/core/component/communication/message_coordinator.py @@ -4,24 +4,24 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.component.communication.communication_message import ( - CommunicationMessage, - ) + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.component.communication.communication_message import ( + CommunicationMessage, + ) class MessageCoordinator(ABC): - @abstractmethod - def coordinate( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - message_manager: MessageManager, - send_message_list: list[CommunicationMessage], - channel_send_message_list: list[list[CommunicationMessage]], - ) -> None: - pass + @abstractmethod + def coordinate( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + message_manager: MessageManager, + send_message_list: list[CommunicationMessage], + channel_send_message_list: list[list[CommunicationMessage]], + ) -> None: + pass diff --git a/src/adf_core_python/core/component/module/abstract_module.py b/src/adf_core_python/core/component/module/abstract_module.py index 4a3f317..8f9e466 100644 --- a/src/adf_core_python/core/component/module/abstract_module.py +++ b/src/adf_core_python/core/component/module/abstract_module.py @@ -7,99 +7,99 @@ from adf_core_python.core.logger.logger import get_agent_logger if TYPE_CHECKING: - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData class AbstractModule(ABC): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - self._agent_info = agent_info - self._world_info = world_info - self._scenario_info = scenario_info - self._module_manager = module_manager - self._develop_data = develop_data - self._count_precompute: int = 0 - self._count_resume: int = 0 - self._count_prepare: int = 0 - self._count_update_info: int = 0 - self._count_update_info_current_time: int = 0 - self._logger = get_agent_logger( - f"{self.__class__.__module__}.{self.__class__.__qualname__}", - self._agent_info, - ) - - self._sub_modules: list[AbstractModule] = [] - - def register_sub_module(self, sub_module: AbstractModule) -> None: - self._sub_modules.append(sub_module) - - def unregister_sub_module(self, sub_module: AbstractModule) -> None: - self._sub_modules.remove(sub_module) - - def precompute(self, precompute_data: PrecomputeData) -> AbstractModule: - self._count_precompute += 1 - for sub_module in self._sub_modules: - sub_module.precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> AbstractModule: - self._count_resume += 1 - for sub_module in self._sub_modules: - sub_module.resume(precompute_data) - return self - - def prepare(self) -> AbstractModule: - self._count_prepare += 1 - for sub_module in self._sub_modules: - start_time = time.time() - sub_module.prepare() - self._logger.debug( - f"{self.__class__.__name__}'s sub_module {sub_module.__class__.__name__} prepare time: {time.time() - start_time:.3f}", - ) - return self - - def update_info(self, message_manager: MessageManager) -> AbstractModule: - self._count_update_info += 1 - for sub_module in self._sub_modules: - sub_module.update_info(message_manager) - return self - - @abstractmethod - def calculate(self) -> AbstractModule: - raise NotImplementedError - - def get_count_precompute(self) -> int: - return self._count_precompute - - def get_count_resume(self) -> int: - return self._count_resume - - def get_count_prepare(self) -> int: - return self._count_prepare - - def get_count_update_info(self) -> int: - return self._count_update_info - - def reset_count_precompute(self) -> None: - self._count_precompute = 0 - - def reset_count_resume(self) -> None: - self._count_resume = 0 - - def reset_count_prepare(self) -> None: - self._count_prepare = 0 - - def reset_count_update_info(self) -> None: - self._count_update_info = 0 + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + self._agent_info = agent_info + self._world_info = world_info + self._scenario_info = scenario_info + self._module_manager = module_manager + self._develop_data = develop_data + self._count_precompute: int = 0 + self._count_resume: int = 0 + self._count_prepare: int = 0 + self._count_update_info: int = 0 + self._count_update_info_current_time: int = 0 + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + + self._sub_modules: list[AbstractModule] = [] + + def register_sub_module(self, sub_module: AbstractModule) -> None: + self._sub_modules.append(sub_module) + + def unregister_sub_module(self, sub_module: AbstractModule) -> None: + self._sub_modules.remove(sub_module) + + def precompute(self, precompute_data: PrecomputeData) -> AbstractModule: + self._count_precompute += 1 + for sub_module in self._sub_modules: + sub_module.precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> AbstractModule: + self._count_resume += 1 + for sub_module in self._sub_modules: + sub_module.resume(precompute_data) + return self + + def prepare(self) -> AbstractModule: + self._count_prepare += 1 + for sub_module in self._sub_modules: + start_time = time.time() + sub_module.prepare() + self._logger.debug( + f"{self.__class__.__name__}'s sub_module {sub_module.__class__.__name__} prepare time: {time.time() - start_time:.3f}", + ) + return self + + def update_info(self, message_manager: MessageManager) -> AbstractModule: + self._count_update_info += 1 + for sub_module in self._sub_modules: + sub_module.update_info(message_manager) + return self + + @abstractmethod + def calculate(self) -> AbstractModule: + raise NotImplementedError + + def get_count_precompute(self) -> int: + return self._count_precompute + + def get_count_resume(self) -> int: + return self._count_resume + + def get_count_prepare(self) -> int: + return self._count_prepare + + def get_count_update_info(self) -> int: + return self._count_update_info + + def reset_count_precompute(self) -> None: + self._count_precompute = 0 + + def reset_count_resume(self) -> None: + self._count_resume = 0 + + def reset_count_prepare(self) -> None: + self._count_prepare = 0 + + def reset_count_update_info(self) -> None: + self._count_update_info = 0 diff --git a/src/adf_core_python/core/component/module/algorithm/clustering.py b/src/adf_core_python/core/component/module/algorithm/clustering.py index 2594cb9..7a0331b 100644 --- a/src/adf_core_python/core/component/module/algorithm/clustering.py +++ b/src/adf_core_python/core/component/module/algorithm/clustering.py @@ -6,63 +6,63 @@ from adf_core_python.core.component.module.abstract_module import AbstractModule if TYPE_CHECKING: - from rcrscore.entities import EntityID - from rcrscore.entities.entity import Entity + from rcrscore.entities import EntityID + from rcrscore.entities.entity import Entity - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData class Clustering(AbstractModule): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) - @abstractmethod - def get_cluster_number(self) -> int: - pass + @abstractmethod + def get_cluster_number(self) -> int: + pass - @abstractmethod - def get_cluster_index(self, entity_id: EntityID) -> int: - pass + @abstractmethod + def get_cluster_index(self, entity_id: EntityID) -> int: + pass - @abstractmethod - def get_cluster_entities(self, cluster_index: int) -> list[Entity]: - pass + @abstractmethod + def get_cluster_entities(self, cluster_index: int) -> list[Entity]: + pass - @abstractmethod - def get_cluster_entity_ids(self, cluster_index: int) -> list[EntityID]: - pass + @abstractmethod + def get_cluster_entity_ids(self, cluster_index: int) -> list[EntityID]: + pass - @abstractmethod - def calculate(self) -> Clustering: - pass + @abstractmethod + def calculate(self) -> Clustering: + pass - def precompute(self, precompute_data: PrecomputeData) -> Clustering: - super().precompute(precompute_data) - return self + def precompute(self, precompute_data: PrecomputeData) -> Clustering: + super().precompute(precompute_data) + return self - def resume(self, precompute_data: PrecomputeData) -> Clustering: - super().resume(precompute_data) - return self + def resume(self, precompute_data: PrecomputeData) -> Clustering: + super().resume(precompute_data) + return self - def prepare(self) -> Clustering: - super().prepare() - return self + def prepare(self) -> Clustering: + super().prepare() + return self - def update_info(self, message_manager: MessageManager) -> Clustering: - super().update_info(message_manager) - return self + def update_info(self, message_manager: MessageManager) -> Clustering: + super().update_info(message_manager) + return self diff --git a/src/adf_core_python/core/component/module/algorithm/path_planning.py b/src/adf_core_python/core/component/module/algorithm/path_planning.py index d7318f1..4a36bbb 100644 --- a/src/adf_core_python/core/component/module/algorithm/path_planning.py +++ b/src/adf_core_python/core/component/module/algorithm/path_planning.py @@ -6,62 +6,62 @@ from adf_core_python.core.component.module.abstract_module import AbstractModule if TYPE_CHECKING: - from rcrscore.entities import EntityID + from rcrscore.entities import EntityID - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData class PathPlanning(AbstractModule): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) - @abstractmethod - def get_path( - self, from_entity_id: EntityID, to_entity_id: EntityID - ) -> list[EntityID]: - pass + @abstractmethod + def get_path( + self, from_entity_id: EntityID, to_entity_id: EntityID + ) -> list[EntityID]: + pass - @abstractmethod - def get_path_to_multiple_destinations( - self, from_entity_id: EntityID, destination_entity_ids: set[EntityID] - ) -> list[EntityID]: - pass + @abstractmethod + def get_path_to_multiple_destinations( + self, from_entity_id: EntityID, destination_entity_ids: set[EntityID] + ) -> list[EntityID]: + pass - @abstractmethod - def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: - pass + @abstractmethod + def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: + pass - def precompute(self, precompute_data: PrecomputeData) -> PathPlanning: - super().precompute(precompute_data) - return self + def precompute(self, precompute_data: PrecomputeData) -> PathPlanning: + super().precompute(precompute_data) + return self - def resume(self, precompute_data: PrecomputeData) -> PathPlanning: - super().resume(precompute_data) - return self + def resume(self, precompute_data: PrecomputeData) -> PathPlanning: + super().resume(precompute_data) + return self - def prepare(self) -> PathPlanning: - super().prepare() - return self + def prepare(self) -> PathPlanning: + super().prepare() + return self - def update_info(self, message_manager: MessageManager) -> PathPlanning: - super().update_info(message_manager) - return self + def update_info(self, message_manager: MessageManager) -> PathPlanning: + super().update_info(message_manager) + return self - @abstractmethod - def calculate(self) -> PathPlanning: - pass + @abstractmethod + def calculate(self) -> PathPlanning: + pass diff --git a/src/adf_core_python/core/component/module/complex/ambulance_target_allocator.py b/src/adf_core_python/core/component/module/complex/ambulance_target_allocator.py index 63e5a5f..8956ea9 100644 --- a/src/adf_core_python/core/component/module/complex/ambulance_target_allocator.py +++ b/src/adf_core_python/core/component/module/complex/ambulance_target_allocator.py @@ -4,54 +4,54 @@ from typing import TYPE_CHECKING from adf_core_python.core.component.module.complex.target_allocator import ( - TargetAllocator, + TargetAllocator, ) if TYPE_CHECKING: - from rcrscore.entities import EntityID + from rcrscore.entities import EntityID - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData class AmbulanceTargetAllocator(TargetAllocator): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - @abstractmethod - def get_result(self) -> dict[EntityID, EntityID]: - pass - - @abstractmethod - def calculate(self) -> AmbulanceTargetAllocator: - pass - - def precompute(self, precompute_data: PrecomputeData) -> AmbulanceTargetAllocator: - super().precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> AmbulanceTargetAllocator: - super().resume(precompute_data) - return self - - def prepare(self) -> AmbulanceTargetAllocator: - super().prepare() - return self - - def update_info(self, message_manager: MessageManager) -> AmbulanceTargetAllocator: - super().update_info(message_manager) - return self + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + @abstractmethod + def get_result(self) -> dict[EntityID, EntityID]: + pass + + @abstractmethod + def calculate(self) -> AmbulanceTargetAllocator: + pass + + def precompute(self, precompute_data: PrecomputeData) -> AmbulanceTargetAllocator: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> AmbulanceTargetAllocator: + super().resume(precompute_data) + return self + + def prepare(self) -> AmbulanceTargetAllocator: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> AmbulanceTargetAllocator: + super().update_info(message_manager) + return self diff --git a/src/adf_core_python/core/component/module/complex/fire_target_allocator.py b/src/adf_core_python/core/component/module/complex/fire_target_allocator.py index 2cf3ec2..79d1905 100644 --- a/src/adf_core_python/core/component/module/complex/fire_target_allocator.py +++ b/src/adf_core_python/core/component/module/complex/fire_target_allocator.py @@ -4,54 +4,54 @@ from typing import TYPE_CHECKING from adf_core_python.core.component.module.complex.target_allocator import ( - TargetAllocator, + TargetAllocator, ) if TYPE_CHECKING: - from rcrscore.entities import EntityID + from rcrscore.entities import EntityID - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData class FireTargetAllocator(TargetAllocator): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - @abstractmethod - def get_result(self) -> dict[EntityID, EntityID]: - pass - - @abstractmethod - def calculate(self) -> FireTargetAllocator: - pass - - def precompute(self, precompute_data: PrecomputeData) -> FireTargetAllocator: - super().precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> FireTargetAllocator: - super().resume(precompute_data) - return self - - def prepare(self) -> FireTargetAllocator: - super().prepare() - return self - - def update_info(self, message_manager: MessageManager) -> FireTargetAllocator: - super().update_info(message_manager) - return self + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + @abstractmethod + def get_result(self) -> dict[EntityID, EntityID]: + pass + + @abstractmethod + def calculate(self) -> FireTargetAllocator: + pass + + def precompute(self, precompute_data: PrecomputeData) -> FireTargetAllocator: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> FireTargetAllocator: + super().resume(precompute_data) + return self + + def prepare(self) -> FireTargetAllocator: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> FireTargetAllocator: + super().update_info(message_manager) + return self diff --git a/src/adf_core_python/core/component/module/complex/human_detector.py b/src/adf_core_python/core/component/module/complex/human_detector.py index 0e7ae6c..a0619b3 100644 --- a/src/adf_core_python/core/component/module/complex/human_detector.py +++ b/src/adf_core_python/core/component/module/complex/human_detector.py @@ -6,43 +6,43 @@ from rcrscore.entities.human import Human from adf_core_python.core.component.module.complex.target_detector import ( - TargetDetector, + TargetDetector, ) if TYPE_CHECKING: - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData class HumanDetector(TargetDetector[Human]): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - def precompute(self, precompute_data: PrecomputeData) -> HumanDetector: - super().precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> HumanDetector: - super().resume(precompute_data) - return self - - def prepare(self) -> HumanDetector: - super().prepare() - return self - - @abstractmethod - def calculate(self) -> HumanDetector: - return self + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + def precompute(self, precompute_data: PrecomputeData) -> HumanDetector: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> HumanDetector: + super().resume(precompute_data) + return self + + def prepare(self) -> HumanDetector: + super().prepare() + return self + + @abstractmethod + def calculate(self) -> HumanDetector: + return self diff --git a/src/adf_core_python/core/component/module/complex/police_target_allocator.py b/src/adf_core_python/core/component/module/complex/police_target_allocator.py index a066304..540a60b 100644 --- a/src/adf_core_python/core/component/module/complex/police_target_allocator.py +++ b/src/adf_core_python/core/component/module/complex/police_target_allocator.py @@ -4,54 +4,54 @@ from typing import TYPE_CHECKING from adf_core_python.core.component.module.complex.target_allocator import ( - TargetAllocator, + TargetAllocator, ) if TYPE_CHECKING: - from rcrscore.entities import EntityID + from rcrscore.entities import EntityID - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData class PoliceTargetAllocator(TargetAllocator): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - @abstractmethod - def get_result(self) -> dict[EntityID, EntityID]: - pass - - @abstractmethod - def calculate(self) -> PoliceTargetAllocator: - pass - - def precompute(self, precompute_data: PrecomputeData) -> PoliceTargetAllocator: - super().precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> PoliceTargetAllocator: - super().resume(precompute_data) - return self - - def prepare(self) -> PoliceTargetAllocator: - super().prepare() - return self - - def update_info(self, message_manager: MessageManager) -> PoliceTargetAllocator: - super().update_info(message_manager) - return self + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + @abstractmethod + def get_result(self) -> dict[EntityID, EntityID]: + pass + + @abstractmethod + def calculate(self) -> PoliceTargetAllocator: + pass + + def precompute(self, precompute_data: PrecomputeData) -> PoliceTargetAllocator: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> PoliceTargetAllocator: + super().resume(precompute_data) + return self + + def prepare(self) -> PoliceTargetAllocator: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> PoliceTargetAllocator: + super().update_info(message_manager) + return self diff --git a/src/adf_core_python/core/component/module/complex/road_detector.py b/src/adf_core_python/core/component/module/complex/road_detector.py index 888ea96..f742b2d 100644 --- a/src/adf_core_python/core/component/module/complex/road_detector.py +++ b/src/adf_core_python/core/component/module/complex/road_detector.py @@ -6,43 +6,43 @@ from rcrscore.entities.road import Road from adf_core_python.core.component.module.complex.target_detector import ( - TargetDetector, + TargetDetector, ) if TYPE_CHECKING: - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData class RoadDetector(TargetDetector[Road]): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - def precompute(self, precompute_data: PrecomputeData) -> RoadDetector: - super().precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> RoadDetector: - super().resume(precompute_data) - return self - - def prepare(self) -> RoadDetector: - super().prepare() - return self - - @abstractmethod - def calculate(self) -> RoadDetector: - return self + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + def precompute(self, precompute_data: PrecomputeData) -> RoadDetector: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> RoadDetector: + super().resume(precompute_data) + return self + + def prepare(self) -> RoadDetector: + super().prepare() + return self + + @abstractmethod + def calculate(self) -> RoadDetector: + return self diff --git a/src/adf_core_python/core/component/module/complex/search.py b/src/adf_core_python/core/component/module/complex/search.py index bfeddef..59c4fde 100644 --- a/src/adf_core_python/core/component/module/complex/search.py +++ b/src/adf_core_python/core/component/module/complex/search.py @@ -6,43 +6,43 @@ from rcrscore.entities.area import Area from adf_core_python.core.component.module.complex.target_detector import ( - TargetDetector, + TargetDetector, ) if TYPE_CHECKING: - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData class Search(TargetDetector[Area]): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - def precompute(self, precompute_data: PrecomputeData) -> Search: - super().precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> Search: - super().resume(precompute_data) - return self - - def prepare(self) -> Search: - super().prepare() - return self - - @abstractmethod - def calculate(self) -> Search: - return self + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + def precompute(self, precompute_data: PrecomputeData) -> Search: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> Search: + super().resume(precompute_data) + return self + + def prepare(self) -> Search: + super().prepare() + return self + + @abstractmethod + def calculate(self) -> Search: + return self diff --git a/src/adf_core_python/core/component/module/complex/target_allocator.py b/src/adf_core_python/core/component/module/complex/target_allocator.py index 88ef50f..6979966 100644 --- a/src/adf_core_python/core/component/module/complex/target_allocator.py +++ b/src/adf_core_python/core/component/module/complex/target_allocator.py @@ -6,50 +6,50 @@ from adf_core_python.core.component.module.abstract_module import AbstractModule if TYPE_CHECKING: - from rcrscore.entities import EntityID + from rcrscore.entities import EntityID - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData class TargetAllocator(AbstractModule): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - @abstractmethod - def get_result(self) -> dict[EntityID, EntityID]: - pass - - @abstractmethod - def calculate(self) -> TargetAllocator: - pass - - def precompute(self, precompute_data: PrecomputeData) -> TargetAllocator: - super().precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> TargetAllocator: - super().resume(precompute_data) - return self - - def prepare(self) -> TargetAllocator: - super().prepare() - return self - - def update_info(self, message_manager: MessageManager) -> TargetAllocator: - super().update_info(message_manager) - return self + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + @abstractmethod + def get_result(self) -> dict[EntityID, EntityID]: + pass + + @abstractmethod + def calculate(self) -> TargetAllocator: + pass + + def precompute(self, precompute_data: PrecomputeData) -> TargetAllocator: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> TargetAllocator: + super().resume(precompute_data) + return self + + def prepare(self) -> TargetAllocator: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> TargetAllocator: + super().update_info(message_manager) + return self diff --git a/src/adf_core_python/core/component/module/complex/target_detector.py b/src/adf_core_python/core/component/module/complex/target_detector.py index 21a79e4..7e7285d 100644 --- a/src/adf_core_python/core/component/module/complex/target_detector.py +++ b/src/adf_core_python/core/component/module/complex/target_detector.py @@ -8,52 +8,52 @@ from adf_core_python.core.component.module.abstract_module import AbstractModule if TYPE_CHECKING: - from rcrscore.entities import EntityID + from rcrscore.entities import EntityID - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData T = TypeVar("T", bound=Entity) class TargetDetector(AbstractModule, Generic[T]): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - @abstractmethod - def get_target_entity_id(self) -> Optional[EntityID]: - pass - - @abstractmethod - def calculate(self) -> TargetDetector[T]: - pass - - def precompute(self, precompute_data: PrecomputeData) -> TargetDetector[T]: - super().precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> TargetDetector[T]: - super().resume(precompute_data) - return self - - def prepare(self) -> TargetDetector[T]: - super().prepare() - return self - - def update_info(self, message_manager: MessageManager) -> TargetDetector[T]: - super().update_info(message_manager) - return self + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + @abstractmethod + def get_target_entity_id(self) -> Optional[EntityID]: + pass + + @abstractmethod + def calculate(self) -> TargetDetector[T]: + pass + + def precompute(self, precompute_data: PrecomputeData) -> TargetDetector[T]: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> TargetDetector[T]: + super().resume(precompute_data) + return self + + def prepare(self) -> TargetDetector[T]: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> TargetDetector[T]: + super().update_info(message_manager) + return self diff --git a/src/adf_core_python/core/component/tactics/tactics_agent.py b/src/adf_core_python/core/component/tactics/tactics_agent.py index 3f1343e..9502a2c 100644 --- a/src/adf_core_python/core/component/tactics/tactics_agent.py +++ b/src/adf_core_python/core/component/tactics/tactics_agent.py @@ -8,165 +8,165 @@ from adf_core_python.core.logger.logger import get_agent_logger if TYPE_CHECKING: - from adf_core_python.core.agent.action.action import Action - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.component.action.extend_action import ExtendAction - from adf_core_python.core.component.module.abstract_module import AbstractModule + from adf_core_python.core.agent.action.action import Action + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.component.action.extend_action import ExtendAction + from adf_core_python.core.component.module.abstract_module import AbstractModule class TacticsAgent(ABC): - def __init__(self, parent: Optional[TacticsAgent] = None) -> None: - self._parent = parent - self._modules: list[AbstractModule] = [] - self._actions: list[ExtendAction] = [] - self._command_executor: list[CommandExecutor] = [] - - @abstractmethod - def initialize( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self._logger = get_agent_logger( - f"{self.__class__.__module__}.{self.__class__.__qualname__}", agent_info - ) - - @abstractmethod - def precompute( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - raise NotImplementedError - - @abstractmethod - def resume( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - raise NotImplementedError - - @abstractmethod - def prepare( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - develop_data: DevelopData, - ) -> None: - raise NotImplementedError - - @abstractmethod - def think( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> Action: - raise NotImplementedError - - def get_parent_tactics(self) -> Optional[TacticsAgent]: - return self._parent - - def register_module(self, module: AbstractModule) -> None: - self._modules.append(module) - - def unregister_module(self, module: AbstractModule) -> None: - self._modules.remove(module) - - def register_action(self, action: ExtendAction) -> None: - self._actions.append(action) - - def unregister_action(self, action: ExtendAction) -> None: - self._actions.remove(action) - - def register_command_executor(self, command_executor: CommandExecutor) -> None: - self._command_executor.append(command_executor) - - def unregister_command_executor(self, command_executor: CommandExecutor) -> None: - self._command_executor.remove(command_executor) - - def module_precompute(self, precompute_data: PrecomputeData) -> None: - for module in self._modules: - module.precompute(precompute_data) - for action in self._actions: - action.precompute(precompute_data) - for executor in self._command_executor: - executor.precompute(precompute_data) - - def module_resume(self, precompute_data: PrecomputeData) -> None: - for module in self._modules: - module.resume(precompute_data) - for action in self._actions: - action.resume(precompute_data) - for executor in self._command_executor: - executor.resume(precompute_data) - - def module_prepare(self) -> None: - for module in self._modules: - start_time = time.time() - module.prepare() - self._logger.debug( - f"module {module.__class__.__name__} prepare time: {time.time() - start_time:.3f}", - ) - for action in self._actions: - start_time = time.time() - action.prepare() - self._logger.debug( - f"module {action.__class__.__name__} prepare time: {time.time() - start_time:.3f}", - ) - for executor in self._command_executor: - executor.prepare() - - def module_update_info(self, message_manager: MessageManager) -> None: - for module in self._modules: - module.update_info(message_manager) - for action in self._actions: - action.update_info(message_manager) - for executor in self._command_executor: - executor.update_info(message_manager) - - def reset_count(self) -> None: - for module in self._modules: - module.reset_count_precompute() - module.reset_count_resume() - module.reset_count_prepare() - module.reset_count_update_info() - for action in self._actions: - action.reset_count_precompute() - action.reset_count_resume() - action.reset_count_prepare() - action.reset_count_update_info() - for executor in self._command_executor: - executor.reset_count_precompute() - executor.reset_count_resume() - executor.reset_count_prepare() - executor.reset_count_update_info() + def __init__(self, parent: Optional[TacticsAgent] = None) -> None: + self._parent = parent + self._modules: list[AbstractModule] = [] + self._actions: list[ExtendAction] = [] + self._command_executor: list[CommandExecutor] = [] + + @abstractmethod + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", agent_info + ) + + @abstractmethod + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + @abstractmethod + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + @abstractmethod + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + @abstractmethod + def think( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> Action: + raise NotImplementedError + + def get_parent_tactics(self) -> Optional[TacticsAgent]: + return self._parent + + def register_module(self, module: AbstractModule) -> None: + self._modules.append(module) + + def unregister_module(self, module: AbstractModule) -> None: + self._modules.remove(module) + + def register_action(self, action: ExtendAction) -> None: + self._actions.append(action) + + def unregister_action(self, action: ExtendAction) -> None: + self._actions.remove(action) + + def register_command_executor(self, command_executor: CommandExecutor) -> None: + self._command_executor.append(command_executor) + + def unregister_command_executor(self, command_executor: CommandExecutor) -> None: + self._command_executor.remove(command_executor) + + def module_precompute(self, precompute_data: PrecomputeData) -> None: + for module in self._modules: + module.precompute(precompute_data) + for action in self._actions: + action.precompute(precompute_data) + for executor in self._command_executor: + executor.precompute(precompute_data) + + def module_resume(self, precompute_data: PrecomputeData) -> None: + for module in self._modules: + module.resume(precompute_data) + for action in self._actions: + action.resume(precompute_data) + for executor in self._command_executor: + executor.resume(precompute_data) + + def module_prepare(self) -> None: + for module in self._modules: + start_time = time.time() + module.prepare() + self._logger.debug( + f"module {module.__class__.__name__} prepare time: {time.time() - start_time:.3f}", + ) + for action in self._actions: + start_time = time.time() + action.prepare() + self._logger.debug( + f"module {action.__class__.__name__} prepare time: {time.time() - start_time:.3f}", + ) + for executor in self._command_executor: + executor.prepare() + + def module_update_info(self, message_manager: MessageManager) -> None: + for module in self._modules: + module.update_info(message_manager) + for action in self._actions: + action.update_info(message_manager) + for executor in self._command_executor: + executor.update_info(message_manager) + + def reset_count(self) -> None: + for module in self._modules: + module.reset_count_precompute() + module.reset_count_resume() + module.reset_count_prepare() + module.reset_count_update_info() + for action in self._actions: + action.reset_count_precompute() + action.reset_count_resume() + action.reset_count_prepare() + action.reset_count_update_info() + for executor in self._command_executor: + executor.reset_count_precompute() + executor.reset_count_resume() + executor.reset_count_prepare() + executor.reset_count_update_info() diff --git a/src/adf_core_python/core/component/tactics/tactics_ambulance_center.py b/src/adf_core_python/core/component/tactics/tactics_ambulance_center.py index da9b2b8..04eba00 100644 --- a/src/adf_core_python/core/component/tactics/tactics_ambulance_center.py +++ b/src/adf_core_python/core/component/tactics/tactics_ambulance_center.py @@ -6,5 +6,5 @@ class TacticsAmbulanceCenter(TacticsCenter): - def __init__(self, parent: Optional[TacticsAmbulanceCenter] = None) -> None: - super().__init__(parent) + def __init__(self, parent: Optional[TacticsAmbulanceCenter] = None) -> None: + super().__init__(parent) diff --git a/src/adf_core_python/core/component/tactics/tactics_ambulance_team.py b/src/adf_core_python/core/component/tactics/tactics_ambulance_team.py index c5ad2b4..5e2257b 100644 --- a/src/adf_core_python/core/component/tactics/tactics_ambulance_team.py +++ b/src/adf_core_python/core/component/tactics/tactics_ambulance_team.py @@ -6,5 +6,5 @@ class TacticsAmbulanceTeam(TacticsAgent): - def __init__(self, parent: Optional[TacticsAmbulanceTeam] = None) -> None: - super().__init__(parent) + def __init__(self, parent: Optional[TacticsAmbulanceTeam] = None) -> None: + super().__init__(parent) diff --git a/src/adf_core_python/core/component/tactics/tactics_center.py b/src/adf_core_python/core/component/tactics/tactics_center.py index 0e76a79..3e35bde 100644 --- a/src/adf_core_python/core/component/tactics/tactics_center.py +++ b/src/adf_core_python/core/component/tactics/tactics_center.py @@ -6,121 +6,121 @@ from adf_core_python.core.component.centralized.command_picker import CommandPicker if TYPE_CHECKING: - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.component.module.abstract_module import AbstractModule + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.component.module.abstract_module import AbstractModule class TacticsCenter(ABC): - def __init__(self, parent: Optional[TacticsCenter] = None) -> None: - self._parent = parent - self._modules: list[AbstractModule] = [] - self._command_pickers: list[CommandPicker] = [] - - @abstractmethod - def initialize( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - raise NotImplementedError - - @abstractmethod - def resume( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - raise NotImplementedError - - @abstractmethod - def precompute( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - raise NotImplementedError - - @abstractmethod - def prepare( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - develop_data: DevelopData, - ) -> None: - raise NotImplementedError - - @abstractmethod - def think( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - raise NotImplementedError - - def get_parent_tactics(self) -> Optional[TacticsCenter]: - return self._parent - - def register_module(self, module: AbstractModule) -> None: - self._modules.append(module) - - def unregister_module(self, module: AbstractModule) -> None: - self._modules.remove(module) - - def register_command_picker(self, command_picker: Any) -> None: - self._command_pickers.append(command_picker) - - def unregister_command_picker(self, command_picker: Any) -> None: - self._command_pickers.remove(command_picker) - - def module_precompute(self, precompute_data: PrecomputeData) -> None: - for module in self._modules: - module.precompute(precompute_data) - for command_picker in self._command_pickers: - command_picker.precompute(precompute_data) - - def module_resume(self, precompute_data: PrecomputeData) -> None: - for module in self._modules: - module.resume(precompute_data) - for command_picker in self._command_pickers: - command_picker.resume(precompute_data) - - def module_prepare(self) -> None: - for module in self._modules: - module.prepare() - for command_picker in self._command_pickers: - command_picker.prepare() - - def module_update_info(self, message_manager: MessageManager) -> None: - for module in self._modules: - module.update_info(message_manager) - for command_picker in self._command_pickers: - command_picker.update_info(message_manager) + def __init__(self, parent: Optional[TacticsCenter] = None) -> None: + self._parent = parent + self._modules: list[AbstractModule] = [] + self._command_pickers: list[CommandPicker] = [] + + @abstractmethod + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + @abstractmethod + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + @abstractmethod + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + @abstractmethod + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + @abstractmethod + def think( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + + def get_parent_tactics(self) -> Optional[TacticsCenter]: + return self._parent + + def register_module(self, module: AbstractModule) -> None: + self._modules.append(module) + + def unregister_module(self, module: AbstractModule) -> None: + self._modules.remove(module) + + def register_command_picker(self, command_picker: Any) -> None: + self._command_pickers.append(command_picker) + + def unregister_command_picker(self, command_picker: Any) -> None: + self._command_pickers.remove(command_picker) + + def module_precompute(self, precompute_data: PrecomputeData) -> None: + for module in self._modules: + module.precompute(precompute_data) + for command_picker in self._command_pickers: + command_picker.precompute(precompute_data) + + def module_resume(self, precompute_data: PrecomputeData) -> None: + for module in self._modules: + module.resume(precompute_data) + for command_picker in self._command_pickers: + command_picker.resume(precompute_data) + + def module_prepare(self) -> None: + for module in self._modules: + module.prepare() + for command_picker in self._command_pickers: + command_picker.prepare() + + def module_update_info(self, message_manager: MessageManager) -> None: + for module in self._modules: + module.update_info(message_manager) + for command_picker in self._command_pickers: + command_picker.update_info(message_manager) diff --git a/src/adf_core_python/core/component/tactics/tactics_fire_brigade.py b/src/adf_core_python/core/component/tactics/tactics_fire_brigade.py index 1df16b3..75bc6b6 100644 --- a/src/adf_core_python/core/component/tactics/tactics_fire_brigade.py +++ b/src/adf_core_python/core/component/tactics/tactics_fire_brigade.py @@ -6,5 +6,5 @@ class TacticsFireBrigade(TacticsAgent): - def __init__(self, parent: Optional[TacticsFireBrigade] = None) -> None: - super().__init__(parent) + def __init__(self, parent: Optional[TacticsFireBrigade] = None) -> None: + super().__init__(parent) diff --git a/src/adf_core_python/core/component/tactics/tactics_fire_station.py b/src/adf_core_python/core/component/tactics/tactics_fire_station.py index 0b6ed40..acd2563 100644 --- a/src/adf_core_python/core/component/tactics/tactics_fire_station.py +++ b/src/adf_core_python/core/component/tactics/tactics_fire_station.py @@ -6,5 +6,5 @@ class TacticsFireStation(TacticsCenter): - def __init__(self, parent: Optional[TacticsFireStation] = None) -> None: - super().__init__(parent) + def __init__(self, parent: Optional[TacticsFireStation] = None) -> None: + super().__init__(parent) diff --git a/src/adf_core_python/core/component/tactics/tactics_police_force.py b/src/adf_core_python/core/component/tactics/tactics_police_force.py index a098128..750e6f2 100644 --- a/src/adf_core_python/core/component/tactics/tactics_police_force.py +++ b/src/adf_core_python/core/component/tactics/tactics_police_force.py @@ -6,5 +6,5 @@ class TacticsPoliceForce(TacticsAgent): - def __init__(self, parent: Optional[TacticsPoliceForce] = None) -> None: - super().__init__(parent) + def __init__(self, parent: Optional[TacticsPoliceForce] = None) -> None: + super().__init__(parent) diff --git a/src/adf_core_python/core/component/tactics/tactics_police_office.py b/src/adf_core_python/core/component/tactics/tactics_police_office.py index 70950e1..a47debd 100644 --- a/src/adf_core_python/core/component/tactics/tactics_police_office.py +++ b/src/adf_core_python/core/component/tactics/tactics_police_office.py @@ -6,5 +6,5 @@ class TacticsPoliceOffice(TacticsCenter): - def __init__(self, parent: Optional[TacticsPoliceOffice] = None) -> None: - super().__init__(parent) + def __init__(self, parent: Optional[TacticsPoliceOffice] = None) -> None: + super().__init__(parent) diff --git a/src/adf_core_python/core/config/config.py b/src/adf_core_python/core/config/config.py index 5f55aea..a2f98a7 100644 --- a/src/adf_core_python/core/config/config.py +++ b/src/adf_core_python/core/config/config.py @@ -4,40 +4,40 @@ class Config: - def __init__(self, config_file: Optional[str] = None) -> None: - self.config: dict[str, Any] = {} - if config_file: - self.config = self.read_from_yaml(config_file) - self.config = self.flatten(self.config) - - def set_value(self, key: str, value: Any) -> None: - self.config[key] = value - - def get_value(self, key: str, default: Any = None) -> Any: - return self.config.get(key, default) - - def read_from_yaml(self, file_name: str) -> dict[str, Any]: - try: - with open(file_name, mode="r", encoding="utf-8") as file: - data = safe_load(file) - except FileNotFoundError: - raise FileNotFoundError(f"Config file not found: {file_name}") - except Exception as e: - raise Exception(f"Error reading config file: {file_name}, {e}") - - return data - - def flatten( - self, data: dict[str, Any], parent_key: str = "", sep: str = "." - ) -> dict[str, Any]: - flatten_data = {} - for key, value in data.items(): - new_key = f"{parent_key}{sep}{key}" if parent_key else key - if isinstance(value, dict): - flatten_data.update(self.flatten(value, new_key, sep=sep)) - else: - flatten_data[new_key] = value - return flatten_data - - def __str__(self) -> str: - return str(self.config) + def __init__(self, config_file: Optional[str] = None) -> None: + self.config: dict[str, Any] = {} + if config_file: + self.config = self.read_from_yaml(config_file) + self.config = self.flatten(self.config) + + def set_value(self, key: str, value: Any) -> None: + self.config[key] = value + + def get_value(self, key: str, default: Any = None) -> Any: + return self.config.get(key, default) + + def read_from_yaml(self, file_name: str) -> dict[str, Any]: + try: + with open(file_name, mode="r", encoding="utf-8") as file: + data = safe_load(file) + except FileNotFoundError: + raise FileNotFoundError(f"Config file not found: {file_name}") + except Exception as e: + raise Exception(f"Error reading config file: {file_name}, {e}") + + return data + + def flatten( + self, data: dict[str, Any], parent_key: str = "", sep: str = "." + ) -> dict[str, Any]: + flatten_data = {} + for key, value in data.items(): + new_key = f"{parent_key}{sep}{key}" if parent_key else key + if isinstance(value, dict): + flatten_data.update(self.flatten(value, new_key, sep=sep)) + else: + flatten_data[new_key] = value + return flatten_data + + def __str__(self) -> str: + return str(self.config) diff --git a/src/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py b/src/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py index c01047f..8f0c902 100644 --- a/src/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py +++ b/src/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py @@ -7,88 +7,88 @@ from adf_core_python.core.component.module.algorithm.clustering import Clustering from adf_core_python.core.gateway.component.module.gateway_abstract_module import ( - GatewayAbstractModule, + GatewayAbstractModule, ) if TYPE_CHECKING: - from rcrscore.entities.entity import Entity + from rcrscore.entities.entity import Entity - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.gateway.gateway_module import GatewayModule + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule class GatewayClustering(GatewayAbstractModule, Clustering): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - gateway_module: GatewayModule, - ) -> None: - super().__init__( - agent_info, - world_info, - scenario_info, - module_manager, - develop_data, - gateway_module, - ) - - def precompute(self, precompute_data: PrecomputeData) -> GatewayClustering: - super().precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> GatewayClustering: - super().resume(precompute_data) - return self - - def prepare(self) -> GatewayClustering: - super().prepare() - return self - - def update_info(self, message_manager: MessageManager) -> GatewayClustering: - super().update_info(message_manager) - return self - - def calculate(self) -> GatewayClustering: - super().calculate() - return self - - def get_cluster_number(self) -> int: - result = self._gateway_module.execute("getClusterNumber") - return int(result.get_value("ClusterNumber") or 0) - - def get_cluster_index(self, entity_id: EntityID) -> int: - arguments: dict[str, str] = {"EntityID": str(entity_id.get_value())} - result = self._gateway_module.execute("getClusterIndex(EntityID)", arguments) - return int(result.get_value("ClusterIndex") or 0) - - def get_cluster_entities(self, cluster_index: int) -> list[Entity]: - arguments: dict[str, str] = {"Index": str(cluster_index)} - result = self._gateway_module.execute("getClusterEntities(int)", arguments) - json_str = result.get_value("EntityIDs") or "[]" - entity_ids: list[int] = json.loads(json_str) - entities: list[Entity] = [] - for entity_id in entity_ids: - entity = self._world_info.get_entity(EntityID(entity_id)) - if entity is not None: - entities.append(entity) - return entities - - def get_cluster_entity_ids(self, cluster_index: int) -> list[EntityID]: - arguments: dict[str, str] = {"Index": str(cluster_index)} - result = self._gateway_module.execute("getClusterEntityIDs(int)", arguments) - json_str = result.get_value("EntityIDs") or "[]" - raw_entity_ids: list[int] = json.loads(json_str) - entity_ids: list[EntityID] = [] - for entity_id in raw_entity_ids: - entity_ids.append(EntityID(entity_id)) - return entity_ids + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute(self, precompute_data: PrecomputeData) -> GatewayClustering: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewayClustering: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewayClustering: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> GatewayClustering: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewayClustering: + super().calculate() + return self + + def get_cluster_number(self) -> int: + result = self._gateway_module.execute("getClusterNumber") + return int(result.get_value("ClusterNumber") or 0) + + def get_cluster_index(self, entity_id: EntityID) -> int: + arguments: dict[str, str] = {"EntityID": str(entity_id.get_value())} + result = self._gateway_module.execute("getClusterIndex(EntityID)", arguments) + return int(result.get_value("ClusterIndex") or 0) + + def get_cluster_entities(self, cluster_index: int) -> list[Entity]: + arguments: dict[str, str] = {"Index": str(cluster_index)} + result = self._gateway_module.execute("getClusterEntities(int)", arguments) + json_str = result.get_value("EntityIDs") or "[]" + entity_ids: list[int] = json.loads(json_str) + entities: list[Entity] = [] + for entity_id in entity_ids: + entity = self._world_info.get_entity(EntityID(entity_id)) + if entity is not None: + entities.append(entity) + return entities + + def get_cluster_entity_ids(self, cluster_index: int) -> list[EntityID]: + arguments: dict[str, str] = {"Index": str(cluster_index)} + result = self._gateway_module.execute("getClusterEntityIDs(int)", arguments) + json_str = result.get_value("EntityIDs") or "[]" + raw_entity_ids: list[int] = json.loads(json_str) + entity_ids: list[EntityID] = [] + for entity_id in raw_entity_ids: + entity_ids.append(EntityID(entity_id)) + return entity_ids diff --git a/src/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py b/src/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py index ef4df09..76a146d 100644 --- a/src/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py +++ b/src/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py @@ -7,101 +7,97 @@ from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning from adf_core_python.core.gateway.component.module.gateway_abstract_module import ( - GatewayAbstractModule, + GatewayAbstractModule, ) if TYPE_CHECKING: - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.gateway.gateway_module import GatewayModule + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule class GatewayPathPlanning(GatewayAbstractModule, PathPlanning): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - gateway_module: GatewayModule, - ) -> None: - super().__init__( - agent_info, - world_info, - scenario_info, - module_manager, - develop_data, - gateway_module, - ) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) - def precompute(self, precompute_data: PrecomputeData) -> GatewayPathPlanning: - super().precompute(precompute_data) - return self + def precompute(self, precompute_data: PrecomputeData) -> GatewayPathPlanning: + super().precompute(precompute_data) + return self - def resume(self, precompute_data: PrecomputeData) -> GatewayPathPlanning: - super().resume(precompute_data) - return self + def resume(self, precompute_data: PrecomputeData) -> GatewayPathPlanning: + super().resume(precompute_data) + return self - def prepare(self) -> GatewayPathPlanning: - super().prepare() - return self + def prepare(self) -> GatewayPathPlanning: + super().prepare() + return self - def update_info(self, message_manager: MessageManager) -> GatewayPathPlanning: - super().update_info(message_manager) - return self + def update_info(self, message_manager: MessageManager) -> GatewayPathPlanning: + super().update_info(message_manager) + return self - def calculate(self) -> GatewayPathPlanning: - super().calculate() - return self + def calculate(self) -> GatewayPathPlanning: + super().calculate() + return self - def get_path( - self, from_entity_id: EntityID, to_entity_id: EntityID - ) -> list[EntityID]: - arguments: dict[str, str] = { - "From": str(from_entity_id.get_value()), - "To": str(to_entity_id.get_value()), - } - result = self._gateway_module.execute( - "getResult(EntityID, EntityID)", arguments - ) - json_str = result.get_value("Result") or "[]" - raw_entity_ids: list[int] = json.loads(json_str) - entity_ids: list[EntityID] = [] - for entity_id in raw_entity_ids: - entity_ids.append(EntityID(entity_id)) - return entity_ids + def get_path( + self, from_entity_id: EntityID, to_entity_id: EntityID + ) -> list[EntityID]: + arguments: dict[str, str] = { + "From": str(from_entity_id.get_value()), + "To": str(to_entity_id.get_value()), + } + result = self._gateway_module.execute("getResult(EntityID, EntityID)", arguments) + json_str = result.get_value("Result") or "[]" + raw_entity_ids: list[int] = json.loads(json_str) + entity_ids: list[EntityID] = [] + for entity_id in raw_entity_ids: + entity_ids.append(EntityID(entity_id)) + return entity_ids - def get_path_to_multiple_destinations( - self, from_entity_id: EntityID, destination_entity_ids: set[EntityID] - ) -> list[EntityID]: - arguments: dict[str, str] = { - "From": str(from_entity_id.get_value()), - "Destinations": json.dumps( - [entity_id.get_value() for entity_id in destination_entity_ids] - ), - } - result = self._gateway_module.execute( - "getResult(EntityID, List[EntityID])", arguments - ) - json_str = result.get_value("Result") or "[]" - raw_entity_ids: list[int] = json.loads(json_str) - entity_ids: list[EntityID] = [] - for entity_id in raw_entity_ids: - entity_ids.append(EntityID(entity_id)) - return entity_ids + def get_path_to_multiple_destinations( + self, from_entity_id: EntityID, destination_entity_ids: set[EntityID] + ) -> list[EntityID]: + arguments: dict[str, str] = { + "From": str(from_entity_id.get_value()), + "Destinations": json.dumps( + [entity_id.get_value() for entity_id in destination_entity_ids] + ), + } + result = self._gateway_module.execute( + "getResult(EntityID, List[EntityID])", arguments + ) + json_str = result.get_value("Result") or "[]" + raw_entity_ids: list[int] = json.loads(json_str) + entity_ids: list[EntityID] = [] + for entity_id in raw_entity_ids: + entity_ids.append(EntityID(entity_id)) + return entity_ids - def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: - arguments: dict[str, str] = { - "From": str(from_entity_id.get_value()), - "To": str(to_entity_id.get_value()), - } - result = self._gateway_module.execute( - "getDistance(EntityID, EntityID)", arguments - ) - return float(result.get_value("Result") or 0.0) + def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: + arguments: dict[str, str] = { + "From": str(from_entity_id.get_value()), + "To": str(to_entity_id.get_value()), + } + result = self._gateway_module.execute("getDistance(EntityID, EntityID)", arguments) + return float(result.get_value("Result") or 0.0) diff --git a/src/adf_core_python/core/gateway/component/module/complex/gateway_ambulance_target_allocator.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_ambulance_target_allocator.py index f570436..fcd4902 100644 --- a/src/adf_core_python/core/gateway/component/module/complex/gateway_ambulance_target_allocator.py +++ b/src/adf_core_python/core/gateway/component/module/complex/gateway_ambulance_target_allocator.py @@ -3,64 +3,62 @@ from typing import TYPE_CHECKING from adf_core_python.core.component.module.complex.ambulance_target_allocator import ( - AmbulanceTargetAllocator, + AmbulanceTargetAllocator, ) from adf_core_python.core.gateway.component.module.complex.gateway_target_allocator import ( - GatewayTargetAllocator, + GatewayTargetAllocator, ) if TYPE_CHECKING: - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.gateway.gateway_module import GatewayModule + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule class GatewayAmbulanceTargetAllocator(GatewayTargetAllocator, AmbulanceTargetAllocator): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - gateway_module: GatewayModule, - ) -> None: - super().__init__( - agent_info, - world_info, - scenario_info, - module_manager, - develop_data, - gateway_module, - ) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) - def precompute( - self, precompute_data: PrecomputeData - ) -> GatewayAmbulanceTargetAllocator: - super().precompute(precompute_data) - return self + def precompute( + self, precompute_data: PrecomputeData + ) -> GatewayAmbulanceTargetAllocator: + super().precompute(precompute_data) + return self - def resume( - self, precompute_data: PrecomputeData - ) -> GatewayAmbulanceTargetAllocator: - super().resume(precompute_data) - return self + def resume(self, precompute_data: PrecomputeData) -> GatewayAmbulanceTargetAllocator: + super().resume(precompute_data) + return self - def prepare(self) -> GatewayAmbulanceTargetAllocator: - super().prepare() - return self + def prepare(self) -> GatewayAmbulanceTargetAllocator: + super().prepare() + return self - def update_info( - self, message_manager: MessageManager - ) -> GatewayAmbulanceTargetAllocator: - super().update_info(message_manager) - return self + def update_info( + self, message_manager: MessageManager + ) -> GatewayAmbulanceTargetAllocator: + super().update_info(message_manager) + return self - def calculate(self) -> GatewayAmbulanceTargetAllocator: - super().calculate() - return self + def calculate(self) -> GatewayAmbulanceTargetAllocator: + super().calculate() + return self diff --git a/src/adf_core_python/core/gateway/component/module/complex/gateway_fire_target_allocator.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_fire_target_allocator.py index 69fb643..c3b4a78 100644 --- a/src/adf_core_python/core/gateway/component/module/complex/gateway_fire_target_allocator.py +++ b/src/adf_core_python/core/gateway/component/module/complex/gateway_fire_target_allocator.py @@ -3,60 +3,58 @@ from typing import TYPE_CHECKING from adf_core_python.core.component.module.complex.fire_target_allocator import ( - FireTargetAllocator, + FireTargetAllocator, ) from adf_core_python.core.gateway.component.module.complex.gateway_target_allocator import ( - GatewayTargetAllocator, + GatewayTargetAllocator, ) if TYPE_CHECKING: - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.gateway.gateway_module import GatewayModule + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule class GatewayFireTargetAllocator(GatewayTargetAllocator, FireTargetAllocator): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - gateway_module: GatewayModule, - ) -> None: - super().__init__( - agent_info, - world_info, - scenario_info, - module_manager, - develop_data, - gateway_module, - ) - - def precompute(self, precompute_data: PrecomputeData) -> GatewayFireTargetAllocator: - super().precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> GatewayFireTargetAllocator: - super().resume(precompute_data) - return self - - def prepare(self) -> GatewayFireTargetAllocator: - super().prepare() - return self - - def update_info( - self, message_manager: MessageManager - ) -> GatewayFireTargetAllocator: - super().update_info(message_manager) - return self - - def calculate(self) -> GatewayFireTargetAllocator: - super().calculate() - return self + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute(self, precompute_data: PrecomputeData) -> GatewayFireTargetAllocator: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewayFireTargetAllocator: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewayFireTargetAllocator: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> GatewayFireTargetAllocator: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewayFireTargetAllocator: + super().calculate() + return self diff --git a/src/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py index 7692f07..41b1520 100644 --- a/src/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py +++ b/src/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py @@ -5,58 +5,58 @@ from rcrscore.entities.human import Human from adf_core_python.core.component.module.complex.human_detector import ( - HumanDetector, + HumanDetector, ) from adf_core_python.core.gateway.component.module.complex.gateway_target_detector import ( - GatewayTargetDetector, + GatewayTargetDetector, ) if TYPE_CHECKING: - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.gateway.gateway_module import GatewayModule + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule class GatewayHumanDetector(GatewayTargetDetector[Human], HumanDetector): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - gateway_module: GatewayModule, - ) -> None: - super().__init__( - agent_info, - world_info, - scenario_info, - module_manager, - develop_data, - gateway_module, - ) - - def precompute(self, precompute_data: PrecomputeData) -> GatewayHumanDetector: - super().precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> GatewayHumanDetector: - super().resume(precompute_data) - return self - - def prepare(self) -> GatewayHumanDetector: - super().prepare() - return self - - def update_info(self, message_manager: MessageManager) -> GatewayHumanDetector: - super().update_info(message_manager) - return self - - def calculate(self) -> GatewayHumanDetector: - super().calculate() - return self + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute(self, precompute_data: PrecomputeData) -> GatewayHumanDetector: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewayHumanDetector: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewayHumanDetector: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> GatewayHumanDetector: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewayHumanDetector: + super().calculate() + return self diff --git a/src/adf_core_python/core/gateway/component/module/complex/gateway_police_target_allocator.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_police_target_allocator.py index bacc0bb..a9ea780 100644 --- a/src/adf_core_python/core/gateway/component/module/complex/gateway_police_target_allocator.py +++ b/src/adf_core_python/core/gateway/component/module/complex/gateway_police_target_allocator.py @@ -3,62 +3,60 @@ from typing import TYPE_CHECKING from adf_core_python.core.component.module.complex.police_target_allocator import ( - PoliceTargetAllocator, + PoliceTargetAllocator, ) from adf_core_python.core.gateway.component.module.complex.gateway_target_allocator import ( - GatewayTargetAllocator, + GatewayTargetAllocator, ) if TYPE_CHECKING: - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.gateway.gateway_module import GatewayModule + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule class GatewayPoliceTargetAllocator(GatewayTargetAllocator, PoliceTargetAllocator): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - gateway_module: GatewayModule, - ) -> None: - super().__init__( - agent_info, - world_info, - scenario_info, - module_manager, - develop_data, - gateway_module, - ) - - def precompute( - self, precompute_data: PrecomputeData - ) -> GatewayPoliceTargetAllocator: - super().precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> GatewayPoliceTargetAllocator: - super().resume(precompute_data) - return self - - def prepare(self) -> GatewayPoliceTargetAllocator: - super().prepare() - return self - - def update_info( - self, message_manager: MessageManager - ) -> GatewayPoliceTargetAllocator: - super().update_info(message_manager) - return self - - def calculate(self) -> GatewayPoliceTargetAllocator: - super().calculate() - return self + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute(self, precompute_data: PrecomputeData) -> GatewayPoliceTargetAllocator: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewayPoliceTargetAllocator: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewayPoliceTargetAllocator: + super().prepare() + return self + + def update_info( + self, message_manager: MessageManager + ) -> GatewayPoliceTargetAllocator: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewayPoliceTargetAllocator: + super().calculate() + return self diff --git a/src/adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py index bfceb81..e5b478a 100644 --- a/src/adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py +++ b/src/adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py @@ -7,54 +7,54 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.component.module.complex.road_detector import RoadDetector from adf_core_python.core.gateway.component.module.complex.gateway_target_detector import ( - GatewayTargetDetector, + GatewayTargetDetector, ) if TYPE_CHECKING: - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.gateway.gateway_module import GatewayModule + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule class GatewayRoadDetector(GatewayTargetDetector[Road], RoadDetector): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - gateway_module: GatewayModule, - ) -> None: - super().__init__( - agent_info, - world_info, - scenario_info, - module_manager, - develop_data, - gateway_module, - ) - - def precompute(self, precompute_data: PrecomputeData) -> GatewayRoadDetector: - super().precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> GatewayRoadDetector: - super().resume(precompute_data) - return self - - def prepare(self) -> GatewayRoadDetector: - super().prepare() - return self - - def update_info(self, message_manager: MessageManager) -> GatewayRoadDetector: - super().update_info(message_manager) - return self - - def calculate(self) -> GatewayRoadDetector: - super().calculate() - return self + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute(self, precompute_data: PrecomputeData) -> GatewayRoadDetector: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewayRoadDetector: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewayRoadDetector: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> GatewayRoadDetector: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewayRoadDetector: + super().calculate() + return self diff --git a/src/adf_core_python/core/gateway/component/module/complex/gateway_search.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_search.py index bf37e2c..083f3cc 100644 --- a/src/adf_core_python/core/gateway/component/module/complex/gateway_search.py +++ b/src/adf_core_python/core/gateway/component/module/complex/gateway_search.py @@ -4,55 +4,55 @@ from adf_core_python.core.component.module.complex.search import Search from adf_core_python.core.gateway.component.module.complex.gateway_target_detector import ( - GatewayTargetDetector, + GatewayTargetDetector, ) if TYPE_CHECKING: - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.gateway.gateway_module import GatewayModule + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule class GatewaySearch(GatewayTargetDetector, Search): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - gateway_module: GatewayModule, - ) -> None: - super().__init__( - agent_info, - world_info, - scenario_info, - module_manager, - develop_data, - gateway_module, - ) - - def precompute(self, precompute_data: PrecomputeData) -> GatewaySearch: - super().precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> GatewaySearch: - super().resume(precompute_data) - return self - - def prepare(self) -> GatewaySearch: - super().prepare() - return self - - def update_info(self, message_manager: MessageManager) -> GatewaySearch: - super().update_info(message_manager) - return self - - def calculate(self) -> GatewaySearch: - super().calculate() - return self + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute(self, precompute_data: PrecomputeData) -> GatewaySearch: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewaySearch: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewaySearch: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> GatewaySearch: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewaySearch: + super().calculate() + return self diff --git a/src/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py index df35491..1765c47 100644 --- a/src/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py +++ b/src/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py @@ -5,70 +5,68 @@ from rcrscore.entities import EntityID from adf_core_python.core.component.module.complex.target_allocator import ( - TargetAllocator, + TargetAllocator, ) from adf_core_python.core.gateway.component.module.gateway_abstract_module import ( - GatewayAbstractModule, + GatewayAbstractModule, ) if TYPE_CHECKING: - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.gateway.gateway_module import GatewayModule + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule class GatewayTargetAllocator(GatewayAbstractModule, TargetAllocator): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - gateway_module: GatewayModule, - ) -> None: - super().__init__( - agent_info, - world_info, - scenario_info, - module_manager, - develop_data, - gateway_module, - ) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) - def precompute(self, precompute_data: PrecomputeData) -> GatewayTargetAllocator: - super().precompute(precompute_data) - return self + def precompute(self, precompute_data: PrecomputeData) -> GatewayTargetAllocator: + super().precompute(precompute_data) + return self - def resume(self, precompute_data: PrecomputeData) -> GatewayTargetAllocator: - super().resume(precompute_data) - return self + def resume(self, precompute_data: PrecomputeData) -> GatewayTargetAllocator: + super().resume(precompute_data) + return self - def prepare(self) -> GatewayTargetAllocator: - super().prepare() - return self + def prepare(self) -> GatewayTargetAllocator: + super().prepare() + return self - def update_info(self, message_manager: MessageManager) -> GatewayTargetAllocator: - super().update_info(message_manager) - return self + def update_info(self, message_manager: MessageManager) -> GatewayTargetAllocator: + super().update_info(message_manager) + return self - def calculate(self) -> GatewayTargetAllocator: - super().calculate() - return self + def calculate(self) -> GatewayTargetAllocator: + super().calculate() + return self - def get_result(self) -> dict[EntityID, EntityID]: - response = self._gateway_module.execute("getResult") - response_keys = response.data.keys() - result: dict[EntityID, EntityID] = {} - for key in response_keys: - value = response.get_value(key) - result[EntityID(int(key))] = EntityID( - int(value if value is not None else "-1") - ) + def get_result(self) -> dict[EntityID, EntityID]: + response = self._gateway_module.execute("getResult") + response_keys = response.data.keys() + result: dict[EntityID, EntityID] = {} + for key in response_keys: + value = response.get_value(key) + result[EntityID(int(key))] = EntityID(int(value if value is not None else "-1")) - return result + return result diff --git a/src/adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py b/src/adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py index 1f50383..4de8f36 100644 --- a/src/adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py +++ b/src/adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py @@ -7,64 +7,64 @@ from adf_core_python.core.component.module.complex.target_detector import TargetDetector from adf_core_python.core.gateway.component.module.gateway_abstract_module import ( - GatewayAbstractModule, + GatewayAbstractModule, ) if TYPE_CHECKING: - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.gateway.gateway_module import GatewayModule + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule T = TypeVar("T", bound=Entity) class GatewayTargetDetector(GatewayAbstractModule, TargetDetector, Generic[T]): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - gateway_module: GatewayModule, - ) -> None: - super().__init__( - agent_info, - world_info, - scenario_info, - module_manager, - develop_data, - gateway_module, - ) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) - def precompute(self, precompute_data: PrecomputeData) -> GatewayTargetDetector[T]: - super().precompute(precompute_data) - return self + def precompute(self, precompute_data: PrecomputeData) -> GatewayTargetDetector[T]: + super().precompute(precompute_data) + return self - def resume(self, precompute_data: PrecomputeData) -> GatewayTargetDetector[T]: - super().resume(precompute_data) - return self + def resume(self, precompute_data: PrecomputeData) -> GatewayTargetDetector[T]: + super().resume(precompute_data) + return self - def prepare(self) -> GatewayTargetDetector[T]: - super().prepare() - return self + def prepare(self) -> GatewayTargetDetector[T]: + super().prepare() + return self - def update_info(self, message_manager: MessageManager) -> GatewayTargetDetector[T]: - super().update_info(message_manager) - return self + def update_info(self, message_manager: MessageManager) -> GatewayTargetDetector[T]: + super().update_info(message_manager) + return self - def calculate(self) -> GatewayTargetDetector[T]: - super().calculate() - return self + def calculate(self) -> GatewayTargetDetector[T]: + super().calculate() + return self - def get_target_entity_id(self) -> Optional[EntityID]: - result = self._gateway_module.execute("getTarget") - entity_id_str = result.get_value("EntityID") or "-1" - if entity_id_str == "-1": - return None - return EntityID(int(entity_id_str)) + def get_target_entity_id(self) -> Optional[EntityID]: + result = self._gateway_module.execute("getTarget") + entity_id_str = result.get_value("EntityID") or "-1" + if entity_id_str == "-1": + return None + return EntityID(int(entity_id_str)) diff --git a/src/adf_core_python/core/gateway/component/module/gateway_abstract_module.py b/src/adf_core_python/core/gateway/component/module/gateway_abstract_module.py index 857977b..0f740a0 100644 --- a/src/adf_core_python/core/gateway/component/module/gateway_abstract_module.py +++ b/src/adf_core_python/core/gateway/component/module/gateway_abstract_module.py @@ -5,51 +5,51 @@ from adf_core_python.core.component.module.abstract_module import AbstractModule if TYPE_CHECKING: - from adf_core_python.core.agent.communication.message_manager import MessageManager - from adf_core_python.core.agent.develop.develop_data import DevelopData - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo - from adf_core_python.core.agent.module.module_manager import ModuleManager - from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.gateway.gateway_module import GatewayModule + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule class GatewayAbstractModule(AbstractModule): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - gateway_module: GatewayModule, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - self._gateway_module = gateway_module - - def precompute(self, precompute_data: PrecomputeData) -> GatewayAbstractModule: - super().precompute(precompute_data) - self._gateway_module.execute("precompute") - return self - - def resume(self, precompute_data: PrecomputeData) -> GatewayAbstractModule: - super().resume(precompute_data) - self._gateway_module.execute("resume") - return self - - def prepare(self) -> GatewayAbstractModule: - super().prepare() - self._gateway_module.execute("preparate") - return self - - def update_info(self, message_manager: MessageManager) -> GatewayAbstractModule: - super().update_info(message_manager) - self._gateway_module.execute("updateInfo") - return self - - def calculate(self) -> GatewayAbstractModule: - self._gateway_module.execute("calc") - return self + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._gateway_module = gateway_module + + def precompute(self, precompute_data: PrecomputeData) -> GatewayAbstractModule: + super().precompute(precompute_data) + self._gateway_module.execute("precompute") + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewayAbstractModule: + super().resume(precompute_data) + self._gateway_module.execute("resume") + return self + + def prepare(self) -> GatewayAbstractModule: + super().prepare() + self._gateway_module.execute("preparate") + return self + + def update_info(self, message_manager: MessageManager) -> GatewayAbstractModule: + super().update_info(message_manager) + self._gateway_module.execute("updateInfo") + return self + + def calculate(self) -> GatewayAbstractModule: + self._gateway_module.execute("calc") + return self diff --git a/src/adf_core_python/core/gateway/gateway_agent.py b/src/adf_core_python/core/gateway/gateway_agent.py index 7f04e19..7e886d7 100644 --- a/src/adf_core_python/core/gateway/gateway_agent.py +++ b/src/adf_core_python/core/gateway/gateway_agent.py @@ -12,108 +12,106 @@ from adf_core_python.core.gateway.message.am_update import AMUpdate from adf_core_python.core.gateway.message.ma_exec_response import MAExecResponse from adf_core_python.core.gateway.message.ma_module_response import ( - MAModuleResponse, + MAModuleResponse, ) from adf_core_python.core.gateway.message.moduleMessageFactory import ( - ModuleMessageFactory, + ModuleMessageFactory, ) from adf_core_python.core.logger.logger import get_logger if TYPE_CHECKING: - from adf_core_python.core.gateway.gateway_launcher import GatewayLauncher - from adf_core_python.core.gateway.gateway_module import GatewayModule + from adf_core_python.core.gateway.gateway_launcher import GatewayLauncher + from adf_core_python.core.gateway.gateway_module import GatewayModule class GatewayAgent: - def __init__(self, gateway_launcher: GatewayLauncher) -> None: - self._gateway_launcher = gateway_launcher - self.send_msg: Optional[Callable] = None - self._is_initialized = False - self._agent_info: Optional[AgentInfo] = None - self._world_info: Optional[WorldInfo] = None - self._scenario_info: Optional[ScenarioInfo] = None - self._gateway_modules: dict[str, GatewayModule] = {} - self._logger = get_logger(__name__) - - def get_module_count(self) -> int: - return len(self._gateway_modules) - - def add_gateway_module(self, gateway_module: GatewayModule) -> None: - self._gateway_modules[gateway_module.get_module_id()] = gateway_module - - def is_initialized(self) -> bool: - return self._is_initialized - - def set_initialize_data( - self, agent_info: AgentInfo, world_info: WorldInfo, scenario_info: ScenarioInfo - ) -> None: - self._agent_info = agent_info - self._world_info = world_info - self._scenario_info = scenario_info - - def initialize(self) -> None: - if self.send_msg is None: - raise RuntimeError("send_msg is None") - if ( - self._agent_info is None - or self._world_info is None - or self._scenario_info is None - ): - raise RuntimeError( - "Required variables is None, " - "You must exec set_initialized_data() before calling initialize()" - ) - - am_agent = AMAgent() - self.send_msg( - am_agent.write( - self._agent_info.get_entity_id(), - list(self._world_info.get_world_model().get_entities()), - self._scenario_info.get_config().config, - int(self._scenario_info.get_mode()), - ) - ) - self._is_initialized = True - - def update(self) -> None: - if self.send_msg is None: - raise RuntimeError("send_msg is None") - if self._agent_info is None or self._world_info is None: - raise RuntimeError( - "Required variables is None, " - "You must exec set_initialized_data() before calling update()" - ) - - am_update = AMUpdate() - self.send_msg( - am_update.write( - self._agent_info.get_time(), - self._world_info.get_change_set(), - self._agent_info.get_heard_commands(), - ) - ) - - def set_send_msg(self, connection_send_func: Callable) -> None: - self.send_msg = connection_send_func - - def message_received(self, msg: RCRSProto_pb2.MessageProto) -> None: - c_msg = ModuleMessageFactory().make_message(msg) - if isinstance(c_msg, MAModuleResponse): - if c_msg.module_id is None or c_msg.class_name is None: - raise RuntimeError("Failed to receive message") - - self._gateway_modules[c_msg.module_id].set_gateway_class_name( - c_msg.class_name - ) - self._gateway_modules[c_msg.module_id].set_is_initialized(True) - if isinstance(c_msg, MAExecResponse): - if c_msg.module_id is None: - raise RuntimeError("Failed to receive message") - - self._gateway_modules[c_msg.module_id].set_execute_response(c_msg.result) - self._gateway_modules[c_msg.module_id].set_is_executed(True) - - if msg.urn == CommandURN.AK_SPEAK: - if self.send_msg is None: - raise RuntimeError("send_msg is None") - self.send_msg(msg) + def __init__(self, gateway_launcher: GatewayLauncher) -> None: + self._gateway_launcher = gateway_launcher + self.send_msg: Optional[Callable] = None + self._is_initialized = False + self._agent_info: Optional[AgentInfo] = None + self._world_info: Optional[WorldInfo] = None + self._scenario_info: Optional[ScenarioInfo] = None + self._gateway_modules: dict[str, GatewayModule] = {} + self._logger = get_logger(__name__) + + def get_module_count(self) -> int: + return len(self._gateway_modules) + + def add_gateway_module(self, gateway_module: GatewayModule) -> None: + self._gateway_modules[gateway_module.get_module_id()] = gateway_module + + def is_initialized(self) -> bool: + return self._is_initialized + + def set_initialize_data( + self, agent_info: AgentInfo, world_info: WorldInfo, scenario_info: ScenarioInfo + ) -> None: + self._agent_info = agent_info + self._world_info = world_info + self._scenario_info = scenario_info + + def initialize(self) -> None: + if self.send_msg is None: + raise RuntimeError("send_msg is None") + if ( + self._agent_info is None + or self._world_info is None + or self._scenario_info is None + ): + raise RuntimeError( + "Required variables is None, " + "You must exec set_initialized_data() before calling initialize()" + ) + + am_agent = AMAgent() + self.send_msg( + am_agent.write( + self._agent_info.get_entity_id(), + list(self._world_info.get_world_model().get_entities()), + self._scenario_info.get_config().config, + int(self._scenario_info.get_mode()), + ) + ) + self._is_initialized = True + + def update(self) -> None: + if self.send_msg is None: + raise RuntimeError("send_msg is None") + if self._agent_info is None or self._world_info is None: + raise RuntimeError( + "Required variables is None, " + "You must exec set_initialized_data() before calling update()" + ) + + am_update = AMUpdate() + self.send_msg( + am_update.write( + self._agent_info.get_time(), + self._world_info.get_change_set(), + self._agent_info.get_heard_commands(), + ) + ) + + def set_send_msg(self, connection_send_func: Callable) -> None: + self.send_msg = connection_send_func + + def message_received(self, msg: RCRSProto_pb2.MessageProto) -> None: + c_msg = ModuleMessageFactory().make_message(msg) + if isinstance(c_msg, MAModuleResponse): + if c_msg.module_id is None or c_msg.class_name is None: + raise RuntimeError("Failed to receive message") + + self._gateway_modules[c_msg.module_id].set_gateway_class_name(c_msg.class_name) + self._gateway_modules[c_msg.module_id].set_is_initialized(True) + if isinstance(c_msg, MAExecResponse): + if c_msg.module_id is None: + raise RuntimeError("Failed to receive message") + + self._gateway_modules[c_msg.module_id].set_execute_response(c_msg.result) + self._gateway_modules[c_msg.module_id].set_is_executed(True) + + if msg.urn == CommandURN.AK_SPEAK: + if self.send_msg is None: + raise RuntimeError("send_msg is None") + self.send_msg(msg) diff --git a/src/adf_core_python/core/gateway/gateway_launcher.py b/src/adf_core_python/core/gateway/gateway_launcher.py index e320d24..47053c6 100644 --- a/src/adf_core_python/core/gateway/gateway_launcher.py +++ b/src/adf_core_python/core/gateway/gateway_launcher.py @@ -7,39 +7,39 @@ class GatewayLauncher: - def __init__(self, host: str, port: int, logger: BoundLogger) -> None: - self.host = host - self.port = port - self.logger = logger - pass - - def make_connection(self) -> Connection: - return Connection(self.host, self.port) - - def connect(self, gateway_agent: GatewayAgent) -> None: + def __init__(self, host: str, port: int, logger: BoundLogger) -> None: + self.host = host + self.port = port + self.logger = logger + pass + + def make_connection(self) -> Connection: + return Connection(self.host, self.port) + + def connect(self, gateway_agent: GatewayAgent) -> None: + self.logger.info( + f"{gateway_agent.__class__.__name__} connecting to {self.host}:{self.port}" + ) + connection = self.make_connection() + try: + connection.connect() + # ソケットが使用しているPORT番号を取得 + if connection.socket is not None: self.logger.info( - f"{gateway_agent.__class__.__name__} connecting to {self.host}:{self.port}" + f"Connected to {self.host}:{self.port} on port {connection.socket.getsockname()[1]}" ) - connection = self.make_connection() - try: - connection.connect() - # ソケットが使用しているPORT番号を取得 - if connection.socket is not None: - self.logger.info( - f"Connected to {self.host}:{self.port} on port {connection.socket.getsockname()[1]}" - ) - except socket.timeout: - self.logger.warning(f"Connection to {self.host}:{self.port} timed out") - return - except socket.error as e: - self.logger.error(f"Failed to connect to {self.host}:{self.port}") - self.logger.error(e) - return - - connection.message_received(gateway_agent.message_received) - gateway_agent.set_send_msg(connection.send_msg) - - try: - connection.parse_message_from_kernel() - except Exception as e: - self.logger.error(f"Failed to connect agent: {self.host}:{self.port} {e}") + except socket.timeout: + self.logger.warning(f"Connection to {self.host}:{self.port} timed out") + return + except socket.error as e: + self.logger.error(f"Failed to connect to {self.host}:{self.port}") + self.logger.error(e) + return + + connection.message_received(gateway_agent.message_received) + gateway_agent.set_send_msg(connection.send_msg) + + try: + connection.parse_message_from_kernel() + except Exception as e: + self.logger.error(f"Failed to connect agent: {self.host}:{self.port} {e}") diff --git a/src/adf_core_python/core/gateway/gateway_module.py b/src/adf_core_python/core/gateway/gateway_module.py index 68bb446..a5fa18d 100644 --- a/src/adf_core_python/core/gateway/gateway_module.py +++ b/src/adf_core_python/core/gateway/gateway_module.py @@ -9,77 +9,75 @@ class GatewayModule: - def __init__(self, gateway_agent: GatewayAgent): - self._gateway_agent = gateway_agent - self._module_id: str = str(uuid.uuid4()) - self._is_initialized = False - self._is_executed = False - self._gateway_class_name: str = "" - self._result: Optional[Config] = None - self._gateway_agent.add_gateway_module(self) - - def get_module_id(self) -> str: - return self._module_id - - def get_gateway_class_name(self) -> str: - return self._gateway_class_name - - def set_gateway_class_name(self, gateway_class_name: str) -> None: - self._gateway_class_name = gateway_class_name - - def get_is_initialized(self) -> bool: - return self._is_initialized - - def set_is_initialized(self, is_initialized: bool) -> None: - self._is_initialized = is_initialized - - def initialize(self, module_name: str, default_class_name: str) -> str: - if not self._gateway_agent.is_initialized(): - self._gateway_agent.initialize() - if self._gateway_agent.send_msg is None: - raise RuntimeError("send_msg is None") - - am_module = AMModule() - self._gateway_agent.send_msg( - am_module.write( - self._module_id, - module_name, - default_class_name, - ) - ) - - while not self.get_is_initialized(): - pass - - return self.get_gateway_class_name() - - def get_execute_response(self) -> Config: - if self._result is None: - raise RuntimeError("No execution result available") - return self._result - - def set_execute_response(self, result: Config) -> None: - self._result = result - - def get_is_executed(self) -> bool: - return self._is_executed - - def set_is_executed(self, _is_executed: bool) -> None: - self._is_executed = _is_executed - - def execute( - self, method_name: str, args: Optional[dict[str, str]] = None - ) -> Config: - if args is None: - args = {} - if self._gateway_agent.send_msg is None: - raise RuntimeError("send_msg is None") - - am_exec = AMExec() - self._gateway_agent.send_msg(am_exec.write(self._module_id, method_name, args)) - - while not self.get_is_executed(): - pass - - self.set_is_executed(False) - return self.get_execute_response() + def __init__(self, gateway_agent: GatewayAgent): + self._gateway_agent = gateway_agent + self._module_id: str = str(uuid.uuid4()) + self._is_initialized = False + self._is_executed = False + self._gateway_class_name: str = "" + self._result: Optional[Config] = None + self._gateway_agent.add_gateway_module(self) + + def get_module_id(self) -> str: + return self._module_id + + def get_gateway_class_name(self) -> str: + return self._gateway_class_name + + def set_gateway_class_name(self, gateway_class_name: str) -> None: + self._gateway_class_name = gateway_class_name + + def get_is_initialized(self) -> bool: + return self._is_initialized + + def set_is_initialized(self, is_initialized: bool) -> None: + self._is_initialized = is_initialized + + def initialize(self, module_name: str, default_class_name: str) -> str: + if not self._gateway_agent.is_initialized(): + self._gateway_agent.initialize() + if self._gateway_agent.send_msg is None: + raise RuntimeError("send_msg is None") + + am_module = AMModule() + self._gateway_agent.send_msg( + am_module.write( + self._module_id, + module_name, + default_class_name, + ) + ) + + while not self.get_is_initialized(): + pass + + return self.get_gateway_class_name() + + def get_execute_response(self) -> Config: + if self._result is None: + raise RuntimeError("No execution result available") + return self._result + + def set_execute_response(self, result: Config) -> None: + self._result = result + + def get_is_executed(self) -> bool: + return self._is_executed + + def set_is_executed(self, _is_executed: bool) -> None: + self._is_executed = _is_executed + + def execute(self, method_name: str, args: Optional[dict[str, str]] = None) -> Config: + if args is None: + args = {} + if self._gateway_agent.send_msg is None: + raise RuntimeError("send_msg is None") + + am_exec = AMExec() + self._gateway_agent.send_msg(am_exec.write(self._module_id, method_name, args)) + + while not self.get_is_executed(): + pass + + self.set_is_executed(False) + return self.get_execute_response() diff --git a/src/adf_core_python/core/gateway/message/am_agent.py b/src/adf_core_python/core/gateway/message/am_agent.py index aac4850..416f702 100644 --- a/src/adf_core_python/core/gateway/message/am_agent.py +++ b/src/adf_core_python/core/gateway/message/am_agent.py @@ -7,48 +7,46 @@ from rcrscore.urn.control_message import ControlMessageURN from adf_core_python.core.gateway.message.urn.urn import ( - ComponentModuleMSG, - ModuleMSG, + ComponentModuleMSG, + ModuleMSG, ) class AMAgent(AKControlMessage): - @staticmethod - def write( - agent_id: EntityID, - entities: list[Entity], - config: dict[str, Any], - mode: int, - ) -> RCRSProto_pb2.MessageProto: - entity_proto_list = [] - for entity in entities: - entity_proto = RCRSProto_pb2.EntityProto() - entity_proto.urn = entity.get_urn() - entity_proto.entityID = entity.get_entity_id().get_value() - - property_proto_list = [] - for k, v in entity.get_properties().items(): - property_proto_list.append(v.to_property_proto()) - entity_proto.properties.extend(property_proto_list) - entity_proto_list.append(entity_proto) - - entity_list_proto = RCRSProto_pb2.EntityListProto() - entity_list_proto.entities.extend(entity_proto_list) - - config_proto = RCRSProto_pb2.ConfigProto() - for key, value in config.items(): - config_proto.data[str(key)] = str(value) - - msg = RCRSProto_pb2.MessageProto() - msg.urn = AMAgent.get_urn() - msg.components[ComponentModuleMSG.AgentID].entityID = agent_id.get_value() - msg.components[ComponentModuleMSG.Entities].entityList.CopyFrom( - entity_list_proto - ) - msg.components[ComponentModuleMSG.Config].config.CopyFrom(config_proto) - msg.components[ComponentModuleMSG.Mode].intValue = mode - return msg - - @staticmethod - def get_urn() -> ControlMessageURN: - return ModuleMSG.AM_AGENT # type: ignore + @staticmethod + def write( + agent_id: EntityID, + entities: list[Entity], + config: dict[str, Any], + mode: int, + ) -> RCRSProto_pb2.MessageProto: + entity_proto_list = [] + for entity in entities: + entity_proto = RCRSProto_pb2.EntityProto() + entity_proto.urn = entity.get_urn() + entity_proto.entityID = entity.get_entity_id().get_value() + + property_proto_list = [] + for k, v in entity.get_properties().items(): + property_proto_list.append(v.to_property_proto()) + entity_proto.properties.extend(property_proto_list) + entity_proto_list.append(entity_proto) + + entity_list_proto = RCRSProto_pb2.EntityListProto() + entity_list_proto.entities.extend(entity_proto_list) + + config_proto = RCRSProto_pb2.ConfigProto() + for key, value in config.items(): + config_proto.data[str(key)] = str(value) + + msg = RCRSProto_pb2.MessageProto() + msg.urn = AMAgent.get_urn() + msg.components[ComponentModuleMSG.AgentID].entityID = agent_id.get_value() + msg.components[ComponentModuleMSG.Entities].entityList.CopyFrom(entity_list_proto) + msg.components[ComponentModuleMSG.Config].config.CopyFrom(config_proto) + msg.components[ComponentModuleMSG.Mode].intValue = mode + return msg + + @staticmethod + def get_urn() -> ControlMessageURN: + return ModuleMSG.AM_AGENT # type: ignore diff --git a/src/adf_core_python/core/gateway/message/am_exec.py b/src/adf_core_python/core/gateway/message/am_exec.py index dd8508b..51f61d0 100644 --- a/src/adf_core_python/core/gateway/message/am_exec.py +++ b/src/adf_core_python/core/gateway/message/am_exec.py @@ -5,25 +5,25 @@ from rcrscore.urn.control_message import ControlMessageURN from adf_core_python.core.gateway.message.urn.urn import ( - ComponentModuleMSG, - ModuleMSG, + ComponentModuleMSG, + ModuleMSG, ) class AMExec(AKControlMessage): - @staticmethod - def write(module_id: str, method_name: str, arguments: dict[str, str]) -> Any: - msg = RCRSProto_pb2.MessageProto() - msg.urn = AMExec.get_urn() - msg.components[ComponentModuleMSG.ModuleID].stringValue = module_id - msg.components[ComponentModuleMSG.MethodName].stringValue = method_name - config_proto = RCRSProto_pb2.ConfigProto() - for key, value in arguments.items(): - config_proto.data[key] = value - msg.components[ComponentModuleMSG.Arguments].config.CopyFrom(config_proto) + @staticmethod + def write(module_id: str, method_name: str, arguments: dict[str, str]) -> Any: + msg = RCRSProto_pb2.MessageProto() + msg.urn = AMExec.get_urn() + msg.components[ComponentModuleMSG.ModuleID].stringValue = module_id + msg.components[ComponentModuleMSG.MethodName].stringValue = method_name + config_proto = RCRSProto_pb2.ConfigProto() + for key, value in arguments.items(): + config_proto.data[key] = value + msg.components[ComponentModuleMSG.Arguments].config.CopyFrom(config_proto) - return msg + return msg - @staticmethod - def get_urn() -> ControlMessageURN: - return ModuleMSG.AM_EXEC # type: ignore + @staticmethod + def get_urn() -> ControlMessageURN: + return ModuleMSG.AM_EXEC # type: ignore diff --git a/src/adf_core_python/core/gateway/message/am_module.py b/src/adf_core_python/core/gateway/message/am_module.py index 6b31662..9b2208f 100644 --- a/src/adf_core_python/core/gateway/message/am_module.py +++ b/src/adf_core_python/core/gateway/message/am_module.py @@ -5,28 +5,26 @@ from rcrscore.urn.control_message import ControlMessageURN from adf_core_python.core.gateway.message.urn.urn import ( - ComponentModuleMSG, - ModuleMSG, + ComponentModuleMSG, + ModuleMSG, ) class AMModule(AKControlMessage): - @staticmethod - def write( - module_id: str, - module_name: str, - default_class_name: str, - ) -> Any: - msg = RCRSProto_pb2.MessageProto() - msg.urn = AMModule.get_urn() - msg.components[ComponentModuleMSG.ModuleID].stringValue = module_id - msg.components[ComponentModuleMSG.ModuleName].stringValue = module_name - msg.components[ - ComponentModuleMSG.DefaultClassName - ].stringValue = default_class_name + @staticmethod + def write( + module_id: str, + module_name: str, + default_class_name: str, + ) -> Any: + msg = RCRSProto_pb2.MessageProto() + msg.urn = AMModule.get_urn() + msg.components[ComponentModuleMSG.ModuleID].stringValue = module_id + msg.components[ComponentModuleMSG.ModuleName].stringValue = module_name + msg.components[ComponentModuleMSG.DefaultClassName].stringValue = default_class_name - return msg + return msg - @staticmethod - def get_urn() -> ControlMessageURN: - return ModuleMSG.AM_MODULE # type: ignore + @staticmethod + def get_urn() -> ControlMessageURN: + return ModuleMSG.AM_MODULE # type: ignore diff --git a/src/adf_core_python/core/gateway/message/am_update.py b/src/adf_core_python/core/gateway/message/am_update.py index e6baa79..c5bf941 100644 --- a/src/adf_core_python/core/gateway/message/am_update.py +++ b/src/adf_core_python/core/gateway/message/am_update.py @@ -7,31 +7,29 @@ from rcrscore.worldmodel import ChangeSet from adf_core_python.core.gateway.message.urn.urn import ( - ComponentModuleMSG, - ModuleMSG, + ComponentModuleMSG, + ModuleMSG, ) class AMUpdate(AKControlMessage): - @staticmethod - def write(time: int, changed: ChangeSet, heard: list[Command]) -> Any: - msg = RCRSProto_pb2.MessageProto() - msg.urn = AMUpdate.get_urn() - msg.components[ComponentModuleMSG.Time].intValue = time - msg.components[ComponentModuleMSG.Changed].changeSet.CopyFrom( - changed.to_change_set_proto() - ) - message_list_proto = RCRSProto_pb2.MessageListProto() - message_proto_list = [] - if heard is not None: - for h in heard: - message_proto_list.append(h.to_message_proto()) - message_list_proto.commands.extend(message_proto_list) - msg.components[ComponentModuleMSG.Heard].commandList.CopyFrom( - message_list_proto - ) - return msg + @staticmethod + def write(time: int, changed: ChangeSet, heard: list[Command]) -> Any: + msg = RCRSProto_pb2.MessageProto() + msg.urn = AMUpdate.get_urn() + msg.components[ComponentModuleMSG.Time].intValue = time + msg.components[ComponentModuleMSG.Changed].changeSet.CopyFrom( + changed.to_change_set_proto() + ) + message_list_proto = RCRSProto_pb2.MessageListProto() + message_proto_list = [] + if heard is not None: + for h in heard: + message_proto_list.append(h.to_message_proto()) + message_list_proto.commands.extend(message_proto_list) + msg.components[ComponentModuleMSG.Heard].commandList.CopyFrom(message_list_proto) + return msg - @staticmethod - def get_urn() -> ControlMessageURN: - return ModuleMSG.AM_UPDATE # type: ignore + @staticmethod + def get_urn() -> ControlMessageURN: + return ModuleMSG.AM_UPDATE # type: ignore diff --git a/src/adf_core_python/core/gateway/message/ma_exec_response.py b/src/adf_core_python/core/gateway/message/ma_exec_response.py index 1e84021..98a50e2 100644 --- a/src/adf_core_python/core/gateway/message/ma_exec_response.py +++ b/src/adf_core_python/core/gateway/message/ma_exec_response.py @@ -4,24 +4,24 @@ from rcrscore.urn.control_message import ControlMessageURN from adf_core_python.core.gateway.message.urn.urn import ( - ComponentModuleMSG, - ModuleMSG, + ComponentModuleMSG, + ModuleMSG, ) class MAExecResponse(KAControlMessage): - def __init__(self, message_proto: RCRSProto_pb2.MessageProto) -> None: - self.result = Config() - self.read(message_proto) + def __init__(self, message_proto: RCRSProto_pb2.MessageProto) -> None: + self.result = Config() + self.read(message_proto) - def read(self, message_proto: RCRSProto_pb2.MessageProto) -> None: - self.module_id: str = message_proto.components[ - ComponentModuleMSG.ModuleID - ].stringValue - result = message_proto.components[ComponentModuleMSG.Result].config - for key, value in result.data.items(): - self.result.set_value(key, value) + def read(self, message_proto: RCRSProto_pb2.MessageProto) -> None: + self.module_id: str = message_proto.components[ + ComponentModuleMSG.ModuleID + ].stringValue + result = message_proto.components[ComponentModuleMSG.Result].config + for key, value in result.data.items(): + self.result.set_value(key, value) - @staticmethod - def get_urn() -> ControlMessageURN: - return ModuleMSG.MA_EXEC_RESPONSE # type: ignore + @staticmethod + def get_urn() -> ControlMessageURN: + return ModuleMSG.MA_EXEC_RESPONSE # type: ignore diff --git a/src/adf_core_python/core/gateway/message/ma_module_response.py b/src/adf_core_python/core/gateway/message/ma_module_response.py index 8e879f3..ce74646 100644 --- a/src/adf_core_python/core/gateway/message/ma_module_response.py +++ b/src/adf_core_python/core/gateway/message/ma_module_response.py @@ -6,25 +6,21 @@ from rcrscore.urn.control_message import ControlMessageURN from adf_core_python.core.gateway.message.urn.urn import ( - ComponentModuleMSG, - ModuleMSG, + ComponentModuleMSG, + ModuleMSG, ) class MAModuleResponse(KAControlMessage, ABC): - def __init__(self, message_proto: RCRSProto_pb2.MessageProto) -> None: - self.module_id: Optional[str] = None - self.class_name: Optional[str] = None - self.read(message_proto) + def __init__(self, message_proto: RCRSProto_pb2.MessageProto) -> None: + self.module_id: Optional[str] = None + self.class_name: Optional[str] = None + self.read(message_proto) - def read(self, message_proto: RCRSProto_pb2.MessageProto) -> None: - self.module_id = message_proto.components[ - ComponentModuleMSG.ModuleID - ].stringValue - self.class_name = message_proto.components[ - ComponentModuleMSG.ClassName - ].stringValue + def read(self, message_proto: RCRSProto_pb2.MessageProto) -> None: + self.module_id = message_proto.components[ComponentModuleMSG.ModuleID].stringValue + self.class_name = message_proto.components[ComponentModuleMSG.ClassName].stringValue - @staticmethod - def get_urn() -> ControlMessageURN: - return ModuleMSG.MA_MODULE_RESPONSE # type: ignore + @staticmethod + def get_urn() -> ControlMessageURN: + return ModuleMSG.MA_MODULE_RESPONSE # type: ignore diff --git a/src/adf_core_python/core/gateway/message/moduleMessageFactory.py b/src/adf_core_python/core/gateway/message/moduleMessageFactory.py index 3874b25..d5a93ed 100644 --- a/src/adf_core_python/core/gateway/message/moduleMessageFactory.py +++ b/src/adf_core_python/core/gateway/message/moduleMessageFactory.py @@ -4,21 +4,21 @@ from adf_core_python.core.gateway.message.ma_exec_response import MAExecResponse from adf_core_python.core.gateway.message.ma_module_response import ( - MAModuleResponse, + MAModuleResponse, ) from adf_core_python.core.gateway.message.urn.urn import ModuleMSG class ModuleMessageFactory: - def __init__(self) -> None: - pass + def __init__(self) -> None: + pass - def make_message( - self, msg: RCRSProto_pb2.MessageProto - ) -> Optional[MAModuleResponse | MAExecResponse]: - if msg.urn == ModuleMSG.MA_MODULE_RESPONSE: - return MAModuleResponse(msg) - elif msg.urn == ModuleMSG.MA_EXEC_RESPONSE: - return MAExecResponse(msg) + def make_message( + self, msg: RCRSProto_pb2.MessageProto + ) -> Optional[MAModuleResponse | MAExecResponse]: + if msg.urn == ModuleMSG.MA_MODULE_RESPONSE: + return MAModuleResponse(msg) + elif msg.urn == ModuleMSG.MA_EXEC_RESPONSE: + return MAExecResponse(msg) - return None + return None diff --git a/src/adf_core_python/core/gateway/message/urn/urn.py b/src/adf_core_python/core/gateway/message/urn/urn.py index d38e435..36a15bf 100644 --- a/src/adf_core_python/core/gateway/message/urn/urn.py +++ b/src/adf_core_python/core/gateway/message/urn/urn.py @@ -2,26 +2,26 @@ class ModuleMSG(IntEnum): - AM_AGENT = 0x0301 - AM_MODULE = 0x0302 - MA_MODULE_RESPONSE = 0x0303 - AM_UPDATE = 0x0304 - AM_EXEC = 0x0305 - MA_EXEC_RESPONSE = 0x0306 + AM_AGENT = 0x0301 + AM_MODULE = 0x0302 + MA_MODULE_RESPONSE = 0x0303 + AM_UPDATE = 0x0304 + AM_EXEC = 0x0305 + MA_EXEC_RESPONSE = 0x0306 class ComponentModuleMSG(IntEnum): - AgentID = 0x0401 - Entities = 0x0402 - Config = 0x0403 - Mode = 0x0404 - ModuleID = 0x0405 - ModuleName = 0x0406 - DefaultClassName = 0x0407 - ClassName = 0x0408 - Time = 0x0409 - Changed = 0x040A - Heard = 0x040B - MethodName = 0x040C - Arguments = 0x040D - Result = 0x040E + AgentID = 0x0401 + Entities = 0x0402 + Config = 0x0403 + Mode = 0x0404 + ModuleID = 0x0405 + ModuleName = 0x0406 + DefaultClassName = 0x0407 + ClassName = 0x0408 + Time = 0x0409 + Changed = 0x040A + Heard = 0x040B + MethodName = 0x040C + Arguments = 0x040D + Result = 0x040E diff --git a/src/adf_core_python/core/gateway/module_dict.py b/src/adf_core_python/core/gateway/module_dict.py index 7f668c6..e814b77 100644 --- a/src/adf_core_python/core/gateway/module_dict.py +++ b/src/adf_core_python/core/gateway/module_dict.py @@ -2,31 +2,31 @@ class ModuleDict: - def __init__(self, module_dict: Optional[dict[str, str]] = None): - self.module_dict: dict[str, str] = { - "adf_core_python.component.module.algorithm.Clustering": "adf_core_python.core.gateway.component.module.complex.gateway_clustering.GatewayClustering", - "adf_core_python.component.module.algorithm.DynamicClustering": "adf_core_python.core.gateway.component.module.complex.gateway_clustering.GatewayClustering", - "adf_core_python.component.module.algorithm.StaticClustering": "adf_core_python.core.gateway.component.module.complex.gateway_clustering.GatewayClustering", - "adf_core_python.component.module.algorithm.PathPlanning": "adf_core_python.core.gateway.component.module.complex.gateway_path_planning.GatewayPathPlanning", - "adf_core_python.component.module.complex.TargetDetector": "adf_core_python.core.gateway.component.module.complex.gateway_target_detector.GatewayTargetDetector", - "adf_core_python.component.module.complex.HumanDetector": "adf_core_python.core.gateway.component.module.complex.gateway_human_detector.GatewayHumanDetector", - "adf_core_python.component.module.complex.RoadDetector": "adf_core_python.core.gateway.component.module.complex.gateway_road_detector.GatewayRoadDetector", - "adf_core_python.component.module.complex.Search": "adf_core_python.core.gateway.component.module.complex.gateway_search.GatewaySearch", - "adf_core_python.component.module.complex.TargetAllocator": "adf_core_python.core.gateway.component.module.complex.gateway_target_allocator.GatewayTargetAllocator", - "adf_core_python.component.module.complex.AmbulanceTargetAllocator": "adf_core_python.core.gateway.component.module.complex.gateway_ambulance_target_allocator.GatewayAmbulanceTargetAllocator", - "adf_core_python.component.module.complex.FireTargetAllocator": "adf_core_python.core.gateway.component.module.complex.gateway_fire_target_allocator.GatewayFireTargetAllocator", - "adf_core_python.component.module.complex.PoliceTargetAllocator": "adf_core_python.core.gateway.component.module.complex.gateway_fire_target_allocator.GatewayPoliceTargetAllocator", - } - if module_dict is not None: - for key, value in module_dict.items(): - self.module_dict[key] = value + def __init__(self, module_dict: Optional[dict[str, str]] = None): + self.module_dict: dict[str, str] = { + "adf_core_python.component.module.algorithm.Clustering": "adf_core_python.core.gateway.component.module.complex.gateway_clustering.GatewayClustering", + "adf_core_python.component.module.algorithm.DynamicClustering": "adf_core_python.core.gateway.component.module.complex.gateway_clustering.GatewayClustering", + "adf_core_python.component.module.algorithm.StaticClustering": "adf_core_python.core.gateway.component.module.complex.gateway_clustering.GatewayClustering", + "adf_core_python.component.module.algorithm.PathPlanning": "adf_core_python.core.gateway.component.module.complex.gateway_path_planning.GatewayPathPlanning", + "adf_core_python.component.module.complex.TargetDetector": "adf_core_python.core.gateway.component.module.complex.gateway_target_detector.GatewayTargetDetector", + "adf_core_python.component.module.complex.HumanDetector": "adf_core_python.core.gateway.component.module.complex.gateway_human_detector.GatewayHumanDetector", + "adf_core_python.component.module.complex.RoadDetector": "adf_core_python.core.gateway.component.module.complex.gateway_road_detector.GatewayRoadDetector", + "adf_core_python.component.module.complex.Search": "adf_core_python.core.gateway.component.module.complex.gateway_search.GatewaySearch", + "adf_core_python.component.module.complex.TargetAllocator": "adf_core_python.core.gateway.component.module.complex.gateway_target_allocator.GatewayTargetAllocator", + "adf_core_python.component.module.complex.AmbulanceTargetAllocator": "adf_core_python.core.gateway.component.module.complex.gateway_ambulance_target_allocator.GatewayAmbulanceTargetAllocator", + "adf_core_python.component.module.complex.FireTargetAllocator": "adf_core_python.core.gateway.component.module.complex.gateway_fire_target_allocator.GatewayFireTargetAllocator", + "adf_core_python.component.module.complex.PoliceTargetAllocator": "adf_core_python.core.gateway.component.module.complex.gateway_fire_target_allocator.GatewayPoliceTargetAllocator", + } + if module_dict is not None: + for key, value in module_dict.items(): + self.module_dict[key] = value - def __getitem__(self, key: str) -> Optional[str]: - if not isinstance(key, str): - raise TypeError("TypeError: Key must be a string") - return self.module_dict.get(key) + def __getitem__(self, key: str) -> Optional[str]: + if not isinstance(key, str): + raise TypeError("TypeError: Key must be a string") + return self.module_dict.get(key) - def __setitem__(self, key: str, value: str) -> None: - if not isinstance(key, str): - raise TypeError("TypeError: Key must be a string") - self.module_dict[key] = value + def __setitem__(self, key: str, value: str) -> None: + if not isinstance(key, str): + raise TypeError("TypeError: Key must be a string") + self.module_dict[key] = value diff --git a/src/adf_core_python/core/launcher/agent_launcher.py b/src/adf_core_python/core/launcher/agent_launcher.py index 94fe7a8..a5c119f 100644 --- a/src/adf_core_python/core/launcher/agent_launcher.py +++ b/src/adf_core_python/core/launcher/agent_launcher.py @@ -10,123 +10,117 @@ from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector from adf_core_python.core.launcher.connect.connector_ambulance_center import ( - ConnectorAmbulanceCenter, + ConnectorAmbulanceCenter, ) from adf_core_python.core.launcher.connect.connector_ambulance_team import ( - ConnectorAmbulanceTeam, + ConnectorAmbulanceTeam, ) from adf_core_python.core.launcher.connect.connector_fire_brigade import ( - ConnectorFireBrigade, + ConnectorFireBrigade, ) from adf_core_python.core.launcher.connect.connector_fire_station import ( - ConnectorFireStation, + ConnectorFireStation, ) from adf_core_python.core.launcher.connect.connector_police_force import ( - ConnectorPoliceForce, + ConnectorPoliceForce, ) from adf_core_python.core.launcher.connect.connector_police_office import ( - ConnectorPoliceOffice, + ConnectorPoliceOffice, ) from adf_core_python.core.logger.logger import get_logger class AgentLauncher: - def __init__(self, config: Config): - self.config = config - self.logger = get_logger(__name__) - self.connectors: list[Connector] = [] - self.agent_thread_list: list[threading.Thread] = [] - - def init_connector(self) -> None: - loader_name, loader_class_name = self.config.get_value( - ConfigKey.KEY_LOADER_CLASS, - "adf_core_python.implement.default_loader.DefaultLoader", - ).rsplit(".", 1) - loader_module = importlib.import_module(loader_name) - self.loader: AbstractLoader = getattr( - loader_module, - loader_class_name, - )( - self.config.get_value(ConfigKey.KEY_TEAM_NAME), - ) - - self.connectors.append(ConnectorAmbulanceTeam()) - self.connectors.append(ConnectorAmbulanceCenter()) - self.connectors.append(ConnectorFireBrigade()) - self.connectors.append(ConnectorFireStation()) - self.connectors.append(ConnectorPoliceForce()) - self.connectors.append(ConnectorPoliceOffice()) - - def launch(self) -> None: - kernel_host: str = self.config.get_value(ConfigKey.KEY_KERNEL_HOST, "localhost") - kernel_port: int = self.config.get_value(ConfigKey.KEY_KERNEL_PORT, 27931) - - component_launcher: ComponentLauncher = ComponentLauncher( - kernel_host, kernel_port, self.logger - ) - timeout: int = self.config.get_value( - ConfigKey.KEY_KERNEL_TIMEOUT, - 30, - ) - if component_launcher.check_kernel_connection(timeout=timeout): - self.logger.info( - f"Kernel is running (host: {kernel_host}, port: {kernel_port})" - ) - else: - self.logger.error( - f"Kernel is not running (host: {kernel_host}, port: {kernel_port})" - ) - return - - self.logger.info( - f"Start agent launcher (host: {kernel_host}, port: {kernel_port})" - ) - - gateway_launcher: Optional[GatewayLauncher] = None - gateway_flag: bool = self.config.get_value(ConfigKey.KEY_GATEWAY_FLAG, False) - if gateway_flag: - gateway_host: str = self.config.get_value( - ConfigKey.KEY_GATEWAY_HOST, "localhost" - ) - gateway_port: int = self.config.get_value(ConfigKey.KEY_GATEWAY_PORT, 27941) - self.logger.info( - f"Start gateway launcher (host: {gateway_host}, port: {gateway_port})" - ) - - gateway_launcher = GatewayLauncher(gateway_host, gateway_port, self.logger) - - connector_thread_list: list[threading.Thread] = [] - for connector in self.connectors: - threads = connector.connect( - component_launcher, gateway_launcher, self.config, self.loader - ) - self.agent_thread_list.extend(threads) - - def connect() -> None: - for thread, event in threads.items(): - thread.daemon = True - thread.start() - event.wait(5) - - connector_thread = threading.Thread(target=connect) - connector_thread_list.append(connector_thread) - connector_thread.start() - - for thread in connector_thread_list: - thread.join() - - self.logger.info("All agents have been launched") - - for thread in self.agent_thread_list: - thread.join() - - def check_kernel_connection(self, host: str, port: int, timeout: int = 5) -> bool: - try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(timeout) - result = sock.connect_ex((host, port)) - sock.close() - return result == 0 - except Exception as e: - self.logger.error(f"カーネルへの接続確認中にエラーが発生しました: {e}") - return False + def __init__(self, config: Config): + self.config = config + self.logger = get_logger(__name__) + self.connectors: list[Connector] = [] + self.agent_thread_list: list[threading.Thread] = [] + + def init_connector(self) -> None: + loader_name, loader_class_name = self.config.get_value( + ConfigKey.KEY_LOADER_CLASS, + "adf_core_python.implement.default_loader.DefaultLoader", + ).rsplit(".", 1) + loader_module = importlib.import_module(loader_name) + self.loader: AbstractLoader = getattr( + loader_module, + loader_class_name, + )( + self.config.get_value(ConfigKey.KEY_TEAM_NAME), + ) + + self.connectors.append(ConnectorAmbulanceTeam()) + self.connectors.append(ConnectorAmbulanceCenter()) + self.connectors.append(ConnectorFireBrigade()) + self.connectors.append(ConnectorFireStation()) + self.connectors.append(ConnectorPoliceForce()) + self.connectors.append(ConnectorPoliceOffice()) + + def launch(self) -> None: + kernel_host: str = self.config.get_value(ConfigKey.KEY_KERNEL_HOST, "localhost") + kernel_port: int = self.config.get_value(ConfigKey.KEY_KERNEL_PORT, 27931) + + component_launcher: ComponentLauncher = ComponentLauncher( + kernel_host, kernel_port, self.logger + ) + timeout: int = self.config.get_value( + ConfigKey.KEY_KERNEL_TIMEOUT, + 30, + ) + if component_launcher.check_kernel_connection(timeout=timeout): + self.logger.info(f"Kernel is running (host: {kernel_host}, port: {kernel_port})") + else: + self.logger.error( + f"Kernel is not running (host: {kernel_host}, port: {kernel_port})" + ) + return + + self.logger.info(f"Start agent launcher (host: {kernel_host}, port: {kernel_port})") + + gateway_launcher: Optional[GatewayLauncher] = None + gateway_flag: bool = self.config.get_value(ConfigKey.KEY_GATEWAY_FLAG, False) + if gateway_flag: + gateway_host: str = self.config.get_value(ConfigKey.KEY_GATEWAY_HOST, "localhost") + gateway_port: int = self.config.get_value(ConfigKey.KEY_GATEWAY_PORT, 27941) + self.logger.info( + f"Start gateway launcher (host: {gateway_host}, port: {gateway_port})" + ) + + gateway_launcher = GatewayLauncher(gateway_host, gateway_port, self.logger) + + connector_thread_list: list[threading.Thread] = [] + for connector in self.connectors: + threads = connector.connect( + component_launcher, gateway_launcher, self.config, self.loader + ) + self.agent_thread_list.extend(threads) + + def connect() -> None: + for thread, event in threads.items(): + thread.daemon = True + thread.start() + event.wait(5) + + connector_thread = threading.Thread(target=connect) + connector_thread_list.append(connector_thread) + connector_thread.start() + + for thread in connector_thread_list: + thread.join() + + self.logger.info("All agents have been launched") + + for thread in self.agent_thread_list: + thread.join() + + def check_kernel_connection(self, host: str, port: int, timeout: int = 5) -> bool: + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + result = sock.connect_ex((host, port)) + sock.close() + return result == 0 + except Exception as e: + self.logger.error(f"カーネルへの接続確認中にエラーが発生しました: {e}") + return False diff --git a/src/adf_core_python/core/launcher/config_key.py b/src/adf_core_python/core/launcher/config_key.py index 2f7f9a0..90d4f3c 100644 --- a/src/adf_core_python/core/launcher/config_key.py +++ b/src/adf_core_python/core/launcher/config_key.py @@ -2,30 +2,30 @@ class ConfigKey: - # General - KEY_LOADER_CLASS: Final[str] = "adf_core_python.launcher.loader" - KEY_KERNEL_HOST: Final[str] = "kernel.host" - KEY_KERNEL_PORT: Final[str] = "kernel.port" - KEY_KERNEL_TIMEOUT: Final[str] = "kernel.timeout" - KEY_TEAM_NAME: Final[str] = "team.name" - KEY_DEBUG_FLAG: Final[str] = "adf.debug.flag" - KEY_DEVELOP_FLAG: Final[str] = "adf.develop.flag" - KEY_DEVELOP_DATA_FILE_NAME: Final[str] = "adf.develop.filename" - KEY_DEVELOP_DATA: Final[str] = "adf.develop.data" - KEY_MODULE_CONFIG_FILE_NAME: Final[str] = "adf.agent.moduleconfig.filename" - KEY_MODULE_DATA: Final[str] = "adf.agent.moduleconfig.data" - KEY_PRECOMPUTE: Final[str] = "adf.launcher.precompute" - # Platoon - KEY_AMBULANCE_TEAM_COUNT: Final[str] = "adf.team.platoon.ambulance.count" - KEY_FIRE_BRIGADE_COUNT: Final[str] = "adf.team.platoon.fire.count" - KEY_POLICE_FORCE_COUNT: Final[str] = "adf.team.platoon.police.count" - # Office - KEY_AMBULANCE_CENTRE_COUNT: Final[str] = "adf.team.office.ambulance.count" - KEY_FIRE_STATION_COUNT: Final[str] = "adf.team.office.fire.count" - KEY_POLICE_OFFICE_COUNT: Final[str] = "adf.team.office.police.count" - # adf-core-python - KEY_PRECOMPUTE_DATA_DIR: Final[str] = "adf.agent.precompute.dir_name" - # Gateway - KEY_GATEWAY_HOST: Final[str] = "gateway.host" - KEY_GATEWAY_PORT: Final[str] = "gateway.port" - KEY_GATEWAY_FLAG: Final[str] = "adf.gateway.flag" + # General + KEY_LOADER_CLASS: Final[str] = "adf_core_python.launcher.loader" + KEY_KERNEL_HOST: Final[str] = "kernel.host" + KEY_KERNEL_PORT: Final[str] = "kernel.port" + KEY_KERNEL_TIMEOUT: Final[str] = "kernel.timeout" + KEY_TEAM_NAME: Final[str] = "team.name" + KEY_DEBUG_FLAG: Final[str] = "adf.debug.flag" + KEY_DEVELOP_FLAG: Final[str] = "adf.develop.flag" + KEY_DEVELOP_DATA_FILE_NAME: Final[str] = "adf.develop.filename" + KEY_DEVELOP_DATA: Final[str] = "adf.develop.data" + KEY_MODULE_CONFIG_FILE_NAME: Final[str] = "adf.agent.moduleconfig.filename" + KEY_MODULE_DATA: Final[str] = "adf.agent.moduleconfig.data" + KEY_PRECOMPUTE: Final[str] = "adf.launcher.precompute" + # Platoon + KEY_AMBULANCE_TEAM_COUNT: Final[str] = "adf.team.platoon.ambulance.count" + KEY_FIRE_BRIGADE_COUNT: Final[str] = "adf.team.platoon.fire.count" + KEY_POLICE_FORCE_COUNT: Final[str] = "adf.team.platoon.police.count" + # Office + KEY_AMBULANCE_CENTRE_COUNT: Final[str] = "adf.team.office.ambulance.count" + KEY_FIRE_STATION_COUNT: Final[str] = "adf.team.office.fire.count" + KEY_POLICE_OFFICE_COUNT: Final[str] = "adf.team.office.police.count" + # adf-core-python + KEY_PRECOMPUTE_DATA_DIR: Final[str] = "adf.agent.precompute.dir_name" + # Gateway + KEY_GATEWAY_HOST: Final[str] = "gateway.host" + KEY_GATEWAY_PORT: Final[str] = "gateway.port" + KEY_GATEWAY_FLAG: Final[str] = "adf.gateway.flag" diff --git a/src/adf_core_python/core/launcher/connect/component_launcher.py b/src/adf_core_python/core/launcher/connect/component_launcher.py index 9a684fa..4890300 100644 --- a/src/adf_core_python/core/launcher/connect/component_launcher.py +++ b/src/adf_core_python/core/launcher/connect/component_launcher.py @@ -10,92 +10,88 @@ class ComponentLauncher: - def __init__(self, host: str, port: int, logger: BoundLogger) -> None: - self.request_id = 0 - self.port = port - self.host = host - self.logger = logger - - def make_connection(self) -> Connection: - return Connection(self.host, self.port) - - def connect(self, agent: Agent, _request_id: int) -> None: - connection = self.make_connection() - try: - connection.connect() - except socket.timeout: - self.logger.warning(f"Connection to {self.host}:{self.port} timed out") - return - except socket.error as e: - self.logger.exception( - f"Failed to connect to {self.host}:{self.port}", exception=str(e) - ) - return - - connection.message_received(agent.message_received) - agent.set_send_msg(connection.send_msg) - agent.start_up(_request_id) - - try: - connection.parse_message_from_kernel() - except AgentError as e: - self.logger.exception( - f"Agent error: {e}", - exception=str(e), - ) - except ServerError as e: - if isinstance(e.__cause__, EOFError): - self.logger.info( - f"Connection closed by server (request_id={_request_id})" - ) - else: - self.logger.exception("Server error", exception=str(e)) - - def generate_request_id(self) -> int: - self.request_id += 1 - return self.request_id - - def check_kernel_connection( - self, timeout: int = 30, retry_interval: float = 5.0 - ) -> bool: - """Attempts to connect to the kernel multiple times within the specified timeout period. - - Args: - timeout (int): Total timeout duration in seconds - retry_interval (float): Interval between retry attempts in seconds - - Returns: - bool: True if connection successful, False otherwise - """ - start_time = time.time() - attempt = 1 - - while True: - try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(retry_interval) - result = sock.connect_ex((self.host, self.port)) - sock.close() - - if result == 0: - self.logger.info( - f"Successfully connected to kernel (attempt: {attempt})" - ) - return True - - elapsed_time = time.time() - start_time - if elapsed_time >= timeout: - self.logger.error( - f"Timeout: Could not connect to kernel within {timeout} seconds (attempts: {attempt})" - ) - return False - - self.logger.debug( - f"Connection attempt {attempt} failed - retrying in {retry_interval} seconds" - ) - time.sleep(retry_interval) - attempt += 1 - - except Exception as e: - self.logger.error(f"Error while checking kernel connection: {e}") - return False + def __init__(self, host: str, port: int, logger: BoundLogger) -> None: + self.request_id = 0 + self.port = port + self.host = host + self.logger = logger + + def make_connection(self) -> Connection: + return Connection(self.host, self.port) + + def connect(self, agent: Agent, _request_id: int) -> None: + connection = self.make_connection() + try: + connection.connect() + except socket.timeout: + self.logger.warning(f"Connection to {self.host}:{self.port} timed out") + return + except socket.error as e: + self.logger.exception( + f"Failed to connect to {self.host}:{self.port}", exception=str(e) + ) + return + + connection.message_received(agent.message_received) + agent.set_send_msg(connection.send_msg) + agent.start_up(_request_id) + + try: + connection.parse_message_from_kernel() + except AgentError as e: + self.logger.exception( + f"Agent error: {e}", + exception=str(e), + ) + except ServerError as e: + if isinstance(e.__cause__, EOFError): + self.logger.info(f"Connection closed by server (request_id={_request_id})") + else: + self.logger.exception("Server error", exception=str(e)) + + def generate_request_id(self) -> int: + self.request_id += 1 + return self.request_id + + def check_kernel_connection( + self, timeout: int = 30, retry_interval: float = 5.0 + ) -> bool: + """Attempts to connect to the kernel multiple times within the specified timeout period. + + Args: + timeout (int): Total timeout duration in seconds + retry_interval (float): Interval between retry attempts in seconds + + Returns: + bool: True if connection successful, False otherwise + """ + start_time = time.time() + attempt = 1 + + while True: + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(retry_interval) + result = sock.connect_ex((self.host, self.port)) + sock.close() + + if result == 0: + self.logger.info(f"Successfully connected to kernel (attempt: {attempt})") + return True + + elapsed_time = time.time() - start_time + if elapsed_time >= timeout: + self.logger.error( + f"Timeout: Could not connect to kernel within {timeout} seconds (attempts: {attempt})" + ) + return False + + self.logger.debug( + f"Connection attempt {attempt} failed - retrying in {retry_interval} seconds" + ) + time.sleep(retry_interval) + attempt += 1 + + except Exception as e: + self.logger.error(f"Error while checking kernel connection: {e}") + return False diff --git a/src/adf_core_python/core/launcher/connect/connection.py b/src/adf_core_python/core/launcher/connect/connection.py index 2935f0d..660d987 100644 --- a/src/adf_core_python/core/launcher/connect/connection.py +++ b/src/adf_core_python/core/launcher/connect/connection.py @@ -1,59 +1,108 @@ import socket from typing import Any, Callable -import rcrs_core.connection.rcrs_encoding_utils as rcrs_encoding_utils +from rcrscore.proto import RCRSProto_pb2 from adf_core_python.core.launcher.connect.error.agent_error import AgentError from adf_core_python.core.launcher.connect.error.server_error import ServerError class Connection: - def __init__(self, host: str, port: int) -> None: - self.socket: socket.socket - self.agent = None - self.buffer_size: int = 4096 - self.data_buffer: bytes = b"" - self.host = host - self.port = port - - def connect(self) -> None: - """ - Connect to the kernel - - Raises - ------ - socket.timeout - If the connection times out - socket.error - If there is an error connecting to the socket - - """ - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.connect((self.host, self.port)) - - def parse_message_from_kernel(self) -> None: - """ - Parse messages from the kernel - - Raises - ------ - ServerError - If there is an error reading from the socket - AgentError - If there is an error in the agent calculation - """ - while True: - try: - msg = rcrs_encoding_utils.read_msg(self.socket) - except Exception as e: - raise ServerError(f"Error reading from socket: {e}") from e - try: - self.agent_message_received(msg) - except Exception as e: - raise AgentError(f"Error agent calculation: {e}") from e - - def message_received(self, agent_message_received: Callable) -> None: - self.agent_message_received = agent_message_received - - def send_msg(self, msg: Any) -> None: - rcrs_encoding_utils.write_msg(msg, self.socket) + def __init__(self, host: str, port: int) -> None: + self.socket: socket.socket + self.agent = None + self.buffer_size: int = 4096 + self.data_buffer: bytes = b"" + self.host = host + self.port = port + + def connect(self) -> None: + """ + Connect to the kernel + + Raises + ------ + socket.timeout + If the connection times out + socket.error + If there is an error connecting to the socket + + """ + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.connect((self.host, self.port)) + + def parse_message_from_kernel(self) -> None: + """ + Parse messages from the kernel + + Raises + ------ + ServerError + If there is an error reading from the socket + AgentError + If there is an error in the agent calculation + """ + while True: + try: + msg = Connection._read_msg(self.socket) + except Exception as e: + raise ServerError(f"Error reading from socket: {e}") from e + try: + self.agent_message_received(msg) + except Exception as e: + raise AgentError(f"Error agent calculation: {e}") from e + + def message_received(self, agent_message_received: Callable) -> None: + self.agent_message_received = agent_message_received + + def send_msg(self, msg: Any) -> None: + Connection._write_msg(msg, self.socket) + + @staticmethod + def _write_int32(value, sock): + b = [ + ((value >> 24) & 0xFF), + ((value >> 16) & 0xFF), + ((value >> 8) & 0xFF), + (value & 0xFF), + ] + + sock.sendall(bytes(b)) + + @staticmethod + def _readnbytes(sock, n): + buff = b"" + while n > 0: + b = sock.recv(n) + buff += b + if len(b) == 0: + raise EOFError # peer socket has received a SH_WR shutdown + n -= len(b) + return buff + + @staticmethod + def _read_int32(sock): + byte_array = Connection._readnbytes(sock, 4) + value = int( + ((byte_array[0]) << 24) + + ((byte_array[1]) << 16) + + ((byte_array[2]) << 8) + + (byte_array[3]) + ) + return value + + @staticmethod + def _write_msg(msg, sock): + out = msg.SerializeToString() + Connection._write_int32(len(out), sock) + + sock.sendall(out) + + @staticmethod + def _read_msg(sock): + # await reader.read(1) + size = Connection._read_int32(sock) + content = Connection._readnbytes(sock, size) + message = RCRSProto_pb2.MessageProto() + message.ParseFromString(bytes(content)) + return message diff --git a/src/adf_core_python/core/launcher/connect/connector.py b/src/adf_core_python/core/launcher/connect/connector.py index a36a04c..f58ce63 100644 --- a/src/adf_core_python/core/launcher/connect/connector.py +++ b/src/adf_core_python/core/launcher/connect/connector.py @@ -9,18 +9,18 @@ class Connector(ABC): - def __init__(self) -> None: - self.connected_agent_count = 0 + def __init__(self) -> None: + self.connected_agent_count = 0 - @abstractmethod - def connect( - self, - component_launcher: ComponentLauncher, - gateway_launcher: Optional[GatewayLauncher], - config: Config, - loader: AbstractLoader, - ) -> dict[threading.Thread, threading.Event]: - raise NotImplementedError + @abstractmethod + def connect( + self, + component_launcher: ComponentLauncher, + gateway_launcher: Optional[GatewayLauncher], + config: Config, + loader: AbstractLoader, + ) -> dict[threading.Thread, threading.Event]: + raise NotImplementedError - def get_connected_agent_count(self) -> int: - return self.connected_agent_count + def get_connected_agent_count(self) -> int: + return self.connected_agent_count diff --git a/src/adf_core_python/core/launcher/connect/connector_ambulance_center.py b/src/adf_core_python/core/launcher/connect/connector_ambulance_center.py index ac99bf7..86e8fe8 100644 --- a/src/adf_core_python/core/launcher/connect/connector_ambulance_center.py +++ b/src/adf_core_python/core/launcher/connect/connector_ambulance_center.py @@ -6,7 +6,7 @@ from adf_core_python.core.agent.office.office_ambulance import OfficeAmbulance from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.component.tactics.tactics_ambulance_center import ( - TacticsAmbulanceCenter, + TacticsAmbulanceCenter, ) from adf_core_python.core.config.config import Config from adf_core_python.core.gateway.gateway_agent import GatewayAgent @@ -18,79 +18,79 @@ class ConnectorAmbulanceCenter(Connector): - def __init__(self) -> None: - super().__init__() - self.logger = get_logger(__name__) + def __init__(self) -> None: + super().__init__() + self.logger = get_logger(__name__) - def connect( - self, - component_launcher: ComponentLauncher, - gateway_launcher: Optional[GatewayLauncher], - config: Config, - loader: AbstractLoader, - ) -> dict[threading.Thread, threading.Event]: - count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) - if count == 0: - return {} + def connect( + self, + component_launcher: ComponentLauncher, + gateway_launcher: Optional[GatewayLauncher], + config: Config, + loader: AbstractLoader, + ) -> dict[threading.Thread, threading.Event]: + count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) + if count == 0: + return {} - threads: dict[threading.Thread, threading.Event] = {} + threads: dict[threading.Thread, threading.Event] = {} - for _ in range(count): - if loader.get_tactics_ambulance_center() is None: - self.logger.error("Cannot load ambulance centre tactics") + for _ in range(count): + if loader.get_tactics_ambulance_center() is None: + self.logger.error("Cannot load ambulance centre tactics") - tactics_ambulance_center: TacticsAmbulanceCenter = ( - loader.get_tactics_ambulance_center() - ) + tactics_ambulance_center: TacticsAmbulanceCenter = ( + loader.get_tactics_ambulance_center() + ) - module_config: ModuleConfig = ModuleConfig( - config.get_value( - ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, - ModuleConfig.DEFAULT_CONFIG_FILE_NAME, - ) - ) + module_config: ModuleConfig = ModuleConfig( + config.get_value( + ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, + ModuleConfig.DEFAULT_CONFIG_FILE_NAME, + ) + ) - develop_data: DevelopData = DevelopData( - config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - config.get_value( - ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME - ), - ) + develop_data: DevelopData = DevelopData( + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value( + ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME + ), + ) - precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/ambulance_center" + precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/ambulance_center" - finish_post_connect_event = threading.Event() - request_id: int = component_launcher.generate_request_id() + finish_post_connect_event = threading.Event() + request_id: int = component_launcher.generate_request_id() - gateway_agent: Optional[GatewayAgent] = None - if isinstance(gateway_launcher, GatewayLauncher): - gateway_agent = GatewayAgent(gateway_launcher) - if isinstance(gateway_agent, GatewayAgent): - gateway_thread = threading.Thread( - target=gateway_launcher.connect, - args=(gateway_agent,), - ) - gateway_thread.daemon = True - gateway_thread.start() + gateway_agent: Optional[GatewayAgent] = None + if isinstance(gateway_launcher, GatewayLauncher): + gateway_agent = GatewayAgent(gateway_launcher) + if isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + gateway_thread.daemon = True + gateway_thread.start() - component_thread = threading.Thread( - target=component_launcher.connect, - args=( - OfficeAmbulance( - tactics_ambulance_center, - "ambulance_center", - config.get_value(ConfigKey.KEY_PRECOMPUTE, False), - config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - precompute_data_dir, - module_config, - develop_data, - finish_post_connect_event, - gateway_agent, - ), - request_id, - ), - name=f"AmbulanceCenterAgent-{request_id}", - ) - threads[component_thread] = finish_post_connect_event + component_thread = threading.Thread( + target=component_launcher.connect, + args=( + OfficeAmbulance( + tactics_ambulance_center, + "ambulance_center", + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + precompute_data_dir, + module_config, + develop_data, + finish_post_connect_event, + gateway_agent, + ), + request_id, + ), + name=f"AmbulanceCenterAgent-{request_id}", + ) + threads[component_thread] = finish_post_connect_event - return threads + return threads diff --git a/src/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/src/adf_core_python/core/launcher/connect/connector_ambulance_team.py index b354c7c..3645637 100644 --- a/src/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/src/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -6,7 +6,7 @@ from adf_core_python.core.agent.platoon.platoon_ambulance import PlatoonAmbulance from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.component.tactics.tactics_ambulance_team import ( - TacticsAmbulanceTeam, + TacticsAmbulanceTeam, ) from adf_core_python.core.config.config import Config from adf_core_python.core.gateway.gateway_agent import GatewayAgent @@ -18,79 +18,77 @@ class ConnectorAmbulanceTeam(Connector): - def __init__(self) -> None: - super().__init__() - self.logger = get_logger(__name__) + def __init__(self) -> None: + super().__init__() + self.logger = get_logger(__name__) - def connect( - self, - component_launcher: ComponentLauncher, - gateway_launcher: Optional[GatewayLauncher], - config: Config, - loader: AbstractLoader, - ) -> dict[threading.Thread, threading.Event]: - count: int = config.get_value(ConfigKey.KEY_AMBULANCE_TEAM_COUNT, 0) - if count == 0: - return {} + def connect( + self, + component_launcher: ComponentLauncher, + gateway_launcher: Optional[GatewayLauncher], + config: Config, + loader: AbstractLoader, + ) -> dict[threading.Thread, threading.Event]: + count: int = config.get_value(ConfigKey.KEY_AMBULANCE_TEAM_COUNT, 0) + if count == 0: + return {} - threads: dict[threading.Thread, threading.Event] = {} + threads: dict[threading.Thread, threading.Event] = {} - for _ in range(count): - if loader.get_tactics_ambulance_team() is None: - self.logger.error("Cannot load ambulance team tactics") + for _ in range(count): + if loader.get_tactics_ambulance_team() is None: + self.logger.error("Cannot load ambulance team tactics") - tactics_ambulance_team: TacticsAmbulanceTeam = ( - loader.get_tactics_ambulance_team() - ) + tactics_ambulance_team: TacticsAmbulanceTeam = loader.get_tactics_ambulance_team() - module_config: ModuleConfig = ModuleConfig( - config.get_value( - ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, - ModuleConfig.DEFAULT_CONFIG_FILE_NAME, - ) - ) + module_config: ModuleConfig = ModuleConfig( + config.get_value( + ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, + ModuleConfig.DEFAULT_CONFIG_FILE_NAME, + ) + ) - develop_data: DevelopData = DevelopData( - config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - config.get_value( - ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME - ), - ) + develop_data: DevelopData = DevelopData( + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value( + ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME + ), + ) - precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/ambulance_team" + precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/ambulance_team" - finish_post_connect_event = threading.Event() - request_id: int = component_launcher.generate_request_id() + finish_post_connect_event = threading.Event() + request_id: int = component_launcher.generate_request_id() - gateway_agent: Optional[GatewayAgent] = None - if isinstance(gateway_launcher, GatewayLauncher): - gateway_agent = GatewayAgent(gateway_launcher) - if isinstance(gateway_agent, GatewayAgent): - gateway_thread = threading.Thread( - target=gateway_launcher.connect, - args=(gateway_agent,), - ) - gateway_thread.daemon = True - gateway_thread.start() + gateway_agent: Optional[GatewayAgent] = None + if isinstance(gateway_launcher, GatewayLauncher): + gateway_agent = GatewayAgent(gateway_launcher) + if isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + gateway_thread.daemon = True + gateway_thread.start() - component_thread = threading.Thread( - target=component_launcher.connect, - args=( - PlatoonAmbulance( - tactics_ambulance_team, - "ambulance_team", - config.get_value(ConfigKey.KEY_PRECOMPUTE, False), - config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - precompute_data_dir, - module_config, - develop_data, - finish_post_connect_event, - gateway_agent, - ), - request_id, - ), - name=f"AmbulanceTeam-{request_id}", - ) - threads[component_thread] = finish_post_connect_event + component_thread = threading.Thread( + target=component_launcher.connect, + args=( + PlatoonAmbulance( + tactics_ambulance_team, + "ambulance_team", + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + precompute_data_dir, + module_config, + develop_data, + finish_post_connect_event, + gateway_agent, + ), + request_id, + ), + name=f"AmbulanceTeam-{request_id}", + ) + threads[component_thread] = finish_post_connect_event - return threads + return threads diff --git a/src/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/src/adf_core_python/core/launcher/connect/connector_fire_brigade.py index 6e5fda3..8889365 100644 --- a/src/adf_core_python/core/launcher/connect/connector_fire_brigade.py +++ b/src/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -6,7 +6,7 @@ from adf_core_python.core.agent.platoon.platoon_fire import PlatoonFire from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.component.tactics.tactics_fire_brigade import ( - TacticsFireBrigade, + TacticsFireBrigade, ) from adf_core_python.core.config.config import Config from adf_core_python.core.gateway.gateway_agent import GatewayAgent @@ -18,77 +18,77 @@ class ConnectorFireBrigade(Connector): - def __init__(self) -> None: - super().__init__() - self.logger = get_logger(__name__) + def __init__(self) -> None: + super().__init__() + self.logger = get_logger(__name__) - def connect( - self, - component_launcher: ComponentLauncher, - gateway_launcher: Optional[GatewayLauncher], - config: Config, - loader: AbstractLoader, - ) -> dict[threading.Thread, threading.Event]: - count: int = config.get_value(ConfigKey.KEY_FIRE_BRIGADE_COUNT, 0) - if count == 0: - return {} + def connect( + self, + component_launcher: ComponentLauncher, + gateway_launcher: Optional[GatewayLauncher], + config: Config, + loader: AbstractLoader, + ) -> dict[threading.Thread, threading.Event]: + count: int = config.get_value(ConfigKey.KEY_FIRE_BRIGADE_COUNT, 0) + if count == 0: + return {} - threads: dict[threading.Thread, threading.Event] = {} + threads: dict[threading.Thread, threading.Event] = {} - for _ in range(count): - if loader.get_tactics_fire_brigade() is None: - self.logger.error("Cannot load fire brigade tactics") + for _ in range(count): + if loader.get_tactics_fire_brigade() is None: + self.logger.error("Cannot load fire brigade tactics") - tactics_fire_brigade: TacticsFireBrigade = loader.get_tactics_fire_brigade() + tactics_fire_brigade: TacticsFireBrigade = loader.get_tactics_fire_brigade() - module_config: ModuleConfig = ModuleConfig( - config.get_value( - ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, - ModuleConfig.DEFAULT_CONFIG_FILE_NAME, - ) - ) + module_config: ModuleConfig = ModuleConfig( + config.get_value( + ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, + ModuleConfig.DEFAULT_CONFIG_FILE_NAME, + ) + ) - develop_data: DevelopData = DevelopData( - config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - config.get_value( - ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME - ), - ) + develop_data: DevelopData = DevelopData( + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value( + ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME + ), + ) - precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/fire_brigade" + precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/fire_brigade" - finish_post_connect_event = threading.Event() - request_id: int = component_launcher.generate_request_id() + finish_post_connect_event = threading.Event() + request_id: int = component_launcher.generate_request_id() - gateway_agent: Optional[GatewayAgent] = None - if isinstance(gateway_launcher, GatewayLauncher): - gateway_agent = GatewayAgent(gateway_launcher) - if isinstance(gateway_agent, GatewayAgent): - gateway_thread = threading.Thread( - target=gateway_launcher.connect, - args=(gateway_agent,), - ) - gateway_thread.daemon = True - gateway_thread.start() + gateway_agent: Optional[GatewayAgent] = None + if isinstance(gateway_launcher, GatewayLauncher): + gateway_agent = GatewayAgent(gateway_launcher) + if isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + gateway_thread.daemon = True + gateway_thread.start() - component_thread = threading.Thread( - target=component_launcher.connect, - args=( - PlatoonFire( - tactics_fire_brigade, - "fire_brigade", - config.get_value(ConfigKey.KEY_PRECOMPUTE, False), - config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - precompute_data_dir, - module_config, - develop_data, - finish_post_connect_event, - gateway_agent, - ), - request_id, - ), - name=f"FireBrigadeAgent-{request_id}", - ) - threads[component_thread] = finish_post_connect_event + component_thread = threading.Thread( + target=component_launcher.connect, + args=( + PlatoonFire( + tactics_fire_brigade, + "fire_brigade", + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + precompute_data_dir, + module_config, + develop_data, + finish_post_connect_event, + gateway_agent, + ), + request_id, + ), + name=f"FireBrigadeAgent-{request_id}", + ) + threads[component_thread] = finish_post_connect_event - return threads + return threads diff --git a/src/adf_core_python/core/launcher/connect/connector_fire_station.py b/src/adf_core_python/core/launcher/connect/connector_fire_station.py index 2263167..d1bdf7a 100644 --- a/src/adf_core_python/core/launcher/connect/connector_fire_station.py +++ b/src/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -6,7 +6,7 @@ from adf_core_python.core.agent.office.office_fire import OfficeFire from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.component.tactics.tactics_fire_station import ( - TacticsFireStation, + TacticsFireStation, ) from adf_core_python.core.config.config import Config from adf_core_python.core.gateway.gateway_agent import GatewayAgent @@ -18,77 +18,77 @@ class ConnectorFireStation(Connector): - def __init__(self) -> None: - super().__init__() - self.logger = get_logger(__name__) + def __init__(self) -> None: + super().__init__() + self.logger = get_logger(__name__) - def connect( - self, - component_launcher: ComponentLauncher, - gateway_launcher: Optional[GatewayLauncher], - config: Config, - loader: AbstractLoader, - ) -> dict[threading.Thread, threading.Event]: - count: int = config.get_value(ConfigKey.KEY_FIRE_STATION_COUNT, 0) - if count == 0: - return {} + def connect( + self, + component_launcher: ComponentLauncher, + gateway_launcher: Optional[GatewayLauncher], + config: Config, + loader: AbstractLoader, + ) -> dict[threading.Thread, threading.Event]: + count: int = config.get_value(ConfigKey.KEY_FIRE_STATION_COUNT, 0) + if count == 0: + return {} - threads: dict[threading.Thread, threading.Event] = {} + threads: dict[threading.Thread, threading.Event] = {} - for _ in range(count): - if loader.get_tactics_fire_station() is None: - self.logger.error("Cannot load fire station tactics") + for _ in range(count): + if loader.get_tactics_fire_station() is None: + self.logger.error("Cannot load fire station tactics") - tactics_fire_station: TacticsFireStation = loader.get_tactics_fire_station() + tactics_fire_station: TacticsFireStation = loader.get_tactics_fire_station() - module_config: ModuleConfig = ModuleConfig( - config.get_value( - ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, - ModuleConfig.DEFAULT_CONFIG_FILE_NAME, - ) - ) + module_config: ModuleConfig = ModuleConfig( + config.get_value( + ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, + ModuleConfig.DEFAULT_CONFIG_FILE_NAME, + ) + ) - develop_data: DevelopData = DevelopData( - config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - config.get_value( - ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME - ), - ) + develop_data: DevelopData = DevelopData( + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value( + ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME + ), + ) - precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/fire_station" + precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/fire_station" - finish_post_connect_event = threading.Event() - request_id: int = component_launcher.generate_request_id() + finish_post_connect_event = threading.Event() + request_id: int = component_launcher.generate_request_id() - gateway_agent: Optional[GatewayAgent] = None - if isinstance(gateway_launcher, GatewayLauncher): - gateway_agent = GatewayAgent(gateway_launcher) - if isinstance(gateway_agent, GatewayAgent): - gateway_thread = threading.Thread( - target=gateway_launcher.connect, - args=(gateway_agent,), - ) - gateway_thread.daemon = True - gateway_thread.start() + gateway_agent: Optional[GatewayAgent] = None + if isinstance(gateway_launcher, GatewayLauncher): + gateway_agent = GatewayAgent(gateway_launcher) + if isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + gateway_thread.daemon = True + gateway_thread.start() - component_thread = threading.Thread( - target=component_launcher.connect, - args=( - OfficeFire( - tactics_fire_station, - "fire_station", - config.get_value(ConfigKey.KEY_PRECOMPUTE, False), - config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - precompute_data_dir, - module_config, - develop_data, - finish_post_connect_event, - gateway_agent, - ), - request_id, - ), - name=f"FireStationAgent-{request_id}", - ) - threads[component_thread] = finish_post_connect_event + component_thread = threading.Thread( + target=component_launcher.connect, + args=( + OfficeFire( + tactics_fire_station, + "fire_station", + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + precompute_data_dir, + module_config, + develop_data, + finish_post_connect_event, + gateway_agent, + ), + request_id, + ), + name=f"FireStationAgent-{request_id}", + ) + threads[component_thread] = finish_post_connect_event - return threads + return threads diff --git a/src/adf_core_python/core/launcher/connect/connector_police_force.py b/src/adf_core_python/core/launcher/connect/connector_police_force.py index e2d379b..45acf86 100644 --- a/src/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/src/adf_core_python/core/launcher/connect/connector_police_force.py @@ -6,7 +6,7 @@ from adf_core_python.core.agent.platoon.platoon_police import PlatoonPolice from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.component.tactics.tactics_police_force import ( - TacticsPoliceForce, + TacticsPoliceForce, ) from adf_core_python.core.config.config import Config from adf_core_python.core.gateway.gateway_agent import GatewayAgent @@ -18,77 +18,77 @@ class ConnectorPoliceForce(Connector): - def __init__(self) -> None: - super().__init__() - self.logger = get_logger(__name__) + def __init__(self) -> None: + super().__init__() + self.logger = get_logger(__name__) - def connect( - self, - component_launcher: ComponentLauncher, - gateway_launcher: Optional[GatewayLauncher], - config: Config, - loader: AbstractLoader, - ) -> dict[threading.Thread, threading.Event]: - count: int = config.get_value(ConfigKey.KEY_POLICE_FORCE_COUNT, 0) - if count == 0: - return {} + def connect( + self, + component_launcher: ComponentLauncher, + gateway_launcher: Optional[GatewayLauncher], + config: Config, + loader: AbstractLoader, + ) -> dict[threading.Thread, threading.Event]: + count: int = config.get_value(ConfigKey.KEY_POLICE_FORCE_COUNT, 0) + if count == 0: + return {} - threads: dict[threading.Thread, threading.Event] = {} + threads: dict[threading.Thread, threading.Event] = {} - for _ in range(count): - if loader.get_tactics_police_force() is None: - self.logger.error("Cannot load police force tactics") + for _ in range(count): + if loader.get_tactics_police_force() is None: + self.logger.error("Cannot load police force tactics") - tactics_police_force: TacticsPoliceForce = loader.get_tactics_police_force() + tactics_police_force: TacticsPoliceForce = loader.get_tactics_police_force() - module_config: ModuleConfig = ModuleConfig( - config.get_value( - ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, - ModuleConfig.DEFAULT_CONFIG_FILE_NAME, - ) - ) + module_config: ModuleConfig = ModuleConfig( + config.get_value( + ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, + ModuleConfig.DEFAULT_CONFIG_FILE_NAME, + ) + ) - develop_data: DevelopData = DevelopData( - config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - config.get_value( - ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME - ), - ) + develop_data: DevelopData = DevelopData( + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value( + ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME + ), + ) - precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/police_force" + precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/police_force" - finish_post_connect_event = threading.Event() - request_id: int = component_launcher.generate_request_id() + finish_post_connect_event = threading.Event() + request_id: int = component_launcher.generate_request_id() - gateway_agent: Optional[GatewayAgent] = None - if isinstance(gateway_launcher, GatewayLauncher): - gateway_agent = GatewayAgent(gateway_launcher) - if isinstance(gateway_agent, GatewayAgent): - gateway_thread = threading.Thread( - target=gateway_launcher.connect, - args=(gateway_agent,), - ) - gateway_thread.daemon = True - gateway_thread.start() + gateway_agent: Optional[GatewayAgent] = None + if isinstance(gateway_launcher, GatewayLauncher): + gateway_agent = GatewayAgent(gateway_launcher) + if isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + gateway_thread.daemon = True + gateway_thread.start() - component_thread = threading.Thread( - target=component_launcher.connect, - args=( - PlatoonPolice( - tactics_police_force, - "police_force", - config.get_value(ConfigKey.KEY_PRECOMPUTE, False), - config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - precompute_data_dir, - module_config, - develop_data, - finish_post_connect_event, - gateway_agent, - ), - request_id, - ), - name=f"PoliceForceAgent-{request_id}", - ) - threads[component_thread] = finish_post_connect_event + component_thread = threading.Thread( + target=component_launcher.connect, + args=( + PlatoonPolice( + tactics_police_force, + "police_force", + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + precompute_data_dir, + module_config, + develop_data, + finish_post_connect_event, + gateway_agent, + ), + request_id, + ), + name=f"PoliceForceAgent-{request_id}", + ) + threads[component_thread] = finish_post_connect_event - return threads + return threads diff --git a/src/adf_core_python/core/launcher/connect/connector_police_office.py b/src/adf_core_python/core/launcher/connect/connector_police_office.py index 1d26a92..97d75f9 100644 --- a/src/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/src/adf_core_python/core/launcher/connect/connector_police_office.py @@ -6,7 +6,7 @@ from adf_core_python.core.agent.office.office_police import OfficePolice from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.component.tactics.tactics_police_office import ( - TacticsPoliceOffice, + TacticsPoliceOffice, ) from adf_core_python.core.config.config import Config from adf_core_python.core.gateway.gateway_agent import GatewayAgent @@ -18,79 +18,77 @@ class ConnectorPoliceOffice(Connector): - def __init__(self) -> None: - super().__init__() - self.logger = get_logger(__name__) + def __init__(self) -> None: + super().__init__() + self.logger = get_logger(__name__) - def connect( - self, - component_launcher: ComponentLauncher, - gateway_launcher: Optional[GatewayLauncher], - config: Config, - loader: AbstractLoader, - ) -> dict[threading.Thread, threading.Event]: - count: int = config.get_value(ConfigKey.KEY_POLICE_OFFICE_COUNT, 0) - if count == 0: - return {} + def connect( + self, + component_launcher: ComponentLauncher, + gateway_launcher: Optional[GatewayLauncher], + config: Config, + loader: AbstractLoader, + ) -> dict[threading.Thread, threading.Event]: + count: int = config.get_value(ConfigKey.KEY_POLICE_OFFICE_COUNT, 0) + if count == 0: + return {} - threads: dict[threading.Thread, threading.Event] = {} + threads: dict[threading.Thread, threading.Event] = {} - for _ in range(count): - if loader.get_tactics_police_office() is None: - self.logger.error("Cannot load police office tactics") + for _ in range(count): + if loader.get_tactics_police_office() is None: + self.logger.error("Cannot load police office tactics") - tactics_police_office: TacticsPoliceOffice = ( - loader.get_tactics_police_office() - ) + tactics_police_office: TacticsPoliceOffice = loader.get_tactics_police_office() - module_config: ModuleConfig = ModuleConfig( - config.get_value( - ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, - ModuleConfig.DEFAULT_CONFIG_FILE_NAME, - ) - ) + module_config: ModuleConfig = ModuleConfig( + config.get_value( + ConfigKey.KEY_MODULE_CONFIG_FILE_NAME, + ModuleConfig.DEFAULT_CONFIG_FILE_NAME, + ) + ) - develop_data: DevelopData = DevelopData( - config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - config.get_value( - ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME - ), - ) + develop_data: DevelopData = DevelopData( + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + config.get_value( + ConfigKey.KEY_DEVELOP_DATA_FILE_NAME, DevelopData.DEFAULT_FILE_NAME + ), + ) - precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/police_office" + precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/police_office" - finish_post_connect_event = threading.Event() - request_id: int = component_launcher.generate_request_id() + finish_post_connect_event = threading.Event() + request_id: int = component_launcher.generate_request_id() - gateway_agent: Optional[GatewayAgent] = None - if isinstance(gateway_launcher, GatewayLauncher): - gateway_agent = GatewayAgent(gateway_launcher) - if isinstance(gateway_agent, GatewayAgent): - gateway_thread = threading.Thread( - target=gateway_launcher.connect, - args=(gateway_agent,), - ) - gateway_thread.daemon = True - gateway_thread.start() + gateway_agent: Optional[GatewayAgent] = None + if isinstance(gateway_launcher, GatewayLauncher): + gateway_agent = GatewayAgent(gateway_launcher) + if isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + gateway_thread.daemon = True + gateway_thread.start() - component_thread = threading.Thread( - target=component_launcher.connect, - args=( - OfficePolice( - tactics_police_office, - "police_office", - config.get_value(ConfigKey.KEY_PRECOMPUTE, False), - config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - precompute_data_dir, - module_config, - develop_data, - finish_post_connect_event, - gateway_agent, - ), - request_id, - ), - name=f"PoliceOfficeAgent-{request_id}", - ) - threads[component_thread] = finish_post_connect_event + component_thread = threading.Thread( + target=component_launcher.connect, + args=( + OfficePolice( + tactics_police_office, + "police_office", + config.get_value(ConfigKey.KEY_PRECOMPUTE, False), + config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), + precompute_data_dir, + module_config, + develop_data, + finish_post_connect_event, + gateway_agent, + ), + request_id, + ), + name=f"PoliceOfficeAgent-{request_id}", + ) + threads[component_thread] = finish_post_connect_event - return threads + return threads diff --git a/src/adf_core_python/core/launcher/connect/error/agent_error.py b/src/adf_core_python/core/launcher/connect/error/agent_error.py index 8725dba..7eee9fe 100644 --- a/src/adf_core_python/core/launcher/connect/error/agent_error.py +++ b/src/adf_core_python/core/launcher/connect/error/agent_error.py @@ -1,2 +1,2 @@ class AgentError(Exception): - pass + pass diff --git a/src/adf_core_python/core/launcher/connect/error/server_error.py b/src/adf_core_python/core/launcher/connect/error/server_error.py index 8c3951c..a1d0ea2 100644 --- a/src/adf_core_python/core/launcher/connect/error/server_error.py +++ b/src/adf_core_python/core/launcher/connect/error/server_error.py @@ -1,2 +1,2 @@ class ServerError(Exception): - pass + pass diff --git a/src/adf_core_python/core/logger/logger.py b/src/adf_core_python/core/logger/logger.py index 8b8f2f6..1f9a61d 100644 --- a/src/adf_core_python/core/logger/logger.py +++ b/src/adf_core_python/core/logger/logger.py @@ -11,86 +11,86 @@ def get_logger(name: str) -> structlog.BoundLogger: - """ - Get a logger with the given name. - For kernel logging, use this function to get a logger. + """ + Get a logger with the given name. + For kernel logging, use this function to get a logger. - Parameters - ---------- - name : str - The name of the logger. + Parameters + ---------- + name : str + The name of the logger. - Returns - ------- - structlog.BoundLogger - The logger with the given name. - """ - return structlog.get_logger(name) + Returns + ------- + structlog.BoundLogger + The logger with the given name. + """ + return structlog.get_logger(name) def get_agent_logger(name: str, agent_info: AgentInfo) -> structlog.BoundLogger: - """ - Get a logger with the given name and agent information. - For agent logging, use this function to get a logger. - - Parameters - ---------- - name : str - The name of the logger. - agent_info : AgentInfo - The agent information. - - Returns - ------- - structlog.BoundLogger - The logger with the given name and agent information. - """ - agent = agent_info.get_myself() - if agent is None: - raise ValueError("Agent information is not available") - - return structlog.get_logger(name).bind( - agent_id=str(agent_info.get_entity_id()), - agent_type=str(agent.get_urn().name), - ) + """ + Get a logger with the given name and agent information. + For agent logging, use this function to get a logger. + + Parameters + ---------- + name : str + The name of the logger. + agent_info : AgentInfo + The agent information. + + Returns + ------- + structlog.BoundLogger + The logger with the given name and agent information. + """ + agent = agent_info.get_myself() + if agent is None: + raise ValueError("Agent information is not available") + + return structlog.get_logger(name).bind( + agent_id=str(agent_info.get_entity_id()), + agent_type=str(agent.get_urn().name), + ) def configure_logger() -> None: - # 既存のログファイルが存在する場合、日付付きでバックアップする - log_file = "agent.log" - if os.path.exists(log_file): - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - backup_file = f"agent_{timestamp}.log" - os.rename(log_file, backup_file) - - structlog.configure( - processors=[ - structlog.stdlib.add_log_level, - structlog.stdlib.add_logger_name, - structlog.stdlib.PositionalArgumentsFormatter(), - structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M.%S", utc=False), - structlog.processors.StackInfoRenderer(), - structlog.processors.UnicodeDecoder(), - structlog.stdlib.ProcessorFormatter.wrap_for_formatter, - ], - logger_factory=structlog.stdlib.LoggerFactory(), - wrapper_class=structlog.stdlib.BoundLogger, - cache_logger_on_first_use=True, - ) - - handler_stdout = logging.StreamHandler(sys.stdout) - handler_stdout.setFormatter( - structlog.stdlib.ProcessorFormatter(processor=ConsoleRenderer()) - ) - handler_stdout.setLevel(logging.INFO) - - handler_file = logging.FileHandler(log_file) - handler_file.setFormatter( - structlog.stdlib.ProcessorFormatter(processor=JSONRenderer()) - ) - handler_file.setLevel(logging.DEBUG) - - root_logger = logging.getLogger() - root_logger.addHandler(handler_stdout) - root_logger.addHandler(handler_file) - root_logger.setLevel(logging.DEBUG) + # 既存のログファイルが存在する場合、日付付きでバックアップする + log_file = "agent.log" + if os.path.exists(log_file): + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_file = f"agent_{timestamp}.log" + os.rename(log_file, backup_file) + + structlog.configure( + processors=[ + structlog.stdlib.add_log_level, + structlog.stdlib.add_logger_name, + structlog.stdlib.PositionalArgumentsFormatter(), + structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M.%S", utc=False), + structlog.processors.StackInfoRenderer(), + structlog.processors.UnicodeDecoder(), + structlog.stdlib.ProcessorFormatter.wrap_for_formatter, + ], + logger_factory=structlog.stdlib.LoggerFactory(), + wrapper_class=structlog.stdlib.BoundLogger, + cache_logger_on_first_use=True, + ) + + handler_stdout = logging.StreamHandler(sys.stdout) + handler_stdout.setFormatter( + structlog.stdlib.ProcessorFormatter(processor=ConsoleRenderer()) + ) + handler_stdout.setLevel(logging.INFO) + + handler_file = logging.FileHandler(log_file) + handler_file.setFormatter( + structlog.stdlib.ProcessorFormatter(processor=JSONRenderer()) + ) + handler_file.setLevel(logging.DEBUG) + + root_logger = logging.getLogger() + root_logger.addHandler(handler_stdout) + root_logger.addHandler(handler_file) + root_logger.setLevel(logging.DEBUG) diff --git a/src/adf_core_python/implement/action/default_extend_action_clear.py b/src/adf_core_python/implement/action/default_extend_action_clear.py index 7e09b41..ad837e9 100644 --- a/src/adf_core_python/implement/action/default_extend_action_clear.py +++ b/src/adf_core_python/implement/action/default_extend_action_clear.py @@ -3,16 +3,16 @@ from typing import Optional, cast from rcrscore.entities import ( - AmbulanceTeam, - Area, - Blockade, - Building, - EntityID, - FireBrigade, - Human, - PoliceForce, - Refuge, - Road, + AmbulanceTeam, + Area, + Blockade, + Building, + EntityID, + FireBrigade, + Human, + PoliceForce, + Refuge, + Road, ) from shapely import LineString, Point, Polygon @@ -33,771 +33,720 @@ class DefaultExtendActionClear(ExtendAction): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - self._clear_distance = float( - self.scenario_info.get_value("clear.repair.distance", 0.0) - ) - self._forced_move = float( - develop_data.get_value( - "adf_core_python.implement.action.DefaultExtendActionClear.forced_move", - 3, - ) - ) - self._threshold_rest = float( - develop_data.get_value( - "adf_core_python.implement.action.DefaultExtendActionClear.rest", 100 - ) - ) - - self._target_entity_id: Optional[EntityID] = None - self._move_point_cache: dict[EntityID, Optional[set[tuple[float, float]]]] = {} - self._old_clear_x = 0 - self._old_clear_y = 0 - self.count = 0 - - self._path_planning = cast( - PathPlanning, - self.module_manager.get_module( - "DefaultExtendActionClear.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - - def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: - super().precompute(precompute_data) - if self.get_count_precompute() >= 2: - return self - self._path_planning.precompute(precompute_data) - self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) - return self - - def resume(self, precompute_data: PrecomputeData) -> ExtendAction: - super().resume(precompute_data) - if self.get_count_resume() >= 2: - return self - self._path_planning.resume(precompute_data) - self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._clear_distance = float( + self.scenario_info.get_value("clear.repair.distance", 0.0) + ) + self._forced_move = float( + develop_data.get_value( + "adf_core_python.implement.action.DefaultExtendActionClear.forced_move", + 3, + ) + ) + self._threshold_rest = float( + develop_data.get_value( + "adf_core_python.implement.action.DefaultExtendActionClear.rest", 100 + ) + ) + + self._target_entity_id: Optional[EntityID] = None + self._move_point_cache: dict[EntityID, Optional[set[tuple[float, float]]]] = {} + self._old_clear_x = 0 + self._old_clear_y = 0 + self.count = 0 + + self._path_planning = cast( + PathPlanning, + self.module_manager.get_module( + "DefaultExtendActionClear.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + + def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: + super().precompute(precompute_data) + if self.get_count_precompute() >= 2: + return self + self._path_planning.precompute(precompute_data) + self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + return self + + def resume(self, precompute_data: PrecomputeData) -> ExtendAction: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + self._path_planning.resume(precompute_data) + self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + return self + + def prepare(self) -> ExtendAction: + super().prepare() + if self.get_count_prepare() >= 2: + return self + self._path_planning.prepare() + self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + return self + + def update_info(self, message_manager: MessageManager) -> ExtendAction: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self + self._path_planning.update_info(message_manager) + return self + + def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: + self._target_entity_id = None + target_entity = self.world_info.get_entity(target_entity_id) + if target_entity is not None: + if isinstance(target_entity, Road): + self._target_entity_id = target_entity_id + elif isinstance(target_entity, Blockade): + self._target_entity_id = target_entity.get_position() + elif isinstance(target_entity, Building): + self._target_entity_id = target_entity_id + return self + + def calculate(self) -> ExtendAction: + self.result = None + police_force = cast(PoliceForce, self.agent_info.get_myself()) + + if self._need_rest(police_force): + target_entity_ids: list[EntityID] = [] + if self._target_entity_id is not None: + target_entity_ids.append(self._target_entity_id) + + self.result = self._calc_rest( + police_force, self._path_planning, target_entity_ids + ) + if self.result is not None: return self - def prepare(self) -> ExtendAction: - super().prepare() - if self.get_count_prepare() >= 2: - return self - self._path_planning.prepare() - self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + if self._target_entity_id is None: + return self + + agent_position_entity_id = police_force.get_position() + if agent_position_entity_id is None: + return self + target_entity = self.world_info.get_entity(self._target_entity_id) + position_entity = self.world_info.get_entity(agent_position_entity_id) + if target_entity is None or isinstance(target_entity, Area) is False: + return self + if isinstance(position_entity, Road): + self.result = self._get_rescue_action(police_force, position_entity) + if self.result is not None: return self - def update_info(self, message_manager: MessageManager) -> ExtendAction: - super().update_info(message_manager) - if self.get_count_update_info() >= 2: - return self - self._path_planning.update_info(message_manager) + if agent_position_entity_id == self._target_entity_id: + self.result = self._get_area_clear_action( + police_force, cast(Road, position_entity) + ) + if self.result is not None: return self + elif cast(Area, target_entity).get_edge_to(agent_position_entity_id) is not None: + self.result = self._get_neighbour_position_action( + police_force, cast(Area, target_entity) + ) + else: + path = self._path_planning.get_path( + agent_position_entity_id, self._target_entity_id + ) + if path is not None and len(path) > 0: + index = self._index_of(path, agent_position_entity_id) + if index == -1: + area = cast(Area, position_entity) + for i in range(0, len(path), 1): + if area.get_edge_to(path[i]) is not None: + index = i + break + + elif index >= 0: + index += 1 + + if index >= 0 and index < len(path): + entity = self.world_info.get_entity(path[index]) + self.result = self._get_neighbour_position_action( + police_force, cast(Area, entity) + ) + if self.result is not None and isinstance(self.result, ActionMove): + action_move = self.result + if action_move.is_destination_defined(): + self.result = None + + if self.result is None: + self.result = ActionMove(path) + + return self + + def _need_rest(self, police_force: PoliceForce) -> bool: + hp = police_force.get_hp() + damage = police_force.get_damage() + + if hp is None or damage is None or hp == 0 or damage == 0: + return False + + active_time = (hp / damage) + (1 if (hp % damage) != 0 else 0) + if self._kernel_time == -1: + self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + + return damage is not None and ( + damage >= self._threshold_rest + or (active_time + self.agent_info.get_time() < self._kernel_time) + ) + + def _calc_rest( + self, + police_force: PoliceForce, + path_planning: PathPlanning, + target_entity_ids: list[EntityID], + ) -> Optional[Action]: + position_entity_id = police_force.get_position() + if position_entity_id is None: + return None + refuges = self.world_info.get_entity_ids_of_types([Refuge]) + current_size = len(refuges) + if position_entity_id in refuges: + return ActionRest() + + first_result: list[EntityID] = [] + while len(refuges) > 0: + path = path_planning.get_path(position_entity_id, refuges[0]) + if path is not None and len(path) > 0: + if first_result == []: + first_result = path.copy() + if target_entity_ids == []: + break + + refuge_entity_id = path[-1] + from_refuge_to_target_path = path_planning.get_path( + refuge_entity_id, target_entity_ids[0] + ) + if from_refuge_to_target_path != []: + return ActionMove(path) - def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: - self._target_entity_id = None - target_entity = self.world_info.get_entity(target_entity_id) - if target_entity is not None: - if isinstance(target_entity, Road): - self._target_entity_id = target_entity_id - elif isinstance(target_entity, Blockade): - self._target_entity_id = target_entity.get_position() - elif isinstance(target_entity, Building): - self._target_entity_id = target_entity_id - return self - - def calculate(self) -> ExtendAction: - self.result = None - police_force = cast(PoliceForce, self.agent_info.get_myself()) - - if self._need_rest(police_force): - target_entity_ids: list[EntityID] = [] - if self._target_entity_id is not None: - target_entity_ids.append(self._target_entity_id) - - self.result = self._calc_rest( - police_force, self._path_planning, target_entity_ids - ) - if self.result is not None: - return self - - if self._target_entity_id is None: - return self - - agent_position_entity_id = police_force.get_position() - if agent_position_entity_id is None: - return self - target_entity = self.world_info.get_entity(self._target_entity_id) - position_entity = self.world_info.get_entity(agent_position_entity_id) - if target_entity is None or isinstance(target_entity, Area) is False: - return self - if isinstance(position_entity, Road): - self.result = self._get_rescue_action(police_force, position_entity) - if self.result is not None: - return self - - if agent_position_entity_id == self._target_entity_id: - self.result = self._get_area_clear_action( - police_force, cast(Road, position_entity) - ) - if self.result is not None: - return self - elif ( - cast(Area, target_entity).get_edge_to(agent_position_entity_id) is not None + refuges.remove(refuge_entity_id) + if current_size == len(refuges): + break + current_size = len(refuges) + else: + break + + return ActionMove(first_result) if first_result != [] else None + + def _get_rescue_action( + self, police_entity: PoliceForce, road: Road + ) -> Optional[Action]: + road_blockades = road.get_blockades() + blockades = set( + [] + if road_blockades is None + else [ + cast(Blockade, self.world_info.get_entity(blockade_entity_id)) + for blockade_entity_id in road_blockades + ] + ) + agent_entities = set( + self.world_info.get_entities_of_types([AmbulanceTeam, FireBrigade]) + ) + + police_x = police_entity.get_x() + police_y = police_entity.get_y() + min_distance = sys.float_info.max + move_action: Optional[ActionMove] = None + + for agent_entity in agent_entities: + human = cast(Human, agent_entity) + human_position = human.get_position() + if ( + human_position is None + or human_position.get_value() != road.get_entity_id().get_value() + ): + continue + + human_x = human.get_x() + human_y = human.get_y() + if human_x is None or human_y is None or police_x is None or police_y is None: + continue + + action_clear: Optional[ActionClear | ActionClearArea] = None + clear_blockade: Optional[Blockade] = None + for blockade in blockades: + blockade_apexes = blockade.get_apexes() + if blockade_apexes is None or not self._is_inside( + human_x, human_y, blockade_apexes ): - self.result = self._get_neighbour_position_action( - police_force, cast(Area, target_entity) - ) - else: - path = self._path_planning.get_path( - agent_position_entity_id, self._target_entity_id - ) - if path is not None and len(path) > 0: - index = self._index_of(path, agent_position_entity_id) - if index == -1: - area = cast(Area, position_entity) - for i in range(0, len(path), 1): - if area.get_edge_to(path[i]) is not None: - index = i - break - - elif index >= 0: - index += 1 - - if index >= 0 and index < len(path): - entity = self.world_info.get_entity(path[index]) - self.result = self._get_neighbour_position_action( - police_force, cast(Area, entity) - ) - if self.result is not None and isinstance(self.result, ActionMove): - action_move = self.result - if action_move.is_destination_defined(): - self.result = None - - if self.result is None: - self.result = ActionMove(path) - - return self + continue + + distance = self._get_distance(police_x, police_y, human_x, human_y) + if self._is_intersecting_area(police_x, police_y, human_x, human_y, road): + action = self._get_intersect_edge_action( + police_x, police_y, human_x, human_y, road + ) + if action is None: + continue + if isinstance(action, ActionClear): + if action_clear is None: + action_clear = action + clear_blockade = blockade + continue + + if clear_blockade is not None: + if self._is_intersecting_blockades(blockade, clear_blockade): + return ActionClear(clear_blockade) - def _need_rest(self, police_force: PoliceForce) -> bool: - hp = police_force.get_hp() - damage = police_force.get_damage() + another_distance = self.world_info.get_distance( + police_entity.get_entity_id(), + clear_blockade.get_entity_id(), + ) + blockade_distance = self.world_info.get_distance( + police_entity.get_entity_id(), blockade.get_entity_id() + ) + if blockade_distance < another_distance: + return action + + return action_clear + elif isinstance(action, ActionMove) and distance < min_distance: + min_distance = distance + move_action = action + + elif self._is_intersecting_blockade( + police_x, police_y, human_x, human_y, blockade + ): + vector = self._scale_clear( + self._get_vector(police_x, police_y, human_x, human_y) + ) + clear_x = int(police_x + vector[0]) + clear_y = int(police_y + vector[1]) - if hp is None or damage is None or hp == 0 or damage == 0: - return False + vector = self._scale_back_clear(vector) + start_x = int(police_x + vector[0]) + start_y = int(police_y + vector[1]) - active_time = (hp / damage) + (1 if (hp % damage) != 0 else 0) - if self._kernel_time == -1: - self._kernel_time = self.scenario_info.get_value("kernel.timesteps", -1) + if self._is_intersecting_blockade( + start_x, start_y, clear_x, clear_y, blockade + ): + if action_clear is None: + action_clear = ActionClearArea(clear_x, clear_y) + clear_blockade = blockade + else: + if clear_blockade is not None: + if self._is_intersecting_blockades(blockade, clear_blockade): + return ActionClear(clear_blockade) + + distance1 = self.world_info.get_distance( + police_entity.get_entity_id(), + clear_blockade.get_entity_id(), + ) + distance2 = self.world_info.get_distance( + police_entity.get_entity_id(), + blockade.get_entity_id(), + ) + if distance1 > distance2: + return ActionClearArea(clear_x, clear_y) - return damage is not None and ( - damage >= self._threshold_rest - or (active_time + self.agent_info.get_time() < self._kernel_time) - ) + return action_clear - def _calc_rest( - self, - police_force: PoliceForce, - path_planning: PathPlanning, - target_entity_ids: list[EntityID], - ) -> Optional[Action]: - position_entity_id = police_force.get_position() - if position_entity_id is None: - return None - refuges = self.world_info.get_entity_ids_of_types([Refuge]) - current_size = len(refuges) - if position_entity_id in refuges: - return ActionRest() - - first_result: list[EntityID] = [] - while len(refuges) > 0: - path = path_planning.get_path(position_entity_id, refuges[0]) - if path is not None and len(path) > 0: - if first_result == []: - first_result = path.copy() - if target_entity_ids == []: - break - - refuge_entity_id = path[-1] - from_refuge_to_target_path = path_planning.get_path( - refuge_entity_id, target_entity_ids[0] - ) - if from_refuge_to_target_path != []: - return ActionMove(path) - - refuges.remove(refuge_entity_id) - if current_size == len(refuges): - break - current_size = len(refuges) + elif distance < min_distance: + min_distance = distance + move_action = ActionMove([road.get_entity_id()], human_x, human_y) + + if action_clear is not None: + return action_clear + + return move_action + + def _is_inside(self, x: float, y: float, apexes: list[int]) -> bool: + point = Point(x, y) + polygon = Polygon([(apexes[i], apexes[i + 1]) for i in range(0, len(apexes), 2)]) + return polygon.contains(point) + + def _get_distance(self, x1: float, y1: float, x2: float, y2: float) -> float: + return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5 + + def _is_intersecting_area( + self, agent_x: float, agent_y: float, point_x: float, point_y: float, area: Area + ) -> bool: + edges = area.get_edges() + if edges is None: + return False + for edge in edges: + start_x = edge.get_start_x() + start_y = edge.get_start_y() + end_x = edge.get_end_x() + end_y = edge.get_end_y() + + line1 = LineString([(agent_x, agent_y), (point_x, point_y)]) + line2 = LineString([(start_x, start_y), (end_x, end_y)]) + if line1.intersects(line2): + mid_x = (start_x + end_x) / 2.0 + mid_y = (start_y + end_y) / 2.0 + if not self._equals_point( + agent_x, agent_y, mid_x, mid_y, 1000 + ) and not self._equals_point(point_x, point_y, mid_x, mid_y, 1000): + return True + + return False + + def _equals_point( + self, x1: float, y1: float, x2: float, y2: float, range: float + ) -> bool: + return (x2 - range < x1 and x1 < x2 + range) and ( + y2 - range < y1 and y1 < y2 + range + ) + + def _get_intersect_edge_action( + self, agent_x: float, agent_y: float, point_x: float, point_y: float, road: Road + ) -> Action: + move_points = self._get_move_points(road) + best_point: Optional[tuple[float, float]] = None + best_distance = sys.float_info.max + for point in move_points: + if not self._is_intersecting_area(agent_x, agent_y, point[0], point[1], road): + if not self._is_intersecting_area(point_x, point_y, point[0], point[1], road): + distance = self._get_distance(point_x, point_y, point[0], point[1]) + if distance < best_distance: + best_point = point + best_distance = distance + + if best_point is not None: + bp_x, bp_y = best_point + if road.get_blockades() is None: + return ActionMove([road.get_entity_id()], int(bp_x), int(bp_y)) + + action_clear: Optional[ActionClearArea] = None + clear_blockade: Optional[Blockade] = None + action_move: Optional[ActionMove] = None + + vector = self._scale_clear(self._get_vector(agent_x, agent_y, bp_x, bp_y)) + clear_x = int(agent_x + vector[0]) + clear_y = int(agent_x + vector[1]) + + vector = self._scale_back_clear(vector) + start_x = int(agent_x + vector[0]) + start_y = int(agent_y + vector[1]) + + for blockade in self.world_info.get_blockades(road): + if self._is_intersecting_blockade(start_x, start_y, bp_x, bp_y, blockade): + if self._is_intersecting_blockade( + start_x, start_y, clear_x, clear_y, blockade + ): + if action_clear is None: + action_clear = ActionClearArea(clear_x, clear_y) + clear_blockade = blockade else: - break - - return ActionMove(first_result) if first_result != [] else None - - def _get_rescue_action( - self, police_entity: PoliceForce, road: Road - ) -> Optional[Action]: - road_blockades = road.get_blockades() - blockades = set( - [] - if road_blockades is None - else [ - cast(Blockade, self.world_info.get_entity(blockade_entity_id)) - for blockade_entity_id in road_blockades - ] - ) - agent_entities = set( - self.world_info.get_entities_of_types([AmbulanceTeam, FireBrigade]) + if clear_blockade is not None and self._is_intersecting_blockades( + blockade, clear_blockade + ): + return ActionClear(clear_blockade) + return action_clear + elif action_move is None: + action_move = ActionMove([road.get_entity_id()], int(bp_x), int(bp_y)) + + if action_clear is not None: + return action_clear + if action_move is not None: + return action_move + + action = self._get_area_clear_action( + cast(PoliceForce, self.agent_info.get_myself()), road + ) + if action is None: + action = ActionMove([road.get_entity_id()], int(point_x), int(point_y)) + return action + + def _get_move_points(self, road: Road) -> set[tuple[float, float]]: + points: Optional[set[tuple[float, float]]] = self._move_point_cache.get( + road.get_entity_id() + ) + if points is None: + points = set() + apex = road.get_apexes() + for i in range(0, len(apex), 2): + for j in range(i + 2, len(apex), 2): + mid_x = (apex[i] + apex[j]) / 2.0 + mid_y = (apex[i + 1] + apex[j + 1]) / 2.0 + if self._is_inside(mid_x, mid_y, apex): + points.add((mid_x, mid_y)) + + edges = road.get_edges() + if edges is not None: + for edge in edges: + mid_x = (edge.get_start_x() + edge.get_end_x()) / 2.0 + mid_y = (edge.get_start_y() + edge.get_end_y()) / 2.0 + if (mid_x, mid_y) in points: + points.remove((mid_x, mid_y)) + + self._move_point_cache[road.get_entity_id()] = points + + return points + + def _get_vector( + self, from_x: float, from_y: float, to_x: float, to_y: float + ) -> tuple[float, float]: + return (to_x - from_x, to_y - from_y) + + def _scale_clear(self, vector: tuple[float, float]) -> tuple[float, float]: + length = 1.0 / math.hypot(vector[0], vector[1]) + return ( + vector[0] * length * self._clear_distance, + vector[1] * length * self._clear_distance, + ) + + def _scale_back_clear(self, vector: tuple[float, float]) -> tuple[float, float]: + length = 1.0 / math.hypot(vector[0], vector[1]) + return (vector[0] * length * -510, vector[1] * length * -510) + + def _is_intersecting_blockade( + self, + agent_x: float, + agent_y: float, + point_x: float, + point_y: float, + blockade: Blockade, + ) -> bool: + apexes = blockade.get_apexes() + if apexes is None or len(apexes) < 4: + return False + for i in range(0, len(apexes) - 3, 2): + line1 = LineString([(apexes[i], apexes[i + 1]), (apexes[i + 2], apexes[i + 3])]) + line2 = LineString([(agent_x, agent_y), (point_x, point_y)]) + if line1.intersects(line2): + return True + return False + + def _is_intersecting_blockades( + self, blockade1: Blockade, blockade2: Blockade + ) -> bool: + apexes1 = blockade1.get_apexes() + apexes2 = blockade2.get_apexes() + if apexes1 is None or apexes2 is None or len(apexes1) < 4 or len(apexes2) < 4: + return False + for i in range(0, len(apexes1) - 2, 2): + for j in range(0, len(apexes2) - 2, 2): + line1 = LineString( + [(apexes1[i], apexes1[i + 1]), (apexes1[i + 2], apexes1[i + 3])] ) - - police_x = police_entity.get_x() - police_y = police_entity.get_y() - min_distance = sys.float_info.max - move_action: Optional[ActionMove] = None - - for agent_entity in agent_entities: - human = cast(Human, agent_entity) - human_position = human.get_position() - if ( - human_position is None - or human_position.get_value() != road.get_entity_id().get_value() - ): - continue - - human_x = human.get_x() - human_y = human.get_y() - if ( - human_x is None - or human_y is None - or police_x is None - or police_y is None - ): - continue - - action_clear: Optional[ActionClear | ActionClearArea] = None - clear_blockade: Optional[Blockade] = None - for blockade in blockades: - blockade_apexes = blockade.get_apexes() - if blockade_apexes is None or not self._is_inside( - human_x, human_y, blockade_apexes - ): - continue - - distance = self._get_distance(police_x, police_y, human_x, human_y) - if self._is_intersecting_area( - police_x, police_y, human_x, human_y, road - ): - action = self._get_intersect_edge_action( - police_x, police_y, human_x, human_y, road - ) - if action is None: - continue - if isinstance(action, ActionClear): - if action_clear is None: - action_clear = action - clear_blockade = blockade - continue - - if clear_blockade is not None: - if self._is_intersecting_blockades( - blockade, clear_blockade - ): - return ActionClear(clear_blockade) - - another_distance = self.world_info.get_distance( - police_entity.get_entity_id(), - clear_blockade.get_entity_id(), - ) - blockade_distance = self.world_info.get_distance( - police_entity.get_entity_id(), blockade.get_entity_id() - ) - if blockade_distance < another_distance: - return action - - return action_clear - elif isinstance(action, ActionMove) and distance < min_distance: - min_distance = distance - move_action = action - - elif self._is_intersecting_blockade( - police_x, police_y, human_x, human_y, blockade - ): - vector = self._scale_clear( - self._get_vector(police_x, police_y, human_x, human_y) - ) - clear_x = int(police_x + vector[0]) - clear_y = int(police_y + vector[1]) - - vector = self._scale_back_clear(vector) - start_x = int(police_x + vector[0]) - start_y = int(police_y + vector[1]) - - if self._is_intersecting_blockade( - start_x, start_y, clear_x, clear_y, blockade - ): - if action_clear is None: - action_clear = ActionClearArea(clear_x, clear_y) - clear_blockade = blockade - else: - if clear_blockade is not None: - if self._is_intersecting_blockades( - blockade, clear_blockade - ): - return ActionClear(clear_blockade) - - distance1 = self.world_info.get_distance( - police_entity.get_entity_id(), - clear_blockade.get_entity_id(), - ) - distance2 = self.world_info.get_distance( - police_entity.get_entity_id(), - blockade.get_entity_id(), - ) - if distance1 > distance2: - return ActionClearArea(clear_x, clear_y) - - return action_clear - - elif distance < min_distance: - min_distance = distance - move_action = ActionMove( - [road.get_entity_id()], human_x, human_y - ) - - if action_clear is not None: - return action_clear - - return move_action - - def _is_inside(self, x: float, y: float, apexes: list[int]) -> bool: - point = Point(x, y) - polygon = Polygon( - [(apexes[i], apexes[i + 1]) for i in range(0, len(apexes), 2)] + line2 = LineString( + [(apexes2[j], apexes2[j + 1]), (apexes2[j + 2], apexes2[j + 3])] ) - return polygon.contains(point) + if line1.intersects(line2): + return True + + for i in range(0, len(apexes1) - 2, 2): + line1 = LineString( + [(apexes1[i], apexes1[i + 1]), (apexes1[i + 2], apexes1[i + 3])] + ) + line2 = LineString([(apexes2[-2], apexes2[-1]), (apexes2[0], apexes2[1])]) + if line1.intersects(line2): + return True + + for i in range(0, len(apexes2) - 2, 2): + line1 = LineString([(apexes1[-2], apexes1[-1]), (apexes1[0], apexes1[1])]) + line2 = LineString( + [(apexes2[i], apexes2[i + 1]), (apexes2[i + 2], apexes2[i + 3])] + ) + if line1.intersects(line2): + return True + + return False + + def _get_area_clear_action( + self, police_entity: PoliceForce, road: Road + ) -> Optional[Action]: + if road.get_blockades() == []: + return None + + blockades = set(self.world_info.get_blockades(road)) + min_distance = sys.float_info.max + clear_blockade: Optional[Blockade] = None + for blockade in blockades: + for another in blockades: + if blockade == another: + continue + + if self._is_intersecting_blockades(blockade, another): + distance1 = self.world_info.get_distance( + police_entity.get_entity_id(), blockade.get_entity_id() + ) + distance2 = self.world_info.get_distance( + police_entity.get_entity_id(), another.get_entity_id() + ) + if distance1 <= distance2 and distance1 < min_distance: + min_distance = distance1 + clear_blockade = blockade + elif distance2 < min_distance: + min_distance = distance2 + clear_blockade = another + + if clear_blockade is not None: + if min_distance < self._clear_distance: + return ActionClear(clear_blockade) + else: + position = police_entity.get_position() + if position is not None: + return ActionMove( + [position], + clear_blockade.get_x(), + clear_blockade.get_y(), + ) + + agent_x = police_entity.get_x() + agent_y = police_entity.get_y() + if agent_x is None or agent_y is None: + return None + clear_blockade = None + min_point_distance = sys.float_info.max + clear_x = 0 + clear_y = 0 + for blockade in blockades: + apexes = blockade.get_apexes() + if apexes is None or len(apexes) < 4: + continue + for i in range(0, len(apexes) - 2, 2): + distance = self._get_distance(agent_x, agent_y, apexes[i], apexes[i + 1]) + if distance < min_point_distance: + clear_blockade = blockade + min_point_distance = distance + clear_x = apexes[i] + clear_y = apexes[i + 1] + + if clear_blockade is not None: + if min_point_distance < self._clear_distance: + vector = self._scale_clear(self._get_vector(agent_x, agent_y, clear_x, clear_y)) + clear_x = int(agent_x + vector[0]) + clear_y = int(agent_y + vector[1]) + return ActionClearArea(clear_x, clear_y) + position = police_entity.get_position() + if position is not None: + return ActionMove([position], clear_x, clear_y) + + return None + + def _index_of(self, list: list[EntityID], x: EntityID) -> int: + return list.index(x) if x in list else -1 + + def _get_neighbour_position_action( + self, police_entity: PoliceForce, target: Area + ) -> Optional[Action]: + agent_x = police_entity.get_x() + agent_y = police_entity.get_y() + if agent_x is None or agent_y is None: + return None + position_id = police_entity.get_position() + if position_id is None: + return None + position = self.world_info.get_entity(position_id) + if position is None: + return None + + edge = target.get_edge_to(position.get_entity_id()) + if edge is None: + return None + + if isinstance(position, Road): + road = position + if road.get_blockades() != []: + mid_x = (edge.get_start_x() + edge.get_end_x()) / 2.0 + mid_y = (edge.get_start_y() + edge.get_end_y()) / 2.0 + if self._is_intersecting_area(agent_x, agent_y, mid_x, mid_y, road): + return self._get_intersect_edge_action(agent_x, agent_y, mid_x, mid_y, road) + + action_clear: Optional[ActionClear | ActionClearArea] = None + clear_blockade: Optional[Blockade] = None + action_move: Optional[ActionMove] = None - def _get_distance(self, x1: float, y1: float, x2: float, y2: float) -> float: - return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5 + vector = self._scale_clear(self._get_vector(agent_x, agent_y, mid_x, mid_y)) + clear_x = int(agent_x + vector[0]) + clear_y = int(agent_y + vector[1]) - def _is_intersecting_area( - self, agent_x: float, agent_y: float, point_x: float, point_y: float, area: Area - ) -> bool: - edges = area.get_edges() - if edges is None: - return False - for edge in edges: - start_x = edge.get_start_x() - start_y = edge.get_start_y() - end_x = edge.get_end_x() - end_y = edge.get_end_y() - - line1 = LineString([(agent_x, agent_y), (point_x, point_y)]) - line2 = LineString([(start_x, start_y), (end_x, end_y)]) - if line1.intersects(line2): - mid_x = (start_x + end_x) / 2.0 - mid_y = (start_y + end_y) / 2.0 - if not self._equals_point( - agent_x, agent_y, mid_x, mid_y, 1000 - ) and not self._equals_point(point_x, point_y, mid_x, mid_y, 1000): - return True - - return False - - def _equals_point( - self, x1: float, y1: float, x2: float, y2: float, range: float - ) -> bool: - return (x2 - range < x1 and x1 < x2 + range) and ( - y2 - range < y1 and y1 < y2 + range - ) + vector = self._scale_back_clear(vector) + start_x = int(agent_x + vector[0]) + start_y = int(agent_y + vector[1]) - def _get_intersect_edge_action( - self, agent_x: float, agent_y: float, point_x: float, point_y: float, road: Road - ) -> Action: - move_points = self._get_move_points(road) - best_point: Optional[tuple[float, float]] = None - best_distance = sys.float_info.max - for point in move_points: - if not self._is_intersecting_area( - agent_x, agent_y, point[0], point[1], road + for blockade in self.world_info.get_blockades(road): + if self._is_intersecting_blockade(start_x, start_y, mid_x, mid_y, blockade): + if self._is_intersecting_blockade( + start_x, start_y, clear_x, clear_y, blockade ): - if not self._is_intersecting_area( - point_x, point_y, point[0], point[1], road + if action_clear is None: + action_clear = ActionClearArea(clear_x, clear_y) + clear_blockade = blockade + if self._equals_point( + self._old_clear_x, + self._old_clear_y, + clear_x, + clear_y, + 1000, ): - distance = self._get_distance(point_x, point_y, point[0], point[1]) - if distance < best_distance: - best_point = point - best_distance = distance - - if best_point is not None: - bp_x, bp_y = best_point - if road.get_blockades() is None: - return ActionMove([road.get_entity_id()], int(bp_x), int(bp_y)) - - action_clear: Optional[ActionClearArea] = None - clear_blockade: Optional[Blockade] = None - action_move: Optional[ActionMove] = None - - vector = self._scale_clear(self._get_vector(agent_x, agent_y, bp_x, bp_y)) - clear_x = int(agent_x + vector[0]) - clear_y = int(agent_x + vector[1]) + if self.count >= self._forced_move: + self.count = 0 + return ActionMove( + [road.get_entity_id()], + int(clear_x), + int(clear_y), + ) + self.count += 1 - vector = self._scale_back_clear(vector) - start_x = int(agent_x + vector[0]) - start_y = int(agent_y + vector[1]) + self._old_clear_x = clear_x + self._old_clear_y = clear_y + else: + if clear_blockade is not None: + if self._is_intersecting_blockades(blockade, clear_blockade): + return ActionClear(clear_blockade) - for blockade in self.world_info.get_blockades(road): - if self._is_intersecting_blockade( - start_x, start_y, bp_x, bp_y, blockade - ): - if self._is_intersecting_blockade( - start_x, start_y, clear_x, clear_y, blockade - ): - if action_clear is None: - action_clear = ActionClearArea(clear_x, clear_y) - clear_blockade = blockade - else: - if ( - clear_blockade is not None - and self._is_intersecting_blockades( - blockade, clear_blockade - ) - ): - return ActionClear(clear_blockade) - return action_clear - elif action_move is None: - action_move = ActionMove( - [road.get_entity_id()], int(bp_x), int(bp_y) - ) - - if action_clear is not None: return action_clear - if action_move is not None: - return action_move + elif action_move is None: + action_move = ActionMove([road.get_entity_id()], int(mid_x), int(mid_y)) - action = self._get_area_clear_action( - cast(PoliceForce, self.agent_info.get_myself()), road - ) - if action is None: - action = ActionMove([road.get_entity_id()], int(point_x), int(point_y)) - return action + if action_clear is not None: + return action_clear + if action_move is not None: + return action_move - def _get_move_points(self, road: Road) -> set[tuple[float, float]]: - points: Optional[set[tuple[float, float]]] = self._move_point_cache.get( - road.get_entity_id() - ) - if points is None: - points = set() - apex = road.get_apexes() - for i in range(0, len(apex), 2): - for j in range(i + 2, len(apex), 2): - mid_x = (apex[i] + apex[j]) / 2.0 - mid_y = (apex[i + 1] + apex[j + 1]) / 2.0 - if self._is_inside(mid_x, mid_y, apex): - points.add((mid_x, mid_y)) - - edges = road.get_edges() - if edges is not None: - for edge in edges: - mid_x = (edge.get_start_x() + edge.get_end_x()) / 2.0 - mid_y = (edge.get_start_y() + edge.get_end_y()) / 2.0 - if (mid_x, mid_y) in points: - points.remove((mid_x, mid_y)) - - self._move_point_cache[road.get_entity_id()] = points - - return points - - def _get_vector( - self, from_x: float, from_y: float, to_x: float, to_y: float - ) -> tuple[float, float]: - return (to_x - from_x, to_y - from_y) - - def _scale_clear(self, vector: tuple[float, float]) -> tuple[float, float]: - length = 1.0 / math.hypot(vector[0], vector[1]) - return ( - vector[0] * length * self._clear_distance, - vector[1] * length * self._clear_distance, - ) + if isinstance(target, Road): + road = target + if road.get_blockades() == []: + return ActionMove([position.get_entity_id(), target.get_entity_id()]) - def _scale_back_clear(self, vector: tuple[float, float]) -> tuple[float, float]: - length = 1.0 / math.hypot(vector[0], vector[1]) - return (vector[0] * length * -510, vector[1] * length * -510) - - def _is_intersecting_blockade( - self, - agent_x: float, - agent_y: float, - point_x: float, - point_y: float, - blockade: Blockade, - ) -> bool: + target_blockade: Optional[Blockade] = None + min_point_distance = sys.float_info.max + clear_x = 0 + clear_y = 0 + for blockade in self.world_info.get_blockades(road): apexes = blockade.get_apexes() if apexes is None or len(apexes) < 4: - return False - for i in range(0, len(apexes) - 3, 2): - line1 = LineString( - [(apexes[i], apexes[i + 1]), (apexes[i + 2], apexes[i + 3])] - ) - line2 = LineString([(agent_x, agent_y), (point_x, point_y)]) - if line1.intersects(line2): - return True - return False - - def _is_intersecting_blockades( - self, blockade1: Blockade, blockade2: Blockade - ) -> bool: - apexes1 = blockade1.get_apexes() - apexes2 = blockade2.get_apexes() - if apexes1 is None or apexes2 is None or len(apexes1) < 4 or len(apexes2) < 4: - return False - for i in range(0, len(apexes1) - 2, 2): - for j in range(0, len(apexes2) - 2, 2): - line1 = LineString( - [(apexes1[i], apexes1[i + 1]), (apexes1[i + 2], apexes1[i + 3])] - ) - line2 = LineString( - [(apexes2[j], apexes2[j + 1]), (apexes2[j + 2], apexes2[j + 3])] - ) - if line1.intersects(line2): - return True - - for i in range(0, len(apexes1) - 2, 2): - line1 = LineString( - [(apexes1[i], apexes1[i + 1]), (apexes1[i + 2], apexes1[i + 3])] - ) - line2 = LineString([(apexes2[-2], apexes2[-1]), (apexes2[0], apexes2[1])]) - if line1.intersects(line2): - return True - - for i in range(0, len(apexes2) - 2, 2): - line1 = LineString([(apexes1[-2], apexes1[-1]), (apexes1[0], apexes1[1])]) - line2 = LineString( - [(apexes2[i], apexes2[i + 1]), (apexes2[i + 2], apexes2[i + 3])] - ) - if line1.intersects(line2): - return True - - return False - - def _get_area_clear_action( - self, police_entity: PoliceForce, road: Road - ) -> Optional[Action]: - if road.get_blockades() == []: - return None - - blockades = set(self.world_info.get_blockades(road)) - min_distance = sys.float_info.max - clear_blockade: Optional[Blockade] = None - for blockade in blockades: - for another in blockades: - if blockade == another: - continue - - if self._is_intersecting_blockades(blockade, another): - distance1 = self.world_info.get_distance( - police_entity.get_entity_id(), blockade.get_entity_id() - ) - distance2 = self.world_info.get_distance( - police_entity.get_entity_id(), another.get_entity_id() - ) - if distance1 <= distance2 and distance1 < min_distance: - min_distance = distance1 - clear_blockade = blockade - elif distance2 < min_distance: - min_distance = distance2 - clear_blockade = another - - if clear_blockade is not None: - if min_distance < self._clear_distance: - return ActionClear(clear_blockade) - else: - position = police_entity.get_position() - if position is not None: - return ActionMove( - [position], - clear_blockade.get_x(), - clear_blockade.get_y(), - ) - - agent_x = police_entity.get_x() - agent_y = police_entity.get_y() - if agent_x is None or agent_y is None: - return None - clear_blockade = None - min_point_distance = sys.float_info.max - clear_x = 0 - clear_y = 0 - for blockade in blockades: - apexes = blockade.get_apexes() - if apexes is None or len(apexes) < 4: - continue - for i in range(0, len(apexes) - 2, 2): - distance = self._get_distance( - agent_x, agent_y, apexes[i], apexes[i + 1] - ) - if distance < min_point_distance: - clear_blockade = blockade - min_point_distance = distance - clear_x = apexes[i] - clear_y = apexes[i + 1] - - if clear_blockade is not None: - if min_point_distance < self._clear_distance: - vector = self._scale_clear( - self._get_vector(agent_x, agent_y, clear_x, clear_y) - ) - clear_x = int(agent_x + vector[0]) - clear_y = int(agent_y + vector[1]) - return ActionClearArea(clear_x, clear_y) - position = police_entity.get_position() - if position is not None: - return ActionMove([position], clear_x, clear_y) - - return None - - def _index_of(self, list: list[EntityID], x: EntityID) -> int: - return list.index(x) if x in list else -1 - - def _get_neighbour_position_action( - self, police_entity: PoliceForce, target: Area - ) -> Optional[Action]: - agent_x = police_entity.get_x() - agent_y = police_entity.get_y() - if agent_x is None or agent_y is None: - return None - position_id = police_entity.get_position() - if position_id is None: - return None - position = self.world_info.get_entity(position_id) - if position is None: - return None - - edge = target.get_edge_to(position.get_entity_id()) - if edge is None: - return None - - if isinstance(position, Road): - road = position - if road.get_blockades() != []: - mid_x = (edge.get_start_x() + edge.get_end_x()) / 2.0 - mid_y = (edge.get_start_y() + edge.get_end_y()) / 2.0 - if self._is_intersecting_area(agent_x, agent_y, mid_x, mid_y, road): - return self._get_intersect_edge_action( - agent_x, agent_y, mid_x, mid_y, road - ) - - action_clear: Optional[ActionClear | ActionClearArea] = None - clear_blockade: Optional[Blockade] = None - action_move: Optional[ActionMove] = None - - vector = self._scale_clear( - self._get_vector(agent_x, agent_y, mid_x, mid_y) - ) - clear_x = int(agent_x + vector[0]) - clear_y = int(agent_y + vector[1]) - - vector = self._scale_back_clear(vector) - start_x = int(agent_x + vector[0]) - start_y = int(agent_y + vector[1]) - - for blockade in self.world_info.get_blockades(road): - if self._is_intersecting_blockade( - start_x, start_y, mid_x, mid_y, blockade - ): - if self._is_intersecting_blockade( - start_x, start_y, clear_x, clear_y, blockade - ): - if action_clear is None: - action_clear = ActionClearArea(clear_x, clear_y) - clear_blockade = blockade - if self._equals_point( - self._old_clear_x, - self._old_clear_y, - clear_x, - clear_y, - 1000, - ): - if self.count >= self._forced_move: - self.count = 0 - return ActionMove( - [road.get_entity_id()], - int(clear_x), - int(clear_y), - ) - self.count += 1 - - self._old_clear_x = clear_x - self._old_clear_y = clear_y - else: - if clear_blockade is not None: - if self._is_intersecting_blockades( - blockade, clear_blockade - ): - return ActionClear(clear_blockade) - - return action_clear - elif action_move is None: - action_move = ActionMove( - [road.get_entity_id()], int(mid_x), int(mid_y) - ) - - if action_clear is not None: - return action_clear - if action_move is not None: - return action_move - - if isinstance(target, Road): - road = target - if road.get_blockades() == []: - return ActionMove([position.get_entity_id(), target.get_entity_id()]) - - target_blockade: Optional[Blockade] = None - min_point_distance = sys.float_info.max - clear_x = 0 - clear_y = 0 - for blockade in self.world_info.get_blockades(road): - apexes = blockade.get_apexes() - if apexes is None or len(apexes) < 4: - continue - for i in range(0, len(apexes) - 2, 2): - distance = self._get_distance( - agent_x, agent_y, apexes[i], apexes[i + 1] - ) - if distance < min_point_distance: - target_blockade = blockade - min_point_distance = distance - clear_x = apexes[i] - clear_y = apexes[i + 1] - - if ( - target_blockade is not None - and min_point_distance < self._clear_distance - ): - vector = self._scale_clear( - self._get_vector(agent_x, agent_y, clear_x, clear_y) - ) - clear_x = int(agent_x + vector[0]) - clear_y = int(agent_y + vector[1]) - if self._equals_point( - self._old_clear_x, self._old_clear_y, clear_x, clear_y, 1000 - ): - if self.count >= self._forced_move: - self.count = 0 - return ActionMove([road.get_entity_id()], clear_x, clear_y) - self.count += 1 + continue + for i in range(0, len(apexes) - 2, 2): + distance = self._get_distance(agent_x, agent_y, apexes[i], apexes[i + 1]) + if distance < min_point_distance: + target_blockade = blockade + min_point_distance = distance + clear_x = apexes[i] + clear_y = apexes[i + 1] + + if target_blockade is not None and min_point_distance < self._clear_distance: + vector = self._scale_clear(self._get_vector(agent_x, agent_y, clear_x, clear_y)) + clear_x = int(agent_x + vector[0]) + clear_y = int(agent_y + vector[1]) + if self._equals_point( + self._old_clear_x, self._old_clear_y, clear_x, clear_y, 1000 + ): + if self.count >= self._forced_move: + self.count = 0 + return ActionMove([road.get_entity_id()], clear_x, clear_y) + self.count += 1 - self._old_clear_x = clear_x - self._old_clear_y = clear_y - return ActionClearArea(clear_x, clear_y) + self._old_clear_x = clear_x + self._old_clear_y = clear_y + return ActionClearArea(clear_x, clear_y) - return ActionMove([position.get_entity_id(), target.get_entity_id()]) + return ActionMove([position.get_entity_id(), target.get_entity_id()]) diff --git a/src/adf_core_python/implement/action/default_extend_action_move.py b/src/adf_core_python/implement/action/default_extend_action_move.py index b14baf3..7287535 100644 --- a/src/adf_core_python/implement/action/default_extend_action_move.py +++ b/src/adf_core_python/implement/action/default_extend_action_move.py @@ -15,90 +15,90 @@ class DefaultExtendActionMove(ExtendAction): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - self._target_entity_id: Optional[EntityID] = None - self._threshold_to_rest: int = develop_data.get_value("threshold_to_rest", 100) - - self._path_planning: PathPlanning = cast( - PathPlanning, - self.module_manager.get_module( - "DefaultExtendActionMove.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - - def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: - super().precompute(precompute_data) - if self.get_count_precompute() > 1: - return self - self._path_planning.precompute(precompute_data) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._target_entity_id: Optional[EntityID] = None + self._threshold_to_rest: int = develop_data.get_value("threshold_to_rest", 100) + + self._path_planning: PathPlanning = cast( + PathPlanning, + self.module_manager.get_module( + "DefaultExtendActionMove.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + + def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: + super().precompute(precompute_data) + if self.get_count_precompute() > 1: + return self + self._path_planning.precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> ExtendAction: + super().resume(precompute_data) + if self.get_count_resume() > 1: + return self + self._path_planning.resume(precompute_data) + return self + + def prepare(self) -> ExtendAction: + super().prepare() + if self.get_count_prepare() > 1: + return self + self._path_planning.prepare() + return self + + def update_info(self, message_manager: MessageManager) -> ExtendAction: + super().update_info(message_manager) + if self.get_count_update_info() > 1: + return self + self._path_planning.update_info(message_manager) + return self + + def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: + entity: Optional[Entity] = self.world_info.get_entity(target_entity_id) + self._target_entity_id = None + + if entity is None: + return self + + if isinstance(entity, Blockade) or isinstance(entity, Human): + position: Optional[EntityID] = entity.get_position() + if position is None: return self + entity = self.world_info.get_entity(position) - def resume(self, precompute_data: PrecomputeData) -> ExtendAction: - super().resume(precompute_data) - if self.get_count_resume() > 1: - return self - self._path_planning.resume(precompute_data) - return self - - def prepare(self) -> ExtendAction: - super().prepare() - if self.get_count_prepare() > 1: - return self - self._path_planning.prepare() - return self + if entity is not None and isinstance(entity, Area): + self._target_entity_id = entity.get_entity_id() - def update_info(self, message_manager: MessageManager) -> ExtendAction: - super().update_info(message_manager) - if self.get_count_update_info() > 1: - return self - self._path_planning.update_info(message_manager) - return self - - def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: - entity: Optional[Entity] = self.world_info.get_entity(target_entity_id) - self._target_entity_id = None + return self - if entity is None: - return self + def calculate(self) -> ExtendAction: + self.result = None + agent: Human = cast(Human, self.agent_info.get_myself()) - if isinstance(entity, Blockade) or isinstance(entity, Human): - position: Optional[EntityID] = entity.get_position() - if position is None: - return self - entity = self.world_info.get_entity(position) + if self._target_entity_id is None: + return self - if entity is not None and isinstance(entity, Area): - self._target_entity_id = entity.get_entity_id() + agent_position = agent.get_position() + if agent_position is None: + return self - return self + path: list[EntityID] = self._path_planning.get_path( + agent_position, self._target_entity_id + ) - def calculate(self) -> ExtendAction: - self.result = None - agent: Human = cast(Human, self.agent_info.get_myself()) + if path is not None and len(path) != 0: + self.result = ActionMove(path) - if self._target_entity_id is None: - return self - - agent_position = agent.get_position() - if agent_position is None: - return self - - path: list[EntityID] = self._path_planning.get_path( - agent_position, self._target_entity_id - ) - - if path is not None and len(path) != 0: - self.result = ActionMove(path) - - return self + return self diff --git a/src/adf_core_python/implement/action/default_extend_action_rescue.py b/src/adf_core_python/implement/action/default_extend_action_rescue.py index b4624bb..489f5d5 100644 --- a/src/adf_core_python/implement/action/default_extend_action_rescue.py +++ b/src/adf_core_python/implement/action/default_extend_action_rescue.py @@ -9,8 +9,8 @@ from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo from adf_core_python.core.agent.info.scenario_info import ( - ScenarioInfo, - ScenarioInfoKeys, + ScenarioInfo, + ScenarioInfoKeys, ) from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager @@ -20,136 +20,136 @@ class DefaultExtendActionRescue(ExtendAction): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - self._kernel_time: int = -1 - self._target_entity_id: Optional[EntityID] = None - self._threshold_rest = develop_data.get_value( - "adf_core_python.implement.action.DefaultExtendActionRescue.rest", 100 - ) - - self._path_planning = cast( - PathPlanning, - self.module_manager.get_module( - "DefaultExtendActionRescue.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - - def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: - super().precompute(precompute_data) - if self.get_count_precompute() >= 2: - return self - self._path_planning.precompute(precompute_data) - self._kernel_time = self.scenario_info.get_value( - ScenarioInfoKeys.KERNEL_TIMESTEPS, -1 - ) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._kernel_time: int = -1 + self._target_entity_id: Optional[EntityID] = None + self._threshold_rest = develop_data.get_value( + "adf_core_python.implement.action.DefaultExtendActionRescue.rest", 100 + ) + + self._path_planning = cast( + PathPlanning, + self.module_manager.get_module( + "DefaultExtendActionRescue.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + + def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: + super().precompute(precompute_data) + if self.get_count_precompute() >= 2: + return self + self._path_planning.precompute(precompute_data) + self._kernel_time = self.scenario_info.get_value( + ScenarioInfoKeys.KERNEL_TIMESTEPS, -1 + ) + return self + + def resume(self, precompute_data: PrecomputeData) -> ExtendAction: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + self._path_planning.resume(precompute_data) + self._kernel_time = self.scenario_info.get_value( + ScenarioInfoKeys.KERNEL_TIMESTEPS, -1 + ) + return self + + def prepare(self) -> ExtendAction: + super().prepare() + if self.get_count_prepare() >= 2: + return self + self._path_planning.prepare() + self._kernel_time = self.scenario_info.get_value( + ScenarioInfoKeys.KERNEL_TIMESTEPS, -1 + ) + return self + + def update_info(self, message_manager: MessageManager) -> ExtendAction: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self + self._path_planning.update_info(message_manager) + return self + + def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: + self._target_entity_id = None + if target_entity_id is not None: + entity = self.world_info.get_entity(target_entity_id) + if isinstance(entity, Human) or isinstance(entity, Area): + self._target_entity_id = target_entity_id return self + return self + + def calculate(self) -> ExtendAction: + self.result = None + agent = cast(FireBrigade, self.agent_info.get_myself()) + + if self._target_entity_id is not None: + self.result = self._calc_rescue( + agent, self._path_planning, self._target_entity_id + ) + + return self + + def _calc_rescue( + self, + agent: FireBrigade, + path_planning: PathPlanning, + target_entity_id: EntityID, + ) -> Optional[Action]: + target_entity = self.world_info.get_entity(target_entity_id) + if target_entity is None: + return None + + agent_position_entity_id = agent.get_position() + if agent_position_entity_id is None: + return None + + if isinstance(target_entity, Human): + human = target_entity + if human.get_hp() == 0: + return None - def resume(self, precompute_data: PrecomputeData) -> ExtendAction: - super().resume(precompute_data) - if self.get_count_resume() >= 2: - return self - self._path_planning.resume(precompute_data) - self._kernel_time = self.scenario_info.get_value( - ScenarioInfoKeys.KERNEL_TIMESTEPS, -1 - ) - return self + target_position_entity_id = human.get_position() + if target_position_entity_id is None: + return None - def prepare(self) -> ExtendAction: - super().prepare() - if self.get_count_prepare() >= 2: - return self - self._path_planning.prepare() - self._kernel_time = self.scenario_info.get_value( - ScenarioInfoKeys.KERNEL_TIMESTEPS, -1 + if agent_position_entity_id == target_position_entity_id: + buriedness = human.get_buriedness() + if buriedness is not None and buriedness > 0: + return ActionRescue(target_entity_id) + else: + path = path_planning.get_path( + agent_position_entity_id, target_position_entity_id ) - return self - - def update_info(self, message_manager: MessageManager) -> ExtendAction: - super().update_info(message_manager) - if self.get_count_update_info() >= 2: - return self - self._path_planning.update_info(message_manager) - return self - - def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: - self._target_entity_id = None - if target_entity_id is not None: - entity = self.world_info.get_entity(target_entity_id) - if isinstance(entity, Human) or isinstance(entity, Area): - self._target_entity_id = target_entity_id - return self - return self + if path != []: + return ActionMove(path) - def calculate(self) -> ExtendAction: - self.result = None - agent = cast(FireBrigade, self.agent_info.get_myself()) + return None - if self._target_entity_id is not None: - self.result = self._calc_rescue( - agent, self._path_planning, self._target_entity_id - ) - - return self + if isinstance(target_entity, Blockade): + blockade = target_entity + blockade_position = blockade.get_position() + if blockade_position is None: + return None - def _calc_rescue( - self, - agent: FireBrigade, - path_planning: PathPlanning, - target_entity_id: EntityID, - ) -> Optional[Action]: - target_entity = self.world_info.get_entity(target_entity_id) - if target_entity is None: - return None - - agent_position_entity_id = agent.get_position() - if agent_position_entity_id is None: - return None - - if isinstance(target_entity, Human): - human = target_entity - if human.get_hp() == 0: - return None - - target_position_entity_id = human.get_position() - if target_position_entity_id is None: - return None - - if agent_position_entity_id == target_position_entity_id: - buriedness = human.get_buriedness() - if buriedness is not None and buriedness > 0: - return ActionRescue(target_entity_id) - else: - path = path_planning.get_path( - agent_position_entity_id, target_position_entity_id - ) - if path != []: - return ActionMove(path) - - return None - - if isinstance(target_entity, Blockade): - blockade = target_entity - blockade_position = blockade.get_position() - if blockade_position is None: - return None - - target_entity = self.world_info.get_entity(blockade_position) - if isinstance(target_entity, Area): - path = self._path_planning.get_path( - agent_position_entity_id, target_entity.get_entity_id() - ) - if path != []: - return ActionMove(path) + target_entity = self.world_info.get_entity(blockade_position) + if isinstance(target_entity, Area): + path = self._path_planning.get_path( + agent_position_entity_id, target_entity.get_entity_id() + ) + if path != []: + return ActionMove(path) - return None + return None diff --git a/src/adf_core_python/implement/action/default_extend_action_transport.py b/src/adf_core_python/implement/action/default_extend_action_transport.py index 5fe0b9d..b647668 100644 --- a/src/adf_core_python/implement/action/default_extend_action_transport.py +++ b/src/adf_core_python/implement/action/default_extend_action_transport.py @@ -1,13 +1,13 @@ from typing import Optional, Union, cast from rcrscore.entities import ( - AmbulanceTeam, - Area, - Civilian, - Entity, - EntityID, - Human, - Refuge, + AmbulanceTeam, + Area, + Civilian, + Entity, + EntityID, + Human, + Refuge, ) from adf_core_python.core.agent.action.ambulance.action_load import ActionLoad @@ -27,234 +27,228 @@ class DefaultExtendActionTransport(ExtendAction): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - self._target_entity_id: Optional[EntityID] = None - self._threshold_to_rest: int = develop_data.get_value("threshold_to_rest", 100) - self._logger = get_agent_logger( - f"{self.__class__.__module__}.{self.__class__.__qualname__}", - self.agent_info, - ) - - self._path_planning: PathPlanning = cast( - PathPlanning, - self.module_manager.get_module( - "DefaultExtendActionMove.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - - def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: - super().precompute(precompute_data) - if self.get_count_precompute() > 1: - return self - self._path_planning.precompute(precompute_data) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._target_entity_id: Optional[EntityID] = None + self._threshold_to_rest: int = develop_data.get_value("threshold_to_rest", 100) + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self.agent_info, + ) + + self._path_planning: PathPlanning = cast( + PathPlanning, + self.module_manager.get_module( + "DefaultExtendActionMove.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + + def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: + super().precompute(precompute_data) + if self.get_count_precompute() > 1: + return self + self._path_planning.precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> ExtendAction: + super().resume(precompute_data) + if self.get_count_resume() > 1: + return self + self._path_planning.resume(precompute_data) + return self + + def prepare(self) -> ExtendAction: + super().prepare() + if self.get_count_prepare() > 1: + return self + self._path_planning.prepare() + return self + + def update_info(self, message_manager: MessageManager) -> ExtendAction: + super().update_info(message_manager) + if self.get_count_update_info() > 1: + return self + self._path_planning.update_info(message_manager) + return self + + def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: + entity: Optional[Entity] = self.world_info.get_world_model().get_entity( + target_entity_id + ) + self._target_entity_id = None + + if entity is None: + return self + + if isinstance(entity, Human) or isinstance(entity, Area): + self._target_entity_id = target_entity_id + + return self + + def calculate(self) -> ExtendAction: + self._result = None + agent: AmbulanceTeam = cast(AmbulanceTeam, self.agent_info.get_myself()) + transport_human: Optional[Human] = self.agent_info.some_one_on_board() + if transport_human is not None: + self._logger.debug(f"transport_human: {transport_human.get_entity_id()}") + self.result = self.calc_unload( + agent, self._path_planning, transport_human, self._target_entity_id + ) + if self.result is not None: return self - def resume(self, precompute_data: PrecomputeData) -> ExtendAction: - super().resume(precompute_data) - if self.get_count_resume() > 1: - return self - self._path_planning.resume(precompute_data) - return self - - def prepare(self) -> ExtendAction: - super().prepare() - if self.get_count_prepare() > 1: - return self - self._path_planning.prepare() - return self - - def update_info(self, message_manager: MessageManager) -> ExtendAction: - super().update_info(message_manager) - if self.get_count_update_info() > 1: - return self - self._path_planning.update_info(message_manager) - return self - - def set_target_entity_id(self, target_entity_id: EntityID) -> ExtendAction: - entity: Optional[Entity] = self.world_info.get_world_model().get_entity( - target_entity_id - ) - self._target_entity_id = None - - if entity is None: - return self - - if isinstance(entity, Human) or isinstance(entity, Area): - self._target_entity_id = target_entity_id - - return self - - def calculate(self) -> ExtendAction: - self._result = None - agent: AmbulanceTeam = cast(AmbulanceTeam, self.agent_info.get_myself()) - transport_human: Optional[Human] = self.agent_info.some_one_on_board() - if transport_human is not None: - self._logger.debug(f"transport_human: {transport_human.get_entity_id()}") - self.result = self.calc_unload( - agent, self._path_planning, transport_human, self._target_entity_id - ) - if self.result is not None: - return self - - if self._target_entity_id is not None: - self.result = self.calc_rescue( - agent, self._path_planning, self._target_entity_id - ) - - return self - - def calc_rescue( - self, - agent: AmbulanceTeam, - path_planning: PathPlanning, - target_id: EntityID, - ) -> Optional[Union[ActionMove, ActionLoad]]: - target_entity = self.world_info.get_entity(target_id) - if target_entity is None: - return None - - agent_position = agent.get_position() - if agent_position is None: - return None - if isinstance(target_entity, Human): - human = target_entity - if human.get_position() is None: - return None - if human.get_hp() is None or human.get_hp() == 0: - return None - - target_position = human.get_position() - if target_position is None: - return None - if agent_position == target_position: - if isinstance(human, Civilian) and ((human.get_buriedness() or 0) == 0): - return ActionLoad(human.get_entity_id()) - else: - path = path_planning.get_path(agent_position, target_position) - if path is not None and len(path) > 0: - return ActionMove(path) - return None - - if isinstance(target_entity, Area): - if agent_position is None: - return None - path = path_planning.get_path(agent_position, target_entity.get_entity_id()) - if path is not None and len(path) > 0: - return ActionMove(path) - + if self._target_entity_id is not None: + self.result = self.calc_rescue(agent, self._path_planning, self._target_entity_id) + + return self + + def calc_rescue( + self, + agent: AmbulanceTeam, + path_planning: PathPlanning, + target_id: EntityID, + ) -> Optional[Union[ActionMove, ActionLoad]]: + target_entity = self.world_info.get_entity(target_id) + if target_entity is None: + return None + + agent_position = agent.get_position() + if agent_position is None: + return None + if isinstance(target_entity, Human): + human = target_entity + if human.get_position() is None: + return None + if human.get_hp() is None or human.get_hp() == 0: return None - def calc_unload( - self, - agent: AmbulanceTeam, - path_planning: PathPlanning, - transport_human: Optional[Human], - target_id: Optional[EntityID], - ) -> Optional[ActionMove | ActionUnload | ActionRest]: - if transport_human is None: - return None - - if not transport_human.get_hp() or transport_human.get_hp() == 0: - return ActionUnload() - - agent_position = agent.get_position() - if agent_position is None: - return None - if target_id is None or transport_human.get_entity_id() == target_id: - position = self.world_info.get_entity(agent_position) - if position is None: - return None - - if isinstance(position, Refuge): - return ActionUnload() - else: - path = self.get_nearest_refuge_path(agent, path_planning) - if path is not None and len(path) > 0: - return ActionMove(path) - - if target_id is None: - return None - - target_entity = self.world_info.get_entity(target_id) - - if isinstance(target_entity, Human): - human = target_entity - human_position = human.get_position() - if human_position is not None: - return self.calc_refuge_action( - agent, path_planning, human_position, True - ) - path = self.get_nearest_refuge_path(agent, path_planning) - if path is not None and len(path) > 0: - return ActionMove(path) - + target_position = human.get_position() + if target_position is None: + return None + if agent_position == target_position: + if isinstance(human, Civilian) and ((human.get_buriedness() or 0) == 0): + return ActionLoad(human.get_entity_id()) + else: + path = path_planning.get_path(agent_position, target_position) + if path is not None and len(path) > 0: + return ActionMove(path) + return None + + if isinstance(target_entity, Area): + if agent_position is None: + return None + path = path_planning.get_path(agent_position, target_entity.get_entity_id()) + if path is not None and len(path) > 0: + return ActionMove(path) + + return None + + def calc_unload( + self, + agent: AmbulanceTeam, + path_planning: PathPlanning, + transport_human: Optional[Human], + target_id: Optional[EntityID], + ) -> Optional[ActionMove | ActionUnload | ActionRest]: + if transport_human is None: + return None + + if not transport_human.get_hp() or transport_human.get_hp() == 0: + return ActionUnload() + + agent_position = agent.get_position() + if agent_position is None: + return None + if target_id is None or transport_human.get_entity_id() == target_id: + position = self.world_info.get_entity(agent_position) + if position is None: return None - def calc_refuge_action( - self, - human: Human, - path_planning: PathPlanning, - target_entity_id: EntityID, - is_unload: bool, - ) -> Optional[ActionMove | ActionUnload | ActionRest]: - position = human.get_position() - if position is None: - return None - refuges = self.world_info.get_entity_ids_of_types([Refuge]) + if isinstance(position, Refuge): + return ActionUnload() + else: + path = self.get_nearest_refuge_path(agent, path_planning) + if path is not None and len(path) > 0: + return ActionMove(path) + + if target_id is None: + return None + + target_entity = self.world_info.get_entity(target_id) + + if isinstance(target_entity, Human): + human = target_entity + human_position = human.get_position() + if human_position is not None: + return self.calc_refuge_action(agent, path_planning, human_position, True) + path = self.get_nearest_refuge_path(agent, path_planning) + if path is not None and len(path) > 0: + return ActionMove(path) + + return None + + def calc_refuge_action( + self, + human: Human, + path_planning: PathPlanning, + target_entity_id: EntityID, + is_unload: bool, + ) -> Optional[ActionMove | ActionUnload | ActionRest]: + position = human.get_position() + if position is None: + return None + refuges = self.world_info.get_entity_ids_of_types([Refuge]) + size = len(refuges) + + if position in refuges: + return ActionUnload() if is_unload else ActionRest() + + first_result = None + while len(refuges) > 0: + path = path_planning.get_path(position, refuges[0]) + + if path is not None and len(path) > 0: + if first_result is None: + first_result = path.copy() + + refuge_id = path[-1] + from_refuge_to_target = path_planning.get_path(refuge_id, target_entity_id) + + if from_refuge_to_target is not None and len(from_refuge_to_target) > 0: + return ActionMove(path) + + refuges.remove(refuge_id) + if size == len(refuges): + break size = len(refuges) - - if position in refuges: - return ActionUnload() if is_unload else ActionRest() - - first_result = None - while len(refuges) > 0: - path = path_planning.get_path(position, refuges[0]) - - if path is not None and len(path) > 0: - if first_result is None: - first_result = path.copy() - - refuge_id = path[-1] - from_refuge_to_target = path_planning.get_path( - refuge_id, target_entity_id - ) - - if from_refuge_to_target is not None and len(from_refuge_to_target) > 0: - return ActionMove(path) - - refuges.remove(refuge_id) - if size == len(refuges): - break - size = len(refuges) - else: - break - - return ActionMove(first_result) if first_result is not None else None - - def get_nearest_refuge_path( - self, human: Human, path_planning: PathPlanning - ) -> list[EntityID]: - position = human.get_position() - if position is None: - return [] - refuges = self.world_info.get_entity_ids_of_types([Refuge]) - nearest_path = None - - for refuge_id in refuges: - path: list[EntityID] = path_planning.get_path(position, refuge_id) - if len(path) > 0: - if nearest_path is None or len(path) < len(nearest_path): - nearest_path = path - - return nearest_path if nearest_path is not None else [] + else: + break + + return ActionMove(first_result) if first_result is not None else None + + def get_nearest_refuge_path( + self, human: Human, path_planning: PathPlanning + ) -> list[EntityID]: + position = human.get_position() + if position is None: + return [] + refuges = self.world_info.get_entity_ids_of_types([Refuge]) + nearest_path = None + + for refuge_id in refuges: + path: list[EntityID] = path_planning.get_path(position, refuge_id) + if len(path) > 0: + if nearest_path is None or len(path) < len(nearest_path): + nearest_path = path + + return nearest_path if nearest_path is not None else [] diff --git a/src/adf_core_python/implement/centralized/default_command_executor_ambulance.py b/src/adf_core_python/implement/centralized/default_command_executor_ambulance.py index d4783c6..ffd0a7d 100644 --- a/src/adf_core_python/implement/centralized/default_command_executor_ambulance.py +++ b/src/adf_core_python/implement/centralized/default_command_executor_ambulance.py @@ -6,13 +6,13 @@ from adf_core_python.core.agent.action.common.action_rest import ActionRest from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( - CommandAmbulance, + CommandAmbulance, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( - MessageReport, + MessageReport, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -25,272 +25,268 @@ class DefaultCommandExecutorAmbulance(CommandExecutor): - ACTION_UNKNOWN: int = -1 - ACTION_REST = CommandAmbulance.ACTION_REST - ACTION_MOVE = CommandAmbulance.ACTION_MOVE - ACTION_RESCUE = CommandAmbulance.ACTION_RESCUE - ACTION_LOAD = CommandAmbulance.ACTION_LOAD - ACTION_UNLOAD = CommandAmbulance.ACTION_UNLOAD - ACTION_AUTONOMY = CommandAmbulance.ACTION_AUTONOMY + ACTION_UNKNOWN: int = -1 + ACTION_REST = CommandAmbulance.ACTION_REST + ACTION_MOVE = CommandAmbulance.ACTION_MOVE + ACTION_RESCUE = CommandAmbulance.ACTION_RESCUE + ACTION_LOAD = CommandAmbulance.ACTION_LOAD + ACTION_UNLOAD = CommandAmbulance.ACTION_UNLOAD + ACTION_AUTONOMY = CommandAmbulance.ACTION_AUTONOMY - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) - self._path_planning: PathPlanning = cast( - PathPlanning, - module_manager.get_module( - "DefaultCommandExecutorAmbulance.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - self._action_transport = module_manager.get_extend_action( - "DefaultCommandExecutorAmbulance.ExtendActionTransport", - "adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport", - ) - self._action_move = module_manager.get_extend_action( - "DefaultCommandExecutorAmbulance.ExtendActionMove", - "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", - ) + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultCommandExecutorAmbulance.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + self._action_transport = module_manager.get_extend_action( + "DefaultCommandExecutorAmbulance.ExtendActionTransport", + "adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport", + ) + self._action_move = module_manager.get_extend_action( + "DefaultCommandExecutorAmbulance.ExtendActionMove", + "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", + ) - self._command_type: int = self.ACTION_UNKNOWN - self._target: Optional[EntityID] = None - self._commander: Optional[EntityID] = None + self._command_type: int = self.ACTION_UNKNOWN + self._target: Optional[EntityID] = None + self._commander: Optional[EntityID] = None - def set_command(self, command: CommandAmbulance) -> CommandExecutor: - agent_id: EntityID = self._agent_info.get_entity_id() - if command.get_command_executor_agent_entity_id() != agent_id: - return self + def set_command(self, command: CommandAmbulance) -> CommandExecutor: + agent_id: EntityID = self._agent_info.get_entity_id() + if command.get_command_executor_agent_entity_id() != agent_id: + return self - self._command_type = command.get_execute_action() or self.ACTION_UNKNOWN - self._target = command.get_command_target_entity_id() - self._commander = command.get_sender_entity_id() - return self + self._command_type = command.get_execute_action() or self.ACTION_UNKNOWN + self._target = command.get_command_target_entity_id() + self._commander = command.get_sender_entity_id() + return self - def calculate(self) -> CommandExecutor: - self._result = None - match self._command_type: - case self.ACTION_REST: - position = self._agent_info.get_entity_id() - if self._target is None: - refuges = self._world_info.get_entity_ids_of_types([Refuge]) - if position in refuges: - self._result = ActionRest() - else: - path = self._path_planning.get_path(position, refuges[0]) - if path: - self._result = ActionMove(path) - else: - self._result = ActionRest() - return self - if position != self._target: - path = self._path_planning.get_path(position, self._target) - if path: - self._result = ActionMove(path) - return self - self._result = ActionRest() - return self - case self.ACTION_MOVE: - if self._target: - self._result = ( - self._action_move.set_target_entity_id(self._target) - .calculate() - .get_action() - ) - return self - case self.ACTION_RESCUE: - if self._target: - self._result = ( - self._action_move.set_target_entity_id(self._target) - .calculate() - .get_action() - ) - return self - case self.ACTION_LOAD: - if self._target: - self._result = ( - self._action_move.set_target_entity_id(self._target) - .calculate() - .get_action() - ) - return self - case self.ACTION_UNLOAD: - if self._target: - self._result = ( - self._action_move.set_target_entity_id(self._target) - .calculate() - .get_action() - ) - return self - case self.ACTION_AUTONOMY: - if self._target is None: - return self - target_entity = self._world_info.get_entity(self._target) - if isinstance(target_entity, Area): - if self._agent_info.some_one_on_board() is None: - self._result = ( - self._action_move.set_target_entity_id(self._target) - .calculate() - .get_action() - ) - else: - self._result = ( - self._action_transport.set_target_entity_id(self._target) - .calculate() - .get_action() - ) - elif isinstance(target_entity, Human): - self._result = ( - self._action_transport.set_target_entity_id(self._target) - .calculate() - .get_action() - ) + def calculate(self) -> CommandExecutor: + self._result = None + match self._command_type: + case self.ACTION_REST: + position = self._agent_info.get_entity_id() + if self._target is None: + refuges = self._world_info.get_entity_ids_of_types([Refuge]) + if position in refuges: + self._result = ActionRest() + else: + path = self._path_planning.get_path(position, refuges[0]) + if path: + self._result = ActionMove(path) + else: + self._result = ActionRest() + return self + if position != self._target: + path = self._path_planning.get_path(position, self._target) + if path: + self._result = ActionMove(path) + return self + self._result = ActionRest() return self + case self.ACTION_MOVE: + if self._target: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + case self.ACTION_RESCUE: + if self._target: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + case self.ACTION_LOAD: + if self._target: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + case self.ACTION_UNLOAD: + if self._target: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + case self.ACTION_AUTONOMY: + if self._target is None: + return self + target_entity = self._world_info.get_entity(self._target) + if isinstance(target_entity, Area): + if self._agent_info.some_one_on_board() is None: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + else: + self._result = ( + self._action_transport.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + elif isinstance(target_entity, Human): + self._result = ( + self._action_transport.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self - def update_info(self, message_manager: MessageManager) -> CommandExecutor: - super().update_info(message_manager) - if self.get_count_update_info() >= 2: - return self + def update_info(self, message_manager: MessageManager) -> CommandExecutor: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self - self._path_planning.update_info(message_manager) - self._action_transport.update_info(message_manager) - self._action_move.update_info(message_manager) + self._path_planning.update_info(message_manager) + self._action_transport.update_info(message_manager) + self._action_move.update_info(message_manager) - if self._is_command_completed(): - if self._command_type == self.ACTION_UNKNOWN: - return self - if self._commander is None: - return self + if self._is_command_completed(): + if self._command_type == self.ACTION_UNKNOWN: + return self + if self._commander is None: + return self - message_manager.add_message( - MessageReport( - True, - True, - False, - self._commander, - StandardMessagePriority.NORMAL, - ) - ) + message_manager.add_message( + MessageReport( + True, + True, + False, + self._commander, + StandardMessagePriority.NORMAL, + ) + ) - if self._command_type == self.ACTION_LOAD: - self._command_type = self.ACTION_UNLOAD - self._target = None - else: - self._command_type = self.ACTION_UNKNOWN - self._target = None - self._commander = None + if self._command_type == self.ACTION_LOAD: + self._command_type = self.ACTION_UNLOAD + self._target = None + else: + self._command_type = self.ACTION_UNKNOWN + self._target = None + self._commander = None - return self + return self - def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: - super().precompute(precompute_data) - if self.get_count_precompute() >= 2: - return self - self._path_planning.precompute(precompute_data) - self._action_transport.precompute(precompute_data) - self._action_move.precompute(precompute_data) - return self + def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().precompute(precompute_data) + if self.get_count_precompute() >= 2: + return self + self._path_planning.precompute(precompute_data) + self._action_transport.precompute(precompute_data) + self._action_move.precompute(precompute_data) + return self - def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: - super().resume(precompute_data) - if self.get_count_resume() >= 2: - return self - self._path_planning.resume(precompute_data) - self._action_transport.resume(precompute_data) - self._action_move.resume(precompute_data) - return self + def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + self._path_planning.resume(precompute_data) + self._action_transport.resume(precompute_data) + self._action_move.resume(precompute_data) + return self - def prepare(self) -> CommandExecutor: - super().prepare() - if self.get_count_prepare() >= 2: - return self - self._path_planning.prepare() - self._action_transport.prepare() - self._action_move.prepare() - return self + def prepare(self) -> CommandExecutor: + super().prepare() + if self.get_count_prepare() >= 2: + return self + self._path_planning.prepare() + self._action_transport.prepare() + self._action_move.prepare() + return self - def _is_command_completed(self) -> bool: - agent = self._agent_info.get_myself() - if not isinstance(agent, Human): - return False + def _is_command_completed(self) -> bool: + agent = self._agent_info.get_myself() + if not isinstance(agent, Human): + return False - match self._command_type: - case self.ACTION_REST: - if self._target is None: - return agent.get_damage() == 0 - if (target_entity := self._world_info.get_entity(self._target)) is None: - return False - if isinstance(target_entity, Refuge): - return agent.get_damage() == 0 - return False - case self.ACTION_MOVE: - return ( - self._target is None - or self._agent_info.get_position_entity_id() == self._target - ) - case self.ACTION_RESCUE: - if self._target is None: - return True - human = self._world_info.get_entity(self._target) - if not isinstance(human, Human): - return True - return human.get_buriedness() == 0 or human.get_hp() == 0 - case self.ACTION_LOAD: - if self._target is None: - return True - human = self._world_info.get_entity(self._target) - if not isinstance(human, Human): - return True - if human.get_hp() == 0: - return True - if isinstance(human, Civilian): - self._command_type = self.ACTION_RESCUE - return self._is_command_completed() - position = human.get_position() - if position is not None: - if position in self._world_info.get_entity_ids_of_types( - [AmbulanceTeam] - ): - return True - elif isinstance(self._world_info.get_entity(position), Refuge): - return True - return False + match self._command_type: + case self.ACTION_REST: + if self._target is None: + return agent.get_damage() == 0 + if (target_entity := self._world_info.get_entity(self._target)) is None: + return False + if isinstance(target_entity, Refuge): + return agent.get_damage() == 0 + return False + case self.ACTION_MOVE: + return ( + self._target is None + or self._agent_info.get_position_entity_id() == self._target + ) + case self.ACTION_RESCUE: + if self._target is None: + return True + human = self._world_info.get_entity(self._target) + if not isinstance(human, Human): + return True + return human.get_buriedness() == 0 or human.get_hp() == 0 + case self.ACTION_LOAD: + if self._target is None: + return True + human = self._world_info.get_entity(self._target) + if not isinstance(human, Human): + return True + if human.get_hp() == 0: + return True + if isinstance(human, Civilian): + self._command_type = self.ACTION_RESCUE + return self._is_command_completed() + position = human.get_position() + if position is not None: + if position in self._world_info.get_entity_ids_of_types([AmbulanceTeam]): + return True + elif isinstance(self._world_info.get_entity(position), Refuge): + return True + return False - case self.ACTION_UNLOAD: - if self._target is not None: - entity = self._world_info.get_entity(self._target) - if entity is not None and isinstance(entity, Refuge): - if self._target == self._agent_info.get_position_entity_id(): - return False - return self._agent_info.some_one_on_board() is None - case self.ACTION_AUTONOMY: - if self._target is not None: - target_entity = self._world_info.get_entity(self._target) - if isinstance(target_entity, Area): - self._command_type = ( - self._agent_info.some_one_on_board() is None - and self.ACTION_MOVE - or self.ACTION_UNLOAD - ) - return self._is_command_completed() - elif isinstance(target_entity, Human): - human = target_entity - if human.get_hp() == 0: - return True - self._command_type = ( - isinstance(human, Civilian) - and self.ACTION_LOAD - or self.ACTION_RESCUE - ) - return self._is_command_completed() - return True - case _: - return True + case self.ACTION_UNLOAD: + if self._target is not None: + entity = self._world_info.get_entity(self._target) + if entity is not None and isinstance(entity, Refuge): + if self._target == self._agent_info.get_position_entity_id(): + return False + return self._agent_info.some_one_on_board() is None + case self.ACTION_AUTONOMY: + if self._target is not None: + target_entity = self._world_info.get_entity(self._target) + if isinstance(target_entity, Area): + self._command_type = ( + self._agent_info.some_one_on_board() is None + and self.ACTION_MOVE + or self.ACTION_UNLOAD + ) + return self._is_command_completed() + elif isinstance(target_entity, Human): + human = target_entity + if human.get_hp() == 0: + return True + self._command_type = ( + isinstance(human, Civilian) and self.ACTION_LOAD or self.ACTION_RESCUE + ) + return self._is_command_completed() + return True + case _: + return True diff --git a/src/adf_core_python/implement/centralized/default_command_executor_fire.py b/src/adf_core_python/implement/centralized/default_command_executor_fire.py index 7def460..9b0451a 100644 --- a/src/adf_core_python/implement/centralized/default_command_executor_fire.py +++ b/src/adf_core_python/implement/centralized/default_command_executor_fire.py @@ -6,13 +6,13 @@ from adf_core_python.core.agent.action.common.action_rest import ActionRest from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( - CommandAmbulance, + CommandAmbulance, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( - MessageReport, + MessageReport, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -25,214 +25,214 @@ class DefaultCommandExecutorFire(CommandExecutor): - ACTION_UNKNOWN: int = -1 - ACTION_REST = CommandAmbulance.ACTION_REST - ACTION_MOVE = CommandAmbulance.ACTION_MOVE - ACTION_RESCUE = CommandAmbulance.ACTION_RESCUE - ACTION_AUTONOMY = CommandAmbulance.ACTION_AUTONOMY - - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - self._path_planning: PathPlanning = cast( - PathPlanning, - module_manager.get_module( - "DefaultCommandExecutorFire.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - self._action_fire_rescue = module_manager.get_extend_action( - "DefaultCommandExecutorFire.ExtendActionFireRescue", - "adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue", - ) - self._action_move = module_manager.get_extend_action( - "DefaultCommandExecutorFire.ExtendActionMove", - "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", - ) - - self._command_type: int = self.ACTION_UNKNOWN - self._target: Optional[EntityID] = None - self._commander: Optional[EntityID] = None - - def set_command(self, command: CommandAmbulance) -> CommandExecutor: - agent_id: EntityID = self._agent_info.get_entity_id() - if command.get_command_executor_agent_entity_id() != agent_id: + ACTION_UNKNOWN: int = -1 + ACTION_REST = CommandAmbulance.ACTION_REST + ACTION_MOVE = CommandAmbulance.ACTION_MOVE + ACTION_RESCUE = CommandAmbulance.ACTION_RESCUE + ACTION_AUTONOMY = CommandAmbulance.ACTION_AUTONOMY + + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultCommandExecutorFire.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + self._action_fire_rescue = module_manager.get_extend_action( + "DefaultCommandExecutorFire.ExtendActionFireRescue", + "adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue", + ) + self._action_move = module_manager.get_extend_action( + "DefaultCommandExecutorFire.ExtendActionMove", + "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", + ) + + self._command_type: int = self.ACTION_UNKNOWN + self._target: Optional[EntityID] = None + self._commander: Optional[EntityID] = None + + def set_command(self, command: CommandAmbulance) -> CommandExecutor: + agent_id: EntityID = self._agent_info.get_entity_id() + if command.get_command_executor_agent_entity_id() != agent_id: + return self + + self._command_type = command.get_execute_action() or self.ACTION_UNKNOWN + self._target = command.get_command_target_entity_id() + self._commander = command.get_sender_entity_id() + return self + + def calculate(self) -> CommandExecutor: + self._result = None + match self._command_type: + case self.ACTION_REST: + position = self._agent_info.get_entity_id() + if self._target is None: + refuges = self._world_info.get_entity_ids_of_types([Refuge]) + if position in refuges: + self._result = ActionRest() + else: + path = self._path_planning.get_path(position, refuges[0]) + if path: + self._result = ActionMove(path) + else: + self._result = ActionRest() + return self + if position != self._target: + path = self._path_planning.get_path(position, self._target) + if path: + self._result = ActionMove(path) return self - - self._command_type = command.get_execute_action() or self.ACTION_UNKNOWN - self._target = command.get_command_target_entity_id() - self._commander = command.get_sender_entity_id() + self._result = ActionRest() return self - - def calculate(self) -> CommandExecutor: - self._result = None - match self._command_type: - case self.ACTION_REST: - position = self._agent_info.get_entity_id() - if self._target is None: - refuges = self._world_info.get_entity_ids_of_types([Refuge]) - if position in refuges: - self._result = ActionRest() - else: - path = self._path_planning.get_path(position, refuges[0]) - if path: - self._result = ActionMove(path) - else: - self._result = ActionRest() - return self - if position != self._target: - path = self._path_planning.get_path(position, self._target) - if path: - self._result = ActionMove(path) - return self - self._result = ActionRest() - return self - case self.ACTION_MOVE: - if self._target: - self._result = ( - self._action_move.set_target_entity_id(self._target) - .calculate() - .get_action() - ) - return self - case self.ACTION_RESCUE: - if self._target: - self._result = ( - self._action_fire_rescue.set_target_entity_id(self._target) - .calculate() - .get_action() - ) - return self - case self.ACTION_AUTONOMY: - if self._target is None: - return self - target_entity = self._world_info.get_entity(self._target) - if isinstance(target_entity, Area): - if self._agent_info.some_one_on_board() is None: - self._result = ( - self._action_move.set_target_entity_id(self._target) - .calculate() - .get_action() - ) - else: - self._result = ( - self._action_move.set_target_entity_id(self._target) - .calculate() - .get_action() - ) - elif isinstance(target_entity, Human): - self._result = ( - self._action_fire_rescue.set_target_entity_id(self._target) - .calculate() - .get_action() - ) + case self.ACTION_MOVE: + if self._target: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) return self - - def update_info(self, message_manager: MessageManager) -> CommandExecutor: - super().update_info(message_manager) - if self.get_count_update_info() >= 2: - return self - - self._path_planning.update_info(message_manager) - self._action_fire_rescue.update_info(message_manager) - self._action_move.update_info(message_manager) - - if self._is_command_completed(): - if self._command_type == self.ACTION_UNKNOWN: - return self - if self._commander is None: - return self - - message_manager.add_message( - MessageReport( - True, - True, - False, - self._commander, - StandardMessagePriority.NORMAL, - ) - ) - self._command_type = self.ACTION_UNKNOWN - self._target = None - self._commander = None - - return self - - def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: - super().precompute(precompute_data) - if self.get_count_precompute() >= 2: - return self - self._path_planning.precompute(precompute_data) - self._action_fire_rescue.precompute(precompute_data) - self._action_move.precompute(precompute_data) + case self.ACTION_RESCUE: + if self._target: + self._result = ( + self._action_fire_rescue.set_target_entity_id(self._target) + .calculate() + .get_action() + ) return self - - def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: - super().resume(precompute_data) - if self.get_count_resume() >= 2: - return self - self._path_planning.resume(precompute_data) - self._action_fire_rescue.resume(precompute_data) - self._action_move.resume(precompute_data) + case self.ACTION_AUTONOMY: + if self._target is None: + return self + target_entity = self._world_info.get_entity(self._target) + if isinstance(target_entity, Area): + if self._agent_info.some_one_on_board() is None: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + else: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + elif isinstance(target_entity, Human): + self._result = ( + self._action_fire_rescue.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + + def update_info(self, message_manager: MessageManager) -> CommandExecutor: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self + + self._path_planning.update_info(message_manager) + self._action_fire_rescue.update_info(message_manager) + self._action_move.update_info(message_manager) + + if self._is_command_completed(): + if self._command_type == self.ACTION_UNKNOWN: return self - - def prepare(self) -> CommandExecutor: - super().prepare() - if self.get_count_prepare() >= 2: - return self - self._path_planning.prepare() - self._action_fire_rescue.prepare() - self._action_move.prepare() + if self._commander is None: return self - def _is_command_completed(self) -> bool: - agent = self._agent_info.get_myself() - if not isinstance(agent, Human): - return False - - match self._command_type: - case self.ACTION_REST: - if self._target is None: - return agent.get_damage() == 0 - if (target_entity := self._world_info.get_entity(self._target)) is None: - return False - if isinstance(target_entity, Refuge): - return agent.get_damage() == 0 - return False - case self.ACTION_MOVE: - return ( - self._target is None - or self._agent_info.get_position_entity_id() == self._target - ) - case self.ACTION_RESCUE: - if self._target is None: - return True - human = self._world_info.get_entity(self._target) - if not isinstance(human, Human): - return True - return human.get_buriedness() == 0 or human.get_hp() == 0 - case self.ACTION_AUTONOMY: - if self._target is not None: - target_entity = self._world_info.get_entity(self._target) - if isinstance(target_entity, Area): - self._command_type = self.ACTION_MOVE - return self._is_command_completed() - elif isinstance(target_entity, Human): - human = target_entity - if human.get_hp() == 0: - return True - if isinstance(human, Civilian): - self._command_type = self.ACTION_RESCUE - return self._is_command_completed() - return True - case _: - return True + message_manager.add_message( + MessageReport( + True, + True, + False, + self._commander, + StandardMessagePriority.NORMAL, + ) + ) + self._command_type = self.ACTION_UNKNOWN + self._target = None + self._commander = None + + return self + + def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().precompute(precompute_data) + if self.get_count_precompute() >= 2: + return self + self._path_planning.precompute(precompute_data) + self._action_fire_rescue.precompute(precompute_data) + self._action_move.precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + self._path_planning.resume(precompute_data) + self._action_fire_rescue.resume(precompute_data) + self._action_move.resume(precompute_data) + return self + + def prepare(self) -> CommandExecutor: + super().prepare() + if self.get_count_prepare() >= 2: + return self + self._path_planning.prepare() + self._action_fire_rescue.prepare() + self._action_move.prepare() + return self + + def _is_command_completed(self) -> bool: + agent = self._agent_info.get_myself() + if not isinstance(agent, Human): + return False + + match self._command_type: + case self.ACTION_REST: + if self._target is None: + return agent.get_damage() == 0 + if (target_entity := self._world_info.get_entity(self._target)) is None: + return False + if isinstance(target_entity, Refuge): + return agent.get_damage() == 0 + return False + case self.ACTION_MOVE: + return ( + self._target is None + or self._agent_info.get_position_entity_id() == self._target + ) + case self.ACTION_RESCUE: + if self._target is None: + return True + human = self._world_info.get_entity(self._target) + if not isinstance(human, Human): + return True + return human.get_buriedness() == 0 or human.get_hp() == 0 + case self.ACTION_AUTONOMY: + if self._target is not None: + target_entity = self._world_info.get_entity(self._target) + if isinstance(target_entity, Area): + self._command_type = self.ACTION_MOVE + return self._is_command_completed() + elif isinstance(target_entity, Human): + human = target_entity + if human.get_hp() == 0: + return True + if isinstance(human, Civilian): + self._command_type = self.ACTION_RESCUE + return self._is_command_completed() + return True + case _: + return True diff --git a/src/adf_core_python/implement/centralized/default_command_executor_police.py b/src/adf_core_python/implement/centralized/default_command_executor_police.py index abfb51f..94367c9 100644 --- a/src/adf_core_python/implement/centralized/default_command_executor_police.py +++ b/src/adf_core_python/implement/centralized/default_command_executor_police.py @@ -6,13 +6,13 @@ from adf_core_python.core.agent.action.common.action_rest import ActionRest from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.communication.standard.bundle.centralized.command_police import ( - CommandPolice, + CommandPolice, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( - MessageReport, + MessageReport, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -25,236 +25,236 @@ class DefaultCommandExecutorPolice(CommandExecutor): - ACTION_UNKNOWN: int = -1 - ACTION_REST = CommandPolice.ACTION_REST - ACTION_MOVE = CommandPolice.ACTION_MOVE - ACTION_CLEAR = CommandPolice.ACTION_CLEAR - ACTION_AUTONOMY = CommandPolice.ACTION_AUTONOMY + ACTION_UNKNOWN: int = -1 + ACTION_REST = CommandPolice.ACTION_REST + ACTION_MOVE = CommandPolice.ACTION_MOVE + ACTION_CLEAR = CommandPolice.ACTION_CLEAR + ACTION_AUTONOMY = CommandPolice.ACTION_AUTONOMY - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) - self._path_planning: PathPlanning = cast( - PathPlanning, - module_manager.get_module( - "DefaultCommandExecutorPolice.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - self._action_clear = module_manager.get_extend_action( - "DefaultCommandExecutorPolice.ExtendActionClear", - "adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear", - ) - self._action_move = module_manager.get_extend_action( - "DefaultCommandExecutorPolice.ExtendActionMove", - "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", - ) + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultCommandExecutorPolice.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + self._action_clear = module_manager.get_extend_action( + "DefaultCommandExecutorPolice.ExtendActionClear", + "adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear", + ) + self._action_move = module_manager.get_extend_action( + "DefaultCommandExecutorPolice.ExtendActionMove", + "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", + ) - self._command_type: int = self.ACTION_UNKNOWN - self._target: Optional[EntityID] = None - self._commander: Optional[EntityID] = None + self._command_type: int = self.ACTION_UNKNOWN + self._target: Optional[EntityID] = None + self._commander: Optional[EntityID] = None - def set_command(self, command: CommandPolice) -> CommandExecutor: - agent_id: EntityID = self._agent_info.get_entity_id() - if command.get_command_executor_agent_entity_id() != agent_id: - return self + def set_command(self, command: CommandPolice) -> CommandExecutor: + agent_id: EntityID = self._agent_info.get_entity_id() + if command.get_command_executor_agent_entity_id() != agent_id: + return self - self._command_type = command.get_execute_action() or self.ACTION_UNKNOWN - self._target = command.get_command_target_entity_id() - self._commander = command.get_sender_entity_id() - return self + self._command_type = command.get_execute_action() or self.ACTION_UNKNOWN + self._target = command.get_command_target_entity_id() + self._commander = command.get_sender_entity_id() + return self - def calculate(self) -> CommandExecutor: - self._result = None - match self._command_type: - case self.ACTION_REST: - position = self._agent_info.get_entity_id() - if self._target is None: - refuges = self._world_info.get_entity_ids_of_types([Refuge]) - if position in refuges: - self._result = ActionRest() - else: - path = self._path_planning.get_path(position, refuges[0]) - if path: - self._result = ActionMove(path) - else: - self._result = ActionRest() - return self - if position != self._target: - path = self._path_planning.get_path(position, self._target) - if path: - self._result = ActionMove(path) - return self - self._result = ActionRest() - return self - case self.ACTION_MOVE: - if self._target: - self._result = ( - self._action_move.set_target_entity_id(self._target) - .calculate() - .get_action() - ) - return self - case self.ACTION_CLEAR: - if self._target: - self._result = ( - self._action_clear.set_target_entity_id(self._target) - .calculate() - .get_action() - ) - return self - case self.ACTION_AUTONOMY: - if self._target is None: - return self - target_entity = self._world_info.get_entity(self._target) - if isinstance(target_entity, Area): - if self._agent_info.some_one_on_board() is None: - self._result = ( - self._action_move.set_target_entity_id(self._target) - .calculate() - .get_action() - ) - else: - self._result = ( - self._action_move.set_target_entity_id(self._target) - .calculate() - .get_action() - ) - elif isinstance(target_entity, Human): - self._result = ( - self._action_clear.set_target_entity_id(self._target) - .calculate() - .get_action() - ) + def calculate(self) -> CommandExecutor: + self._result = None + match self._command_type: + case self.ACTION_REST: + position = self._agent_info.get_entity_id() + if self._target is None: + refuges = self._world_info.get_entity_ids_of_types([Refuge]) + if position in refuges: + self._result = ActionRest() + else: + path = self._path_planning.get_path(position, refuges[0]) + if path: + self._result = ActionMove(path) + else: + self._result = ActionRest() + return self + if position != self._target: + path = self._path_planning.get_path(position, self._target) + if path: + self._result = ActionMove(path) + return self + self._result = ActionRest() return self + case self.ACTION_MOVE: + if self._target: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + case self.ACTION_CLEAR: + if self._target: + self._result = ( + self._action_clear.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + case self.ACTION_AUTONOMY: + if self._target is None: + return self + target_entity = self._world_info.get_entity(self._target) + if isinstance(target_entity, Area): + if self._agent_info.some_one_on_board() is None: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + else: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + elif isinstance(target_entity, Human): + self._result = ( + self._action_clear.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self - def update_info(self, message_manager: MessageManager) -> CommandExecutor: - super().update_info(message_manager) - if self.get_count_update_info() >= 2: - return self + def update_info(self, message_manager: MessageManager) -> CommandExecutor: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self - self._path_planning.update_info(message_manager) - self._action_clear.update_info(message_manager) - self._action_move.update_info(message_manager) + self._path_planning.update_info(message_manager) + self._action_clear.update_info(message_manager) + self._action_move.update_info(message_manager) - if self._is_command_completed(): - if self._command_type == self.ACTION_UNKNOWN: - return self - if self._commander is None: - return self + if self._is_command_completed(): + if self._command_type == self.ACTION_UNKNOWN: + return self + if self._commander is None: + return self - message_manager.add_message( - MessageReport( - True, - True, - False, - self._commander, - StandardMessagePriority.NORMAL, - ) - ) - self._command_type = self.ACTION_UNKNOWN - self._target = None - self._commander = None + message_manager.add_message( + MessageReport( + True, + True, + False, + self._commander, + StandardMessagePriority.NORMAL, + ) + ) + self._command_type = self.ACTION_UNKNOWN + self._target = None + self._commander = None - return self + return self - def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: - super().precompute(precompute_data) - if self.get_count_precompute() >= 2: - return self - self._path_planning.precompute(precompute_data) - self._action_clear.precompute(precompute_data) - self._action_move.precompute(precompute_data) - return self + def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().precompute(precompute_data) + if self.get_count_precompute() >= 2: + return self + self._path_planning.precompute(precompute_data) + self._action_clear.precompute(precompute_data) + self._action_move.precompute(precompute_data) + return self - def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: - super().resume(precompute_data) - if self.get_count_resume() >= 2: - return self - self._path_planning.resume(precompute_data) - self._action_clear.resume(precompute_data) - self._action_move.resume(precompute_data) - return self + def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + self._path_planning.resume(precompute_data) + self._action_clear.resume(precompute_data) + self._action_move.resume(precompute_data) + return self - def prepare(self) -> CommandExecutor: - super().prepare() - if self.get_count_prepare() >= 2: - return self - self._path_planning.prepare() - self._action_clear.prepare() - self._action_move.prepare() - return self + def prepare(self) -> CommandExecutor: + super().prepare() + if self.get_count_prepare() >= 2: + return self + self._path_planning.prepare() + self._action_clear.prepare() + self._action_move.prepare() + return self - def _is_command_completed(self) -> bool: - agent = self._agent_info.get_myself() - if not isinstance(agent, Human): - return False + def _is_command_completed(self) -> bool: + agent = self._agent_info.get_myself() + if not isinstance(agent, Human): + return False - match self._command_type: - case self.ACTION_REST: - if self._target is None: - damage = agent.get_damage() - return damage is not None and damage == 0 - if (target_entity := self._world_info.get_entity(self._target)) is None: - return False - if isinstance(target_entity, Refuge): - damage = agent.get_damage() - return damage is not None and damage == 0 - return False - case self.ACTION_MOVE: - return ( - self._target is None - or self._agent_info.get_position_entity_id() == self._target - ) - case self.ACTION_CLEAR: - if self._target is None: - return True - entity = self._world_info.get_entity(self._target) - if isinstance(entity, Road): - blockades = entity.get_blockades() - if blockades is not None: - return len(blockades) == 0 - return self._agent_info.get_position_entity_id() == self._target - return True - case self.ACTION_AUTONOMY: - if self._target is not None: - target_entity = self._world_info.get_entity(self._target) - if isinstance(target_entity, Refuge): - damage = agent.get_damage() - self._command_type = ( - self.ACTION_REST - if damage is not None and damage > 0 - else self.ACTION_CLEAR - ) - return self._is_command_completed() - elif isinstance(target_entity, Area): - self._command_type = self.ACTION_CLEAR - return self._is_command_completed() - elif isinstance(target_entity, Human): - if target_entity.get_hp() == 0: - return True - position = target_entity.get_position() - if position is not None and isinstance( - self._world_info.get_entity(position), - Area, - ): - self._target = target_entity.get_position() - self._command_type = self.ACTION_CLEAR - return self._is_command_completed() - elif isinstance(target_entity, Blockade): - if target_entity.get_position() is not None: - self._target = target_entity.get_position() - self._command_type = self.ACTION_CLEAR - return self._is_command_completed() - return True - case _: - return True + match self._command_type: + case self.ACTION_REST: + if self._target is None: + damage = agent.get_damage() + return damage is not None and damage == 0 + if (target_entity := self._world_info.get_entity(self._target)) is None: + return False + if isinstance(target_entity, Refuge): + damage = agent.get_damage() + return damage is not None and damage == 0 + return False + case self.ACTION_MOVE: + return ( + self._target is None + or self._agent_info.get_position_entity_id() == self._target + ) + case self.ACTION_CLEAR: + if self._target is None: + return True + entity = self._world_info.get_entity(self._target) + if isinstance(entity, Road): + blockades = entity.get_blockades() + if blockades is not None: + return len(blockades) == 0 + return self._agent_info.get_position_entity_id() == self._target + return True + case self.ACTION_AUTONOMY: + if self._target is not None: + target_entity = self._world_info.get_entity(self._target) + if isinstance(target_entity, Refuge): + damage = agent.get_damage() + self._command_type = ( + self.ACTION_REST + if damage is not None and damage > 0 + else self.ACTION_CLEAR + ) + return self._is_command_completed() + elif isinstance(target_entity, Area): + self._command_type = self.ACTION_CLEAR + return self._is_command_completed() + elif isinstance(target_entity, Human): + if target_entity.get_hp() == 0: + return True + position = target_entity.get_position() + if position is not None and isinstance( + self._world_info.get_entity(position), + Area, + ): + self._target = target_entity.get_position() + self._command_type = self.ACTION_CLEAR + return self._is_command_completed() + elif isinstance(target_entity, Blockade): + if target_entity.get_position() is not None: + self._target = target_entity.get_position() + self._command_type = self.ACTION_CLEAR + return self._is_command_completed() + return True + case _: + return True diff --git a/src/adf_core_python/implement/centralized/default_command_executor_scout.py b/src/adf_core_python/implement/centralized/default_command_executor_scout.py index 7941ed5..8693cdf 100644 --- a/src/adf_core_python/implement/centralized/default_command_executor_scout.py +++ b/src/adf_core_python/implement/centralized/default_command_executor_scout.py @@ -5,13 +5,13 @@ from adf_core_python.core.agent.action.common.action_move import ActionMove from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( - CommandScout, + CommandScout, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( - MessageReport, + MessageReport, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -24,139 +24,136 @@ class DefaultCommandExecutorScout(CommandExecutor): - ACTION_UNKNOWN: int = -1 - ACTION_SCOUT: int = 1 - - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - self._path_planning: PathPlanning = cast( - PathPlanning, - module_manager.get_module( - "DefaultCommandExecutorScout.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - - self._command_type: int = self.ACTION_UNKNOWN - self._targets: list[EntityID] = [] - self._commander: Optional[EntityID] = None - - def set_command(self, command: CommandScout) -> CommandExecutor: - agent_id: EntityID = self._agent_info.get_entity_id() - if command.get_command_executor_agent_entity_id() != agent_id: - return self - - target = command.get_command_target_entity_id() - if target is None: - target = self._agent_info.get_position_entity_id() - if target is None: - return self - - self._command_type = self.ACTION_SCOUT - self._commander = command.get_sender_entity_id() - self._targets = [] - if (scout_distance := command.get_scout_range()) is None: - return self - - for entity in self._world_info.get_entities_of_types([Road, Building]): - if isinstance(entity, Refuge): - continue - if ( - self._world_info.get_distance(target, entity.get_entity_id()) - <= scout_distance - ): - self._targets.append(entity.get_entity_id()) - + ACTION_UNKNOWN: int = -1 + ACTION_SCOUT: int = 1 + + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultCommandExecutorScout.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + + self._command_type: int = self.ACTION_UNKNOWN + self._targets: list[EntityID] = [] + self._commander: Optional[EntityID] = None + + def set_command(self, command: CommandScout) -> CommandExecutor: + agent_id: EntityID = self._agent_info.get_entity_id() + if command.get_command_executor_agent_entity_id() != agent_id: + return self + + target = command.get_command_target_entity_id() + if target is None: + target = self._agent_info.get_position_entity_id() + if target is None: return self - def calculate(self) -> CommandExecutor: - self._result = None - match self._command_type: - case self.ACTION_SCOUT: - if len(self._targets) == 0: - return self - agent_position = self._agent_info.get_position_entity_id() - if agent_position is None: - return self - path = self._path_planning.get_path(agent_position, self._targets[0]) - if path is None: - return self - self._result = ActionMove(path) - case _: - return self + self._command_type = self.ACTION_SCOUT + self._commander = command.get_sender_entity_id() + self._targets = [] + if (scout_distance := command.get_scout_range()) is None: + return self + + for entity in self._world_info.get_entities_of_types([Road, Building]): + if isinstance(entity, Refuge): + continue + if ( + self._world_info.get_distance(target, entity.get_entity_id()) <= scout_distance + ): + self._targets.append(entity.get_entity_id()) + + return self + + def calculate(self) -> CommandExecutor: + self._result = None + match self._command_type: + case self.ACTION_SCOUT: + if len(self._targets) == 0: + return self + agent_position = self._agent_info.get_position_entity_id() + if agent_position is None: + return self + path = self._path_planning.get_path(agent_position, self._targets[0]) + if path is None: + return self + self._result = ActionMove(path) + case _: return self + return self - def update_info(self, message_manager: MessageManager) -> CommandExecutor: - super().update_info(message_manager) - if self.get_count_update_info() >= 2: - return self - - self._path_planning.update_info(message_manager) - - if self._is_command_completed(): - if self._command_type == self.ACTION_UNKNOWN: - return self - if self._commander is None: - return self - - message_manager.add_message( - MessageReport( - True, - True, - False, - self._commander, - StandardMessagePriority.NORMAL, - ) - ) - self._command_type = self.ACTION_UNKNOWN - self._target = None - self._commander = None + def update_info(self, message_manager: MessageManager) -> CommandExecutor: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self - return self + self._path_planning.update_info(message_manager) - def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: - super().precompute(precompute_data) - if self.get_count_precompute() >= 2: - return self - self._path_planning.precompute(precompute_data) + if self._is_command_completed(): + if self._command_type == self.ACTION_UNKNOWN: return self - - def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: - super().resume(precompute_data) - if self.get_count_resume() >= 2: - return self - self._path_planning.resume(precompute_data) + if self._commander is None: return self - def prepare(self) -> CommandExecutor: - super().prepare() - if self.get_count_prepare() >= 2: - return self - self._path_planning.prepare() - return self - - def _is_command_completed(self) -> bool: - agent = self._agent_info.get_myself() - if not isinstance(agent, Human): - return False - - match self._command_type: - case self.ACTION_SCOUT: - if len(self._targets) != 0: - for entity in self._world_info.get_entities_of_types( - [Road, Building] - ): - self._targets.remove(entity.get_entity_id()) - return len(self._targets) == 0 - case _: - return True + message_manager.add_message( + MessageReport( + True, + True, + False, + self._commander, + StandardMessagePriority.NORMAL, + ) + ) + self._command_type = self.ACTION_UNKNOWN + self._target = None + self._commander = None + + return self + + def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().precompute(precompute_data) + if self.get_count_precompute() >= 2: + return self + self._path_planning.precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + self._path_planning.resume(precompute_data) + return self + + def prepare(self) -> CommandExecutor: + super().prepare() + if self.get_count_prepare() >= 2: + return self + self._path_planning.prepare() + return self + + def _is_command_completed(self) -> bool: + agent = self._agent_info.get_myself() + if not isinstance(agent, Human): + return False + + match self._command_type: + case self.ACTION_SCOUT: + if len(self._targets) != 0: + for entity in self._world_info.get_entities_of_types([Road, Building]): + self._targets.remove(entity.get_entity_id()) + return len(self._targets) == 0 + case _: + return True diff --git a/src/adf_core_python/implement/centralized/default_command_executor_scout_police.py b/src/adf_core_python/implement/centralized/default_command_executor_scout_police.py index f1806ad..00d4f47 100644 --- a/src/adf_core_python/implement/centralized/default_command_executor_scout_police.py +++ b/src/adf_core_python/implement/centralized/default_command_executor_scout_police.py @@ -5,13 +5,13 @@ from adf_core_python.core.agent.action.common.action_move import ActionMove from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( - CommandScout, + CommandScout, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( - MessageReport, + MessageReport, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -25,150 +25,149 @@ class DefaultCommandExecutorScoutPolice(CommandExecutor): - ACTION_UNKNOWN: int = -1 - ACTION_SCOUT: int = 1 - - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - self._path_planning: PathPlanning = cast( - PathPlanning, - module_manager.get_module( - "DefaultCommandExecutorScoutPolice.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - self._action_clear: ExtendAction = module_manager.get_extend_action( - "DefaultCommandExecutorScoutPolice.ExtendActionClear", - "adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear", - ) - - self._command_type: int = self.ACTION_UNKNOWN - self._targets: list[EntityID] = [] - self._commander: Optional[EntityID] = None - - def set_command(self, command: CommandScout) -> CommandExecutor: - agent_id: EntityID = self._agent_info.get_entity_id() - if command.get_command_executor_agent_entity_id() != agent_id: - return self - - target = command.get_command_target_entity_id() - if target is None: - target = self._agent_info.get_position_entity_id() - if target is None: - return self - - self._command_type = self.ACTION_SCOUT - self._commander = command.get_sender_entity_id() - self._targets = [] - if (scout_distance := command.get_scout_range()) is None: - return self - - for entity in self._world_info.get_entities_of_types([Road, Building, Refuge]): - if isinstance(entity, Refuge): - continue - if ( - self._world_info.get_distance(target, entity.get_entity_id()) - <= scout_distance - ): - self._targets.append(entity.get_entity_id()) - + ACTION_UNKNOWN: int = -1 + ACTION_SCOUT: int = 1 + + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultCommandExecutorScoutPolice.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + self._action_clear: ExtendAction = module_manager.get_extend_action( + "DefaultCommandExecutorScoutPolice.ExtendActionClear", + "adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear", + ) + + self._command_type: int = self.ACTION_UNKNOWN + self._targets: list[EntityID] = [] + self._commander: Optional[EntityID] = None + + def set_command(self, command: CommandScout) -> CommandExecutor: + agent_id: EntityID = self._agent_info.get_entity_id() + if command.get_command_executor_agent_entity_id() != agent_id: + return self + + target = command.get_command_target_entity_id() + if target is None: + target = self._agent_info.get_position_entity_id() + if target is None: return self - def calculate(self) -> CommandExecutor: - self._result = None - match self._command_type: - case self.ACTION_SCOUT: - if len(self._targets) == 0: - return self - agent_position = self._agent_info.get_position_entity_id() - if agent_position is None: - return self - path = self._path_planning.get_path(agent_position, self._targets[0]) - if path is None: - return self - action = ( - self._action_clear.set_target_entity_id(self._targets[0]) - .calculate() - .get_action() - ) - if action is None: - action = ActionMove(path) - self._result = action - case _: - return self + self._command_type = self.ACTION_SCOUT + self._commander = command.get_sender_entity_id() + self._targets = [] + if (scout_distance := command.get_scout_range()) is None: + return self + + for entity in self._world_info.get_entities_of_types([Road, Building, Refuge]): + if isinstance(entity, Refuge): + continue + if ( + self._world_info.get_distance(target, entity.get_entity_id()) <= scout_distance + ): + self._targets.append(entity.get_entity_id()) + + return self + + def calculate(self) -> CommandExecutor: + self._result = None + match self._command_type: + case self.ACTION_SCOUT: + if len(self._targets) == 0: + return self + agent_position = self._agent_info.get_position_entity_id() + if agent_position is None: + return self + path = self._path_planning.get_path(agent_position, self._targets[0]) + if path is None: + return self + action = ( + self._action_clear.set_target_entity_id(self._targets[0]) + .calculate() + .get_action() + ) + if action is None: + action = ActionMove(path) + self._result = action + case _: return self + return self - def update_info(self, message_manager: MessageManager) -> CommandExecutor: - super().update_info(message_manager) - if self.get_count_update_info() >= 2: - return self - - self._path_planning.update_info(message_manager) - - if self._is_command_completed(): - if self._command_type == self.ACTION_UNKNOWN: - return self - if self._commander is None: - return self - - message_manager.add_message( - MessageReport( - True, - True, - False, - self._commander, - StandardMessagePriority.NORMAL, - ) - ) - self._command_type = self.ACTION_UNKNOWN - self._target = None - self._commander = None - - return self + def update_info(self, message_manager: MessageManager) -> CommandExecutor: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self - def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: - super().precompute(precompute_data) - if self.get_count_precompute() >= 2: - return self - self._path_planning.precompute(precompute_data) - return self + self._path_planning.update_info(message_manager) - def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: - super().resume(precompute_data) - if self.get_count_resume() >= 2: - return self - self._path_planning.resume(precompute_data) + if self._is_command_completed(): + if self._command_type == self.ACTION_UNKNOWN: return self - - def prepare(self) -> CommandExecutor: - super().prepare() - if self.get_count_prepare() >= 2: - return self - self._path_planning.prepare() + if self._commander is None: return self - def _is_command_completed(self) -> bool: - agent = self._agent_info.get_myself() - if not isinstance(agent, Human): - return False - - match self._command_type: - case self.ACTION_SCOUT: - if len(self._targets) != 0: - for entity in self._world_info.get_entities_of_types( - [Road, Building, Refuge] - ): - self._targets.remove(entity.get_entity_id()) - return len(self._targets) == 0 - case _: - return True + message_manager.add_message( + MessageReport( + True, + True, + False, + self._commander, + StandardMessagePriority.NORMAL, + ) + ) + self._command_type = self.ACTION_UNKNOWN + self._target = None + self._commander = None + + return self + + def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().precompute(precompute_data) + if self.get_count_precompute() >= 2: + return self + self._path_planning.precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + self._path_planning.resume(precompute_data) + return self + + def prepare(self) -> CommandExecutor: + super().prepare() + if self.get_count_prepare() >= 2: + return self + self._path_planning.prepare() + return self + + def _is_command_completed(self) -> bool: + agent = self._agent_info.get_myself() + if not isinstance(agent, Human): + return False + + match self._command_type: + case self.ACTION_SCOUT: + if len(self._targets) != 0: + for entity in self._world_info.get_entities_of_types( + [Road, Building, Refuge] + ): + self._targets.remove(entity.get_entity_id()) + return len(self._targets) == 0 + case _: + return True diff --git a/src/adf_core_python/implement/centralized/default_command_picker_ambulance.py b/src/adf_core_python/implement/centralized/default_command_picker_ambulance.py index edddf4e..753b188 100644 --- a/src/adf_core_python/implement/centralized/default_command_picker_ambulance.py +++ b/src/adf_core_python/implement/centralized/default_command_picker_ambulance.py @@ -3,13 +3,13 @@ from rcrscore.entities import AmbulanceTeam, Area, EntityID, Human from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( - CommandAmbulance, + CommandAmbulance, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( - CommandScout, + CommandScout, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -18,69 +18,69 @@ from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.component.centralized.command_picker import CommandPicker from adf_core_python.core.component.communication.communication_message import ( - CommunicationMessage, + CommunicationMessage, ) class DefaultCommandPickerAmbulance(CommandPicker): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - self.messages: list[CommunicationMessage] = [] - self.scout_distance: int = 40000 - self.allocation_data: dict[EntityID, EntityID] = {} + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self.messages: list[CommunicationMessage] = [] + self.scout_distance: int = 40000 + self.allocation_data: dict[EntityID, EntityID] = {} - def set_allocator_result( - self, allocation_data: dict[EntityID, EntityID] - ) -> CommandPicker: - self.allocation_data = allocation_data - return self + def set_allocator_result( + self, allocation_data: dict[EntityID, EntityID] + ) -> CommandPicker: + self.allocation_data = allocation_data + return self - def calculate(self) -> CommandPicker: - self.messages.clear() - if not self.allocation_data: - return self + def calculate(self) -> CommandPicker: + self.messages.clear() + if not self.allocation_data: + return self - for ambulance_id in self.allocation_data.keys(): - agent = self._world_info.get_entity(ambulance_id) - if agent is None or not isinstance(agent, AmbulanceTeam): - continue + for ambulance_id in self.allocation_data.keys(): + agent = self._world_info.get_entity(ambulance_id) + if agent is None or not isinstance(agent, AmbulanceTeam): + continue - target = self._world_info.get_entity(self.allocation_data[ambulance_id]) - if target is None: - continue + target = self._world_info.get_entity(self.allocation_data[ambulance_id]) + if target is None: + continue - command: CommunicationMessage - if isinstance(target, Human): - command = CommandAmbulance( - True, - ambulance_id, - self._agent_info.get_entity_id(), - CommandAmbulance.ACTION_RESCUE, - StandardMessagePriority.NORMAL, - target.get_entity_id(), - ) - self.messages.append(command) + command: CommunicationMessage + if isinstance(target, Human): + command = CommandAmbulance( + True, + ambulance_id, + self._agent_info.get_entity_id(), + CommandAmbulance.ACTION_RESCUE, + StandardMessagePriority.NORMAL, + target.get_entity_id(), + ) + self.messages.append(command) - if isinstance(target, Area): - command = CommandScout( - True, - ambulance_id, - self._agent_info.get_entity_id(), - self.scout_distance, - StandardMessagePriority.NORMAL, - target.get_entity_id(), - ) - self.messages.append(command) - return self + if isinstance(target, Area): + command = CommandScout( + True, + ambulance_id, + self._agent_info.get_entity_id(), + self.scout_distance, + StandardMessagePriority.NORMAL, + target.get_entity_id(), + ) + self.messages.append(command) + return self - def get_result(self) -> list[CommunicationMessage]: - return self.messages + def get_result(self) -> list[CommunicationMessage]: + return self.messages diff --git a/src/adf_core_python/implement/centralized/default_command_picker_fire.py b/src/adf_core_python/implement/centralized/default_command_picker_fire.py index a5c8017..b7ec038 100644 --- a/src/adf_core_python/implement/centralized/default_command_picker_fire.py +++ b/src/adf_core_python/implement/centralized/default_command_picker_fire.py @@ -3,13 +3,13 @@ from rcrscore.entities import Area, EntityID, FireBrigade, Human from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( - CommandAmbulance, + CommandAmbulance, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( - CommandScout, + CommandScout, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -18,69 +18,69 @@ from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.component.centralized.command_picker import CommandPicker from adf_core_python.core.component.communication.communication_message import ( - CommunicationMessage, + CommunicationMessage, ) class DefaultCommandPickerFire(CommandPicker): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - self.messages: list[CommunicationMessage] = [] - self.scout_distance: int = 40000 - self.allocation_data: dict[EntityID, EntityID] = {} + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self.messages: list[CommunicationMessage] = [] + self.scout_distance: int = 40000 + self.allocation_data: dict[EntityID, EntityID] = {} - def set_allocator_result( - self, allocation_data: dict[EntityID, EntityID] - ) -> CommandPicker: - self.allocation_data = allocation_data - return self + def set_allocator_result( + self, allocation_data: dict[EntityID, EntityID] + ) -> CommandPicker: + self.allocation_data = allocation_data + return self - def calculate(self) -> CommandPicker: - self.messages.clear() - if not self.allocation_data: - return self + def calculate(self) -> CommandPicker: + self.messages.clear() + if not self.allocation_data: + return self - for ambulance_id in self.allocation_data.keys(): - agent = self._world_info.get_entity(ambulance_id) - if agent is None or not isinstance(agent, FireBrigade): - continue + for ambulance_id in self.allocation_data.keys(): + agent = self._world_info.get_entity(ambulance_id) + if agent is None or not isinstance(agent, FireBrigade): + continue - target = self._world_info.get_entity(self.allocation_data[ambulance_id]) - if target is None: - continue + target = self._world_info.get_entity(self.allocation_data[ambulance_id]) + if target is None: + continue - command: CommunicationMessage - if isinstance(target, Human): - command = CommandAmbulance( - True, - ambulance_id, - self._agent_info.get_entity_id(), - CommandAmbulance.ACTION_RESCUE, - StandardMessagePriority.NORMAL, - target.get_entity_id(), - ) - self.messages.append(command) + command: CommunicationMessage + if isinstance(target, Human): + command = CommandAmbulance( + True, + ambulance_id, + self._agent_info.get_entity_id(), + CommandAmbulance.ACTION_RESCUE, + StandardMessagePriority.NORMAL, + target.get_entity_id(), + ) + self.messages.append(command) - if isinstance(target, Area): - command = CommandScout( - True, - ambulance_id, - self._agent_info.get_entity_id(), - self.scout_distance, - StandardMessagePriority.NORMAL, - target.get_entity_id(), - ) - self.messages.append(command) - return self + if isinstance(target, Area): + command = CommandScout( + True, + ambulance_id, + self._agent_info.get_entity_id(), + self.scout_distance, + StandardMessagePriority.NORMAL, + target.get_entity_id(), + ) + self.messages.append(command) + return self - def get_result(self) -> list[CommunicationMessage]: - return self.messages + def get_result(self) -> list[CommunicationMessage]: + return self.messages diff --git a/src/adf_core_python/implement/centralized/default_command_picker_police.py b/src/adf_core_python/implement/centralized/default_command_picker_police.py index 0dbde23..f6be460 100644 --- a/src/adf_core_python/implement/centralized/default_command_picker_police.py +++ b/src/adf_core_python/implement/centralized/default_command_picker_police.py @@ -3,10 +3,10 @@ from rcrscore.entities import Area, EntityID, PoliceForce from adf_core_python.core.agent.communication.standard.bundle.centralized.command_police import ( - CommandPolice, + CommandPolice, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -15,56 +15,56 @@ from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.component.centralized.command_picker import CommandPicker from adf_core_python.core.component.communication.communication_message import ( - CommunicationMessage, + CommunicationMessage, ) class DefaultCommandPickerPolice(CommandPicker): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - self.messages: list[CommunicationMessage] = [] - self.allocation_data: dict[EntityID, EntityID] = {} + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self.messages: list[CommunicationMessage] = [] + self.allocation_data: dict[EntityID, EntityID] = {} - def set_allocator_result( - self, allocation_data: dict[EntityID, EntityID] - ) -> CommandPicker: - self.allocation_data = allocation_data - return self + def set_allocator_result( + self, allocation_data: dict[EntityID, EntityID] + ) -> CommandPicker: + self.allocation_data = allocation_data + return self - def calculate(self) -> CommandPicker: - self.messages.clear() - if not self.allocation_data: - return self + def calculate(self) -> CommandPicker: + self.messages.clear() + if not self.allocation_data: + return self - for ambulance_id in self.allocation_data.keys(): - agent = self._world_info.get_entity(ambulance_id) - if agent is None or not isinstance(agent, PoliceForce): - continue + for ambulance_id in self.allocation_data.keys(): + agent = self._world_info.get_entity(ambulance_id) + if agent is None or not isinstance(agent, PoliceForce): + continue - target = self._world_info.get_entity(self.allocation_data[ambulance_id]) - if target is None: - continue + target = self._world_info.get_entity(self.allocation_data[ambulance_id]) + if target is None: + continue - if isinstance(target, Area): - command = CommandPolice( - True, - agent.get_entity_id(), - self._agent_info.get_entity_id(), - CommandPolice.ACTION_AUTONOMY, - StandardMessagePriority.NORMAL, - target.get_entity_id(), - ) - self.messages.append(command) - return self + if isinstance(target, Area): + command = CommandPolice( + True, + agent.get_entity_id(), + self._agent_info.get_entity_id(), + CommandPolice.ACTION_AUTONOMY, + StandardMessagePriority.NORMAL, + target.get_entity_id(), + ) + self.messages.append(command) + return self - def get_result(self) -> list[CommunicationMessage]: - return self.messages + def get_result(self) -> list[CommunicationMessage]: + return self.messages diff --git a/src/adf_core_python/implement/default_loader.py b/src/adf_core_python/implement/default_loader.py index f3af8f8..9b63888 100644 --- a/src/adf_core_python/implement/default_loader.py +++ b/src/adf_core_python/implement/default_loader.py @@ -1,57 +1,57 @@ from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.component.tactics.tactics_ambulance_center import ( - TacticsAmbulanceCenter, + TacticsAmbulanceCenter, ) from adf_core_python.core.component.tactics.tactics_ambulance_team import ( - TacticsAmbulanceTeam, + TacticsAmbulanceTeam, ) from adf_core_python.core.component.tactics.tactics_fire_brigade import ( - TacticsFireBrigade, + TacticsFireBrigade, ) from adf_core_python.core.component.tactics.tactics_fire_station import ( - TacticsFireStation, + TacticsFireStation, ) from adf_core_python.core.component.tactics.tactics_police_force import ( - TacticsPoliceForce, + TacticsPoliceForce, ) from adf_core_python.core.component.tactics.tactics_police_office import ( - TacticsPoliceOffice, + TacticsPoliceOffice, ) from adf_core_python.implement.tactics.default_tactics_ambulance_center import ( - DefaultTacticsAmbulanceCenter, + DefaultTacticsAmbulanceCenter, ) from adf_core_python.implement.tactics.default_tactics_ambulance_team import ( - DefaultTacticsAmbulanceTeam, + DefaultTacticsAmbulanceTeam, ) from adf_core_python.implement.tactics.default_tactics_fire_brigade import ( - DefaultTacticsFireBrigade, + DefaultTacticsFireBrigade, ) from adf_core_python.implement.tactics.default_tactics_fire_station import ( - DefaultTacticsFireStation, + DefaultTacticsFireStation, ) from adf_core_python.implement.tactics.default_tactics_police_force import ( - DefaultTacticsPoliceForce, + DefaultTacticsPoliceForce, ) from adf_core_python.implement.tactics.default_tactics_police_office import ( - DefaultTacticsPoliceOffice, + DefaultTacticsPoliceOffice, ) class DefaultLoader(AbstractLoader): - def get_tactics_ambulance_team(self) -> TacticsAmbulanceTeam: - return DefaultTacticsAmbulanceTeam() + def get_tactics_ambulance_team(self) -> TacticsAmbulanceTeam: + return DefaultTacticsAmbulanceTeam() - def get_tactics_fire_brigade(self) -> TacticsFireBrigade: - return DefaultTacticsFireBrigade() + def get_tactics_fire_brigade(self) -> TacticsFireBrigade: + return DefaultTacticsFireBrigade() - def get_tactics_police_force(self) -> TacticsPoliceForce: - return DefaultTacticsPoliceForce() + def get_tactics_police_force(self) -> TacticsPoliceForce: + return DefaultTacticsPoliceForce() - def get_tactics_ambulance_center(self) -> TacticsAmbulanceCenter: - return DefaultTacticsAmbulanceCenter() + def get_tactics_ambulance_center(self) -> TacticsAmbulanceCenter: + return DefaultTacticsAmbulanceCenter() - def get_tactics_fire_station(self) -> TacticsFireStation: - return DefaultTacticsFireStation() + def get_tactics_fire_station(self) -> TacticsFireStation: + return DefaultTacticsFireStation() - def get_tactics_police_office(self) -> TacticsPoliceOffice: - return DefaultTacticsPoliceOffice() + def get_tactics_police_office(self) -> TacticsPoliceOffice: + return DefaultTacticsPoliceOffice() diff --git a/src/adf_core_python/implement/module/algorithm/a_star_path_planning.py b/src/adf_core_python/implement/module/algorithm/a_star_path_planning.py index e31cef3..8b2741a 100644 --- a/src/adf_core_python/implement/module/algorithm/a_star_path_planning.py +++ b/src/adf_core_python/implement/module/algorithm/a_star_path_planning.py @@ -8,96 +8,86 @@ from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.component.module.algorithm.path_planning import ( - PathPlanning, + PathPlanning, ) class AStarPathPlanning(PathPlanning): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + entities: list[Entity] = self._world_info.get_entities_of_types([Building, Road]) + self._graph: dict[EntityID, set[EntityID]] = {} + for entity in entities: + if isinstance(entity, Area): + self._graph[entity.get_entity_id()] = set( + neighbor for neighbor in entity.get_neighbors() if neighbor != EntityID(0) ) - entities: list[Entity] = self._world_info.get_entities_of_types( - [Building, Road] - ) - self._graph: dict[EntityID, set[EntityID]] = {} - for entity in entities: - if isinstance(entity, Area): - self._graph[entity.get_entity_id()] = set( - neighbor - for neighbor in entity.get_neighbors() - if neighbor != EntityID(0) - ) - def get_path( - self, from_entity_id: EntityID, to_entity_id: EntityID - ) -> list[EntityID]: - open_set: set[EntityID] = {from_entity_id} - came_from: dict[EntityID, EntityID] = {} - g_score: dict[EntityID, float] = {from_entity_id: 0.0} - f_score: dict[EntityID, float] = { - from_entity_id: self.heuristic(from_entity_id, to_entity_id) - } + def get_path( + self, from_entity_id: EntityID, to_entity_id: EntityID + ) -> list[EntityID]: + open_set: set[EntityID] = {from_entity_id} + came_from: dict[EntityID, EntityID] = {} + g_score: dict[EntityID, float] = {from_entity_id: 0.0} + f_score: dict[EntityID, float] = { + from_entity_id: self.heuristic(from_entity_id, to_entity_id) + } - while open_set: - current: EntityID = min( - open_set, key=lambda x: f_score.get(x, float("inf")) - ) - if current == to_entity_id: - return self.reconstruct_path(came_from, current) + while open_set: + current: EntityID = min(open_set, key=lambda x: f_score.get(x, float("inf"))) + if current == to_entity_id: + return self.reconstruct_path(came_from, current) - open_set.remove(current) - for neighbor in self._graph.get(current, []): - tentative_g_score: float = g_score[current] + self.distance( - current, neighbor - ) - if tentative_g_score < g_score.get(neighbor, float("inf")): - came_from[neighbor] = current - g_score[neighbor] = tentative_g_score - f_score[neighbor] = tentative_g_score + self.heuristic( - neighbor, to_entity_id - ) - if neighbor not in open_set: - open_set.add(neighbor) + open_set.remove(current) + for neighbor in self._graph.get(current, []): + tentative_g_score: float = g_score[current] + self.distance(current, neighbor) + if tentative_g_score < g_score.get(neighbor, float("inf")): + came_from[neighbor] = current + g_score[neighbor] = tentative_g_score + f_score[neighbor] = tentative_g_score + self.heuristic(neighbor, to_entity_id) + if neighbor not in open_set: + open_set.add(neighbor) - return [] + return [] - def get_path_to_multiple_destinations( - self, from_entity_id: EntityID, destination_entity_ids: set[EntityID] - ) -> list[EntityID]: - return [] + def get_path_to_multiple_destinations( + self, from_entity_id: EntityID, destination_entity_ids: set[EntityID] + ) -> list[EntityID]: + return [] - def heuristic(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: - # Implement a heuristic function, for example, Euclidean distance - return self._world_info.get_distance(from_entity_id, to_entity_id) + def heuristic(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: + # Implement a heuristic function, for example, Euclidean distance + return self._world_info.get_distance(from_entity_id, to_entity_id) - def distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: - # Implement a distance function, for example, Euclidean distance - return self._world_info.get_distance(from_entity_id, to_entity_id) + def distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: + # Implement a distance function, for example, Euclidean distance + return self._world_info.get_distance(from_entity_id, to_entity_id) - def reconstruct_path( - self, came_from: dict[EntityID, EntityID], current: EntityID - ) -> list[EntityID]: - total_path = [current] - while current in came_from: - current = came_from[current] - total_path.append(current) - total_path.reverse() - return total_path + def reconstruct_path( + self, came_from: dict[EntityID, EntityID], current: EntityID + ) -> list[EntityID]: + total_path = [current] + while current in came_from: + current = came_from[current] + total_path.append(current) + total_path.reverse() + return total_path - def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: - path: list[EntityID] = self.get_path(from_entity_id, to_entity_id) - distance: float = 0.0 - for i in range(len(path) - 1): - distance += self.distance(path[i], path[i + 1]) - return distance + def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: + path: list[EntityID] = self.get_path(from_entity_id, to_entity_id) + distance: float = 0.0 + for i in range(len(path) - 1): + distance += self.distance(path[i], path[i + 1]) + return distance - def calculate(self) -> AStarPathPlanning: - return self + def calculate(self) -> AStarPathPlanning: + return self diff --git a/src/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py b/src/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py index dac1549..43a0121 100644 --- a/src/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py +++ b/src/adf_core_python/implement/module/algorithm/dijkstra_path_planning.py @@ -14,120 +14,118 @@ class DijkstraPathPlanning(PathPlanning): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self.graph: dict[EntityID, list[tuple[EntityID, float]]] = {} + # グラフの構築 + for area in self._world_info.get_entities_of_types([Road, Building]): + if not isinstance(area, Area): + continue + if (neighbors := area.get_neighbors()) is None: + continue + area_id = area.get_entity_id() + self.graph[area_id] = [ + ( + neighbor, + self._world_info.get_distance(area_id, entity_id2=neighbor), ) - self.graph: dict[EntityID, list[tuple[EntityID, float]]] = {} - # グラフの構築 - for area in self._world_info.get_entities_of_types([Road, Building]): - if not isinstance(area, Area): - continue - if (neighbors := area.get_neighbors()) is None: - continue - area_id = area.get_entity_id() - self.graph[area_id] = [ - ( - neighbor, - self._world_info.get_distance(area_id, entity_id2=neighbor), - ) - for neighbor in neighbors - if neighbor.get_value() != 0 - ] - - def calculate(self) -> PathPlanning: - return self - - def get_path( - self, from_entity_id: EntityID, to_entity_id: EntityID - ) -> list[EntityID]: - # ダイクストラ法で最短経路を計算 - queue: list[tuple[float, EntityID]] = [] - heapq.heappush(queue, (0, from_entity_id)) - distance: dict[EntityID, float] = {from_entity_id: 0} - previous: dict[EntityID, Optional[EntityID]] = {from_entity_id: None} - - while queue: - current_distance, current_node = heapq.heappop(queue) - if current_node == to_entity_id: - break - - self._logger.info( - f"current_node: {current_node}, current_entity: {self._world_info.get_entity(current_node)}" - ) - - for neighbor, weight in self.graph[current_node]: - new_distance = current_distance + weight - if neighbor not in distance or new_distance < distance[neighbor]: - distance[neighbor] = new_distance - heapq.heappush(queue, (new_distance, neighbor)) - previous[neighbor] = current_node - - path: list[EntityID] = [] - current_path_node: Optional[EntityID] = to_entity_id - while current_path_node is not None: - path.append(current_path_node) - current_path_node = previous.get(current_path_node) - - return path[::-1] - - def get_path_to_multiple_destinations( - self, from_entity_id: EntityID, destination_entity_ids: set[EntityID] - ) -> list[EntityID]: - open_list = [from_entity_id] - ancestors = {from_entity_id: from_entity_id} - found = False - next_node = None - - while open_list and not found: - next_node = open_list.pop(0) - if self.is_goal(next_node, destination_entity_ids): - found = True - break - - neighbors = self.graph.get(next_node, []) - if not neighbors: - continue - - for neighbor, _ in neighbors: - if self.is_goal(neighbor, destination_entity_ids): - ancestors[neighbor] = next_node - next_node = neighbor - found = True - break - elif neighbor not in ancestors: - open_list.append(neighbor) - ancestors[neighbor] = next_node - - if not found: - return [] - - path: list[EntityID] = [] - current = next_node - while current != from_entity_id: - if current is None: - raise RuntimeError( - "Found a node with no ancestor! Something is broken." - ) - path.insert(0, current) - current = ancestors.get(current) - path.insert(0, from_entity_id) - - return path - - def is_goal(self, entity_id: EntityID, target_ids: set[EntityID]) -> bool: - return entity_id in target_ids - - def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: - path = self.get_path(from_entity_id, to_entity_id) - distance = 0.0 - for i in range(len(path) - 1): - distance += self._world_info.get_distance(path[i], path[i + 1]) - return distance + for neighbor in neighbors + if neighbor.get_value() != 0 + ] + + def calculate(self) -> PathPlanning: + return self + + def get_path( + self, from_entity_id: EntityID, to_entity_id: EntityID + ) -> list[EntityID]: + # ダイクストラ法で最短経路を計算 + queue: list[tuple[float, EntityID]] = [] + heapq.heappush(queue, (0, from_entity_id)) + distance: dict[EntityID, float] = {from_entity_id: 0} + previous: dict[EntityID, Optional[EntityID]] = {from_entity_id: None} + + while queue: + current_distance, current_node = heapq.heappop(queue) + if current_node == to_entity_id: + break + + self._logger.info( + f"current_node: {current_node}, current_entity: {self._world_info.get_entity(current_node)}" + ) + + for neighbor, weight in self.graph[current_node]: + new_distance = current_distance + weight + if neighbor not in distance or new_distance < distance[neighbor]: + distance[neighbor] = new_distance + heapq.heappush(queue, (new_distance, neighbor)) + previous[neighbor] = current_node + + path: list[EntityID] = [] + current_path_node: Optional[EntityID] = to_entity_id + while current_path_node is not None: + path.append(current_path_node) + current_path_node = previous.get(current_path_node) + + return path[::-1] + + def get_path_to_multiple_destinations( + self, from_entity_id: EntityID, destination_entity_ids: set[EntityID] + ) -> list[EntityID]: + open_list = [from_entity_id] + ancestors = {from_entity_id: from_entity_id} + found = False + next_node = None + + while open_list and not found: + next_node = open_list.pop(0) + if self.is_goal(next_node, destination_entity_ids): + found = True + break + + neighbors = self.graph.get(next_node, []) + if not neighbors: + continue + + for neighbor, _ in neighbors: + if self.is_goal(neighbor, destination_entity_ids): + ancestors[neighbor] = next_node + next_node = neighbor + found = True + break + elif neighbor not in ancestors: + open_list.append(neighbor) + ancestors[neighbor] = next_node + + if not found: + return [] + + path: list[EntityID] = [] + current = next_node + while current != from_entity_id: + if current is None: + raise RuntimeError("Found a node with no ancestor! Something is broken.") + path.insert(0, current) + current = ancestors.get(current) + path.insert(0, from_entity_id) + + return path + + def is_goal(self, entity_id: EntityID, target_ids: set[EntityID]) -> bool: + return entity_id in target_ids + + def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: + path = self.get_path(from_entity_id, to_entity_id) + distance = 0.0 + for i in range(len(path) - 1): + distance += self._world_info.get_distance(path[i], path[i + 1]) + return distance diff --git a/src/adf_core_python/implement/module/algorithm/k_means_clustering.py b/src/adf_core_python/implement/module/algorithm/k_means_clustering.py index a567abe..3ccdcf0 100644 --- a/src/adf_core_python/implement/module/algorithm/k_means_clustering.py +++ b/src/adf_core_python/implement/module/algorithm/k_means_clustering.py @@ -1,15 +1,15 @@ import numpy as np from rcrscore.entities import ( - AmbulanceCenter, - Building, - Entity, - EntityID, - FireStation, - GasStation, - Hydrant, - PoliceOffice, - Refuge, - Road, + AmbulanceCenter, + Building, + Entity, + EntityID, + FireStation, + GasStation, + Hydrant, + PoliceOffice, + Refuge, + Road, ) from rcrscore.urn import EntityURN from sklearn.cluster import KMeans @@ -24,140 +24,137 @@ class KMeansClustering(Clustering): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + myself = agent_info.get_myself() + if myself is None: + raise RuntimeError("Could not get agent entity") + match myself.get_urn(): + case EntityURN.AMBULANCE_TEAM: + self._cluster_number = int( + scenario_info.get_value( + "scenario.agents.at", + 1, + ) ) - myself = agent_info.get_myself() - if myself is None: - raise RuntimeError("Could not get agent entity") - match myself.get_urn(): - case EntityURN.AMBULANCE_TEAM: - self._cluster_number = int( - scenario_info.get_value( - "scenario.agents.at", - 1, - ) - ) - case EntityURN.POLICE_FORCE: - self._cluster_number = int( - scenario_info.get_value( - "scenario.agents.pf", - 1, - ) - ) - case EntityURN.FIRE_BRIGADE: - self._cluster_number = int( - scenario_info.get_value( - "scenario.agents.fb", - 1, - ) - ) - case _: - self._cluster_number = 1 - - sorted_entities = sorted( - world_info.get_entities_of_types( - [ - myself.__class__, - ] - ), - key=lambda entity: entity.get_entity_id().get_value(), + case EntityURN.POLICE_FORCE: + self._cluster_number = int( + scenario_info.get_value( + "scenario.agents.pf", + 1, + ) ) - self.entity_cluster_indices = { - entity.get_entity_id(): idx for idx, entity in enumerate(sorted_entities) - } - - self.cluster_entities: list[list[Entity]] = [] - self.entities: list[Entity] = world_info.get_entities_of_types( - [ - AmbulanceCenter, - FireStation, - GasStation, - Hydrant, - PoliceOffice, - Refuge, - Road, - Building, - ] + case EntityURN.FIRE_BRIGADE: + self._cluster_number = int( + scenario_info.get_value( + "scenario.agents.fb", + 1, + ) ) + case _: + self._cluster_number = 1 - def calculate(self) -> Clustering: - return self - - def precompute(self, precompute_data: PrecomputeData) -> Clustering: - cluster_entities = self.create_cluster(self._cluster_number, self.entities) - precompute_data.write_json_data( - { - "cluster_entities": [ - [entity.get_entity_id().get_value() for entity in cluster] - for cluster in cluster_entities - ] - }, - self.__class__.__name__, - ) - return self - - def resume(self, precompute_data: PrecomputeData) -> Clustering: - data = precompute_data.read_json_data(self.__class__.__name__) - self.cluster_entities = [ - [ - entity - for entity_id in cluster - if (entity := self._world_info.get_entity(EntityID(entity_id))) - is not None - ] - for cluster in data["cluster_entities"] + sorted_entities = sorted( + world_info.get_entities_of_types( + [ + myself.__class__, ] - return self - - def get_cluster_number(self) -> int: - return self._cluster_number - - def get_cluster_index(self, entity_id: EntityID) -> int: - return self.entity_cluster_indices.get(entity_id, 0) - - def get_cluster_entities(self, cluster_index: int) -> list[Entity]: - if cluster_index >= len(self.cluster_entities): - return [] - return self.cluster_entities[cluster_index] - - def get_cluster_entity_ids(self, cluster_index: int) -> list[EntityID]: - if cluster_index >= len(self.cluster_entities): - return [] - return [ - entity.get_entity_id() for entity in self.cluster_entities[cluster_index] + ), + key=lambda entity: entity.get_entity_id().get_value(), + ) + self.entity_cluster_indices = { + entity.get_entity_id(): idx for idx, entity in enumerate(sorted_entities) + } + + self.cluster_entities: list[list[Entity]] = [] + self.entities: list[Entity] = world_info.get_entities_of_types( + [ + AmbulanceCenter, + FireStation, + GasStation, + Hydrant, + PoliceOffice, + Refuge, + Road, + Building, + ] + ) + + def calculate(self) -> Clustering: + return self + + def precompute(self, precompute_data: PrecomputeData) -> Clustering: + cluster_entities = self.create_cluster(self._cluster_number, self.entities) + precompute_data.write_json_data( + { + "cluster_entities": [ + [entity.get_entity_id().get_value() for entity in cluster] + for cluster in cluster_entities ] - - def prepare(self) -> Clustering: - super().prepare() - if self.get_count_prepare() > 1: - return self - self.cluster_entities = self.create_cluster(self._cluster_number, self.entities) - return self - - def create_cluster( - self, cluster_number: int, entities: list[Entity] - ) -> list[list[Entity]]: - kmeans = KMeans(n_clusters=cluster_number, random_state=0) - entity_positions: np.ndarray = np.array([]) - for entity in entities: - location1_x, location1_y = entity.get_location() - if location1_x is None or location1_y is None: - continue - entity_positions = np.append(entity_positions, [location1_x, location1_y]) - - kmeans.fit(entity_positions.reshape(-1, 2)) - - clusters: list[list[Entity]] = [[] for _ in range(cluster_number)] - for entity, label in zip(entities, kmeans.labels_): - clusters[label].append(entity) - - return clusters + }, + self.__class__.__name__, + ) + return self + + def resume(self, precompute_data: PrecomputeData) -> Clustering: + data = precompute_data.read_json_data(self.__class__.__name__) + self.cluster_entities = [ + [ + entity + for entity_id in cluster + if (entity := self._world_info.get_entity(EntityID(entity_id))) is not None + ] + for cluster in data["cluster_entities"] + ] + return self + + def get_cluster_number(self) -> int: + return self._cluster_number + + def get_cluster_index(self, entity_id: EntityID) -> int: + return self.entity_cluster_indices.get(entity_id, 0) + + def get_cluster_entities(self, cluster_index: int) -> list[Entity]: + if cluster_index >= len(self.cluster_entities): + return [] + return self.cluster_entities[cluster_index] + + def get_cluster_entity_ids(self, cluster_index: int) -> list[EntityID]: + if cluster_index >= len(self.cluster_entities): + return [] + return [entity.get_entity_id() for entity in self.cluster_entities[cluster_index]] + + def prepare(self) -> Clustering: + super().prepare() + if self.get_count_prepare() > 1: + return self + self.cluster_entities = self.create_cluster(self._cluster_number, self.entities) + return self + + def create_cluster( + self, cluster_number: int, entities: list[Entity] + ) -> list[list[Entity]]: + kmeans = KMeans(n_clusters=cluster_number, random_state=0) + entity_positions: np.ndarray = np.array([]) + for entity in entities: + location1_x, location1_y = entity.get_location() + if location1_x is None or location1_y is None: + continue + entity_positions = np.append(entity_positions, [location1_x, location1_y]) + + kmeans.fit(entity_positions.reshape(-1, 2)) + + clusters: list[list[Entity]] = [[] for _ in range(cluster_number)] + for entity, label in zip(entities, kmeans.labels_): + clusters[label].append(entity) + + return clusters diff --git a/src/adf_core_python/implement/module/communication/default_channel_subscriber.py b/src/adf_core_python/implement/module/communication/default_channel_subscriber.py index 398b720..c9df4b2 100644 --- a/src/adf_core_python/implement/module/communication/default_channel_subscriber.py +++ b/src/adf_core_python/implement/module/communication/default_channel_subscriber.py @@ -6,96 +6,89 @@ from adf_core_python.core.agent.info.scenario_info import ScenarioInfoKeys from adf_core_python.core.component.communication.channel_subscriber import ( - ChannelSubscriber, + ChannelSubscriber, ) if TYPE_CHECKING: - from adf_core_python.core.agent.info.agent_info import AgentInfo - from adf_core_python.core.agent.info.scenario_info import ScenarioInfo - from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo class DefaultChannelSubscriber(ChannelSubscriber): - def subscribe( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - ) -> list[int]: - agent = world_info.get_entity(agent_info.get_entity_id()) - if agent is None: - return [] - - agent_type = agent.get_urn() - - number_of_channels: int = ( - scenario_info.get_value(ScenarioInfoKeys.COMMUNICATION_CHANNELS_COUNT, 1) - - 1 - ) - - is_platoon: bool = ( - agent_type == EntityURN.FIRE_BRIGADE - or agent_type == EntityURN.POLICE_FORCE - or agent_type == EntityURN.AMBULANCE_TEAM - ) - - max_channel_count: int = ( - scenario_info.get_value( - ScenarioInfoKeys.COMMUNICATION_CHANNELS_MAX_PLATOON, 1 - ) - if is_platoon - else scenario_info.get_value( - ScenarioInfoKeys.COMMUNICATION_CHANNELS_MAX_OFFICE, 1 - ) - ) - - channels = [ - self.get_channel_number(agent_type, i, number_of_channels) - for i in range(max_channel_count) - ] - return channels - - @staticmethod - def get_channel_number( - agent_type: EntityURN, channel_index: int, number_of_channels: int - ) -> int: - agent_index = 0 - if agent_type == EntityURN.FIRE_BRIGADE or agent_type == EntityURN.FIRE_STATION: - agent_index = 1 - elif ( - agent_type == EntityURN.POLICE_FORCE - or agent_type == EntityURN.POLICE_OFFICE - ): - agent_index = 2 - elif ( - agent_type == EntityURN.AMBULANCE_TEAM - or agent_type == EntityURN.AMBULANCE_CENTER - ): - agent_index = 3 - - index = (3 * channel_index) + agent_index - if (index % number_of_channels) == 0: - index = number_of_channels - else: - index = index % number_of_channels - return index + def subscribe( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + ) -> list[int]: + agent = world_info.get_entity(agent_info.get_entity_id()) + if agent is None: + return [] + + agent_type = agent.get_urn() + + number_of_channels: int = ( + scenario_info.get_value(ScenarioInfoKeys.COMMUNICATION_CHANNELS_COUNT, 1) - 1 + ) + + is_platoon: bool = ( + agent_type == EntityURN.FIRE_BRIGADE + or agent_type == EntityURN.POLICE_FORCE + or agent_type == EntityURN.AMBULANCE_TEAM + ) + + max_channel_count: int = ( + scenario_info.get_value(ScenarioInfoKeys.COMMUNICATION_CHANNELS_MAX_PLATOON, 1) + if is_platoon + else scenario_info.get_value( + ScenarioInfoKeys.COMMUNICATION_CHANNELS_MAX_OFFICE, 1 + ) + ) + + channels = [ + self.get_channel_number(agent_type, i, number_of_channels) + for i in range(max_channel_count) + ] + return channels + + @staticmethod + def get_channel_number( + agent_type: EntityURN, channel_index: int, number_of_channels: int + ) -> int: + agent_index = 0 + if agent_type == EntityURN.FIRE_BRIGADE or agent_type == EntityURN.FIRE_STATION: + agent_index = 1 + elif agent_type == EntityURN.POLICE_FORCE or agent_type == EntityURN.POLICE_OFFICE: + agent_index = 2 + elif ( + agent_type == EntityURN.AMBULANCE_TEAM or agent_type == EntityURN.AMBULANCE_CENTER + ): + agent_index = 3 + + index = (3 * channel_index) + agent_index + if (index % number_of_channels) == 0: + index = number_of_channels + else: + index = index % number_of_channels + return index if __name__ == "__main__": - num_channels = 1 - max_channels = 2 - - for i in range(max_channels): - print( - f"FIREBRIGADE-{i}: {DefaultChannelSubscriber.get_channel_number(EntityURN.FIRE_BRIGADE, i, num_channels)}" - ) - - for i in range(max_channels): - print( - f"POLICE-{i}: {DefaultChannelSubscriber.get_channel_number(EntityURN.POLICE_OFFICE, i, num_channels)}" - ) - - for i in range(max_channels): - print( - f"AMB-{i}: {DefaultChannelSubscriber.get_channel_number(EntityURN.AMBULANCE_CENTER, i, num_channels)}" - ) + num_channels = 1 + max_channels = 2 + + for i in range(max_channels): + print( + f"FIREBRIGADE-{i}: {DefaultChannelSubscriber.get_channel_number(EntityURN.FIRE_BRIGADE, i, num_channels)}" + ) + + for i in range(max_channels): + print( + f"POLICE-{i}: {DefaultChannelSubscriber.get_channel_number(EntityURN.POLICE_OFFICE, i, num_channels)}" + ) + + for i in range(max_channels): + print( + f"AMB-{i}: {DefaultChannelSubscriber.get_channel_number(EntityURN.AMBULANCE_CENTER, i, num_channels)}" + ) diff --git a/src/adf_core_python/implement/module/communication/default_message_coordinator.py b/src/adf_core_python/implement/module/communication/default_message_coordinator.py index 28c8490..3428304 100644 --- a/src/adf_core_python/implement/module/communication/default_message_coordinator.py +++ b/src/adf_core_python/implement/module/communication/default_message_coordinator.py @@ -4,242 +4,235 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( - CommandAmbulance, + CommandAmbulance, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.command_fire import ( - CommandFire, + CommandFire, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.command_police import ( - CommandPolice, + CommandPolice, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( - CommandScout, + CommandScout, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( - MessageReport, + MessageReport, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_ambulance_team import ( - MessageAmbulanceTeam, + MessageAmbulanceTeam, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_building import ( - MessageBuilding, + MessageBuilding, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_civilian import ( - MessageCivilian, + MessageCivilian, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_fire_brigade import ( - MessageFireBrigade, + MessageFireBrigade, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_police_force import ( - MessagePoliceForce, + MessagePoliceForce, ) from adf_core_python.core.agent.communication.standard.bundle.information.message_road import ( - MessageRoad, + MessageRoad, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( - StandardMessagePriority, + StandardMessagePriority, ) from adf_core_python.core.agent.info.agent_info import AgentInfo from adf_core_python.core.agent.info.scenario_info import ScenarioInfo, ScenarioInfoKeys from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.component.communication.communication_message import ( - CommunicationMessage, + CommunicationMessage, ) from adf_core_python.core.component.communication.message_coordinator import ( - MessageCoordinator, + MessageCoordinator, ) from adf_core_python.implement.module.communication.default_channel_subscriber import ( - DefaultChannelSubscriber, + DefaultChannelSubscriber, ) class DefaultMessageCoordinator(MessageCoordinator): - def coordinate( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - message_manager: MessageManager, - send_message_list: list[CommunicationMessage], - channel_send_message_list: list[list[CommunicationMessage]], - ) -> None: - police_messages: list[StandardMessage] = [] - ambulance_messages: list[StandardMessage] = [] - fire_brigade_messages: list[StandardMessage] = [] - voice_messages: list[StandardMessage] = [] - - agent_type = self.get_agent_type(agent_info, world_info) - - for msg in send_message_list: - if isinstance(msg, StandardMessage) and not msg.is_wireless_message(): - voice_messages.append(msg) - else: - if isinstance(msg, MessageBuilding): - fire_brigade_messages.append(msg) - elif isinstance(msg, MessageCivilian): - ambulance_messages.append(msg) - elif isinstance(msg, MessageRoad): - fire_brigade_messages.append(msg) - ambulance_messages.append(msg) - police_messages.append(msg) - elif isinstance(msg, CommandAmbulance): - ambulance_messages.append(msg) - elif isinstance(msg, CommandFire): - fire_brigade_messages.append(msg) - elif isinstance(msg, CommandPolice): - police_messages.append(msg) - elif isinstance(msg, CommandScout): - if agent_type == EntityURN.FIRE_STATION: - fire_brigade_messages.append(msg) - elif agent_type == EntityURN.POLICE_OFFICE: - police_messages.append(msg) - elif agent_type == EntityURN.AMBULANCE_CENTER: - ambulance_messages.append(msg) - elif isinstance(msg, MessageReport): - if agent_type == EntityURN.FIRE_BRIGADE: - fire_brigade_messages.append(msg) - elif agent_type == EntityURN.POLICE_FORCE: - police_messages.append(msg) - elif agent_type == EntityURN.AMBULANCE_TEAM: - ambulance_messages.append(msg) - elif isinstance(msg, MessageFireBrigade): - fire_brigade_messages.append(msg) - ambulance_messages.append(msg) - police_messages.append(msg) - elif isinstance(msg, MessagePoliceForce): - ambulance_messages.append(msg) - police_messages.append(msg) - elif isinstance(msg, MessageAmbulanceTeam): - ambulance_messages.append(msg) - police_messages.append(msg) - - if int(scenario_info.get_value("comms.channels.count", 1)) > 1: - channel_size = [0] * ( - int(scenario_info.get_value("comms.channels.count", 1)) - ) - self.set_send_messages( - scenario_info, - EntityURN.POLICE_FORCE, - agent_info, - world_info, - police_messages, - channel_send_message_list, - channel_size, - ) - self.set_send_messages( - scenario_info, - EntityURN.AMBULANCE_TEAM, - agent_info, - world_info, - ambulance_messages, - channel_send_message_list, - channel_size, - ) - self.set_send_messages( - scenario_info, - EntityURN.FIRE_BRIGADE, - agent_info, - world_info, - fire_brigade_messages, - channel_send_message_list, - channel_size, - ) - - voice_message_low_list = [] - voice_message_normal_list = [] - voice_message_high_list = [] - - for msg in voice_messages: - if isinstance(msg, StandardMessage): - if msg.get_priority() == StandardMessagePriority.LOW: - voice_message_low_list.append(msg) - elif msg.get_priority() == StandardMessagePriority.NORMAL: - voice_message_normal_list.append(msg) - elif msg.get_priority() == StandardMessagePriority.HIGH: - voice_message_high_list.append(msg) - - channel_send_message_list[0].extend(voice_message_high_list) - channel_send_message_list[0].extend(voice_message_normal_list) - channel_send_message_list[0].extend(voice_message_low_list) - - def get_channels_by_agent_type( - self, - agent_type: EntityURN, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - ) -> list[int]: - num_channels = ( - scenario_info.get_value(ScenarioInfoKeys.COMMUNICATION_CHANNELS_COUNT, 1) - - 1 - ) - max_channel_count = int( - # scenario_info.get_comms_channels_max_platoon() - scenario_info.get_value( - ScenarioInfoKeys.COMMUNICATION_CHANNELS_MAX_PLATOON, 1 - ) - if self.is_platoon_agent(agent_info, world_info) - else scenario_info.get_value( - ScenarioInfoKeys.COMMUNICATION_CHANNELS_MAX_OFFICE, 1 - ) - ) - channels = [ - DefaultChannelSubscriber.get_channel_number(agent_type, i, num_channels) - for i in range(max_channel_count) - ] - return channels - - def is_platoon_agent(self, agent_info: AgentInfo, world_info: WorldInfo) -> bool: - agent_type = self.get_agent_type(agent_info, world_info) - return agent_type in [ - EntityURN.FIRE_BRIGADE, - EntityURN.POLICE_FORCE, - EntityURN.AMBULANCE_TEAM, - ] - - def get_agent_type( - self, agent_info: AgentInfo, world_info: WorldInfo - ) -> Optional[EntityURN]: - entity = world_info.get_entity(agent_info.get_entity_id()) - if entity is None: - return None - return entity.get_urn() - - def set_send_messages( - self, - scenario_info: ScenarioInfo, - agent_type: EntityURN, - agent_info: AgentInfo, - world_info: WorldInfo, - messages: list[StandardMessage], - channel_send_message_list: list[list[CommunicationMessage]], - channel_size: list[int], - ) -> None: - channels = self.get_channels_by_agent_type( - agent_type, agent_info, world_info, scenario_info - ) - channel_capacities = [ - scenario_info.get_value("comms.channels." + str(channel) + ".bandwidth", 0) - for channel in range( - scenario_info.get_value( - ScenarioInfoKeys.COMMUNICATION_CHANNELS_COUNT, 1 - ) - ) - ] - - sorted_messages = sorted( - messages, key=lambda x: x.get_priority().value, reverse=True - ) - - for message in sorted_messages: - for channel in channels: - if message not in channel_send_message_list[channel] and ( - (channel_size[channel] + message.get_bit_size()) - <= channel_capacities[channel] - ): - channel_size[channel] += message.get_bit_size() - channel_send_message_list[channel].append(message) - break + def coordinate( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + message_manager: MessageManager, + send_message_list: list[CommunicationMessage], + channel_send_message_list: list[list[CommunicationMessage]], + ) -> None: + police_messages: list[StandardMessage] = [] + ambulance_messages: list[StandardMessage] = [] + fire_brigade_messages: list[StandardMessage] = [] + voice_messages: list[StandardMessage] = [] + + agent_type = self.get_agent_type(agent_info, world_info) + + for msg in send_message_list: + if isinstance(msg, StandardMessage) and not msg.is_wireless_message(): + voice_messages.append(msg) + else: + if isinstance(msg, MessageBuilding): + fire_brigade_messages.append(msg) + elif isinstance(msg, MessageCivilian): + ambulance_messages.append(msg) + elif isinstance(msg, MessageRoad): + fire_brigade_messages.append(msg) + ambulance_messages.append(msg) + police_messages.append(msg) + elif isinstance(msg, CommandAmbulance): + ambulance_messages.append(msg) + elif isinstance(msg, CommandFire): + fire_brigade_messages.append(msg) + elif isinstance(msg, CommandPolice): + police_messages.append(msg) + elif isinstance(msg, CommandScout): + if agent_type == EntityURN.FIRE_STATION: + fire_brigade_messages.append(msg) + elif agent_type == EntityURN.POLICE_OFFICE: + police_messages.append(msg) + elif agent_type == EntityURN.AMBULANCE_CENTER: + ambulance_messages.append(msg) + elif isinstance(msg, MessageReport): + if agent_type == EntityURN.FIRE_BRIGADE: + fire_brigade_messages.append(msg) + elif agent_type == EntityURN.POLICE_FORCE: + police_messages.append(msg) + elif agent_type == EntityURN.AMBULANCE_TEAM: + ambulance_messages.append(msg) + elif isinstance(msg, MessageFireBrigade): + fire_brigade_messages.append(msg) + ambulance_messages.append(msg) + police_messages.append(msg) + elif isinstance(msg, MessagePoliceForce): + ambulance_messages.append(msg) + police_messages.append(msg) + elif isinstance(msg, MessageAmbulanceTeam): + ambulance_messages.append(msg) + police_messages.append(msg) + + if int(scenario_info.get_value("comms.channels.count", 1)) > 1: + channel_size = [0] * (int(scenario_info.get_value("comms.channels.count", 1))) + self.set_send_messages( + scenario_info, + EntityURN.POLICE_FORCE, + agent_info, + world_info, + police_messages, + channel_send_message_list, + channel_size, + ) + self.set_send_messages( + scenario_info, + EntityURN.AMBULANCE_TEAM, + agent_info, + world_info, + ambulance_messages, + channel_send_message_list, + channel_size, + ) + self.set_send_messages( + scenario_info, + EntityURN.FIRE_BRIGADE, + agent_info, + world_info, + fire_brigade_messages, + channel_send_message_list, + channel_size, + ) + + voice_message_low_list = [] + voice_message_normal_list = [] + voice_message_high_list = [] + + for msg in voice_messages: + if isinstance(msg, StandardMessage): + if msg.get_priority() == StandardMessagePriority.LOW: + voice_message_low_list.append(msg) + elif msg.get_priority() == StandardMessagePriority.NORMAL: + voice_message_normal_list.append(msg) + elif msg.get_priority() == StandardMessagePriority.HIGH: + voice_message_high_list.append(msg) + + channel_send_message_list[0].extend(voice_message_high_list) + channel_send_message_list[0].extend(voice_message_normal_list) + channel_send_message_list[0].extend(voice_message_low_list) + + def get_channels_by_agent_type( + self, + agent_type: EntityURN, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + ) -> list[int]: + num_channels = ( + scenario_info.get_value(ScenarioInfoKeys.COMMUNICATION_CHANNELS_COUNT, 1) - 1 + ) + max_channel_count = int( + # scenario_info.get_comms_channels_max_platoon() + scenario_info.get_value(ScenarioInfoKeys.COMMUNICATION_CHANNELS_MAX_PLATOON, 1) + if self.is_platoon_agent(agent_info, world_info) + else scenario_info.get_value( + ScenarioInfoKeys.COMMUNICATION_CHANNELS_MAX_OFFICE, 1 + ) + ) + channels = [ + DefaultChannelSubscriber.get_channel_number(agent_type, i, num_channels) + for i in range(max_channel_count) + ] + return channels + + def is_platoon_agent(self, agent_info: AgentInfo, world_info: WorldInfo) -> bool: + agent_type = self.get_agent_type(agent_info, world_info) + return agent_type in [ + EntityURN.FIRE_BRIGADE, + EntityURN.POLICE_FORCE, + EntityURN.AMBULANCE_TEAM, + ] + + def get_agent_type( + self, agent_info: AgentInfo, world_info: WorldInfo + ) -> Optional[EntityURN]: + entity = world_info.get_entity(agent_info.get_entity_id()) + if entity is None: + return None + return entity.get_urn() + + def set_send_messages( + self, + scenario_info: ScenarioInfo, + agent_type: EntityURN, + agent_info: AgentInfo, + world_info: WorldInfo, + messages: list[StandardMessage], + channel_send_message_list: list[list[CommunicationMessage]], + channel_size: list[int], + ) -> None: + channels = self.get_channels_by_agent_type( + agent_type, agent_info, world_info, scenario_info + ) + channel_capacities = [ + scenario_info.get_value("comms.channels." + str(channel) + ".bandwidth", 0) + for channel in range( + scenario_info.get_value(ScenarioInfoKeys.COMMUNICATION_CHANNELS_COUNT, 1) + ) + ] + + sorted_messages = sorted( + messages, key=lambda x: x.get_priority().value, reverse=True + ) + + for message in sorted_messages: + for channel in channels: + if message not in channel_send_message_list[channel] and ( + (channel_size[channel] + message.get_bit_size()) + <= channel_capacities[channel] + ): + channel_size[channel] += message.get_bit_size() + channel_send_message_list[channel].append(message) + break diff --git a/src/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py b/src/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py index bdeb89a..599a75c 100644 --- a/src/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py +++ b/src/adf_core_python/implement/module/complex/default_ambulance_target_allocator.py @@ -11,143 +11,143 @@ from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.module.complex.ambulance_target_allocator import ( - AmbulanceTargetAllocator, + AmbulanceTargetAllocator, ) class DefaultAmbulanceTargetAllocator(AmbulanceTargetAllocator): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - self._priority_humans: set[EntityID] = set() - self._target_humans: set[EntityID] = set() - self._ambulance_team_info_map: dict[ - EntityID, DefaultAmbulanceTargetAllocator.AmbulanceTeamInfo - ] = {} - - def resume(self, precompute_data: PrecomputeData) -> AmbulanceTargetAllocator: - super().resume(precompute_data) - if self.get_count_resume() >= 2: - return self - for entity_id in self._world_info.get_entity_ids_of_types([AmbulanceTeam]): - self._ambulance_team_info_map[entity_id] = self.AmbulanceTeamInfo(entity_id) - return self - - def prepare(self) -> AmbulanceTargetAllocator: - super().prepare() - if self.get_count_prepare() >= 2: - return self - for entity_id in self._world_info.get_entity_ids_of_types([AmbulanceTeam]): - self._ambulance_team_info_map[entity_id] = self.AmbulanceTeamInfo(entity_id) - return self - - def update_info(self, message_manager: MessageManager) -> AmbulanceTargetAllocator: - super().update_info(message_manager) - # TODO: implement after message_manager is implemented - return self - - def calculate(self) -> AmbulanceTargetAllocator: - agents = self._get_action_agents(self._ambulance_team_info_map) - removes = [] - current_time = self._agent_info.get_time() - - for target in self._priority_humans: - if len(agents) > 0: - target_entity = self._world_info.get_entity(target) - if target_entity is not None and isinstance(target_entity, Human): - agents = sorted( - agents, key=cmp_to_key(self._compare_by_distance(target_entity)) - ) - result = agents.pop(0) - info = self._ambulance_team_info_map[result.get_entity_id()] - if info is not None: - info._can_new_action = False - info._target = target - info.command_time = current_time - self._ambulance_team_info_map[result.get_entity_id()] = info - removes.append(target) - - for r in removes: - self._priority_humans.remove(r) - removes.clear() - - for target in self._target_humans: - if len(agents) > 0: - target_entity = self._world_info.get_entity(target) - if target_entity is not None and isinstance(target_entity, Human): - agents = sorted( - agents, key=cmp_to_key(self._compare_by_distance(target_entity)) - ) - result = agents.pop(0) - info = self._ambulance_team_info_map[result.get_entity_id()] - if info is not None: - info._can_new_action = False - info._target = target - info.command_time = current_time - self._ambulance_team_info_map[result.get_entity_id()] = info - removes.append(target) - - for r in removes: - self._target_humans.remove(r) - - return self - - def get_result(self) -> dict[EntityID, EntityID]: - return self._convert(self._ambulance_team_info_map) - - def _get_action_agents( - self, - info_map: dict[EntityID, "DefaultAmbulanceTargetAllocator.AmbulanceTeamInfo"], - ) -> list[AmbulanceTeam]: - result = [] - for entity in self._world_info.get_entities_of_types([AmbulanceTeam]): - if isinstance(entity, AmbulanceTeam): - info = info_map[entity.get_entity_id()] - if info is not None and info._can_new_action: - result.append(entity) - return result - - def _compare_by_distance( - self, target_entity: Entity - ) -> Callable[[Entity, Entity], int]: - def _cmp_func(entity_a: Entity, entity_b: Entity) -> int: - distance_a = self._world_info.get_distance( - target_entity.get_entity_id(), entity_a.get_entity_id() - ) - distance_b = self._world_info.get_distance( - target_entity.get_entity_id(), entity_b.get_entity_id() - ) - if distance_a < distance_b: - return -1 - elif distance_a > distance_b: - return 1 - else: - return 0 - - return _cmp_func - - def _convert( - self, - info_map: dict[EntityID, "DefaultAmbulanceTargetAllocator.AmbulanceTeamInfo"], - ) -> dict[EntityID, EntityID]: - result = {} - for entity_id in info_map.keys(): - info = info_map[entity_id] - if info is not None and info._target is not None: - result[entity_id] = info._target - return result - - class AmbulanceTeamInfo: - def __init__(self, entity_id: EntityID) -> None: - self._agent_id: EntityID = entity_id - self._target: Optional[EntityID] = None - self._can_new_action: bool = True - self.command_time: int = -1 + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._priority_humans: set[EntityID] = set() + self._target_humans: set[EntityID] = set() + self._ambulance_team_info_map: dict[ + EntityID, DefaultAmbulanceTargetAllocator.AmbulanceTeamInfo + ] = {} + + def resume(self, precompute_data: PrecomputeData) -> AmbulanceTargetAllocator: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + for entity_id in self._world_info.get_entity_ids_of_types([AmbulanceTeam]): + self._ambulance_team_info_map[entity_id] = self.AmbulanceTeamInfo(entity_id) + return self + + def prepare(self) -> AmbulanceTargetAllocator: + super().prepare() + if self.get_count_prepare() >= 2: + return self + for entity_id in self._world_info.get_entity_ids_of_types([AmbulanceTeam]): + self._ambulance_team_info_map[entity_id] = self.AmbulanceTeamInfo(entity_id) + return self + + def update_info(self, message_manager: MessageManager) -> AmbulanceTargetAllocator: + super().update_info(message_manager) + # TODO: implement after message_manager is implemented + return self + + def calculate(self) -> AmbulanceTargetAllocator: + agents = self._get_action_agents(self._ambulance_team_info_map) + removes = [] + current_time = self._agent_info.get_time() + + for target in self._priority_humans: + if len(agents) > 0: + target_entity = self._world_info.get_entity(target) + if target_entity is not None and isinstance(target_entity, Human): + agents = sorted( + agents, key=cmp_to_key(self._compare_by_distance(target_entity)) + ) + result = agents.pop(0) + info = self._ambulance_team_info_map[result.get_entity_id()] + if info is not None: + info._can_new_action = False + info._target = target + info.command_time = current_time + self._ambulance_team_info_map[result.get_entity_id()] = info + removes.append(target) + + for r in removes: + self._priority_humans.remove(r) + removes.clear() + + for target in self._target_humans: + if len(agents) > 0: + target_entity = self._world_info.get_entity(target) + if target_entity is not None and isinstance(target_entity, Human): + agents = sorted( + agents, key=cmp_to_key(self._compare_by_distance(target_entity)) + ) + result = agents.pop(0) + info = self._ambulance_team_info_map[result.get_entity_id()] + if info is not None: + info._can_new_action = False + info._target = target + info.command_time = current_time + self._ambulance_team_info_map[result.get_entity_id()] = info + removes.append(target) + + for r in removes: + self._target_humans.remove(r) + + return self + + def get_result(self) -> dict[EntityID, EntityID]: + return self._convert(self._ambulance_team_info_map) + + def _get_action_agents( + self, + info_map: dict[EntityID, "DefaultAmbulanceTargetAllocator.AmbulanceTeamInfo"], + ) -> list[AmbulanceTeam]: + result = [] + for entity in self._world_info.get_entities_of_types([AmbulanceTeam]): + if isinstance(entity, AmbulanceTeam): + info = info_map[entity.get_entity_id()] + if info is not None and info._can_new_action: + result.append(entity) + return result + + def _compare_by_distance( + self, target_entity: Entity + ) -> Callable[[Entity, Entity], int]: + def _cmp_func(entity_a: Entity, entity_b: Entity) -> int: + distance_a = self._world_info.get_distance( + target_entity.get_entity_id(), entity_a.get_entity_id() + ) + distance_b = self._world_info.get_distance( + target_entity.get_entity_id(), entity_b.get_entity_id() + ) + if distance_a < distance_b: + return -1 + elif distance_a > distance_b: + return 1 + else: + return 0 + + return _cmp_func + + def _convert( + self, + info_map: dict[EntityID, "DefaultAmbulanceTargetAllocator.AmbulanceTeamInfo"], + ) -> dict[EntityID, EntityID]: + result = {} + for entity_id in info_map.keys(): + info = info_map[entity_id] + if info is not None and info._target is not None: + result[entity_id] = info._target + return result + + class AmbulanceTeamInfo: + def __init__(self, entity_id: EntityID) -> None: + self._agent_id: EntityID = entity_id + self._target: Optional[EntityID] = None + self._can_new_action: bool = True + self.command_time: int = -1 diff --git a/src/adf_core_python/implement/module/complex/default_fire_target_allocator.py b/src/adf_core_python/implement/module/complex/default_fire_target_allocator.py index 4373d5d..b4b3a2e 100644 --- a/src/adf_core_python/implement/module/complex/default_fire_target_allocator.py +++ b/src/adf_core_python/implement/module/complex/default_fire_target_allocator.py @@ -11,141 +11,141 @@ from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.module.complex.fire_target_allocator import ( - FireTargetAllocator, + FireTargetAllocator, ) class DefaultFireTargetAllocator(FireTargetAllocator): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - self._priority_humans: set[EntityID] = set() - self._target_humans: set[EntityID] = set() - self._fire_brigade_info_map: dict[ - EntityID, DefaultFireTargetAllocator.FireBrigadeInfo - ] = {} - - def resume(self, precompute_data: PrecomputeData) -> FireTargetAllocator: - super().resume(precompute_data) - if self.get_count_resume() >= 2: - return self - for entity_id in self._world_info.get_entity_ids_of_types([FireBrigade]): - self._fire_brigade_info_map[entity_id] = self.FireBrigadeInfo(entity_id) - return self - - def prepare(self) -> FireTargetAllocator: - super().prepare() - if self.get_count_prepare() >= 2: - return self - for entity_id in self._world_info.get_entity_ids_of_types([FireBrigade]): - self._fire_brigade_info_map[entity_id] = self.FireBrigadeInfo(entity_id) - return self - - def update_info(self, message_manager: MessageManager) -> FireTargetAllocator: - super().update_info(message_manager) - # TODO: implement after message_manager is implemented - return self - - def calculate(self) -> FireTargetAllocator: - agents = self._get_action_agents(self._fire_brigade_info_map) - removes = [] - current_time = self._agent_info.get_time() - - for target in self._priority_humans: - if len(agents) > 0: - target_entity = self._world_info.get_entity(target) - if target_entity is not None and isinstance(target_entity, Human): - agents = sorted( - agents, key=cmp_to_key(self._compare_by_distance(target_entity)) - ) - result = agents.pop(0) - info = self._fire_brigade_info_map[result.get_entity_id()] - if info is not None: - info._can_new_action = False - info._target = target - info.command_time = current_time - self._fire_brigade_info_map[result.get_entity_id()] = info - removes.append(target) - - for r in removes: - self._priority_humans.remove(r) - removes.clear() - - for target in self._target_humans: - if len(agents) > 0: - target_entity = self._world_info.get_entity(target) - if target_entity is not None and isinstance(target_entity, Human): - agents = sorted( - agents, key=cmp_to_key(self._compare_by_distance(target_entity)) - ) - result = agents.pop(0) - info = self._fire_brigade_info_map[result.get_entity_id()] - if info is not None: - info._can_new_action = False - info._target = target - info.command_time = current_time - self._fire_brigade_info_map[result.get_entity_id()] = info - removes.append(target) - - for r in removes: - self._target_humans.remove(r) - - return self - - def get_result(self) -> dict[EntityID, EntityID]: - return self._convert(self._fire_brigade_info_map) - - def _get_action_agents( - self, info_map: dict[EntityID, "DefaultFireTargetAllocator.FireBrigadeInfo"] - ) -> list[FireBrigade]: - result = [] - for entity in self._world_info.get_entities_of_types([FireBrigade]): - if isinstance(entity, FireBrigade): - info = info_map[entity.get_entity_id()] - if info is not None and info._can_new_action: - result.append(entity) - return result - - def _compare_by_distance( - self, target_entity: Entity - ) -> Callable[[Entity, Entity], int]: - def _cmp_func(entity_a: Entity, entity_b: Entity) -> int: - distance_a = self._world_info.get_distance( - target_entity.get_entity_id(), entity_a.get_entity_id() - ) - distance_b = self._world_info.get_distance( - target_entity.get_entity_id(), entity_b.get_entity_id() - ) - if distance_a < distance_b: - return -1 - elif distance_a > distance_b: - return 1 - else: - return 0 - - return _cmp_func - - def _convert( - self, info_map: dict[EntityID, "DefaultFireTargetAllocator.FireBrigadeInfo"] - ) -> dict[EntityID, EntityID]: - result = {} - for entity_id in info_map.keys(): - info = info_map[entity_id] - if info is not None and info._target is not None: - result[entity_id] = info._target - return result - - class FireBrigadeInfo: - def __init__(self, entity_id: EntityID) -> None: - self._agent_id: EntityID = entity_id - self._target: Optional[EntityID] = None - self._can_new_action: bool = True - self.command_time: int = -1 + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._priority_humans: set[EntityID] = set() + self._target_humans: set[EntityID] = set() + self._fire_brigade_info_map: dict[ + EntityID, DefaultFireTargetAllocator.FireBrigadeInfo + ] = {} + + def resume(self, precompute_data: PrecomputeData) -> FireTargetAllocator: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + for entity_id in self._world_info.get_entity_ids_of_types([FireBrigade]): + self._fire_brigade_info_map[entity_id] = self.FireBrigadeInfo(entity_id) + return self + + def prepare(self) -> FireTargetAllocator: + super().prepare() + if self.get_count_prepare() >= 2: + return self + for entity_id in self._world_info.get_entity_ids_of_types([FireBrigade]): + self._fire_brigade_info_map[entity_id] = self.FireBrigadeInfo(entity_id) + return self + + def update_info(self, message_manager: MessageManager) -> FireTargetAllocator: + super().update_info(message_manager) + # TODO: implement after message_manager is implemented + return self + + def calculate(self) -> FireTargetAllocator: + agents = self._get_action_agents(self._fire_brigade_info_map) + removes = [] + current_time = self._agent_info.get_time() + + for target in self._priority_humans: + if len(agents) > 0: + target_entity = self._world_info.get_entity(target) + if target_entity is not None and isinstance(target_entity, Human): + agents = sorted( + agents, key=cmp_to_key(self._compare_by_distance(target_entity)) + ) + result = agents.pop(0) + info = self._fire_brigade_info_map[result.get_entity_id()] + if info is not None: + info._can_new_action = False + info._target = target + info.command_time = current_time + self._fire_brigade_info_map[result.get_entity_id()] = info + removes.append(target) + + for r in removes: + self._priority_humans.remove(r) + removes.clear() + + for target in self._target_humans: + if len(agents) > 0: + target_entity = self._world_info.get_entity(target) + if target_entity is not None and isinstance(target_entity, Human): + agents = sorted( + agents, key=cmp_to_key(self._compare_by_distance(target_entity)) + ) + result = agents.pop(0) + info = self._fire_brigade_info_map[result.get_entity_id()] + if info is not None: + info._can_new_action = False + info._target = target + info.command_time = current_time + self._fire_brigade_info_map[result.get_entity_id()] = info + removes.append(target) + + for r in removes: + self._target_humans.remove(r) + + return self + + def get_result(self) -> dict[EntityID, EntityID]: + return self._convert(self._fire_brigade_info_map) + + def _get_action_agents( + self, info_map: dict[EntityID, "DefaultFireTargetAllocator.FireBrigadeInfo"] + ) -> list[FireBrigade]: + result = [] + for entity in self._world_info.get_entities_of_types([FireBrigade]): + if isinstance(entity, FireBrigade): + info = info_map[entity.get_entity_id()] + if info is not None and info._can_new_action: + result.append(entity) + return result + + def _compare_by_distance( + self, target_entity: Entity + ) -> Callable[[Entity, Entity], int]: + def _cmp_func(entity_a: Entity, entity_b: Entity) -> int: + distance_a = self._world_info.get_distance( + target_entity.get_entity_id(), entity_a.get_entity_id() + ) + distance_b = self._world_info.get_distance( + target_entity.get_entity_id(), entity_b.get_entity_id() + ) + if distance_a < distance_b: + return -1 + elif distance_a > distance_b: + return 1 + else: + return 0 + + return _cmp_func + + def _convert( + self, info_map: dict[EntityID, "DefaultFireTargetAllocator.FireBrigadeInfo"] + ) -> dict[EntityID, EntityID]: + result = {} + for entity_id in info_map.keys(): + info = info_map[entity_id] + if info is not None and info._target is not None: + result[entity_id] = info._target + return result + + class FireBrigadeInfo: + def __init__(self, entity_id: EntityID) -> None: + self._agent_id: EntityID = entity_id + self._target: Optional[EntityID] = None + self._can_new_action: bool = True + self.command_time: int = -1 diff --git a/src/adf_core_python/implement/module/complex/default_human_detector.py b/src/adf_core_python/implement/module/complex/default_human_detector.py index 8f5e860..56d64a7 100644 --- a/src/adf_core_python/implement/module/complex/default_human_detector.py +++ b/src/adf_core_python/implement/module/complex/default_human_detector.py @@ -14,136 +14,135 @@ class DefaultHumanDetector(HumanDetector): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._clustering: Clustering = cast( + Clustering, + module_manager.get_module( + "DefaultHumanDetector.Clustering", + "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", + ), + ) + self.register_sub_module(self._clustering) + + self._result: Optional[EntityID] = None + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + + def calculate(self) -> HumanDetector: + transport_human: Optional[Human] = self._agent_info.some_one_on_board() + if transport_human is not None: + self._result = transport_human.get_entity_id() + return self + + if self._result is not None: + if not self._is_valid_human(self._result): + self._result = None + + if self._result is None: + self._result = self._select_target() + + return self + + def _select_target(self) -> Optional[EntityID]: + if self._result is not None and self._is_valid_human(self._result): + return self._result + + cluster_index: int = self._clustering.get_cluster_index( + self._agent_info.get_entity_id() + ) + cluster_entities: list[Entity] = self._clustering.get_cluster_entities( + cluster_index + ) + + cluster_valid_human_entities: list[Entity] = [ + entity + for entity in cluster_entities + if self._is_valid_human(entity.get_entity_id()) and isinstance(entity, Civilian) + ] + if len(cluster_valid_human_entities) != 0: + nearest_human_entity = cluster_valid_human_entities[0] + nearest_distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), + nearest_human_entity.get_entity_id(), + ) + for entity in cluster_valid_human_entities: + distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), + entity.get_entity_id(), ) - self._clustering: Clustering = cast( - Clustering, - module_manager.get_module( - "DefaultHumanDetector.Clustering", - "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", - ), + if distance < nearest_distance: + nearest_distance = distance + nearest_human_entity = entity + return nearest_human_entity.get_entity_id() + + world_valid_human_entities: list[Entity] = [ + entity + for entity in self._world_info.get_entities_of_types([Civilian]) + if self._is_valid_human(entity.get_entity_id()) + ] + if len(world_valid_human_entities) != 0: + nearest_human_entity = world_valid_human_entities[0] + nearest_distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), + nearest_human_entity.get_entity_id(), + ) + for entity in world_valid_human_entities: + distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), + entity.get_entity_id(), ) - self.register_sub_module(self._clustering) - - self._result: Optional[EntityID] = None - self._logger = get_agent_logger( - f"{self.__class__.__module__}.{self.__class__.__qualname__}", - self._agent_info, - ) - - def calculate(self) -> HumanDetector: - transport_human: Optional[Human] = self._agent_info.some_one_on_board() - if transport_human is not None: - self._result = transport_human.get_entity_id() - return self - - if self._result is not None: - if not self._is_valid_human(self._result): - self._result = None - - if self._result is None: - self._result = self._select_target() - - return self - - def _select_target(self) -> Optional[EntityID]: - if self._result is not None and self._is_valid_human(self._result): - return self._result - - cluster_index: int = self._clustering.get_cluster_index( - self._agent_info.get_entity_id() - ) - cluster_entities: list[Entity] = self._clustering.get_cluster_entities( - cluster_index - ) - - cluster_valid_human_entities: list[Entity] = [ - entity - for entity in cluster_entities - if self._is_valid_human(entity.get_entity_id()) - and isinstance(entity, Civilian) - ] - if len(cluster_valid_human_entities) != 0: - nearest_human_entity = cluster_valid_human_entities[0] - nearest_distance = self._world_info.get_distance( - self._agent_info.get_entity_id(), - nearest_human_entity.get_entity_id(), - ) - for entity in cluster_valid_human_entities: - distance = self._world_info.get_distance( - self._agent_info.get_entity_id(), - entity.get_entity_id(), - ) - if distance < nearest_distance: - nearest_distance = distance - nearest_human_entity = entity - return nearest_human_entity.get_entity_id() - - world_valid_human_entities: list[Entity] = [ - entity - for entity in self._world_info.get_entities_of_types([Civilian]) - if self._is_valid_human(entity.get_entity_id()) - ] - if len(world_valid_human_entities) != 0: - nearest_human_entity = world_valid_human_entities[0] - nearest_distance = self._world_info.get_distance( - self._agent_info.get_entity_id(), - nearest_human_entity.get_entity_id(), - ) - for entity in world_valid_human_entities: - distance = self._world_info.get_distance( - self._agent_info.get_entity_id(), - entity.get_entity_id(), - ) - if distance < nearest_distance: - nearest_distance = distance - nearest_human_entity = entity - return nearest_human_entity.get_entity_id() - - return None - - def _is_valid_human(self, target_entity_id: EntityID) -> bool: - target: Optional[Entity] = self._world_info.get_entity(target_entity_id) - if target is None: - return False - if not isinstance(target, Human): - return False - hp: Optional[int] = target.get_hp() - if hp is None or hp <= 0: - return False - buriedness: Optional[int] = target.get_buriedness() - if buriedness is None: - return False - myself = self._agent_info.get_myself() - if myself is None: - return False - if myself.get_urn() == EntityURN.FIRE_BRIGADE and buriedness == 0: - return False - if myself.get_urn() == EntityURN.AMBULANCE_TEAM and buriedness > 0: - return False - damage: Optional[int] = target.get_damage() - if damage is None or damage == 0: - return False - position_entity_id: Optional[EntityID] = target.get_position() - if position_entity_id is None: - return False - position: Optional[Entity] = self._world_info.get_entity(position_entity_id) - if position is None: - return False - urn: EntityURN = position.get_urn() - if urn == EntityURN.REFUGE or urn == EntityURN.AMBULANCE_TEAM: - return False - - return True - - def get_target_entity_id(self) -> Optional[EntityID]: - return self._result + if distance < nearest_distance: + nearest_distance = distance + nearest_human_entity = entity + return nearest_human_entity.get_entity_id() + + return None + + def _is_valid_human(self, target_entity_id: EntityID) -> bool: + target: Optional[Entity] = self._world_info.get_entity(target_entity_id) + if target is None: + return False + if not isinstance(target, Human): + return False + hp: Optional[int] = target.get_hp() + if hp is None or hp <= 0: + return False + buriedness: Optional[int] = target.get_buriedness() + if buriedness is None: + return False + myself = self._agent_info.get_myself() + if myself is None: + return False + if myself.get_urn() == EntityURN.FIRE_BRIGADE and buriedness == 0: + return False + if myself.get_urn() == EntityURN.AMBULANCE_TEAM and buriedness > 0: + return False + damage: Optional[int] = target.get_damage() + if damage is None or damage == 0: + return False + position_entity_id: Optional[EntityID] = target.get_position() + if position_entity_id is None: + return False + position: Optional[Entity] = self._world_info.get_entity(position_entity_id) + if position is None: + return False + urn: EntityURN = position.get_urn() + if urn == EntityURN.REFUGE or urn == EntityURN.AMBULANCE_TEAM: + return False + + return True + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._result diff --git a/src/adf_core_python/implement/module/complex/default_police_target_allocator.py b/src/adf_core_python/implement/module/complex/default_police_target_allocator.py index 9499ebb..2244d82 100644 --- a/src/adf_core_python/implement/module/complex/default_police_target_allocator.py +++ b/src/adf_core_python/implement/module/complex/default_police_target_allocator.py @@ -2,13 +2,13 @@ from typing import Callable, Optional, cast from rcrscore.entities import ( - Building, - Entity, - EntityID, - GasStation, - PoliceForce, - Refuge, - Road, + Building, + Entity, + EntityID, + GasStation, + PoliceForce, + Refuge, + Road, ) from adf_core_python.core.agent.communication.message_manager import MessageManager @@ -19,173 +19,173 @@ from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.module.complex.police_target_allocator import ( - PoliceTargetAllocator, + PoliceTargetAllocator, ) class DefaultPoliceTargetAllocator(PoliceTargetAllocator): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - self._priority_areas: set[EntityID] = set() - self._target_areas: set[EntityID] = set() - self._agent_info_map: dict[ - EntityID, DefaultPoliceTargetAllocator.PoliceForceInfo - ] = {} - - def resume(self, precompute_data: PrecomputeData) -> PoliceTargetAllocator: - super().resume(precompute_data) - if self.get_count_resume() >= 2: - return self - - for entity_id in self._world_info.get_entity_ids_of_types([PoliceForce]): - self._agent_info_map[entity_id] = self.PoliceForceInfo(entity_id) - for entity in self._world_info.get_entities_of_types( - [Refuge, Building, GasStation] - ): - building: Building = cast(Building, entity) - for entity_id in building.get_neighbors(): - neighbor = self._world_info.get_entity(entity_id) - if isinstance(neighbor, Road): - self._target_areas.add(entity_id) - - for entity in self._world_info.get_entities_of_types([Refuge]): - refuge: Refuge = cast(Refuge, entity) - for entity_id in refuge.get_neighbors(): - neighbor = self._world_info.get_entity(entity_id) - if isinstance(neighbor, Road): - self._priority_areas.add(entity_id) - return self - - def prepare(self) -> PoliceTargetAllocator: - super().prepare() - if self.get_count_prepare() >= 2: - return self - - for entity_id in self._world_info.get_entity_ids_of_types([PoliceForce]): - self._agent_info_map[entity_id] = self.PoliceForceInfo(entity_id) - - for entity in self._world_info.get_entities_of_types( - [Refuge, Building, GasStation] - ): - building: Building = cast(Building, entity) - for entity_id in building.get_neighbors(): - neighbor = self._world_info.get_entity(entity_id) - if isinstance(neighbor, Road): - self._target_areas.add(entity_id) - - for entity in self._world_info.get_entities_of_types([Refuge]): - refuge: Refuge = cast(Refuge, entity) - for entity_id in refuge.get_neighbors(): - neighbor = self._world_info.get_entity(entity_id) - if isinstance(neighbor, Road): - self._priority_areas.add(entity_id) - - return self - - def update_info(self, message_manager: MessageManager) -> PoliceTargetAllocator: - super().update_info(message_manager) - # TODO: implement after message_manager is implemented - return self - - def calculate(self) -> PoliceTargetAllocator: - agents = self._get_action_agents(self._agent_info_map) - removes = [] - current_time = self._agent_info.get_time() - - for target in self._priority_areas: - if len(agents) > 0: - target_entity = self._world_info.get_entity(target) - if target_entity is not None: - agents = sorted( - agents, key=cmp_to_key(self._compare_by_distance(target_entity)) - ) - selected_agent = agents.pop(0) - info = self._agent_info_map[selected_agent.get_entity_id()] - if info is not None: - info._can_new_action = False - info._target = target - info.command_time = current_time - self._agent_info_map[selected_agent.get_entity_id()] = info - removes.append(target) - - for r in removes: - self._priority_areas.remove(r) - - areas = [] - for target in self._target_areas: - target_entity = self._world_info.get_entity(target) - if target_entity is not None: - areas.append(target_entity) - - for agent in agents: - if len(areas) > 0: - areas.sort(key=cmp_to_key(self._compare_by_distance(agent))) - target_area: Entity = areas.pop(0) - self._target_areas.remove(target_area.get_entity_id()) - info = self._agent_info_map[agent.get_entity_id()] - if info is not None: - info._can_new_action = False - info._target = target_area.get_entity_id() - info.command_time = current_time - self._agent_info_map[agent.get_entity_id()] = info - - return self - - def get_result(self) -> dict[EntityID, EntityID]: - return self._convert(self._agent_info_map) - - def _get_action_agents( - self, info_map: dict[EntityID, "DefaultPoliceTargetAllocator.PoliceForceInfo"] - ) -> list[PoliceForce]: - result = [] - for entity in self._world_info.get_entities_of_types([PoliceForce]): - if isinstance(entity, PoliceForce): - info = info_map[entity.get_entity_id()] - if info is not None and info._can_new_action: - result.append(entity) - return result - - def _compare_by_distance( - self, target_entity: Entity - ) -> Callable[[Entity, Entity], int]: - def _cmp_func(entity_a: Entity, entity_b: Entity) -> int: - distance_a = self._world_info.get_distance( - target_entity.get_entity_id(), entity_a.get_entity_id() - ) - distance_b = self._world_info.get_distance( - target_entity.get_entity_id(), entity_b.get_entity_id() - ) - if distance_a < distance_b: - return -1 - elif distance_a > distance_b: - return 1 - else: - return 0 - - return _cmp_func - - def _convert( - self, info_map: dict[EntityID, "DefaultPoliceTargetAllocator.PoliceForceInfo"] - ) -> dict[EntityID, EntityID]: - result: dict[EntityID, EntityID] = {} - for entity_id in info_map.keys(): - info = info_map[entity_id] - if info is not None and info._target is not None: - result[entity_id] = info._target - return result - - class PoliceForceInfo: - def __init__(self, entity_id: EntityID) -> None: - self._agent_id: EntityID = entity_id - self._target: Optional[EntityID] = None - self._can_new_action: bool = True - self.command_time: int = -1 + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._priority_areas: set[EntityID] = set() + self._target_areas: set[EntityID] = set() + self._agent_info_map: dict[ + EntityID, DefaultPoliceTargetAllocator.PoliceForceInfo + ] = {} + + def resume(self, precompute_data: PrecomputeData) -> PoliceTargetAllocator: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + + for entity_id in self._world_info.get_entity_ids_of_types([PoliceForce]): + self._agent_info_map[entity_id] = self.PoliceForceInfo(entity_id) + for entity in self._world_info.get_entities_of_types( + [Refuge, Building, GasStation] + ): + building: Building = cast(Building, entity) + for entity_id in building.get_neighbors(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._target_areas.add(entity_id) + + for entity in self._world_info.get_entities_of_types([Refuge]): + refuge: Refuge = cast(Refuge, entity) + for entity_id in refuge.get_neighbors(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._priority_areas.add(entity_id) + return self + + def prepare(self) -> PoliceTargetAllocator: + super().prepare() + if self.get_count_prepare() >= 2: + return self + + for entity_id in self._world_info.get_entity_ids_of_types([PoliceForce]): + self._agent_info_map[entity_id] = self.PoliceForceInfo(entity_id) + + for entity in self._world_info.get_entities_of_types( + [Refuge, Building, GasStation] + ): + building: Building = cast(Building, entity) + for entity_id in building.get_neighbors(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._target_areas.add(entity_id) + + for entity in self._world_info.get_entities_of_types([Refuge]): + refuge: Refuge = cast(Refuge, entity) + for entity_id in refuge.get_neighbors(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._priority_areas.add(entity_id) + + return self + + def update_info(self, message_manager: MessageManager) -> PoliceTargetAllocator: + super().update_info(message_manager) + # TODO: implement after message_manager is implemented + return self + + def calculate(self) -> PoliceTargetAllocator: + agents = self._get_action_agents(self._agent_info_map) + removes = [] + current_time = self._agent_info.get_time() + + for target in self._priority_areas: + if len(agents) > 0: + target_entity = self._world_info.get_entity(target) + if target_entity is not None: + agents = sorted( + agents, key=cmp_to_key(self._compare_by_distance(target_entity)) + ) + selected_agent = agents.pop(0) + info = self._agent_info_map[selected_agent.get_entity_id()] + if info is not None: + info._can_new_action = False + info._target = target + info.command_time = current_time + self._agent_info_map[selected_agent.get_entity_id()] = info + removes.append(target) + + for r in removes: + self._priority_areas.remove(r) + + areas = [] + for target in self._target_areas: + target_entity = self._world_info.get_entity(target) + if target_entity is not None: + areas.append(target_entity) + + for agent in agents: + if len(areas) > 0: + areas.sort(key=cmp_to_key(self._compare_by_distance(agent))) + target_area: Entity = areas.pop(0) + self._target_areas.remove(target_area.get_entity_id()) + info = self._agent_info_map[agent.get_entity_id()] + if info is not None: + info._can_new_action = False + info._target = target_area.get_entity_id() + info.command_time = current_time + self._agent_info_map[agent.get_entity_id()] = info + + return self + + def get_result(self) -> dict[EntityID, EntityID]: + return self._convert(self._agent_info_map) + + def _get_action_agents( + self, info_map: dict[EntityID, "DefaultPoliceTargetAllocator.PoliceForceInfo"] + ) -> list[PoliceForce]: + result = [] + for entity in self._world_info.get_entities_of_types([PoliceForce]): + if isinstance(entity, PoliceForce): + info = info_map[entity.get_entity_id()] + if info is not None and info._can_new_action: + result.append(entity) + return result + + def _compare_by_distance( + self, target_entity: Entity + ) -> Callable[[Entity, Entity], int]: + def _cmp_func(entity_a: Entity, entity_b: Entity) -> int: + distance_a = self._world_info.get_distance( + target_entity.get_entity_id(), entity_a.get_entity_id() + ) + distance_b = self._world_info.get_distance( + target_entity.get_entity_id(), entity_b.get_entity_id() + ) + if distance_a < distance_b: + return -1 + elif distance_a > distance_b: + return 1 + else: + return 0 + + return _cmp_func + + def _convert( + self, info_map: dict[EntityID, "DefaultPoliceTargetAllocator.PoliceForceInfo"] + ) -> dict[EntityID, EntityID]: + result: dict[EntityID, EntityID] = {} + for entity_id in info_map.keys(): + info = info_map[entity_id] + if info is not None and info._target is not None: + result[entity_id] = info._target + return result + + class PoliceForceInfo: + def __init__(self, entity_id: EntityID) -> None: + self._agent_id: EntityID = entity_id + self._target: Optional[EntityID] = None + self._can_new_action: bool = True + self.command_time: int = -1 diff --git a/src/adf_core_python/implement/module/complex/default_road_detector.py b/src/adf_core_python/implement/module/complex/default_road_detector.py index ed7a02d..40655c4 100644 --- a/src/adf_core_python/implement/module/complex/default_road_detector.py +++ b/src/adf_core_python/implement/module/complex/default_road_detector.py @@ -10,147 +10,143 @@ from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.module.algorithm.path_planning import ( - PathPlanning, + PathPlanning, ) from adf_core_python.core.component.module.complex.road_detector import RoadDetector class DefaultRoadDetector(RoadDetector): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - self._path_planning: PathPlanning = cast( - PathPlanning, - module_manager.get_module( - "DefaultRoadDetector.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - - self.register_sub_module(self._path_planning) - self._result: Optional[EntityID] = None - - def precompute(self, precompute_data: PrecomputeData) -> RoadDetector: - super().precompute(precompute_data) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultRoadDetector.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + + self.register_sub_module(self._path_planning) + self._result: Optional[EntityID] = None + + def precompute(self, precompute_data: PrecomputeData) -> RoadDetector: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> RoadDetector: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + + self._target_areas: set[EntityID] = set() + entities = self._world_info.get_entities_of_types([Refuge, Building, GasStation]) + for entity in entities: + if not isinstance(entity, Building): + continue + for entity_id in entity.get_neighbors(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._target_areas.add(entity_id) + + self._priority_roads = set() + for entity in self._world_info.get_entities_of_types([Refuge]): + if not isinstance(entity, Building): + continue + for entity_id in entity.get_neighbors(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._priority_roads.add(entity_id) + + return self + + def prepare(self) -> RoadDetector: + super().prepare() + if self.get_count_prepare() >= 2: + return self + + self._target_areas = set() + entities = self._world_info.get_entities_of_types([Refuge, Building, GasStation]) + for entity in entities: + building: Building = cast(Building, entity) + for entity_id in building.get_neighbors(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._target_areas.add(entity_id) + + self._priority_roads = set() + for entity in self._world_info.get_entities_of_types([Refuge]): + refuge: Refuge = cast(Refuge, entity) + for entity_id in refuge.get_neighbors(): + neighbor = self._world_info.get_entity(entity_id) + if isinstance(neighbor, Road): + self._priority_roads.add(entity_id) + + return self + + def update_info(self, message_manager: MessageManager) -> RoadDetector: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self + + if self._result is not None: + if self._agent_info.get_position_entity_id == self._result: + entity = self._world_info.get_entity(self._result) + if isinstance(entity, Building): + self._result = None + elif isinstance(entity, Road): + road = entity + if road.get_blockades() == []: + self._target_areas.remove(self._result) + self._result = None + + return self + + def calculate(self) -> RoadDetector: + if self._result is None: + position_entity_id = self._agent_info.get_position_entity_id() + if position_entity_id is None: return self - - def resume(self, precompute_data: PrecomputeData) -> RoadDetector: - super().resume(precompute_data) - if self.get_count_resume() >= 2: - return self - - self._target_areas: set[EntityID] = set() - entities = self._world_info.get_entities_of_types( - [Refuge, Building, GasStation] - ) - for entity in entities: - if not isinstance(entity, Building): - continue - for entity_id in entity.get_neighbors(): - neighbor = self._world_info.get_entity(entity_id) - if isinstance(neighbor, Road): - self._target_areas.add(entity_id) - - self._priority_roads = set() - for entity in self._world_info.get_entities_of_types([Refuge]): - if not isinstance(entity, Building): - continue - for entity_id in entity.get_neighbors(): - neighbor = self._world_info.get_entity(entity_id) - if isinstance(neighbor, Road): - self._priority_roads.add(entity_id) - + if position_entity_id in self._target_areas: + self._result = position_entity_id return self - - def prepare(self) -> RoadDetector: - super().prepare() - if self.get_count_prepare() >= 2: - return self - - self._target_areas = set() - entities = self._world_info.get_entities_of_types( - [Refuge, Building, GasStation] + remove_list = [] + for entity_id in self._priority_roads: + if entity_id not in self._target_areas: + remove_list.append(entity_id) + + self._priority_roads = self._priority_roads - set(remove_list) + if len(self._priority_roads) > 0: + agent_position = self._agent_info.get_position_entity_id() + if agent_position is None: + return self + _nearest_target_area = agent_position + _nearest_distance = float("inf") + for target_area in self._target_areas: + if ( + self._world_info.get_distance(agent_position, target_area) + < _nearest_distance + ): + _nearest_target_area = target_area + _nearest_distance = self._world_info.get_distance( + agent_position, target_area + ) + path: list[EntityID] = self._path_planning.get_path( + agent_position, _nearest_target_area ) - for entity in entities: - building: Building = cast(Building, entity) - for entity_id in building.get_neighbors(): - neighbor = self._world_info.get_entity(entity_id) - if isinstance(neighbor, Road): - self._target_areas.add(entity_id) - - self._priority_roads = set() - for entity in self._world_info.get_entities_of_types([Refuge]): - refuge: Refuge = cast(Refuge, entity) - for entity_id in refuge.get_neighbors(): - neighbor = self._world_info.get_entity(entity_id) - if isinstance(neighbor, Road): - self._priority_roads.add(entity_id) - - return self - - def update_info(self, message_manager: MessageManager) -> RoadDetector: - super().update_info(message_manager) - if self.get_count_update_info() >= 2: - return self - - if self._result is not None: - if self._agent_info.get_position_entity_id == self._result: - entity = self._world_info.get_entity(self._result) - if isinstance(entity, Building): - self._result = None - elif isinstance(entity, Road): - road = entity - if road.get_blockades() == []: - self._target_areas.remove(self._result) - self._result = None + if path is not None and len(path) > 0: + self._result = path[-1] - return self - - def calculate(self) -> RoadDetector: - if self._result is None: - position_entity_id = self._agent_info.get_position_entity_id() - if position_entity_id is None: - return self - if position_entity_id in self._target_areas: - self._result = position_entity_id - return self - remove_list = [] - for entity_id in self._priority_roads: - if entity_id not in self._target_areas: - remove_list.append(entity_id) - - self._priority_roads = self._priority_roads - set(remove_list) - if len(self._priority_roads) > 0: - agent_position = self._agent_info.get_position_entity_id() - if agent_position is None: - return self - _nearest_target_area = agent_position - _nearest_distance = float("inf") - for target_area in self._target_areas: - if ( - self._world_info.get_distance(agent_position, target_area) - < _nearest_distance - ): - _nearest_target_area = target_area - _nearest_distance = self._world_info.get_distance( - agent_position, target_area - ) - path: list[EntityID] = self._path_planning.get_path( - agent_position, _nearest_target_area - ) - if path is not None and len(path) > 0: - self._result = path[-1] - - return self + return self - def get_target_entity_id(self) -> Optional[EntityID]: - return self._result + def get_target_entity_id(self) -> Optional[EntityID]: + return self._result diff --git a/src/adf_core_python/implement/module/complex/default_search.py b/src/adf_core_python/implement/module/complex/default_search.py index 1050029..9d303ed 100644 --- a/src/adf_core_python/implement/module/complex/default_search.py +++ b/src/adf_core_python/implement/module/complex/default_search.py @@ -15,90 +15,90 @@ class DefaultSearch(Search): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - self._unreached_building_ids: set[EntityID] = set() - self._result: Optional[EntityID] = None - - self._clustering: Clustering = cast( - Clustering, - module_manager.get_module( - "DefaultSearch.Clustering", - "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", - ), - ) - - self._path_planning: PathPlanning = cast( - PathPlanning, - module_manager.get_module( - "DefaultSearch.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - - self._logger = get_agent_logger( - f"{self.__class__.__module__}.{self.__class__.__qualname__}", - self._agent_info, - ) - - self.register_sub_module(self._clustering) - self.register_sub_module(self._path_planning) - - def update_info(self, message_manager: MessageManager) -> Search: - super().update_info(message_manager) - if self.get_count_update_info() > 1: - return self - - self._logger.debug( - f"unreached_building_ids: {[str(id) for id in self._unreached_building_ids]}" - ) - - searched_building_id = self._agent_info.get_position_entity_id() - if searched_building_id is not None: - self._unreached_building_ids.discard(searched_building_id) - - if len(self._unreached_building_ids) == 0: - self._unreached_building_ids = self._get_search_targets() - - return self - - def calculate(self) -> Search: - nearest_building_id: Optional[EntityID] = None - nearest_distance: Optional[float] = None - for building_id in self._unreached_building_ids: - distance = self._world_info.get_distance( - self._agent_info.get_entity_id(), building_id - ) - if nearest_distance is None or distance < nearest_distance: - nearest_building_id = building_id - nearest_distance = distance - self._result = nearest_building_id - return self - - def get_target_entity_id(self) -> Optional[EntityID]: - return self._result - - def _get_search_targets(self) -> set[EntityID]: - cluster_index: int = self._clustering.get_cluster_index( - self._agent_info.get_entity_id() - ) - cluster_entities: list[Entity] = self._clustering.get_cluster_entities( - cluster_index - ) - building_entity_ids: list[EntityID] = [ - entity.get_entity_id() - for entity in cluster_entities - if isinstance(entity, Building) and not isinstance(entity, Refuge) - ] - - return set(building_entity_ids) + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + self._unreached_building_ids: set[EntityID] = set() + self._result: Optional[EntityID] = None + + self._clustering: Clustering = cast( + Clustering, + module_manager.get_module( + "DefaultSearch.Clustering", + "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", + ), + ) + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultSearch.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + + self.register_sub_module(self._clustering) + self.register_sub_module(self._path_planning) + + def update_info(self, message_manager: MessageManager) -> Search: + super().update_info(message_manager) + if self.get_count_update_info() > 1: + return self + + self._logger.debug( + f"unreached_building_ids: {[str(id) for id in self._unreached_building_ids]}" + ) + + searched_building_id = self._agent_info.get_position_entity_id() + if searched_building_id is not None: + self._unreached_building_ids.discard(searched_building_id) + + if len(self._unreached_building_ids) == 0: + self._unreached_building_ids = self._get_search_targets() + + return self + + def calculate(self) -> Search: + nearest_building_id: Optional[EntityID] = None + nearest_distance: Optional[float] = None + for building_id in self._unreached_building_ids: + distance = self._world_info.get_distance( + self._agent_info.get_entity_id(), building_id + ) + if nearest_distance is None or distance < nearest_distance: + nearest_building_id = building_id + nearest_distance = distance + self._result = nearest_building_id + return self + + def get_target_entity_id(self) -> Optional[EntityID]: + return self._result + + def _get_search_targets(self) -> set[EntityID]: + cluster_index: int = self._clustering.get_cluster_index( + self._agent_info.get_entity_id() + ) + cluster_entities: list[Entity] = self._clustering.get_cluster_entities( + cluster_index + ) + building_entity_ids: list[EntityID] = [ + entity.get_entity_id() + for entity in cluster_entities + if isinstance(entity, Building) and not isinstance(entity, Refuge) + ] + + return set(building_entity_ids) diff --git a/src/adf_core_python/implement/tactics/default_tactics_ambulance_center.py b/src/adf_core_python/implement/tactics/default_tactics_ambulance_center.py index 68c9ff8..f7108a5 100644 --- a/src/adf_core_python/implement/tactics/default_tactics_ambulance_center.py +++ b/src/adf_core_python/implement/tactics/default_tactics_ambulance_center.py @@ -11,91 +11,89 @@ from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.centralized.command_picker import CommandPicker from adf_core_python.core.component.module.complex.target_allocator import ( - TargetAllocator, + TargetAllocator, ) from adf_core_python.core.component.tactics.tactics_ambulance_center import ( - TacticsAmbulanceCenter, + TacticsAmbulanceCenter, ) class DefaultTacticsAmbulanceCenter(TacticsAmbulanceCenter): - def initialize( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self._allocator: TargetAllocator = cast( - TargetAllocator, - module_manager.get_module( - "DefaultTacticsAmbulanceCenter.TargetAllocator", - "adf_core_python.implement.module.complex.default_ambulance_target_allocator.DefaultAmbulanceTargetAllocator", - ), - ) - self._picker: CommandPicker = module_manager.get_command_picker( - "DefaultTacticsAmbulanceCenter.CommandPicker", - "adf_core_python.implement.centralized.default_command_picker_ambulance.DefaultCommandPickerAmbulance", - ) - self.register_module(self._allocator) - self.register_command_picker(self._picker) + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self._allocator: TargetAllocator = cast( + TargetAllocator, + module_manager.get_module( + "DefaultTacticsAmbulanceCenter.TargetAllocator", + "adf_core_python.implement.module.complex.default_ambulance_target_allocator.DefaultAmbulanceTargetAllocator", + ), + ) + self._picker: CommandPicker = module_manager.get_command_picker( + "DefaultTacticsAmbulanceCenter.CommandPicker", + "adf_core_python.implement.centralized.default_command_picker_ambulance.DefaultCommandPickerAmbulance", + ) + self.register_module(self._allocator) + self.register_command_picker(self._picker) - def precompute( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self.module_precompute(precompute_data) + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_precompute(precompute_data) - def resume( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self.module_resume(precompute_data) + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_resume(precompute_data) - def prepare( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - develop_data: DevelopData, - ) -> None: - self.module_prepare() + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + self.module_prepare() - def think( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self.module_update_info(message_manager) + def think( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_update_info(message_manager) - allocation_result: dict[EntityID, EntityID] = ( - self._allocator.calculate().get_result() - ) - for message in ( - self._picker.set_allocator_result(allocation_result) - .calculate() - .get_result() - ): - message_manager.add_message(message) + allocation_result: dict[EntityID, EntityID] = ( + self._allocator.calculate().get_result() + ) + for message in ( + self._picker.set_allocator_result(allocation_result).calculate().get_result() + ): + message_manager.add_message(message) diff --git a/src/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/src/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 688c845..2ab674b 100644 --- a/src/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/src/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -6,13 +6,13 @@ from adf_core_python.core.agent.action.common.action_rest import ActionRest from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( - CommandAmbulance, + CommandAmbulance, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( - CommandScout, + CommandScout, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -23,182 +23,176 @@ from adf_core_python.core.component.module.complex.human_detector import HumanDetector from adf_core_python.core.component.module.complex.search import Search from adf_core_python.core.component.tactics.tactics_ambulance_team import ( - TacticsAmbulanceTeam, + TacticsAmbulanceTeam, ) class DefaultTacticsAmbulanceTeam(TacticsAmbulanceTeam): - def initialize( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - # world_info.index_class() - super().initialize( - agent_info, - world_info, - scenario_info, - module_manager, - precompute_data, - message_manager, - develop_data, - ) - - self._search: Search = cast( - Search, - module_manager.get_module( - "DefaultTacticsAmbulanceTeam.Search", - "adf_core_python.implement.module.complex.default_search.DefaultSearch", - ), - ) - self._human_detector: HumanDetector = cast( - HumanDetector, - module_manager.get_module( - "DefaultTacticsAmbulanceTeam.HumanDetector", - "adf_core_python.implement.module.complex.default_human_detector.DefaultHumanDetector", - ), - ) - self._action_transport = module_manager.get_extend_action( - "DefaultTacticsAmbulanceTeam.ExtendActionTransport", - "adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport", - ) - self._action_ext_move = module_manager.get_extend_action( - "DefaultTacticsAmbulanceTeam.ExtendActionMove", - "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", - ) - self._command_executor_ambulance = module_manager.get_command_executor( - "DefaultTacticsAmbulanceTeam.CommandExecutorAmbulance", - "adf_core_python.implement.centralized.default_command_executor_ambulance.DefaultCommandExecutorAmbulance", - ) - self._command_executor_scout = module_manager.get_command_executor( - "DefaultTacticsAmbulanceTeam.CommandExecutorScout", - "adf_core_python.implement.centralized.default_command_executor_scout.DefaultCommandExecutorScout", - ) - - self.register_module(self._search) - self.register_module(self._human_detector) - self.register_action(self._action_transport) - self.register_action(self._action_ext_move) - self.register_command_executor(self._command_executor_ambulance) - self.register_command_executor(self._command_executor_scout) - - self._recent_command: Optional[StandardMessage] = None - - def precompute( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self.module_precompute(precompute_data) - - def resume( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self.module_resume(precompute_data) - - def prepare( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - develop_data: DevelopData, - ) -> None: - self.module_prepare() - - def think( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> Action: - self.reset_count() - self.module_update_info(message_manager) - - agent: AmbulanceTeam = cast(AmbulanceTeam, agent_info.get_myself()) # noqa: F841 - entity_id = agent_info.get_entity_id() # noqa: F841 - - self._logger.debug( - f"received messages: {[str(message) for message in message_manager.get_received_message_list()]}, help: {message_manager.get_heard_agent_help_message_count()}" - ) - - for message in message_manager.get_received_message_list(): - if isinstance(message, CommandScout): - if ( - message.get_command_executor_agent_entity_id() - == agent_info.get_entity_id() - ): - self._recent_command = message - self._command_executor_scout.set_command(message) - if isinstance(message, CommandAmbulance): - if ( - message.get_command_executor_agent_entity_id() - == agent_info.get_entity_id() - ): - self._recent_command = message - self._command_executor_ambulance.set_command(message) - - if self._recent_command is not None: - action: Optional[Action] = None - if isinstance(self._recent_command, CommandScout): - action = self._command_executor_scout.calculate().get_action() - elif isinstance(self._recent_command, CommandAmbulance): - action = self._command_executor_ambulance.calculate().get_action() - if action is not None: - self._logger.debug( - f"action decided by command: {action}", time=agent_info.get_time() - ) - return action - - target_entity_id = self._human_detector.calculate().get_target_entity_id() - self._logger.debug( - f"human detector target_entity_id: {target_entity_id}", - time=agent_info.get_time(), - ) - if target_entity_id is not None: - action = ( - self._action_transport.set_target_entity_id(target_entity_id) - .calculate() - .get_action() - ) - if action is not None: - self._logger.debug(f"action: {action}", time=agent_info.get_time()) - return action - - target_entity_id = self._search.calculate().get_target_entity_id() + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + # world_info.index_class() + super().initialize( + agent_info, + world_info, + scenario_info, + module_manager, + precompute_data, + message_manager, + develop_data, + ) + + self._search: Search = cast( + Search, + module_manager.get_module( + "DefaultTacticsAmbulanceTeam.Search", + "adf_core_python.implement.module.complex.default_search.DefaultSearch", + ), + ) + self._human_detector: HumanDetector = cast( + HumanDetector, + module_manager.get_module( + "DefaultTacticsAmbulanceTeam.HumanDetector", + "adf_core_python.implement.module.complex.default_human_detector.DefaultHumanDetector", + ), + ) + self._action_transport = module_manager.get_extend_action( + "DefaultTacticsAmbulanceTeam.ExtendActionTransport", + "adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport", + ) + self._action_ext_move = module_manager.get_extend_action( + "DefaultTacticsAmbulanceTeam.ExtendActionMove", + "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", + ) + self._command_executor_ambulance = module_manager.get_command_executor( + "DefaultTacticsAmbulanceTeam.CommandExecutorAmbulance", + "adf_core_python.implement.centralized.default_command_executor_ambulance.DefaultCommandExecutorAmbulance", + ) + self._command_executor_scout = module_manager.get_command_executor( + "DefaultTacticsAmbulanceTeam.CommandExecutorScout", + "adf_core_python.implement.centralized.default_command_executor_scout.DefaultCommandExecutorScout", + ) + + self.register_module(self._search) + self.register_module(self._human_detector) + self.register_action(self._action_transport) + self.register_action(self._action_ext_move) + self.register_command_executor(self._command_executor_ambulance) + self.register_command_executor(self._command_executor_scout) + + self._recent_command: Optional[StandardMessage] = None + + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_precompute(precompute_data) + + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_resume(precompute_data) + + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + self.module_prepare() + + def think( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> Action: + self.reset_count() + self.module_update_info(message_manager) + + agent: AmbulanceTeam = cast(AmbulanceTeam, agent_info.get_myself()) # noqa: F841 + entity_id = agent_info.get_entity_id() # noqa: F841 + + self._logger.debug( + f"received messages: {[str(message) for message in message_manager.get_received_message_list()]}, help: {message_manager.get_heard_agent_help_message_count()}" + ) + + for message in message_manager.get_received_message_list(): + if isinstance(message, CommandScout): + if message.get_command_executor_agent_entity_id() == agent_info.get_entity_id(): + self._recent_command = message + self._command_executor_scout.set_command(message) + if isinstance(message, CommandAmbulance): + if message.get_command_executor_agent_entity_id() == agent_info.get_entity_id(): + self._recent_command = message + self._command_executor_ambulance.set_command(message) + + if self._recent_command is not None: + action: Optional[Action] = None + if isinstance(self._recent_command, CommandScout): + action = self._command_executor_scout.calculate().get_action() + elif isinstance(self._recent_command, CommandAmbulance): + action = self._command_executor_ambulance.calculate().get_action() + if action is not None: self._logger.debug( - f"search target_entity_id: {target_entity_id}", time=agent_info.get_time() + f"action decided by command: {action}", time=agent_info.get_time() ) - if target_entity_id is not None: - action = ( - self._action_ext_move.set_target_entity_id(target_entity_id) - .calculate() - .get_action() - ) - if action is not None: - self._logger.debug(f"action: {action}", time=agent_info.get_time()) - return action - - return ActionRest() + return action + + target_entity_id = self._human_detector.calculate().get_target_entity_id() + self._logger.debug( + f"human detector target_entity_id: {target_entity_id}", + time=agent_info.get_time(), + ) + if target_entity_id is not None: + action = ( + self._action_transport.set_target_entity_id(target_entity_id) + .calculate() + .get_action() + ) + if action is not None: + self._logger.debug(f"action: {action}", time=agent_info.get_time()) + return action + + target_entity_id = self._search.calculate().get_target_entity_id() + self._logger.debug( + f"search target_entity_id: {target_entity_id}", time=agent_info.get_time() + ) + if target_entity_id is not None: + action = ( + self._action_ext_move.set_target_entity_id(target_entity_id) + .calculate() + .get_action() + ) + if action is not None: + self._logger.debug(f"action: {action}", time=agent_info.get_time()) + return action + + return ActionRest() diff --git a/src/adf_core_python/implement/tactics/default_tactics_fire_brigade.py b/src/adf_core_python/implement/tactics/default_tactics_fire_brigade.py index de4f706..5b9b8db 100644 --- a/src/adf_core_python/implement/tactics/default_tactics_fire_brigade.py +++ b/src/adf_core_python/implement/tactics/default_tactics_fire_brigade.py @@ -6,13 +6,13 @@ from adf_core_python.core.agent.action.common.action_rest import ActionRest from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.communication.standard.bundle.centralized.command_fire import ( - CommandFire, + CommandFire, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( - CommandScout, + CommandScout, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -23,178 +23,172 @@ from adf_core_python.core.component.module.complex.human_detector import HumanDetector from adf_core_python.core.component.module.complex.search import Search from adf_core_python.core.component.tactics.tactics_fire_brigade import ( - TacticsFireBrigade, + TacticsFireBrigade, ) class DefaultTacticsFireBrigade(TacticsFireBrigade): - def initialize( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - # world_info.index_class() - super().initialize( - agent_info, - world_info, - scenario_info, - module_manager, - precompute_data, - message_manager, - develop_data, - ) - - self._search: Search = cast( - Search, - module_manager.get_module( - "DefaultTacticsFireBrigade.Search", - "adf_core_python.implement.module.complex.default_search.DefaultSearch", - ), - ) - self._human_detector: HumanDetector = cast( - HumanDetector, - module_manager.get_module( - "DefaultTacticsFireBrigade.HumanDetector", - "adf_core_python.implement.module.complex.default_human_detector.DefaultHumanDetector", - ), - ) - self._action_rescue = module_manager.get_extend_action( - "DefaultTacticsFireBrigade.ExtendActionRescue", - "adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue", - ) - self._action_ext_move = module_manager.get_extend_action( - "DefaultTacticsAmbulanceTeam.ExtendActionMove", - "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", - ) - self._command_executor_fire = module_manager.get_command_executor( - "DefaultTacticsFireBrigade.CommandExecutorFire", - "adf_core_python.implement.centralized.default_command_executor_fire.DefaultCommandExecutorFire", - ) - self._command_executor_scout = module_manager.get_command_executor( - "DefaultTacticsAmbulanceTeam.CommandExecutorScout", - "adf_core_python.implement.centralized.default_command_executor_scout.DefaultCommandExecutorScout", - ) - - self.register_module(self._search) - self.register_module(self._human_detector) - self.register_action(self._action_rescue) - self.register_action(self._action_ext_move) - self.register_command_executor(self._command_executor_fire) - self.register_command_executor(self._command_executor_scout) - - self._recent_command: Optional[StandardMessage] = None - - def precompute( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self.module_precompute(precompute_data) - - def resume( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self.module_resume(precompute_data) - - def prepare( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - develop_data: DevelopData, - ) -> None: - self.module_prepare() - - def think( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> Action: - self.reset_count() - self.module_update_info(message_manager) - - agent: FireBrigade = cast(FireBrigade, agent_info.get_myself()) # noqa: F841 - entity_id = agent_info.get_entity_id() # noqa: F841 - - for message in message_manager.get_received_message_list(): - if isinstance(message, CommandScout): - if ( - message.get_command_executor_agent_entity_id() - == agent_info.get_entity_id() - ): - self._recent_command = message - self._command_executor_scout.set_command(command=message) - if isinstance(message, CommandFire): - if ( - message.get_command_executor_agent_entity_id() - == agent_info.get_entity_id() - ): - self._recent_command = message - self._command_executor_fire.set_command(message) - - if self._recent_command is not None: - action: Optional[Action] = None - if isinstance(self._recent_command, CommandScout): - action = self._command_executor_scout.calculate().get_action() - elif isinstance(self._recent_command, CommandFire): - action = self._command_executor_fire.calculate().get_action() - if action is not None: - self._logger.debug( - f"action decided by command: {action}", time=agent_info.get_time() - ) - return action - - target_entity_id = self._human_detector.calculate().get_target_entity_id() - self._logger.debug( - f"human detector target_entity_id: {target_entity_id}", - time=agent_info.get_time(), - ) - if target_entity_id is not None: - action = ( - self._action_rescue.set_target_entity_id(target_entity_id) - .calculate() - .get_action() - ) - if action is not None: - self._logger.debug(f"action: {action}", time=agent_info.get_time()) - return action - - target_entity_id = self._search.calculate().get_target_entity_id() + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + # world_info.index_class() + super().initialize( + agent_info, + world_info, + scenario_info, + module_manager, + precompute_data, + message_manager, + develop_data, + ) + + self._search: Search = cast( + Search, + module_manager.get_module( + "DefaultTacticsFireBrigade.Search", + "adf_core_python.implement.module.complex.default_search.DefaultSearch", + ), + ) + self._human_detector: HumanDetector = cast( + HumanDetector, + module_manager.get_module( + "DefaultTacticsFireBrigade.HumanDetector", + "adf_core_python.implement.module.complex.default_human_detector.DefaultHumanDetector", + ), + ) + self._action_rescue = module_manager.get_extend_action( + "DefaultTacticsFireBrigade.ExtendActionRescue", + "adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue", + ) + self._action_ext_move = module_manager.get_extend_action( + "DefaultTacticsAmbulanceTeam.ExtendActionMove", + "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", + ) + self._command_executor_fire = module_manager.get_command_executor( + "DefaultTacticsFireBrigade.CommandExecutorFire", + "adf_core_python.implement.centralized.default_command_executor_fire.DefaultCommandExecutorFire", + ) + self._command_executor_scout = module_manager.get_command_executor( + "DefaultTacticsAmbulanceTeam.CommandExecutorScout", + "adf_core_python.implement.centralized.default_command_executor_scout.DefaultCommandExecutorScout", + ) + + self.register_module(self._search) + self.register_module(self._human_detector) + self.register_action(self._action_rescue) + self.register_action(self._action_ext_move) + self.register_command_executor(self._command_executor_fire) + self.register_command_executor(self._command_executor_scout) + + self._recent_command: Optional[StandardMessage] = None + + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_precompute(precompute_data) + + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_resume(precompute_data) + + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + self.module_prepare() + + def think( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> Action: + self.reset_count() + self.module_update_info(message_manager) + + agent: FireBrigade = cast(FireBrigade, agent_info.get_myself()) # noqa: F841 + entity_id = agent_info.get_entity_id() # noqa: F841 + + for message in message_manager.get_received_message_list(): + if isinstance(message, CommandScout): + if message.get_command_executor_agent_entity_id() == agent_info.get_entity_id(): + self._recent_command = message + self._command_executor_scout.set_command(command=message) + if isinstance(message, CommandFire): + if message.get_command_executor_agent_entity_id() == agent_info.get_entity_id(): + self._recent_command = message + self._command_executor_fire.set_command(message) + + if self._recent_command is not None: + action: Optional[Action] = None + if isinstance(self._recent_command, CommandScout): + action = self._command_executor_scout.calculate().get_action() + elif isinstance(self._recent_command, CommandFire): + action = self._command_executor_fire.calculate().get_action() + if action is not None: self._logger.debug( - f"search target_entity_id: {target_entity_id}", time=agent_info.get_time() + f"action decided by command: {action}", time=agent_info.get_time() ) - if target_entity_id is not None: - action = ( - self._action_ext_move.set_target_entity_id(target_entity_id) - .calculate() - .get_action() - ) - if action is not None: - self._logger.debug(f"action: {action}", time=agent_info.get_time()) - return action - - return ActionRest() + return action + + target_entity_id = self._human_detector.calculate().get_target_entity_id() + self._logger.debug( + f"human detector target_entity_id: {target_entity_id}", + time=agent_info.get_time(), + ) + if target_entity_id is not None: + action = ( + self._action_rescue.set_target_entity_id(target_entity_id) + .calculate() + .get_action() + ) + if action is not None: + self._logger.debug(f"action: {action}", time=agent_info.get_time()) + return action + + target_entity_id = self._search.calculate().get_target_entity_id() + self._logger.debug( + f"search target_entity_id: {target_entity_id}", time=agent_info.get_time() + ) + if target_entity_id is not None: + action = ( + self._action_ext_move.set_target_entity_id(target_entity_id) + .calculate() + .get_action() + ) + if action is not None: + self._logger.debug(f"action: {action}", time=agent_info.get_time()) + return action + + return ActionRest() diff --git a/src/adf_core_python/implement/tactics/default_tactics_fire_station.py b/src/adf_core_python/implement/tactics/default_tactics_fire_station.py index ef6425d..099d2b1 100644 --- a/src/adf_core_python/implement/tactics/default_tactics_fire_station.py +++ b/src/adf_core_python/implement/tactics/default_tactics_fire_station.py @@ -11,91 +11,89 @@ from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.centralized.command_picker import CommandPicker from adf_core_python.core.component.module.complex.target_allocator import ( - TargetAllocator, + TargetAllocator, ) from adf_core_python.core.component.tactics.tactics_fire_station import ( - TacticsFireStation, + TacticsFireStation, ) class DefaultTacticsFireStation(TacticsFireStation): - def initialize( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self._allocator: TargetAllocator = cast( - TargetAllocator, - module_manager.get_module( - "DefaultTacticsFireStation.TargetAllocator", - "adf_core_python.implement.module.complex.default_fire_target_allocator.DefaultFireTargetAllocator", - ), - ) - self._picker: CommandPicker = module_manager.get_command_picker( - "DefaultTacticsFireStation.CommandPicker", - "adf_core_python.implement.centralized.default_command_picker_fire.DefaultCommandPickerFire", - ) - self.register_module(self._allocator) - self.register_command_picker(self._picker) + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self._allocator: TargetAllocator = cast( + TargetAllocator, + module_manager.get_module( + "DefaultTacticsFireStation.TargetAllocator", + "adf_core_python.implement.module.complex.default_fire_target_allocator.DefaultFireTargetAllocator", + ), + ) + self._picker: CommandPicker = module_manager.get_command_picker( + "DefaultTacticsFireStation.CommandPicker", + "adf_core_python.implement.centralized.default_command_picker_fire.DefaultCommandPickerFire", + ) + self.register_module(self._allocator) + self.register_command_picker(self._picker) - def resume( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self.module_resume(precompute_data) + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_resume(precompute_data) - def precompute( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self.module_precompute(precompute_data) + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_precompute(precompute_data) - def prepare( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - develop_data: DevelopData, - ) -> None: - self.module_prepare() + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + self.module_prepare() - def think( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self.module_update_info(message_manager) + def think( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_update_info(message_manager) - allocation_result: dict[EntityID, EntityID] = ( - self._allocator.calculate().get_result() - ) - for message in ( - self._picker.set_allocator_result(allocation_result) - .calculate() - .get_result() - ): - message_manager.add_message(message) + allocation_result: dict[EntityID, EntityID] = ( + self._allocator.calculate().get_result() + ) + for message in ( + self._picker.set_allocator_result(allocation_result).calculate().get_result() + ): + message_manager.add_message(message) diff --git a/src/adf_core_python/implement/tactics/default_tactics_police_force.py b/src/adf_core_python/implement/tactics/default_tactics_police_force.py index 8b75a07..3e26216 100644 --- a/src/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/src/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -6,16 +6,16 @@ from adf_core_python.core.agent.action.common.action_rest import ActionRest from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.communication.standard.bundle.centralized.command_fire import ( - CommandFire, + CommandFire, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.command_police import ( - CommandPolice, + CommandPolice, ) from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( - CommandScout, + CommandScout, ) from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( - StandardMessage, + StandardMessage, ) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -26,181 +26,175 @@ from adf_core_python.core.component.module.complex.road_detector import RoadDetector from adf_core_python.core.component.module.complex.search import Search from adf_core_python.core.component.tactics.tactics_police_force import ( - TacticsPoliceForce, + TacticsPoliceForce, ) class DefaultTacticsPoliceForce(TacticsPoliceForce): - def initialize( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - # world_info.index_class() - super().initialize( - agent_info, - world_info, - scenario_info, - module_manager, - precompute_data, - message_manager, - develop_data, - ) - # self._clear_distance = int( - # scenario_info.get_value("clear.repair.distance", "null") - # ) - - self._search: Search = cast( - Search, - module_manager.get_module( - "DefaultTacticsPoliceForce.Search", - "adf_core_python.implement.module.complex.default_search.DefaultSearch", - ), - ) - self._road_detector: RoadDetector = cast( - RoadDetector, - module_manager.get_module( - "DefaultTacticsPoliceForce.RoadDetector", - "adf_core_python.core.component.module.complex.road_detector.RoadDetector", - ), - ) - self._action_ext_clear = module_manager.get_extend_action( - "DefaultTacticsPoliceForce.ExtendActionClear", - "adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear", - ) - self._action_ext_move = module_manager.get_extend_action( - "DefaultTacticsPoliceForce.ExtendActionMove", - "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", - ) - self._command_executor_police = module_manager.get_command_executor( - "DefaultTacticsPoliceForce.CommandExecutorPolice", - "adf_core_python.implement.centralized.default_command_executor_police.DefaultCommandExecutorPolice", - ) - self._command_executor_scout = module_manager.get_command_executor( - "DefaultTacticsPoliceForce.CommandExecutorScout", - "adf_core_python.implement.centralized.default_command_executor_scout_police.DefaultCommandExecutorScoutPolice", - ) - - self.register_module(self._search) - self.register_module(self._road_detector) - self.register_action(self._action_ext_clear) - self.register_action(self._action_ext_move) - self.register_command_executor(self._command_executor_police) - self.register_command_executor(self._command_executor_scout) - - self._recent_command: Optional[StandardMessage] = None - - def precompute( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self.module_precompute(precompute_data) - - def resume( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self.module_resume(precompute_data) - - def prepare( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - develop_data: DevelopData, - ) -> None: - self.module_prepare() - - def think( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> Action: - self.reset_count() - self.module_update_info(message_manager) - - agent: PoliceForce = cast(PoliceForce, agent_info.get_myself()) # noqa: F841 - entity_id = agent_info.get_entity_id() # noqa: F841 - - for message in message_manager.get_received_message_list(): - if isinstance(message, CommandScout): - if ( - message.get_command_executor_agent_entity_id() - == agent_info.get_entity_id() - ): - self._recent_command = message - self._command_executor_scout.set_command(command=message) - if isinstance(message, CommandPolice): - if ( - message.get_command_executor_agent_entity_id() - == agent_info.get_entity_id() - ): - self._recent_command = message - self._command_executor_police.set_command(message) - - if self._recent_command is not None: - action: Optional[Action] = None - if isinstance(self._recent_command, CommandScout): - action = self._command_executor_scout.calculate().get_action() - elif isinstance(self._recent_command, CommandFire): - action = self._command_executor_police.calculate().get_action() - if action is not None: - self._logger.debug( - f"action decided by command: {action}", time=agent_info.get_time() - ) - return action - - target_entity_id = self._road_detector.calculate().get_target_entity_id() - self._logger.debug( - f"road detector target_entity_id: {target_entity_id}", - time=agent_info.get_time(), - ) - if target_entity_id is not None: - action = ( - self._action_ext_clear.set_target_entity_id(target_entity_id) - .calculate() - .get_action() - ) - if action is not None: - self._logger.debug(f"action: {action}", time=agent_info.get_time()) - return action - - target_entity_id = self._search.calculate().get_target_entity_id() + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + # world_info.index_class() + super().initialize( + agent_info, + world_info, + scenario_info, + module_manager, + precompute_data, + message_manager, + develop_data, + ) + # self._clear_distance = int( + # scenario_info.get_value("clear.repair.distance", "null") + # ) + + self._search: Search = cast( + Search, + module_manager.get_module( + "DefaultTacticsPoliceForce.Search", + "adf_core_python.implement.module.complex.default_search.DefaultSearch", + ), + ) + self._road_detector: RoadDetector = cast( + RoadDetector, + module_manager.get_module( + "DefaultTacticsPoliceForce.RoadDetector", + "adf_core_python.core.component.module.complex.road_detector.RoadDetector", + ), + ) + self._action_ext_clear = module_manager.get_extend_action( + "DefaultTacticsPoliceForce.ExtendActionClear", + "adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear", + ) + self._action_ext_move = module_manager.get_extend_action( + "DefaultTacticsPoliceForce.ExtendActionMove", + "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", + ) + self._command_executor_police = module_manager.get_command_executor( + "DefaultTacticsPoliceForce.CommandExecutorPolice", + "adf_core_python.implement.centralized.default_command_executor_police.DefaultCommandExecutorPolice", + ) + self._command_executor_scout = module_manager.get_command_executor( + "DefaultTacticsPoliceForce.CommandExecutorScout", + "adf_core_python.implement.centralized.default_command_executor_scout_police.DefaultCommandExecutorScoutPolice", + ) + + self.register_module(self._search) + self.register_module(self._road_detector) + self.register_action(self._action_ext_clear) + self.register_action(self._action_ext_move) + self.register_command_executor(self._command_executor_police) + self.register_command_executor(self._command_executor_scout) + + self._recent_command: Optional[StandardMessage] = None + + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_precompute(precompute_data) + + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_resume(precompute_data) + + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + self.module_prepare() + + def think( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> Action: + self.reset_count() + self.module_update_info(message_manager) + + agent: PoliceForce = cast(PoliceForce, agent_info.get_myself()) # noqa: F841 + entity_id = agent_info.get_entity_id() # noqa: F841 + + for message in message_manager.get_received_message_list(): + if isinstance(message, CommandScout): + if message.get_command_executor_agent_entity_id() == agent_info.get_entity_id(): + self._recent_command = message + self._command_executor_scout.set_command(command=message) + if isinstance(message, CommandPolice): + if message.get_command_executor_agent_entity_id() == agent_info.get_entity_id(): + self._recent_command = message + self._command_executor_police.set_command(message) + + if self._recent_command is not None: + action: Optional[Action] = None + if isinstance(self._recent_command, CommandScout): + action = self._command_executor_scout.calculate().get_action() + elif isinstance(self._recent_command, CommandFire): + action = self._command_executor_police.calculate().get_action() + if action is not None: self._logger.debug( - f"search target_entity_id: {target_entity_id}", time=agent_info.get_time() + f"action decided by command: {action}", time=agent_info.get_time() ) - if target_entity_id is not None: - action = ( - self._action_ext_move.set_target_entity_id(target_entity_id) - .calculate() - .get_action() - ) - if action is not None: - self._logger.debug(f"action: {action}", time=agent_info.get_time()) - return action - - return ActionRest() + return action + + target_entity_id = self._road_detector.calculate().get_target_entity_id() + self._logger.debug( + f"road detector target_entity_id: {target_entity_id}", + time=agent_info.get_time(), + ) + if target_entity_id is not None: + action = ( + self._action_ext_clear.set_target_entity_id(target_entity_id) + .calculate() + .get_action() + ) + if action is not None: + self._logger.debug(f"action: {action}", time=agent_info.get_time()) + return action + + target_entity_id = self._search.calculate().get_target_entity_id() + self._logger.debug( + f"search target_entity_id: {target_entity_id}", time=agent_info.get_time() + ) + if target_entity_id is not None: + action = ( + self._action_ext_move.set_target_entity_id(target_entity_id) + .calculate() + .get_action() + ) + if action is not None: + self._logger.debug(f"action: {action}", time=agent_info.get_time()) + return action + + return ActionRest() diff --git a/src/adf_core_python/implement/tactics/default_tactics_police_office.py b/src/adf_core_python/implement/tactics/default_tactics_police_office.py index d605244..d830de2 100644 --- a/src/adf_core_python/implement/tactics/default_tactics_police_office.py +++ b/src/adf_core_python/implement/tactics/default_tactics_police_office.py @@ -11,91 +11,89 @@ from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.centralized.command_picker import CommandPicker from adf_core_python.core.component.module.complex.target_allocator import ( - TargetAllocator, + TargetAllocator, ) from adf_core_python.core.component.tactics.tactics_police_office import ( - TacticsPoliceOffice, + TacticsPoliceOffice, ) class DefaultTacticsPoliceOffice(TacticsPoliceOffice): - def initialize( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self._allocator: TargetAllocator = cast( - TargetAllocator, - module_manager.get_module( - "DefaultTacticsPoliceOffice.TargetAllocator", - "adf_core_python.implement.module.complex.default_police_target_allocator.DefaultPoliceTargetAllocator", - ), - ) - self._picker: CommandPicker = module_manager.get_command_picker( - "DefaultTacticsPoliceOffice.CommandPicker", - "adf_core_python.implement.centralized.default_command_picker_police.DefaultCommandPickerPolice", - ) - self.register_module(self._allocator) - self.register_command_picker(self._picker) + def initialize( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self._allocator: TargetAllocator = cast( + TargetAllocator, + module_manager.get_module( + "DefaultTacticsPoliceOffice.TargetAllocator", + "adf_core_python.implement.module.complex.default_police_target_allocator.DefaultPoliceTargetAllocator", + ), + ) + self._picker: CommandPicker = module_manager.get_command_picker( + "DefaultTacticsPoliceOffice.CommandPicker", + "adf_core_python.implement.centralized.default_command_picker_police.DefaultCommandPickerPolice", + ) + self.register_module(self._allocator) + self.register_command_picker(self._picker) - def resume( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self.module_resume(precompute_data) + def resume( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_resume(precompute_data) - def precompute( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self.module_precompute(precompute_data) + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_precompute(precompute_data) - def prepare( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - develop_data: DevelopData, - ) -> None: - self.module_prepare() + def prepare( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + develop_data: DevelopData, + ) -> None: + self.module_prepare() - def think( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - precompute_data: PrecomputeData, - message_manager: MessageManager, - develop_data: DevelopData, - ) -> None: - self.module_update_info(message_manager) + def think( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_update_info(message_manager) - allocation_result: dict[EntityID, EntityID] = ( - self._allocator.calculate().get_result() - ) - for message in ( - self._picker.set_allocator_result(allocation_result) - .calculate() - .get_result() - ): - message_manager.add_message(message) + allocation_result: dict[EntityID, EntityID] = ( + self._allocator.calculate().get_result() + ) + for message in ( + self._picker.set_allocator_result(allocation_result).calculate().get_result() + ): + message_manager.add_message(message) diff --git a/src/adf_core_python/launcher.py b/src/adf_core_python/launcher.py index 0982f70..e69b17a 100644 --- a/src/adf_core_python/launcher.py +++ b/src/adf_core_python/launcher.py @@ -8,138 +8,138 @@ class Launcher: - def __init__( - self, - launcher_config_file: str, - ) -> None: - try: - resource.setrlimit(resource.RLIMIT_NOFILE, (8192, 1048576)) - except Exception as e: - print( - f"Failed to set resource limit: {e}. " - "This may cause issues with the number of open files." - ) + def __init__( + self, + launcher_config_file: str, + ) -> None: + try: + resource.setrlimit(resource.RLIMIT_NOFILE, (8192, 1048576)) + except Exception as e: + print( + f"Failed to set resource limit: {e}. " + "This may cause issues with the number of open files." + ) - self.launcher_config = Config(launcher_config_file) + self.launcher_config = Config(launcher_config_file) - parser = argparse.ArgumentParser(description="Agent Launcher") + parser = argparse.ArgumentParser(description="Agent Launcher") - parser.add_argument( - "--host", - type=str, - help="host name(Default: localhost)", - metavar="", - ) - parser.add_argument( - "--port", - type=int, - help="port number(Default: 27931)", - metavar="", - ) - parser.add_argument( - "-a", - "--ambulanceteam", - type=int, - help="number of ambulance agents(Default: all ambulance)", - metavar="", - ) - parser.add_argument( - "-f", - "--firebrigade", - type=int, - help="number of firebrigade agents(Default: all firebrigade)", - metavar="", - ) - parser.add_argument( - "-p", - "--policeforce", - type=int, - help="number of policeforce agents(Default: all policeforce)", - metavar="", - ) - parser.add_argument( - "-ac", - "--ambulancecenter", - type=int, - help="number of ambulance center agents(Default: all ambulance center)", - metavar="", - ) - parser.add_argument( - "-fs", - "--firestation", - type=int, - help="number of fire station agents(Default: all fire station)", - metavar="", - ) - parser.add_argument( - "-po", - "--policeoffice", - type=int, - help="number of police office agents(Default: all police office)", - metavar="", - ) - parser.add_argument( - "--precompute", - action="store_true", - help="precompute flag", - ) - parser.add_argument( - "--timeout", - type=int, - help="timeout in seconds", - metavar="", - ) - parser.add_argument("--debug", action="store_true", help="debug flag") - parser.add_argument( - "--java", - action="store_true", - help="using java module flag", - ) - args = parser.parse_args() + parser.add_argument( + "--host", + type=str, + help="host name(Default: localhost)", + metavar="", + ) + parser.add_argument( + "--port", + type=int, + help="port number(Default: 27931)", + metavar="", + ) + parser.add_argument( + "-a", + "--ambulanceteam", + type=int, + help="number of ambulance agents(Default: all ambulance)", + metavar="", + ) + parser.add_argument( + "-f", + "--firebrigade", + type=int, + help="number of firebrigade agents(Default: all firebrigade)", + metavar="", + ) + parser.add_argument( + "-p", + "--policeforce", + type=int, + help="number of policeforce agents(Default: all policeforce)", + metavar="", + ) + parser.add_argument( + "-ac", + "--ambulancecenter", + type=int, + help="number of ambulance center agents(Default: all ambulance center)", + metavar="", + ) + parser.add_argument( + "-fs", + "--firestation", + type=int, + help="number of fire station agents(Default: all fire station)", + metavar="", + ) + parser.add_argument( + "-po", + "--policeoffice", + type=int, + help="number of police office agents(Default: all police office)", + metavar="", + ) + parser.add_argument( + "--precompute", + action="store_true", + help="precompute flag", + ) + parser.add_argument( + "--timeout", + type=int, + help="timeout in seconds", + metavar="", + ) + parser.add_argument("--debug", action="store_true", help="debug flag") + parser.add_argument( + "--java", + action="store_true", + help="using java module flag", + ) + args = parser.parse_args() - config_map = { - ConfigKey.KEY_KERNEL_HOST: args.host, - ConfigKey.KEY_KERNEL_PORT: args.port, - ConfigKey.KEY_AMBULANCE_TEAM_COUNT: args.ambulanceteam, - ConfigKey.KEY_FIRE_BRIGADE_COUNT: args.firebrigade, - ConfigKey.KEY_POLICE_FORCE_COUNT: args.policeforce, - ConfigKey.KEY_AMBULANCE_CENTRE_COUNT: args.ambulancecenter, - ConfigKey.KEY_FIRE_STATION_COUNT: args.firestation, - ConfigKey.KEY_POLICE_OFFICE_COUNT: args.policeoffice, - ConfigKey.KEY_PRECOMPUTE: args.precompute, - ConfigKey.KEY_KERNEL_TIMEOUT: args.timeout, - ConfigKey.KEY_DEBUG_FLAG: args.debug, - ConfigKey.KEY_GATEWAY_FLAG: args.java, - } + config_map = { + ConfigKey.KEY_KERNEL_HOST: args.host, + ConfigKey.KEY_KERNEL_PORT: args.port, + ConfigKey.KEY_AMBULANCE_TEAM_COUNT: args.ambulanceteam, + ConfigKey.KEY_FIRE_BRIGADE_COUNT: args.firebrigade, + ConfigKey.KEY_POLICE_FORCE_COUNT: args.policeforce, + ConfigKey.KEY_AMBULANCE_CENTRE_COUNT: args.ambulancecenter, + ConfigKey.KEY_FIRE_STATION_COUNT: args.firestation, + ConfigKey.KEY_POLICE_OFFICE_COUNT: args.policeoffice, + ConfigKey.KEY_PRECOMPUTE: args.precompute, + ConfigKey.KEY_KERNEL_TIMEOUT: args.timeout, + ConfigKey.KEY_DEBUG_FLAG: args.debug, + ConfigKey.KEY_GATEWAY_FLAG: args.java, + } - for key, value in config_map.items(): - if value is not None: - self.launcher_config.set_value(key, value) + for key, value in config_map.items(): + if value is not None: + self.launcher_config.set_value(key, value) - configure_logger() - self.logger = get_logger(__name__) + configure_logger() + self.logger = get_logger(__name__) - self.logger.debug(f"launcher_config: {self.launcher_config}") + self.logger.debug(f"launcher_config: {self.launcher_config}") - def launch(self) -> None: - agent_launcher: AgentLauncher = AgentLauncher( - self.launcher_config, - ) - agent_launcher.init_connector() + def launch(self) -> None: + agent_launcher: AgentLauncher = AgentLauncher( + self.launcher_config, + ) + agent_launcher.init_connector() - try: - agent_launcher.launch() - except KeyboardInterrupt: - self.logger.info("Agent launcher interrupted") - except Exception as e: - self.logger.exception("Agent launcher failed", exc_info=e) - raise e - self.logger.info("Agent launcher finished") + try: + agent_launcher.launch() + except KeyboardInterrupt: + self.logger.info("Agent launcher interrupted") + except Exception as e: + self.logger.exception("Agent launcher failed", exc_info=e) + raise e + self.logger.info("Agent launcher finished") if __name__ == "__main__": - launcher = Launcher( - "config/launcher.yaml", - ) + launcher = Launcher( + "config/launcher.yaml", + ) - launcher.launch() + launcher.launch() diff --git a/tests/core/agent/config/test_module_config.py b/tests/core/agent/config/test_module_config.py index af0aa95..ffb8ca5 100644 --- a/tests/core/agent/config/test_module_config.py +++ b/tests/core/agent/config/test_module_config.py @@ -6,43 +6,43 @@ class TestModuleConfig: - def test_can_read_from_yaml(self) -> None: - script_dir = os.path.dirname(os.path.abspath(__file__)) - config_file_path = os.path.join(script_dir, "module.yaml") - config = ModuleConfig(config_file_path) - assert ( - config.get_value("DefaultTacticsPoliceOffice.TargetAllocator") - == "sample_team.module.complex.SamplePoliceTargetAllocator" - ) - assert ( - config.get_value("DefaultTacticsPoliceOffice.CommandPicker") - == "adf_core_python.implement.centralized.DefaultCommandPickerPolice" - ) - assert ( - config.get_value("SampleSearch.PathPlanning.Ambulance") - == "adf_core_python.implement.module.algorithm.DijkstraPathPlanning" - ) - assert ( - config.get_value("SampleSearch.PathPlanning.Fire") - == "adf_core_python.implement.module.algorithm.DijkstraPathPlanning" - ) - assert ( - config.get_value("SampleSearch.PathPlanning.Police") - == "adf_core_python.implement.module.algorithm.DijkstraPathPlanning" - ) - assert ( - config.get_value("SampleSearch.Clustering.Ambulance") - == "adf_core_python.implement.module.algorithm.KMeansClustering" - ) - assert ( - config.get_value("SampleSearch.Clustering.Fire") - == "adf_core_python.implement.module.algorithm.KMeansClustering" - ) - assert ( - config.get_value("SampleSearch.Clustering.Police") - == "adf_core_python.implement.module.algorithm.KMeansClustering" - ) + def test_can_read_from_yaml(self) -> None: + script_dir = os.path.dirname(os.path.abspath(__file__)) + config_file_path = os.path.join(script_dir, "module.yaml") + config = ModuleConfig(config_file_path) + assert ( + config.get_value("DefaultTacticsPoliceOffice.TargetAllocator") + == "sample_team.module.complex.SamplePoliceTargetAllocator" + ) + assert ( + config.get_value("DefaultTacticsPoliceOffice.CommandPicker") + == "adf_core_python.implement.centralized.DefaultCommandPickerPolice" + ) + assert ( + config.get_value("SampleSearch.PathPlanning.Ambulance") + == "adf_core_python.implement.module.algorithm.DijkstraPathPlanning" + ) + assert ( + config.get_value("SampleSearch.PathPlanning.Fire") + == "adf_core_python.implement.module.algorithm.DijkstraPathPlanning" + ) + assert ( + config.get_value("SampleSearch.PathPlanning.Police") + == "adf_core_python.implement.module.algorithm.DijkstraPathPlanning" + ) + assert ( + config.get_value("SampleSearch.Clustering.Ambulance") + == "adf_core_python.implement.module.algorithm.KMeansClustering" + ) + assert ( + config.get_value("SampleSearch.Clustering.Fire") + == "adf_core_python.implement.module.algorithm.KMeansClustering" + ) + assert ( + config.get_value("SampleSearch.Clustering.Police") + == "adf_core_python.implement.module.algorithm.KMeansClustering" + ) - def test_if_file_not_found(self) -> None: - with pytest.raises(FileNotFoundError): - ModuleConfig("not_found.yaml") + def test_if_file_not_found(self) -> None: + with pytest.raises(FileNotFoundError): + ModuleConfig("not_found.yaml") diff --git a/tests/core/agent/develop/test_develop.py b/tests/core/agent/develop/test_develop.py index 877e50a..9501be4 100644 --- a/tests/core/agent/develop/test_develop.py +++ b/tests/core/agent/develop/test_develop.py @@ -6,17 +6,17 @@ class TestDevelopData: - def test_can_read_from_yaml(self) -> None: - script_dir = os.path.dirname(os.path.abspath(__file__)) - develop_file_path = os.path.join(script_dir, "develop.json") - develop_data = DevelopData(True, develop_file_path) + def test_can_read_from_yaml(self) -> None: + script_dir = os.path.dirname(os.path.abspath(__file__)) + develop_file_path = os.path.join(script_dir, "develop.json") + develop_data = DevelopData(True, develop_file_path) - assert develop_data.get_value("string") == "test" - assert develop_data.get_value("number") == 1 - assert develop_data.get_value("boolean") is True - assert develop_data.get_value("dict") == {"test": "test"} - assert develop_data.get_value("array") == ["test", "test"] + assert develop_data.get_value("string") == "test" + assert develop_data.get_value("number") == 1 + assert develop_data.get_value("boolean") is True + assert develop_data.get_value("dict") == {"test": "test"} + assert develop_data.get_value("array") == ["test", "test"] - def test_if_file_not_found(self) -> None: - with pytest.raises(FileNotFoundError): - DevelopData(True, "not_found.json") + def test_if_file_not_found(self) -> None: + with pytest.raises(FileNotFoundError): + DevelopData(True, "not_found.json") diff --git a/tests/core/agent/module/test_module_manager.py b/tests/core/agent/module/test_module_manager.py index 470061e..d6ec498 100644 --- a/tests/core/agent/module/test_module_manager.py +++ b/tests/core/agent/module/test_module_manager.py @@ -8,25 +8,25 @@ class TestModuleManager: - @pytest.mark.skip(reason="一時的に無効化") - def test_can_get_module(self) -> None: - script_dir = os.path.dirname(os.path.abspath(__file__)) - config_file_path = os.path.join(script_dir, "module.yaml") - config = ModuleConfig(config_file_path) - config.set_value( - "test_module", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ) - module_manager = self.create_module_manager(config) - module = module_manager.get_module("test_module", "test_module") - assert isinstance(module, AbstractModule) - assert module.__class__.__name__ == "AStarPathPlanning" + @pytest.mark.skip(reason="一時的に無効化") + def test_can_get_module(self) -> None: + script_dir = os.path.dirname(os.path.abspath(__file__)) + config_file_path = os.path.join(script_dir, "module.yaml") + config = ModuleConfig(config_file_path) + config.set_value( + "test_module", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ) + module_manager = self.create_module_manager(config) + module = module_manager.get_module("test_module", "test_module") + assert isinstance(module, AbstractModule) + assert module.__class__.__name__ == "AStarPathPlanning" - def create_module_manager(self, config: ModuleConfig) -> ModuleManager: - return ModuleManager( - None, # type: ignore - None, # type: ignore - None, # type: ignore - config, - None, # type: ignore - ) + def create_module_manager(self, config: ModuleConfig) -> ModuleManager: + return ModuleManager( + None, # type: ignore + None, # type: ignore + None, # type: ignore + config, + None, # type: ignore + ) From 810dfc7f17bd30a83aa3a0d725b7eab825919677 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 15:26:44 +0900 Subject: [PATCH 238/249] =?UTF-8?q?feat:=20docs=E3=83=87=E3=82=A3=E3=83=AC?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=83=AA=E3=82=92mypy=E3=81=AE=E9=99=A4?= =?UTF-8?q?=E5=A4=96=E3=83=AA=E3=82=B9=E3=83=88=E3=81=AB=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 0e429bd..40a3160 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,6 +133,7 @@ disallow_untyped_defs = true no_implicit_optional = true check_untyped_defs = true warn_redundant_casts = true +exclude = ["docs"] [tool.uv.sources] rcrscore = { git = "https://github.com/adf-python/rcrs-core-python" , tag = "v0.2.0" } From 365e3e08ed3f2ee46c09830d190d9ae81c97f8ea Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 15:26:52 +0900 Subject: [PATCH 239/249] =?UTF-8?q?feat:=20=E5=9E=8B=E3=83=92=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97=E3=81=A6=E9=96=A2?= =?UTF-8?q?=E6=95=B0=E3=81=AE=E6=98=8E=E7=A2=BA=E3=81=95=E3=82=92=E5=90=91?= =?UTF-8?q?=E4=B8=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/launcher/connect/connection.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/adf_core_python/core/launcher/connect/connection.py b/src/adf_core_python/core/launcher/connect/connection.py index 660d987..3923471 100644 --- a/src/adf_core_python/core/launcher/connect/connection.py +++ b/src/adf_core_python/core/launcher/connect/connection.py @@ -59,7 +59,7 @@ def send_msg(self, msg: Any) -> None: Connection._write_msg(msg, self.socket) @staticmethod - def _write_int32(value, sock): + def _write_int32(value: int, sock: socket.socket) -> None: b = [ ((value >> 24) & 0xFF), ((value >> 16) & 0xFF), @@ -70,7 +70,7 @@ def _write_int32(value, sock): sock.sendall(bytes(b)) @staticmethod - def _readnbytes(sock, n): + def _readnbytes(sock: socket.socket, n: int) -> bytes: buff = b"" while n > 0: b = sock.recv(n) @@ -81,7 +81,7 @@ def _readnbytes(sock, n): return buff @staticmethod - def _read_int32(sock): + def _read_int32(sock: socket.socket) -> int: byte_array = Connection._readnbytes(sock, 4) value = int( ((byte_array[0]) << 24) @@ -92,15 +92,14 @@ def _read_int32(sock): return value @staticmethod - def _write_msg(msg, sock): + def _write_msg(msg: Any, sock: socket.socket) -> None: out = msg.SerializeToString() Connection._write_int32(len(out), sock) sock.sendall(out) @staticmethod - def _read_msg(sock): - # await reader.read(1) + def _read_msg(sock: socket.socket) -> RCRSProto_pb2.MessageProto: size = Connection._read_int32(sock) content = Connection._readnbytes(sock, size) message = RCRSProto_pb2.MessageProto() From 1cc2fa6a31eb9470e066aa21d1c8efc7c0239c28 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 15:27:15 +0900 Subject: [PATCH 240/249] =?UTF-8?q?feat:=20README=E3=81=ABPoetry=E3=81=8B?= =?UTF-8?q?=E3=82=89uv=E3=81=B8=E3=81=AE=E7=A7=BB=E8=A1=8C=E3=81=A8Python?= =?UTF-8?q?=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7=E3=83=B3=E3=81=AE=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index da12692..d0f490a 100644 --- a/README.md +++ b/README.md @@ -8,32 +8,34 @@ ### Prerequisites -- Python (3.12 or higher) -- Poetry (1.8.3 or higher) +- Python (3.13 or higher) +- uv (0.8.2 or higher) ### Installation ```bash -poetry install +uv sync ``` ### Run Agent ```bash -poetry run python ./adf_core_python/launcher.py +uv run python ./adf_core_python/launcher.py # get help -poetry run python ./adf_core_python/launcher.py -h +uv run python ./adf_core_python/launcher.py -h ``` ### Build ```bash -poetry build +uv build ``` ### Pre Commit ```bash -poetry run task precommit +uv run ruff format . +uv run ruff check . +uv run mypy . ``` From 1c6c26d176c3bffe866c436e00176994c563f663 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 15:47:12 +0900 Subject: [PATCH 241/249] =?UTF-8?q?feat:=20CI=E3=81=A7ruff=E3=81=A8mypy?= =?UTF-8?q?=E3=81=AE=E5=AE=9F=E8=A1=8C=E3=81=ABuv=E3=82=92=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 24ebbf5..95a231d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -55,10 +55,10 @@ jobs: run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH - name: Check ruff version - run: ruff --version + run: uv run ruff --version - name: Run ruff format check - run: ruff format --check . + run: uv run ruff format --check . ruff-lint: needs: setup @@ -77,10 +77,10 @@ jobs: run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH - name: Check ruff version - run: ruff --version + run: uv run ruff --version - name: Run ruff lint - run: ruff check --output-format=github . + run: uv run ruff check --output-format=github . mypy-type-check: needs: setup @@ -99,10 +99,10 @@ jobs: run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH - name: Check mypy version - run: mypy --version + run: uv run mypy --version - name: Run mypy type check - run: mypy . + run: uv run mypy . pytest: needs: setup From d3be3dd961d6e8164ba9c9ca1f40c9aa44170e1a Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 15:52:41 +0900 Subject: [PATCH 242/249] =?UTF-8?q?feat:=20CI=E3=81=AE=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=82=92uv=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E6=9B=B4=E6=96=B0=E3=81=97=E3=80=81?= =?UTF-8?q?=E3=82=AD=E3=83=A3=E3=83=83=E3=82=B7=E3=83=A5=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E3=82=92=E6=9C=80=E9=81=A9=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yaml | 93 ++++++++++++++------------------------- 1 file changed, 33 insertions(+), 60 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 95a231d..06c53ee 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,116 +12,89 @@ on: jobs: setup: + name: Setup ✅ runs-on: ubuntu-latest + outputs: + cache-hit: ${{ steps.setup_uv.outputs.cache-hit }} steps: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Install uv (+ enable cache) + id: setup_uv + uses: astral-sh/setup-uv@v6 with: - python-version: 3.12 + enable-cache: true - - name: Cache virtual environment - id: cached-virtualenv - uses: actions/cache@v4 - with: - path: .venv - key: ${{ runner.os }}-venv-${{ hashFiles('**/uv.lock') }} - - - name: Install dependencies - if: steps.cached-virtualenv.outputs.cache-hit != 'true' - run: | - python -m venv .venv - source .venv/bin/activate - pip install --upgrade pip - pip install uv - uv sync + - name: Sync dependencies + run: uv sync --locked --dev + + - name: Prune uv cache (optimized for CI) + run: uv cache prune --ci ruff-format: + name: Ruff format check needs: setup runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - - name: Restore virtual environment cache - uses: actions/cache@v4 + - name: Install uv + restore cache + uses: astral-sh/setup-uv@v6 with: - path: .venv - key: ${{ runner.os }}-venv-${{ hashFiles('**/uv.lock') }} - - - name: Set path - run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH + enable-cache: true - - name: Check ruff version - run: uv run ruff --version - - - name: Run ruff format check + - name: Check ruff formatting run: uv run ruff format --check . ruff-lint: + name: Ruff lint check needs: setup runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - - name: Restore virtual environment cache - uses: actions/cache@v4 + - name: Install uv + restore cache + uses: astral-sh/setup-uv@v6 with: - path: .venv - key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} - - - name: Set path - run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH - - - name: Check ruff version - run: uv run ruff --version + enable-cache: true - name: Run ruff lint run: uv run ruff check --output-format=github . mypy-type-check: + name: MyPy type check needs: setup runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - - name: Restore virtual environment cache - uses: actions/cache@v4 + - name: Install uv + restore cache + uses: astral-sh/setup-uv@v6 with: - path: .venv - key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} - - - name: Set path - run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH + enable-cache: true - - name: Check mypy version - run: uv run mypy --version - - - name: Run mypy type check - run: uv run mypy . + - name: Run mypy + run: uv run --frozen mypy . pytest: + name: PyTest needs: setup runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - - name: Restore virtual environment cache - uses: actions/cache@v4 + - name: Install uv + restore cache + uses: astral-sh/setup-uv@v6 with: - path: .venv - key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} - - - name: Set path - run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH + enable-cache: true - - name: Run pytest - run: pytest tests/ + - name: Run tests + run: uv run --frozen pytest tests/ publish: runs-on: ubuntu-latest From d72d84c8c0ddfbe54728aa189c2e85dc2b44aa1b Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 15:54:58 +0900 Subject: [PATCH 243/249] =?UTF-8?q?feat:=20CI=E3=81=AE=E3=82=BB=E3=83=83?= =?UTF-8?q?=E3=83=88=E3=82=A2=E3=83=83=E3=83=97=E5=90=8D=E3=81=8B=E3=82=89?= =?UTF-8?q?=E7=B5=B5=E6=96=87=E5=AD=97=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 06c53ee..787cd22 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,7 +12,7 @@ on: jobs: setup: - name: Setup ✅ + name: Setup runs-on: ubuntu-latest outputs: cache-hit: ${{ steps.setup_uv.outputs.cache-hit }} From 75a142bd390809da78523719c32290c992df5534 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 16:00:49 +0900 Subject: [PATCH 244/249] =?UTF-8?q?feat:=20CI=E3=82=B8=E3=83=A7=E3=83=96?= =?UTF-8?q?=E5=90=8D=E3=82=92=E5=B0=8F=E6=96=87=E5=AD=97=E3=81=AB=E7=B5=B1?= =?UTF-8?q?=E4=B8=80=E3=81=97=E3=80=81pytest=E3=81=AE=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yaml | 8 ++++---- pyproject.toml | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 787cd22..9a39d65 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,7 +33,7 @@ jobs: run: uv cache prune --ci ruff-format: - name: Ruff format check + name: ruff-format needs: setup runs-on: ubuntu-latest steps: @@ -49,7 +49,7 @@ jobs: run: uv run ruff format --check . ruff-lint: - name: Ruff lint check + name: ruff-lint needs: setup runs-on: ubuntu-latest steps: @@ -65,7 +65,7 @@ jobs: run: uv run ruff check --output-format=github . mypy-type-check: - name: MyPy type check + name: mypy-type-check needs: setup runs-on: ubuntu-latest steps: @@ -81,7 +81,7 @@ jobs: run: uv run --frozen mypy . pytest: - name: PyTest + name: pytest needs: setup runs-on: ubuntu-latest steps: diff --git a/pyproject.toml b/pyproject.toml index 40a3160..0754df2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -135,5 +135,9 @@ check_untyped_defs = true warn_redundant_casts = true exclude = ["docs"] +[tool.pytest.ini_options] +pythonpath = ["src"] +testpaths = ["tests"] + [tool.uv.sources] rcrscore = { git = "https://github.com/adf-python/rcrs-core-python" , tag = "v0.2.0" } From 61f25e3879452c5b51e46aaebdb51f3148b1e218 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 16:11:09 +0900 Subject: [PATCH 245/249] =?UTF-8?q?feat:=20pytest=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=96=E3=82=92CI=E3=81=8B=E3=82=89=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yaml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9a39d65..958de34 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -80,22 +80,6 @@ jobs: - name: Run mypy run: uv run --frozen mypy . - pytest: - name: pytest - needs: setup - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install uv + restore cache - uses: astral-sh/setup-uv@v6 - with: - enable-cache: true - - - name: Run tests - run: uv run --frozen pytest tests/ - publish: runs-on: ubuntu-latest permissions: From b7998ee964cc3b9979ee1efb31c39f0770ff48e3 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 16:40:30 +0900 Subject: [PATCH 246/249] =?UTF-8?q?feat:=20precompute=E3=83=87=E3=83=BC?= =?UTF-8?q?=E3=82=BF=E3=82=92=E7=AE=A1=E7=90=86=E3=81=99=E3=82=8B=E3=81=9F?= =?UTF-8?q?=E3=82=81=E3=81=AEPrecomputeData=E3=82=AF=E3=83=A9=E3=82=B9?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - .../core/agent/precompute/__init__.py | 0 .../core/agent/precompute/precompute_data.py | 75 +++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 src/adf_core_python/core/agent/precompute/__init__.py create mode 100644 src/adf_core_python/core/agent/precompute/precompute_data.py diff --git a/.gitignore b/.gitignore index 41060c9..ab0dc67 100644 --- a/.gitignore +++ b/.gitignore @@ -172,5 +172,4 @@ cython_debug/ # ADF agent.log* -precompute !java/lib/src/main/java/adf_core_python/core/agent/precompute diff --git a/src/adf_core_python/core/agent/precompute/__init__.py b/src/adf_core_python/core/agent/precompute/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/adf_core_python/core/agent/precompute/precompute_data.py b/src/adf_core_python/core/agent/precompute/precompute_data.py new file mode 100644 index 0000000..41b2029 --- /dev/null +++ b/src/adf_core_python/core/agent/precompute/precompute_data.py @@ -0,0 +1,75 @@ +import json +import os + +ENCODE = "utf-8" + + +class PrecomputeData: + def __init__(self, dir_path: str) -> None: + """ + Initialize the PrecomputeData object. + + Parameters + ---------- + dir_path : str + The directory path to save the precompute data. + + Raises + ------ + Exception + """ + self._dir_path = dir_path + + def read_json_data(self, module_name: str) -> dict: + """ + Read the precompute data from the file. + + Returns + ------- + dict + The precompute data. + + Raises + ------ + Exception + """ + + with open(f"{self._dir_path}/{module_name}.json", "r", encoding=ENCODE) as file: + return json.load(file) + + def write_json_data(self, data: dict, module_name: str) -> None: + """ + Write the precompute data to the file. + + Parameters + ---------- + data : dict + The data to write. + + Raises + ------ + Exception + """ + if not os.path.exists(self._dir_path): + os.makedirs(self._dir_path) + + with open(f"{self._dir_path}/{module_name}.json", "w", encoding=ENCODE) as file: + json.dump(data, file, indent=4) + + def remove_precompute_data(self) -> None: + """ + Remove the precompute data file. + """ + if os.path.exists(self._dir_path): + os.remove(self._dir_path) + + def is_available(self) -> bool: + """ + Check if the precompute data is available. + + Returns + ------- + bool + True if the precompute data is available, False otherwise. + """ + return os.path.exists(self._dir_path) From 12aea365cfacadd84367aa26197e45d4b7e57a63 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 16:42:31 +0900 Subject: [PATCH 247/249] =?UTF-8?q?feat:=20PrecomputeData=E3=82=AF?= =?UTF-8?q?=E3=83=A9=E3=82=B9=E3=81=AE=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89?= =?UTF-8?q?=E3=81=AE=E3=82=A4=E3=83=B3=E3=83=87=E3=83=B3=E3=83=88=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/agent/precompute/precompute_data.py | 136 +++++++++--------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/src/adf_core_python/core/agent/precompute/precompute_data.py b/src/adf_core_python/core/agent/precompute/precompute_data.py index 41b2029..3dc3373 100644 --- a/src/adf_core_python/core/agent/precompute/precompute_data.py +++ b/src/adf_core_python/core/agent/precompute/precompute_data.py @@ -5,71 +5,71 @@ class PrecomputeData: - def __init__(self, dir_path: str) -> None: - """ - Initialize the PrecomputeData object. - - Parameters - ---------- - dir_path : str - The directory path to save the precompute data. - - Raises - ------ - Exception - """ - self._dir_path = dir_path - - def read_json_data(self, module_name: str) -> dict: - """ - Read the precompute data from the file. - - Returns - ------- - dict - The precompute data. - - Raises - ------ - Exception - """ - - with open(f"{self._dir_path}/{module_name}.json", "r", encoding=ENCODE) as file: - return json.load(file) - - def write_json_data(self, data: dict, module_name: str) -> None: - """ - Write the precompute data to the file. - - Parameters - ---------- - data : dict - The data to write. - - Raises - ------ - Exception - """ - if not os.path.exists(self._dir_path): - os.makedirs(self._dir_path) - - with open(f"{self._dir_path}/{module_name}.json", "w", encoding=ENCODE) as file: - json.dump(data, file, indent=4) - - def remove_precompute_data(self) -> None: - """ - Remove the precompute data file. - """ - if os.path.exists(self._dir_path): - os.remove(self._dir_path) - - def is_available(self) -> bool: - """ - Check if the precompute data is available. - - Returns - ------- - bool - True if the precompute data is available, False otherwise. - """ - return os.path.exists(self._dir_path) + def __init__(self, dir_path: str) -> None: + """ + Initialize the PrecomputeData object. + + Parameters + ---------- + dir_path : str + The directory path to save the precompute data. + + Raises + ------ + Exception + """ + self._dir_path = dir_path + + def read_json_data(self, module_name: str) -> dict: + """ + Read the precompute data from the file. + + Returns + ------- + dict + The precompute data. + + Raises + ------ + Exception + """ + + with open(f"{self._dir_path}/{module_name}.json", "r", encoding=ENCODE) as file: + return json.load(file) + + def write_json_data(self, data: dict, module_name: str) -> None: + """ + Write the precompute data to the file. + + Parameters + ---------- + data : dict + The data to write. + + Raises + ------ + Exception + """ + if not os.path.exists(self._dir_path): + os.makedirs(self._dir_path) + + with open(f"{self._dir_path}/{module_name}.json", "w", encoding=ENCODE) as file: + json.dump(data, file, indent=4) + + def remove_precompute_data(self) -> None: + """ + Remove the precompute data file. + """ + if os.path.exists(self._dir_path): + os.remove(self._dir_path) + + def is_available(self) -> bool: + """ + Check if the precompute data is available. + + Returns + ------- + bool + True if the precompute data is available, False otherwise. + """ + return os.path.exists(self._dir_path) From 3af518ec307152482efa0de6231797b631e34925 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 16:46:03 +0900 Subject: [PATCH 248/249] =?UTF-8?q?feat:=20=E3=83=90=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=82=920.2.0=E3=81=8B=E3=82=890.2.1?= =?UTF-8?q?=E3=81=AB=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0754df2..b53e1f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "adf_core_python" -version = "0.2.0" +version = "0.2.1" description = "Agent Development Framework for Python" readme = "README.md" authors = [ From d48cfdd6b8a9720845b7ae846f430e247d92d5c5 Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 4 Aug 2025 17:29:39 +0900 Subject: [PATCH 249/249] =?UTF-8?q?feat:=20adf-core-python=E3=81=AE?= =?UTF-8?q?=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7=E3=83=B3=E3=82=920.2.0?= =?UTF-8?q?=E3=81=8B=E3=82=890.2.1=E3=81=AB=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 8d71127..fba9de0 100644 --- a/uv.lock +++ b/uv.lock @@ -16,7 +16,7 @@ wheels = [ [[package]] name = "adf-core-python" -version = "0.2.0" +version = "0.2.1" source = { editable = "." } dependencies = [ { name = "bitarray" },

    tR;Pn98aHW{MdpX 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%0A`6sg0pv>04xWtQ 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 XKsf1!?Iz!~w+F^L)jT8V$93U=Sl zw)E7Jzh*z*=xM;MVZ6Lv3%N{k91a$SZZ$quiYCnYAb(F2^n$e2{QIAc_?=ePl>^ew zysB%57@$uyW&TEUb>XP&D%;?L3#EY_qaCxscce9r-8Ef4_#%HK`_0mj?tR6@=2U5N z7i}~PW&Q5aaQ;Zurz-{}ut+JED93wNNQht{6$B1p3$TVb7JcqGODEpS z28kfy2T`Kb#*Lcg_ZdCmijHrjIs^m-nuuY4op~x7ZubE z%H~{0>pRWF9uPx3J}C8xNNPI^5#~Efg)mz0Mz~YS3g^mr8&~}ekbwq^9oExBgCh?u*=%wacw9(sW9l~A^m zUl@;M&p{*9?hpe)YJ$$Zw;$rcAuiE~JX8@OJcULtM~CJJr=Ol^61A*;W9j|-i`}aF z1%X0GI_tED_W$kma8j0LDqwr6*LRU3I{L{h_tt*0GIP{Tbdf^Ys4%`vLP4B0%OoSf zG57;duvEj%Z^SJ9%g~yHW|!###)DTzK}iK?eG-buuNX-3{7(GK?QQz}@=9~6=rSz~ zgWB>l1V@0+dH%bY(XB$SgsGM*VvaRlLtEUi*(}_WnG;94%M-#?Cf61Q9rT=umrv9EuB_et>yFG8Dzn|YW z@mZPb%?ANKK+U|5Stsouj>Y`uN5RN`WIz*CbdzAO5d;k2MyL^cVbqsRvE3P}A1l%eRkw_{Lg__P!JDkpb6`JQL*q`4N0 z1M@ERdNp3yt(qa(H%#qVwD=*X_CE|Z9m4m>Ql-T6h@LNpgSNvF{K#vBmYgz1?~DlX z<(E+^B**PC3LMEC+#YQ6KC=6rz4=+wli_C03N{j@EKIVFEyGT&3i{Ti89t^T>;+VP zn&S@~pDfwa*;T8Cg=tL;DYXf+)(?YCm9@rKukx9 zj?B+n?dGDCcCjEge1~k~DPM6prMYOS9%_?jwNY%B3+iwjdtA@w<~vYQ*LF%hNi1+# zOTYLmq3M5OVQp|+6G}Tns;#e5y_%a_siYHjw*(akW23khEZaoz88?FZ zPIZf`e6|RrP|$||l!f@+RvSp~h1RlK8`Ym4gGyEP08bQx(Da@UaMfDKnA~|!zHu&( zWf456{mI|$skSTtUX^h2!DAWhl#~9GD@(XEKQlq_+!v_^z!G_2T zc_jAHGHJ$9&)*ymiV)Zxl)*<&C50nPtR{m-QX+y*V5+Yt)%MyvAusEp6jnmc%OVZ^ zi!|Vi_X9!B4}uDBh7Z>`^hAJs zkE^>_d^_;x1;;$g6R-z?$-JvBDG>_!nKf6GYh9g_<-w-Wt3a6?r*D!@`LEVAmBN}y zpE(`uKB`)L=i$x*>`cUZk2&%zUGDzd2;4Q*@pU@WhB(L1LAvi(-VG+OWmcIqc-|x( zaIBO+^;~*2;3t8S8$QLn^%a#Ev>wIC58s`dFqtuv8%h{%HcB~H`jS`y1dJc{->k|A zT0pim8V{~#qMv5Y9*ZmSWa`Nwu}mL0Z40@KdxZh9%DAgV(6E!+hmFXfy5b|eDBEGK z`(G_y-YNk|Yz8776ol!2 z287#AI_7$vCYcWz0;tl)ONW0oJAh!0Nj)pvK=#@%hF5x=thYK#F{reeoQ0?gZan7i z-Mh)v6=$-VzD*Dj!L8Kvk5|@>PH4Ea#(yR=Mr=+CHVos`nbhTef8D20bq~x&w$o9~ ztuaUD+8kK9=WWuYne*;1^SVE-HhfhV+v*YciDjHm<0K#Mk>7*!$}!4IhFQ&SUeVI5 z=av00S+$#OJ)nV6l{uhma{>q637@S`J|72oOH1(8&teo8rw@40y7#3XRO9~HW_cn> zv+&3O>D;Em;SHjJg;`IM!0}xh+vpOAW6mw2St3;&$V1w6KA2d#-R8Y=idUBO;N(li z?LMjA^6cGfmqZrWcxb8)P`d?)WE;94GJL8JqXQ&Nd9l+4$WlXs=h%Id>bwMt``AbQ zuZ@t^GvU>{se6WSfxx62A8|3bah%(#gmbV2_6h?+yv_N32Fj2mktP6jbe~w#P6xZ8 z`R(LR5JYY>4GTz=s+#_~?i{od9nXO`@@5Vmvv6rpZv5QSUKgBmKn*z$4g|?{r|D}* z(G4mYC^JF~Zba{+m@K7dc+r82c_?+%O8D2HMDSRD-cY+-rgvDQu7B|-`lijKeNBer z+*)fLf(#HiUV}Dv-+aafIfUTfD~-4c)*)!@B;&> zudm)tIC+Z}0taa7kjtxah;4cuc;&gIa!c8dZhq@YvV75$8lA7&RU5*^?Q*YSYkx2M zO{b0q6W0ZX(c8r@ZFV0sGY9qaw6xkdoS|EQe?6+I#c1s4>u?>bgk=!L8KQYo#6{mT zPUnfX(893YwevZG(^q84M8fAN9%OyalZxTMUebD`-2P?#FfOd4F zq;_S+z55U5+wQO3)lNmhT^5C5`C-T5+a8GpXSa<>TzvjtvKyoL&A$u|`Br zSgFG!b6vmdC>H*Fxkk}OL+=ou#AXZ7Y7a^J^1k#|NZ)RV^dS9L8n+SV>?gG zGV;b+@KUzoP;LFMCiY(BoD0u|oV67X4;@X#5V4H5#Aj|I9idf+N{vSrIAJT@Q)AWuyy`?e zmWpo??%!uA$F4`yipza~(w8Z2)O&35(LEU!Bvp*)3di|<%Vi9AT9y4o-tg&EoZUkN zQ}0~{v5ySvAW_-iCyGMq1)aZ;!HJ`Nh}Frx%Bo$K^`+I#xjeBz$BOG6&EUd?(ykuUY?5z60jc@QSLMuh|n1&U&Bc_nYvoOS)=~^ff};{OCmsiyX+>ld z?4-;FcDXTbv){bX21)y@?S9Vk_OLZsRfW#JCy1Nvezu_N8);TOE#I|Yst2>K*Y9ZE zU6>Q+FtI*nlCl+Rjwak)HGm&|=3`1LDYA8HPzw6;d|!YUdUxYK|3Q<;6Pk(76X*Z8 zp!44sNyl}D1M%5*F2ucJ%*=wRx_yHrP%TIln45j?CzPDs?-;#><*dEz5BVB0?A;yV ze#czltCRM36Emh;-W@snWu52YtxA^HmA;KD$98?CXl8M}xE4zM*2`XE-Bci6VQi$X zgl}cRN#k?;*ji7D@iz;8VVha!=J%>+hj(j11PKSNJ5CpZkDH96?|s$#1)Dw(4P@+; zLS2n>t=#!l>Uu*h1Jf3%d#(r$_7Y^yqnKk1EUuBtHl&ZRcPt9H%b5;yVv&v0O{z79 zReiFih+&5CofKuqwi(8;6T7Q7GnqG~V6~)wz=fKw4a7iv^raOb-l^UCK{fUcVzP*~ zf9>dcy;DnE|HOh+kc++~S$e3%b#0xh(eyaxc*39hM)`VunZ2k{6YhxCtOA&KMo>sO zfFD%1U;HEW5kcLVd>FJz9#b+Pt9967jLM+o`!$q1Q!jjTdlJ2aC{>C2%^xv;P#?EX za=AP0I^}76O06tDAbX$t0FUa0o0WX9&`BM*M0hEPX?dnUQC&lucA`}!iB003vbKJ` zb#Ui0ce_!R;R)>wQA@(czzV2-+@zeN zGH~uZ`PKsfK5+-fNmj){AUbc^znYqMg|!p&Hw#PMllo){DOxqYi@Zt%q^-$&UF`b~ zJ8!KOS;$Q#uspDZdLXB;qmPKA{pnJI(FG;-xSkitai`md^0KcVXvem8z2?#BjLN0- z9+=Gi^!Zq~D&G|{7YA>oijhur^KJq_(f8z={afgteyc@uk^A(alb~G5;pHaP@9mkR0d7;F^ z2(jy;=TTc|`-h&)Uh%{C!fgd|&u;Cp7OtFo(qrRmY83Itz-!)hvF$3)d=aiG(Zu6Y zQK>?U1qqEqc)O-T=5Hs|RgBw8UFsbbIx7_CuIQQ~h8rdHXr!~n6G;mhJ+GZ~L@bn^ z_NGySKTG{Lr7F+5j)~{Y_LVo^eMiY z8~?@YW}38R$70C)_GSC~VeE*VW$9t|44Gr8dtJds%E_vvn>$->ZFF=n-K`#Id;@g4 zW5u9FSfK|aIh22JHMJ*_jmd$lNAAxRR7QmTG=%Ay5pl;St=hrlS8bz5(Dkl)k-GIVTVH?x-UU>wWx}kC5mLocf9&jI_qqb+YV&}Jro-1`^_SnOafdu0 z)_ym(xJ>}){TL?XOS{C?=ozsNYaRd#Hi^7yn&hKzf(!ptEKELltOf5P)6^!3w^B(Uv#HO2mno7*P^%VGXVs#3Us-(5VhJ|c z<+R!(Wce3Qo4V+MH3N>DA+exsG__8= z>wZ>f)c5nDHf>QkqVC-8R{Y_*^5Td0l3Q17I*+pp?z$+b&pWXxj{_03bSc|!=Xk_5%{O0jv|*uwSj|8YAr1~@@m7)Ts^4B(KldcY z59qBA9{pET@UE#Ad3i8MAI@0wDBz6y*LRx9!jdNoPuPcT8KlPOM4io7I{nmi&EKKu zlIOz2ha<5*0eez6BV$CQH;wMszyIug!B%{FK8)?P(c0RGVfxT>b{FK+cs-yT=by3o z9=ImfTxBV+ROIw+_XuzOGx!}rU06yvDtIHkU+E6j^tqXP03EwM3!@9-u42#|Lt)P<8f27>UK1 zA-0Vkl0AW5&P~Ls8x0Kbrj*W)Q$wlWN?r(FV~FP6dItV)9Q3-ILnp1;W2nw>VfvS} zB6Q(l%Pe$Uh39+Wn>)8zv9ncMsOT(%Y2&vuo+itOHt9pm|*RrQM2C6xrOIngcqr78J);Xw(IarJYK)@JWfzX>23qDazB+t8UM zM`Z|zVTd@%Cp1KWCw>NyIQn>LT;Lzu@0*0@J(AOLc6SwMq&r&6HqpwG9t(X-agbyy z&+kUVD3zLb#G+HXfRG**f5~UW9Q_t?_-jnpqB6_SzX8&`d7*LTq0$_xPh>PAk< zFAf0}FbTiaZfYkRB=0)&oa-;U4GBj5`P>=%kbbTdnchhO65)CXuWZ6b&0_YJ4I@=O zGDn5(uH>VG{C)zye(t@WWERj5V*wcGH!m0yNB1K}j8zfTmq>w`*|{~4Ra8z#iAuc$0#4~%MBG& z5ULA-<|tvS-}$Pie+xxGBn2Z(^)=Q%$bHc2DQQ|jaH^MGyi_1WOnRh-ykBaGOPz4Bk$gQdCv7uCMr4sKq z(6I~^?2xTM~yy$hB>Q8V$djf773J#VUK0${ygc0x?7W^=^+V7)iky(+-n^nyozOTMpTO_sOdS}T&~mzoqvr=t859M8_4d1_fftR zeK*n8&C(&(ND)qB*`xEt8oRgY1cN5rP$}fcZp;-fGd(stc+-P|xt1#| z)=(G(NdG(g@LvwSNp7)8$W!U?Et8lKC`YR}&@f()ny0^KBx_4Kf`C>hAfwT>j>yY) za&YrYBvU6;(+PgBeYbC$D@NWa#aaOcTveshCXF)8%%aQrR;;{o&DT%JcohD^%nE%8 zgSOTS1j<>}U$C0lq3cyqJT4Z*)GHM$S7W09>m(cRepeHzCj@_O%7vZi6}0knbgQr# z=DOySdJFTMogY_P`Qz)QYnC-A*&piT^*#AP^4jPfPAgaEOprdYqQ0Y_d5Bj2)-N}` zuSa_3mdmI3%|*J{huEDlOy3to+*(33d!Ge58u_J>g&vc-Vlf`$3j!cZtUYwY9Xnoeo6&TPS%I-rxlkZBnaUG}jVb+nvDRX*- z&CR`~yWIM4XHafiknf#);RCX(t$|Y7GlS5Im;zQ61=k0hgW|8?$F1Iha0(x5LD!}? z^1&@JO&kWHOxie+LKX5-R%2_a_FQX2G@-t!VNA{)A_`b4sPK#*Gh>XYk<1UyWKmRDvs&%*V zb?~V2$bRSZTgtJ4RhONm*#O-SSOD5Y*IGyK%?rY75G69=DK{LMq>O#c4)i7&VAHhE z$Bk8v@0Ap-bT$NC1r@2z$X+q^#Q!dAmjDQ-mxMgXJaPux;P;D_Ua z45`Y0wqSAPdR%hJHG}usk-ib*)ox(*zF#x@QnsS1PS5|>$Qfr8 z#{!M52rE7c{xE7P)5Gd|SVN=rwb%m0EYX@b> zkGyw_n+&k(%B&;r?bCQ(aHXIOe_HPzFGvP#Z%Gq-`hT~bi?7QC^G9YM{zw{(OrcKSUJ{;qyeQ^d=2>x`YjK;7)il% zJdBZ7N$x=sNLnz%#=gjbx*m@p${-m7c$F5;hWngvT;WzbIpBG+$l;>Dd0I+-kAS7~ z>tDhWM=$=q>_C$b6G0B=2QFJ&z0H(c-&CP1-Vz1>6CB^`=LH=%Ql3psRrbLfWEpV| zzrAwfY_-+J4VtHb=Gt8z)4u8wrB|A}Qc}Um@Ba6m{La<W*3Wou?4B zvjeBn*s6{<^)Kn&#I}>KUZ6>h?_eSRFbGXloy}N?5k7QW;UeH(dEo&X{1)f=aa%+0qYpTg|+wLr|A`Q;)AQKI$GzgAz z0lgws-vXj~=*u55Lvn)-$EU1jc+@OZ`#&=WPl56-4zlZComdD1KV&A4#ET+s9Saqo z0Bv~(e~GR_pd}t?+nn8*1K?HBql|V39rFv}^ZJoP+^fRGyp7JeYxUsyz|C`tzxP$Z?L!u|(Atnx3$>bN-z1)1 zP_=T1zjBre!1sbtj(`JWta8H7Ff8d8P&uoGOwG-SAL*!$*^=t);oV=~;_x_@5=95u zTL<89yglH0b|vE%JJspM6D9zwM}YF2WDc*_3tTbirG8r+knSiV&zqdJJ-PAS zBYQiO%+dnM-qt_9&_Vrhr#S0LMyWufBBYqNwV|+DXwc}<5_ui@rEK9fgvO&<9$T5T ze=k-bR=V3EVfhThwZO!!vw}Y*I|}} zb+FUE(NLs^m;k0)_(zv1>W!-9O2zLS?&aY-ot?);`6b_XU=yutE4Fw}>p2R1G3bp+ ziF<7FHKMg`IH(Zyuv+m7CJumR7K?=`~CDS6yxq5G#NZESOnGfC)xHo6Rwh+>%a zb8m>%Amt7rDIWJBHt7b|L_S%@5|v%IE0EmSs~3$sX?2fun@(L>*iVE`d|#za`NPZj z^vkeAwN>cHImRkxzwIU_v0Gy|=v{*wFLz*6jz6*gaF0Eg7m_h8?p`|+zS^ri_`43a z6{&X(VlY!}7oVYL)cL})13h}z;~Jeca{;T|bZ}Fba}5_FkVzD}bQ+f#zlZRDXtS$>uthl#9*?;I^>n zJh;tk+X-w&hAc{B(KSn+CI0uLe@7>R^kC<5x%!tN>zF~+oK@1pK-zYY3v-YxS;UXb z269!M3`3__*mLGDeuJi5Zo=TtJT*6U^_7vjRg7Ko!wqh32@d^f zgP&%GbN7}8>m#*p);%o0rgxPtIx#_NL*;$p(I2K=?hk(o(Mh`wm9($*<|Lg*UMi0N zMp?j2S-Ol&mcsCwb?WK2r6dnK|4+=~|DA6wt+3TI{wSk9>2<%3xKm|$9#>Cn(3`MZVcZQc;u-07hY z&?*UJRiW(Q1sWqeVNr;zEpJI}n2|-7ppi{rA*U(Xm9O!M;9BUZIHxV#M04U~GmW~% zqPYgL=hMawbih>xAI3R_fVb$<6E+awMCTxoMerNM!ut}t+4LS4ey@t;wGP`4%!x4M`-A%p>i(D?G0DsWl*SHpqB zVOJ7d)_)U-9>hLb{5w6TOHoxOZ=h5q>J9j-$xE2zf^mFq48mwvY+!e>CbG_p!lNun zsf3Lls=sa#E~Ds@fn!1Yw%Y?vX6LLl;T!6M2XeE?5g)`4inh)&C$pJe*5u`TTPAD$ zuuYgSK`uD`ic4YMz;rO=N)XPt8kY5HmxfEW>ArveMA=&9coLwAA5C*k3&Qc;SkUHl z%gm)=S3z3!JW?PF;6e}+A;%wgJ%EJT`P}?`vSRcTk-tHWrn!}KlUll$Zv-E;=B z_9G1V9j?hv`q&P^Kpk%nb#;{l87;U7IPpC5V+nN`y`kg)BDR(?zn3mo=c^@me=ib#cnzvQ7o)+%;u~8`+~@ zHUz68Txyr!)5@aAL>%ZBuKrUs6(myKIu}OQrO$ALZf(;^Ytm`%vhdwB^WgDyn?A2wHo;dV&|73|M}yqzAAj`Ny`AL=|08u_>(vPyUn(GrYTe2qt}GI?}Sxe)e}y7Bu z%gqN(Zv^%YXEhRoSpI|^cXW*$7_W_F$2&Y;8D6TNeh_0wa4oP%6kEPXZ=I}ebI-gX zQyk@N7EC*dd_^bOvG%|*?uM$NXwvFODgkMlejxaMcrS z2(ay(a`Rw~w+%-8*k+eQTDxzrVWrRLLVCmyzUB2b^mG$6e1DI)j|lx#)MRzBHzSl9 zs2|rkHNn0IuGTRe1J0_)C82KKX&ofstq(!8ZYK;1>aGvo&5wYSP%i8_~=5RON{sDQc{RFeW#Savqy;Ky|Z( zS*XKwof_oP3uieO1%J3~Oq6Y)Lb)tkF!ei%ufP16!_r%yJKzy|IV8(l;-v5n(1duD{DT8LV8Ke3m(lH zUwvd!&ik+CP}P2}`srh*+WY;1&bk^A>@6aCQLi7g^F56&j^mLZeTEz%x zCLqqX^uMpC0LIj+A!m#H3ec*?j;xzjHq-uqxMJ%o2S`(354HQdUHM$jR`yo%{z49T z=V_3G_klyqHafIw($8rolw-m@w)r!a8J@d}5=6U$v`VsmC&81jEf9sN5lTX=#;b_p zUrO^v@xQ}9aO?5ulH-oh9_17Lenhj!qsBU{@Upxv)4=p4R_-L?0Tbu7Xk4&~u3Zb4 z?!z0K*DSBpNQ^wbUZ7QT(scR)BF4hhPIZAddR{_z(q)S4XyEk!2~ql=FxU<@2J&K5 z=a^>~e(QW?(6eN{Y#D9k<-yi!jdGrOm0tM4dl~1P=@Oqi zrfZkeWQti5i)A>Fv%fa)-+*RTrx9Yh_idHCKgvs3SN3Ej=k zgQ4(C+fKw%P_NMoS_s5C7-K+Q)-{ylz#F}aSf5P7ORAh>1-in1umY!k(U-txoc;b* z|NQfRbROIS(qKTSAbyk&RUTHDbzAw1t9?R!(3>uxT#F+f=f{2VRojKP(-w&P9+%2b zghI!{HBBf+0U6MPx@XEh;lBY;ZJcNG2{Iv>S(-hr87`+3D!}vduI!Ins>8vFaW1LO#%RFk~Xi|c&&8llvT z+d})LbW^UKEeGyE&C$#v!t8Bf?o{|$?c0srAHWgVLtWIa^b7{=m5cYiWeMjCx0<*b z0D$bj=nN7krL44;KPv#%7ePw$pL=!%>X>S%Gd<$f*dq~NM0NfVArip1DhJ?IM` z-VWMb(2Opu`J9juo!a>YySVoVx!Kx02K@C!agTphI6fZ_mq??EKa*(1JMtNLT~*`( z{Y&^MXW8BqHd8iXkc~^ifde9nlr_#I3dF(60)P$QvXFeI6OK(X=m7}YNSyh-8gWh_ zW&h38(Nvw~^-E|)4chcOJ!m@@O8Ni;=ZkOp{<$@htH1`8266{Gw0d7Dt#~|OnZV5r zPo;{tl}=njz6tBxv|_E5Y%|Ub^sqKsYyxFD7D3?Wh1pzy@=v#(>CE~+$^wOXuGxX5s}b);MC|@( zr4d^LuSY*$?Z}6SyUC?G9X>mhbT#HEeh`_3U@aWtRu}VvB2iDg0Je*M?1rNWoIuaLgD?)(%u0#;WD z78|gx8mtg5%96@%w6mOIR~5X?z~T&nlcyOap4>Xwml2#-a3@ZuL1EXTB}=q#4dc6{ zS?jdYWJD>d7)FO&8Z2dJneFa{>iS6yt~JKCG*#GSuc>*^D9^N^em(obyj9zO+uwo3 zwp!^Mr?b@;sPq|5<>?$>?;&VM`p&XyO>A!M4K!ia5NUm!DFUR>;ARWR2hU8h%!%_- zfxoDxuI%!cLpzL$2y1sfpvOYRuuy{6YsPkT>jo|5!7nnXL7tk-foF* z<3nP_-v}7cb5l>ArovxOD^FTcc7b#oL??zv|Jju-!Q{yLk=%$y@C)qcnWl}OlUxW} zn?e4D#dvOzfN0M6cCu(%c;RC1J&7~gqA5>rUf2@h|LRFY1LMse4oSIVH*Fn^TTx*t zZ8D6W31t!_URf4;b&*-^O4F=Xh@M|TGJbopv%f~lURPLgty-1YlA3umQ5$vYQ?%|g zmF-rYNQj#9qWoZ_g@5wS*VM*}fHfyXR44Towgd7Wqpuoh9z=~D#9Wea?3IO^!~pj4 zH)`21dY>vMm>>$b8vDG+xgxuYto-@N4p!Od5yHh}*TBU1jT|E0U_o9G--*~SM;mBVU0 z?II=4jd{=QuGB5OcA{TbWmA%l_H4H}dL}xEDxAaxGr+7Rp7!2@O9EY|n@t`zKQdSq{tzIG_dopvBy z%08u)4GxPQzfNWz!Il4$_|k-PQ<+<4LmkXX+OJ=?%kmukXp3)yjKybYq)Ir4Z@==; zu(_TU+Qn%4ZdqLVk_P^ynRZ01hm?+_<-EY>g*oddlbp=PxC`T#l!?cUCcBe#C+Wg( z`##KhwR&};Wq}4MC`Nz$RJ)AW^$&Cm z)!DCu;C+qtMaky1vC|3yW0vTyhEFN^&ei>FF?(hwj!T?p!sjJ} zl*)k<1%2P>@`DN_tK6Ielk|L+3u^LZYst?-*;+JBeJ+%d(}MQ&L8dA9E(hT_O3o)Y zC=?w?ZRnW-Qbsi}V?+z^Z|7vxHyFvDs54k_5&^tX>E6zcaJ! zW&}^R7)0=wd^;7A>K7QcS&935b+WIS-k8|*xgh@y7x5p>HZ=lGzH#1VUagxpfk$l_ z+I2cXB*e+{R5J*DFHV*XM<=ThF)x)XQsz6J1HVMm$!8f;9gmMnXtKoU34Fi#u-$RvFKU}pT`U6W*5|dbcsb%2qcef>m*3LulINK%a$Sk-1=AcE&wu|p~ z_>E6=@VM}Hlzj6@sskh@-5!9W)qQ`o@^fNO^X|3|aqYi~fW4c%D{iJa(~9|7Zl6lc zUc%WtiTb?NGb>Jhr(L~PHPx62jLyAA(#SUrAkeAxg$8|Ng&6&4#jYxpFjQC3I5gK` zP7tCW=Vi9wk2o*8rfXq)5HrEs*f6uxM`hJ74m(UIdLobwa$yR6`}62~3E8Yvn{7Pg%T>nELf`XgE| z%dFOJr8}LiA;~^q__Q-_F3g~D_TJzzy_D3anNOXuCYBdRq0`CgZhNT%v|HsMgTh0+ z$)tJfnrYt01`9}@kghX0D$aFPa>?(jZD23Cl!=OZaCog25Vk9xdTLs*ysP?M;PgtI zPg(VpzZlIE+^2eH(({6a4^Qi2?7>R8%u@)h4E(SvwvLLzcuUrA9d@4KDbMdj``AG| zl*sR4SbTmWymo7cV!vS-!f`n~L{69h0KdpT7 zZ>;y;_#!DKc>!lpBPp;KX(_zw@+a;G;xo#Y(n*P=a8ablf|dHj!GkG0$>vaJ9MQEQ zh9|XSWV7P%Bq0nOyI(~ln-<&-Lakkr!eAEm7vpC4z3I6EP&*h(*`vXOa6S)f?r)0e zqn>b9JejA#lMvL&@R{}pSGWM$<5h@Q+G_Wqx?b#)Nh6*kraJ;OT&cHsE|ES5d(cC7 zPP`soU_N5cSXfiHUF5;p zNRj1`jeErFB8KlIKl$13%ZoXt-YxI9>8(#Z5&L93RYsZbDwR*Kf1>t8=a1g^^GsRj zlC>SG+iVN@Gb=X-#8DZbLv?cj3llfINDwZ$wgiu@d?vU6^rt+{G=*+&|K(rb`ZJn% zp62s5ZZvMMV|O>|gYez(eS0y?CKIaMw+_7!KNlC; zJ^*;(oZq8%)KO3Iy9ml|uM2s>Wp5WBi9U!7N|bOh*EsP%kA!l-(I<< zK1q!q?zcx>0y_CG{P?X~FV;25VHK&bDra`;u(t`0v~Uc;OSR}(v}P(l1P zT_^(!SSX&73R-sGK6vE(r(G`z^)aT31ENoH<;V73y?f;BY0ra% zJFMUyxbM&wMOYqD{v?CDvp=Q(!TXt#q|fx|`GW03HqSksJg$j19BeBS12FlZyZCqA zc(&N!t)2L(YXN*h0(ga2vV3+P9#y#7M~R)Vh*aO#ad<`evd-aMZ0Ion%JX0%t3Er; z5+uWiHWj}HT*;Eia_(c2AM#-PJ|1)oKAd%dJY_MZfgYG(xxH+yCJLcIGu>f_wU{aSSn z)j^4-_-4cBP@R-VK^K7@Yl96>nmJD-Pj3bBQ|6r&-4Q+m(tN&y8M8O8g*@@VZtqV? zg|iPvesp{HRD%>Es{-tW)y_zL>bDjTw2*N(&+i`iIspyPPvd(Zd4atk8l~@DzPy!JrCKxn?c_*$?dVeE&yCv( z?;{1vN&|5D6|n2e+7pZ6y1jW8uy1`wV!Sa_+m8%$pUPZj{fKFRAv93Kcq{AF;=np z9L_Xq2dm0!d44_t+^1lI{UhLC*eS(3^r}uN1P<$>$d)66q3#lWUA!5Wg6EJ}Z2arGSA=b*BSqmTD>|EY@{ff|Mh!T&^9i!X<6j5wS0ScJ#>?|9*G& zFl^>@YTASBp~Gs2Q+dvPzFjUk?<40|o=~uRE&a8n;Nf3VJLAoH*QZaO%Hm;_*9ig+ zAuRk?%R3-U0vJIa#!`&N#9R1|22YYxC#z`#F#5neD+#(Vk7Vv4(N!nSNGy#p{paQB z{aR!r&$fSD_r8e%@E#g69(v5om~h#qGPfXu$p}dO#%g(76 zHcSp?KE|pErZpc=Y7TWL;#DR8t2h6bYX>Q_&)xjM++mxar6(}AB0CT?By(vC%iLww z2V{%BA#HV_w;NM8Bw3}oh*ur>M`~eQ*q+r**(Pr+U4V&Xj|2{3jPaEGuo24oaYk+E z=-yDt##_R{XEab0L_3M_ha0-}mDeO-1uqB@|89Am6V{_~}K1K=O>E7q&@p;wEOVlpT zj`bmTQGq0c9Mi`+k=UBz&iX}4%3WSEDy$!Lll37bWOyey384z?^tHh$5y#4eQ(vki zq_zmogF9)nuCL&Lh0O-10}DUT-Ur~DG9$wM_2bmFUN?I$ zeHj>Lvw1!TzT6FgPYW;>GWgwML8|N+QX$NoRe%dKn_yMs|D@zN@OK&PEF|$6+)?~b zpUQvG>AX|HB()y#$Z-QvA3(kasb(q6uIasw^l&HUu8Ob7tw@OGW_DGl5cIH4U*3jB z_`L@CCSUhDRq=U6zWsZ$)4FK{EKqG>fuNUnR6_jg0h;7h81@+!jP>wcx)dl1hL16%LDvwV zAIv!E%utnbe&f?S!5O};CDN`*(#}B)Ji~##`Bas?iY*Zo%9mXTHXv%V|FRy0u4Id+dslpIbB4wE6j0&WAmW2mXPv_F82j_VoRe> ze$SxlRtQsAOb{{XUbL3b(8ns-?!YBU5V~&zG&IFZu#bU5KehS$(G(mx!H0Gj}I6?6@X0;Y2Bn2!VfLq zfa^j*JedMd&p;08D1J=IvA~Orxan5OgnH@!bOw@d&jy|DtF;hPwmI%HTknUOF9sQ5R_q)IH+3#J=5|0zKhfrl{*J6K9^he3Z0 z?VDXj{{`28ra3JqEyb)VS#8oLF|=63R(CtjHwTzoB3aIsn$K5~m0lQRm$Hy4F^u`x zIQIizkuBqGtF^?V%?=+wjZV(a5!pFiQm$?+3rQf@Mv}rU5bThS4GvB$Wno%0qrqs1`bJYo1Gv*~hs;pGC(c~+t4s`SP z-L3bQ-GHNC<6dcvQ*9w2bBgg1zp%-QuaY-0(IMc4t9%XLJ`5agEHBG9!-#RmUjTTcnVR^~v>XgXQuLr97Idcgee1}^hua>49gOxGwdM5fw9HaWV z#h)`}NikU9&k*yCL3+YwDERZgeY^?6n)UcT(8&QqlV(~pK)q#Lj_S7|M7Vj99@mcF(MeBz=UK-!RjrT2A}n$}DV`SS5d)wJ8VVw+nP)k^5NR_sU;Q>(*5t3XWTS55 z7%WnTwd)~k)t%TRgdm4_cV!wR72GXZgoRls^?ls`v%vzO*=zyT365atlmdvylO;o);#d`_(-@tFhh*+yB;cdAFmyex#^mqyvRMA=WN zbEvuUvM+x>diR!A8SXc?h3>PFlmn4K=JT?SiNM{|4pxb{zwfNdWc7x^6f4GonSofD zCut?F3j3z5)9kPR14bE~KQyF0bbnIPbb-}}0AkLd0vO#Ltln;9I6=0dz5j$0tLXJ> zMaEFs@&Rk7t1IZdbKef77uUAGyJ$ZYoSQ;yzO-z=@spd~;M&x%f{4Pi4k^sgn+-1! z^*=-QS6e3H?t45uUjbD+EN2*Q(SO>L_>&QmRx#w~l-H==ZyN4x`4pB8J;(*Guz>Q% zt7%XESo`U;*evO+n?SjP!%Ml5x}YRk@MC%vE4u9I}6Sv1f1}c zsDyK+t#_ygehrbJoD@F>bDGIxb0oB6ztEPjPjMr~_?;}U`G49(etH>( z%hVD;@}Nq!$0)!HB@PAY)<=7Lw{+b81w#+))2D zSock=Dv_t2p?=Kt0k1SY2+5Y*)+egME9hs;4Xb*hq&)I+h7;B)<8*T(_ioY(c+U_$ zwIF6$^MG~+LVO*%_?6WtmrjG`^=M*|l_Q+UQY~`CNuLAFw04ry&^b?GY z4Sa86&%ZVMX7en>!Bhd1{PtRRa+&zB_%gTgZ^Yxdb%MRrYwWvKVHn;uanlIM$JQjd z3t<^NtO=ro(adu1r>R6k8-z8UwT+8(tNBSB#gv>-Imz?gx19URNN*=Im^3(qnaA8B z=w@U=AJF*7SGTmyqOK|rUN(6?ZJI__pEYioUod@;=XfOi zgmSd7nYt)Z-|s%QNZ`a|>KKHk8=#H#93rhXequhw7$1F3F{fT!)ZOY0aHGv#{2{+^ z6D^3R;cg&mr+@Fz%k5ujyja<*JVzYzqyhz+kIzB}^!!kbH@89%`!Rkem|98v=VI1^ zDxq4xSXcDb_qW2Fs8BJO4*VBo(!@-0pG}bs6lJPl!E?I_G{(nC$QD%xj%Zt#9pVny zq8h>x?dt|)y0&{T0~3jk#xm!3TMQc4kr96kUidu}3hEa40oHsN)u%)R?INGif{ya| zrts4(r_ZyOhraW2Zmb?zZ4xqx>zjaC<~vEW{hBfnQoI7K|5YhBzP7ej5%v674G+&5 zF!aliK}Y2=kA*_7zdepq*Ws0u8#`}h0~lNLMv!_|g;9bGE6`1FFzo6`NPxhrk&7)# z$xhK$spE*(Q;(^<0SoxeEwj9#2;LZl@O&1vk(HZ%m?x&L=An{Vpiw{c$>nwodaxYUvng8 zp?rTBRxH)X+=F4H(dnAm&M?z0_x9jx0VL4>hF*S}$K6C;4J3R|(F z9#`b*V@?O(sU(@!1EijK-|gE8iOLh%DIKi>hRfjpFx8Mb96b>XhpRKO>JBKfLr&^9*U# z9S&>pf5^nn*b2ZZVdnQQW*Bq6ysu>s<=>cm<2_S^IZCwq4}QD|w~ifs_ks0pGW8%r zZ2$a(?w=o2o&E?Mx6b!LeoqmOxNv5s@1yA05zB~6A7RJA89f4>h!xMj?y_(y=U#O)L{TTr zCn5kYKH{tKpfa2`HM#rZ-**vJN(%L?0o60QNRaHxrUu{Mb%-F8Y!xE`F(0%SeB9Rz0NayhxlKeH|2EH-J^{*-ted9x?)AQD29vvz5VsI^td2xi349%S1GxPYHUJCS=Ltum zc4$8~{f8$b1ksYTs2eTOh`x%*iMsenWwPnzmIWh*-e4u4@<3r8P!_;P4rMlkSkE!1 z`Y6BvG_xy#jA9-iI0}bSpLAg53w?pEj$bBAxMT7w@;;)QFI(^ZTtC9JBWvdSir8N} z*ETq*D2~9W50&(n+>pUIH^#^ja_i`5cag!3&wSNwjuhuLEJ4~-c#B?l{yI9J&JJ-$ zawPZ{+<>b+(_<5*!PQNXTJ3AI^KQ!*Cy8MT*REId-LyVj(Ey&6-9-)5cY-a}Z!%x9 zU~q@BAhi)ed{6g8NuQ<~&i+)G{}o0|!drZxW0~;dRF%JB(ciq2FSP&jaaLqyM3Z&W zTH}BZmZx_0V*6Y?l*-6-AeO0*2q7lz3KNPms}=sqKVg6#2K(-9Ghi41y%NBM-9K)E z{#*{zW16W*GYy=Y73l@$bx@k5cie;W%}f|*Y>K_i-6P4Dn5CPV?{gd_ zhuyhkL{HGSY!M=G=GL&OEGJ~>aq|16Y?k!u0q$>XEbv*+y4jCo-eauejei;=5bz+rXb>2v6l+S1xMSD{a=`4oQFfAECA!6aH}kRdLjKeL%VCbvDUB z9a~`4a=FQW5SzK{=Uw1lKW83D(j0v5pTt(n>|i80O7R#w#w?X5{fTw=SXSQp%2C1g zzEU_QSL}FF&^$x^Ac%%H);1V_)scxCPN$Sj26${^_dH`MkD1c^q9upzCDgWe#EWGZ z++xddi&-PF3C>fJwKf(ACjsJ{fuWveFCIMH{iA(T-wdwe3o!O4Qh8fuXt42le5zrj zuaCB?13Az(?^u0|?ZkF5y0^y5G9RwPVGgGEG$~nUcxw3^g^vHoWGq3ON8g>aXf6}- zFx{*JqZ!sBgV^PSSM>+SNoM{hNtS*fiFH^1vTt(sy`=~9qn4GwI~O{3WcV1a)m(j~ zLVizN?l@;YG%jWOy3N25t8CNOAjGzj5^YlNTLsUL;C);fwb*2q;E4|veM`k#u z?ddwhlpVC21$^^j`8uit)p6wL6`{T;zmE0x0JU9Cm+@59%c}2Ik|z^#c($Fpt6Ky- zBfynn=t+|&3;yaF4`+{mZWTDwEWJml{6^i_@M+uYqbL6*Eg()JU-t>6dgtpGUmzB( z^ej(Obr9$XdRyuVs?e$Xe?bUuo%1gi0+C;BRT5Q&{9+})mWub!m4J2dEj-WG{u&?M z{S{5mPcUPXZZIUSM9;92WH!R8NQMO6PqZ&&?7+(V<(1>qLx#wPju>RB&9YimptE(Z zL!R@XL5+pftA}+`v01OZuU{TG^5(@4@F9ecA$p#8M)}M+pU-FvO(#AVgWnG3v z%5%o^ob6k@&}gfWH-L_!{uVCYVHv`m9NV_rjO23LlGBlul*lMm5Y#bB7=8#Pa}#|h)Q!1sVo4c-qN=`Z zuKzka^5A`%q4rD|-j{BLvC|`cUX$;uI7pVe5k(TYzwo?0?-nTebdoJinmGpC?iU z&RqJAZ?hf!roSaJh4K7z))jn33t();mpOPLv}Bq8)&^?ld8K;frzyMV3F_A5LI>~P zvxjOwIyNCqE@d%#G4se94rv>Mq>-Jxd6zP^$hvpg?H9p&b~IPJKWdMd)ciH=HOaM{ zF~mN5zi+RF{>iHXlIi94pH;NH2;4kTj+uT=QUN||eHHgSE-WT9PzH78)Xiw|_O7cqz;TsFtCvQGYE zxElrj-PVWCHHWuE5Q&^G`VALs{I>AL9OF)4TUXGMo*f+5Ehfjhx|^3%R4rP&H|JdC zS`1iz7tzedqO+0^_<(8OpTze>Vyp11?_2keOG+r3E^u@|f6?eO6}z7TNs(1G@Bz8P zMb#!B?*IE($O2%vjUW4$F5?T>=ZI5@U-3t~b6DTyUVwj!)6T{RY~37pLhUnSv%v=Y z%$Yl8Lv@<`i|o4{ppg{2#A*nhgm}Tcp}(-#Mu{c0SYKpuD5z=+pR^6X8JcQIvyb_7 zij5ym0^HZNlY{^b6Zr=57qU4j4-NsZX+b5WLllEOO_!%U6>VNu$d$s*wzFx z8~lW9dVatArBv4$;*R(dddS19`^d57`V8Ajd4$1(nQDoPRnU+y9;Gtd(!&i)8wd}> zWH(9|`&)IUz?qGWxdn`S3f=qRV`9P7#hDC>w2|0iSWOY6fv?q`JdPqniY(V+A_caj zn3#&DRL-1{ADNM$?W5y%H6J0T$2lmFs);8*`if7yy!C-b;JIOWmWC3k-KQS+X_P>V z0RTD`ejp5Ju|oq7;3nd#55<`%Wb9sk?32jnc*N@nt)9mPX6KLQtkgcC|poC3P0K9T_&M4Y_^wQ8xdk)PJ8pY@ysnubC}l}x97%r z1EJx!a~Y4_R5)N7Fw-#i;pmNt!NaTfpQSpeTiIr)sRpVTupaL|!+FkI@E8t+o*M;Ezn z8JTLFDM-$2LCkYaYfjcQz#MU%iy;phIJ>jQ$~P-V|6|sVw^BkVkR*OOe<#d>exHCfQtb$6$n0nGZjqRsjGX3>18#~ ztoU5wCGVjhdk@exE1HB(cbR7WCabtnHIxac=m7~DQJ6ds7R%&N z{1pA^6g&JO`&q-Az6FmLHVa(`FX=5$TIJ?M@mYJMX)Fd*N{uKfP(>}EpO+{f0=iD~ z-*oARu2yTh-(^8@qFOxby1RwBI{~=v>NoC>7a+qfmbVP|vn69f;?1uIhYGNf_h(O1 zWp@NHCF?v3J^RTPCs^m?wK|NS%<9oEnElMlM#}V!h8Wk6ET13R@_I0F2olK&nIGIw z(VU=mxNGd!hg4irJ#))o8OEQ|!swiYoMqGh>i}^ZiBqj-eJyCnVRQ;aY=gx$mtOZf zlmc$=eBTo;VH+#DSY>hr1>`(>s<%e+`pVG-~LU# zCEk7TR_n(i_Llza_wlm6ri0JtdcDmdN?P%<7@5Xbw?WWei~GltoNocxeYr8y<>#%uhDw zhx5^B?R5Q{?yf4wL5fqX~xODH=g*B=sk#|E)5ys97l)+f;j)&urf+P$&o#@x(i`MSIxKwX? z3poYLo2~+n|9SAz|Fze`rRoB=Pw_MiIwA1KNC64)wB({fF)7c>3?X{IWMBqPIKJ|M z*TU59jeR2&^#qb+cr@zS$#pe!Zq)ggj}4^*r;TwV`-?i0cm zm{Dk>juhSaf;r(Jy8TwlwsnyHo>-u&dRGt7+kxPWk7}-jHs5Qls*xs(LVla`Dg|ml zIvm$@K%SUv7S3T$og?_2wTr5d?5?x-@9AZFVvR9Cc_1@0bqgv1*fLUsPWx^|(x$Kh zpCiD|HeHv}8l$M<|L}wL%{KI23W`(-*NOG*+j+Qxgfg2LGrLputm1f=m2Lozfhk!X zK5Gh!z)$Y9jaUvVbPO{sg}odgcR+mP#-aws`ea-;=LEl)P5>xUxgCH1eh3#tQay z8QKR?Z*z=%=@ESr!4{-Rp9Iz86$sMj81XLS2qbLKGz~d^9-=;Fedot6=gisKb0}JT z{-jgEyB7Iv46;hVTZtIF#!=<*%F2g zeVlmbZ<#8%8h~rt4MP@VNtHc&04nf!^RZbm7HHcix3T**R#D8$|ghf4>%9w(d65*66H($N8RNsFR@}TT~imF$9SiK zrkCzLRlp^Mb%&<%O!@7&Y7CbOGGMb`xG?gUAGIiCtX@57$MWv)ca~0?=A&JeTcmE- z&{Hf;!Fo7xJC#oeAz~lJd$mvVg-w;TaV7%HX*c~q#rg}TK_@U633$xLM_FW;^4{uy zr0=FAM`JUyHuebX7b<(B27PPVWwMP0YROs z=m+;&pomJC5#|_6q0bTFFSrK|W8MJH;C6->du*y@?i6s(9-|TwB-?~2A6#D>hm)1} z1mb5Wi&m4jVdOpVJsoPAwXst)#CH_6k%hC}c$5^-k~hGX*GGToO@~M zOgy#O*4;V16B+W~;n~DQHT-nyX2K?ZbEbH~^-kKa#4Tf@v0Js1Af2%)N4yEBRKjO;?{(}E zeKz+t6)zaH|3kbI5Gw%s+BCU$m%YVgf!uFvfRZUDfXT8C6tOr}aXLopVO#cABHERvNnjQl9AMOGAqQZFM7ZTyMR^t8TN{neN%9 z*_PQis{hJ(Y_f_X;eIp&MjC2+O6&+5FCWNp1p{jq{x9i8pYxvNeko+bSpj z5_&AobiqAX*bT^U;(GHMK%Z(2y?t$tm<=SoV840FfRUvFGZ^xFpfDo36>bCz3to4? zcZmcwXbNqhm-Ph5e>teX`5aU_32$8K5J*iYC6psQ3J~QBzJ?O>&UlK2hJT0*;mhEp z0HJ1`lweyr*!~I%(T?g=hJGy>q3=6R*cm+>=`RWwE(f`$o#> z&T)@|zNg-;Xp%_$y$bt3`V{Nh@iOx-z>BOt;@H<2rpAHQ!fEU`I=s~FhILw7vZur7 zc1j{~<`04dNEIC4a*p}Iu4>VCoFn2(7F2rF z!z=^?3P*GlUX+0N6S#|=3_KP-YlGaDj5;7S?)sV10c2y4+ohM>H&bmi(2SdBzL%Q8 zI?s(Ru!)}IXpXLk?>_Oo0|=Hhn!IQFJ3jSFb^oK6LV|(x+d@TOSDojEc~4D7md}}1 zvLc0ZOhH5q%AI-okS=RO`4#>75KT@f504{Bu(Sz$bWK54^&-B-7C|kn>ulIdTOFhw zXM_gwPyt(}LlNP04(wL)J7yo|QP)lZ0@QNZ@^BEx){RqB8V;!PjwX)h3KJ7?veMxD zLf`J874{rt?N{2*%uT1N4EZd^+NR&`#wJiG3NNHhz)tL^X~Za z6{F)_r7#!w>s+^-(Q>f0>)x9&x`)i4J@kQbS;cDnF^Z0O?N>*>>$eAWbOJq$4nO4Z zMF9U%nk>>~Nc$CHO(;Et{H2j>RhFc#t|W~@bDePT4b%*Z+J47Tla&s9ecpg_<@h(1 zN88z}HK)zkAq+?B_X@b5$aJ5H2ur0)>8=IW#r2Z~)OGwq4mD_vJiK83EbtoI>y}25 zC-KB^^MLA5+|1M^o1@rEfVWqZ1fX^Ao9J1h^~$*))-tr_0EvPsQ4OeuFJ0P#2y4Us zky9=8aa5%CRbuxIJApH48Z41f+aTck&Fm8JsVkin^m)^P%|3dIP5H*|XHCjHC(A0F zJ2|HWo8)EW`^doaCy$e71~@=Z5bEZSp z>f_yjdN#+Sqy*YwGRIJD1%j~B&Ik-3`m~}s9(+Q+1#37!a_RD9z*ce-chc7ER~6c- zQLXG=?Uvr$pdRWh^UdUx31a%K#g{fVsl?=I9yP{g?J6hoa=wCyfK^}5+i?s|M}Ug% zLg&sg1;yG>d+R$}N83~~2D3lOF4qYE49qXP@ny;@=EG`y_%JBqvhTlR``iLXf`(s-$h52jzOgmmh3gJRf#Qq5o=P z&ec2~buF&;;OEkH!-vy6k}H-4#>&*+-26Clm6NlkurlP+(D^zxXOAM_@pQOr?Tq?+ z;CdBGq@XWZ>vENO78=m;IY3cDM9!Wq;_!z(L#^~DR;^ez zi9bNS@}_kOwcYfK4oUmo+O{S~&@L*ybd6JMmSY$m>L@gTxoip0{tRo0}@S=1{w}tlXZ3+;ekmcpSHO4IKsmUWH zgdUPo3Qtn_;b3@4)$%}B@xn7Ws&>_^fM@FtFshtXz!!HQ|eh-+?-z-d-`z`Spj zw=*9NuGc7N7)}TUEg`JfZ+H$I7Adan<|~aU=w&XuV-FY1-NQ5+Y@ly^W)*?%*S$le zi%bv>sifx?;ZS6F0oIYt;-8owVMDE@hyLuvTtC9e;A>-Wv;dl!(Wv*>BdFETpE&^M z;sD!#(=>pM0aXAI%ue4KCV3xNE)by2LyC4Tar2*?%mhm23xy)&=4V5DC79zs0ySma zWiU|z!&`_Et=M(od7Jcj{WKJ(l3?pkv(;U*W;j*zLY7OG?^TvcvLG& zQ6ry$xXvj11poO#wQcCc;*%@6-!amYB#;6|#N*3>i{}Eq2SBghF!Z=p*wG36Cx`}j zbF&hNSU>po4WevA1$#aG34t%T#+sgg&rIm938EYxh$eYs1m8O<^aT{)P>0P=QbIF1FS0*cvm|xc6gqd0K@>h}X*MJ7vd}(M?8q&y=1*b54T~ z2X^SYRIf@=LJHj*M-xY~8!*+?W8eg6ObiIQ zhv)eYHJ^f6mjLC$krO^r!e5YctnPt)KCkMZTnDX22g5nyhiXp?z(bjf1wD9Hv5lZA9$I!>GGifToCuX+oZOX(VnK_lzr|z6!MEh#0_6PhoIJeM!@Vzieye;$|0ch) z2Wh=RqRXL-%fI>FXBLdiB3D88ua+4mtnrsUl>vROOjpS6Ty4ED*_Etv$|0vTfaCub ze*J$0L{nB5HqNsqjC#L6{p8L=u7lECAlLIBh_RXnrMUE`$2@malO-Cid6)ZU0<(_G zm)f|B-ww+awMmUtF!AAMyT#=be5P7lq2u_5#_yM*y!DdLC+0L!Wf$%w2bPi5=eM6i z#~_F1dhVzzENV`5anlzRqdp?VaXRo5rQcc)8vK00!TuiLb}H=daemsXT*f)9jM>jW zS{;d1j~5Q4^A_Z(CZ~(Mnd>^Eg^2E_d~-#` z1JiKAzl##tRR`*#$)V+8WOuY(62fR>xuuS=eaDph)8-%r4LqoHAgNs6QGwg^s|C$u zW=DRGrkrcy>5xM>rpK{Z)O92U3#nk^{yN8BS5P_sAnaE!uTU1RW9RXQoy*T@xrFMc z(COj%2-)qT8eSg@f`5ms;E?ggJsGXJM-}rr3X|Ey>Vw~TC&B{!^CRthwE1vOu(q8{ z;fQHTbDM+|*Gz_!d#=-Ha7B4~NCLE4S$!$KYq~;#GaqJo0kZ~#lDu;1nvkW7aDMNz zers;N5bRaE`x`uvOI9CLcL(K^c6`kNQT4AXi?mTj;4wf{+>##`c=%hH#)ClfaE*{N zNVAXg+yHY&pWkW9IJ%bKG4Pxm~geIm)_%JQ1o-x_~ zTLKd5%SU1G)Y)%eFY;UDPj*Lc%0hCj?7`X6t1~SpKQMz~?}bH1b6P43m8lUGT}m$y zLlRqcwagWFjw4px1Pj6?)=2kxeo^{5PgC6HZn(e$ zEd>cA73)srHd(H1FW^@q)O~Y5^Zy#`vj>NU{0JF8pxYA`@<2H5XN(8$tzIF@kZOs7 z`?gRk=Z{CPC+Y%trPZa`@rn(QWcgF<+Q}jK-=QVL&AxmRk;Bvkh!eo7j9 zKW}XgQE~qZwfLjm~}<|jCGPr+wRD>DO)u5n!V4`LDYoQ6N)=; zp_5>JaBH$pG28umFSLA${sV#GJAV1s5902HV)A7NK2@vu{w^3<;+4;Rh7T`%Gw9YUb#IpHMOS;H^m0Vh889y@xWW2VZvXF1}TqOXr{O8OA)B zQmWB;*Sz>y%xX;I1L5&j^^qnm>)X&pS?$44M#98)DMOFUPxI}RhCXVGRb@Fs`+}+oXV7zkZ&GG{i|PFy*i0MOlis+_J;+O!g6Nd${>!B zaYQPA>l`D?dN3iSIeW8DlV?N$b}0R7qCK{_kX@GXGd5EAtH8m>T0i1m7xbVQ7h-K_ zZ`ls&jnG(U&v~6ul?u#E5Be@Sp>vt?*@BY6(z^W>`z9e0A!^8%3|sv@&Oy}A=LGz7 zcgM)Q!-OA`LLkZh5q;ZvObdl@z;-}U_ws=5%JDTQ#HbJ8 zjG#m84}RR#CVYGS_Mkr_qOl$&VAHL-ZHJ z3TM0qAaxJ%^bVHV6t;Fx0eaS!`=bq|&$Obw+=m~zs8JWYOC8TjSL=8LhTAn`ev zP_K~B*Zk!Q-X7P~2p+e7iD8R=N2zw1(!dJzrIVMuclErkMt=s-@b6r$r*6CKNea40 z>eal_-P?Qa((n1n$*FMp5fC^DdZy%sYPq?GbIby9_F+IxnX9c6i`R}#v!%|v^MWkz zV0+P_QE}iR=v>DU-oGYb`@q}%EOsfQ%yWQXGXP3u3J9%{vxcALDOKfYtd*;9mJ0*2l6r@DHg}8f6K?EJD@u_(yX;owC zoDOi8YBmUZx`bvBV3P6S7UF$-_p6rzJ(Brwbxm z7W;*i>oRN(Z%L7L#tS9RdkCa!O^$3E2J0>-zs@SVa5p))AIVj<)^*2>qeCL`0e$e@ zM^Ck6{orS1pA=zNE}Yk)b}KrH4A8-uLJo-iS6=v6~ zkOAFG7QgEgseGt^iM00xTtl>b17&v(|0vN85(MWxnX>X&%$x=wh+U8~Ye243;L&V%QTp1%KD7Nzm5 zt*%h-)WW0FBC)ePjUVxmr=Ds>>u_*u54NG)5073M;`HTRw3^jCx`5~yG(D7uQBXXd zs1d{q>$zl{WFwLxmd-iBW6)s~bfS*aVQb;V?$PlW9S8bVR(<*or(b5O>s%O3NMWx) zAx^Lnl%uv+=*7Et0#VOmwb=n$9|P1HCqHaD%Kb{DK10?NN1H#3|`+NJZJxx z9hYP7)Q}zyqAY-pPgfbGRRcbjT`MF^R6$k2I*KcR2iTpE4^uWFv))U6+J+YpL;bQbd)G5Ton9sO;yP>9LnlQ|RnpJAALd6zOP%^| zTbh=9cO;QN^POnmO;&x%2igvv?JX*?d|UKYvvM|UMSnbwYl#hFbc+x3WPA`Y8oZeM z!)-)aU4JsVcQqep9R7}Qe!Q|k`5J}TtV1{`a`iex3gYv=5}l#>c@FTvs6&!ZNysry zs`=^J{WFbT!qx%4G zofcLIlhDU(=x$;AA$pU7m>B~Vs$o}N;78Aj6b=M$+>R{C^crq?P~gsj(b{fGQ=<^mAh)#QmQw2d~CG0^zFpPk(W&c3>8J;=ewptazh*bI#&$)&L@AdylAa+JZ>WX z4vO04_%@U+swc*c5>z0GlDn>M=ffXH%2gQj$&&LNnDpt1d@t>%3E!|Yd-=B?jv*1T z-`1f#y9DD$UnQX~`8at!N_zbv32XxMr=vOE`pgzhzUe2(-u+}en&fDUun&QjuXze9 z@7Z^?6m3$Fthry`UutNmqw%IjqB1}C?(v;;R8h4RyU`*B@#JESyS%nc#!|Z8SyYC$ z4BR_%%+tM3R`t;v#@_VB9!vk8gkivLEkOr&g55$_*RHco<^ITOYi#-j_1P@a0Uak9 z7Z+9!yoLt)sOske%#P-?>Wb> zPDG=csQk{~Ao)+~?_Co>lz-KFolG}9r#!!5zd1nVYBq}#nfwoJ?5hgwDsgD0LG&j+ zh8m0rN4{eQfwu~n*5F4x?cI&tQ;_7RqwCRF36ScKSPuPil8Hw+Dc(e$@qajb^Khv9 zw|!ixD4|kGgVC*$TPU(GlN4>JRCYrWvSnY#R4SD<$<7dw?AypPCMi2(%P?cjGQ$|l zj2UKqf5&})KF{<0{(jGMJfF{V9RA?&7soZ%b)DzyJkLu}I$}$=xN2)@CT&Y~>qmT- zSsNl(T6i89zIMv>9Z4Mzd=E+n>H~6W6Pf#z^?yY$ENY^p5u(R|EC;F`$DEL1MN-|6 z!30-^uQw$Z+x}NsP5*yp`~O*)*Pa~c*fV|P*6)jlmGoNVMIvU1CWl0>K6~XSkiSI~ zK0Q42$~Q4Y$KmIO@tVni$?;CP(JKa;xXdLP(9Rcx7L+a2baoU7iy z@Gbg8EXXxo5h@ih%zWG$=TABPAv|e%ftDV49GBw^`er|8_)J+zF2QF;b6CBnxepuEv5#nIA46t&{_Fbbj1+B|8!g6} zvZD5r8{yN?;Ab!An1ULg{wl|Gg>7;D0JE?z=n;1UByXko*k70D|1E!00(2kkKVB5_ zjUMwLy!O#!JAx$sQ8~J#dYI>^qbFK=p}3ep8DeOz8)M(o`&r9Hs4*-(YQ@A_GpBx@ zQZQY`^Q46kuOK8ZpS2FI-Pe73bVt{pA0~`IGX_vs*^GVJ?-6bfqmv%}%oJ_VyjFHj zC5!QT6Iq<&Y5u7_E#MY-+9kBF*8=&+1>MV6ux~>TU5NE-RXYBbneHej67H(ZXfV=` zvJZC^h~MqtlP#LHL%i^f(B^*MGu3emL+w>gW8pW#x_9v-@_?jrKmTzgSY_k%S+|P$ zI^v(hTRRmH)r>5G4r1uz{Z@~*6Iotvhf?u~cY?e5LBiiE7Ux+z*46F!huZi9EU{hL z;w%v4%|k>U>!o4-{OX#`GDgh@Ru3d}Zr)AZzMPr$Ta-oFnMz!vlw;(p<43tJ zGei|aFP?oG?9(0ee|dTT#m!S%dgmCQ?oN}h>|Gz0`Q9rn-kvd$ekgc(#?YZh%k^i- zklFF24);8R>X$a1<~jRmyBcK>RP7|mfiz|3$%i`>=Q{=qK%Q$?G`12Ydw3$44DkyI zflqIF)~ta_1+mZNQE2E&u!ToZP`y|FusX*`c$jvCJOO@csQ_k7nC#^i;!;fc#f=USs{?Lu3kwa4}&J%_<_W2a}syP^tL z`1&rFt1fo6UQM9!t_Z7%ZuLuNEv5v!QA@g<(WA|mlxGTrtNJzLOLwhi?F-0p-|s;x z3Z(exY$ZHg-SX@rKFE~Zg4x_{=x@8G?o7*b{khw8v$HUeXIzL*mSk2~=SI@b(VtK> z9Z^}{mYbFrjb{b*-l)_pHzSo66dB0@wthJOMsArqd`LZ~>%yLdTzBP0CYyqK$vukoEuO`IE+!;3Z#JKjYRxJ z{PP)gg1uKBx2G$@Z@}H3+H+H>bokk+2G>&Y)d`Kcn+(Nq=)HB~D|E{1Nv0=2>IvHf zAFakTLL1|czIXax)adb@q7nS(#K#TJQ-AAR{Av8$bo8ph(>^;XsTY?(SOd~@hQ#P4 zErisQJdId2^yFIZH;Ju_#*{q=2}QEE1i4b@?uHD>Z(RlH9DePdRrBsyPKG>!-aj#U z6kTg&=qq{du6C02q@cxkx;NBfp+q-UW7}mT$RHV<%m6ny+&6_!Z_dpgsn+ zr)Vfl0<^_+?aEQ`OznKMX;t9bhsb2XjzfL>Ztet(i@_eLMiHKZ)|@EpO(N(&sc+_}w+2QkN8ldM?3J6oyYq^uUGDj_LOslwVOIlIG5^=OdTod!6{^C z%A4qomw{#OkmVG^2U0}o2d}BWTPU;A2vMoiv20XCAXQIhG>?{Dnre>+D~EkzT6}2w zki2VE9!_%35DMF^*Dz)foo&VoKI%xh>zeb+@cQ&i`_YUJnG=}KDi32{f7HXJ=1=>r z@rE@)Ia>azX>`r|FzPC)hJ3g7Jao}RN2t_OR<M|j68H178D8#A{Kx_fLCo%ClLX$;@^RE_Cx%F-e>;sIs6LZk&Z5~^4g4lSrH<#=c^_QA}1zE&*a(cbpExP?ny`diaM>59G$Xa&n(0k0U{M>Ccsd_#`^g;(3 zg(?lb1Y;IYjoe5OJJpeUr$Hh0HT|{h#n-|=YfjxRxH!^Rs=jZbGTi^9!`=wRvbgUD zBwqW3{d(p)A#Aq(Xx}10UgNko^v1a=ZWyQ zZQkwFQ`Nx>(2-06oHNtBNpRCUIco{Hx7@9N^<3j!Ra;k}qoQ-p+9_6~oL{P z1A-TD`(yNW*0|3q2!DC1uJd*5S(^~tHCb}Rhg!JbUdg?Rc}H(YJMQfDh_0wOlWgS7 z=T%00)Tfwf7u;2)Kd*~3=XiM#%Ioj-XqV8Xseafrh;zrOcn@r=v0Ha_sqAs6kmG6F zl)mn^JQQoTIRNox685ys(gE!w0<=|lR3|sz-C>Ro`}nT+WaFK{Qjas_Olf)bFVU3FW(AeD+yVGoZ+mEtR(w{fW+xK${m78FugzTYTle4?h zBV_t;#1hi$WDwAIh1+nt1#v!cJqnuommGq(aE^kl;Dx=%|29Pt#<$k^=L~FAvV{_j6Cnkc#?VV>=Rs-xjuW>{hZf;k3^VIKJ8!FG&xqcy z&EtKH{|%*j;2;nD%zmk^eERj+?gq`AmyZQLO6CXrW=fP?vLWmpe+GVLuxllOASmp4 z?a;jN-bO{;!@}O;H7&dC2gB;pDDM2Pt3E~UOwM;I{sm1e*KKm` zhvg~Lro8!e(LQ|Ej-R>3S-7Eu99bIpY|g!JhX}pUoER=BJ>S zTTqqbsYk}vF5fCA3-6ZcUb*E^Mh1JHc9tfO`R_g>8~G`j2FG9BMSI*g&-Hz}iWHiz z<&XCE8oj%uaWsSzCD)|pou}nXjGP?J=W|LCd!iWju#UtnXCV9pZf7Wax>VFeCTksH z3o9+WS;-#*lOAcA^an;WjX?Sf#mMiwt?TkKo8N%dx{cg#o(k%+upw&f**DU5`n@hm zSedFOO6gD#nK1hxR}iw-am_I@b2>w5|eF!uwKP_!f_~N;(B^ zR=lp+52;ftyKo*k%pwmaL20tQ;Ii}--B&7qxmgD!`xuMS5NMAFN0A0Da3|$cZJfrPZ^8;z&;Ezy!c2;5aGjWfnN} z>wCfhA|n-i{M$Pf@82(@i1*^qS0*!Jic)Oj(e+-UjNU?=uPrFdP3}!MP%K$AU;V~n zY}Tgvi)(zuz0wspN7!LW5%ofr0CK7HA3Gr<_wP2yiwAh9=OO=E!uG^tpQDfim!2KU0bE}=p zi`u#d?)(tmT>jqw^Xg>k!@Gh7AC<#{8=tG+6asJ6hW|)D%at$x+em!)SO@1lD?TLN z%9wiCMaP-zcpbxLHI+bEiWlu$CFmvZvNS8$n@7L|HNl5ayFJyrr6KWb%^qG)&;>4j9RAKq(rRdzZf8buW`#ew-n5|*)%auFdR{qn z%dOI;{QFw-$jJo;P}0eXqA8||8PO>Xsgv)HT0~AVyX(7i#l1GJz^Da$RYWro=_>l0 zbp7-a*9zJlH3`e_QyI;_8P^p@hzXSt55DC$z4vO3p1SFEedhT3 zs|VZ<+?oXT=~exf>hnME+jG1bYli1MEwo?*H2T*+;}mU<87SjUq0i?D&nK6w_n$w_ zQ8&{Y-;?%T;_nAlM)m{y9^Sx+qA63AsXOIKW;Q!YlHId@H7o|eAh%AD%9jzNU{AmyfcBJz$}F-gx3^Og{IoeXEfGA4V7MQS^K`fi3TUgBdR*njWKBtEJMbi z6kzSc4}tS~)Zfw9T){;MiXPB~85ys(6j$aLDKmZ>hDD%L##e5#T;|B;OWb=|BM?n5 zh=g_5r|Mv%XgN$JT|NkIud2zb*Nd zC~NZBkJIC4DkIdQC=z#Kx~)ZN!BMLpb6L*~8I(|qzhm~TbgKD=J^Ap}{EO*{P~WkK z23h=1Y>hrAJdbxpnUk*Y-L5r!r2(-7?|gA}m#UUW5tUfIPw?1dW@xm-c~`~~Fr}A&xY2I(+|n5;XEk;Y9luBPiv)BL`NPM33yiA4SZ5LGDV_IJ^SI# zDOamE-fz(F&bbozDgg`Ky|-Fqv-SGh+~u9Mj#312!wZS(2wBmi4SSwgJm6p#tV@9s zmST0>=;12IoYgkPR(e!OYPN6ZJ$MJz=YZOVm%CQYsj-z*I=JR!ijS!d=>{V6j0P%x z?c{Aj9{)EDRGJ{>8mTU3tl&iyvyfq@%Shhu7^~MMVL7h0GCXty7nsnv80|&REx%iw zM>@qo^1s~8)LLtrkq@0LF~P3{Wj%<$)Vp9yO$o*W^DtL@Td6+o!B{g$`BJ3JT)kY8ILRjnv9MhI_&9K1!$-0dC?g#cQtdsr zx-QOE>!WLK8&nEIWVdyq<@!fXvLs6(JwEn+5+r>Wy@kxV={ft$#mi$f^zr)?CT8b& z#uY5Cloqsmw^Pus40t7kIZEQ`_j}v@&WBeH;rK4Ld~ligI3Aer@Q(8FAvaF@hy%=g z4T*;(a0tkKNp1EGZf9d+@dn0eo@XY*J$gzIu7}q=!^ijNFBPiekHMMK+zfCHa+c^` z=uEKWjtFI4c1rUy%qGoTwm){~qkG&0zbM_$7*uexaL;R z`9k-XlDLkzVScZk=)B1bH{tzO-Zcxniz3*7BjSsZ`zh~MN!sP`zxHGoh;O-a&#^T4 zz7(!7FEw;~AY1cNpMP)@C*G1Th%rVHF2L-N3!K&9`vd+Z8t2WS)zY@w!4-LrK@G{fgnUUpgt|& zi36;;E^d7BD{$&$Mxq?%D00rn3moZ((}C+3>i<_6$NxjM8C={R!^ifDXyy`|bHtWR z3}r-G&d%waDUAE|xt@miQ2>aqV)OUCO?##+5>qKTf8>X(x=2KBY4D+wyUIlqSAB!i z!QS0Yey_WUVhxj%lgKY~jEj-BOVfE?9#St}?(`oPZD`PV@YPP}i}K>fZ_v$qJ8mQ@ zXPpF1_TN&Lj5Y|eq6|29V_zMcyIMeLQmjbG{VGOT#H`2?c$H_AXq=Ki-c$vqAg)%voLt0NYr#nv)=j|EEW7`jK8 z#gf<@ArRCVSdJAf>9inuPv{^m5<0DKSPS$u^sr&NBlM4>JNh28&c=G~Ed{783LWmS zrO*0DATO}(;W(ut?vS{@v!OymgVdbz1JMskH7(pc6z-nmDc8>{2nRxWjHlKid8Uq*s|-Ce#^C;a^Lw}}e&&i+@90Z!38Hr3Z1H_Y0fkUc+L^R8c=TA;-z4sJMc8k^> z@Qn=wP6?r|N@LzBH*F^fVVwW@oLIe_g5tVxYIzLLKXUNtkhK#YklEsd6)Jp9HN@c* zcdutf-EbgbMjmj1j6P;Tl&BoJj1TJcDMj#PM=N0PYGX{lY(67fw+LTKn`$Om={Kjb zeZtGhstsgbs5AI1lxr*mrA%Za?cugOU)=xqP{x1q+W*aOq_ufuWBBnMyGjo{tm3!6 zoD-ZXb$FIfLZIr*v4n3@yVLC_sVFjEO?+$R<6YBwOP zCwTHwoU^6I+#3Oq88UpGQXuppFPmvrA?%ze%DX|(=6kl46QtGf+0^sfOI>zSS>;{G zsaDE6jrrCgPF<###k_-2R`*!{iI5rt0dLE3ihPRgESN!M{w+QV``b8hq7)LdSxekZ zEDabYHK+$R$H(3}GTF39!;MW?AH~8oB_vd3 z^<`Fz^ui6)!r1{SiiOTnI=*TM$%Y}ixt880ht?nGHLvj43E zbji9Gm!*L3_Kby}cek1VzAyi*WsFhMePAY5qmsyp7oQ$}0UC^m*MPGE5vG5BXil;j znaC;|TghAYuZ=7xxAI7Rte>RQNuKEB!X|<+^|zWRc%p}w`e7*egZtqAYt9?gMnBox z=s7!$0`S6{D-owNGT!BB9f7>GX=R{uWhMs!8LRfYQk#= zzjQxrJsdDFgZaY`c}1=h+j@Z<?#ZO_rn*I>SkP`x3;lL}#D|ayi-q*E%EV-T0!;mxA#Cv zhRtH+uH?%WxSqg*+?dN@ozCyipGw3ZySw9IKeHi0z<6h`d?c8Vl%^4rV8++zA+xMz zhe_@ae`f~rPCBfN9rE6?f^XFH9XH;mpAs(caj0Q1AzrbrT<*gu@#cGR9{%65XB%wb zT@oOVyB1*>EV0C0mh;e@)zIQe`x2sZ{}T`ZynADM=IOq*kMN}Tf2GiDClYL@i^ngE zGlc$ra{g`_%aPBpZtg$=5PO!5KBflnkzZ*vFV zpEE-@fxN7JnHqO$4V5vc=)m}~X8PistZcRaM;PoboD4sKPOnAJG8r0sx{yG@y28B# zXTZGjm*^Q#Ru}GbMhUee(q!+TP_s)2mQm}NCU|l$ ziF?uTXcjL_qH)ZkUOnn7(BTVFL|i<%BrkW&tyY(KvK>bF9Q$X|`vg(lxq``IEi3(` zB{c5($_kwKRu!8@VpX;u+fD-=S4e|{Cz-9Nau$M-5OUj1K#?_hON&&p zVCs3?eh#&Qw{H+ZKH`3miu9RraDS7C;uEDLDoge6)z8kjIU67aGTb26kwrF=!^`2g zrp1Vrz{EoCx2zXZCo@DshkQk(!lllUwYvuFGN3irh6DQV0KCMNm8g&>x^m2q!dM+w zP`wDH0E#+sL@~vS$V1s*DZ3weM8hL72Z1ZGs8_Y<&l22*`!Ko-xme#aP$WYuBda#5e{;4w$2Ba$%*G= z0Lk$^oK3#;9A(e-#e~}TBPXWdYtu94lI!ikPfrsFmmy^Epxn?n0q7r5$qhhHh~uoe z`D$<~QpAf8{$CwM`ERi4e?r(JV2T+q27#-0$#(dE6FH|z{9*k3jwYf-K2ygm3nKDr z6OR`RJTDb+QsCRx)r9>9I=fmfy4NohUCibj)dZ?Lh7A7Vgx_Byda9nJ|zPMMDAbHOWR}(r@78FUdY+VYa z$~h}WUCF9`*%)s@iH>^s`o1a_`DR#S&hfo`M}6$e7O~@Fhwxm3WY(1XrTN^v@UkN# zm+HI$1irw?ZRf_d--VTz!)gzf z#ULV~zXrZ>ZNC+t0|)ET&hz*Y%Q2lwNF43L zcGBp1X!!Bv)eMT872%2FGUVDS3BC78xuJO!GQOj+hbtbibuV46&U3Gn-)T_fwRX-O z!ep?{d}B=Dg0W7x6eXhnDEIvJmU!)fRmO3Bx;Y^PA5@`W?{WCmo~-g^$m3tRzrdS9 z4AzWQ&Fs+l9=)d16Uk%wjQ>1q^*=#{|6J`Xw?V_E#6FP+GoQanO1T-0?Z25;^Qc%R zxlpRf$-2(TFY>u>U_}gG>)a#nAE#1{4b%xwrRoNJJj>)KTzwSb0?g0KyAI2r5zJZw z_O-n`>nb^HSRqOqX$&X!SARz-Ja3e--S_ynXA}h_Jq$OPW;E zi#;bdKbbbFVkt*lk1%YDr^{u=V<0OMgU#)lJkgR#R2tk;h(6lE&Y(r9pfT_@zHbzsl}m{F#f#$M&7M zUs36CY_~?n>O$%CoLlj2LOv1;3NL!EE5FlhS^ar|od$_ePtxkTZwNm%)Do?6>6w`G z$&(?GM?!{H^4jD%`kMzs6JGSqeE`)m1bV%-f z3hlHDDnS_9X_25qOH`Kro)~hq!RoaCbtL2`)`>r~+aDh7w|+3oCn?B^%WxHA*IblhsWM8`YH@vDl*Z{4rL>_++j()O|{ zn7x{|F-!i2VIrbV-Q6iy%CW2qKn(1J-_b4tlARc3e)S=v;KKj%#?Jp#R1UHU!rwsA z#i{}yH67Y^#2&a!pp3n~D7ExRCSJoqGvVh(pP5#N%$cZZ$qzaguZG~(25g9dx6BT@ z=NWk(K5@o)hbB%_bzYWrwmdyBQQw8JYjKPG#C_JhQv=f(udzCL4;X-XpFDBkZJ@%a z@p|31B#tq69her*gU?vE{89A ziM1F~H7LIvvGav)qGvs8&fH9k{;fB5@-I7 znzTGm{o^&le}H;G9&SRS4=>79c`AIza?&Wd?a99tO_A~@0`Wzn-iSj+(mJCKNMYK= zcVD#TRHcvU)bUbTE;$*FNhf)Yq=e3y>79Z;n_v4dIZ-{*8Y}@!0QfTFx&49T`37?bCwi^#9IFzqcKJ6y*s|fl^Uy z1R4GTQ6>HD?vP94M>+~M>3dQO!@Ln1$bnUI*4=^PYboGbj&j{kzyMPX7*A5HLowvA z*45;AC8U@JoOLfz$*#1Hk!UQ67b^&WXJO9&FEGNi)i$APb+hccWa_0{%wC*NMI8Sl z!TUZHF>dmH6+)h!Vh37%1{2ayW*xD(L_Py-g9FK5=X0p~zU-{AMNBo*TnN#@CEIP(LB|s5C1eb%U;R z^kTPNy!`AWb8CSA4fT|OY~k{iG5ab^LxA>oHQUb}40cnnFWTLYtC z%j2Jv0x7`DvuBe|kWVj}As!iO&^BOajPHlA|53-0et^@I8m|nH-%L;p z73_)?W`8V(CYel3kQLDZx=f6k=TuYI_gxyD@V7Mhoi+yv-h&Lf?EccT$n;gH6X zH_-$O1SI(A^h{)-Bmx|4q=lFXj(a3^(nG3U^@pxPUqeyP)>(5F7Cb7Yi0Soih%9jC1j9-k>=O2`!8*6npCfy{c2XfZf^8H#A9Rrc(!BU)=S_ zfoFo3Wp@6L-+EKK7ced#j0d$7f8{DXw7kGs1O6 zn1Z97dW9ot3F7?|wVda&T+S_L!#J9@{$6p%VK>C145gz~DnfiK$u$m}qA;5UIE!zf z6FebSz{w{Fzcvq2H?+&Bu7U`p{GQq(aU0bQ)PbIcfHKiuG|)_Iul0){_^~k!S|rVg zog_vrx4O+Y{s~_o$x`5y8PMu83wyy%Yfm>#rHPlhn=G7LdRu=#P^eUSaMwJF-djy< zWbc|MY<{<>$LU8z)7=`eZN!KL6>s3DI{6s*cLTDNd3hMoG*e{Hx~LVB-)nAUU$Cg| zi9H=atN#j4`BX?(Fk?A7CE$sAR#_!c;>bR_5BbFDjVx@wXK{e5xIi6LHQNrybHYysp2}mvU3}=L5`fs1* z&`l^+l*-6{jJ2S8lX!Xd-0i{=ul~Jtn}Mj1VJ~qtxH4$@nT_4oND9ZRr6&<^dhpky zJt^8-DXovVXo0eU^0E-45M&XsXq#7Z-t#vDLr@+ZEgb(wW7{xNFp#9IhR>?%Q>#nR zRS2vh`$mz!8G=cky+3L3FIUjP*WZeFY4frFOeWk^|CBj1UgK*o8@cR3@ty6Hd~h)? zKI7r*ceIOd0xT8)RG8$TgKQiKZpyMpAU$T&HXpu^)i7ph`k(gn@VZ8BVdH?RWobUW5>_&wgiZ)8T*(3Bz89d8)O) z>+&0+X=~HHjw4g+qD^olq9xS|;okd_cX_AeCoy1FMH|BiQqXT+yOvs5lm|Fgk|2xS z`hH`QJ++yEIkOvRqzGz)($&qrG9mlt9|6_yxauto3cuBe zf$)O(_BtE)LfWbR)KzJ*QphYd_QoJH1K9D5t;|_vo3ZKeLZH{1<%xj|!B_UPSL(8X zHRVY(PyxssRb8Y>-Zc5HE6U0=j+^yJ(a#pLdT$EYb34{_^)bbysh403yyFjQIuNbI zKd-Nl3L#DPu#nMBVZ3K;Y1&caf1&aGvokfB$Nts-cnHpe*U*JulXye+t8fT8LSMq|9 z0MOfFr*Uc>f9qN7U2JZ~5cht{Kl?h}f0V5QXFsj|LL;z7rceh%N(-l|xYZ)O*ZD0- zHCOcjgI|g`q%4D}W!VBWz1J?waggWxTf)l-@evN2gI6vA{G3%Qq5IN-11=Zi>2DO# zcsYbF*#=>W=w)_%w$K~BY>J-~8@&T-4uE0ty9wZTX^;|Mq}V}RzR82~%2hRA;z=_W zZW1XCGm3MN#uC?L&IMe{FpGg)7cI#%k;s}a7i?baXDTd5=NZPA&7?`eadjU_-A*Db4hF&fuBh1iC}|x z0*+n?)r0p%Rd#nEVM_AKP(Mo18Q;4{r`M*~jwp7#1o%k@a2$XDR5btuC%FW39U@$^ zH>K%T$vz%yT5%y5wP#g!DK1T%WfcK0IXE1W4HU#-bP3f&-4rW*ztVTCBW$~pnxiiYJVhT&0ojbNU8)V6 zOrxid#u`gL`P8g^X4a6In4!B=;nN;YjxBS4A3Ogee7|imHu7}xR6s>g6!Jun^y76L z{bNRhm+2qs-V#$$Zdc>&roI~izjQ)UIKS-E*+jHAe|{GA^mFRd-VGDG(fUo7K|1Ko zUSygtv+IQ8{_?pvx79KDzF;A$%T_sH#2x%(=frx?laOtn0Tyot!Bt)S0X5(P!1W_x zoGM{kvZ@y4i_Avsl)D(Mp6lltp#0e#)g`6KpO`MB1ubCQ zbg+J|AiY8$S82nZ9<-zm^x@l+1lmRgiwlU{(hVLJY3MU}@3zXFCO zSiI(>(k4}m^-kh!sq+5d2{`p8o?SgMvoJM+7?~oQXEyJbV1TItZfkPwErA2lDJ`THr634ZAqXrGIB?VfXs;ZINQ^Pmbn4c+$GhP2*>U#6r^T zdNX8jTuM7!>!_j zS*G0x*-094Z6EW+mZYTp>C7sNj5Z)QQ}^NiJSSW@t_mf6{}+;`h|s1Uwf0YsZ_*!~ zn9K(^V_Pw@W)njNjjI~yXDT=?d<<+OH3}9{Oo*%>f|I6b!rkjqjKwwQ4+iDf;zE2J za?N7z(kH30f8YgzZYq}Djhoh<34PE&b74PSU$ll(Ary3`ECoVEO9}DTTm)ciyr$JL zyGR|HO>*JUKBY=!rDhp~kVR&z zoCTze@V&bk6Q^&3sXn2-O6!p=h&b^-FTeksL6@iT+qHnV^6d1J-7n#l=`*_zUW#ay z-Q}z(8z?61>rrE1WHr>mc%)9c<1O+v&g9{$Z$2b~{gvkRiPTN6tb8qOkPivpT1&`? zteF%0yt*Js&VbMhQTFIZz>wUs8Bi4FrTcv!C`*Zz=bbjuJFB=4w^@3r(W4e)s3h1o zGq3g4LB~z#CJ_j!=-}o>X^cBVxUrB`_-fq>q@aBP+}>&}C+!{J&b8Y#B4LJ@-N#WH zO64s+W$0Pare>~rTNEvomKMs?$ym!{J9D7Ec!m0T+2Xj$_O^uqPv#1IQ32%)zX#Vp*o7bUD;fLU+eb-Yo0w}$mZd>E8p8wII_r0Ch}S8ldd8##0{s( zm1HmUR(HHzNovk7#`t9QoTPFe0Cq*vyO7Ag*U zrM4?Eh)$9?qoq4WGNO2oP`I@FaI46j6ts?fRdY@J*}#59fw^ih@YkY?p@OYb>nj;` zv_hH&APXn})bJ6O8poR}v%p}t9N(m{tEJBb)4ygzmSus{dKj@;Im-B@-n|`&X8!s) zI}C)H+3NQeBSr8V(4Fl;k2wfv;qoW3SVU#1A~_=h3Bg$6AY>m{fgAXl?rgP562f`( zcAxtofp(Or@b45$&~~u|tvA^djgD#*CoCAIiwf|wi#hL`MQ&Cce5I=)R^;A(qn!{X zeO9(EiQIZ5OiY4l0Tb2w%&IbzQpfc`fjSfV_g(`hxQJ@Z{*1Kouc`siWe9HOv^pt! zK8S^@U(O3tm*aQ=DZ}?%m}cqP(kJ!F)h(_^%-ylAuWp_WR6VRx?MSKu#(^m!@LgD7 z>CoxZdOg=zujb_8lfw^r*H!b}ajCGjD{K^RO2@pcw-}Kc=U~}GY*y3=!bj{;Y*qj! zpuKi^)e2r&3Y;gWstwcD(gByOEW@m_fv^)$YRi^s*{2e+8EJI}(1iCe;}!L10avKR z;B0a8K50HarEk|R8{GBl1&a)nKjDs%xGHafD^g^IpiM|Y`K+9bYUl<;2_Owe?uRu; zxwqEsFA!e(q*o!E;KEv&&v#3KHK&+^fh%C&V;rPLCe`?S&9h8rTy@U{m0fW2-;J=Y z-r0n6kp9lzDe@AKn3mhXtPnZ+(Qh#)jy~W9+^2V8A(E_()d~Hx?4EAs5U>s^LMP~X ze#Pj2BYgAw$1c;0E`^jKO3@531Byl4%)uAnevmCTb%feZ!a#Zobm42p3J3(csr`%v z1Y52Lw~KmC7-Oh~EFBEzW)P}ExVdEgXzvqq_Yw9>P3!D8%YG9f_czDh`QoD`F*dr0 zsYncblRfw!o#bEGe4%pvJEqH3L+?G0RrHFu?V$dv2TIdeZI)QLDeELfVdT7@J}uoH zw7{guC@ofEjI!kKC6Dsnm5+_`{Wu}M(|2T`Il!~~UWDh-OL8-~uaV7*8noeOF`Vi2 zCm+nR#*ST{a`8)uN;CqWQcvH@2(u1y4kWZTu6 zJFVi_Tej4Z8|9sHXKv^|ql===kD~W-kh{k!VKg&@iPn-|Z@c@FqmWT^ZnExoI|Th5 zR$m24QwyQ>_f_|~fh7XO%#OXK;u!*C zqD?-bKgXll(k{@pJx#n$Q&6czZV9rLP^Mq|C@SRwk`u-|zW?WvpANTyZx5t4;AdSI zknS?i#=u8_1;(3i)aT9Ym|h5z-SWPJT758d;EnKexAKteR03&U8N%(&4*NmxsqSE~ z*F9Q8oUl2*!-CgVEWz03WRRa1r%yttp#g zJceddHLPgnk%UI&t?50-%6fxlF7QsPH3tkEhI@UKkl>-D zg5&d_3#E98wkZr;zHJs1SEzJI%)V|lH*t&|IXCJuJNT=t(v0O&{hk8}2(55p8f3Vb z4_G2%Zy^IHuB_^;<#zOXI|m4d`U90S6+F_fjz-vka66bJ9Lbs#T3Y@sE3i2rnH9%~ z^5uwP;rNtyNnO*lVw$;boCMa#Y!D6)(r^fFVp|&POtT3B}tL0M;@o7 z#;de-nCi5Fe;>DbWge&7)lseIHLV{Y<6;f#VQjps2}qDVL#pMCPz$}TBKM4~tgB|I zZt&$P=b+kv&b5sA@llKRzH#UV@TL9hIau65RyoAgW)+rt0ZpF$wo(d=8kgTR4a?(g zRfzS`Ex)p+1;h%P0IBI`&sK9wP0@pEZ5jbkgLZ!-0xOmJM=5wEI!6szMCcb zI~^?=`+T9HiZ_?1niZrsxAFNWR*3OTFhI;;n z2C-+kK@&~o=p+SIZGy})V~-E@ouBjx?^*{~8FPvLMEKB>KGw$*S1>>_K-Q6|kFZ#F z`OqG!geTz30YN*}?+CCu#2gCH&w7YyzJo4$b0&!Mj6IaS$k@@TpDIpWU)xTv+d}no zLWtFG)-h;MK0i?H9yCLr|CPc8LPB4c4Jd9r zDZg|;FUqyYP#l!+A)qq!sK!np;X@_ths}eZ=9@IGd4Vn6@d#YRM~(X^Z=0r!_fznL z@kmNcT^YK17aYc!5?W&prZ|dG?B8RVT*i!C$X6_lQ{sonGetaN$>F0IzMLRiM8oGB zQUv&KRZcR;{1siMjQjH>$t;PE*coC=)nXOflg;Bw z7)vK%l)G}Y&xQ<-<6y`>M9g&CI(8X<^Z{h)UWC7`zKR>ax)}@kVf3l}b7qYe=gMkr zF2aQRTGkyXx*_HHPCeBngx=mB2cN}+}w z=21TS)$fd_8Idk3**05s*Ad1G>jXwfg0ARhT$vfmD#|_l0_Vc-_YmxOM#@Teom#xM zt-_7)-E4$gbYOyB`8%~SYCx%dOyAlA!>{lHWF?zD?hKZVOCkd2Myu4q_K=(kN{P+n5H$G^;juXBaIc z>5%h=a@c9FIE$MyPGK9yE3kZhx*#`DXMIbX|yk9n_4y4Ns<;(VMX& zoI1jV1qJf-~zATg&1R7{G1s~t7OboP83ZHge|G}k=O5D3{xu?59v zhuAkk;31!bJ$ekL{kU3qqh{KhYv!xc9o75sP5_<-ryJ-5fj<2^pZwGvfX66yLpcpZPb!$%=1J8eU;@i<_a9%eFp>gyk;22Cdms%V1Raoe! zj>1X-N&jrRdxqvesfy*XCuXs|Xe?6)bQS0Xhu5pznRKjns zmgt0XV6KGWv`DmMRYWn+VHahD{164x!r%9JQVNS{vf&~GvMq{{c1t{j9W&N8CCMgo z-MJw!jvLpLw-KPi1y=+(U5J~=1G z!LF>f)kZ93U7VF9p$}tzJ=W|6ce4zL>oxCgYosODMHQvcT@hSopu2VD9~6})ptE^M z1K`3)qV8hoXQV%b#^P<^<+RmJEIc9z{|s0UBSCzb;9)XPzXr^Z#v{+hyOpNeR)(<*S>#SRP-vKaw{T3 zP?X`KA}E9@iIobaDp;{r1PO{vWhO=lAsm$!v;xr!%8)>`qR0~n^9)gg3}J{066Oej z1PLKRNJ8d!qW7-f`>l1?dSyM%TKvOaa5y>pyFb%+Z%5owMvcA?FRn~GaNY-w#xrG8 zbPH_9V0C%j?T?JA^pcC7z0vl|0|%FDUfYR;VI|!LrL#qgl^OKZ_V(E}kr#T5#40;-eL1mh>+=cdt?J`hJQZ6kPAYJ|^X~`8e)YH%qw_ahAkh zRJELgs=zHC>*QoP86c=$!&F#00;LkTKS(gk2jO7a55Dn9+Y1>7DZgRn_H!gB9BCj1 zz)UvLFVewUKu@Xry0vTjqEoLk&l0GM{bg}Y0z=w%5c2E1mW+*FM;V=trKYEukDH`) zqgdLKh+Laow^TeKalbh)7y@MxrTYg8o9^XlISzxntz?HqNAgA_fms@moGqAm1JUM! zxrcB?Cp;)eG4kO`esX#n`t)xpMmiWlB;Fn%s|G74{0bs!#1fQ9Zz+cfo>E5YyP82+ zq_9bO-D5_V-Z;*dXpc@B1L1DaYAyXyNaoZuGKdhFkToR0}1)_iLU?Jtk3g?U{~;m zK5~z4(ZnPSR0hpE zmcy#z32wzRw=yoaWi)oQQ&c5?hRWEqLgZn}Anew&{(5x=t*h}P6y>v*5(3ufix7xM zS)l&$=(7$eUQS!FTQT=!(y(a`szCV=I3MY9el$k;AukORScuCF$#o^V=Z3TiCEVI3 za-Z+qkcy#=(yL0^$bK%cgB6dL*HFr--8HRifYp0GF}8ve5J?pV3Hlp z0Fg#t$3rhJFB0qppNIGCra#(EYe;TY%^}myQ~<-cIt0#VG^{UbtcE+OS|Cxp|A%aa zph~%ZSEYMPSw)*_-lCbgNeZ3vP1zq)TIN+QSNv3;Q0MjB&C?Rt_@q!iS0qH#NyE0@sf@Fl=+OEG7q@pT=Jw*B=D6 z6XzXVxkWV=BMs1S9yEzDST;$L!zQ%w_|#fa8u8>twWwyO0(* zDWTgg`&cDM%K?QXTzLvouwfw=++u!2(__rG3Mq{xFa1JBzsIq&LK=CNnSVXWvTb!q zjh9(@Q5g}$OB%h6o7m$gQTfM%?N|K>Q^6WqXR2>=+PYI~(!%dv*4XwPjFyFHD%3#* z3?%Ugg|1Xau!(R_y|O-{*X(hnD(s_#GO=S`)$x)!@z2)Qxsr?;UW8zWFET25`g*ru zY|q_X-j8Cc&G4BV#uz;E;dg_68MUgSY6#G|h4u2yDxoh;?WOinIAe*qyDtC_V~{|% ztWb!Aq&P)gb<=!l%ZMFlPx52j2OF8uHfg$^s(2*l@ER?Ax`ydVr+HvKc- zDWI&N>?)4e)H5m5kzYz(>RTX-YEMOAEwI`n$Rcs{UhMS0qDw^Gd?2I%#S2yVaDzVw zmwr)(D8>Fel7$={(6XV|(tn-dah`t5*VLG4Tb6SKh4g?dC5ld}DOuZ`QtMbqdIIEG zKw!U2Y#7?0J(;YOheZxyCQE-s-4#Fl-2mmVbE#0n08iaR`-iwnqG`oFd!0b6peCEx zZ^)zXW_pp>JkPnelXEj$d2=SDL&B%6^ZSs5s9k>|*70I8(&LIRN(FIUfm`;pX)Mnj z>W5Q8!nTip~#WLLvY03Mr)57eKR{+Iq54ZyW?Uz3S~+kW7^((&HH=8 zz#|o1WGvIw<}Q!^f8nY_$n8WuA|js6>XF=^X&uMB#&j&RnQUZ7D(hA3v_m)uP`_M~ zI$;inQoEYGB%f`WcfpoX+D;`FX5A0e;*4U>E7_`B953|a>a~4BYS@A75qmfjBF9g1 z&GezP$$_(by)f^h79Gi!<_(YH%!l-3_c{_9R|-15l7 z?QyN3{#>D0%Bt_1WiJB6^ri+ zZuYREzhd628WIB5B0Tf##de?_+=}##vK98!=y1_Jb7JI#KJYf@+O17>_rqo3ZTlmN zPexQP7rl2D%*?u4o`7>L(4K^0+lOFQMf5(STyVV(wJZ}ujFnUJV9j05IxO; z+Hg5m^xkyy{1ss6H*f;9@LMU(Dt6v5<}p~ga)fkYpB*RN7nt-{wc;-T)+8Iyh)M~0 zr-p&`a_b?(Z)Oq8lFcgZf^`hLMM}9) z{WBpgozeW^QI88$Zjokmb*wxHa}kf54-H(XwhUV+;JxfGA%%-99;L;6EX?@9nvj2P zgk!ycmrMPh4Cgz@P)E%{kbw4Hzz#{SUllXcH*;#i0~&sE>*y`!tcEHJsC&|LRijes zg+u{XF`i5BCA;4?qnK`pDIGfH7X^eCb76|Vi z4j?uD-DrMa5Q3hY^bKHKhgI#A9x zMiX~O8XJT)h^~JVWTCA5Q~9Uq2D^rH>EtG*?b8Cj(EJD8 zPwV|Ri4Z z2R0)yvqW#RI#Iem(t&*6`vG(N#Ue$47iQTL@^@TUFfci@esf+|_Ue@ce{Z&XIaqmY z%$igkWH>*EO=2TC9-ZsOKZ0RHJh$A2bqqxDUu(?n}! zsp@5elh9G+VC|?gAt(tfWa#ooqUb2uuOAVtg0)$y5L3_-12!reJ<`l7B&h-LFz9r{ z?)R(NWA{Z2L+v7M!mVc@!VKcnqHA&UFzBAfOK&w=X(Mfi`|QwVKpiWJbdYQYi*z~W zW;D!-yf*BP6mgE)C4Kn2ihJ~di=lG{Wu%L#i0?~DzlZuHj9R}6>F2cU zyZs?4dg5?Kscp7*aIbK6JL87W6))J+G7j-OCBi6F_hp?vA%t1~M%g4kCQXQQkjZ!H zMn39js&GgVy?@@{oc^&=fa#;s4f?y?q5c(=^rjMmg8QO&|Lq{~9^eQ`R~H!OaB){B zJRFK`)4tG7HZTLiBQ}7qe-?d;R=XU6RaQo}m$UqkQ*@Z>ZS+kW`~DTrm2dxAnD;YL z`Gi+TdJ(CGTF!<~8?=Kex>L2>sy!4=A3T(e`X9%C3si!~gZ>=i1oce_4kQ?0YJUHX z0PriFm{mZXE{K>LfBKOyKk!uZ4qq72`S`V0Vu^EN1vL7u2|k8#xG{^47Lhfo4`r%a zZnNQ0yMSu_HW_}?3MRRYeGhzrm_v}Evs9e%zEi8u75IVj@RqjwVPg$rg3uh>5JkP5 zT&<+XoEmFXZ6pidCehB*`;RDu^&{)?)>pJ_cPmC~?JZMAE|Mf&v7x>bw6=X@93x=1 z)CJ3mUPT0qVFc z`CIk(mZ=LT7F;g-gRd_cwLWm#FWO82K3qq)J8P-|7uZ*=N@`QY(xz_ zB%JUd#7BKE8Z93W>WV+St;$fow_#g`?af3_EG<-4PBh1mPxu1d22x(^?iy`E0@TYEA&C)BTv= z92?bZl-;Y^wA`RYN`o>6`3Cp~_4`Rt%XjdAbE~6LeiC<=++D82NE!|Bt&3e?jy8xGDVj$uh)$QLUtZ5#?jt;B+bLPB*_R~g>yNgG^Qu3aV{PsCL#zx zC*_N&mg|}arUYrKUR7J+B~Qw-AA&9iNn3(=kq!eY{V7o@@B@f^f;;{f?DBuJZ~0X< zyw8&FHLfuU4!p1F7coh^lS|!3JHt+eKm#kMx|Ri6@$)|^|CyHpx`?t9ZwH=+UTs+N zavmaRm>KRMXh3K_X-BG(wy-By3v|9(*Tq=Su(xH8WqhaW4ycT1RFu-O^ERWuQmyb( zrlqpS3J8BQv*Me-%N^q7yzmf~b5OsP zX*>?V<08XIwUMKD=nhVOW1>0gW57#tVp!AwKSOZQj+yd){0sI$UcXYVw4FV445WNp zV@_TLN!FjA*;9yDx|iS>xTOE%#TWH5 z%<>TG!hqtd#=$F;GFhHf^jH8ZH^`RD0<= zHzNd6Zo+%gqt=TzjE1Z+0_p5)sUp1FMt4E|2w8&{xFnj-I} zO51JsvQhK(<1JW>8Yv2n%1WIlL&lvafWz+8V+Z^9&1}mD4nmZ03e7!OtVYG^3&}ah zz5^o3Vz3X7WvExa&l!cQRVwR;kbrplFLglt>-{2q_R`)l=f-p0b?K*kqK5O6wCnpi z;%3Jyj^}f|3;Tegx1AlQg@s||Wq~IPtJq=uL098T3o&+SSIf9;*ULI#dyY3Ivhz=c z{W8|=?JgoylgyzGyQb+5X>KgK>~FikdAzm!%V(_o;VZ$b#>`$JvtC(M$mC_kBROH_ zQ;w?6RYY-r<#ISc%?hA&KpNy5XjeS-Z=i2}co~CU=#_Sc(Pv39XqeoY29N0M_I zS%xPvDBPGme(FY)otk|VLESaR*4&*JzJwY$v&v7#-QbUUdR0VDV&`#Xyb>DD5xW2l zLu(3Z%jKWvpUO>~;a3!*yCCqLT-Xp4B|S6LhX`p4ns2&%q|ZP{^!{CZ$B=9B4Hu0> zxe2x7d;23<9JANng)Xzb?hl;Sm1i{8S<}v3^EYL=w~pWufqyPR30LT{Cu(;Y^Dg#f z>pS_Fl+_g8+pzkWYc1F!+?0M8UL;9?E!EZW7M{BoRF|VI%umxFdI~(o*xqUOebq|J zfjUyWThW268@9I2j1!Das}=wS&h@_bw6#t<3_f>;WIIB#F;Mj&!{iyHX@%>_Pm@#9 z7GN=~r>hto;P>2B|6Q@y(PTM`o*N@yxYK!lS!#%wDF`EXt23E4#;fY(rB3NbKi-co zqX?oq`*kG{AVc*vV@+V6d;$dfl&)yb?j6um?;^@6<1eTkG)olf3E^3P#`bKDqNd!FYaT?%b4F>r);5x>(HZe?oNrhepbo^VDZer>L%Ozy2d>T#m3 z;?BQ&v+BXNnCxM@S3y@sfGdMJ3pmgD;jf#I_s(d!dMdx3v76L7+|l4kqcE?WbW&CH3G1Rdq^UVP4rW zuEwq>z%IQ&YoEYfI)f?2z1=>Se0H{~~Z zHL1!|#Qk<1YV9TScUeAbIHo{NnH6u9X@Pu-A@+3puuR)*66@z3l@CUJr3#t&R%@4K z525{lYrXI;OqfejaO}SJT({b_GGpegZ&e%tmgr2a&OekA$IK>Go68|Ks53L=-fvE|DIOoj{?42)`fx8f}TPQG6uhs=<4B2j>Bbs8CNPpl7Ok7*JJ~W z)wJANK2{~^*7A->m>%u>orC}`7(AJ5HQe>EH^cj?66{3n-dGV)ZTI z`tDyuL~bb*&HmJc=_t+=NC!*yV@4nlwW2IYo-$rinwar$;DOGt0Xen$R^Y2uLsw31 zU{|iq_o2#2B`$K|E@#!mtjtf~1zzgDB|2E;7P}kBYpKVobvLxXQuQPUL;af`sB-51uNnt>JG`OQ`_y$1lo3kXJTv^*MwYJXHh-gvOqW3F|}Q-KQmiy6D8Efo5LT7 z%jZU&vaPs&u6eU;b#y<5ZgVgnAbh?q`ubVj(ki!%ep2SzSXFM9?GxqCr2|++tl_t7 zGW($FFey^nT&*umH28$(ZLn=B+ky!RK0op zgRDfiB>#c$xWgmWg_wBfp=QqWJFA1JQ5W+2$41lDg_3yZnXGuadE`h5rv~G1)ocZ8 z>rZ4edFT0sU~`(YeShrL%tpZlpHBTHAG%!TU`lGiCbP&(oMDH~w~p-o@SxjTAuz!r zD_yRBoH##q>2v@So?VU{A_IKz;BTAg)R%x06d{gX8kMtl{$njN@2wZU>V;@W9^eBf zFveeWxY^y*{Ec4!!+0~y;J!n;B#PmlhHiAriGH!0nQ?Lk+iF!xUO=YlUfQn0ScS6q ziFa;`|A-Ysfa?!GFUD!YYN2=;K{UMsz`jWj9J1FrHI9vu;%W0<`AWHDKy;!6`{3R= zb`&*XU548PI}=Vrw}Z8d`^iX`LEGd_!MF0)mL=*nyzo0q3T{d$I54)bc!GrLrr zjHKl-`Ne3Bpwg@0{y4E-3BbY~;jEwQsAC5>#x5>9i#-5_g$1Jtl2LiMQHS?h@aBI6 z_3|9`ZBtgl0aqmK+3Z*ttZR1n!JiXy?;Hu@d~F=G-x+;3ezc-DiFXB2QXD|?!dR3Od}pAgW6?#4vqi&^5aaru~>n`;8+n+jS>C$?Ue(d$cc)IDGycVQA;^4qjK5ojabfIlz$lLcMKsLcC4kZ=|7Kc^8{B# zWJq>9WbccwU9^AJ$Ifj*bL_&dq{D6r&s_|^!166aqFQ7s^@xV q!_RB+&;R55_y6Ayu<3>h(rjOLd(ZW$vtNN<9>+buFFzJ=^S=S%?KZUl literal 0 HcmV?d00001 diff --git a/docs/source/index.rst b/docs/source/index.rst index 5779f93..725e808 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -12,15 +12,21 @@ adf-core-pythonのドキュメント .. warning:: - パッケージとしてまだ公開していないので、pip でインストールすることはできません。 + パッケージとしてまだ公開していないため、pip でインストールすることはできません。 .. contents:: 目次 :depth: 2 :local: +概要 +---- +adf-core-pythonは、RoboCup Rescue Simulation(RRS)におけるエージェント開発を支援するためのライブラリ及びフレームワークです。 +adf-core-pythonを使用することで、エージェントの開発を効率化し、再利用性を向上させることができます。 + 特徴 ---- +adf-core-pythonには以下のような特徴があります。 - **モジュール単位での開発**: モジュール単位でエージェント開発を行い、モジュールの入れ替えが容易です。 - **モジュールの再利用**: 他のエージェントで使用されているモジュールを再利用することができます。 @@ -28,15 +34,26 @@ adf-core-pythonのドキュメント はじめに -------- +adf-core-pythonを始めるには、インストールに従い、このドキュメントに記載されているチュートリアルやハンズオンを参照してください。 + +.. toctree:: + :maxdepth: 1 + :caption: インストール: -ADF Core Python を始めるには、インストール手順に従い、このドキュメントに記載されている例を参照してください。 + install/environment/environment + install/install/install + +.. toctree:: + :maxdepth: 1 + :caption: クイックスタート: + + quickstart/quickstart .. toctree:: :maxdepth: 1 :caption: チュートリアル: tutorial/environment/environment - tutorial/install/install tutorial/agent/agent tutorial/agent/agent_control tutorial/config/config @@ -46,14 +63,16 @@ ADF Core Python を始めるには、インストール手順に従い、この :maxdepth: 1 :caption: ハンズオン: - hands-on/clustering hands-on/search + hands-on/clustering .. toctree:: :maxdepth: 1 - :caption: クイックスタート: + :caption: APIドキュメント: - quickstart/quickstart + genindex + modindex + search .. automodule:: adf_core_python :members: diff --git a/docs/source/install/environment/environment.md b/docs/source/install/environment/environment.md new file mode 100644 index 0000000..e7b8fcc --- /dev/null +++ b/docs/source/install/environment/environment.md @@ -0,0 +1,50 @@ +# 環境構築 +adf-core-pythonをインストールするには以下の必要条件が必要です。 +既にお使いのPCにインストールされている場合は再度インストールする必要はありません。 + +## 必要条件 + +- Git +- Python 3.12 以上 +- OpenJDK 17 + +各OSでのインストール方法は以下のページをそれぞれ参照してください + +[Windowsでの必要条件のインストール方法](./windows/install.md) + +[MacOSでの必要条件のインストール方法](./mac/install.md) + +[Linuxでの必要条件のインストール方法](./linux/install.md) + +## シミュレーションサーバーのインストール +次にRoboCup Rescue Simulationのシミュレーションサーバーをインストールします。 + +```{note} +WORKING_DIR は任意のディレクトリを作成、指定してください。 +``` + +```bash +mkdir WORKING_DIR +cd WORKING_DIR +git clone https://github.com/roborescue/rcrs-server.git +cd rcrs-server +./gradlew completeBuild +``` + +ビルドした際に以下のようなメッセージが表示されたら成功です。 + +```bash +BUILD SUCCESSFUL in ... +``` + +## シュミレーションサーバーの動作確認 + +```bash +cd WORKING_DIR/rcrs-server/scripts +./start-comprun.sh -m ../maps/test/map -c ../maps/test/config +``` + +![シミュレーションサーバーの起動](../../images/launch_server.png) + +上記のように何個かのウィンドウが表示されたら成功です。 +コマンドラインで `Ctrl + C` (MacOSの場合は `Command + C` ) を押すとシミュレーションサーバーが終了します。 diff --git a/docs/source/install/environment/linux/install.md b/docs/source/install/environment/linux/install.md new file mode 100644 index 0000000..c7369b2 --- /dev/null +++ b/docs/source/install/environment/linux/install.md @@ -0,0 +1,49 @@ +# Linuxでの環境構築 + +## 1. Gitのインストール + +OS標準のパッケージマネージャーを使用してインストールします +- DebianベースのOSの場合(Ubuntuなど) + ```bash + sudo apt install git + ``` +- Red HatベースのOSの場合(Fedoraなど) + ```bash + sudo yum install git + ``` + + ```bash + sudo dnf install git + ``` + +## 2. Pythonのインストール + +OS標準のパッケージマネージャーを使用してインストールします +- DebianベースのOSの場合(Ubuntuなど) + ```bash + sudo apt install python + ``` +- Red HatベースのOSの場合(Fedoraなど) + ```bash + sudo yum install python + ``` + + ```bash + sudo dnf install python + ``` + +## 3. OpenJDKのインストール + +OS標準のパッケージマネージャーを使用してインストールします +- DebianベースのOSの場合(Ubuntuなど) + ```bash + sudo apt install openjdk-17-jdk + ``` +- Red HatベースのOSの場合(Fedoraなど) + ```bash + sudo yum install java-17-openjdk + ``` + + ```bash + sudo dnf install java-17-openjdk-devel + ``` diff --git a/docs/source/install/environment/mac/install.md b/docs/source/install/environment/mac/install.md new file mode 100644 index 0000000..2b58966 --- /dev/null +++ b/docs/source/install/environment/mac/install.md @@ -0,0 +1,21 @@ +# Macでの環境構築 + +## 1. Gitのインストール + +1. Terminalを起動します。 +2. XcodeのCommand Line Toolsをインストールします。 + ```bash + xcode-select --install + ``` + +## 2. Pythonのインストール + +1. [Python](https://www.python.org/downloads/)の公式サイトにアクセスします。 +2. ダウンロードページから最新のバージョンをダウンロードします。 +3. ダウンロードしたファイルを開き、インストールを開始します。 + +## 3. OpenJDKのインストール + +1. [OpenJDK](https://jdk.java.net/archive/)の公式サイトにアクセスします。 +2. ダウンロードページから17.0.2のバージョンをダウンロードします。 +3. ダウンロードしたファイルを開き、インストールを開始します。 diff --git a/docs/source/tutorial/environment/windows/install.md b/docs/source/install/environment/windows/install.md similarity index 100% rename from docs/source/tutorial/environment/windows/install.md rename to docs/source/install/environment/windows/install.md diff --git a/docs/source/tutorial/install/install.md b/docs/source/install/install/install.md similarity index 100% rename from docs/source/tutorial/install/install.md rename to docs/source/install/install/install.md diff --git a/docs/source/tutorial/agent/agent.md b/docs/source/tutorial/agent/agent.md index 1beb851..f358b42 100644 --- a/docs/source/tutorial/agent/agent.md +++ b/docs/source/tutorial/agent/agent.md @@ -56,7 +56,7 @@ python main.py ``` エージェントが正常に起動すると、シミュレーションサーバーに接続され、エージェントがシミュレーションに参加し、エージェントが動き出します。 -途中で止めたい場合は、それぞれのコマンドラインで `Ctrl + C` を押してください。 +途中で止めたい場合は、それぞれのコマンドラインで `Ctrl + C` (MacOSの場合は `Command + C` ) を押してください。 ```{warning} シミュレーションサーバーを停止させたあとは、プロセスが残ってしまう場合があるので`./kill.sh` を実行してください。 diff --git a/docs/source/tutorial/environment/environment.md b/docs/source/tutorial/environment/environment.md index 5f8a394..d991e30 100644 --- a/docs/source/tutorial/environment/environment.md +++ b/docs/source/tutorial/environment/environment.md @@ -1,38 +1,12 @@ # 環境構築 - -## 必要なもの - -- Git -- Python 3.12 以上 -- OpenJDK 17 - -[Windowsでの必要なもののインストール方法](./windows/install.md) - -## シミュレーションサーバーのインストール - -```{note} -WORKING_DIR は任意のディレクトリを指定してください。 -``` - -```bash -cd WORKING_DIR -git clone https://github.com/roborescue/rcrs-server.git -cd rcrs-server -./gradlew completeBuild -``` - -ビルドした際に以下のようなメッセージが表示されたら成功です。 - -```bash -BUILD SUCCESSFUL in ... -``` +今回はチュートリアル用のシナリオを使用してチュートリアルを行います。 ## チュートリアルで使用するマップのダウンロード {download}`マップのダウンロード <./../../download/tutorial_map.zip>` をクリックしてダウンロードしてください。 -ダウンロードしたファイルを解凍し、中のファイルを `WORKING_DIR/rcrs-server/maps/` の中に移動させてください。 +ダウンロードしたファイルを解凍し、中のファイルを `rcrs-server/maps/` の中に移動させてください。 ## シュミレーションサーバーの動作確認 @@ -42,4 +16,4 @@ cd WORKING_DIR/rcrs-server/scripts ``` 何個かのウィンドウが表示されたら成功です。 -コマンドラインで `Ctrl + C` を押すとシミュレーションサーバーが終了します。 +コマンドラインで `Ctrl + C` (MacOSの場合は `Command + C` ) を押すとシミュレーションサーバーが終了します。 From 8484ab9f9d97f414bb2cbc8a12ca8ed561ec2d4d Mon Sep 17 00:00:00 2001 From: harrki Date: Fri, 29 Nov 2024 21:33:19 +0900 Subject: [PATCH 173/249] fix: Fix incorrect words --- docs/source/install/environment/environment.md | 2 +- docs/source/tutorial/environment/environment.md | 2 +- docs/source/tutorial/module/module.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/install/environment/environment.md b/docs/source/install/environment/environment.md index e7b8fcc..7009f3f 100644 --- a/docs/source/install/environment/environment.md +++ b/docs/source/install/environment/environment.md @@ -37,7 +37,7 @@ cd rcrs-server BUILD SUCCESSFUL in ... ``` -## シュミレーションサーバーの動作確認 +## シミュレーションサーバーの動作確認 ```bash cd WORKING_DIR/rcrs-server/scripts diff --git a/docs/source/tutorial/environment/environment.md b/docs/source/tutorial/environment/environment.md index d991e30..bacef41 100644 --- a/docs/source/tutorial/environment/environment.md +++ b/docs/source/tutorial/environment/environment.md @@ -8,7 +8,7 @@ ダウンロードしたファイルを解凍し、中のファイルを `rcrs-server/maps/` の中に移動させてください。 -## シュミレーションサーバーの動作確認 +## シミュレーションサーバーの動作確認 ```bash cd WORKING_DIR/rcrs-server/scripts diff --git a/docs/source/tutorial/module/module.md b/docs/source/tutorial/module/module.md index ce8c9de..3410120 100644 --- a/docs/source/tutorial/module/module.md +++ b/docs/source/tutorial/module/module.md @@ -60,7 +60,7 @@ class SampleSearch(Search): ), ) - # モジュールの登録(これをしないと、モジュール内のシュミレーション環境の情報が更新されません) + # モジュールの登録(これをしないと、モジュール内のシミュレーション環境の情報が更新されません) self.register_sub_module(self._clustering) self.register_sub_module(self._path_planning) ``` From 93558d7284bed23b9773a171490bfb74e182d18f Mon Sep 17 00:00:00 2001 From: harrki Date: Fri, 29 Nov 2024 21:49:32 +0900 Subject: [PATCH 174/249] fix: Minor fixes --- docs/source/index.rst | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 725e808..43361e6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,10 +15,6 @@ adf-core-pythonのドキュメント パッケージとしてまだ公開していないため、pip でインストールすることはできません。 -.. contents:: 目次 - :depth: 2 - :local: - 概要 ---- adf-core-pythonは、RoboCup Rescue Simulation(RRS)におけるエージェント開発を支援するためのライブラリ及びフレームワークです。 @@ -38,20 +34,20 @@ adf-core-pythonを始めるには、インストールに従い、このドキ .. toctree:: :maxdepth: 1 - :caption: インストール: + :caption: インストール install/environment/environment install/install/install .. toctree:: :maxdepth: 1 - :caption: クイックスタート: + :caption: クイックスタート quickstart/quickstart .. toctree:: :maxdepth: 1 - :caption: チュートリアル: + :caption: チュートリアル tutorial/environment/environment tutorial/agent/agent @@ -61,14 +57,14 @@ adf-core-pythonを始めるには、インストールに従い、このドキ .. toctree:: :maxdepth: 1 - :caption: ハンズオン: + :caption: ハンズオン - hands-on/search hands-on/clustering + hands-on/search .. toctree:: :maxdepth: 1 - :caption: APIドキュメント: + :caption: APIドキュメント genindex modindex From aef5243391226938b165ee09cce7f7ebb09e629d Mon Sep 17 00:00:00 2001 From: harrki Date: Sat, 30 Nov 2024 01:03:02 +0900 Subject: [PATCH 175/249] fix: Add some sentences for docs --- docs/source/index.rst | 8 -------- docs/source/install/environment/environment.md | 2 +- docs/source/tutorial/agent/agent.md | 10 +++++++--- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 43361e6..ced7ae6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -74,11 +74,3 @@ adf-core-pythonを始めるには、インストールに従い、このドキ :members: :undoc-members: :show-inheritance: - - -パッケージの詳細 ---------------------- - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/source/install/environment/environment.md b/docs/source/install/environment/environment.md index 7009f3f..1818228 100644 --- a/docs/source/install/environment/environment.md +++ b/docs/source/install/environment/environment.md @@ -40,7 +40,7 @@ BUILD SUCCESSFUL in ... ## シミュレーションサーバーの動作確認 ```bash -cd WORKING_DIR/rcrs-server/scripts +cd scripts ./start-comprun.sh -m ../maps/test/map -c ../maps/test/config ``` diff --git a/docs/source/tutorial/agent/agent.md b/docs/source/tutorial/agent/agent.md index f358b42..853858a 100644 --- a/docs/source/tutorial/agent/agent.md +++ b/docs/source/tutorial/agent/agent.md @@ -41,16 +41,20 @@ Creating a new agent team with name: my-agent ## シミュレーションを実行する -シミュレーションサーバーを以下のコマンドで起動します: +ターミナルを2つ起動します。 + +片方のターミナルを開き、シミュレーションサーバーを以下のコマンドで起動します: ```bash +# Terminal A cd WORKING_DIR/rcrs-server/scripts -./start-comprun.sh -m ../maps/test/map -c ../maps/test/config +./start-comprun.sh -m ../maps/tutorial_fire_brigade_only/map -c ../maps/tutorial_fire_brigade_only/config ``` -その後、エージェントを起動します: +その後、別のターミナルを開き、エージェントを起動します: ```bash +# Terminal B cd WORKING_DIR/my-agent python main.py ``` From 0b917706699b8ad1e86f381a1ff5b72922f7e3bd Mon Sep 17 00:00:00 2001 From: shima004 Date: Sat, 30 Nov 2024 01:23:28 +0900 Subject: [PATCH 176/249] fix: Update documentation for clustering and search modules, including terminal commands and agent team name references --- docs/source/download/cluster_plot.zip | Bin 0 -> 11999 bytes docs/source/hands-on/clustering.md | 57 +++++++++++++++++- docs/source/hands-on/search.md | 38 ++++++++---- docs/source/images/cluster.png | Bin 0 -> 64512 bytes .../source/install/environment/environment.md | 6 ++ docs/source/tutorial/agent/agent.md | 7 ++- docs/source/tutorial/agent/agent_control.md | 38 +++++++++--- docs/source/tutorial/config/config.md | 2 +- 8 files changed, 126 insertions(+), 22 deletions(-) create mode 100644 docs/source/download/cluster_plot.zip create mode 100644 docs/source/images/cluster.png diff --git a/docs/source/download/cluster_plot.zip b/docs/source/download/cluster_plot.zip new file mode 100644 index 0000000000000000000000000000000000000000..122b555b4497dde4ce0d5e33bbe4e20006e84846 GIT binary patch literal 11999 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.module.algorithm.k_means_pp_clustering.KMeansPPClustering ``` -シミュレーションサーバーを起動して、エージェントを起動してください。エージェントが起動すると、標準出力にクラスタリング結果が表示されます。 +ターミナルを2つ起動します。 + +片方のターミナルを開き、シミュレーションサーバーを以下のコマンドで起動します: + +```bash +# Terminal A +cd WORKING_DIR/rcrs-server/scripts +./start-comprun.sh -m ../maps/tutorial_ambulance_team_only/map -c ../maps/tutorial_ambulance_team_only/config +``` + +その後、別のターミナルを開き、エージェントを起動します: + +```bash +# Terminal B +cd WORKING_DIR/ +python main.py +``` + +エージェントが起動すると、標準出力にクラスタリング結果が表示されます。 + +```bash +[info ] Clustered entities: [[257, 259, 262, 263, 270, 278, 280, 297, 336, 913, 914, 915, 916, 917, 918, 919, 933, 941, 942, 943, 944, 945, 946, 947, 974, 250, 253], [349, 896, 899, 902, 934, 960, 968, 969, 970, 971, 248, 251], [258, 266, 268, 269, 274, 275, 279, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 932, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 975, 976, 254, 255], [256, 271, 273, 281, 296, 298, 314, 330, 903, 904, 905, 910, 911, 912, 935, 936, 937, 938, 939, 940, 247, 249]] [KMeansPPClustering] +[info ] Agent cluster indices: [([89544, 19925], 1), ([69989, 120063], 0), ([130029, 50380], 2), ([29898, 59056], 3)] [KMeansPPClustering] +``` + +このままだと、クラスタリング結果がわかりにくいので、クラスタリング結果を地図上に表示してみましょう。 + +{download}`クラスターの可視化用スクリプト <./../download/cluster_plot.zip>`をダウンロードして解凍し、`main.py`の以下の部分に + +```python +# クラスタリング結果 +clusters = [] +``` + +出力の`Clustered entities: `の後ろの部分の配列をコピーして貼り付けてください。 + +例 + +```python +# クラスタリング結果 +clusters = [[257, 259, 262, 263, 270, 278, 280, 297, 336, 913, 914, 915, 916, 917, 918, 919, 933, 941, 942, 943, 944, 945, 946, 947, 974, 250, 253], [349, 896, 899, 902, 934, 960, 968, 969, 970, 971, 248, 251], [258, 266, 268, 269, 274, 275, 279, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 932, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 975, 976, 254, 255], [256, 271, 273, 281, 296, 298, 314, 330, 903, 904, 905, 910, 911, 912, 935, 936, 937, 938, 939, 940, 247, 249]] +``` + +貼り付けたら、以下のコマンドを実行してください。 + +```bash +python main.py +``` + +以下のような画像が出力されます。 + +![クラスタリングの画像](./../images/cluster.png) diff --git a/docs/source/hands-on/search.md b/docs/source/hands-on/search.md index 8b6ac94..293ef8b 100644 --- a/docs/source/hands-on/search.md +++ b/docs/source/hands-on/search.md @@ -70,13 +70,13 @@ class KMeansPPSearch(Search): ```yaml DefaultTacticsAmbulanceTeam: - Search: src..module.complex.k_means_pp_search.KMeansPPSearch + Search: src..module.complex.k_means_pp_search.KMeansPPSearch DefaultTacticsFireBrigade: - Search: src..module.complex.k_means_pp_search.KMeansPPSearch + Search: src..module.complex.k_means_pp_search.KMeansPPSearch DefaultTacticsPoliceForce: - Search: src..module.complex.k_means_pp_search.KMeansPPSearch + Search: src..module.complex.k_means_pp_search.KMeansPPSearch ``` ## モジュールの実装 @@ -87,7 +87,7 @@ DefaultTacticsPoliceForce: ```yaml KMeansPPSearch: - Clustering: src..module.algorithm.k_means_pp_clustering.KMeansPPClustering + Clustering: src..module.algorithm.k_means_pp_clustering.KMeansPPClustering ``` 次に、`KMeansPPSearch` モジュールで `KMeansPPClustering` モジュールを呼び出せるようにします。 @@ -146,7 +146,23 @@ class KMeansPPSearch(Search): 以上で、`KMeansPPClustering` モジュールを用いた `KMeansPPSearch` モジュールの実装が完了しました。 -実行すると、各エージェントが担当地域内からランダムに探索対象を選択し、探索を行います。 +ターミナルを2つ起動します。 + +片方のターミナルを開き、シミュレーションサーバーを以下のコマンドで起動します: + +```bash +# Terminal A +cd WORKING_DIR/rcrs-server/scripts +./start-comprun.sh -m ../maps/tutorial_ambulance_team_only/map -c ../maps/tutorial_ambulance_team_only/config +``` + +その後、別のターミナルを開き、エージェントを起動します: + +```bash +# Terminal B +cd WORKING_DIR/ +python main.py +``` ## モジュールの改善 @@ -172,13 +188,13 @@ class KMeansPPSearch(Search): ### 探索対象がステップごとに変わってしまう問題 ```{admonition} 方針のヒント -:class: tip dropdown +:class: hint dropdown 一度選択した探索対象に到達するまで、探索対象を変更しないようにする ``` ```{admonition} プログラム例 -:class: tip dropdown +:class: hint dropdown ````python def calculate(self) -> Search: @@ -212,13 +228,13 @@ class KMeansPPSearch(Search): ### すでに探索したエンティティを再度探索対象として選択してしまう問題 ```{admonition} 方針のヒント -:class: tip dropdown +:class: hint dropdown すでに探索したエンティティを何かしらの方法で記録し、再度探索対象として選択しないようにする ``` ```{admonition} プログラム例 -:class: tip dropdown +:class: hint dropdown ````python def __init__( @@ -287,13 +303,13 @@ class KMeansPPSearch(Search): ### 近くに未探索のエンティティがあるのに、遠くのエンティティを探索対象として選択してしまう ```{admonition} 方針のヒント -:class: tip dropdown +:class: hint dropdown エンティティ間の距離を計算し、もっとも近いエンティティを探索対象として選択する ``` ```{admonition} プログラム例 -:class: tip dropdown +:class: hint dropdown ````python def calculate(self) -> Search: diff --git a/docs/source/images/cluster.png b/docs/source/images/cluster.png new file mode 100644 index 0000000000000000000000000000000000000000..4e0b9fb886e3f98080d8afa11f04e09e66051ba4 GIT binary patch literal 64512 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 として参照します。 +``` + 入力後、下記のようなエージェントのテンプレートがカレントディレクトリに作成されます。 ```bash @@ -55,7 +60,7 @@ cd WORKING_DIR/rcrs-server/scripts ```bash # Terminal B -cd WORKING_DIR/my-agent +cd WORKING_DIR/ python main.py ``` diff --git a/docs/source/tutorial/agent/agent_control.md b/docs/source/tutorial/agent/agent_control.md index afc3457..cc4c6f0 100644 --- a/docs/source/tutorial/agent/agent_control.md +++ b/docs/source/tutorial/agent/agent_control.md @@ -107,9 +107,20 @@ DefaultTacticsFireBrigade: HumanDetector: src..module.complex.fire_brigade_human_detector.FireBrigadeHumanDetector ``` -シミュレーションサーバーを起動し、エージェントを実行してみましょう。 +ターミナルを2つ起動します。 + +片方のターミナルを開き、シミュレーションサーバーを以下のコマンドで起動します: + +```bash +# Terminal A +cd WORKING_DIR/rcrs-server/scripts +./start-comprun.sh -m ../maps/tutorial_fire_brigade_only/map -c ../maps/tutorial_fire_brigade_only/config +``` + +その後、別のターミナルを開き、エージェントを起動します: ```bash +# Terminal B cd WORKING_DIR/ python main.py ``` @@ -151,13 +162,13 @@ RRS上のエンティティは下図のように `Entity` を継承したクラ - `entity` が市民であるかどうかを判定する ```python -isinstance(entity, Civilian) +is_civilian: bool = isinstance(entity, Civilian) ``` - エンティティIDを取得する ```python -entity.get_id() +entity_id: EntityID = entity.get_id() ``` - 市民が生きているかどうかを判定する @@ -185,19 +196,19 @@ if buriedness is None or buriedness <= 0: - エンティティIDからエンティティを取得する ```python -self._world_info.get_entity(entity_id) +entity: Entity = self._world_info.get_entity(entity_id) ``` - 指定したクラスのエンティティを全て取得する ```python -self._world_info.get_entities_by_type([Building, Road]) +entities: list[Entity] = self._world_info.get_entities_by_type([Building, Road]) ``` - エージェントの位置から指定したエンティティまでの距離を取得する ```python -self._world_info.get_distance(me, civilian.get_id()) +distance: float = self._world_info.get_distance(me, civilian.get_id()) ``` [詳細はこちら](../../adf_core_python.core.agent.info.rst) @@ -211,7 +222,7 @@ self._world_info.get_distance(me, civilian.get_id()) - 自分自身のエンティティIDを取得する ```python -self._agent_info.get_entity_id() +my_entity_id: EntityID = self._agent_info.get_entity_id() ``` [詳細はこちら](../../adf_core_python.core.agent.info.rst) @@ -305,9 +316,20 @@ class SampleHumanDetector(HumanDetector): return self._result ``` -シミュレーションサーバーを起動し、エージェントを実行してみましょう。 +ターミナルを2つ起動します。 + +片方のターミナルを開き、シミュレーションサーバーを以下のコマンドで起動します: + +```bash +# Terminal A +cd WORKING_DIR/rcrs-server/scripts +./start-comprun.sh -m ../maps/tutorial_fire_brigade_only/map -c ../maps/tutorial_fire_brigade_only/config +``` + +その後、別のターミナルを開き、エージェントを起動します: ```bash +# Terminal B cd WORKING_DIR/ python main.py ``` diff --git a/docs/source/tutorial/config/config.md b/docs/source/tutorial/config/config.md index 3257086..5f951c1 100644 --- a/docs/source/tutorial/config/config.md +++ b/docs/source/tutorial/config/config.md @@ -4,7 +4,7 @@ ```bash . -└── +└── └── config ├── development.json ├── launcher.yaml From f32e24ca8b43096706d97a4fe04cc370605e5225 Mon Sep 17 00:00:00 2001 From: shima004 Date: Sat, 30 Nov 2024 09:04:17 +0900 Subject: [PATCH 177/249] fix: Update office agent counts in launcher configuration --- adf_core_python/cli/template/config/launcher.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adf_core_python/cli/template/config/launcher.yaml b/adf_core_python/cli/template/config/launcher.yaml index 6782672..a47a583 100644 --- a/adf_core_python/cli/template/config/launcher.yaml +++ b/adf_core_python/cli/template/config/launcher.yaml @@ -28,8 +28,8 @@ adf: count: 100 office: ambulance: - count: -1 + count: 5 fire: - count: -1 + count: 5 police: - count: -1 + count: 5 From 7f8a6b42294f6d14e6634374ecd5a96893164281 Mon Sep 17 00:00:00 2001 From: shima004 Date: Sat, 30 Nov 2024 10:08:59 +0900 Subject: [PATCH 178/249] fix: Improve documentation with additional comments and examples for clustering and search modules --- docs/source/hands-on/clustering.md | 8 ++++- docs/source/hands-on/search.md | 11 +++++- docs/source/tutorial/agent/agent_control.md | 37 +++++++++++++-------- docs/source/tutorial/config/config.md | 9 +++++ docs/source/tutorial/module/module.md | 4 +-- 5 files changed, 52 insertions(+), 17 deletions(-) diff --git a/docs/source/hands-on/clustering.md b/docs/source/hands-on/clustering.md index 62f842d..df3962a 100644 --- a/docs/source/hands-on/clustering.md +++ b/docs/source/hands-on/clustering.md @@ -69,11 +69,13 @@ class KMeansPPClustering(Clustering): super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) + # ロガーの取得 self._logger = get_logger(f"{self.__class__.__name__}") # クラスター数の設定 self._cluster_number: int = 1 match agent_info.get_myself().get_urn(): + # エージェントのクラスに応じてクラスター数を設定 case EntityURN.AMBULANCE_TEAM: self._cluster_number = scenario_info.get_value( ScenarioInfoKeys.SCENARIO_AGENTS_AT, @@ -271,6 +273,7 @@ class KMeansPPClustering(Clustering): np.ndarray エージェントとクラスターの対応付け結果 """ + # エージェントの位置のリストを取得 agent_positions = np.array( [ [agent.get_x(), agent.get_y()] @@ -279,10 +282,12 @@ class KMeansPPClustering(Clustering): ] ) + # エージェントとクラスターの距離行列を計算 agent_positions = agent_positions.reshape(-1, 2) cost_matrix = np.linalg.norm( agent_positions[:, np.newaxis] - cluster_positions, axis=2 ) + # ハンガリアンアルゴリズムによりエージェントとクラスターの対応付けを行う _, col_ind = linear_sum_assignment(cost_matrix) return col_ind ``` @@ -329,7 +334,7 @@ python main.py このままだと、クラスタリング結果がわかりにくいので、クラスタリング結果を地図上に表示してみましょう。 -{download}`クラスターの可視化用スクリプト <./../download/cluster_plot.zip>`をダウンロードして解凍し、`main.py`の以下の部分に +{download}`クラスターの可視化用スクリプト <./../download/cluster_plot.zip>`をダウンロードして解凍し、中の`main.py`の以下の部分に ```python # クラスタリング結果 @@ -348,6 +353,7 @@ clusters = [[257, 259, 262, 263, 270, 278, 280, 297, 336, 913, 914, 915, 916, 91 貼り付けたら、以下のコマンドを実行してください。 ```bash +pip install -r requirements.txt python main.py ``` diff --git a/docs/source/hands-on/search.md b/docs/source/hands-on/search.md index 293ef8b..1668ea8 100644 --- a/docs/source/hands-on/search.md +++ b/docs/source/hands-on/search.md @@ -125,7 +125,9 @@ class KMeansPPSearch(Search): self.register_sub_module(self._clustering) ``` -そして、`calculate` メソッドでクラスタリングモジュールを呼び出し、探索対象を決定します。 +そして、`calculate` メソッドでクラスタリングモジュールを呼び出し、探索対象を決定するように変更します。 + +以下のコードを `k_means_pp_search.py` に追記してください。 ```python def calculate(self) -> Search: @@ -141,6 +143,9 @@ class KMeansPPSearch(Search): if cluster_entity_ids: self._result = random.choice(cluster_entity_ids) + # ログ出力 + self._logger.info(f"Target entity ID: {self._result}") + return self ``` @@ -185,6 +190,10 @@ python main.py ここに上げた問題以外にも、改善すべき点が存在すると思うので、それを改善していただいても構いません。 ``` +```{warning} +プログラム例のプログラムにも一部問題があるので、余裕があったら修正してみてください。 +``` + ### 探索対象がステップごとに変わってしまう問題 ```{admonition} 方針のヒント diff --git a/docs/source/tutorial/agent/agent_control.md b/docs/source/tutorial/agent/agent_control.md index cc4c6f0..089fcdc 100644 --- a/docs/source/tutorial/agent/agent_control.md +++ b/docs/source/tutorial/agent/agent_control.md @@ -235,10 +235,6 @@ my_entity_id: EntityID = self._agent_info.get_entity_id() ```python from typing import Optional -from rcrs_core.worldmodel.entityID import EntityID -from rcrs_core.entities.civilian import Civilian -from rcrs_core.entities.entity import Entity - from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo from adf_core_python.core.agent.info.scenario_info import ScenarioInfo @@ -246,9 +242,12 @@ from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.component.module.complex.human_detector import HumanDetector from adf_core_python.core.logger.logger import get_agent_logger +from rcrs_core.entities.civilian import Civilian +from rcrs_core.entities.entity import Entity +from rcrs_core.worldmodel.entityID import EntityID -class SampleHumanDetector(HumanDetector): +class FireBrigadeHumanDetector(HumanDetector): def __init__( self, agent_info: AgentInfo, @@ -271,44 +270,56 @@ class SampleHumanDetector(HumanDetector): def calculate(self) -> HumanDetector: """ 行動対象を決定する - + Returns ------- HumanDetector: 自身のインスタンス """ + # 自分自身のEntityIDを取得 me: EntityID = self._agent_info.get_entity_id() + # すべてのCivilianを取得 civilians: list[Entity] = self._world_info.get_entities_of_types( [ Civilian, ] ) - + + # 最も近いCivilianを探す nearest_civilian: Optional[EntityID] = None nearest_distance: Optional[float] = None for civilian in civilians: + # civilianがCivilianクラスのインスタンスでない場合はスキップ if not isinstance(civilian, Civilian): continue - + + # civilianのHPが0以下の場合はすでに死んでしまっているのでスキップ if civilian.get_hp() <= 0: continue - + + # civilianの埋没度が0以下の場合は掘り起こす必要がないのでスキップ if civilian.get_buriedness() <= 0: continue - + + # 自分自身との距離を計算 distance: float = self._world_info.get_distance(me, civilian.get_id()) - + + # 最も近いCivilianを更新 if nearest_distance is None or distance < nearest_distance: nearest_civilian = civilian.get_id() nearest_distance = distance - + + # 計算結果を格納 self._result = nearest_civilian + # ロガーに出力 + self._logger.info(f"Target: {self._result}") + return self def get_target_entity_id(self) -> Optional[EntityID]: """ 行動対象のEntityIDを取得する - + Returns ------- Optional[EntityID]: 行動対象のEntityID diff --git a/docs/source/tutorial/config/config.md b/docs/source/tutorial/config/config.md index 5f951c1..ed55219 100644 --- a/docs/source/tutorial/config/config.md +++ b/docs/source/tutorial/config/config.md @@ -24,6 +24,15 @@ 読み込むモジュールのパスなどが記述されています。 パスを指定する際は、プロジェクトの`main.py`からの相対パスで指定してください。 +```{admonition} 例 +:class: note + +```yaml +DefaultSearch: + PathPlanning: src..module.complex.sample_search.SampleSearch + Clustering: src..module.complex.sample_search.SampleClustering +``` + ### development.json `development.json` は、開発時に使用する設定ファイルです。 diff --git a/docs/source/tutorial/module/module.md b/docs/source/tutorial/module/module.md index 3410120..43f90f2 100644 --- a/docs/source/tutorial/module/module.md +++ b/docs/source/tutorial/module/module.md @@ -8,8 +8,8 @@ ```yaml SampleSearch: - PathPlanning: src.my-agent.module.complex.sample_search.SampleSearch - Clustering: src.my-agent.module.complex.sample_search.SampleClustering + PathPlanning: src..module.complex.sample_search.SampleSearch + Clustering: src..module.complex.sample_search.SampleClustering ``` この場合、`SampleSearch` というモジュールで使用される、`PathPlanning` と `Clustering` というモジュールを指定しています。 From 3571cf613d4a4766a6e4986eef7a28b76406c293 Mon Sep 17 00:00:00 2001 From: harrki Date: Sat, 30 Nov 2024 10:36:36 +0900 Subject: [PATCH 179/249] fix: Add some sentences for installing docs --- .../install/environment/linux/install.md | 82 +++++++++++++------ .../source/install/environment/mac/install.md | 62 +++++++++++--- 2 files changed, 109 insertions(+), 35 deletions(-) diff --git a/docs/source/install/environment/linux/install.md b/docs/source/install/environment/linux/install.md index c7369b2..9996b5a 100644 --- a/docs/source/install/environment/linux/install.md +++ b/docs/source/install/environment/linux/install.md @@ -2,48 +2,82 @@ ## 1. Gitのインストール -OS標準のパッケージマネージャーを使用してインストールします -- DebianベースのOSの場合(Ubuntuなど) +1. Terminalを起動し、以下のコマンドを実行します。 ```bash - sudo apt install git - ``` -- Red HatベースのOSの場合(Fedoraなど) - ```bash - sudo yum install git + git --version ``` +2. もし、`command not found`などのエラーが出た場合、OS標準のパッケージマネージャーを使用してインストールします。 + - DebianベースのOSの場合(Ubuntuなど) + ```bash + sudo apt install git + ``` + - Red HatベースのOSの場合(Fedoraなど) + ```bash + sudo yum install git + ``` + + ```bash + sudo dnf install git + ``` + +3. 以下のコマンドを入力し、バージョンが表示されたら成功です。(表示されない方はTerminalを再起動してください) ```bash - sudo dnf install git + git --version ``` ## 2. Pythonのインストール -OS標準のパッケージマネージャーを使用してインストールします -- DebianベースのOSの場合(Ubuntuなど) - ```bash - sudo apt install python - ``` -- Red HatベースのOSの場合(Fedoraなど) +1. Terminalを起動し、以下のコマンドを実行します。また、バージョンが3.12以上になっていることを確認します。 ```bash - sudo yum install python + python --version + # OR + python3 --version ``` +2. もし、`command not found`などのエラーが出た場合やバージョンが低い場合、OS標準のパッケージマネージャーを使用してインストールします + - DebianベースのOSの場合(Ubuntuなど) + ```bash + sudo apt install python3.12 python3.12-pip + ``` + - Red HatベースのOSの場合(Fedoraなど) + ```bash + sudo yum install python3.12 python3.12-pip + ``` + + ```bash + sudo dnf install python3.12 python3.12-pip + ``` + +3. 以下のコマンドを入力し、バージョンが表示されたら成功です。(表示されない方はTerminalを再起動してください) ```bash - sudo dnf install python + python --version + # OR + python3 --version ``` ## 3. OpenJDKのインストール -OS標準のパッケージマネージャーを使用してインストールします -- DebianベースのOSの場合(Ubuntuなど) - ```bash - sudo apt install openjdk-17-jdk - ``` -- Red HatベースのOSの場合(Fedoraなど) +1. Terminalを起動し、以下のコマンドを実行します。また、バージョンが17になっていることを確認します。 ```bash - sudo yum install java-17-openjdk + java --version ``` +2. もし、`command not found`などのエラーが出た場合やバージョンが異なる場合、OS標準のパッケージマネージャーを使用してインストールします + - DebianベースのOSの場合(Ubuntuなど) + ```bash + sudo apt install openjdk-17-jdk + ``` + - Red HatベースのOSの場合(Fedoraなど) + ```bash + sudo yum install java-17-openjdk-devel + ``` + + ```bash + sudo dnf install java-17-openjdk-devel + ``` + +3. 以下のコマンドを入力し、バージョンが表示されたら成功です。(表示されない方はTerminalを再起動してください) ```bash - sudo dnf install java-17-openjdk-devel + java --version ``` diff --git a/docs/source/install/environment/mac/install.md b/docs/source/install/environment/mac/install.md index 2b58966..179406d 100644 --- a/docs/source/install/environment/mac/install.md +++ b/docs/source/install/environment/mac/install.md @@ -1,21 +1,61 @@ # Macでの環境構築 +## 1. Homebrewのインストール +1. Terminalを起動し、以下のコマンドを実行します。 + ```bash + brew -v + ``` + +2. もし、`command not found`などのエラーが出た場合、以下のコマンドを実行します。 + ```bash + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + ``` + +3. もう一度、以下のコマンドを実行してバージョンが表示されたら成功です。(表示されない方はTerminalを再起動してください) + ```bash + brew -v + ``` -## 1. Gitのインストール +## 2. Gitのインストール -1. Terminalを起動します。 -2. XcodeのCommand Line Toolsをインストールします。 +1. Terminalを起動し、以下のコマンドを実行します。 + ```bash + git --version + ``` +2. もし、`command not found`などのエラーが出た場合、以下のコマンドを実行します。 + ```bash + brew install git + ``` +3. 以下のコマンドを入力し、バージョンが表示されたら成功です。(表示されない方はTerminalを再起動してください) ```bash - xcode-select --install + git --version ``` -## 2. Pythonのインストール +## 3. Pythonのインストール -1. [Python](https://www.python.org/downloads/)の公式サイトにアクセスします。 -2. ダウンロードページから最新のバージョンをダウンロードします。 -3. ダウンロードしたファイルを開き、インストールを開始します。 +1. Terminalを起動し、以下のコマンドを実行します。また、バージョンが3.12以上になっていることを確認します。 + ```bash + python --version + ``` +2. もし、`command not found`などのエラーが出た場合やバージョンが低い場合、以下のコマンドを実行します。 + ```bash + brew install python + ``` +3. 以下のコマンドを入力し、バージョンが表示されたら成功です。(表示されない方はTerminalを再起動してください) + ```bash + python --version + ``` ## 3. OpenJDKのインストール -1. [OpenJDK](https://jdk.java.net/archive/)の公式サイトにアクセスします。 -2. ダウンロードページから17.0.2のバージョンをダウンロードします。 -3. ダウンロードしたファイルを開き、インストールを開始します。 +1. Terminalを起動し、以下のコマンドを実行します。また、バージョンが17になっていることを確認します。 + ```bash + java --version + ``` +2. もし、`command not found`などのエラーが出た場合やバージョンが異なる場合、以下のコマンドを実行します。 + ```bash + brew install openjdk@17 + ``` +3. 以下のコマンドを入力し、バージョンが表示されたら成功です。(表示されない方はTerminalを再起動してください) + ```bash + java --version + ``` From d42042cf69e4cc78363e79cfba46071bcc03533b Mon Sep 17 00:00:00 2001 From: shima004 Date: Sat, 30 Nov 2024 10:37:37 +0900 Subject: [PATCH 180/249] chore: Add logging and improve documentation in search module --- docs/source/hands-on/search.md | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/source/hands-on/search.md b/docs/source/hands-on/search.md index 1668ea8..8bd4ba5 100644 --- a/docs/source/hands-on/search.md +++ b/docs/source/hands-on/search.md @@ -51,6 +51,7 @@ class KMeansPPSearch(Search): agent_info, world_info, scenario_info, module_manager, develop_data ) self._result: Optional[EntityID] = None + # ロガーの取得 self._logger = get_agent_logger( f"{self.__class__.__module__}.{self.__class__.__qualname__}", self._agent_info, @@ -108,20 +109,25 @@ class KMeansPPSearch(Search): agent_info, world_info, scenario_info, module_manager, develop_data ) self._result: Optional[EntityID] = None - + + # ロガーの取得 self._logger = get_agent_logger( f"{self.__class__.__module__}.{self.__class__.__qualname__}", self._agent_info, ) - + + # クラスタリングモジュールの読み込み self._clustering: Clustering = cast( Clustering, module_manager.get_module( + # config.yamlに登録したkey "KMeansPPSearch.Clustering", + # 上記のkeyが登録されていなかった場合のデフォルトモジュール "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", ), ) + # クラスタリングモジュールの登録 self.register_sub_module(self._clustering) ``` @@ -191,7 +197,7 @@ python main.py ``` ```{warning} -プログラム例のプログラムにも一部問題があるので、余裕があったら修正してみてください。 +プログラム例のプログラムにも一部改善点があるので、余裕があったら修正してみてください。 ``` ### 探索対象がステップごとに変わってしまう問題 @@ -230,6 +236,9 @@ python main.py # 探索対象が未選択の場合 if not self._result and cluster_entity_ids: self._result = random.choice(cluster_entity_ids) + + # ログ出力 + self._logger.info(f"Target entity ID: {self._result}") return self ``` @@ -259,19 +268,24 @@ python main.py ) self._result: Optional[EntityID] = None + # ロガーの取得 self._logger = get_agent_logger( f"{self.__class__.__module__}.{self.__class__.__qualname__}", self._agent_info, ) + # クラスタリングモジュールの読み込み self._clustering: Clustering = cast( Clustering, module_manager.get_module( + # config.yamlに登録したkey "KMeansPPSearch.Clustering", + # 上記のkeyが登録されていなかった場合のデフォルトモジュール "adf_core_python.implement.module.algorithm.k_means_clustering.KMeansClustering", ), ) + # クラスタリングモジュールの要録 self.register_sub_module(self._clustering) # 探索したいエンティティIDのリスト(追加) @@ -305,6 +319,9 @@ python main.py # 探索対象が未選択の場合(変更) if not self._result and self._search_entity_ids: self._result = random.choice(self._search_entity_ids) + + # ログ出力 + self._logger.info(f"Target entity ID: {self._result}") return self ``` @@ -358,4 +375,9 @@ python main.py nearest_entity_id = entity_id nearest_distance = distance self._result = nearest_entity_id + + # ログ出力 + self._logger.info(f"Target entity ID: {self._result}") + + return self ``` From 4bc5737310020643f8afba1aa93699a8214af3b9 Mon Sep 17 00:00:00 2001 From: harrki Date: Sat, 30 Nov 2024 23:51:16 +0900 Subject: [PATCH 181/249] fix: Add environment installing sentences --- .../install/environment/linux/install.md | 77 ++++++++++++++++--- .../install/environment/windows/install.md | 44 ++++++++--- 2 files changed, 102 insertions(+), 19 deletions(-) diff --git a/docs/source/install/environment/linux/install.md b/docs/source/install/environment/linux/install.md index 9996b5a..e431db1 100644 --- a/docs/source/install/environment/linux/install.md +++ b/docs/source/install/environment/linux/install.md @@ -10,6 +10,8 @@ 2. もし、`command not found`などのエラーが出た場合、OS標準のパッケージマネージャーを使用してインストールします。 - DebianベースのOSの場合(Ubuntuなど) ```bash + sudo apt update + sudo apt upgrade -y sudo apt install git ``` - Red HatベースのOSの場合(Fedoraなど) @@ -31,29 +33,82 @@ 1. Terminalを起動し、以下のコマンドを実行します。また、バージョンが3.12以上になっていることを確認します。 ```bash python --version - # OR - python3 --version ``` -2. もし、`command not found`などのエラーが出た場合やバージョンが低い場合、OS標準のパッケージマネージャーを使用してインストールします - - DebianベースのOSの場合(Ubuntuなど) +2. もし、`command not found`などのエラーが出た場合やバージョンが低い場合、Pythonのバージョン管理ツールであるpyenvを使用してインストールします + + ```{warning} + インストール方法の内容が最新ではない場合があるため、[https://github.com/pyenv/pyenv](https://github.com/pyenv/pyenv)を参照してください。 + ``` + + 1. 以下のコマンドを実行します。 ```bash - sudo apt install python3.12 python3.12-pip + curl https://pyenv.run | bash ``` - - Red HatベースのOSの場合(Fedoraなど) + + 2. 次に以下のコマンドを実行して、使用しているShellを確認します。 ```bash - sudo yum install python3.12 python3.12-pip + echo $SHELL + ``` + + 3. 表示されたShellに従ってコマンドを実行してください。 + + `bash`が表示された方は以下のコマンドを実行してください + ```bash + echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc + echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc + echo 'eval "$(pyenv init -)"' >> ~/.bashrc + + echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.profile + echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.profile + + echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile + echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile + echo 'eval "$(pyenv init -)"' >> ~/.bash_profile ``` + `zsh`が表示された方は以下のコマンドを実行してください ```bash - sudo dnf install python3.12 python3.12-pip + echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc + echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc + echo 'eval "$(pyenv init -)"' >> ~/.zshrc ``` + `fish`が表示された方は以下のコマンドを実行してください + ```bash + set -Ux PYENV_ROOT $HOME/.pyenv + fish_add_path $PYENV_ROOT/bin + pyenv init - | source + ``` + + 4. 必要パッケージのインストール + - DebianベースのOSの場合(Ubuntuなど) + ```bash + sudo apt update + sudo apt upgrade -y + sudo apt install make libssl-dev build-essential zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget llvm libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev + ``` + - Red HatベースのOSの場合(Fedoraなど) + ```bash + sudo yum install gcc bzip2 bzip2-devel openssl openssl-devel readline readline-devel sqlite-devel tk-devel + ``` + + ```bash + sudo dnf install gcc bzip2 bzip2-devel openssl openssl-devel readline readline-devel sqlite-devel tk-devel + ``` + + 5. python3.12のインストール + ```bash + exec "$SHELL" + pyenv install 3.12 + pyenv global 3.12 + ``` + + + 3. 以下のコマンドを入力し、バージョンが表示されたら成功です。(表示されない方はTerminalを再起動してください) ```bash python --version - # OR - python3 --version ``` ## 3. OpenJDKのインストール @@ -66,6 +121,8 @@ 2. もし、`command not found`などのエラーが出た場合やバージョンが異なる場合、OS標準のパッケージマネージャーを使用してインストールします - DebianベースのOSの場合(Ubuntuなど) ```bash + sudo apt update + sudo apt upgrade -y sudo apt install openjdk-17-jdk ``` - Red HatベースのOSの場合(Fedoraなど) diff --git a/docs/source/install/environment/windows/install.md b/docs/source/install/environment/windows/install.md index ce36a29..f2f8bd0 100644 --- a/docs/source/install/environment/windows/install.md +++ b/docs/source/install/environment/windows/install.md @@ -3,18 +3,44 @@ ## 1. Gitのインストール 1. [Git for Windows](https://gitforwindows.org/)の公式サイトにアクセスします。 -2. ダウンロードページから最新のバージョンをダウンロードします。 -3. ダウンロードしたファイルを開き、インストールを開始します。 +2. トップページの"Download"をクリックします +3. ダウンロードが完了した後、インストーラーを実行します。 +4. 全て"Next"をクリックします。 +5. インストールが完了するまで待ちます。 +6. インストールが完了したら"Finish"をクリックします。 +7. 検索バーに"Git Bash"と入力し、Git Bashを実行します。 +8. 画面が表示されていたらインストール成功です。 ## 2. Pythonのインストール -1. [Python](https://www.python.org/downloads/)の公式サイトにアクセスします。 -2. ダウンロードページから最新のバージョンをダウンロードします。 -3. ダウンロードしたファイルを開き、インストールを開始します。 -4. インストール時に「Add python.exe to PATH」にチェックを入れてください。 +1. [Python](https://www.python.org/)の公式サイトにアクセスします。 +2. トップページの"Download Python ~"をクリックします +3. ダウンロードが完了した後、インストーラーを実行します。 +4. "Add python.exe to PATH"にチェックが入っていることを確認した後、"Install Now"をクリックします。 +5. インストールが完了するまで待ちます。 +6. インストールが完了したら"Close"をクリックします。 +7. Git Bashを開き、`python --version`と入力し、`Python [バージョン]`が表示されたら成功です。(もし表示されない場合はGit Bashを開き直してください) ## 3. OpenJDKのインストール -1. [OpenJDK](https://jdk.java.net/archive/)の公式サイトにアクセスします。 -2. ダウンロードページから17.0.2のバージョンをダウンロードします。 -3. ダウンロードしたファイルを開き、インストールを開始します。 +1. [OpenJDK](https://jdk.java.net/archive/)のダウンロードページにアクセスします。 +2. 17.0.2のWindowsの横にある"zip"をクリックします。 +3. ダウンロードしたzipを展開(解凍)します。 +4. 展開(解凍)すると"jdk-17.0.2"のようなフォルダができるのを確認します。 +5. このフォルダ"jdk-17.0.2"を`C:¥`の直下に移動させます。 +6. Windowsでコマンドプロンプトを管理者として実行します。 +7. 開いたら以下のコマンドを実行します。 + ``` + powershell -command "[System.Environment]::SetEnvironmentVariable(\"JAVA_HOME\", \"c:\jdk-17.0.2\", \"Machine\")" + ``` +8. 次に以下のコマンドを実行します。 + ``` + powershell -command "$oldpath = [System.Environment]::GetEnvironmentVariable(\"Path\", \"Machine\"); $oldpath += \";c:\jdk-17.0.2\bin\"; [System.Environment]::SetEnvironmentVariable(\"Path\", $oldpath, \"Machine\")" + ``` +9. Git Bashを開き、`java -version`と入力します。 + 以下のような文字が表示されたらインストール成功です。 + ``` + openjdk version "17.0.2" 2022-01-18 + OpenJDK Runtime Environment (build 17.0.2+8-86) + OpenJDK 64-Bit Server VM (build 17.0.2+8-86, mixed mode, sharing) + ``` \ No newline at end of file From 7ea87efa14a05441546ac961e61925a8a137b6bb Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 4 Dec 2024 17:14:35 +0900 Subject: [PATCH 182/249] fix: Update connector methods to return a dictionary of threads and events --- adf_core_python/core/agent/agent.py | 13 ++++++++++++- adf_core_python/core/agent/platoon/platoon.py | 10 ++++++++++ .../core/agent/platoon/platoon_ambulance.py | 4 ++++ adf_core_python/core/agent/platoon/platoon_fire.py | 4 ++++ .../core/agent/platoon/platoon_police.py | 4 ++++ .../core/component/module/abstract_module.py | 10 ++++++++++ .../core/component/tactics/tactics_agent.py | 9 +++++++++ adf_core_python/core/launcher/agent_launcher.py | 2 ++ adf_core_python/core/launcher/connect/connector.py | 2 +- .../launcher/connect/connector_ambulance_center.py | 9 +++++---- .../launcher/connect/connector_ambulance_team.py | 9 +++++---- .../core/launcher/connect/connector_fire_brigade.py | 9 +++++---- .../core/launcher/connect/connector_fire_station.py | 9 +++++---- .../core/launcher/connect/connector_police_force.py | 9 +++++---- .../launcher/connect/connector_police_office.py | 9 +++++---- .../module/algorithm/k_means_clustering.py | 7 ++++++- 16 files changed, 92 insertions(+), 27 deletions(-) diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index a5bbee6..5ff66de 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -1,5 +1,7 @@ import sys +import time as _time from abc import abstractmethod +from threading import Event from typing import Any, Callable, NoReturn from bitarray import bitarray @@ -91,6 +93,7 @@ def __init__( data_storage_name: str, module_config: ModuleConfig, develop_data: DevelopData, + finish_post_connect_event: Event, ) -> None: self.name = name self.connect_request_id = None @@ -102,6 +105,7 @@ def __init__( self.logger = get_logger( f"{self.__class__.__module__}.{self.__class__.__qualname__}" ) + self.finish_post_connect_event = finish_post_connect_event self.team_name = team_name self.is_debug = is_debug @@ -293,9 +297,16 @@ def handler_sense(self, msg: Any) -> None: ].intValue, ) ) - + start_marge_time = _time.time() self.world_model.merge(change_set) + end_marge_time = _time.time() + self.logger.debug( + f"Time to merge: {end_marge_time - start_marge_time:.2f} seconds" + ) self.update_step_info(time, change_set, heard_commands) + self.logger.info( + f"Time to update_step_info: {_time.time() - end_marge_time:.2f} seconds" + ) def send_acknowledge(self, request_id: int) -> None: ak_ack = AKAcknowledge() diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py index 01babaa..a58d672 100644 --- a/adf_core_python/core/agent/platoon/platoon.py +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -1,3 +1,6 @@ +import time +from threading import Event + from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.agent import Agent from adf_core_python.core.agent.config.module_config import ModuleConfig @@ -19,6 +22,7 @@ def __init__( data_storage_name: str, module_config: ModuleConfig, develop_data: DevelopData, + finish_post_connect_event: Event, ) -> None: super().__init__( is_precompute, @@ -28,6 +32,7 @@ def __init__( data_storage_name, module_config, develop_data, + finish_post_connect_event, ) self._tactics_agent = tactics_agent self._team_name = team_name @@ -83,6 +88,10 @@ def post_connect(self) -> None: case Mode.PRECOMPUTED: pass case Mode.NON_PRECOMPUTE: + start_time = time.time() + self._logger.info( + f"Prepare start {self._agent_info.get_entity_id().get_value()}" + ) self._tactics_agent.prepare( self._agent_info, self._world_info, @@ -91,6 +100,7 @@ def post_connect(self) -> None: self.precompute_data, self._develop_data, ) + self._logger.info(f"Prepare time: {time.time() - start_time:.3f} sec") def think(self) -> None: action: Action = self._tactics_agent.think( diff --git a/adf_core_python/core/agent/platoon/platoon_ambulance.py b/adf_core_python/core/agent/platoon/platoon_ambulance.py index ca8e169..8a4d976 100644 --- a/adf_core_python/core/agent/platoon/platoon_ambulance.py +++ b/adf_core_python/core/agent/platoon/platoon_ambulance.py @@ -1,3 +1,5 @@ +from threading import Event + from rcrs_core.connection.URN import Entity as EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig @@ -16,6 +18,7 @@ def __init__( data_storage_name: str, module_config: ModuleConfig, develop_data: DevelopData, + finish_post_connect_event: Event, ): super().__init__( tactics_agent, @@ -25,6 +28,7 @@ def __init__( data_storage_name, module_config, develop_data, + finish_post_connect_event, ) def precompute(self) -> None: diff --git a/adf_core_python/core/agent/platoon/platoon_fire.py b/adf_core_python/core/agent/platoon/platoon_fire.py index 1935795..01d75ba 100644 --- a/adf_core_python/core/agent/platoon/platoon_fire.py +++ b/adf_core_python/core/agent/platoon/platoon_fire.py @@ -1,3 +1,5 @@ +from threading import Event + from rcrs_core.connection.URN import Entity as EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig @@ -16,6 +18,7 @@ def __init__( data_storage_name: str, module_config: ModuleConfig, develop_data: DevelopData, + finish_post_connect_event: Event, ): super().__init__( tactics_agent, @@ -25,6 +28,7 @@ def __init__( data_storage_name, module_config, develop_data, + finish_post_connect_event, ) def precompute(self) -> None: diff --git a/adf_core_python/core/agent/platoon/platoon_police.py b/adf_core_python/core/agent/platoon/platoon_police.py index 129c663..569159e 100644 --- a/adf_core_python/core/agent/platoon/platoon_police.py +++ b/adf_core_python/core/agent/platoon/platoon_police.py @@ -1,3 +1,5 @@ +from threading import Event + from rcrs_core.connection.URN import Entity as EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig @@ -16,6 +18,7 @@ def __init__( data_storage_name: str, module_config: ModuleConfig, develop_data: DevelopData, + finish_post_connect_event: Event, ): super().__init__( tactics_agent, @@ -25,6 +28,7 @@ def __init__( data_storage_name, module_config, develop_data, + finish_post_connect_event, ) def precompute(self) -> None: diff --git a/adf_core_python/core/component/module/abstract_module.py b/adf_core_python/core/component/module/abstract_module.py index 7660519..8115692 100644 --- a/adf_core_python/core/component/module/abstract_module.py +++ b/adf_core_python/core/component/module/abstract_module.py @@ -1,8 +1,11 @@ from __future__ import annotations +import time from abc import ABC, abstractmethod from typing import TYPE_CHECKING +from adf_core_python.core.logger.logger import get_logger + if TYPE_CHECKING: from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -32,6 +35,9 @@ def __init__( self._count_prepare: int = 0 self._count_update_info: int = 0 self._count_update_info_current_time: int = 0 + self._logger = get_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + ) self._sub_modules: list[AbstractModule] = [] @@ -56,7 +62,11 @@ def resume(self, precompute_data: PrecomputeData) -> AbstractModule: def prepare(self) -> AbstractModule: self._count_prepare += 1 for sub_module in self._sub_modules: + start_time = time.time() sub_module.prepare() + self._logger.info( + f"module {sub_module.__class__.__name__} prepare time: {time.time() - start_time:.3f}", + ) return self def update_info(self, message_manager: MessageManager) -> AbstractModule: diff --git a/adf_core_python/core/component/tactics/tactics_agent.py b/adf_core_python/core/component/tactics/tactics_agent.py index b513441..d2d27e6 100644 --- a/adf_core_python/core/component/tactics/tactics_agent.py +++ b/adf_core_python/core/component/tactics/tactics_agent.py @@ -1,5 +1,6 @@ from __future__ import annotations +import time from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Any, Optional @@ -130,9 +131,17 @@ def module_resume(self, precompute_data: PrecomputeData) -> None: def module_prepare(self) -> None: for module in self._modules: + start_time = time.time() module.prepare() + self._logger.info( + f"module {module.__class__.__name__} prepare time: {time.time() - start_time:.3f}", + ) for action in self._actions: + start_time = time.time() action.prepare() + self._logger.info( + f"action {action.__class__.__name__} prepare time: {time.time() - start_time:.3f}", + ) # for executor in self._command_executor: # executor.prepare() diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index 9b275f6..1a05f97 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -1,5 +1,6 @@ import importlib import threading +import time from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.config.config import Config @@ -68,6 +69,7 @@ def launch(self) -> None: for thread in threads: thread.daemon = True thread.start() + time.sleep(0.5) self.thread_list.extend(threads) for thread in self.thread_list: diff --git a/adf_core_python/core/launcher/connect/connector.py b/adf_core_python/core/launcher/connect/connector.py index 850023a..e517a84 100644 --- a/adf_core_python/core/launcher/connect/connector.py +++ b/adf_core_python/core/launcher/connect/connector.py @@ -16,7 +16,7 @@ def connect( component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader, - ) -> list[threading.Thread]: + ) -> dict[threading.Thread, threading.Event]: raise NotImplementedError def get_connected_agent_count(self) -> int: diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_center.py b/adf_core_python/core/launcher/connect/connector_ambulance_center.py index 98e02d8..36e22f6 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_center.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_center.py @@ -24,12 +24,12 @@ def connect( component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader, - ) -> list[threading.Thread]: + ) -> dict[threading.Thread, threading.Event]: count: int = config.get_value(ConfigKey.KEY_AMBULANCE_CENTRE_COUNT, 0) if count == 0: - return [] + return {} - threads: list[threading.Thread] = [] + threads: dict[threading.Thread, threading.Event] = {} for _ in range(count): if loader.get_tactics_ambulance_center() is None: @@ -54,6 +54,7 @@ def connect( ) request_id: int = component_launcher.generate_request_id() + finish_post_connect_event = threading.Event() thread = threading.Thread( target=component_launcher.connect, args=( @@ -70,7 +71,7 @@ def connect( ), name=f"AmbulanceCenterAgent-{request_id}", ) - threads.append(thread) + threads[thread] = finish_post_connect_event self.logger.info("Connected ambulance center (count: %d)" % count) return threads diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index 350d6bd..50c4167 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -24,12 +24,12 @@ def connect( component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader, - ) -> list[threading.Thread]: + ) -> dict[threading.Thread, threading.Event]: count: int = config.get_value(ConfigKey.KEY_AMBULANCE_TEAM_COUNT, 0) if count == 0: - return [] + return {} - threads: list[threading.Thread] = [] + threads: dict[threading.Thread, threading.Event] = {} for _ in range(count): if loader.get_tactics_ambulance_team() is None: @@ -54,6 +54,7 @@ def connect( ) request_id: int = component_launcher.generate_request_id() + finish_post_connect_event = threading.Event() thread = threading.Thread( target=component_launcher.connect, args=( @@ -70,7 +71,7 @@ def connect( ), name=f"AmbulanceTeam-{request_id}", ) - threads.append(thread) + threads[thread] = finish_post_connect_event self.logger.info("Connected ambulance team (count: %d)" % count) return threads diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/adf_core_python/core/launcher/connect/connector_fire_brigade.py index 9fc24aa..a416f66 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_brigade.py +++ b/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -24,12 +24,12 @@ def connect( component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader, - ) -> list[threading.Thread]: + ) -> dict[threading.Thread, threading.Event]: count: int = config.get_value(ConfigKey.KEY_FIRE_BRIGADE_COUNT, 0) if count == 0: - return [] + return {} - threads: list[threading.Thread] = [] + threads: dict[threading.Thread, threading.Event] = {} for _ in range(count): if loader.get_tactics_fire_brigade() is None: @@ -52,6 +52,7 @@ def connect( ) request_id: int = component_launcher.generate_request_id() + finish_post_connect_event = threading.Event() thread = threading.Thread( target=component_launcher.connect, args=( @@ -68,7 +69,7 @@ def connect( ), name=f"FireBrigadeAgent-{request_id}", ) - threads.append(thread) + threads[thread] = finish_post_connect_event self.logger.info("Connected fire brigade (count: %d)" % count) return threads diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/adf_core_python/core/launcher/connect/connector_fire_station.py index f9f4abc..413eeca 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_station.py +++ b/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -24,12 +24,12 @@ def connect( component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader, - ) -> list[threading.Thread]: + ) -> dict[threading.Thread, threading.Event]: count: int = config.get_value(ConfigKey.KEY_FIRE_STATION_COUNT, 0) if count == 0: - return [] + return {} - threads: list[threading.Thread] = [] + threads: dict[threading.Thread, threading.Event] = {} for _ in range(count): if loader.get_tactics_fire_station() is None: @@ -52,6 +52,7 @@ def connect( ) request_id: int = component_launcher.generate_request_id() + finish_post_connect_event = threading.Event() thread = threading.Thread( target=component_launcher.connect, args=( @@ -68,7 +69,7 @@ def connect( ), name=f"FireStationAgent-{request_id}", ) - threads.append(thread) + threads[thread] = finish_post_connect_event self.logger.info("Connected fire station (count: %d)" % count) return threads diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py index 2b3a905..1a11a86 100644 --- a/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -24,12 +24,12 @@ def connect( component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader, - ) -> list[threading.Thread]: + ) -> dict[threading.Thread, threading.Event]: count: int = config.get_value(ConfigKey.KEY_POLICE_FORCE_COUNT, 0) if count == 0: - return [] + return {} - threads: list[threading.Thread] = [] + threads: dict[threading.Thread, threading.Event] = {} for _ in range(count): if loader.get_tactics_police_force() is None: @@ -52,6 +52,7 @@ def connect( ) request_id: int = component_launcher.generate_request_id() + finish_post_connect_event = threading.Event() thread = threading.Thread( target=component_launcher.connect, args=( @@ -68,7 +69,7 @@ def connect( ), name=f"PoliceForceAgent-{request_id}", ) - threads.append(thread) + threads[thread] = finish_post_connect_event self.logger.info("Connected police force (count: %d)" % count) return threads diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py index 01601fc..c039f89 100644 --- a/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -24,12 +24,12 @@ def connect( component_launcher: ComponentLauncher, config: Config, loader: AbstractLoader, - ) -> list[threading.Thread]: + ) -> dict[threading.Thread, threading.Event]: count: int = config.get_value(ConfigKey.KEY_POLICE_OFFICE_COUNT, 0) if count == 0: - return [] + return {} - threads: list[threading.Thread] = [] + threads: dict[threading.Thread, threading.Event] = {} for _ in range(count): if loader.get_tactics_police_office() is None: @@ -54,6 +54,7 @@ def connect( ) request_id: int = component_launcher.generate_request_id() + finish_post_connect_event = threading.Event() thread = threading.Thread( target=component_launcher.connect, args=( @@ -70,7 +71,7 @@ def connect( ), name=f"PoliceOfficeAgent-{request_id}", ) - threads.append(thread) + threads[thread] = finish_post_connect_event self.logger.info("Connected police office (count: %d)" % count) return threads diff --git a/adf_core_python/implement/module/algorithm/k_means_clustering.py b/adf_core_python/implement/module/algorithm/k_means_clustering.py index b40f7c4..804ae36 100644 --- a/adf_core_python/implement/module/algorithm/k_means_clustering.py +++ b/adf_core_python/implement/module/algorithm/k_means_clustering.py @@ -1,3 +1,5 @@ +import time + import numpy as np from rcrs_core.connection.URN import Entity as EntityURN from rcrs_core.entities.ambulanceCenter import AmbulanceCentre @@ -112,7 +114,7 @@ def prepare(self) -> Clustering: def create_cluster( self, cluster_number: int, entities: list[Entity] ) -> list[list[Entity]]: - kmeans = KMeans(n_clusters=cluster_number, random_state=0) + kmeans = KMeans(n_clusters=cluster_number, random_state=0, init="k-means++") entity_positions: np.ndarray = np.array([]) for entity in entities: location1_x, location1_y = entity.get_location() @@ -120,7 +122,10 @@ def create_cluster( continue entity_positions = np.append(entity_positions, [location1_x, location1_y]) + self._logger.info(f"Entity positions: {len(entity_positions) // 2}") + start_time = time.time() kmeans.fit(entity_positions.reshape(-1, 2)) + self._logger.info(f"KMeans clustering time: {time.time() - start_time:.3f} sec") clusters: list[list[Entity]] = [[] for _ in range(cluster_number)] for entity, label in zip(entities, kmeans.labels_): From 0e7f894870b64202e9d52c2232c0af98574fac78 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 5 Dec 2024 21:51:21 +0900 Subject: [PATCH 183/249] fix: Update logging levels to debug and add finish_post_connect_event parameter in connectors --- adf_core_python/core/agent/agent.py | 12 +++----- adf_core_python/core/agent/office/office.py | 5 ++++ .../core/agent/office/office_ambulance.py | 4 +++ .../core/agent/office/office_fire.py | 4 +++ .../core/agent/office/office_police.py | 4 +++ adf_core_python/core/agent/platoon/platoon.py | 7 ++--- .../core/component/module/abstract_module.py | 9 +++--- .../core/component/tactics/tactics_agent.py | 6 ++-- .../core/launcher/agent_launcher.py | 28 ++++++++++++++----- adf_core_python/core/launcher/config_key.py | 1 + .../launcher/connect/component_launcher.py | 9 +----- .../connect/connector_ambulance_center.py | 1 + .../connect/connector_ambulance_team.py | 1 + .../connect/connector_fire_brigade.py | 1 + .../connect/connector_fire_station.py | 1 + .../connect/connector_police_force.py | 1 + .../connect/connector_police_office.py | 1 + .../module/algorithm/k_means_clustering.py | 3 -- adf_core_python/launcher.py | 2 +- 19 files changed, 61 insertions(+), 39 deletions(-) diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index 5ff66de..ec0933c 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -152,7 +152,7 @@ def post_connect(self) -> None: self._agent_info, ) - self.logger.info(f"config: {self.config}") + self.logger.debug(f"agent_config: {self.config}") def update_step_info( self, time: int, change_set: ChangeSet, hear: list[Command] @@ -297,15 +297,11 @@ def handler_sense(self, msg: Any) -> None: ].intValue, ) ) - start_marge_time = _time.time() self.world_model.merge(change_set) - end_marge_time = _time.time() - self.logger.debug( - f"Time to merge: {end_marge_time - start_marge_time:.2f} seconds" - ) + start_update_info_time = _time.time() self.update_step_info(time, change_set, heard_commands) - self.logger.info( - f"Time to update_step_info: {_time.time() - end_marge_time:.2f} seconds" + self.logger.debug( + f"{time} step calculation time: {_time.time() - start_update_info_time}" ) def send_acknowledge(self, request_id: int) -> None: diff --git a/adf_core_python/core/agent/office/office.py b/adf_core_python/core/agent/office/office.py index 4c43c4b..0aed6bc 100644 --- a/adf_core_python/core/agent/office/office.py +++ b/adf_core_python/core/agent/office/office.py @@ -1,3 +1,5 @@ +from threading import Event + from adf_core_python.core.agent.agent import Agent from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -18,6 +20,7 @@ def __init__( data_storage_name: str, module_config: ModuleConfig, develop_data: DevelopData, + finish_post_connect_event: Event, ) -> None: super().__init__( is_precompute, @@ -27,6 +30,7 @@ def __init__( data_storage_name, module_config, develop_data, + finish_post_connect_event, ) self._tactics_center = tactics_center self._team_name = team_name @@ -90,6 +94,7 @@ def post_connect(self) -> None: self.precompute_data, self._develop_data, ) + self.finish_post_connect_event.set() def think(self) -> None: self._tactics_center.think( diff --git a/adf_core_python/core/agent/office/office_ambulance.py b/adf_core_python/core/agent/office/office_ambulance.py index 4076f67..6bec530 100644 --- a/adf_core_python/core/agent/office/office_ambulance.py +++ b/adf_core_python/core/agent/office/office_ambulance.py @@ -1,3 +1,5 @@ +from threading import Event + from rcrs_core.connection.URN import Entity as EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig @@ -16,6 +18,7 @@ def __init__( data_storage_name: str, module_config: ModuleConfig, develop_data: DevelopData, + finish_post_connect_event: Event, ) -> None: super().__init__( tactics_center, @@ -25,6 +28,7 @@ def __init__( data_storage_name, module_config, develop_data, + finish_post_connect_event, ) def precompute(self) -> None: diff --git a/adf_core_python/core/agent/office/office_fire.py b/adf_core_python/core/agent/office/office_fire.py index bcd1946..dcab2bd 100644 --- a/adf_core_python/core/agent/office/office_fire.py +++ b/adf_core_python/core/agent/office/office_fire.py @@ -1,3 +1,5 @@ +from threading import Event + from rcrs_core.connection.URN import Entity as EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig @@ -16,6 +18,7 @@ def __init__( data_storage_name: str, module_config: ModuleConfig, develop_data: DevelopData, + finish_post_connect_event: Event, ) -> None: super().__init__( tactics_center, @@ -25,6 +28,7 @@ def __init__( data_storage_name, module_config, develop_data, + finish_post_connect_event, ) def precompute(self) -> None: diff --git a/adf_core_python/core/agent/office/office_police.py b/adf_core_python/core/agent/office/office_police.py index ccfcbca..b4c9fc4 100644 --- a/adf_core_python/core/agent/office/office_police.py +++ b/adf_core_python/core/agent/office/office_police.py @@ -1,3 +1,5 @@ +from threading import Event + from rcrs_core.connection.URN import Entity as EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig @@ -16,6 +18,7 @@ def __init__( data_storage_name: str, module_config: ModuleConfig, develop_data: DevelopData, + finish_post_connect_event: Event, ) -> None: super().__init__( tactics_center, @@ -25,6 +28,7 @@ def __init__( data_storage_name, module_config, develop_data, + finish_post_connect_event, ) def precompute(self) -> None: diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py index a58d672..41e1f32 100644 --- a/adf_core_python/core/agent/platoon/platoon.py +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -88,10 +88,6 @@ def post_connect(self) -> None: case Mode.PRECOMPUTED: pass case Mode.NON_PRECOMPUTE: - start_time = time.time() - self._logger.info( - f"Prepare start {self._agent_info.get_entity_id().get_value()}" - ) self._tactics_agent.prepare( self._agent_info, self._world_info, @@ -100,7 +96,8 @@ def post_connect(self) -> None: self.precompute_data, self._develop_data, ) - self._logger.info(f"Prepare time: {time.time() - start_time:.3f} sec") + + self.finish_post_connect_event.set() def think(self) -> None: action: Action = self._tactics_agent.think( diff --git a/adf_core_python/core/component/module/abstract_module.py b/adf_core_python/core/component/module/abstract_module.py index 8115692..dcba3aa 100644 --- a/adf_core_python/core/component/module/abstract_module.py +++ b/adf_core_python/core/component/module/abstract_module.py @@ -4,7 +4,7 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING -from adf_core_python.core.logger.logger import get_logger +from adf_core_python.core.logger.logger import get_agent_logger, get_logger if TYPE_CHECKING: from adf_core_python.core.agent.communication.message_manager import MessageManager @@ -35,8 +35,9 @@ def __init__( self._count_prepare: int = 0 self._count_update_info: int = 0 self._count_update_info_current_time: int = 0 - self._logger = get_logger( + self._logger = get_agent_logger( f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, ) self._sub_modules: list[AbstractModule] = [] @@ -64,8 +65,8 @@ def prepare(self) -> AbstractModule: for sub_module in self._sub_modules: start_time = time.time() sub_module.prepare() - self._logger.info( - f"module {sub_module.__class__.__name__} prepare time: {time.time() - start_time:.3f}", + self._logger.debug( + f"{self.__class__.__name__}'s sub_module {sub_module.__class__.__name__} prepare time: {time.time() - start_time:.3f}", ) return self diff --git a/adf_core_python/core/component/tactics/tactics_agent.py b/adf_core_python/core/component/tactics/tactics_agent.py index d2d27e6..b092517 100644 --- a/adf_core_python/core/component/tactics/tactics_agent.py +++ b/adf_core_python/core/component/tactics/tactics_agent.py @@ -133,14 +133,14 @@ def module_prepare(self) -> None: for module in self._modules: start_time = time.time() module.prepare() - self._logger.info( + self._logger.debug( f"module {module.__class__.__name__} prepare time: {time.time() - start_time:.3f}", ) for action in self._actions: start_time = time.time() action.prepare() - self._logger.info( - f"action {action.__class__.__name__} prepare time: {time.time() - start_time:.3f}", + self._logger.debug( + f"module {action.__class__.__name__} prepare time: {time.time() - start_time:.3f}", ) # for executor in self._command_executor: # executor.prepare() diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index 1a05f97..aebcd24 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -33,7 +33,7 @@ def __init__(self, config: Config): self.config = config self.logger = get_logger(__name__) self.connectors: list[Connector] = [] - self.thread_list: list[threading.Thread] = [] + self.agent_thread_list: list[threading.Thread] = [] def init_connector(self) -> None: loader_name, loader_class_name = self.config.get_value( @@ -64,13 +64,27 @@ def launch(self) -> None: host, port, self.logger ) + connector_thread_list: list[threading.Thread] = [] for connector in self.connectors: threads = connector.connect(component_launcher, self.config, self.loader) - for thread in threads: - thread.daemon = True - thread.start() - time.sleep(0.5) - self.thread_list.extend(threads) + self.agent_thread_list.extend(threads) - for thread in self.thread_list: + def connect(): + for thread, event in threads.items(): + thread.daemon = True + thread.start() + is_not_timeout = event.wait(5) + if not is_not_timeout: + break + + connector_thread = threading.Thread(target=connect) + connector_thread_list.append(connector_thread) + connector_thread.start() + + for thread in connector_thread_list: + thread.join() + + self.logger.info("All agents have been launched") + + for thread in self.agent_thread_list: thread.join() diff --git a/adf_core_python/core/launcher/config_key.py b/adf_core_python/core/launcher/config_key.py index 69d7d35..d32afb2 100644 --- a/adf_core_python/core/launcher/config_key.py +++ b/adf_core_python/core/launcher/config_key.py @@ -22,3 +22,4 @@ class ConfigKey: KEY_AMBULANCE_CENTRE_COUNT: Final[str] = "adf.team.office.ambulance.count" KEY_FIRE_STATION_COUNT: Final[str] = "adf.team.office.fire.count" KEY_POLICE_OFFICE_COUNT: Final[str] = "adf.team.office.police.count" + # adf-core-python diff --git a/adf_core_python/core/launcher/connect/component_launcher.py b/adf_core_python/core/launcher/connect/component_launcher.py index d4f6ffd..8e45009 100644 --- a/adf_core_python/core/launcher/connect/component_launcher.py +++ b/adf_core_python/core/launcher/connect/component_launcher.py @@ -17,19 +17,12 @@ def make_connection(self) -> Connection: return Connection(self.host, self.port) def connect(self, agent: Agent, _request_id: int) -> None: - # self.logger.bind(agent_id=agent.get_id()) - self.logger.info( - f"{agent.__class__.__name__} connecting to {self.host}:{self.port} request_id: {_request_id}" + f"{agent.__class__.__name__} trying to connect to {self.host}:{self.port} request_id: {_request_id}" ) connection = self.make_connection() try: connection.connect() - # ソケットが使用しているPORT番号を取得 - if connection.socket is not None: - self.logger.info( - f"Connected to {self.host}:{self.port} on port {connection.socket.getsockname()[1]}" - ) except socket.timeout: self.logger.warning(f"Connection to {self.host}:{self.port} timed out") return diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_center.py b/adf_core_python/core/launcher/connect/connector_ambulance_center.py index 36e22f6..1dca5af 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_center.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_center.py @@ -66,6 +66,7 @@ def connect( "test", module_config, develop_data, + finish_post_connect_event, ), request_id, ), diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index 50c4167..879f313 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -66,6 +66,7 @@ def connect( "test", module_config, develop_data, + finish_post_connect_event, ), request_id, ), diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/adf_core_python/core/launcher/connect/connector_fire_brigade.py index a416f66..ac7ecb3 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_brigade.py +++ b/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -64,6 +64,7 @@ def connect( "test", module_config, develop_data, + finish_post_connect_event, ), request_id, ), diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/adf_core_python/core/launcher/connect/connector_fire_station.py index 413eeca..de8dbc6 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_station.py +++ b/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -64,6 +64,7 @@ def connect( "test", module_config, develop_data, + finish_post_connect_event, ), request_id, ), diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py index 1a11a86..d2f45b1 100644 --- a/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -64,6 +64,7 @@ def connect( "test", module_config, develop_data, + finish_post_connect_event, ), request_id, ), diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py index c039f89..1c7a7f7 100644 --- a/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -66,6 +66,7 @@ def connect( "test", module_config, develop_data, + finish_post_connect_event, ), request_id, ), diff --git a/adf_core_python/implement/module/algorithm/k_means_clustering.py b/adf_core_python/implement/module/algorithm/k_means_clustering.py index 804ae36..c18133d 100644 --- a/adf_core_python/implement/module/algorithm/k_means_clustering.py +++ b/adf_core_python/implement/module/algorithm/k_means_clustering.py @@ -122,10 +122,7 @@ def create_cluster( continue entity_positions = np.append(entity_positions, [location1_x, location1_y]) - self._logger.info(f"Entity positions: {len(entity_positions) // 2}") - start_time = time.time() kmeans.fit(entity_positions.reshape(-1, 2)) - self._logger.info(f"KMeans clustering time: {time.time() - start_time:.3f} sec") clusters: list[list[Entity]] = [[] for _ in range(cluster_number)] for entity, label in zip(entities, kmeans.labels_): diff --git a/adf_core_python/launcher.py b/adf_core_python/launcher.py index e523a1f..310ff9d 100644 --- a/adf_core_python/launcher.py +++ b/adf_core_python/launcher.py @@ -98,7 +98,7 @@ def __init__( if value is not None: self.launcher_config.set_value(key, value) - self.logger.info(f"Config: {self.launcher_config}") + self.logger.debug(f"launcher_config: {self.launcher_config}") def launch(self) -> None: agent_launcher: AgentLauncher = AgentLauncher( From dabb446ce532571cfdccdb82ed080172245afd36 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 5 Dec 2024 21:58:36 +0900 Subject: [PATCH 184/249] fix: Remove unused time imports and add type hint for connect function fix: Remove init parameter from KMeans instantiation in create_cluster method fix: Remove unused import of time in k_means_clustering.py fix: Remove unused time imports and add type hint for connect function --- adf_core_python/core/agent/platoon/platoon.py | 1 - adf_core_python/core/component/module/abstract_module.py | 2 +- adf_core_python/core/launcher/agent_launcher.py | 3 +-- .../implement/module/algorithm/k_means_clustering.py | 4 +--- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py index 41e1f32..36c46a6 100644 --- a/adf_core_python/core/agent/platoon/platoon.py +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -1,4 +1,3 @@ -import time from threading import Event from adf_core_python.core.agent.action.action import Action diff --git a/adf_core_python/core/component/module/abstract_module.py b/adf_core_python/core/component/module/abstract_module.py index dcba3aa..4a3f317 100644 --- a/adf_core_python/core/component/module/abstract_module.py +++ b/adf_core_python/core/component/module/abstract_module.py @@ -4,7 +4,7 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING -from adf_core_python.core.logger.logger import get_agent_logger, get_logger +from adf_core_python.core.logger.logger import get_agent_logger if TYPE_CHECKING: from adf_core_python.core.agent.communication.message_manager import MessageManager diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index aebcd24..f9efa15 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -1,6 +1,5 @@ import importlib import threading -import time from adf_core_python.core.component.abstract_loader import AbstractLoader from adf_core_python.core.config.config import Config @@ -69,7 +68,7 @@ def launch(self) -> None: threads = connector.connect(component_launcher, self.config, self.loader) self.agent_thread_list.extend(threads) - def connect(): + def connect() -> None: for thread, event in threads.items(): thread.daemon = True thread.start() diff --git a/adf_core_python/implement/module/algorithm/k_means_clustering.py b/adf_core_python/implement/module/algorithm/k_means_clustering.py index c18133d..b40f7c4 100644 --- a/adf_core_python/implement/module/algorithm/k_means_clustering.py +++ b/adf_core_python/implement/module/algorithm/k_means_clustering.py @@ -1,5 +1,3 @@ -import time - import numpy as np from rcrs_core.connection.URN import Entity as EntityURN from rcrs_core.entities.ambulanceCenter import AmbulanceCentre @@ -114,7 +112,7 @@ def prepare(self) -> Clustering: def create_cluster( self, cluster_number: int, entities: list[Entity] ) -> list[list[Entity]]: - kmeans = KMeans(n_clusters=cluster_number, random_state=0, init="k-means++") + kmeans = KMeans(n_clusters=cluster_number, random_state=0) entity_positions: np.ndarray = np.array([]) for entity in entities: location1_x, location1_y = entity.get_location() From 947da282eed3e79d560bab9e3440548dba83d07d Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 6 Dec 2024 00:58:59 +0900 Subject: [PATCH 185/249] fix: Set finish_post_connect_event after successful connection and remove redundant logging --- adf_core_python/core/agent/agent.py | 7 +++++++ adf_core_python/core/agent/office/office.py | 1 - adf_core_python/core/agent/platoon/platoon.py | 2 -- adf_core_python/core/launcher/agent_launcher.py | 4 +--- .../core/launcher/connect/component_launcher.py | 3 --- .../core/launcher/connect/connector_ambulance_center.py | 1 - .../core/launcher/connect/connector_ambulance_team.py | 1 - .../core/launcher/connect/connector_fire_brigade.py | 1 - .../core/launcher/connect/connector_fire_station.py | 1 - .../core/launcher/connect/connector_police_force.py | 1 - .../core/launcher/connect/connector_police_office.py | 1 - 11 files changed, 8 insertions(+), 15 deletions(-) diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index ec0933c..55506e1 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -238,6 +238,7 @@ def handle_connect_error(self, msg: Any) -> NoReturn: msg.reason, msg.request_id, ) + self.finish_post_connect_event.set() else: self.logger.error( "Failed to connect agent: %s(request_id: %s)", @@ -264,10 +265,16 @@ def handle_connect_ok(self, msg: Any) -> None: self.config.set_value(key, value) self.send_acknowledge(msg.request_id) self.post_connect() + self.logger.info( + f"Connected to kernel: {self.__class__.__qualname__} (request_id: {msg.request_id})", + request_id=msg.request_id, + ) if self.precompute_flag: print("self.precompute_flag: ", self.precompute_flag) self.precompute() + self.finish_post_connect_event.set() + def handler_sense(self, msg: Any) -> None: _id = EntityID(msg.agent_id) time = msg.time diff --git a/adf_core_python/core/agent/office/office.py b/adf_core_python/core/agent/office/office.py index 0aed6bc..09e97bd 100644 --- a/adf_core_python/core/agent/office/office.py +++ b/adf_core_python/core/agent/office/office.py @@ -94,7 +94,6 @@ def post_connect(self) -> None: self.precompute_data, self._develop_data, ) - self.finish_post_connect_event.set() def think(self) -> None: self._tactics_center.think( diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py index 36c46a6..c5ff9f8 100644 --- a/adf_core_python/core/agent/platoon/platoon.py +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -96,8 +96,6 @@ def post_connect(self) -> None: self._develop_data, ) - self.finish_post_connect_event.set() - def think(self) -> None: action: Action = self._tactics_agent.think( self._agent_info, diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index f9efa15..f6f0787 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -72,9 +72,7 @@ def connect() -> None: for thread, event in threads.items(): thread.daemon = True thread.start() - is_not_timeout = event.wait(5) - if not is_not_timeout: - break + event.wait(5) connector_thread = threading.Thread(target=connect) connector_thread_list.append(connector_thread) diff --git a/adf_core_python/core/launcher/connect/component_launcher.py b/adf_core_python/core/launcher/connect/component_launcher.py index 8e45009..0f308c1 100644 --- a/adf_core_python/core/launcher/connect/component_launcher.py +++ b/adf_core_python/core/launcher/connect/component_launcher.py @@ -17,9 +17,6 @@ def make_connection(self) -> Connection: return Connection(self.host, self.port) def connect(self, agent: Agent, _request_id: int) -> None: - self.logger.info( - f"{agent.__class__.__name__} trying to connect to {self.host}:{self.port} request_id: {_request_id}" - ) connection = self.make_connection() try: connection.connect() diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_center.py b/adf_core_python/core/launcher/connect/connector_ambulance_center.py index 1dca5af..33e736e 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_center.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_center.py @@ -74,5 +74,4 @@ def connect( ) threads[thread] = finish_post_connect_event - self.logger.info("Connected ambulance center (count: %d)" % count) return threads diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index 879f313..44620e8 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -74,5 +74,4 @@ def connect( ) threads[thread] = finish_post_connect_event - self.logger.info("Connected ambulance team (count: %d)" % count) return threads diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/adf_core_python/core/launcher/connect/connector_fire_brigade.py index ac7ecb3..2f5ecf5 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_brigade.py +++ b/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -72,5 +72,4 @@ def connect( ) threads[thread] = finish_post_connect_event - self.logger.info("Connected fire brigade (count: %d)" % count) return threads diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/adf_core_python/core/launcher/connect/connector_fire_station.py index de8dbc6..7efec93 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_station.py +++ b/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -72,5 +72,4 @@ def connect( ) threads[thread] = finish_post_connect_event - self.logger.info("Connected fire station (count: %d)" % count) return threads diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py index d2f45b1..bd29160 100644 --- a/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -72,5 +72,4 @@ def connect( ) threads[thread] = finish_post_connect_event - self.logger.info("Connected police force (count: %d)" % count) return threads diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py index 1c7a7f7..69fd76b 100644 --- a/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -74,5 +74,4 @@ def connect( ) threads[thread] = finish_post_connect_event - self.logger.info("Connected police office (count: %d)" % count) return threads From 9507800447654e83d61371e59f89139b40f09e97 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 6 Dec 2024 02:13:16 +0900 Subject: [PATCH 186/249] fix: Simplify module loading by removing redundant error handling and introduce custom exceptions for agent and server errors --- .../core/agent/module/module_manager.py | 22 ++++--------------- .../launcher/connect/component_launcher.py | 13 +++++++++-- .../core/launcher/connect/connection.py | 17 +++++++++++--- .../launcher/connect/error/agent_error.py | 2 ++ .../launcher/connect/error/server_error.py | 2 ++ 5 files changed, 33 insertions(+), 23 deletions(-) create mode 100644 adf_core_python/core/launcher/connect/error/agent_error.py create mode 100644 adf_core_python/core/launcher/connect/error/server_error.py diff --git a/adf_core_python/core/agent/module/module_manager.py b/adf_core_python/core/agent/module/module_manager.py index 9a6d35d..bb6855e 100644 --- a/adf_core_python/core/agent/module/module_manager.py +++ b/adf_core_python/core/agent/module/module_manager.py @@ -51,10 +51,7 @@ def get_module(self, module_name: str, default_module_name: str) -> AbstractModu ) class_name = default_module_name - try: - module_class: type = self._load_module(class_name) - except (ImportError, AttributeError) as e: - raise RuntimeError(f"Failed to load module {class_name}") from e + module_class: type = self._load_module(class_name) instance = self._modules.get(module_name) if instance is not None: @@ -80,10 +77,7 @@ def get_extend_action( action_name, default_action_name ) - try: - action_class: type = self._load_module(class_name) - except (ImportError, AttributeError) as e: - raise RuntimeError(f"Failed to load action {class_name}") from e + action_class: type = self._load_module(class_name) instance = self._actions.get(action_name) if instance is not None: @@ -109,10 +103,7 @@ def get_channel_subscriber( channel_subscriber_name, default_channel_subscriber_name ) - try: - channel_subscriber_class: type = self._load_module(class_name) - except (ImportError, AttributeError) as e: - raise RuntimeError(f"Failed to load channel subscriber {class_name}") from e + channel_subscriber_class: type = self._load_module(class_name) instance = self._channel_subscribers.get(channel_subscriber_name) if instance is not None: @@ -134,12 +125,7 @@ def get_message_coordinator( message_coordinator_name, default_message_coordinator_name ) - try: - message_coordinator_class: type = self._load_module(class_name) - except (ImportError, AttributeError) as e: - raise RuntimeError( - f"Failed to load message coordinator {class_name}" - ) from e + message_coordinator_class: type = self._load_module(class_name) instance = self._message_coordinators.get(message_coordinator_name) if instance is not None: diff --git a/adf_core_python/core/launcher/connect/component_launcher.py b/adf_core_python/core/launcher/connect/component_launcher.py index 0f308c1..7b60008 100644 --- a/adf_core_python/core/launcher/connect/component_launcher.py +++ b/adf_core_python/core/launcher/connect/component_launcher.py @@ -4,6 +4,8 @@ from adf_core_python.core.agent.agent import Agent from adf_core_python.core.launcher.connect.connection import Connection +from adf_core_python.core.launcher.connect.error.agent_error import AgentError +from adf_core_python.core.launcher.connect.error.server_error import ServerError class ComponentLauncher: @@ -35,11 +37,18 @@ def connect(self, agent: Agent, _request_id: int) -> None: try: connection.parse_message_from_kernel() - except Exception as e: + except AgentError as e: self.logger.exception( - f"Agent threw an exception {e}", + f"Agent error: {e}", exception=str(e), ) + except ServerError as e: + if isinstance(e.__cause__, EOFError): + self.logger.info( + f"Connection closed by server (request_id={_request_id})" + ) + else: + self.logger.exception("Server error", exception=str(e)) def generate_request_id(self) -> int: self.request_id += 1 diff --git a/adf_core_python/core/launcher/connect/connection.py b/adf_core_python/core/launcher/connect/connection.py index 6497231..2935f0d 100644 --- a/adf_core_python/core/launcher/connect/connection.py +++ b/adf_core_python/core/launcher/connect/connection.py @@ -3,6 +3,9 @@ import rcrs_core.connection.rcrs_encoding_utils as rcrs_encoding_utils +from adf_core_python.core.launcher.connect.error.agent_error import AgentError +from adf_core_python.core.launcher.connect.error.server_error import ServerError + class Connection: def __init__(self, host: str, port: int) -> None: @@ -34,12 +37,20 @@ def parse_message_from_kernel(self) -> None: Raises ------ - IOError + ServerError If there is an error reading from the socket + AgentError + If there is an error in the agent calculation """ while True: - msg = rcrs_encoding_utils.read_msg(self.socket) - self.agent_message_received(msg) + try: + msg = rcrs_encoding_utils.read_msg(self.socket) + except Exception as e: + raise ServerError(f"Error reading from socket: {e}") from e + try: + self.agent_message_received(msg) + except Exception as e: + raise AgentError(f"Error agent calculation: {e}") from e def message_received(self, agent_message_received: Callable) -> None: self.agent_message_received = agent_message_received diff --git a/adf_core_python/core/launcher/connect/error/agent_error.py b/adf_core_python/core/launcher/connect/error/agent_error.py new file mode 100644 index 0000000..8725dba --- /dev/null +++ b/adf_core_python/core/launcher/connect/error/agent_error.py @@ -0,0 +1,2 @@ +class AgentError(Exception): + pass diff --git a/adf_core_python/core/launcher/connect/error/server_error.py b/adf_core_python/core/launcher/connect/error/server_error.py new file mode 100644 index 0000000..8c3951c --- /dev/null +++ b/adf_core_python/core/launcher/connect/error/server_error.py @@ -0,0 +1,2 @@ +class ServerError(Exception): + pass From 7a4a475ff146c25f53a07dd3588e36c2bcde034b Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 4 Dec 2024 15:55:19 +0900 Subject: [PATCH 187/249] WIP --- adf_core_python/core/agent/agent.py | 7 +- .../core/agent/precompute/precompute_data.py | 72 ++++++++++++++++++- .../launcher/connect/errors/agent_error.py | 2 + .../launcher/connect/errors/server_error.py | 2 + 4 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 adf_core_python/core/launcher/connect/errors/agent_error.py create mode 100644 adf_core_python/core/launcher/connect/errors/server_error.py diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index 55506e1..fbf6c11 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -112,9 +112,14 @@ def __init__( self.is_precompute = is_precompute if is_precompute: - # PrecomputeData.remove_date(data_storage_name) self.mode = Mode.PRECOMPUTATION + try: + precompute_data = PrecomputeData(data_storage_name) + precompute_data.remove_precompute_data() + except Exception as _: + pass + self._module_config = module_config self._develop_data = develop_data self._precompute_data = PrecomputeData(data_storage_name) diff --git a/adf_core_python/core/agent/precompute/precompute_data.py b/adf_core_python/core/agent/precompute/precompute_data.py index ba5840f..a80ce96 100644 --- a/adf_core_python/core/agent/precompute/precompute_data.py +++ b/adf_core_python/core/agent/precompute/precompute_data.py @@ -1,4 +1,74 @@ -# TODO: Implement the PrecomputeData class +import json +import os + +ENCODE = "utf-8" + + class PrecomputeData: def __init__(self, file_path: str) -> None: + """ + Initialize the PrecomputeData object. + + Parameters + ---------- + file_path : str + The path to the precompute data file. + + Raises + ------ + Exception + """ + self._precompute_data = self.read_json_data() self._file_path = file_path + + def read_json_data(self) -> dict: + """ + Read the precompute data from the file. + + Returns + ------- + dict + The precompute data. + + Raises + ------ + Exception + """ + + with open(self._file_path, "r", encoding=ENCODE) as file: + return json.load(file) + + def write_json_data(self, data: dict) -> None: + """ + Write the precompute data to the file. + + Parameters + ---------- + data : dict + The data to write. + + Raises + ------ + Exception + """ + + with open(self._file_path, "w", encoding=ENCODE) as file: + json.dump(data, file, indent=4) + + def remove_precompute_data(self) -> None: + """ + Remove the precompute data file. + """ + if os.path.exists(self._file_path): + os.remove(self._file_path) + + def get_precompute_data(self) -> dict: + """ + Get the precompute data. + + Returns + ------- + dict + The precompute data. + """ + return self._precompute_data diff --git a/adf_core_python/core/launcher/connect/errors/agent_error.py b/adf_core_python/core/launcher/connect/errors/agent_error.py new file mode 100644 index 0000000..8725dba --- /dev/null +++ b/adf_core_python/core/launcher/connect/errors/agent_error.py @@ -0,0 +1,2 @@ +class AgentError(Exception): + pass diff --git a/adf_core_python/core/launcher/connect/errors/server_error.py b/adf_core_python/core/launcher/connect/errors/server_error.py new file mode 100644 index 0000000..8c3951c --- /dev/null +++ b/adf_core_python/core/launcher/connect/errors/server_error.py @@ -0,0 +1,2 @@ +class ServerError(Exception): + pass From c6e13d0d40c9fa1046855e2ecbd50087165032cb Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 11 Dec 2024 16:07:37 +0900 Subject: [PATCH 188/249] fix: Remove unused AgentError and ServerError classes to streamline error handling --- adf_core_python/core/launcher/connect/errors/agent_error.py | 2 -- adf_core_python/core/launcher/connect/errors/server_error.py | 2 -- 2 files changed, 4 deletions(-) delete mode 100644 adf_core_python/core/launcher/connect/errors/agent_error.py delete mode 100644 adf_core_python/core/launcher/connect/errors/server_error.py diff --git a/adf_core_python/core/launcher/connect/errors/agent_error.py b/adf_core_python/core/launcher/connect/errors/agent_error.py deleted file mode 100644 index 8725dba..0000000 --- a/adf_core_python/core/launcher/connect/errors/agent_error.py +++ /dev/null @@ -1,2 +0,0 @@ -class AgentError(Exception): - pass diff --git a/adf_core_python/core/launcher/connect/errors/server_error.py b/adf_core_python/core/launcher/connect/errors/server_error.py deleted file mode 100644 index 8c3951c..0000000 --- a/adf_core_python/core/launcher/connect/errors/server_error.py +++ /dev/null @@ -1,2 +0,0 @@ -class ServerError(Exception): - pass From a0044b4f8c386f483cf44a62e8bb608a23cfc2b3 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 11 Dec 2024 17:24:31 +0900 Subject: [PATCH 189/249] feat: Add precompute configuration and implement precompute method in agent classes --- .../module/complex/sample_road_detector.py | 17 ++++--- adf_core_python/core/agent/agent.py | 14 +++--- adf_core_python/core/agent/office/office.py | 20 +++++++- adf_core_python/core/agent/platoon/platoon.py | 20 +++++++- .../core/agent/precompute/precompute_data.py | 33 ++++++------- .../core/component/tactics/tactics_center.py | 13 ++++++ adf_core_python/core/launcher/config_key.py | 1 + .../connect/connector_ambulance_center.py | 6 ++- .../connect/connector_ambulance_team.py | 6 ++- .../connect/connector_fire_brigade.py | 4 +- .../connect/connector_fire_station.py | 4 +- .../connect/connector_police_force.py | 4 +- .../connect/connector_police_office.py | 4 +- .../action/default_extend_action_clear.py | 20 +++----- .../action/default_extend_action_move.py | 20 +++----- .../action/default_extend_action_rescue.py | 20 +++----- .../action/default_extend_action_transport.py | 20 +++----- .../module/algorithm/k_means_clustering.py | 30 ++++++++++++ .../module/complex/default_road_detector.py | 17 ++++--- .../default_tactics_ambulance_center.py | 28 +++++++---- .../tactics/default_tactics_ambulance_team.py | 46 +++++++++---------- .../tactics/default_tactics_fire_brigade.py | 46 +++++++++---------- .../tactics/default_tactics_fire_station.py | 28 +++++++---- .../tactics/default_tactics_police_force.py | 46 +++++++++---------- .../tactics/default_tactics_police_office.py | 28 +++++++---- config/launcher.yaml | 2 + 26 files changed, 292 insertions(+), 205 deletions(-) diff --git a/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py b/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py index 3dd95d8..fcb29a9 100644 --- a/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py @@ -31,15 +31,14 @@ def __init__( super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - match scenario_info.get_mode(): - case Mode.NON_PRECOMPUTE: - self._path_planning: PathPlanning = cast( - PathPlanning, - module_manager.get_module( - "SampleRoadDetector.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "SampleRoadDetector.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) self.register_sub_module(self._path_planning) self._result = None diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index fbf6c11..7cfef70 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -115,14 +115,12 @@ def __init__( self.mode = Mode.PRECOMPUTATION try: - precompute_data = PrecomputeData(data_storage_name) - precompute_data.remove_precompute_data() + self._precompute_data = PrecomputeData(data_storage_name) except Exception as _: pass self._module_config = module_config self._develop_data = develop_data - self._precompute_data = PrecomputeData(data_storage_name) self._message_manager: MessageManager = MessageManager() self._communication_module: CommunicationModule = StandardCommunicationModule() @@ -136,11 +134,10 @@ def post_connect(self) -> None: if self.is_precompute: self._mode = Mode.PRECOMPUTATION else: - # if self._precompute_data.is_ready(): - # self._mode = Mode.PRECOMPUTED - # else: - # self._mode = Mode.NON_PRECOMPUTE - self._mode = Mode.NON_PRECOMPUTE + if self._precompute_data.is_available(): + self._mode = Mode.PRECOMPUTED + else: + self._mode = Mode.NON_PRECOMPUTE self.config.set_value(ConfigKey.KEY_DEBUG_FLAG, self.is_debug) self.config.set_value( @@ -157,6 +154,7 @@ def post_connect(self) -> None: self._agent_info, ) + self.logger.info("Agent running in %s mode", self._mode) self.logger.debug(f"agent_config: {self.config}") def update_step_info( diff --git a/adf_core_python/core/agent/office/office.py b/adf_core_python/core/agent/office/office.py index 09e97bd..98911f8 100644 --- a/adf_core_python/core/agent/office/office.py +++ b/adf_core_python/core/agent/office/office.py @@ -82,9 +82,25 @@ def post_connect(self) -> None: match self._scenario_info.get_mode(): case Mode.PRECOMPUTATION: - pass + self._tactics_center.precompute( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + self._message_manager, + self._develop_data, + ) case Mode.PRECOMPUTED: - pass + self._tactics_center.resume( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + self._message_manager, + self._develop_data, + ) case Mode.NON_PRECOMPUTE: self._tactics_center.prepare( self._agent_info, diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py index c5ff9f8..8a66099 100644 --- a/adf_core_python/core/agent/platoon/platoon.py +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -83,9 +83,25 @@ def post_connect(self) -> None: match self._scenario_info.get_mode(): case Mode.PRECOMPUTATION: - pass + self._tactics_agent.precompute( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + self._message_manager, + self._develop_data, + ) case Mode.PRECOMPUTED: - pass + self._tactics_agent.resume( + self._agent_info, + self._world_info, + self._scenario_info, + self._module_manager, + self._precompute_data, + self._message_manager, + self._develop_data, + ) case Mode.NON_PRECOMPUTE: self._tactics_agent.prepare( self._agent_info, diff --git a/adf_core_python/core/agent/precompute/precompute_data.py b/adf_core_python/core/agent/precompute/precompute_data.py index a80ce96..41b2029 100644 --- a/adf_core_python/core/agent/precompute/precompute_data.py +++ b/adf_core_python/core/agent/precompute/precompute_data.py @@ -5,23 +5,22 @@ class PrecomputeData: - def __init__(self, file_path: str) -> None: + def __init__(self, dir_path: str) -> None: """ Initialize the PrecomputeData object. Parameters ---------- - file_path : str - The path to the precompute data file. + dir_path : str + The directory path to save the precompute data. Raises ------ Exception """ - self._precompute_data = self.read_json_data() - self._file_path = file_path + self._dir_path = dir_path - def read_json_data(self) -> dict: + def read_json_data(self, module_name: str) -> dict: """ Read the precompute data from the file. @@ -35,10 +34,10 @@ def read_json_data(self) -> dict: Exception """ - with open(self._file_path, "r", encoding=ENCODE) as file: + with open(f"{self._dir_path}/{module_name}.json", "r", encoding=ENCODE) as file: return json.load(file) - def write_json_data(self, data: dict) -> None: + def write_json_data(self, data: dict, module_name: str) -> None: """ Write the precompute data to the file. @@ -51,24 +50,26 @@ def write_json_data(self, data: dict) -> None: ------ Exception """ + if not os.path.exists(self._dir_path): + os.makedirs(self._dir_path) - with open(self._file_path, "w", encoding=ENCODE) as file: + with open(f"{self._dir_path}/{module_name}.json", "w", encoding=ENCODE) as file: json.dump(data, file, indent=4) def remove_precompute_data(self) -> None: """ Remove the precompute data file. """ - if os.path.exists(self._file_path): - os.remove(self._file_path) + if os.path.exists(self._dir_path): + os.remove(self._dir_path) - def get_precompute_data(self) -> dict: + def is_available(self) -> bool: """ - Get the precompute data. + Check if the precompute data is available. Returns ------- - dict - The precompute data. + bool + True if the precompute data is available, False otherwise. """ - return self._precompute_data + return os.path.exists(self._dir_path) diff --git a/adf_core_python/core/component/tactics/tactics_center.py b/adf_core_python/core/component/tactics/tactics_center.py index ea95edf..2469cc6 100644 --- a/adf_core_python/core/component/tactics/tactics_center.py +++ b/adf_core_python/core/component/tactics/tactics_center.py @@ -46,6 +46,19 @@ def resume( ) -> None: raise NotImplementedError + @abstractmethod + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + raise NotImplementedError + @abstractmethod def prepare( self, diff --git a/adf_core_python/core/launcher/config_key.py b/adf_core_python/core/launcher/config_key.py index d32afb2..591876e 100644 --- a/adf_core_python/core/launcher/config_key.py +++ b/adf_core_python/core/launcher/config_key.py @@ -23,3 +23,4 @@ class ConfigKey: KEY_FIRE_STATION_COUNT: Final[str] = "adf.team.office.fire.count" KEY_POLICE_OFFICE_COUNT: Final[str] = "adf.team.office.police.count" # adf-core-python + KEY_PRECOMPUTE_DATA_DIR: Final[str] = "adf.agent.precompute.dir_name" diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_center.py b/adf_core_python/core/launcher/connect/connector_ambulance_center.py index 33e736e..132a985 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_center.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_center.py @@ -53,8 +53,10 @@ def connect( ), ) - request_id: int = component_launcher.generate_request_id() + precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/ambulance_center" + finish_post_connect_event = threading.Event() + request_id: int = component_launcher.generate_request_id() thread = threading.Thread( target=component_launcher.connect, args=( @@ -63,7 +65,7 @@ def connect( "ambulance_center", config.get_value(ConfigKey.KEY_PRECOMPUTE, False), config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - "test", + precompute_data_dir, module_config, develop_data, finish_post_connect_event, diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index 44620e8..d2aa8c0 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -53,8 +53,10 @@ def connect( ), ) - request_id: int = component_launcher.generate_request_id() + precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/ambulance_team" + finish_post_connect_event = threading.Event() + request_id: int = component_launcher.generate_request_id() thread = threading.Thread( target=component_launcher.connect, args=( @@ -63,7 +65,7 @@ def connect( "ambulance_team", config.get_value(ConfigKey.KEY_PRECOMPUTE, False), config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - "test", + precompute_data_dir, module_config, develop_data, finish_post_connect_event, diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/adf_core_python/core/launcher/connect/connector_fire_brigade.py index 2f5ecf5..747f503 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_brigade.py +++ b/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -51,6 +51,8 @@ def connect( ), ) + precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/fire_brigade" + request_id: int = component_launcher.generate_request_id() finish_post_connect_event = threading.Event() thread = threading.Thread( @@ -61,7 +63,7 @@ def connect( "fire_brigade", config.get_value(ConfigKey.KEY_PRECOMPUTE, False), config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - "test", + precompute_data_dir, module_config, develop_data, finish_post_connect_event, diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/adf_core_python/core/launcher/connect/connector_fire_station.py index 7efec93..8f099ee 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_station.py +++ b/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -51,6 +51,8 @@ def connect( ), ) + precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/fire_station" + request_id: int = component_launcher.generate_request_id() finish_post_connect_event = threading.Event() thread = threading.Thread( @@ -61,7 +63,7 @@ def connect( "fire_station", config.get_value(ConfigKey.KEY_PRECOMPUTE, False), config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - "test", + precompute_data_dir, module_config, develop_data, finish_post_connect_event, diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py index bd29160..e17fe76 100644 --- a/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -51,6 +51,8 @@ def connect( ), ) + precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/police_force" + request_id: int = component_launcher.generate_request_id() finish_post_connect_event = threading.Event() thread = threading.Thread( @@ -61,7 +63,7 @@ def connect( "police_force", config.get_value(ConfigKey.KEY_PRECOMPUTE, False), config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - "test", + precompute_data_dir, module_config, develop_data, finish_post_connect_event, diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py index 69fd76b..52c3aac 100644 --- a/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -53,6 +53,8 @@ def connect( ), ) + precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/police_office" + request_id: int = component_launcher.generate_request_id() finish_post_connect_event = threading.Event() thread = threading.Thread( @@ -63,7 +65,7 @@ def connect( "police_office", config.get_value(ConfigKey.KEY_PRECOMPUTE, False), config.get_value(ConfigKey.KEY_DEBUG_FLAG, False), - "test", + precompute_data_dir, module_config, develop_data, finish_post_connect_event, diff --git a/adf_core_python/implement/action/default_extend_action_clear.py b/adf_core_python/implement/action/default_extend_action_clear.py index 39aa859..53c2ebc 100644 --- a/adf_core_python/implement/action/default_extend_action_clear.py +++ b/adf_core_python/implement/action/default_extend_action_clear.py @@ -63,19 +63,13 @@ def __init__( self._old_clear_y = 0 self.count = 0 - match self.scenario_info.get_mode(): - case Mode.NON_PRECOMPUTE: - self._path_planning = cast( - PathPlanning, - self.module_manager.get_module( - "DefaultExtendActionClear.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - case Mode.PRECOMPUTATION: - pass - case Mode.PRECOMPUTED: - pass + self._path_planning = cast( + PathPlanning, + self.module_manager.get_module( + "DefaultExtendActionClear.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: super().precompute(precompute_data) diff --git a/adf_core_python/implement/action/default_extend_action_move.py b/adf_core_python/implement/action/default_extend_action_move.py index 6614ffd..94f9ade 100644 --- a/adf_core_python/implement/action/default_extend_action_move.py +++ b/adf_core_python/implement/action/default_extend_action_move.py @@ -33,19 +33,13 @@ def __init__( self._target_entity_id: Optional[EntityID] = None self._threshold_to_rest: int = develop_data.get_value("threshold_to_rest", 100) - match self.scenario_info.get_mode(): - case Mode.NON_PRECOMPUTE: - self._path_planning: PathPlanning = cast( - PathPlanning, - self.module_manager.get_module( - "DefaultExtendActionMove.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - case Mode.PRECOMPUTATION: - pass - case Mode.PRECOMPUTED: - pass + self._path_planning: PathPlanning = cast( + PathPlanning, + self.module_manager.get_module( + "DefaultExtendActionMove.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: super().precompute(precompute_data) diff --git a/adf_core_python/implement/action/default_extend_action_rescue.py b/adf_core_python/implement/action/default_extend_action_rescue.py index 640d088..2291136 100644 --- a/adf_core_python/implement/action/default_extend_action_rescue.py +++ b/adf_core_python/implement/action/default_extend_action_rescue.py @@ -42,19 +42,13 @@ def __init__( "adf_core_python.implement.action.DefaultExtendActionRescue.rest", 100 ) - match self.scenario_info.get_mode(): - case Mode.NON_PRECOMPUTE: - self._path_planning = cast( - PathPlanning, - self.module_manager.get_module( - "DefaultExtendActionRescue.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - case Mode.PRECOMPUTATION: - pass - case Mode.PRECOMPUTED: - pass + self._path_planning = cast( + PathPlanning, + self.module_manager.get_module( + "DefaultExtendActionRescue.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: super().precompute(precompute_data) diff --git a/adf_core_python/implement/action/default_extend_action_transport.py b/adf_core_python/implement/action/default_extend_action_transport.py index 1ee2fd4..3475192 100644 --- a/adf_core_python/implement/action/default_extend_action_transport.py +++ b/adf_core_python/implement/action/default_extend_action_transport.py @@ -44,19 +44,13 @@ def __init__( self.agent_info, ) - match self.scenario_info.get_mode(): - case Mode.NON_PRECOMPUTE: - self._path_planning: PathPlanning = cast( - PathPlanning, - self.module_manager.get_module( - "DefaultExtendActionMove.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) - case Mode.PRECOMPUTATION: - pass - case Mode.PRECOMPUTED: - pass + self._path_planning: PathPlanning = cast( + PathPlanning, + self.module_manager.get_module( + "DefaultExtendActionMove.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) def precompute(self, precompute_data: PrecomputeData) -> ExtendAction: super().precompute(precompute_data) diff --git a/adf_core_python/implement/module/algorithm/k_means_clustering.py b/adf_core_python/implement/module/algorithm/k_means_clustering.py index b40f7c4..8598abf 100644 --- a/adf_core_python/implement/module/algorithm/k_means_clustering.py +++ b/adf_core_python/implement/module/algorithm/k_means_clustering.py @@ -17,6 +17,7 @@ from adf_core_python.core.agent.info.scenario_info import ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.module.algorithm.clustering import Clustering @@ -86,6 +87,35 @@ def __init__( def calculate(self) -> Clustering: return self + def precompute(self, precompute_data: PrecomputeData) -> Clustering: + cluster_entities = self.create_cluster(self._cluster_number, self.entities) + precompute_data.write_json_data( + { + "cluster_entities": [ + [entity.get_id().get_value() for entity in cluster] + for cluster in cluster_entities + ] + }, + self.__class__.__name__, + ) + return self + + def resume(self, precompute_data): + data = precompute_data.read_json_data(self.__class__.__name__) + self.cluster_entities = [ + [ + entity + for entity_id in cluster + if (entity := self._world_info.get_entity(EntityID(entity_id))) + is not None + ] + for cluster in data["cluster_entities"] + ] + self._logger.info( + f"Resume {self.__class__.__name__} with {len(self.cluster_entities)} clusters" + ) + return self + def get_cluster_number(self) -> int: return self._cluster_number diff --git a/adf_core_python/implement/module/complex/default_road_detector.py b/adf_core_python/implement/module/complex/default_road_detector.py index 5e598b9..03562a5 100644 --- a/adf_core_python/implement/module/complex/default_road_detector.py +++ b/adf_core_python/implement/module/complex/default_road_detector.py @@ -31,15 +31,14 @@ def __init__( super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - match scenario_info.get_mode(): - case Mode.NON_PRECOMPUTE: - self._path_planning: PathPlanning = cast( - PathPlanning, - module_manager.get_module( - "DefaultRoadDetector.PathPlanning", - "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", - ), - ) + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultRoadDetector.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) self.register_sub_module(self._path_planning) self._result = None diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py b/adf_core_python/implement/tactics/default_tactics_ambulance_center.py index 909f448..65f1f54 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_center.py @@ -26,17 +26,27 @@ def initialize( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - match scenario_info.get_mode(): - case Mode.NON_PRECOMPUTE: - self._allocator: TargetAllocator = cast( - TargetAllocator, - module_manager.get_module( - "DefaultTacticsAmbulanceCenter.TargetAllocator", - "adf_core_python.implement.module.complex.default_ambulance_target_allocator.DefaultAmbulanceTargetAllocator", - ), - ) + self._allocator: TargetAllocator = cast( + TargetAllocator, + module_manager.get_module( + "DefaultTacticsAmbulanceCenter.TargetAllocator", + "adf_core_python.implement.module.complex.default_ambulance_target_allocator.DefaultAmbulanceTargetAllocator", + ), + ) self.register_module(self._allocator) + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_precompute(precompute_data) + def resume( self, agent_info: AgentInfo, diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 888caf2..c1016d5 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -39,30 +39,28 @@ def initialize( message_manager, develop_data, ) - match scenario_info.get_mode(): - case Mode.NON_PRECOMPUTE: - self._search: Search = cast( - Search, - module_manager.get_module( - "DefaultTacticsAmbulanceTeam.Search", - "adf_core_python.core.component.module.complex.search.Search", - ), - ) - self._human_detector: HumanDetector = cast( - HumanDetector, - module_manager.get_module( - "DefaultTacticsAmbulanceTeam.HumanDetector", - "adf_core_python.core.component.module.complex.human_detector.HumanDetector", - ), - ) - self._action_transport = module_manager.get_extend_action( - "DefaultTacticsAmbulanceTeam.ExtendActionTransport", - "adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport", - ) - self._action_ext_move = module_manager.get_extend_action( - "DefaultTacticsAmbulanceTeam.ExtendActionMove", - "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", - ) + self._search: Search = cast( + Search, + module_manager.get_module( + "DefaultTacticsAmbulanceTeam.Search", + "adf_core_python.core.component.module.complex.search.Search", + ), + ) + self._human_detector: HumanDetector = cast( + HumanDetector, + module_manager.get_module( + "DefaultTacticsAmbulanceTeam.HumanDetector", + "adf_core_python.core.component.module.complex.human_detector.HumanDetector", + ), + ) + self._action_transport = module_manager.get_extend_action( + "DefaultTacticsAmbulanceTeam.ExtendActionTransport", + "adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport", + ) + self._action_ext_move = module_manager.get_extend_action( + "DefaultTacticsAmbulanceTeam.ExtendActionMove", + "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", + ) self.register_module(self._search) self.register_module(self._human_detector) self.register_action(self._action_transport) diff --git a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py index 4247b48..3b4c8e7 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py @@ -40,30 +40,28 @@ def initialize( develop_data, ) - match scenario_info.get_mode(): - case Mode.NON_PRECOMPUTE: - self._search: Search = cast( - Search, - module_manager.get_module( - "DefaultTacticsFireBrigade.Search", - "adf_core_python.core.component.module.complex.search.Search", - ), - ) - self._human_detector: HumanDetector = cast( - HumanDetector, - module_manager.get_module( - "DefaultTacticsFireBrigade.HumanDetector", - "adf_core_python.core.component.module.complex.human_detector.HumanDetector", - ), - ) - self._action_rescue = module_manager.get_extend_action( - "DefaultTacticsFireBrigade.ExtendActionRescue", - "adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue", - ) - self._action_ext_move = module_manager.get_extend_action( - "DefaultTacticsAmbulanceTeam.ExtendActionMove", - "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", - ) + self._search: Search = cast( + Search, + module_manager.get_module( + "DefaultTacticsFireBrigade.Search", + "adf_core_python.core.component.module.complex.search.Search", + ), + ) + self._human_detector: HumanDetector = cast( + HumanDetector, + module_manager.get_module( + "DefaultTacticsFireBrigade.HumanDetector", + "adf_core_python.core.component.module.complex.human_detector.HumanDetector", + ), + ) + self._action_rescue = module_manager.get_extend_action( + "DefaultTacticsFireBrigade.ExtendActionRescue", + "adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue", + ) + self._action_ext_move = module_manager.get_extend_action( + "DefaultTacticsAmbulanceTeam.ExtendActionMove", + "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", + ) self.register_module(self._search) self.register_module(self._human_detector) self.register_action(self._action_rescue) diff --git a/adf_core_python/implement/tactics/default_tactics_fire_station.py b/adf_core_python/implement/tactics/default_tactics_fire_station.py index 63693d7..491e837 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_station.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_station.py @@ -26,15 +26,13 @@ def initialize( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - match scenario_info.get_mode(): - case Mode.NON_PRECOMPUTE: - self._allocator: TargetAllocator = cast( - TargetAllocator, - module_manager.get_module( - "DefaultTacticsFireStation.TargetAllocator", - "adf_core_python.implement.module.complex.default_fire_target_allocator.DefaultFireTargetAllocator", - ), - ) + self._allocator: TargetAllocator = cast( + TargetAllocator, + module_manager.get_module( + "DefaultTacticsFireStation.TargetAllocator", + "adf_core_python.implement.module.complex.default_fire_target_allocator.DefaultFireTargetAllocator", + ), + ) self.register_module(self._allocator) def resume( @@ -49,6 +47,18 @@ def resume( ) -> None: self.module_resume(precompute_data) + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_precompute(precompute_data) + def prepare( self, agent_info: AgentInfo, diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py index d605cba..ed8c429 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -43,30 +43,28 @@ def initialize( # scenario_info.get_value("clear.repair.distance", "null") # ) - match scenario_info.get_mode(): - case Mode.NON_PRECOMPUTE: - self._search: Search = cast( - Search, - module_manager.get_module( - "DefaultTacticsPoliceForce.Search", - "adf_core_python.core.component.module.complex.search.Search", - ), - ) - self._road_detector: RoadDetector = cast( - RoadDetector, - module_manager.get_module( - "DefaultTacticsPoliceForce.RoadDetector", - "adf_core_python.core.component.module.complex.road_detector.RoadDetector", - ), - ) - self._action_ext_clear = module_manager.get_extend_action( - "DefaultTacticsPoliceForce.ExtendActionClear", - "adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear", - ) - self._action_ext_move = module_manager.get_extend_action( - "DefaultTacticsPoliceForce.ExtendActionMove", - "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", - ) + self._search: Search = cast( + Search, + module_manager.get_module( + "DefaultTacticsPoliceForce.Search", + "adf_core_python.core.component.module.complex.search.Search", + ), + ) + self._road_detector: RoadDetector = cast( + RoadDetector, + module_manager.get_module( + "DefaultTacticsPoliceForce.RoadDetector", + "adf_core_python.core.component.module.complex.road_detector.RoadDetector", + ), + ) + self._action_ext_clear = module_manager.get_extend_action( + "DefaultTacticsPoliceForce.ExtendActionClear", + "adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear", + ) + self._action_ext_move = module_manager.get_extend_action( + "DefaultTacticsPoliceForce.ExtendActionMove", + "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", + ) self.register_module(self._search) self.register_module(self._road_detector) self.register_action(self._action_ext_clear) diff --git a/adf_core_python/implement/tactics/default_tactics_police_office.py b/adf_core_python/implement/tactics/default_tactics_police_office.py index f2d54c9..05be972 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_office.py +++ b/adf_core_python/implement/tactics/default_tactics_police_office.py @@ -26,15 +26,13 @@ def initialize( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - match scenario_info.get_mode(): - case Mode.NON_PRECOMPUTE: - self._allocator: TargetAllocator = cast( - TargetAllocator, - module_manager.get_module( - "DefaultTacticsPoliceOffice.TargetAllocator", - "adf_core_python.implement.module.complex.default_police_target_allocator.DefaultPoliceTargetAllocator", - ), - ) + self._allocator: TargetAllocator = cast( + TargetAllocator, + module_manager.get_module( + "DefaultTacticsPoliceOffice.TargetAllocator", + "adf_core_python.implement.module.complex.default_police_target_allocator.DefaultPoliceTargetAllocator", + ), + ) self.register_module(self._allocator) def resume( @@ -49,6 +47,18 @@ def resume( ) -> None: self.module_resume(precompute_data) + def precompute( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + precompute_data: PrecomputeData, + message_manager: MessageManager, + develop_data: DevelopData, + ) -> None: + self.module_precompute(precompute_data) + def prepare( self, agent_info: AgentInfo, diff --git a/config/launcher.yaml b/config/launcher.yaml index 1b6bdfd..7702d17 100644 --- a/config/launcher.yaml +++ b/config/launcher.yaml @@ -13,6 +13,8 @@ adf: agent: moduleconfig: filename: config/module.yaml + precompute: + dir_name: precompute develop: flag: true From 2df08659bd0a4c78cdb02b5dc974b9c0dd8f6a7d Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 11 Dec 2024 17:26:19 +0900 Subject: [PATCH 190/249] fix: Remove unused Mode import from scenario_info in multiple files --- .../src/team_name/module/complex/sample_road_detector.py | 2 +- adf_core_python/implement/action/default_extend_action_clear.py | 2 +- adf_core_python/implement/action/default_extend_action_move.py | 2 +- .../implement/action/default_extend_action_rescue.py | 1 - .../implement/action/default_extend_action_transport.py | 2 +- .../implement/module/algorithm/k_means_clustering.py | 2 +- .../implement/module/complex/default_road_detector.py | 2 +- .../implement/tactics/default_tactics_ambulance_center.py | 2 +- .../implement/tactics/default_tactics_ambulance_team.py | 2 +- .../implement/tactics/default_tactics_fire_brigade.py | 2 +- .../implement/tactics/default_tactics_fire_station.py | 2 +- .../implement/tactics/default_tactics_police_force.py | 2 +- .../implement/tactics/default_tactics_police_office.py | 2 +- 13 files changed, 12 insertions(+), 13 deletions(-) diff --git a/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py b/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py index fcb29a9..2f554db 100644 --- a/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py +++ b/adf_core_python/cli/template/src/team_name/module/complex/sample_road_detector.py @@ -9,7 +9,7 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData diff --git a/adf_core_python/implement/action/default_extend_action_clear.py b/adf_core_python/implement/action/default_extend_action_clear.py index 53c2ebc..d23c8d1 100644 --- a/adf_core_python/implement/action/default_extend_action_clear.py +++ b/adf_core_python/implement/action/default_extend_action_clear.py @@ -22,7 +22,7 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData diff --git a/adf_core_python/implement/action/default_extend_action_move.py b/adf_core_python/implement/action/default_extend_action_move.py index 94f9ade..e4936a4 100644 --- a/adf_core_python/implement/action/default_extend_action_move.py +++ b/adf_core_python/implement/action/default_extend_action_move.py @@ -10,7 +10,7 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData diff --git a/adf_core_python/implement/action/default_extend_action_rescue.py b/adf_core_python/implement/action/default_extend_action_rescue.py index 2291136..e76e96a 100644 --- a/adf_core_python/implement/action/default_extend_action_rescue.py +++ b/adf_core_python/implement/action/default_extend_action_rescue.py @@ -13,7 +13,6 @@ from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo from adf_core_python.core.agent.info.scenario_info import ( - Mode, ScenarioInfo, ScenarioInfoKeys, ) diff --git a/adf_core_python/implement/action/default_extend_action_transport.py b/adf_core_python/implement/action/default_extend_action_transport.py index 3475192..8e6c71a 100644 --- a/adf_core_python/implement/action/default_extend_action_transport.py +++ b/adf_core_python/implement/action/default_extend_action_transport.py @@ -15,7 +15,7 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData diff --git a/adf_core_python/implement/module/algorithm/k_means_clustering.py b/adf_core_python/implement/module/algorithm/k_means_clustering.py index 8598abf..d45a64c 100644 --- a/adf_core_python/implement/module/algorithm/k_means_clustering.py +++ b/adf_core_python/implement/module/algorithm/k_means_clustering.py @@ -100,7 +100,7 @@ def precompute(self, precompute_data: PrecomputeData) -> Clustering: ) return self - def resume(self, precompute_data): + def resume(self, precompute_data: PrecomputeData) -> Clustering: data = precompute_data.read_json_data(self.__class__.__name__) self.cluster_entities = [ [ diff --git a/adf_core_python/implement/module/complex/default_road_detector.py b/adf_core_python/implement/module/complex/default_road_detector.py index 03562a5..035cc61 100644 --- a/adf_core_python/implement/module/complex/default_road_detector.py +++ b/adf_core_python/implement/module/complex/default_road_detector.py @@ -9,7 +9,7 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py b/adf_core_python/implement/tactics/default_tactics_ambulance_center.py index 65f1f54..d1dd248 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_center.py @@ -3,7 +3,7 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index c1016d5..83d0cdf 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -7,7 +7,7 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData diff --git a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py index 3b4c8e7..d0f8512 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py @@ -7,7 +7,7 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData diff --git a/adf_core_python/implement/tactics/default_tactics_fire_station.py b/adf_core_python/implement/tactics/default_tactics_fire_station.py index 491e837..6b3dcb2 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_station.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_station.py @@ -3,7 +3,7 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py index ed8c429..63836a3 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -7,7 +7,7 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData diff --git a/adf_core_python/implement/tactics/default_tactics_police_office.py b/adf_core_python/implement/tactics/default_tactics_police_office.py index 05be972..554fe53 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_office.py +++ b/adf_core_python/implement/tactics/default_tactics_police_office.py @@ -3,7 +3,7 @@ from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import Mode, ScenarioInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData From b674043cd397b870985ae7a60d8d7938210d190e Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 12 Dec 2024 13:48:58 +0900 Subject: [PATCH 191/249] fix: Remove precompute method from agent classes and update logging in connection handling --- .gitignore | 1 + adf_core_python/core/agent/agent.py | 13 ++++--------- adf_core_python/core/agent/office/office_fire.py | 3 --- adf_core_python/core/agent/office/office_police.py | 3 --- .../core/agent/platoon/platoon_ambulance.py | 3 --- adf_core_python/core/agent/platoon/platoon_fire.py | 3 --- .../core/agent/platoon/platoon_police.py | 3 --- 7 files changed, 5 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 7e7baef..6119e70 100644 --- a/.gitignore +++ b/.gitignore @@ -172,3 +172,4 @@ cython_debug/ # ADF agent.log* +precompute diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index 7cfef70..d371cac 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -154,7 +154,6 @@ def post_connect(self) -> None: self._agent_info, ) - self.logger.info("Agent running in %s mode", self._mode) self.logger.debug(f"agent_config: {self.config}") def update_step_info( @@ -213,10 +212,6 @@ def update_step_info( def think(self) -> None: pass - @abstractmethod - def precompute(self) -> None: - pass - @abstractmethod def get_requested_entities(self) -> list[EntityURN]: pass @@ -269,12 +264,12 @@ def handle_connect_ok(self, msg: Any) -> None: self.send_acknowledge(msg.request_id) self.post_connect() self.logger.info( - f"Connected to kernel: {self.__class__.__qualname__} (request_id: {msg.request_id})", + f"Connected to kernel: {self.__class__.__qualname__} (request_id: {msg.request_id}, agent_id: {self.agent_id}, mode: {self.mode})", request_id=msg.request_id, ) - if self.precompute_flag: - print("self.precompute_flag: ", self.precompute_flag) - self.precompute() + if self.is_precompute: + self.logger.info("Precompute finished") + exit(0) self.finish_post_connect_event.set() diff --git a/adf_core_python/core/agent/office/office_fire.py b/adf_core_python/core/agent/office/office_fire.py index dcab2bd..16b93cd 100644 --- a/adf_core_python/core/agent/office/office_fire.py +++ b/adf_core_python/core/agent/office/office_fire.py @@ -31,8 +31,5 @@ def __init__( finish_post_connect_event, ) - def precompute(self) -> None: - pass - def get_requested_entities(self) -> list[EntityURN]: return [EntityURN.FIRE_STATION] diff --git a/adf_core_python/core/agent/office/office_police.py b/adf_core_python/core/agent/office/office_police.py index b4c9fc4..4b2ca8b 100644 --- a/adf_core_python/core/agent/office/office_police.py +++ b/adf_core_python/core/agent/office/office_police.py @@ -31,8 +31,5 @@ def __init__( finish_post_connect_event, ) - def precompute(self) -> None: - pass - def get_requested_entities(self) -> list[EntityURN]: return [EntityURN.POLICE_OFFICE] diff --git a/adf_core_python/core/agent/platoon/platoon_ambulance.py b/adf_core_python/core/agent/platoon/platoon_ambulance.py index 8a4d976..169d208 100644 --- a/adf_core_python/core/agent/platoon/platoon_ambulance.py +++ b/adf_core_python/core/agent/platoon/platoon_ambulance.py @@ -31,8 +31,5 @@ def __init__( finish_post_connect_event, ) - def precompute(self) -> None: - pass - def get_requested_entities(self) -> list[EntityURN]: return [EntityURN.AMBULANCE_TEAM] diff --git a/adf_core_python/core/agent/platoon/platoon_fire.py b/adf_core_python/core/agent/platoon/platoon_fire.py index 01d75ba..3071eef 100644 --- a/adf_core_python/core/agent/platoon/platoon_fire.py +++ b/adf_core_python/core/agent/platoon/platoon_fire.py @@ -31,8 +31,5 @@ def __init__( finish_post_connect_event, ) - def precompute(self) -> None: - pass - def get_requested_entities(self) -> list[EntityURN]: return [EntityURN.FIRE_BRIGADE] diff --git a/adf_core_python/core/agent/platoon/platoon_police.py b/adf_core_python/core/agent/platoon/platoon_police.py index 569159e..6df9440 100644 --- a/adf_core_python/core/agent/platoon/platoon_police.py +++ b/adf_core_python/core/agent/platoon/platoon_police.py @@ -31,8 +31,5 @@ def __init__( finish_post_connect_event, ) - def precompute(self) -> None: - pass - def get_requested_entities(self) -> list[EntityURN]: return [EntityURN.POLICE_FORCE] From 93954d7c235b3a24e73a779d6277c114ca7b95e7 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 12 Dec 2024 17:03:35 +0900 Subject: [PATCH 192/249] fix: Update mode attribute to private in Agent class and adjust logging accordingly --- adf_core_python/core/agent/agent.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index d371cac..bdf45c6 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -112,7 +112,7 @@ def __init__( self.is_precompute = is_precompute if is_precompute: - self.mode = Mode.PRECOMPUTATION + self._mode = Mode.PRECOMPUTATION try: self._precompute_data = PrecomputeData(data_storage_name) @@ -264,7 +264,7 @@ def handle_connect_ok(self, msg: Any) -> None: self.send_acknowledge(msg.request_id) self.post_connect() self.logger.info( - f"Connected to kernel: {self.__class__.__qualname__} (request_id: {msg.request_id}, agent_id: {self.agent_id}, mode: {self.mode})", + f"Connected to kernel: {self.__class__.__qualname__} (request_id: {msg.request_id}, agent_id: {self.agent_id}, mode: {self._mode})", request_id=msg.request_id, ) if self.is_precompute: From b689fa202603f1b9e9718713384a646101348ba0 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 12 Dec 2024 17:30:41 +0900 Subject: [PATCH 193/249] fix: Remove unnecessary logging in KMeansClustering and update argument parsing for precompute and debug flags --- .../implement/module/algorithm/k_means_clustering.py | 3 --- adf_core_python/launcher.py | 5 ++--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/adf_core_python/implement/module/algorithm/k_means_clustering.py b/adf_core_python/implement/module/algorithm/k_means_clustering.py index d45a64c..57228b7 100644 --- a/adf_core_python/implement/module/algorithm/k_means_clustering.py +++ b/adf_core_python/implement/module/algorithm/k_means_clustering.py @@ -111,9 +111,6 @@ def resume(self, precompute_data: PrecomputeData) -> Clustering: ] for cluster in data["cluster_entities"] ] - self._logger.info( - f"Resume {self.__class__.__name__} with {len(self.cluster_entities)} clusters" - ) return self def get_cluster_number(self) -> int: diff --git a/adf_core_python/launcher.py b/adf_core_python/launcher.py index 310ff9d..bd2025d 100644 --- a/adf_core_python/launcher.py +++ b/adf_core_python/launcher.py @@ -74,11 +74,10 @@ def __init__( ) parser.add_argument( "--precompute", - type=bool, + action="store_true", help="precompute flag", - metavar="", ) - parser.add_argument("--debug", type=bool, help="debug flag", metavar="") + parser.add_argument("--debug", action="store_true", help="debug flag") args = parser.parse_args() config_map = { From ed14af960abb6a10735eb60c79993660c76dbd1a Mon Sep 17 00:00:00 2001 From: harrki Date: Thu, 5 Dec 2024 18:38:40 +0900 Subject: [PATCH 194/249] feat: [WIP] add java modules --- adf_core_python/core/agent/agent.py | 4 +- .../core/agent/module/module_manager.py | 24 +- adf_core_python/core/agent/platoon/platoon.py | 15 +- .../core/agent/platoon/platoon_ambulance.py | 3 + .../core/agent/platoon/platoon_fire.py | 3 + .../core/agent/platoon/platoon_police.py | 3 + .../core/component/gateway/gateway_agent.py | 92 +++++++ .../component/gateway/gateway_launcher.py | 47 ++++ .../core/component/gateway/gateway_module.py | 38 +++ .../component/gateway/message/am_agent.py | 40 +++ .../core/component/gateway/message/am_exec.py | 26 ++ .../component/gateway/message/am_module.py | 36 +++ .../component/gateway/message/am_update.py | 30 +++ .../gateway/message/ma_module_response.py | 26 ++ .../gateway/message/moduleMessageFactory.py | 27 ++ .../core/component/gateway/message/urn/urn.py | 22 ++ .../core/launcher/agent_launcher.py | 27 +- adf_core_python/core/launcher/config_key.py | 3 + .../core/launcher/connect/connector.py | 2 + .../connect/connector_ambulance_center.py | 2 + .../connect/connector_ambulance_team.py | 17 +- .../connect/connector_fire_brigade.py | 19 +- .../connect/connector_fire_station.py | 2 + .../connect/connector_police_force.py | 19 +- .../connect/connector_police_office.py | 2 + .../module/complex/java_human_detector.py | 51 ++++ config/launcher.yaml | 4 + java/.gitattributes | 12 + java/.gitignore | 5 + java/gradle/libs.versions.toml | 12 + java/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43583 bytes java/gradle/wrapper/gradle-wrapper.properties | 7 + java/gradlew | 252 ++++++++++++++++++ java/gradlew.bat | 94 +++++++ java/settings.gradle | 14 + 35 files changed, 959 insertions(+), 21 deletions(-) create mode 100644 adf_core_python/core/component/gateway/gateway_agent.py create mode 100644 adf_core_python/core/component/gateway/gateway_launcher.py create mode 100644 adf_core_python/core/component/gateway/gateway_module.py create mode 100644 adf_core_python/core/component/gateway/message/am_agent.py create mode 100644 adf_core_python/core/component/gateway/message/am_exec.py create mode 100644 adf_core_python/core/component/gateway/message/am_module.py create mode 100644 adf_core_python/core/component/gateway/message/am_update.py create mode 100644 adf_core_python/core/component/gateway/message/ma_module_response.py create mode 100644 adf_core_python/core/component/gateway/message/moduleMessageFactory.py create mode 100644 adf_core_python/core/component/gateway/message/urn/urn.py create mode 100644 adf_core_python/implement/module/complex/java_human_detector.py create mode 100644 java/.gitattributes create mode 100644 java/.gitignore create mode 100644 java/gradle/libs.versions.toml create mode 100644 java/gradle/wrapper/gradle-wrapper.jar create mode 100644 java/gradle/wrapper/gradle-wrapper.properties create mode 100755 java/gradlew create mode 100644 java/gradlew.bat create mode 100644 java/settings.gradle diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index bdf45c6..9f581a6 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -196,7 +196,7 @@ def update_step_info( self._message_manager.refresh() self._communication_module.receive(self, self._message_manager) - self.think() + self.think(time, change_set, hear) self.logger.debug( f"send messages: {self._message_manager.get_send_message_list()}", @@ -209,7 +209,7 @@ def update_step_info( self._communication_module.send(self, self._message_manager) @abstractmethod - def think(self) -> None: + def think(self, time: int, change_set: ChangeSet, hear: list[Command]) -> None: pass @abstractmethod diff --git a/adf_core_python/core/agent/module/module_manager.py b/adf_core_python/core/agent/module/module_manager.py index bb6855e..bc6ac4c 100644 --- a/adf_core_python/core/agent/module/module_manager.py +++ b/adf_core_python/core/agent/module/module_manager.py @@ -10,6 +10,8 @@ from adf_core_python.core.component.communication.message_coordinator import ( MessageCoordinator, ) +from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent +from adf_core_python.core.component.gateway.gateway_module import GatewayModule from adf_core_python.core.component.module.abstract_module import AbstractModule from adf_core_python.core.logger.logger import get_logger @@ -29,12 +31,14 @@ def __init__( scenario_info: ScenarioInfo, module_config: ModuleConfig, develop_data: DevelopData, + gateway_agent: GatewayAgent, ) -> None: self._agent_info = agent_info self._world_info = world_info self._scenario_info = scenario_info self._module_config = module_config self._develop_data = develop_data + self._gateway_agent = gateway_agent self._modules: dict[str, AbstractModule] = {} self._actions: dict[str, ExtendAction] = {} @@ -43,13 +47,13 @@ def __init__( self._channel_subscribers: dict[str, Any] = {} self._message_coordinators: dict[str, Any] = {} - def get_module(self, module_name: str, default_module_name: str) -> AbstractModule: + def get_module(self, module_name: str, default_class_name: str) -> AbstractModule: class_name = self._module_config.get_value(module_name) if class_name is None: get_logger("ModuleManager").warning( - f"Module key {module_name} not found in config, using default module {default_module_name}" + f"Module key {module_name} not found in config, using default module {default_class_name}" ) - class_name = default_module_name + class_name = default_class_name module_class: type = self._load_module(class_name) @@ -71,10 +75,10 @@ def get_module(self, module_name: str, default_module_name: str) -> AbstractModu raise RuntimeError(f"Module {class_name} is not a subclass of AbstractModule") def get_extend_action( - self, action_name: str, default_action_name: str + self, action_name: str, default_action_class_name: str ) -> ExtendAction: class_name = self._module_config.get_value_or_default( - action_name, default_action_name + action_name, default_action_class_name ) action_class: type = self._load_module(class_name) @@ -140,6 +144,16 @@ def get_message_coordinator( f"Message coordinator {class_name} is not a subclass of MessageCoordinator" ) + def get_java_module( + self, module_name: str, default_class_name: str + ) -> AbstractModule: # type: ignore + # TODO: Implement this method + gateway_module = GatewayModule(self._gateway_agent) + gateway_module.initialize(module_name, default_class_name) + self._gateway_agent.add_gateway_module(gateway_module) + a = gateway_module.get_class_names() + pass + def _load_module(self, class_name: str) -> type: module_name, module_class_name = class_name.rsplit(".", 1) module = importlib.import_module(module_name) diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py index 8a66099..b4f08e4 100644 --- a/adf_core_python/core/agent/platoon/platoon.py +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -7,8 +7,11 @@ from adf_core_python.core.agent.info.scenario_info import Mode from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent from adf_core_python.core.logger.logger import get_agent_logger +from rcrs_core.worldmodel.changeSet import ChangeSet +from rcrs_core.commands.Command import Command class Platoon(Agent): @@ -22,6 +25,7 @@ def __init__( module_config: ModuleConfig, develop_data: DevelopData, finish_post_connect_event: Event, + gateway_agent: GatewayAgent, ) -> None: super().__init__( is_precompute, @@ -40,6 +44,7 @@ def __init__( self._data_storage_name = data_storage_name self._module_config = module_config self._develop_data = develop_data + self._gateway_agent = gateway_agent def post_connect(self) -> None: super().post_connect() @@ -56,6 +61,7 @@ def post_connect(self) -> None: self._scenario_info, self._module_config, self._develop_data, + self._gateway_agent, ) self._message_manager.set_channel_subscriber( @@ -112,7 +118,14 @@ def post_connect(self) -> None: self._develop_data, ) - def think(self) -> None: + self._gateway_agent.set_initialize_data(self._agent_info, self._world_info) + self._gateway_agent.initialize() + + def think(self, time: int, change_set: ChangeSet, hear: list[Command]) -> None: + self._gateway_agent.set_update_data(time, change_set, hear) + # if self._gateway_agent.get_module_count() > 0: + self._gateway_agent.update() + action: Action = self._tactics_agent.think( self._agent_info, self._world_info, diff --git a/adf_core_python/core/agent/platoon/platoon_ambulance.py b/adf_core_python/core/agent/platoon/platoon_ambulance.py index 169d208..d1da73f 100644 --- a/adf_core_python/core/agent/platoon/platoon_ambulance.py +++ b/adf_core_python/core/agent/platoon/platoon_ambulance.py @@ -5,6 +5,7 @@ from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.platoon.platoon import Platoon +from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent @@ -19,6 +20,7 @@ def __init__( module_config: ModuleConfig, develop_data: DevelopData, finish_post_connect_event: Event, + gateway_agent: GatewayAgent, ): super().__init__( tactics_agent, @@ -29,6 +31,7 @@ def __init__( module_config, develop_data, finish_post_connect_event, + gateway_agent, ) def get_requested_entities(self) -> list[EntityURN]: diff --git a/adf_core_python/core/agent/platoon/platoon_fire.py b/adf_core_python/core/agent/platoon/platoon_fire.py index 3071eef..f7b6d2e 100644 --- a/adf_core_python/core/agent/platoon/platoon_fire.py +++ b/adf_core_python/core/agent/platoon/platoon_fire.py @@ -5,6 +5,7 @@ from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.platoon.platoon import Platoon +from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent @@ -19,6 +20,7 @@ def __init__( module_config: ModuleConfig, develop_data: DevelopData, finish_post_connect_event: Event, + gateway_agent: GatewayAgent, ): super().__init__( tactics_agent, @@ -29,6 +31,7 @@ def __init__( module_config, develop_data, finish_post_connect_event, + gateway_agent, ) def get_requested_entities(self) -> list[EntityURN]: diff --git a/adf_core_python/core/agent/platoon/platoon_police.py b/adf_core_python/core/agent/platoon/platoon_police.py index 6df9440..4f1a9fc 100644 --- a/adf_core_python/core/agent/platoon/platoon_police.py +++ b/adf_core_python/core/agent/platoon/platoon_police.py @@ -5,6 +5,7 @@ from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.platoon.platoon import Platoon +from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent @@ -19,6 +20,7 @@ def __init__( module_config: ModuleConfig, develop_data: DevelopData, finish_post_connect_event: Event, + gateway_agent: GatewayAgent, ): super().__init__( tactics_agent, @@ -29,6 +31,7 @@ def __init__( module_config, develop_data, finish_post_connect_event, + gateway_agent, ) def get_requested_entities(self) -> list[EntityURN]: diff --git a/adf_core_python/core/component/gateway/gateway_agent.py b/adf_core_python/core/component/gateway/gateway_agent.py new file mode 100644 index 0000000..de99d1f --- /dev/null +++ b/adf_core_python/core/component/gateway/gateway_agent.py @@ -0,0 +1,92 @@ +from __future__ import annotations + +from typing import Optional, TYPE_CHECKING + +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.component.gateway.message.am_agent import AMAgent +from adf_core_python.core.component.gateway.message.am_update import AMUpdate +from adf_core_python.core.component.gateway.message.ma_module_response import ( + MAModuleResponse, +) +from adf_core_python.core.component.gateway.message.moduleMessageFactory import ( + ModuleMessageFactory, +) +from adf_core_python.core.logger.logger import get_logger + +if TYPE_CHECKING: + from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher + from adf_core_python.core.component.gateway.gateway_module import GatewayModule + + +class GatewayAgent: + def __init__(self, gateway_launcher: GatewayLauncher) -> None: + self._gateway_launcher = gateway_launcher + self.send_msg = None + self._is_initialized = False + self._agent_info: Optional[AgentInfo] = None + self._world_info: Optional[WorldInfo] = None + self._time = None + self._change_set = None + self._hear = None + self._gateway_modules: dict[str, GatewayModule] = {} + self._logger = get_logger("GatewayAgent") + + def get_agent_entity_id(self) -> EntityID: + return self._agent_info.get_entity_id() + + def get_module_count(self) -> int: + return len(self._gateway_modules) + + def add_gateway_module(self, gateway_module: GatewayModule) -> None: + self._gateway_modules[gateway_module.get_module_id()] = gateway_module + + def is_initialized(self) -> bool: + return self._is_initialized + + def set_initialize_data(self, agent_info: AgentInfo, world_info: WorldInfo) -> None: + self._agent_info = agent_info + self._world_info = world_info + + def initialize(self) -> None: + self._logger.info( + type(list(self._world_info.get_world_model().get_entities())[0]) + ) + am_agent = AMAgent() + self.send_msg( + am_agent.write( + self._agent_info.get_entity_id(), + list(self._world_info.get_world_model().get_entities()), + ) + ) + self._logger.info( + "Sent AMAgent ( EntityID: " + str(self._agent_info.get_entity_id()) + ) + self._is_initialized = True + + def set_update_data(self, time, change_set, hear): + self._time = time + self._change_set = change_set + self._hear = hear + pass + + def update(self): + am_update = AMUpdate() + self.send_msg( + am_update.write( + self._agent_info.get_entity_id(), + self._agent_info.get_time(), + self._world_info.get_change_set(), + None, + ) + ) + + def set_send_msg(self, connection_send_func): + self.send_msg = connection_send_func + + def message_received(self, msg): + c_msg = ModuleMessageFactory().make_message(msg) + if isinstance(c_msg, MAModuleResponse): + self._gateway_modules[msg.module_id].set_class_names(msg.class_names) diff --git a/adf_core_python/core/component/gateway/gateway_launcher.py b/adf_core_python/core/component/gateway/gateway_launcher.py new file mode 100644 index 0000000..d4d096c --- /dev/null +++ b/adf_core_python/core/component/gateway/gateway_launcher.py @@ -0,0 +1,47 @@ +import socket + +from structlog import BoundLogger + +from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent +from adf_core_python.core.launcher.connect.connection import Connection + + +class GatewayLauncher: + def __init__(self, host: str, port: int, logger: BoundLogger) -> None: + self.host = host + self.port = port + self.logger = logger + pass + + def make_connection(self) -> Connection: + return Connection(self.host, self.port) + + def connect(self, gateway_agent: GatewayAgent) -> None: + # self.logger.bind(agent_id=gateway_agent.get_agent_entity_id()) + + self.logger.info( + f"{gateway_agent.__class__.__name__} connecting to {self.host}:{self.port}" + ) + connection = self.make_connection() + try: + connection.connect() + # ソケットが使用しているPORT番号を取得 + if connection.socket is not None: + self.logger.info( + f"Connected to {self.host}:{self.port} on port {connection.socket.getsockname()[1]}" + ) + except socket.timeout: + self.logger.warning(f"Connection to {self.host}:{self.port} timed out") + return + except socket.error as e: + self.logger.error(f"Failed to connect to {self.host}:{self.port}") + self.logger.error(e) + return + + connection.message_received(gateway_agent.message_received) + gateway_agent.set_send_msg(connection.send_msg) + + try: + connection.parse_message_from_kernel() + except Exception as e: + self.logger.error(f"Failed to connect agent: {self.host}:{self.port} {e}") diff --git a/adf_core_python/core/component/gateway/gateway_module.py b/adf_core_python/core/component/gateway/gateway_module.py new file mode 100644 index 0000000..55b31a5 --- /dev/null +++ b/adf_core_python/core/component/gateway/gateway_module.py @@ -0,0 +1,38 @@ +import uuid + +from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent +from adf_core_python.core.component.gateway.message.am_exec import AMExec +from adf_core_python.core.component.gateway.message.am_module import AMModule + + +class GatewayModule: + def __init__(self, gateway_agent: GatewayAgent): + self._gateway_agent = gateway_agent + self._module_id: str = str(uuid.uuid4()) + self._class_names: [str] = [] + + def get_module_id(self) -> str: + return self._module_id + + def get_class_names(self) -> [str]: + return self._class_names + + def set_class_names(self, class_names: [str]): + self._class_names = class_names + + def initialize(self, module_name: str, default_class_name: str): + if not self._gateway_agent.is_initialized(): + self._gateway_agent.initialize() + am_module = AMModule() + self._gateway_agent.send_msg( + am_module.write( + self._gateway_agent.get_agent_entity_id(), + self._module_id, + module_name, + default_class_name, + ) + ) + + def execute(self, method_name: str, *args): + am_exec = AMExec() + self._gateway_agent.send_msg(am_exec.write(self._module_id, method_name)) diff --git a/adf_core_python/core/component/gateway/message/am_agent.py b/adf_core_python/core/component/gateway/message/am_agent.py new file mode 100644 index 0000000..0817e4c --- /dev/null +++ b/adf_core_python/core/component/gateway/message/am_agent.py @@ -0,0 +1,40 @@ +from typing import Any + +from rcrs_core.connection import RCRSProto_pb2 +from rcrs_core.entities.entity import Entity +from rcrs_core.messages.message import Message +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.component.gateway.message.urn.urn import ( + ModuleMSG, + ComponentModuleMSG, +) + + +class AMAgent(Message): + def __init__(self) -> None: + super().__init__(ModuleMSG.AM_AGENT) + + def read(self) -> None: + pass + + def write(self, agent_id: EntityID, entities: list[Entity]) -> Any: + entity_proto_list = [] + for entity in entities: + entity_proto = RCRSProto_pb2.EntityProto() + entity_proto.urn = entity.get_urn() + entity_proto.entityID = entity.get_id() + + property_proto_list = [] + for k, v in entity.get_properties().items(): + property_proto_list.append(v.to_property_proto()) + entity_proto.properties.extend(property_proto_list) + entity_proto_list.append(entity_proto) + + entity_list_proto = RCRSProto_pb2.EntityListProto() + entity_list_proto.entities.extend(entity_proto_list) + msg = RCRSProto_pb2.MessageProto() + msg.urn = self.get_urn() + msg.components[ComponentModuleMSG.AgentID].entityID = agent_id.get_value() + msg.components[ComponentModuleMSG.Entities].entityList = entity_list_proto + return msg diff --git a/adf_core_python/core/component/gateway/message/am_exec.py b/adf_core_python/core/component/gateway/message/am_exec.py new file mode 100644 index 0000000..69bb4f2 --- /dev/null +++ b/adf_core_python/core/component/gateway/message/am_exec.py @@ -0,0 +1,26 @@ +from abc import ABC +from typing import Any + +from rcrs_core.connection import RCRSProto_pb2 +from rcrs_core.messages.message import Message + +from adf_core_python.core.component.gateway.message.urn.urn import ( + ModuleMSG, + ComponentModuleMSG, +) + + +class AMExec(Message, ABC): + def __init__(self) -> None: + super().__init__(ModuleMSG.AM_EXEC) + + def read(self) -> None: + pass + + def write(self, module_id: str, method_name: str) -> Any: + msg = RCRSProto_pb2.MessageProto() + msg.urn = self.get_urn() + msg.components[ComponentModuleMSG.ModuleID].stringValue = module_id + msg.components[ComponentModuleMSG.MethodName].stringValue = method_name + + return msg diff --git a/adf_core_python/core/component/gateway/message/am_module.py b/adf_core_python/core/component/gateway/message/am_module.py new file mode 100644 index 0000000..8de6bfe --- /dev/null +++ b/adf_core_python/core/component/gateway/message/am_module.py @@ -0,0 +1,36 @@ +from typing import Any + +from rcrs_core.connection import RCRSProto_pb2 +from rcrs_core.messages.message import Message +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.component.gateway.message.urn.urn import ( + ModuleMSG, + ComponentModuleMSG, +) + + +class AMModule(Message): + def __init__(self) -> None: + super().__init__(ModuleMSG.AM_MODULE) + + def read(self) -> None: + pass + + def write( + self, + agent_id: EntityID, + module_id: str, + module_name: str, + default_class_name: str, + ) -> Any: + msg = RCRSProto_pb2.MessageProto() + msg.urn = self.get_urn() + msg.components[ComponentModuleMSG.AgentID].entityID = agent_id + msg.components[ComponentModuleMSG.ModuleID].stringValue = module_id + msg.components[ComponentModuleMSG.ModuleName].stringValue = module_name + msg.components[ + ComponentModuleMSG.DefaultClassName + ].stringValue = default_class_name + + return msg diff --git a/adf_core_python/core/component/gateway/message/am_update.py b/adf_core_python/core/component/gateway/message/am_update.py new file mode 100644 index 0000000..30fd47c --- /dev/null +++ b/adf_core_python/core/component/gateway/message/am_update.py @@ -0,0 +1,30 @@ +from abc import ABC +from typing import Any + +from rcrs_core.connection import RCRSProto_pb2 +from rcrs_core.messages.message import Message +from rcrs_core.worldmodel.changeSet import ChangeSet +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.component.gateway.message.urn.urn import ( + ModuleMSG, + ComponentModuleMSG, +) + + +class AMUpdate(Message, ABC): + def __init__(self) -> None: + super().__init__(ModuleMSG.AM_UPDATE) + + def read(self) -> None: + pass + + def write(self, agent_id: EntityID, time: int, changed: ChangeSet, heard) -> Any: + msg = RCRSProto_pb2.MessageProto() + msg.urn = self.get_urn() + msg.components[ComponentModuleMSG.AgentID].entityID = agent_id.get_value() + msg.components[ComponentModuleMSG.Time].intValue = time + msg.components[ComponentModuleMSG.Changed].changeSet = changed + msg.components[ComponentModuleMSG.Heard].commandList = heard + + return msg diff --git a/adf_core_python/core/component/gateway/message/ma_module_response.py b/adf_core_python/core/component/gateway/message/ma_module_response.py new file mode 100644 index 0000000..d164325 --- /dev/null +++ b/adf_core_python/core/component/gateway/message/ma_module_response.py @@ -0,0 +1,26 @@ +from abc import ABC + +from rcrs_core.connection import RCRSProto_pb2 +from rcrs_core.messages.message import Message + +from adf_core_python.core.component.gateway.message.urn.urn import ( + ModuleMSG, + ComponentModuleMSG, +) + + +class MAModuleResponse(Message, ABC): + def __init__(self, data: RCRSProto_pb2) -> None: + super().__init__(ModuleMSG) + self.module_id = None + self.class_names = None + self.data = data + + def read(self) -> None: + self.module_id = self.data.components[ComponentModuleMSG.ModuleID].stringValue + self.class_names = self.data.components[ + ComponentModuleMSG.ClassNames + ].stringList + + def write(self) -> None: + pass diff --git a/adf_core_python/core/component/gateway/message/moduleMessageFactory.py b/adf_core_python/core/component/gateway/message/moduleMessageFactory.py new file mode 100644 index 0000000..c8c0796 --- /dev/null +++ b/adf_core_python/core/component/gateway/message/moduleMessageFactory.py @@ -0,0 +1,27 @@ +from adf_core_python.core.component.gateway.message.am_agent import AMAgent +from adf_core_python.core.component.gateway.message.am_exec import AMExec +from adf_core_python.core.component.gateway.message.am_module import AMModule +from adf_core_python.core.component.gateway.message.am_update import AMUpdate +from adf_core_python.core.component.gateway.message.ma_module_response import ( + MAModuleResponse, +) +from adf_core_python.core.component.gateway.message.urn.urn import ModuleMSG + + +class ModuleMessageFactory: + def __init__(self) -> None: + pass + + def make_message(self, msg): + if msg.urn == ModuleMSG.AM_AGENT: + return AMAgent(msg) + elif msg.urn == ModuleMSG.AM_MODULE: + return AMModule(msg) + elif msg.urn == ModuleMSG.MA_MODULE_RESPONSE: + return MAModuleResponse(msg) + elif msg.urn == ModuleMSG.AM_UPDATE: + return AMUpdate(msg) + elif msg.urn == ModuleMSG.AM_EXEC: + return AMExec(msg) + + return None diff --git a/adf_core_python/core/component/gateway/message/urn/urn.py b/adf_core_python/core/component/gateway/message/urn/urn.py new file mode 100644 index 0000000..363b931 --- /dev/null +++ b/adf_core_python/core/component/gateway/message/urn/urn.py @@ -0,0 +1,22 @@ +from enum import IntEnum + + +class ModuleMSG(IntEnum): + AM_AGENT = 0x0301 + AM_MODULE = 0x0302 + MA_MODULE_RESPONSE = 0x0303 + AM_UPDATE = 0x0304 + AM_EXEC = 0x0305 + + +class ComponentModuleMSG(IntEnum): + AgentID = 0x0401 + Entities = 0x0402 + ModuleName = 0x0403 + DefaultClassName = 0x0404 + ModuleID = 0x0405 + ClassNames = 0x0406 + Time = 0x0407 + Changed = 0x0408 + Heard = 0x0409 + MethodName = 0x0410 diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index f6f0787..eda337d 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -2,6 +2,7 @@ import threading from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher @@ -55,17 +56,33 @@ def init_connector(self) -> None: self.connectors.append(ConnectorPoliceOffice()) def launch(self) -> None: - host: str = self.config.get_value(ConfigKey.KEY_KERNEL_HOST, "localhost") - port: int = self.config.get_value(ConfigKey.KEY_KERNEL_PORT, 27931) - self.logger.info(f"Start agent launcher (host: {host}, port: {port})") + kernel_host: str = self.config.get_value(ConfigKey.KEY_KERNEL_HOST, "localhost") + kernel_port: int = self.config.get_value(ConfigKey.KEY_KERNEL_PORT, 27931) + self.logger.info( + f"Start agent launcher (host: {kernel_host}, port: {kernel_port})" + ) component_launcher: ComponentLauncher = ComponentLauncher( - host, port, self.logger + kernel_host, kernel_port, self.logger + ) + + gateway_host: str = self.config.get_value( + ConfigKey.KEY_GATEWAY_HOST, "localhost" + ) + gateway_port: int = self.config.get_value(ConfigKey.KEY_GATEWAY_PORT, 27930) + self.logger.info( + f"Start gateway launcher (host: {kernel_host}, port: {kernel_port})" + ) + + gateway_launcher: GatewayLauncher = GatewayLauncher( + gateway_host, gateway_port, self.logger ) connector_thread_list: list[threading.Thread] = [] for connector in self.connectors: - threads = connector.connect(component_launcher, self.config, self.loader) + threads = connector.connect( + component_launcher, gateway_launcher, self.config, self.loader + ) self.agent_thread_list.extend(threads) def connect() -> None: diff --git a/adf_core_python/core/launcher/config_key.py b/adf_core_python/core/launcher/config_key.py index 591876e..dcd987f 100644 --- a/adf_core_python/core/launcher/config_key.py +++ b/adf_core_python/core/launcher/config_key.py @@ -24,3 +24,6 @@ class ConfigKey: KEY_POLICE_OFFICE_COUNT: Final[str] = "adf.team.office.police.count" # adf-core-python KEY_PRECOMPUTE_DATA_DIR: Final[str] = "adf.agent.precompute.dir_name" + # Gateway + KEY_GATEWAY_HOST: Final[str] = "gateway.host" + KEY_GATEWAY_PORT: Final[str] = "gateway.port" diff --git a/adf_core_python/core/launcher/connect/connector.py b/adf_core_python/core/launcher/connect/connector.py index e517a84..408e904 100644 --- a/adf_core_python/core/launcher/connect/connector.py +++ b/adf_core_python/core/launcher/connect/connector.py @@ -2,6 +2,7 @@ from abc import ABC, abstractmethod from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher @@ -14,6 +15,7 @@ def __init__(self) -> None: def connect( self, component_launcher: ComponentLauncher, + gateway_launcher: GatewayLauncher, config: Config, loader: AbstractLoader, ) -> dict[threading.Thread, threading.Event]: diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_center.py b/adf_core_python/core/launcher/connect/connector_ambulance_center.py index 132a985..985bcc3 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_center.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_center.py @@ -7,6 +7,7 @@ from adf_core_python.core.component.tactics.tactics_ambulance_center import ( TacticsAmbulanceCenter, ) +from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher @@ -22,6 +23,7 @@ def __init__(self) -> None: def connect( self, component_launcher: ComponentLauncher, + gateway_launcher: GatewayLauncher, config: Config, loader: AbstractLoader, ) -> dict[threading.Thread, threading.Event]: diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index d2aa8c0..f27bf8a 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -4,6 +4,8 @@ from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.platoon.platoon_ambulance import PlatoonAmbulance from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent +from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.component.tactics.tactics_ambulance_team import ( TacticsAmbulanceTeam, ) @@ -22,6 +24,7 @@ def __init__(self) -> None: def connect( self, component_launcher: ComponentLauncher, + gateway_launcher: GatewayLauncher, config: Config, loader: AbstractLoader, ) -> dict[threading.Thread, threading.Event]: @@ -57,7 +60,10 @@ def connect( finish_post_connect_event = threading.Event() request_id: int = component_launcher.generate_request_id() - thread = threading.Thread( + + gateway_agent: GatewayAgent = GatewayAgent(gateway_launcher) + + component_thread = threading.Thread( target=component_launcher.connect, args=( PlatoonAmbulance( @@ -69,11 +75,18 @@ def connect( module_config, develop_data, finish_post_connect_event, + gateway_agent, ), request_id, ), name=f"AmbulanceTeam-{request_id}", ) - threads[thread] = finish_post_connect_event + threads[component_thread] = finish_post_connect_event + + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + threads[gateway_thread] = finish_post_connect_event return threads diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/adf_core_python/core/launcher/connect/connector_fire_brigade.py index 747f503..4cee656 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_brigade.py +++ b/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -4,6 +4,8 @@ from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.platoon.platoon_fire import PlatoonFire from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent +from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.component.tactics.tactics_fire_brigade import ( TacticsFireBrigade, ) @@ -22,6 +24,7 @@ def __init__(self) -> None: def connect( self, component_launcher: ComponentLauncher, + gateway_launcher: GatewayLauncher, config: Config, loader: AbstractLoader, ) -> dict[threading.Thread, threading.Event]: @@ -53,9 +56,12 @@ def connect( precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/fire_brigade" - request_id: int = component_launcher.generate_request_id() finish_post_connect_event = threading.Event() - thread = threading.Thread( + request_id: int = component_launcher.generate_request_id() + + gateway_agent: GatewayAgent = GatewayAgent(gateway_launcher) + + component_thread = threading.Thread( target=component_launcher.connect, args=( PlatoonFire( @@ -67,11 +73,18 @@ def connect( module_config, develop_data, finish_post_connect_event, + gateway_agent, ), request_id, ), name=f"FireBrigadeAgent-{request_id}", ) - threads[thread] = finish_post_connect_event + threads[component_thread] = finish_post_connect_event + + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + threads[gateway_thread] = finish_post_connect_event return threads diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/adf_core_python/core/launcher/connect/connector_fire_station.py index 8f099ee..df0fdbc 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_station.py +++ b/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -7,6 +7,7 @@ from adf_core_python.core.component.tactics.tactics_fire_station import ( TacticsFireStation, ) +from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher @@ -22,6 +23,7 @@ def __init__(self) -> None: def connect( self, component_launcher: ComponentLauncher, + gateway_launcher: GatewayLauncher, config: Config, loader: AbstractLoader, ) -> dict[threading.Thread, threading.Event]: diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py index e17fe76..028211d 100644 --- a/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -4,6 +4,8 @@ from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.platoon.platoon_police import PlatoonPolice from adf_core_python.core.component.abstract_loader import AbstractLoader +from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent +from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.component.tactics.tactics_police_force import ( TacticsPoliceForce, ) @@ -22,6 +24,7 @@ def __init__(self) -> None: def connect( self, component_launcher: ComponentLauncher, + gateway_launcher: GatewayLauncher, config: Config, loader: AbstractLoader, ) -> dict[threading.Thread, threading.Event]: @@ -53,9 +56,12 @@ def connect( precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/police_force" - request_id: int = component_launcher.generate_request_id() finish_post_connect_event = threading.Event() - thread = threading.Thread( + request_id: int = component_launcher.generate_request_id() + + gateway_agent: GatewayAgent = GatewayAgent(gateway_launcher) + + component_thread = threading.Thread( target=component_launcher.connect, args=( PlatoonPolice( @@ -67,11 +73,18 @@ def connect( module_config, develop_data, finish_post_connect_event, + gateway_agent, ), request_id, ), name=f"PoliceForceAgent-{request_id}", ) - threads[thread] = finish_post_connect_event + threads[component_thread] = finish_post_connect_event + + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + threads[gateway_thread] = finish_post_connect_event return threads diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py index 52c3aac..eea1a89 100644 --- a/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -7,6 +7,7 @@ from adf_core_python.core.component.tactics.tactics_police_office import ( TacticsPoliceOffice, ) +from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.config.config import Config from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher @@ -22,6 +23,7 @@ def __init__(self) -> None: def connect( self, component_launcher: ComponentLauncher, + gateway_launcher: GatewayLauncher, config: Config, loader: AbstractLoader, ) -> dict[threading.Thread, threading.Event]: diff --git a/adf_core_python/implement/module/complex/java_human_detector.py b/adf_core_python/implement/module/complex/java_human_detector.py new file mode 100644 index 0000000..47e4507 --- /dev/null +++ b/adf_core_python/implement/module/complex/java_human_detector.py @@ -0,0 +1,51 @@ +from typing import Optional + +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.module.complex.human_detector import HumanDetector +from adf_core_python.core.component.module.complex.target_detector import ( + TargetDetector, + T, +) + + +class JavaHumanDetector(HumanDetector): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + + def precompute(self, precompute_data: PrecomputeData) -> HumanDetector: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> HumanDetector: + super().resume(precompute_data) + return self + + def prepare(self) -> HumanDetector: + super().prepare() + return self + + def calculate(self) -> HumanDetector: + pass + + def get_target_entity_id(self) -> Optional[EntityID]: + pass + + def update_info(self, message_manager: MessageManager) -> TargetDetector[T]: + return super().update_info(message_manager) diff --git a/config/launcher.yaml b/config/launcher.yaml index 7702d17..54d6b53 100644 --- a/config/launcher.yaml +++ b/config/launcher.yaml @@ -2,6 +2,10 @@ kernel: host: localhost port: 27931 +gateway: + host: localhost + port: 27920 + team: name: AIT-Rescue diff --git a/java/.gitattributes b/java/.gitattributes new file mode 100644 index 0000000..f91f646 --- /dev/null +++ b/java/.gitattributes @@ -0,0 +1,12 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + +# Binary files should be left untouched +*.jar binary + diff --git a/java/.gitignore b/java/.gitignore new file mode 100644 index 0000000..1b6985c --- /dev/null +++ b/java/.gitignore @@ -0,0 +1,5 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build diff --git a/java/gradle/libs.versions.toml b/java/gradle/libs.versions.toml new file mode 100644 index 0000000..81f6352 --- /dev/null +++ b/java/gradle/libs.versions.toml @@ -0,0 +1,12 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format + +[versions] +commons-math3 = "3.6.1" +guava = "33.2.1-jre" +junit-jupiter = "5.10.3" + +[libraries] +commons-math3 = { module = "org.apache.commons:commons-math3", version.ref = "commons-math3" } +guava = { module = "com.google.guava:guava", version.ref = "guava" } +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } diff --git a/java/gradle/wrapper/gradle-wrapper.jar b/java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..a4b76b9530d66f5e68d973ea569d8e19de379189 GIT binary patch literal 43583 zcma&N1CXTcmMvW9vTb(Rwr$&4wr$(C?dmSu>@vG-+vuvg^_??!{yS%8zW-#zn-LkA z5&1^$^{lnmUON?}LBF8_K|(?T0Ra(xUH{($5eN!MR#ZihR#HxkUPe+_R8Cn`RRs(P z_^*#_XlXmGv7!4;*Y%p4nw?{bNp@UZHv1?Um8r6)Fei3p@ClJn0ECfg1hkeuUU@Or zDaPa;U3fE=3L}DooL;8f;P0ipPt0Z~9P0)lbStMS)ag54=uL9ia-Lm3nh|@(Y?B`; zx_#arJIpXH!U{fbCbI^17}6Ri*H<>OLR%c|^mh8+)*h~K8Z!9)DPf zR2h?lbDZQ`p9P;&DQ4F0sur@TMa!Y}S8irn(%d-gi0*WxxCSk*A?3lGh=gcYN?FGl z7D=Js!i~0=u3rox^eO3i@$0=n{K1lPNU zwmfjRVmLOCRfe=seV&P*1Iq=^i`502keY8Uy-WNPwVNNtJFx?IwAyRPZo2Wo1+S(xF37LJZ~%i)kpFQ3Fw=mXfd@>%+)RpYQLnr}B~~zoof(JVm^^&f zxKV^+3D3$A1G;qh4gPVjhrC8e(VYUHv#dy^)(RoUFM?o%W-EHxufuWf(l*@-l+7vt z=l`qmR56K~F|v<^Pd*p~1_y^P0P^aPC##d8+HqX4IR1gu+7w#~TBFphJxF)T$2WEa zxa?H&6=Qe7d(#tha?_1uQys2KtHQ{)Qco)qwGjrdNL7thd^G5i8Os)CHqc>iOidS} z%nFEDdm=GXBw=yXe1W-ShHHFb?Cc70+$W~z_+}nAoHFYI1MV1wZegw*0y^tC*s%3h zhD3tN8b=Gv&rj}!SUM6|ajSPp*58KR7MPpI{oAJCtY~JECm)*m_x>AZEu>DFgUcby z1Qaw8lU4jZpQ_$;*7RME+gq1KySGG#Wql>aL~k9tLrSO()LWn*q&YxHEuzmwd1?aAtI zBJ>P=&$=l1efe1CDU;`Fd+_;&wI07?V0aAIgc(!{a z0Jg6Y=inXc3^n!U0Atk`iCFIQooHqcWhO(qrieUOW8X(x?(RD}iYDLMjSwffH2~tB z)oDgNBLB^AJBM1M^c5HdRx6fBfka`(LD-qrlh5jqH~);#nw|iyp)()xVYak3;Ybik z0j`(+69aK*B>)e_p%=wu8XC&9e{AO4c~O1U`5X9}?0mrd*m$_EUek{R?DNSh(=br# z#Q61gBzEpmy`$pA*6!87 zSDD+=@fTY7<4A?GLqpA?Pb2z$pbCc4B4zL{BeZ?F-8`s$?>*lXXtn*NC61>|*w7J* z$?!iB{6R-0=KFmyp1nnEmLsA-H0a6l+1uaH^g%c(p{iT&YFrbQ$&PRb8Up#X3@Zsk zD^^&LK~111%cqlP%!_gFNa^dTYT?rhkGl}5=fL{a`UViaXWI$k-UcHJwmaH1s=S$4 z%4)PdWJX;hh5UoK?6aWoyLxX&NhNRqKam7tcOkLh{%j3K^4Mgx1@i|Pi&}<^5>hs5 zm8?uOS>%)NzT(%PjVPGa?X%`N2TQCKbeH2l;cTnHiHppPSJ<7y-yEIiC!P*ikl&!B z%+?>VttCOQM@ShFguHVjxX^?mHX^hSaO_;pnyh^v9EumqSZTi+#f&_Vaija0Q-e*| z7ulQj6Fs*bbmsWp{`auM04gGwsYYdNNZcg|ph0OgD>7O}Asn7^Z=eI>`$2*v78;sj-}oMoEj&@)9+ycEOo92xSyY344^ z11Hb8^kdOvbf^GNAK++bYioknrpdN>+u8R?JxG=!2Kd9r=YWCOJYXYuM0cOq^FhEd zBg2puKy__7VT3-r*dG4c62Wgxi52EMCQ`bKgf*#*ou(D4-ZN$+mg&7$u!! z-^+Z%;-3IDwqZ|K=ah85OLwkO zKxNBh+4QHh)u9D?MFtpbl)us}9+V!D%w9jfAMYEb>%$A;u)rrI zuBudh;5PN}_6J_}l55P3l_)&RMlH{m!)ai-i$g)&*M`eN$XQMw{v^r@-125^RRCF0 z^2>|DxhQw(mtNEI2Kj(;KblC7x=JlK$@78`O~>V!`|1Lm-^JR$-5pUANAnb(5}B}JGjBsliK4& zk6y(;$e&h)lh2)L=bvZKbvh@>vLlreBdH8No2>$#%_Wp1U0N7Ank!6$dFSi#xzh|( zRi{Uw%-4W!{IXZ)fWx@XX6;&(m_F%c6~X8hx=BN1&q}*( zoaNjWabE{oUPb!Bt$eyd#$5j9rItB-h*5JiNi(v^e|XKAj*8(k<5-2$&ZBR5fF|JA z9&m4fbzNQnAU}r8ab>fFV%J0z5awe#UZ|bz?Ur)U9bCIKWEzi2%A+5CLqh?}K4JHi z4vtM;+uPsVz{Lfr;78W78gC;z*yTch~4YkLr&m-7%-xc ztw6Mh2d>_iO*$Rd8(-Cr1_V8EO1f*^@wRoSozS) zy1UoC@pruAaC8Z_7~_w4Q6n*&B0AjOmMWa;sIav&gu z|J5&|{=a@vR!~k-OjKEgPFCzcJ>#A1uL&7xTDn;{XBdeM}V=l3B8fE1--DHjSaxoSjNKEM9|U9#m2<3>n{Iuo`r3UZp;>GkT2YBNAh|b z^jTq-hJp(ebZh#Lk8hVBP%qXwv-@vbvoREX$TqRGTgEi$%_F9tZES@z8Bx}$#5eeG zk^UsLBH{bc2VBW)*EdS({yw=?qmevwi?BL6*=12k9zM5gJv1>y#ML4!)iiPzVaH9% zgSImetD@dam~e>{LvVh!phhzpW+iFvWpGT#CVE5TQ40n%F|p(sP5mXxna+Ev7PDwA zamaV4m*^~*xV+&p;W749xhb_X=$|LD;FHuB&JL5?*Y2-oIT(wYY2;73<^#46S~Gx| z^cez%V7x$81}UWqS13Gz80379Rj;6~WdiXWOSsdmzY39L;Hg3MH43o*y8ibNBBH`(av4|u;YPq%{R;IuYow<+GEsf@R?=@tT@!}?#>zIIn0CoyV!hq3mw zHj>OOjfJM3F{RG#6ujzo?y32m^tgSXf@v=J$ELdJ+=5j|=F-~hP$G&}tDZsZE?5rX ztGj`!S>)CFmdkccxM9eGIcGnS2AfK#gXwj%esuIBNJQP1WV~b~+D7PJTmWGTSDrR` zEAu4B8l>NPuhsk5a`rReSya2nfV1EK01+G!x8aBdTs3Io$u5!6n6KX%uv@DxAp3F@{4UYg4SWJtQ-W~0MDb|j-$lwVn znAm*Pl!?Ps&3wO=R115RWKb*JKoexo*)uhhHBncEDMSVa_PyA>k{Zm2(wMQ(5NM3# z)jkza|GoWEQo4^s*wE(gHz?Xsg4`}HUAcs42cM1-qq_=+=!Gk^y710j=66(cSWqUe zklbm8+zB_syQv5A2rj!Vbw8;|$@C!vfNmNV!yJIWDQ>{+2x zKjuFX`~~HKG~^6h5FntRpnnHt=D&rq0>IJ9#F0eM)Y-)GpRjiN7gkA8wvnG#K=q{q z9dBn8_~wm4J<3J_vl|9H{7q6u2A!cW{bp#r*-f{gOV^e=8S{nc1DxMHFwuM$;aVI^ zz6A*}m8N-&x8;aunp1w7_vtB*pa+OYBw=TMc6QK=mbA-|Cf* zvyh8D4LRJImooUaSb7t*fVfih<97Gf@VE0|z>NcBwBQze);Rh!k3K_sfunToZY;f2 z^HmC4KjHRVg+eKYj;PRN^|E0>Gj_zagfRbrki68I^#~6-HaHg3BUW%+clM1xQEdPYt_g<2K+z!$>*$9nQ>; zf9Bei{?zY^-e{q_*|W#2rJG`2fy@{%6u0i_VEWTq$*(ZN37|8lFFFt)nCG({r!q#9 z5VK_kkSJ3?zOH)OezMT{!YkCuSSn!K#-Rhl$uUM(bq*jY? zi1xbMVthJ`E>d>(f3)~fozjg^@eheMF6<)I`oeJYx4*+M&%c9VArn(OM-wp%M<-`x z7sLP1&3^%Nld9Dhm@$3f2}87!quhI@nwd@3~fZl_3LYW-B?Ia>ui`ELg z&Qfe!7m6ze=mZ`Ia9$z|ARSw|IdMpooY4YiPN8K z4B(ts3p%2i(Td=tgEHX z0UQ_>URBtG+-?0E;E7Ld^dyZ;jjw0}XZ(}-QzC6+NN=40oDb2^v!L1g9xRvE#@IBR zO!b-2N7wVfLV;mhEaXQ9XAU+>=XVA6f&T4Z-@AX!leJ8obP^P^wP0aICND?~w&NykJ#54x3_@r7IDMdRNy4Hh;h*!u(Ol(#0bJdwEo$5437-UBjQ+j=Ic>Q2z` zJNDf0yO6@mr6y1#n3)s(W|$iE_i8r@Gd@!DWDqZ7J&~gAm1#~maIGJ1sls^gxL9LLG_NhU!pTGty!TbhzQnu)I*S^54U6Yu%ZeCg`R>Q zhBv$n5j0v%O_j{QYWG!R9W?5_b&67KB$t}&e2LdMvd(PxN6Ir!H4>PNlerpBL>Zvyy!yw z-SOo8caEpDt(}|gKPBd$qND5#a5nju^O>V&;f890?yEOfkSG^HQVmEbM3Ugzu+UtH zC(INPDdraBN?P%kE;*Ae%Wto&sgw(crfZ#Qy(<4nk;S|hD3j{IQRI6Yq|f^basLY; z-HB&Je%Gg}Jt@={_C{L$!RM;$$|iD6vu#3w?v?*;&()uB|I-XqEKqZPS!reW9JkLewLb!70T7n`i!gNtb1%vN- zySZj{8-1>6E%H&=V}LM#xmt`J3XQoaD|@XygXjdZ1+P77-=;=eYpoEQ01B@L*a(uW zrZeZz?HJsw_4g0vhUgkg@VF8<-X$B8pOqCuWAl28uB|@r`19DTUQQsb^pfqB6QtiT z*`_UZ`fT}vtUY#%sq2{rchyfu*pCg;uec2$-$N_xgjZcoumE5vSI{+s@iLWoz^Mf; zuI8kDP{!XY6OP~q5}%1&L}CtfH^N<3o4L@J@zg1-mt{9L`s^z$Vgb|mr{@WiwAqKg zp#t-lhrU>F8o0s1q_9y`gQNf~Vb!F%70f}$>i7o4ho$`uciNf=xgJ>&!gSt0g;M>*x4-`U)ysFW&Vs^Vk6m%?iuWU+o&m(2Jm26Y(3%TL; zA7T)BP{WS!&xmxNw%J=$MPfn(9*^*TV;$JwRy8Zl*yUZi8jWYF>==j~&S|Xinsb%c z2?B+kpet*muEW7@AzjBA^wAJBY8i|#C{WtO_or&Nj2{=6JTTX05}|H>N2B|Wf!*3_ z7hW*j6p3TvpghEc6-wufFiY!%-GvOx*bZrhZu+7?iSrZL5q9}igiF^*R3%DE4aCHZ zqu>xS8LkW+Auv%z-<1Xs92u23R$nk@Pk}MU5!gT|c7vGlEA%G^2th&Q*zfg%-D^=f z&J_}jskj|Q;73NP4<4k*Y%pXPU2Thoqr+5uH1yEYM|VtBPW6lXaetokD0u z9qVek6Q&wk)tFbQ8(^HGf3Wp16gKmr>G;#G(HRBx?F`9AIRboK+;OfHaLJ(P>IP0w zyTbTkx_THEOs%Q&aPrxbZrJlio+hCC_HK<4%f3ZoSAyG7Dn`=X=&h@m*|UYO-4Hq0 z-Bq&+Ie!S##4A6OGoC~>ZW`Y5J)*ouaFl_e9GA*VSL!O_@xGiBw!AF}1{tB)z(w%c zS1Hmrb9OC8>0a_$BzeiN?rkPLc9%&;1CZW*4}CDDNr2gcl_3z+WC15&H1Zc2{o~i) z)LLW=WQ{?ricmC`G1GfJ0Yp4Dy~Ba;j6ZV4r{8xRs`13{dD!xXmr^Aga|C=iSmor% z8hi|pTXH)5Yf&v~exp3o+sY4B^^b*eYkkCYl*T{*=-0HniSA_1F53eCb{x~1k3*`W zr~};p1A`k{1DV9=UPnLDgz{aJH=-LQo<5%+Em!DNN252xwIf*wF_zS^!(XSm(9eoj z=*dXG&n0>)_)N5oc6v!>-bd(2ragD8O=M|wGW z!xJQS<)u70m&6OmrF0WSsr@I%T*c#Qo#Ha4d3COcX+9}hM5!7JIGF>7<~C(Ear^Sn zm^ZFkV6~Ula6+8S?oOROOA6$C&q&dp`>oR-2Ym3(HT@O7Sd5c~+kjrmM)YmgPH*tL zX+znN>`tv;5eOfX?h{AuX^LK~V#gPCu=)Tigtq9&?7Xh$qN|%A$?V*v=&-2F$zTUv z`C#WyIrChS5|Kgm_GeudCFf;)!WH7FI60j^0o#65o6`w*S7R@)88n$1nrgU(oU0M9 zx+EuMkC>(4j1;m6NoGqEkpJYJ?vc|B zOlwT3t&UgL!pX_P*6g36`ZXQ; z9~Cv}ANFnJGp(;ZhS(@FT;3e)0)Kp;h^x;$*xZn*k0U6-&FwI=uOGaODdrsp-!K$Ac32^c{+FhI-HkYd5v=`PGsg%6I`4d9Jy)uW0y%) zm&j^9WBAp*P8#kGJUhB!L?a%h$hJgQrx!6KCB_TRo%9{t0J7KW8!o1B!NC)VGLM5! zpZy5Jc{`r{1e(jd%jsG7k%I+m#CGS*BPA65ZVW~fLYw0dA-H_}O zrkGFL&P1PG9p2(%QiEWm6x;U-U&I#;Em$nx-_I^wtgw3xUPVVu zqSuKnx&dIT-XT+T10p;yjo1Y)z(x1fb8Dzfn8e yu?e%!_ptzGB|8GrCfu%p?(_ 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 GZ~q4|mN%#X literal 0 HcmV?d00001 diff --git a/java/gradle/wrapper/gradle-wrapper.properties b/java/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..df97d72 --- /dev/null +++ b/java/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/java/gradlew b/java/gradlew new file mode 100755 index 0000000..f5feea6 --- /dev/null +++ b/java/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/java/gradlew.bat b/java/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/java/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/java/settings.gradle b/java/settings.gradle new file mode 100644 index 0000000..2e78246 --- /dev/null +++ b/java/settings.gradle @@ -0,0 +1,14 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.10.2/userguide/multi_project_builds.html in the Gradle documentation. + */ + +plugins { + // Apply the foojay-resolver plugin to allow automatic download of JDKs + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' +} + +rootProject.name = 'adf-core-python' +include('lib') From c0de8547b629e471f6d16c5a8ee92f907a983530 Mon Sep 17 00:00:00 2001 From: harrki Date: Fri, 13 Dec 2024 20:17:31 +0900 Subject: [PATCH 195/249] feat: add gateway modules --- adf_core_python/core/agent/agent.py | 30 +- .../agent/communication/message_manager.py | 2 +- .../core/agent/info/scenario_info.py | 4 +- .../core/agent/module/module_manager.py | 101 +++-- adf_core_python/core/agent/office/office.py | 4 + .../core/agent/office/office_ambulance.py | 4 + .../core/agent/office/office_fire.py | 4 + .../core/agent/office/office_police.py | 4 + adf_core_python/core/agent/platoon/platoon.py | 17 +- .../core/agent/platoon/platoon_ambulance.py | 5 +- .../core/agent/platoon/platoon_fire.py | 5 +- .../core/agent/platoon/platoon_police.py | 5 +- .../core/component/gateway/gateway_agent.py | 92 ---- .../core/component/gateway/gateway_module.py | 38 -- .../component/gateway/message/am_update.py | 30 -- .../gateway/message/moduleMessageFactory.py | 27 -- .../complex/ambulance_target_allocator.py | 35 +- .../module/complex/fire_target_allocator.py | 35 +- .../module/complex/police_target_allocator.py | 35 +- .../module/complex/target_allocator.py | 18 +- adf_core_python/core/gateway/__init__.py | 0 .../core/gateway/component/__init__.py | 0 .../core/gateway/component/module/__init__.py | 0 .../component/module/algorithm/__init__.py | 0 .../module/algorithm/gateway_clustering.py | 92 ++++ .../module/algorithm/gateway_path_planning.py | 88 ++++ .../component/module/complex/__init__.py | 0 .../gateway_ambulance_target_allocator.py | 66 +++ .../complex/gateway_fire_target_allocator.py | 62 +++ .../module/complex/gateway_human_detector.py | 61 +++ .../gateway_police_target_allocator.py | 64 +++ .../module/complex/gateway_road_detector.py | 60 +++ .../module/complex/gateway_search.py | 58 +++ .../complex/gateway_target_allocator.py | 73 +++ .../module/complex/gateway_target_detector.py | 70 +++ .../module/gateway_abstract_module.py | 55 +++ adf_core_python/core/gateway/gateway_agent.py | 113 +++++ .../gateway/gateway_launcher.py | 4 +- .../core/gateway/gateway_module.py | 83 ++++ .../core/gateway/message/__init__.py | 0 .../gateway/message/am_agent.py | 23 +- .../gateway/message/am_exec.py | 8 +- .../gateway/message/am_module.py | 5 +- .../core/gateway/message/am_update.py | 38 ++ .../core/gateway/message/ma_exec_response.py | 28 ++ .../gateway/message/ma_module_response.py | 14 +- .../gateway/message/moduleMessageFactory.py | 24 + .../core/gateway/message/urn/__init__.py | 0 .../gateway/message/urn/urn.py | 19 +- adf_core_python/core/gateway/module_dict.py | 32 ++ .../core/launcher/agent_launcher.py | 24 +- adf_core_python/core/launcher/config_key.py | 1 + .../core/launcher/connect/connector.py | 5 +- .../connect/connector_ambulance_center.py | 23 +- .../connect/connector_ambulance_team.py | 22 +- .../connect/connector_fire_brigade.py | 22 +- .../connect/connector_fire_station.py | 25 +- .../connect/connector_police_force.py | 24 +- .../connect/connector_police_office.py | 25 +- .../action/default_extend_action_clear.py | 2 +- .../module/complex/java_human_detector.py | 51 --- .../tactics/default_tactics_ambulance_team.py | 6 +- .../tactics/default_tactics_fire_brigade.py | 5 +- .../tactics/default_tactics_police_force.py | 3 +- adf_core_python/launcher.py | 6 + config/launcher.yaml | 5 +- java/.gitignore | 2 + java/lib/build.gradle | 57 +++ .../src/main/java/adf_core_python/Main.java | 10 + .../java/adf_core_python/agent/Agent.java | 116 +++++ .../agent/config/ModuleConfig.java | 39 ++ .../agent/develop/DevelopData.java | 40 ++ .../adf_core_python/agent/info/AgentInfo.java | 119 +++++ .../agent/module/ModuleManager.java | 427 ++++++++++++++++++ .../agent/precompute/PreData.java | 56 +++ .../agent/precompute/PrecomputeData.java | 400 ++++++++++++++++ .../component/extaction/ExtAction.java | 137 ++++++ .../component/module/AbstractModule.java | 138 ++++++ .../module/algorithm/Clustering.java | 86 ++++ .../module/algorithm/DynamicClustering.java | 14 + .../module/algorithm/PathPlanning.java | 101 +++++ .../module/algorithm/StaticClustering.java | 14 + .../complex/AmbulanceTargetAllocator.java | 47 ++ .../module/complex/BuildingDetector.java | 49 ++ .../module/complex/FireTargetAllocator.java | 47 ++ .../module/complex/HumanDetector.java | 49 ++ .../module/complex/PoliceTargetAllocator.java | 47 ++ .../module/complex/RoadDetector.java | 49 ++ .../component/module/complex/Search.java | 49 ++ .../module/complex/TargetAllocator.java | 54 +++ .../module/complex/TargetDetector.java | 54 +++ .../adf_core_python/gateway/Coordinator.java | 132 ++++++ .../java/adf_core_python/gateway/Gateway.java | 48 ++ .../gateway/mapper/AbstractMapper.java | 21 + .../gateway/mapper/MapperDict.java | 51 +++ .../mapper/module/AbstractModuleMapper.java | 64 +++ .../module/algorithm/ClusteringMapper.java | 133 ++++++ .../algorithm/DynamicClusteringMapper.java | 12 + .../module/algorithm/PathPlanningMapper.java | 108 +++++ .../algorithm/StaticClusteringMapper.java | 12 + .../AmbulanceTargetAllocatorMapper.java | 12 + .../complex/BuildingDetectorMapper.java | 12 + .../complex/FireTargetAllocatorMapper.java | 12 + .../module/complex/HumanDetectorMapper.java | 12 + .../complex/PoliceTargetAllocatorMapper.java | 12 + .../module/complex/RoadDetectorMapper.java | 12 + .../mapper/module/complex/SearchMapper.java | 11 + .../module/complex/TargetAllocatorMapper.java | 34 ++ .../module/complex/TargetDetectorMapper.java | 35 ++ .../gateway/message/AMAgent.java | 71 +++ .../gateway/message/AMExec.java | 57 +++ .../gateway/message/AMModule.java | 55 +++ .../gateway/message/AMUpdate.java | 61 +++ .../gateway/message/MAExecResponse.java | 49 ++ .../gateway/message/MAModuleResponse.java | 47 ++ .../message/ModuleControlMessageFactory.java | 42 ++ .../urn/ModuleMessageComponentURN.java | 52 +++ .../gateway/message/urn/ModuleMessageURN.java | 42 ++ java/lib/src/main/resources/log4j2.xml | 13 + .../test/java/org/example/LibraryTest.java | 14 + poetry.lock | 9 +- 121 files changed, 4891 insertions(+), 439 deletions(-) delete mode 100644 adf_core_python/core/component/gateway/gateway_agent.py delete mode 100644 adf_core_python/core/component/gateway/gateway_module.py delete mode 100644 adf_core_python/core/component/gateway/message/am_update.py delete mode 100644 adf_core_python/core/component/gateway/message/moduleMessageFactory.py create mode 100644 adf_core_python/core/gateway/__init__.py create mode 100644 adf_core_python/core/gateway/component/__init__.py create mode 100644 adf_core_python/core/gateway/component/module/__init__.py create mode 100644 adf_core_python/core/gateway/component/module/algorithm/__init__.py create mode 100644 adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py create mode 100644 adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py create mode 100644 adf_core_python/core/gateway/component/module/complex/__init__.py create mode 100644 adf_core_python/core/gateway/component/module/complex/gateway_ambulance_target_allocator.py create mode 100644 adf_core_python/core/gateway/component/module/complex/gateway_fire_target_allocator.py create mode 100644 adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py create mode 100644 adf_core_python/core/gateway/component/module/complex/gateway_police_target_allocator.py create mode 100644 adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py create mode 100644 adf_core_python/core/gateway/component/module/complex/gateway_search.py create mode 100644 adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py create mode 100644 adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py create mode 100644 adf_core_python/core/gateway/component/module/gateway_abstract_module.py create mode 100644 adf_core_python/core/gateway/gateway_agent.py rename adf_core_python/core/{component => }/gateway/gateway_launcher.py (91%) create mode 100644 adf_core_python/core/gateway/gateway_module.py create mode 100644 adf_core_python/core/gateway/message/__init__.py rename adf_core_python/core/{component => }/gateway/message/am_agent.py (62%) rename adf_core_python/core/{component => }/gateway/message/am_exec.py (61%) rename adf_core_python/core/{component => }/gateway/message/am_module.py (78%) create mode 100644 adf_core_python/core/gateway/message/am_update.py create mode 100644 adf_core_python/core/gateway/message/ma_exec_response.py rename adf_core_python/core/{component => }/gateway/message/ma_module_response.py (55%) create mode 100644 adf_core_python/core/gateway/message/moduleMessageFactory.py create mode 100644 adf_core_python/core/gateway/message/urn/__init__.py rename adf_core_python/core/{component => }/gateway/message/urn/urn.py (50%) create mode 100644 adf_core_python/core/gateway/module_dict.py delete mode 100644 adf_core_python/implement/module/complex/java_human_detector.py create mode 100644 java/lib/build.gradle create mode 100644 java/lib/src/main/java/adf_core_python/Main.java create mode 100644 java/lib/src/main/java/adf_core_python/agent/Agent.java create mode 100644 java/lib/src/main/java/adf_core_python/agent/config/ModuleConfig.java create mode 100644 java/lib/src/main/java/adf_core_python/agent/develop/DevelopData.java create mode 100644 java/lib/src/main/java/adf_core_python/agent/info/AgentInfo.java create mode 100644 java/lib/src/main/java/adf_core_python/agent/module/ModuleManager.java create mode 100644 java/lib/src/main/java/adf_core_python/agent/precompute/PreData.java create mode 100644 java/lib/src/main/java/adf_core_python/agent/precompute/PrecomputeData.java create mode 100644 java/lib/src/main/java/adf_core_python/component/extaction/ExtAction.java create mode 100644 java/lib/src/main/java/adf_core_python/component/module/AbstractModule.java create mode 100644 java/lib/src/main/java/adf_core_python/component/module/algorithm/Clustering.java create mode 100644 java/lib/src/main/java/adf_core_python/component/module/algorithm/DynamicClustering.java create mode 100644 java/lib/src/main/java/adf_core_python/component/module/algorithm/PathPlanning.java create mode 100644 java/lib/src/main/java/adf_core_python/component/module/algorithm/StaticClustering.java create mode 100644 java/lib/src/main/java/adf_core_python/component/module/complex/AmbulanceTargetAllocator.java create mode 100644 java/lib/src/main/java/adf_core_python/component/module/complex/BuildingDetector.java create mode 100644 java/lib/src/main/java/adf_core_python/component/module/complex/FireTargetAllocator.java create mode 100644 java/lib/src/main/java/adf_core_python/component/module/complex/HumanDetector.java create mode 100644 java/lib/src/main/java/adf_core_python/component/module/complex/PoliceTargetAllocator.java create mode 100644 java/lib/src/main/java/adf_core_python/component/module/complex/RoadDetector.java create mode 100644 java/lib/src/main/java/adf_core_python/component/module/complex/Search.java create mode 100644 java/lib/src/main/java/adf_core_python/component/module/complex/TargetAllocator.java create mode 100644 java/lib/src/main/java/adf_core_python/component/module/complex/TargetDetector.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/Coordinator.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/Gateway.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/AbstractMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/MapperDict.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/module/AbstractModuleMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/ClusteringMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/DynamicClusteringMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/PathPlanningMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/StaticClusteringMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/AmbulanceTargetAllocatorMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/BuildingDetectorMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/FireTargetAllocatorMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/HumanDetectorMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/PoliceTargetAllocatorMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/RoadDetectorMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/SearchMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/TargetAllocatorMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/TargetDetectorMapper.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/message/AMAgent.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/message/AMExec.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/message/AMModule.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/message/AMUpdate.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/message/MAExecResponse.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/message/MAModuleResponse.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/message/ModuleControlMessageFactory.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/message/urn/ModuleMessageComponentURN.java create mode 100644 java/lib/src/main/java/adf_core_python/gateway/message/urn/ModuleMessageURN.java create mode 100644 java/lib/src/main/resources/log4j2.xml create mode 100644 java/lib/src/test/java/org/example/LibraryTest.java diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index 9f581a6..7593410 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -2,7 +2,7 @@ import time as _time from abc import abstractmethod from threading import Event -from typing import Any, Callable, NoReturn +from typing import Any, Callable, NoReturn, Optional from bitarray import bitarray from rcrs_core.commands.AKClear import AKClear @@ -24,10 +24,10 @@ from rcrs_core.connection.URN import Entity as EntityURN from rcrs_core.messages.AKAcknowledge import AKAcknowledge from rcrs_core.messages.AKConnect import AKConnect -from rcrs_core.messages.controlMessageFactory import ControlMessageFactory from rcrs_core.messages.KAConnectError import KAConnectError from rcrs_core.messages.KAConnectOK import KAConnectOK from rcrs_core.messages.KASense import KASense +from rcrs_core.messages.controlMessageFactory import ControlMessageFactory from rcrs_core.worldmodel.changeSet import ChangeSet from rcrs_core.worldmodel.entityID import EntityID from rcrs_core.worldmodel.worldmodel import WorldModel @@ -79,6 +79,7 @@ CommunicationModule, ) from adf_core_python.core.config.config import Config +from adf_core_python.core.gateway.gateway_agent import GatewayAgent from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.logger.logger import get_agent_logger, get_logger @@ -94,6 +95,7 @@ def __init__( module_config: ModuleConfig, develop_data: DevelopData, finish_post_connect_event: Event, + gateway_agent: Optional[GatewayAgent], ) -> None: self.name = name self.connect_request_id = None @@ -124,6 +126,8 @@ def __init__( self._message_manager: MessageManager = MessageManager() self._communication_module: CommunicationModule = StandardCommunicationModule() + self._gateway_agent: Optional[GatewayAgent] = gateway_agent + def get_entity_id(self) -> EntityID: return self.agent_id @@ -146,14 +150,20 @@ def post_connect(self) -> None: self._ignore_time: int = int( self.config.get_value("kernel.agents.ignoreuntil", 3) ) + self._scenario_info: ScenarioInfo = ScenarioInfo(self.config, self._mode) self._world_info: WorldInfo = WorldInfo(self.world_model) self._agent_info = AgentInfo(self, self.world_model) + + if isinstance(self._gateway_agent, GatewayAgent): + self._gateway_agent.set_initialize_data( + self._agent_info, self._world_info, self._scenario_info + ) + self.logger = get_agent_logger( f"{self.__class__.__module__}.{self.__class__.__qualname__}", self._agent_info, ) - self.logger.debug(f"agent_config: {self.config}") def update_step_info( @@ -193,10 +203,16 @@ def update_step_info( self._agent_info.set_change_set(change_set) self._world_info.set_change_set(change_set) + if ( + isinstance(self._gateway_agent, GatewayAgent) + and self._gateway_agent.is_initialized() + ): + self._gateway_agent.update() + self._message_manager.refresh() self._communication_module.receive(self, self._message_manager) - self.think(time, change_set, hear) + self.think() self.logger.debug( f"send messages: {self._message_manager.get_send_message_list()}", @@ -209,7 +225,7 @@ def update_step_info( self._communication_module.send(self, self._message_manager) @abstractmethod - def think(self, time: int, change_set: ChangeSet, hear: list[Command]) -> None: + def think(self) -> None: pass @abstractmethod @@ -288,9 +304,9 @@ def handler_sense(self, msg: Any) -> None: if herad_command.urn == CommandURN.AK_SPEAK: heard_commands.append( AKSpeak( - herad_command.components[ + EntityID(herad_command.components[ ComponentControlMessageID.AgentID - ].entityID, + ].entityID), herad_command.components[ ComponentControlMessageID.Time ].intValue, diff --git a/adf_core_python/core/agent/communication/message_manager.py b/adf_core_python/core/agent/communication/message_manager.py index f318b1a..fc16479 100644 --- a/adf_core_python/core/agent/communication/message_manager.py +++ b/adf_core_python/core/agent/communication/message_manager.py @@ -234,7 +234,7 @@ def register_message_class( """ if index >= self.MAX_MESSAGE_CLASS_COUNT: raise ValueError( - f"Possible index values are 0 to {self.MAX_MESSAGE_CLASS_COUNT-1}" + f"Possible index values are 0 to {self.MAX_MESSAGE_CLASS_COUNT - 1}" ) self.__message_classes[index] = message_class diff --git a/adf_core_python/core/agent/info/scenario_info.py b/adf_core_python/core/agent/info/scenario_info.py index 85bf7da..29f4244 100644 --- a/adf_core_python/core/agent/info/scenario_info.py +++ b/adf_core_python/core/agent/info/scenario_info.py @@ -1,4 +1,4 @@ -from enum import Enum +from enum import IntEnum from typing import TypeVar from adf_core_python.core.config.config import Config @@ -6,7 +6,7 @@ T = TypeVar("T") -class Mode(Enum): +class Mode(IntEnum): NON_PRECOMPUTE = 0 PRECOMPUTED = 1 PRECOMPUTATION = 2 diff --git a/adf_core_python/core/agent/module/module_manager.py b/adf_core_python/core/agent/module/module_manager.py index bc6ac4c..18346aa 100644 --- a/adf_core_python/core/agent/module/module_manager.py +++ b/adf_core_python/core/agent/module/module_manager.py @@ -1,7 +1,7 @@ from __future__ import annotations import importlib -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Optional from adf_core_python.core.component.action.extend_action import ExtendAction from adf_core_python.core.component.communication.channel_subscriber import ( @@ -10,10 +10,14 @@ from adf_core_python.core.component.communication.message_coordinator import ( MessageCoordinator, ) -from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent -from adf_core_python.core.component.gateway.gateway_module import GatewayModule from adf_core_python.core.component.module.abstract_module import AbstractModule -from adf_core_python.core.logger.logger import get_logger +from adf_core_python.core.gateway.component.module.gateway_abstract_module import ( + GatewayAbstractModule, +) +from adf_core_python.core.gateway.gateway_agent import GatewayAgent +from adf_core_python.core.gateway.gateway_module import GatewayModule +from adf_core_python.core.gateway.module_dict import ModuleDict +from adf_core_python.core.logger.logger import get_agent_logger if TYPE_CHECKING: from adf_core_python.core.agent.config.module_config import ModuleConfig @@ -31,7 +35,7 @@ def __init__( scenario_info: ScenarioInfo, module_config: ModuleConfig, develop_data: DevelopData, - gateway_agent: GatewayAgent, + gateway_agent: Optional[GatewayAgent] = None, ) -> None: self._agent_info = agent_info self._world_info = world_info @@ -47,30 +51,71 @@ def __init__( self._channel_subscribers: dict[str, Any] = {} self._message_coordinators: dict[str, Any] = {} - def get_module(self, module_name: str, default_class_name: str) -> AbstractModule: - class_name = self._module_config.get_value(module_name) - if class_name is None: - get_logger("ModuleManager").warning( - f"Module key {module_name} not found in config, using default module {default_class_name}" - ) - class_name = default_class_name + self._module_dict: ModuleDict = ModuleDict() - module_class: type = self._load_module(class_name) + self._logger = get_agent_logger( + f"{self.__class__.__module__}.{self.__class__.__qualname__}", + self._agent_info, + ) + def get_module( + self, module_name: str, default_module_class_name: str + ) -> AbstractModule: instance = self._modules.get(module_name) if instance is not None: return instance - if issubclass(module_class, AbstractModule): - instance = module_class( - self._agent_info, - self._world_info, - self._scenario_info, - self, - self._develop_data, - ) - self._modules[module_name] = instance - return instance + class_name = self._module_config.get_value(module_name) + if class_name is not None: + try: + module_class: type = self._load_module(class_name) + if issubclass(module_class, AbstractModule): + instance = module_class( + self._agent_info, + self._world_info, + self._scenario_info, + self, + self._develop_data, + ) + self._modules[module_name] = instance + return instance + except ModuleNotFoundError: + self._logger.warning( + f"Module {module_name} not found in python. " + f"If gateway flag is active, using module {module_name} in java" + ) + + if isinstance(self._gateway_agent, GatewayAgent): + gateway_module = GatewayModule(self._gateway_agent) + java_class_name = gateway_module.initialize(module_name, "") + class_name = self._module_dict[java_class_name] + if class_name is not None: + module_class = self._load_module(class_name) + if issubclass(module_class, GatewayAbstractModule): + instance = module_class( + self._agent_info, + self._world_info, + self._scenario_info, + self, + self._develop_data, + gateway_module, + ) + self._modules[module_name] = instance + return instance + + class_name = default_module_class_name + if class_name is not None: + module_class = self._load_module(class_name) + if issubclass(module_class, AbstractModule): + instance = module_class( + self._agent_info, + self._world_info, + self._scenario_info, + self, + self._develop_data, + ) + self._modules[module_name] = instance + return instance raise RuntimeError(f"Module {class_name} is not a subclass of AbstractModule") @@ -144,16 +189,6 @@ def get_message_coordinator( f"Message coordinator {class_name} is not a subclass of MessageCoordinator" ) - def get_java_module( - self, module_name: str, default_class_name: str - ) -> AbstractModule: # type: ignore - # TODO: Implement this method - gateway_module = GatewayModule(self._gateway_agent) - gateway_module.initialize(module_name, default_class_name) - self._gateway_agent.add_gateway_module(gateway_module) - a = gateway_module.get_class_names() - pass - def _load_module(self, class_name: str) -> type: module_name, module_class_name = class_name.rsplit(".", 1) module = importlib.import_module(module_name) diff --git a/adf_core_python/core/agent/office/office.py b/adf_core_python/core/agent/office/office.py index 98911f8..97fcf6e 100644 --- a/adf_core_python/core/agent/office/office.py +++ b/adf_core_python/core/agent/office/office.py @@ -1,4 +1,5 @@ from threading import Event +from typing import Optional from adf_core_python.core.agent.agent import Agent from adf_core_python.core.agent.config.module_config import ModuleConfig @@ -7,6 +8,7 @@ from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.tactics.tactics_center import TacticsCenter +from adf_core_python.core.gateway.gateway_agent import GatewayAgent from adf_core_python.core.logger.logger import get_agent_logger @@ -21,6 +23,7 @@ def __init__( module_config: ModuleConfig, develop_data: DevelopData, finish_post_connect_event: Event, + gateway_agent: Optional[GatewayAgent], ) -> None: super().__init__( is_precompute, @@ -31,6 +34,7 @@ def __init__( module_config, develop_data, finish_post_connect_event, + gateway_agent, ) self._tactics_center = tactics_center self._team_name = team_name diff --git a/adf_core_python/core/agent/office/office_ambulance.py b/adf_core_python/core/agent/office/office_ambulance.py index 6bec530..f746fd5 100644 --- a/adf_core_python/core/agent/office/office_ambulance.py +++ b/adf_core_python/core/agent/office/office_ambulance.py @@ -1,4 +1,5 @@ from threading import Event +from typing import Optional from rcrs_core.connection.URN import Entity as EntityURN @@ -6,6 +7,7 @@ from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.office.office import Office from adf_core_python.core.component.tactics.tactics_center import TacticsCenter +from adf_core_python.core.gateway.gateway_agent import GatewayAgent class OfficeAmbulance(Office): @@ -19,6 +21,7 @@ def __init__( module_config: ModuleConfig, develop_data: DevelopData, finish_post_connect_event: Event, + gateway_agent: Optional[GatewayAgent], ) -> None: super().__init__( tactics_center, @@ -29,6 +32,7 @@ def __init__( module_config, develop_data, finish_post_connect_event, + gateway_agent, ) def precompute(self) -> None: diff --git a/adf_core_python/core/agent/office/office_fire.py b/adf_core_python/core/agent/office/office_fire.py index 16b93cd..6128edd 100644 --- a/adf_core_python/core/agent/office/office_fire.py +++ b/adf_core_python/core/agent/office/office_fire.py @@ -1,4 +1,5 @@ from threading import Event +from typing import Optional from rcrs_core.connection.URN import Entity as EntityURN @@ -6,6 +7,7 @@ from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.office.office import Office from adf_core_python.core.component.tactics.tactics_center import TacticsCenter +from adf_core_python.core.gateway.gateway_agent import GatewayAgent class OfficeFire(Office): @@ -19,6 +21,7 @@ def __init__( module_config: ModuleConfig, develop_data: DevelopData, finish_post_connect_event: Event, + gateway_agent: Optional[GatewayAgent], ) -> None: super().__init__( tactics_center, @@ -29,6 +32,7 @@ def __init__( module_config, develop_data, finish_post_connect_event, + gateway_agent, ) def get_requested_entities(self) -> list[EntityURN]: diff --git a/adf_core_python/core/agent/office/office_police.py b/adf_core_python/core/agent/office/office_police.py index 4b2ca8b..9717ac2 100644 --- a/adf_core_python/core/agent/office/office_police.py +++ b/adf_core_python/core/agent/office/office_police.py @@ -1,4 +1,5 @@ from threading import Event +from typing import Optional from rcrs_core.connection.URN import Entity as EntityURN @@ -6,6 +7,7 @@ from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.office.office import Office from adf_core_python.core.component.tactics.tactics_center import TacticsCenter +from adf_core_python.core.gateway.gateway_agent import GatewayAgent class OfficePolice(Office): @@ -19,6 +21,7 @@ def __init__( module_config: ModuleConfig, develop_data: DevelopData, finish_post_connect_event: Event, + gateway_agent: Optional[GatewayAgent], ) -> None: super().__init__( tactics_center, @@ -29,6 +32,7 @@ def __init__( module_config, develop_data, finish_post_connect_event, + gateway_agent, ) def get_requested_entities(self) -> list[EntityURN]: diff --git a/adf_core_python/core/agent/platoon/platoon.py b/adf_core_python/core/agent/platoon/platoon.py index b4f08e4..2f2781c 100644 --- a/adf_core_python/core/agent/platoon/platoon.py +++ b/adf_core_python/core/agent/platoon/platoon.py @@ -1,4 +1,5 @@ from threading import Event +from typing import Optional from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.agent import Agent @@ -7,11 +8,9 @@ from adf_core_python.core.agent.info.scenario_info import Mode from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData -from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent +from adf_core_python.core.gateway.gateway_agent import GatewayAgent from adf_core_python.core.logger.logger import get_agent_logger -from rcrs_core.worldmodel.changeSet import ChangeSet -from rcrs_core.commands.Command import Command class Platoon(Agent): @@ -25,7 +24,7 @@ def __init__( module_config: ModuleConfig, develop_data: DevelopData, finish_post_connect_event: Event, - gateway_agent: GatewayAgent, + gateway_agent: Optional[GatewayAgent], ) -> None: super().__init__( is_precompute, @@ -36,6 +35,7 @@ def __init__( module_config, develop_data, finish_post_connect_event, + gateway_agent, ) self._tactics_agent = tactics_agent self._team_name = team_name @@ -118,14 +118,7 @@ def post_connect(self) -> None: self._develop_data, ) - self._gateway_agent.set_initialize_data(self._agent_info, self._world_info) - self._gateway_agent.initialize() - - def think(self, time: int, change_set: ChangeSet, hear: list[Command]) -> None: - self._gateway_agent.set_update_data(time, change_set, hear) - # if self._gateway_agent.get_module_count() > 0: - self._gateway_agent.update() - + def think(self) -> None: action: Action = self._tactics_agent.think( self._agent_info, self._world_info, diff --git a/adf_core_python/core/agent/platoon/platoon_ambulance.py b/adf_core_python/core/agent/platoon/platoon_ambulance.py index d1da73f..0daad4a 100644 --- a/adf_core_python/core/agent/platoon/platoon_ambulance.py +++ b/adf_core_python/core/agent/platoon/platoon_ambulance.py @@ -1,12 +1,13 @@ from threading import Event +from typing import Optional from rcrs_core.connection.URN import Entity as EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.platoon.platoon import Platoon -from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent +from adf_core_python.core.gateway.gateway_agent import GatewayAgent class PlatoonAmbulance(Platoon): @@ -20,7 +21,7 @@ def __init__( module_config: ModuleConfig, develop_data: DevelopData, finish_post_connect_event: Event, - gateway_agent: GatewayAgent, + gateway_agent: Optional[GatewayAgent], ): super().__init__( tactics_agent, diff --git a/adf_core_python/core/agent/platoon/platoon_fire.py b/adf_core_python/core/agent/platoon/platoon_fire.py index f7b6d2e..8debf99 100644 --- a/adf_core_python/core/agent/platoon/platoon_fire.py +++ b/adf_core_python/core/agent/platoon/platoon_fire.py @@ -1,12 +1,13 @@ from threading import Event +from typing import Optional from rcrs_core.connection.URN import Entity as EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.platoon.platoon import Platoon -from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent +from adf_core_python.core.gateway.gateway_agent import GatewayAgent class PlatoonFire(Platoon): @@ -20,7 +21,7 @@ def __init__( module_config: ModuleConfig, develop_data: DevelopData, finish_post_connect_event: Event, - gateway_agent: GatewayAgent, + gateway_agent: Optional[GatewayAgent], ): super().__init__( tactics_agent, diff --git a/adf_core_python/core/agent/platoon/platoon_police.py b/adf_core_python/core/agent/platoon/platoon_police.py index 4f1a9fc..441602f 100644 --- a/adf_core_python/core/agent/platoon/platoon_police.py +++ b/adf_core_python/core/agent/platoon/platoon_police.py @@ -1,12 +1,13 @@ from threading import Event +from typing import Optional from rcrs_core.connection.URN import Entity as EntityURN from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.platoon.platoon import Platoon -from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent from adf_core_python.core.component.tactics.tactics_agent import TacticsAgent +from adf_core_python.core.gateway.gateway_agent import GatewayAgent class PlatoonPolice(Platoon): @@ -20,7 +21,7 @@ def __init__( module_config: ModuleConfig, develop_data: DevelopData, finish_post_connect_event: Event, - gateway_agent: GatewayAgent, + gateway_agent: Optional[GatewayAgent], ): super().__init__( tactics_agent, diff --git a/adf_core_python/core/component/gateway/gateway_agent.py b/adf_core_python/core/component/gateway/gateway_agent.py deleted file mode 100644 index de99d1f..0000000 --- a/adf_core_python/core/component/gateway/gateway_agent.py +++ /dev/null @@ -1,92 +0,0 @@ -from __future__ import annotations - -from typing import Optional, TYPE_CHECKING - -from rcrs_core.worldmodel.entityID import EntityID - -from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.world_info import WorldInfo -from adf_core_python.core.component.gateway.message.am_agent import AMAgent -from adf_core_python.core.component.gateway.message.am_update import AMUpdate -from adf_core_python.core.component.gateway.message.ma_module_response import ( - MAModuleResponse, -) -from adf_core_python.core.component.gateway.message.moduleMessageFactory import ( - ModuleMessageFactory, -) -from adf_core_python.core.logger.logger import get_logger - -if TYPE_CHECKING: - from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher - from adf_core_python.core.component.gateway.gateway_module import GatewayModule - - -class GatewayAgent: - def __init__(self, gateway_launcher: GatewayLauncher) -> None: - self._gateway_launcher = gateway_launcher - self.send_msg = None - self._is_initialized = False - self._agent_info: Optional[AgentInfo] = None - self._world_info: Optional[WorldInfo] = None - self._time = None - self._change_set = None - self._hear = None - self._gateway_modules: dict[str, GatewayModule] = {} - self._logger = get_logger("GatewayAgent") - - def get_agent_entity_id(self) -> EntityID: - return self._agent_info.get_entity_id() - - def get_module_count(self) -> int: - return len(self._gateway_modules) - - def add_gateway_module(self, gateway_module: GatewayModule) -> None: - self._gateway_modules[gateway_module.get_module_id()] = gateway_module - - def is_initialized(self) -> bool: - return self._is_initialized - - def set_initialize_data(self, agent_info: AgentInfo, world_info: WorldInfo) -> None: - self._agent_info = agent_info - self._world_info = world_info - - def initialize(self) -> None: - self._logger.info( - type(list(self._world_info.get_world_model().get_entities())[0]) - ) - am_agent = AMAgent() - self.send_msg( - am_agent.write( - self._agent_info.get_entity_id(), - list(self._world_info.get_world_model().get_entities()), - ) - ) - self._logger.info( - "Sent AMAgent ( EntityID: " + str(self._agent_info.get_entity_id()) - ) - self._is_initialized = True - - def set_update_data(self, time, change_set, hear): - self._time = time - self._change_set = change_set - self._hear = hear - pass - - def update(self): - am_update = AMUpdate() - self.send_msg( - am_update.write( - self._agent_info.get_entity_id(), - self._agent_info.get_time(), - self._world_info.get_change_set(), - None, - ) - ) - - def set_send_msg(self, connection_send_func): - self.send_msg = connection_send_func - - def message_received(self, msg): - c_msg = ModuleMessageFactory().make_message(msg) - if isinstance(c_msg, MAModuleResponse): - self._gateway_modules[msg.module_id].set_class_names(msg.class_names) diff --git a/adf_core_python/core/component/gateway/gateway_module.py b/adf_core_python/core/component/gateway/gateway_module.py deleted file mode 100644 index 55b31a5..0000000 --- a/adf_core_python/core/component/gateway/gateway_module.py +++ /dev/null @@ -1,38 +0,0 @@ -import uuid - -from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent -from adf_core_python.core.component.gateway.message.am_exec import AMExec -from adf_core_python.core.component.gateway.message.am_module import AMModule - - -class GatewayModule: - def __init__(self, gateway_agent: GatewayAgent): - self._gateway_agent = gateway_agent - self._module_id: str = str(uuid.uuid4()) - self._class_names: [str] = [] - - def get_module_id(self) -> str: - return self._module_id - - def get_class_names(self) -> [str]: - return self._class_names - - def set_class_names(self, class_names: [str]): - self._class_names = class_names - - def initialize(self, module_name: str, default_class_name: str): - if not self._gateway_agent.is_initialized(): - self._gateway_agent.initialize() - am_module = AMModule() - self._gateway_agent.send_msg( - am_module.write( - self._gateway_agent.get_agent_entity_id(), - self._module_id, - module_name, - default_class_name, - ) - ) - - def execute(self, method_name: str, *args): - am_exec = AMExec() - self._gateway_agent.send_msg(am_exec.write(self._module_id, method_name)) diff --git a/adf_core_python/core/component/gateway/message/am_update.py b/adf_core_python/core/component/gateway/message/am_update.py deleted file mode 100644 index 30fd47c..0000000 --- a/adf_core_python/core/component/gateway/message/am_update.py +++ /dev/null @@ -1,30 +0,0 @@ -from abc import ABC -from typing import Any - -from rcrs_core.connection import RCRSProto_pb2 -from rcrs_core.messages.message import Message -from rcrs_core.worldmodel.changeSet import ChangeSet -from rcrs_core.worldmodel.entityID import EntityID - -from adf_core_python.core.component.gateway.message.urn.urn import ( - ModuleMSG, - ComponentModuleMSG, -) - - -class AMUpdate(Message, ABC): - def __init__(self) -> None: - super().__init__(ModuleMSG.AM_UPDATE) - - def read(self) -> None: - pass - - def write(self, agent_id: EntityID, time: int, changed: ChangeSet, heard) -> Any: - msg = RCRSProto_pb2.MessageProto() - msg.urn = self.get_urn() - msg.components[ComponentModuleMSG.AgentID].entityID = agent_id.get_value() - msg.components[ComponentModuleMSG.Time].intValue = time - msg.components[ComponentModuleMSG.Changed].changeSet = changed - msg.components[ComponentModuleMSG.Heard].commandList = heard - - return msg diff --git a/adf_core_python/core/component/gateway/message/moduleMessageFactory.py b/adf_core_python/core/component/gateway/message/moduleMessageFactory.py deleted file mode 100644 index c8c0796..0000000 --- a/adf_core_python/core/component/gateway/message/moduleMessageFactory.py +++ /dev/null @@ -1,27 +0,0 @@ -from adf_core_python.core.component.gateway.message.am_agent import AMAgent -from adf_core_python.core.component.gateway.message.am_exec import AMExec -from adf_core_python.core.component.gateway.message.am_module import AMModule -from adf_core_python.core.component.gateway.message.am_update import AMUpdate -from adf_core_python.core.component.gateway.message.ma_module_response import ( - MAModuleResponse, -) -from adf_core_python.core.component.gateway.message.urn.urn import ModuleMSG - - -class ModuleMessageFactory: - def __init__(self) -> None: - pass - - def make_message(self, msg): - if msg.urn == ModuleMSG.AM_AGENT: - return AMAgent(msg) - elif msg.urn == ModuleMSG.AM_MODULE: - return AMModule(msg) - elif msg.urn == ModuleMSG.MA_MODULE_RESPONSE: - return MAModuleResponse(msg) - elif msg.urn == ModuleMSG.AM_UPDATE: - return AMUpdate(msg) - elif msg.urn == ModuleMSG.AM_EXEC: - return AMExec(msg) - - return None diff --git a/adf_core_python/core/component/module/complex/ambulance_target_allocator.py b/adf_core_python/core/component/module/complex/ambulance_target_allocator.py index 2c36e74..7359e68 100644 --- a/adf_core_python/core/component/module/complex/ambulance_target_allocator.py +++ b/adf_core_python/core/component/module/complex/ambulance_target_allocator.py @@ -1,18 +1,23 @@ -from abc import abstractmethod +from __future__ import annotations -from rcrs_core.worldmodel.entityID import EntityID +from abc import abstractmethod +from typing import TYPE_CHECKING -from adf_core_python.core.agent.communication.message_manager import MessageManager -from adf_core_python.core.agent.develop.develop_data import DevelopData -from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import ScenarioInfo -from adf_core_python.core.agent.info.world_info import WorldInfo -from adf_core_python.core.agent.module.module_manager import ModuleManager -from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.module.complex.target_allocator import ( TargetAllocator, ) +if TYPE_CHECKING: + from rcrs_core.worldmodel.entityID import EntityID + + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + class AmbulanceTargetAllocator(TargetAllocator): def __init__( @@ -32,17 +37,21 @@ def get_result(self) -> dict[EntityID, EntityID]: pass @abstractmethod - def calculate(self) -> TargetAllocator: + def calculate(self) -> AmbulanceTargetAllocator: pass - def resume(self, precompute_data: PrecomputeData) -> TargetAllocator: + def precompute(self, precompute_data: PrecomputeData) -> AmbulanceTargetAllocator: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> AmbulanceTargetAllocator: super().resume(precompute_data) return self - def prepare(self) -> TargetAllocator: + def prepare(self) -> AmbulanceTargetAllocator: super().prepare() return self - def update_info(self, message_manager: MessageManager) -> TargetAllocator: + def update_info(self, message_manager: MessageManager) -> AmbulanceTargetAllocator: super().update_info(message_manager) return self diff --git a/adf_core_python/core/component/module/complex/fire_target_allocator.py b/adf_core_python/core/component/module/complex/fire_target_allocator.py index a537ef6..9e35373 100644 --- a/adf_core_python/core/component/module/complex/fire_target_allocator.py +++ b/adf_core_python/core/component/module/complex/fire_target_allocator.py @@ -1,18 +1,23 @@ -from abc import abstractmethod +from __future__ import annotations -from rcrs_core.worldmodel.entityID import EntityID +from abc import abstractmethod +from typing import TYPE_CHECKING -from adf_core_python.core.agent.communication.message_manager import MessageManager -from adf_core_python.core.agent.develop.develop_data import DevelopData -from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import ScenarioInfo -from adf_core_python.core.agent.info.world_info import WorldInfo -from adf_core_python.core.agent.module.module_manager import ModuleManager -from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.module.complex.target_allocator import ( TargetAllocator, ) +if TYPE_CHECKING: + from rcrs_core.worldmodel.entityID import EntityID + + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + class FireTargetAllocator(TargetAllocator): def __init__( @@ -32,17 +37,21 @@ def get_result(self) -> dict[EntityID, EntityID]: pass @abstractmethod - def calculate(self) -> TargetAllocator: + def calculate(self) -> FireTargetAllocator: pass - def resume(self, precompute_data: PrecomputeData) -> TargetAllocator: + def precompute(self, precompute_data: PrecomputeData) -> FireTargetAllocator: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> FireTargetAllocator: super().resume(precompute_data) return self - def prepare(self) -> TargetAllocator: + def prepare(self) -> FireTargetAllocator: super().prepare() return self - def update_info(self, message_manager: MessageManager) -> TargetAllocator: + def update_info(self, message_manager: MessageManager) -> FireTargetAllocator: super().update_info(message_manager) return self diff --git a/adf_core_python/core/component/module/complex/police_target_allocator.py b/adf_core_python/core/component/module/complex/police_target_allocator.py index 8403d8f..bf31d15 100644 --- a/adf_core_python/core/component/module/complex/police_target_allocator.py +++ b/adf_core_python/core/component/module/complex/police_target_allocator.py @@ -1,18 +1,23 @@ -from abc import abstractmethod +from __future__ import annotations -from rcrs_core.worldmodel.entityID import EntityID +from abc import abstractmethod +from typing import TYPE_CHECKING -from adf_core_python.core.agent.communication.message_manager import MessageManager -from adf_core_python.core.agent.develop.develop_data import DevelopData -from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import ScenarioInfo -from adf_core_python.core.agent.info.world_info import WorldInfo -from adf_core_python.core.agent.module.module_manager import ModuleManager -from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.module.complex.target_allocator import ( TargetAllocator, ) +if TYPE_CHECKING: + from rcrs_core.worldmodel.entityID import EntityID + + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + class PoliceTargetAllocator(TargetAllocator): def __init__( @@ -32,17 +37,21 @@ def get_result(self) -> dict[EntityID, EntityID]: pass @abstractmethod - def calculate(self) -> TargetAllocator: + def calculate(self) -> PoliceTargetAllocator: pass - def resume(self, precompute_data: PrecomputeData) -> TargetAllocator: + def precompute(self, precompute_data: PrecomputeData) -> PoliceTargetAllocator: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> PoliceTargetAllocator: super().resume(precompute_data) return self - def prepare(self) -> TargetAllocator: + def prepare(self) -> PoliceTargetAllocator: super().prepare() return self - def update_info(self, message_manager: MessageManager) -> TargetAllocator: + def update_info(self, message_manager: MessageManager) -> PoliceTargetAllocator: super().update_info(message_manager) return self diff --git a/adf_core_python/core/component/module/complex/target_allocator.py b/adf_core_python/core/component/module/complex/target_allocator.py index 2548a2d..55869c9 100644 --- a/adf_core_python/core/component/module/complex/target_allocator.py +++ b/adf_core_python/core/component/module/complex/target_allocator.py @@ -1,9 +1,7 @@ from __future__ import annotations from abc import abstractmethod -from typing import TYPE_CHECKING, Generic, TypeVar - -from rcrs_core.entities.entity import Entity +from typing import TYPE_CHECKING from adf_core_python.core.component.module.abstract_module import AbstractModule @@ -18,10 +16,8 @@ from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData -T = TypeVar("T", bound=Entity) - -class TargetAllocator(AbstractModule, Generic[T]): +class TargetAllocator(AbstractModule): def __init__( self, agent_info: AgentInfo, @@ -39,21 +35,21 @@ def get_result(self) -> dict[EntityID, EntityID]: pass @abstractmethod - def calculate(self) -> TargetAllocator[T]: + def calculate(self) -> TargetAllocator: pass - def precompute(self, precompute_data: PrecomputeData) -> TargetAllocator[T]: + def precompute(self, precompute_data: PrecomputeData) -> TargetAllocator: super().precompute(precompute_data) return self - def resume(self, precompute_data: PrecomputeData) -> TargetAllocator[T]: + def resume(self, precompute_data: PrecomputeData) -> TargetAllocator: super().resume(precompute_data) return self - def prepare(self) -> TargetAllocator[T]: + def prepare(self) -> TargetAllocator: super().prepare() return self - def update_info(self, message_manager: MessageManager) -> TargetAllocator[T]: + def update_info(self, message_manager: MessageManager) -> TargetAllocator: super().update_info(message_manager) return self diff --git a/adf_core_python/core/gateway/__init__.py b/adf_core_python/core/gateway/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/gateway/component/__init__.py b/adf_core_python/core/gateway/component/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/gateway/component/module/__init__.py b/adf_core_python/core/gateway/component/module/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/gateway/component/module/algorithm/__init__.py b/adf_core_python/core/gateway/component/module/algorithm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py b/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py new file mode 100644 index 0000000..6518770 --- /dev/null +++ b/adf_core_python/core/gateway/component/module/algorithm/gateway_clustering.py @@ -0,0 +1,92 @@ +from __future__ import annotations + +import json +from typing import TYPE_CHECKING + +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.component.module.algorithm.clustering import Clustering +from adf_core_python.core.gateway.component.module.gateway_abstract_module import ( + GatewayAbstractModule, +) + +if TYPE_CHECKING: + from rcrs_core.entities.entity import Entity + + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule + + +class GatewayClustering(GatewayAbstractModule, Clustering): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute(self, precompute_data: PrecomputeData) -> GatewayClustering: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewayClustering: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewayClustering: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> GatewayClustering: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewayClustering: + super().calculate() + return self + + def get_cluster_number(self) -> int: + result = self._gateway_module.execute("getClusterNumber") + return int(result.get_value_or_default("ClusterNumber", "0")) + + def get_cluster_index(self, entity_id: EntityID) -> int: + arguments: dict[str, str] = {"EntityID": str(entity_id.get_value())} + result = self._gateway_module.execute("getClusterIndex(EntityID)", arguments) + return int(result.get_value_or_default("ClusterIndex", "0")) + + def get_cluster_entities(self, cluster_index: int) -> list[Entity]: + arguments: dict[str, str] = {"Index": str(cluster_index)} + result = self._gateway_module.execute("getClusterEntities(int)", arguments) + json_str = result.get_value_or_default("EntityIDs", "[]") + entity_ids: list[int] = json.loads(json_str) + entities: list[Entity] = [] + for entity_id in entity_ids: + entities.append(self._world_info.get_entity(EntityID(entity_id))) + return entities + + def get_cluster_entity_ids(self, cluster_index: int) -> list[EntityID]: + arguments: dict[str, str] = {"Index": str(cluster_index)} + result = self._gateway_module.execute("getClusterEntityIDs(int)", arguments) + json_str = result.get_value_or_default("EntityIDs", "[]") + raw_entity_ids: list[int] = json.loads(json_str) + entity_ids: list[EntityID] = [] + for entity_id in raw_entity_ids: + entity_ids.append(EntityID(entity_id)) + return entity_ids diff --git a/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py b/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py new file mode 100644 index 0000000..7d3b218 --- /dev/null +++ b/adf_core_python/core/gateway/component/module/algorithm/gateway_path_planning.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +import json +from typing import TYPE_CHECKING + +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning +from adf_core_python.core.gateway.component.module.gateway_abstract_module import ( + GatewayAbstractModule, +) + +if TYPE_CHECKING: + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule + + +class GatewayPathPlanning(GatewayAbstractModule, PathPlanning): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute(self, precompute_data: PrecomputeData) -> GatewayPathPlanning: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewayPathPlanning: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewayPathPlanning: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> GatewayPathPlanning: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewayPathPlanning: + super().calculate() + return self + + def get_path( + self, from_entity_id: EntityID, to_entity_id: EntityID + ) -> list[EntityID]: + arguments: dict[str, str] = { + "From": str(from_entity_id.get_value()), + "To": str(to_entity_id.get_value()), + } + result = self._gateway_module.execute( + "getResult(EntityID, EntityID)", arguments + ) + json_str = result.get_value_or_default("Result", "[]") + raw_entity_ids: list[int] = json.loads(json_str) + entity_ids: list[EntityID] = [] + for entity_id in raw_entity_ids: + entity_ids.append(EntityID(entity_id)) + return entity_ids + + def get_distance(self, from_entity_id: EntityID, to_entity_id: EntityID) -> float: + arguments: dict[str, str] = { + "From": str(from_entity_id.get_value()), + "To": str(to_entity_id.get_value()), + } + result = self._gateway_module.execute( + "getDistance(EntityID, EntityID)", arguments + ) + return float(result.get_value_or_default("Result", "0.0")) diff --git a/adf_core_python/core/gateway/component/module/complex/__init__.py b/adf_core_python/core/gateway/component/module/complex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_ambulance_target_allocator.py b/adf_core_python/core/gateway/component/module/complex/gateway_ambulance_target_allocator.py new file mode 100644 index 0000000..f570436 --- /dev/null +++ b/adf_core_python/core/gateway/component/module/complex/gateway_ambulance_target_allocator.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from adf_core_python.core.component.module.complex.ambulance_target_allocator import ( + AmbulanceTargetAllocator, +) +from adf_core_python.core.gateway.component.module.complex.gateway_target_allocator import ( + GatewayTargetAllocator, +) + +if TYPE_CHECKING: + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule + + +class GatewayAmbulanceTargetAllocator(GatewayTargetAllocator, AmbulanceTargetAllocator): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute( + self, precompute_data: PrecomputeData + ) -> GatewayAmbulanceTargetAllocator: + super().precompute(precompute_data) + return self + + def resume( + self, precompute_data: PrecomputeData + ) -> GatewayAmbulanceTargetAllocator: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewayAmbulanceTargetAllocator: + super().prepare() + return self + + def update_info( + self, message_manager: MessageManager + ) -> GatewayAmbulanceTargetAllocator: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewayAmbulanceTargetAllocator: + super().calculate() + return self diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_fire_target_allocator.py b/adf_core_python/core/gateway/component/module/complex/gateway_fire_target_allocator.py new file mode 100644 index 0000000..69fb643 --- /dev/null +++ b/adf_core_python/core/gateway/component/module/complex/gateway_fire_target_allocator.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from adf_core_python.core.component.module.complex.fire_target_allocator import ( + FireTargetAllocator, +) +from adf_core_python.core.gateway.component.module.complex.gateway_target_allocator import ( + GatewayTargetAllocator, +) + +if TYPE_CHECKING: + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule + + +class GatewayFireTargetAllocator(GatewayTargetAllocator, FireTargetAllocator): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute(self, precompute_data: PrecomputeData) -> GatewayFireTargetAllocator: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewayFireTargetAllocator: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewayFireTargetAllocator: + super().prepare() + return self + + def update_info( + self, message_manager: MessageManager + ) -> GatewayFireTargetAllocator: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewayFireTargetAllocator: + super().calculate() + return self diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py b/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py new file mode 100644 index 0000000..715feba --- /dev/null +++ b/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from rcrs_core.entities.human import Human + +if TYPE_CHECKING: + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.component.module.complex.human_detector import ( + HumanDetector, + ) + from adf_core_python.core.gateway.component.module.complex.gateway_target_detector import ( + GatewayTargetDetector, + ) + from adf_core_python.core.gateway.gateway_module import GatewayModule + + +class GatewayHumanDetector(GatewayTargetDetector[Human], HumanDetector): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute(self, precompute_data: PrecomputeData) -> GatewayHumanDetector: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewayHumanDetector: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewayHumanDetector: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> GatewayHumanDetector: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewayHumanDetector: + super().calculate() + return self diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_police_target_allocator.py b/adf_core_python/core/gateway/component/module/complex/gateway_police_target_allocator.py new file mode 100644 index 0000000..bacc0bb --- /dev/null +++ b/adf_core_python/core/gateway/component/module/complex/gateway_police_target_allocator.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from adf_core_python.core.component.module.complex.police_target_allocator import ( + PoliceTargetAllocator, +) +from adf_core_python.core.gateway.component.module.complex.gateway_target_allocator import ( + GatewayTargetAllocator, +) + +if TYPE_CHECKING: + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule + + +class GatewayPoliceTargetAllocator(GatewayTargetAllocator, PoliceTargetAllocator): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute( + self, precompute_data: PrecomputeData + ) -> GatewayPoliceTargetAllocator: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewayPoliceTargetAllocator: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewayPoliceTargetAllocator: + super().prepare() + return self + + def update_info( + self, message_manager: MessageManager + ) -> GatewayPoliceTargetAllocator: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewayPoliceTargetAllocator: + super().calculate() + return self diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py b/adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py new file mode 100644 index 0000000..da275b9 --- /dev/null +++ b/adf_core_python/core/gateway/component/module/complex/gateway_road_detector.py @@ -0,0 +1,60 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from rcrs_core.entities.road import Road + +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.component.module.complex.road_detector import RoadDetector +from adf_core_python.core.gateway.component.module.complex.gateway_target_detector import ( + GatewayTargetDetector, +) + +if TYPE_CHECKING: + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule + + +class GatewayRoadDetector(GatewayTargetDetector[Road], RoadDetector): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute(self, precompute_data: PrecomputeData) -> GatewayRoadDetector: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewayRoadDetector: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewayRoadDetector: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> GatewayRoadDetector: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewayRoadDetector: + super().calculate() + return self diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_search.py b/adf_core_python/core/gateway/component/module/complex/gateway_search.py new file mode 100644 index 0000000..bf37e2c --- /dev/null +++ b/adf_core_python/core/gateway/component/module/complex/gateway_search.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from adf_core_python.core.component.module.complex.search import Search +from adf_core_python.core.gateway.component.module.complex.gateway_target_detector import ( + GatewayTargetDetector, +) + +if TYPE_CHECKING: + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule + + +class GatewaySearch(GatewayTargetDetector, Search): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute(self, precompute_data: PrecomputeData) -> GatewaySearch: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewaySearch: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewaySearch: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> GatewaySearch: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewaySearch: + super().calculate() + return self diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py b/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py new file mode 100644 index 0000000..98649a4 --- /dev/null +++ b/adf_core_python/core/gateway/component/module/complex/gateway_target_allocator.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.component.module.complex.target_allocator import ( + TargetAllocator, +) +from adf_core_python.core.gateway.component.module.gateway_abstract_module import ( + GatewayAbstractModule, +) + +if TYPE_CHECKING: + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule + + +class GatewayTargetAllocator(GatewayAbstractModule, TargetAllocator): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute(self, precompute_data: PrecomputeData) -> GatewayTargetAllocator: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewayTargetAllocator: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewayTargetAllocator: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> GatewayTargetAllocator: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewayTargetAllocator: + super().calculate() + return self + + def get_result(self) -> dict[EntityID, EntityID]: + response = self._gateway_module.execute("getResult") + response_keys = response.get_all_keys() + result: dict[EntityID, EntityID] = {} + for key in response_keys: + result[EntityID(int(key))] = EntityID( + int(response.get_value_or_default(key, "-1")) + ) + + return result diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py b/adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py new file mode 100644 index 0000000..a58b25f --- /dev/null +++ b/adf_core_python/core/gateway/component/module/complex/gateway_target_detector.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +from typing import Optional, Generic, TypeVar, TYPE_CHECKING + +from rcrs_core.entities.entity import Entity +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.component.module.complex.target_detector import TargetDetector +from adf_core_python.core.gateway.component.module.gateway_abstract_module import ( + GatewayAbstractModule, +) + +if TYPE_CHECKING: + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule + +T = TypeVar("T", bound=Entity) + + +class GatewayTargetDetector(GatewayAbstractModule, TargetDetector, Generic[T]): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, + world_info, + scenario_info, + module_manager, + develop_data, + gateway_module, + ) + + def precompute(self, precompute_data: PrecomputeData) -> GatewayTargetDetector[T]: + super().precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewayTargetDetector[T]: + super().resume(precompute_data) + return self + + def prepare(self) -> GatewayTargetDetector[T]: + super().prepare() + return self + + def update_info(self, message_manager: MessageManager) -> GatewayTargetDetector[T]: + super().update_info(message_manager) + return self + + def calculate(self) -> GatewayTargetDetector[T]: + super().calculate() + return self + + def get_target_entity_id(self) -> Optional[EntityID]: + result = self._gateway_module.execute("getTarget") + entity_id_str = result.get_value_or_default("EntityID", "-1") + if entity_id_str == "-1": + return None + return EntityID(int(entity_id_str)) diff --git a/adf_core_python/core/gateway/component/module/gateway_abstract_module.py b/adf_core_python/core/gateway/component/module/gateway_abstract_module.py new file mode 100644 index 0000000..857977b --- /dev/null +++ b/adf_core_python/core/gateway/component/module/gateway_abstract_module.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from adf_core_python.core.component.module.abstract_module import AbstractModule + +if TYPE_CHECKING: + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData + from adf_core_python.core.gateway.gateway_module import GatewayModule + + +class GatewayAbstractModule(AbstractModule): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + gateway_module: GatewayModule, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._gateway_module = gateway_module + + def precompute(self, precompute_data: PrecomputeData) -> GatewayAbstractModule: + super().precompute(precompute_data) + self._gateway_module.execute("precompute") + return self + + def resume(self, precompute_data: PrecomputeData) -> GatewayAbstractModule: + super().resume(precompute_data) + self._gateway_module.execute("resume") + return self + + def prepare(self) -> GatewayAbstractModule: + super().prepare() + self._gateway_module.execute("preparate") + return self + + def update_info(self, message_manager: MessageManager) -> GatewayAbstractModule: + super().update_info(message_manager) + self._gateway_module.execute("updateInfo") + return self + + def calculate(self) -> GatewayAbstractModule: + self._gateway_module.execute("calc") + return self diff --git a/adf_core_python/core/gateway/gateway_agent.py b/adf_core_python/core/gateway/gateway_agent.py new file mode 100644 index 0000000..036c49a --- /dev/null +++ b/adf_core_python/core/gateway/gateway_agent.py @@ -0,0 +1,113 @@ +from __future__ import annotations + +from typing import Optional, TYPE_CHECKING, Callable + +from rcrs_core.connection import RCRSProto_pb2 + +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.gateway.message.am_agent import AMAgent +from adf_core_python.core.gateway.message.am_update import AMUpdate +from adf_core_python.core.gateway.message.ma_exec_response import MAExecResponse +from adf_core_python.core.gateway.message.ma_module_response import ( + MAModuleResponse, +) +from adf_core_python.core.gateway.message.moduleMessageFactory import ( + ModuleMessageFactory, +) +from adf_core_python.core.logger.logger import get_logger + +if TYPE_CHECKING: + from adf_core_python.core.gateway.gateway_launcher import GatewayLauncher + from adf_core_python.core.gateway.gateway_module import GatewayModule + + +class GatewayAgent: + def __init__(self, gateway_launcher: GatewayLauncher) -> None: + self._gateway_launcher = gateway_launcher + self.send_msg: Optional[Callable] = None + self._is_initialized = False + self._agent_info: Optional[AgentInfo] = None + self._world_info: Optional[WorldInfo] = None + self._scenario_info: Optional[ScenarioInfo] = None + self._gateway_modules: dict[str, GatewayModule] = {} + self._logger = get_logger(__name__) + + def get_module_count(self) -> int: + return len(self._gateway_modules) + + def add_gateway_module(self, gateway_module: GatewayModule) -> None: + self._gateway_modules[gateway_module.get_module_id()] = gateway_module + + def is_initialized(self) -> bool: + return self._is_initialized + + def set_initialize_data( + self, agent_info: AgentInfo, world_info: WorldInfo, scenario_info: ScenarioInfo + ) -> None: + self._agent_info = agent_info + self._world_info = world_info + self._scenario_info = scenario_info + + def initialize(self) -> None: + if self.send_msg is None: + raise RuntimeError("send_msg is None") + if ( + self._agent_info is None + or self._world_info is None + or self._scenario_info is None + ): + raise RuntimeError( + "Required variables is None, " + "You must exec set_initialized_data() before calling initialize()" + ) + + am_agent = AMAgent() + self.send_msg( + am_agent.write( + self._agent_info.get_entity_id(), + list(self._world_info.get_world_model().get_entities()), + self._scenario_info.get_config().config, + int(self._scenario_info.get_mode()), + ) + ) + self._is_initialized = True + + def update(self) -> None: + if self.send_msg is None: + raise RuntimeError("send_msg is None") + if self._agent_info is None or self._world_info is None: + raise RuntimeError( + "Required variables is None, " + "You must exec set_initialized_data() before calling update()" + ) + + am_update = AMUpdate() + self.send_msg( + am_update.write( + self._agent_info.get_time(), + self._world_info.get_change_set(), + self._agent_info.get_heard_commands(), + ) + ) + + def set_send_msg(self, connection_send_func: Callable) -> None: + self.send_msg = connection_send_func + + def message_received(self, msg: RCRSProto_pb2) -> None: + c_msg = ModuleMessageFactory().make_message(msg) + if isinstance(c_msg, MAModuleResponse): + if c_msg.module_id is None or c_msg.class_name is None: + raise RuntimeError("Failed to receive message") + + self._gateway_modules[c_msg.module_id].set_gateway_class_name( + c_msg.class_name + ) + self._gateway_modules[c_msg.module_id].set_is_initialized(True) + if isinstance(c_msg, MAExecResponse): + if c_msg.module_id is None: + raise RuntimeError("Failed to receive message") + + self._gateway_modules[c_msg.module_id].set_execute_response(c_msg.result) + self._gateway_modules[c_msg.module_id].set_is_executed(True) diff --git a/adf_core_python/core/component/gateway/gateway_launcher.py b/adf_core_python/core/gateway/gateway_launcher.py similarity index 91% rename from adf_core_python/core/component/gateway/gateway_launcher.py rename to adf_core_python/core/gateway/gateway_launcher.py index d4d096c..e320d24 100644 --- a/adf_core_python/core/component/gateway/gateway_launcher.py +++ b/adf_core_python/core/gateway/gateway_launcher.py @@ -2,7 +2,7 @@ from structlog import BoundLogger -from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent +from adf_core_python.core.gateway.gateway_agent import GatewayAgent from adf_core_python.core.launcher.connect.connection import Connection @@ -17,8 +17,6 @@ def make_connection(self) -> Connection: return Connection(self.host, self.port) def connect(self, gateway_agent: GatewayAgent) -> None: - # self.logger.bind(agent_id=gateway_agent.get_agent_entity_id()) - self.logger.info( f"{gateway_agent.__class__.__name__} connecting to {self.host}:{self.port}" ) diff --git a/adf_core_python/core/gateway/gateway_module.py b/adf_core_python/core/gateway/gateway_module.py new file mode 100644 index 0000000..e79b239 --- /dev/null +++ b/adf_core_python/core/gateway/gateway_module.py @@ -0,0 +1,83 @@ +import uuid +from typing import Optional + +from rcrs_core.config.config import Config + +from adf_core_python.core.gateway.gateway_agent import GatewayAgent +from adf_core_python.core.gateway.message.am_exec import AMExec +from adf_core_python.core.gateway.message.am_module import AMModule + + +class GatewayModule: + def __init__(self, gateway_agent: GatewayAgent): + self._gateway_agent = gateway_agent + self._module_id: str = str(uuid.uuid4()) + self._is_initialized = False + self._is_executed = False + self._gateway_class_name: str = "" + self._result: Optional[Config] = None + self._gateway_agent.add_gateway_module(self) + + def get_module_id(self) -> str: + return self._module_id + + def get_gateway_class_name(self) -> str: + return self._gateway_class_name + + def set_gateway_class_name(self, gateway_class_name: str) -> None: + self._gateway_class_name = gateway_class_name + + def get_is_initialized(self) -> bool: + return self._is_initialized + + def set_is_initialized(self, is_initialized: bool) -> None: + self._is_initialized = is_initialized + + def initialize(self, module_name: str, default_class_name: str) -> str: + if not self._gateway_agent.is_initialized(): + self._gateway_agent.initialize() + if self._gateway_agent.send_msg is None: + raise RuntimeError("send_msg is None") + + am_module = AMModule() + self._gateway_agent.send_msg( + am_module.write( + self._module_id, + module_name, + default_class_name, + ) + ) + + while not self.get_is_initialized(): + pass + + return self.get_gateway_class_name() + + def get_execute_response(self) -> Config: + return self._result + + def set_execute_response(self, result: Config) -> None: + self._result = result + + def get_is_executed(self) -> bool: + return self._is_executed + + def set_is_executed(self, _is_executed: bool) -> None: + self._is_executed = _is_executed + + def execute( + self, method_name: str, args: Optional[dict[str, str]] = None + ) -> Config: + if args is None: + args = {} + if self._gateway_agent.send_msg is None: + raise RuntimeError("send_msg is None") + + am_exec = AMExec() + self._gateway_agent.send_msg(am_exec.write(self._module_id, method_name, args)) + + while not self.get_is_executed(): + pass + + self.set_is_executed(False) + return self.get_execute_response() diff --git a/adf_core_python/core/gateway/message/__init__.py b/adf_core_python/core/gateway/message/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/component/gateway/message/am_agent.py b/adf_core_python/core/gateway/message/am_agent.py similarity index 62% rename from adf_core_python/core/component/gateway/message/am_agent.py rename to adf_core_python/core/gateway/message/am_agent.py index 0817e4c..3d6bc8d 100644 --- a/adf_core_python/core/component/gateway/message/am_agent.py +++ b/adf_core_python/core/gateway/message/am_agent.py @@ -5,7 +5,7 @@ from rcrs_core.messages.message import Message from rcrs_core.worldmodel.entityID import EntityID -from adf_core_python.core.component.gateway.message.urn.urn import ( +from adf_core_python.core.gateway.message.urn.urn import ( ModuleMSG, ComponentModuleMSG, ) @@ -18,12 +18,18 @@ def __init__(self) -> None: def read(self) -> None: pass - def write(self, agent_id: EntityID, entities: list[Entity]) -> Any: + def write( + self, + agent_id: EntityID, + entities: list[Entity], + config: dict[str, Any], + mode: int, + ) -> Any: entity_proto_list = [] for entity in entities: entity_proto = RCRSProto_pb2.EntityProto() entity_proto.urn = entity.get_urn() - entity_proto.entityID = entity.get_id() + entity_proto.entityID = entity.get_id().get_value() property_proto_list = [] for k, v in entity.get_properties().items(): @@ -33,8 +39,17 @@ def write(self, agent_id: EntityID, entities: list[Entity]) -> Any: entity_list_proto = RCRSProto_pb2.EntityListProto() entity_list_proto.entities.extend(entity_proto_list) + + config_proto = RCRSProto_pb2.ConfigProto() + for key, value in config.items(): + config_proto.data[str(key)] = str(value) + msg = RCRSProto_pb2.MessageProto() msg.urn = self.get_urn() msg.components[ComponentModuleMSG.AgentID].entityID = agent_id.get_value() - msg.components[ComponentModuleMSG.Entities].entityList = entity_list_proto + msg.components[ComponentModuleMSG.Entities].entityList.CopyFrom( + entity_list_proto + ) + msg.components[ComponentModuleMSG.Config].config.CopyFrom(config_proto) + msg.components[ComponentModuleMSG.Mode].intValue = mode return msg diff --git a/adf_core_python/core/component/gateway/message/am_exec.py b/adf_core_python/core/gateway/message/am_exec.py similarity index 61% rename from adf_core_python/core/component/gateway/message/am_exec.py rename to adf_core_python/core/gateway/message/am_exec.py index 69bb4f2..9bd36be 100644 --- a/adf_core_python/core/component/gateway/message/am_exec.py +++ b/adf_core_python/core/gateway/message/am_exec.py @@ -4,7 +4,7 @@ from rcrs_core.connection import RCRSProto_pb2 from rcrs_core.messages.message import Message -from adf_core_python.core.component.gateway.message.urn.urn import ( +from adf_core_python.core.gateway.message.urn.urn import ( ModuleMSG, ComponentModuleMSG, ) @@ -17,10 +17,14 @@ def __init__(self) -> None: def read(self) -> None: pass - def write(self, module_id: str, method_name: str) -> Any: + def write(self, module_id: str, method_name: str, arguments: dict[str, str]) -> Any: msg = RCRSProto_pb2.MessageProto() msg.urn = self.get_urn() msg.components[ComponentModuleMSG.ModuleID].stringValue = module_id msg.components[ComponentModuleMSG.MethodName].stringValue = method_name + config_proto = RCRSProto_pb2.ConfigProto() + for key, value in arguments.items(): + config_proto.data[key] = value + msg.components[ComponentModuleMSG.Arguments].config.CopyFrom(config_proto) return msg diff --git a/adf_core_python/core/component/gateway/message/am_module.py b/adf_core_python/core/gateway/message/am_module.py similarity index 78% rename from adf_core_python/core/component/gateway/message/am_module.py rename to adf_core_python/core/gateway/message/am_module.py index 8de6bfe..0dd26bc 100644 --- a/adf_core_python/core/component/gateway/message/am_module.py +++ b/adf_core_python/core/gateway/message/am_module.py @@ -2,9 +2,8 @@ from rcrs_core.connection import RCRSProto_pb2 from rcrs_core.messages.message import Message -from rcrs_core.worldmodel.entityID import EntityID -from adf_core_python.core.component.gateway.message.urn.urn import ( +from adf_core_python.core.gateway.message.urn.urn import ( ModuleMSG, ComponentModuleMSG, ) @@ -19,14 +18,12 @@ def read(self) -> None: def write( self, - agent_id: EntityID, module_id: str, module_name: str, default_class_name: str, ) -> Any: msg = RCRSProto_pb2.MessageProto() msg.urn = self.get_urn() - msg.components[ComponentModuleMSG.AgentID].entityID = agent_id msg.components[ComponentModuleMSG.ModuleID].stringValue = module_id msg.components[ComponentModuleMSG.ModuleName].stringValue = module_name msg.components[ diff --git a/adf_core_python/core/gateway/message/am_update.py b/adf_core_python/core/gateway/message/am_update.py new file mode 100644 index 0000000..0988920 --- /dev/null +++ b/adf_core_python/core/gateway/message/am_update.py @@ -0,0 +1,38 @@ +from abc import ABC +from typing import Any + +from rcrs_core.commands.Command import Command +from rcrs_core.connection import RCRSProto_pb2 +from rcrs_core.messages.message import Message +from rcrs_core.worldmodel.changeSet import ChangeSet + +from adf_core_python.core.gateway.message.urn.urn import ( + ModuleMSG, + ComponentModuleMSG, +) + + +class AMUpdate(Message, ABC): + def __init__(self) -> None: + super().__init__(ModuleMSG.AM_UPDATE) + + def read(self) -> None: + pass + + def write(self, time: int, changed: ChangeSet, heard: list[Command]) -> Any: + msg = RCRSProto_pb2.MessageProto() + msg.urn = self.get_urn() + msg.components[ComponentModuleMSG.Time].intValue = time + msg.components[ComponentModuleMSG.Changed].changeSet.CopyFrom( + changed.to_change_set_proto() + ) + message_list_proto = RCRSProto_pb2.MessageListProto() + message_proto_list = [] + if heard is not None: + for h in heard: + message_proto_list.append(h.prepare_cmd()) + message_list_proto.commands.extend(message_proto_list) + msg.components[ComponentModuleMSG.Heard].commandList.CopyFrom( + message_list_proto + ) + return msg diff --git a/adf_core_python/core/gateway/message/ma_exec_response.py b/adf_core_python/core/gateway/message/ma_exec_response.py new file mode 100644 index 0000000..8c68fbc --- /dev/null +++ b/adf_core_python/core/gateway/message/ma_exec_response.py @@ -0,0 +1,28 @@ +from abc import ABC + +from rcrs_core.config.config import Config +from rcrs_core.connection import RCRSProto_pb2 +from rcrs_core.messages.message import Message + +from adf_core_python.core.gateway.message.urn.urn import ( + ModuleMSG, + ComponentModuleMSG, +) + + +class MAExecResponse(Message, ABC): + def __init__(self, data: RCRSProto_pb2) -> None: + super().__init__(ModuleMSG.MA_EXEC_RESPONSE) + self.module_id = None + self.result = Config() + self.data = data + self.read() + + def read(self) -> None: + self.module_id = self.data.components[ComponentModuleMSG.ModuleID].stringValue + result = self.data.components[ComponentModuleMSG.Result].config + for key, value in result.data.items(): + self.result.set_value(key, value) + + def write(self) -> None: + pass diff --git a/adf_core_python/core/component/gateway/message/ma_module_response.py b/adf_core_python/core/gateway/message/ma_module_response.py similarity index 55% rename from adf_core_python/core/component/gateway/message/ma_module_response.py rename to adf_core_python/core/gateway/message/ma_module_response.py index d164325..c680419 100644 --- a/adf_core_python/core/component/gateway/message/ma_module_response.py +++ b/adf_core_python/core/gateway/message/ma_module_response.py @@ -1,9 +1,10 @@ from abc import ABC +from typing import Optional from rcrs_core.connection import RCRSProto_pb2 from rcrs_core.messages.message import Message -from adf_core_python.core.component.gateway.message.urn.urn import ( +from adf_core_python.core.gateway.message.urn.urn import ( ModuleMSG, ComponentModuleMSG, ) @@ -11,16 +12,15 @@ class MAModuleResponse(Message, ABC): def __init__(self, data: RCRSProto_pb2) -> None: - super().__init__(ModuleMSG) - self.module_id = None - self.class_names = None + super().__init__(ModuleMSG.MA_MODULE_RESPONSE) + self.module_id: Optional[str] = None + self.class_name: Optional[str] = None self.data = data + self.read() def read(self) -> None: self.module_id = self.data.components[ComponentModuleMSG.ModuleID].stringValue - self.class_names = self.data.components[ - ComponentModuleMSG.ClassNames - ].stringList + self.class_name = self.data.components[ComponentModuleMSG.ClassName].stringValue def write(self) -> None: pass diff --git a/adf_core_python/core/gateway/message/moduleMessageFactory.py b/adf_core_python/core/gateway/message/moduleMessageFactory.py new file mode 100644 index 0000000..6fac9d8 --- /dev/null +++ b/adf_core_python/core/gateway/message/moduleMessageFactory.py @@ -0,0 +1,24 @@ +from typing import Optional + +from rcrs_core.connection import RCRSProto_pb2 + +from adf_core_python.core.gateway.message.ma_exec_response import MAExecResponse +from adf_core_python.core.gateway.message.ma_module_response import ( + MAModuleResponse, +) +from adf_core_python.core.gateway.message.urn.urn import ModuleMSG + + +class ModuleMessageFactory: + def __init__(self) -> None: + pass + + def make_message( + self, msg: RCRSProto_pb2 + ) -> Optional[MAModuleResponse | MAExecResponse]: + if msg.urn == ModuleMSG.MA_MODULE_RESPONSE: + return MAModuleResponse(msg) + elif msg.urn == ModuleMSG.MA_EXEC_RESPONSE: + return MAExecResponse(msg) + + return None diff --git a/adf_core_python/core/gateway/message/urn/__init__.py b/adf_core_python/core/gateway/message/urn/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adf_core_python/core/component/gateway/message/urn/urn.py b/adf_core_python/core/gateway/message/urn/urn.py similarity index 50% rename from adf_core_python/core/component/gateway/message/urn/urn.py rename to adf_core_python/core/gateway/message/urn/urn.py index 363b931..d38e435 100644 --- a/adf_core_python/core/component/gateway/message/urn/urn.py +++ b/adf_core_python/core/gateway/message/urn/urn.py @@ -7,16 +7,21 @@ class ModuleMSG(IntEnum): MA_MODULE_RESPONSE = 0x0303 AM_UPDATE = 0x0304 AM_EXEC = 0x0305 + MA_EXEC_RESPONSE = 0x0306 class ComponentModuleMSG(IntEnum): AgentID = 0x0401 Entities = 0x0402 - ModuleName = 0x0403 - DefaultClassName = 0x0404 + Config = 0x0403 + Mode = 0x0404 ModuleID = 0x0405 - ClassNames = 0x0406 - Time = 0x0407 - Changed = 0x0408 - Heard = 0x0409 - MethodName = 0x0410 + ModuleName = 0x0406 + DefaultClassName = 0x0407 + ClassName = 0x0408 + Time = 0x0409 + Changed = 0x040A + Heard = 0x040B + MethodName = 0x040C + Arguments = 0x040D + Result = 0x040E diff --git a/adf_core_python/core/gateway/module_dict.py b/adf_core_python/core/gateway/module_dict.py new file mode 100644 index 0000000..7f668c6 --- /dev/null +++ b/adf_core_python/core/gateway/module_dict.py @@ -0,0 +1,32 @@ +from typing import Optional + + +class ModuleDict: + def __init__(self, module_dict: Optional[dict[str, str]] = None): + self.module_dict: dict[str, str] = { + "adf_core_python.component.module.algorithm.Clustering": "adf_core_python.core.gateway.component.module.complex.gateway_clustering.GatewayClustering", + "adf_core_python.component.module.algorithm.DynamicClustering": "adf_core_python.core.gateway.component.module.complex.gateway_clustering.GatewayClustering", + "adf_core_python.component.module.algorithm.StaticClustering": "adf_core_python.core.gateway.component.module.complex.gateway_clustering.GatewayClustering", + "adf_core_python.component.module.algorithm.PathPlanning": "adf_core_python.core.gateway.component.module.complex.gateway_path_planning.GatewayPathPlanning", + "adf_core_python.component.module.complex.TargetDetector": "adf_core_python.core.gateway.component.module.complex.gateway_target_detector.GatewayTargetDetector", + "adf_core_python.component.module.complex.HumanDetector": "adf_core_python.core.gateway.component.module.complex.gateway_human_detector.GatewayHumanDetector", + "adf_core_python.component.module.complex.RoadDetector": "adf_core_python.core.gateway.component.module.complex.gateway_road_detector.GatewayRoadDetector", + "adf_core_python.component.module.complex.Search": "adf_core_python.core.gateway.component.module.complex.gateway_search.GatewaySearch", + "adf_core_python.component.module.complex.TargetAllocator": "adf_core_python.core.gateway.component.module.complex.gateway_target_allocator.GatewayTargetAllocator", + "adf_core_python.component.module.complex.AmbulanceTargetAllocator": "adf_core_python.core.gateway.component.module.complex.gateway_ambulance_target_allocator.GatewayAmbulanceTargetAllocator", + "adf_core_python.component.module.complex.FireTargetAllocator": "adf_core_python.core.gateway.component.module.complex.gateway_fire_target_allocator.GatewayFireTargetAllocator", + "adf_core_python.component.module.complex.PoliceTargetAllocator": "adf_core_python.core.gateway.component.module.complex.gateway_fire_target_allocator.GatewayPoliceTargetAllocator", + } + if module_dict is not None: + for key, value in module_dict.items(): + self.module_dict[key] = value + + def __getitem__(self, key: str) -> Optional[str]: + if not isinstance(key, str): + raise TypeError("TypeError: Key must be a string") + return self.module_dict.get(key) + + def __setitem__(self, key: str, value: str) -> None: + if not isinstance(key, str): + raise TypeError("TypeError: Key must be a string") + self.module_dict[key] = value diff --git a/adf_core_python/core/launcher/agent_launcher.py b/adf_core_python/core/launcher/agent_launcher.py index eda337d..63f31bc 100644 --- a/adf_core_python/core/launcher/agent_launcher.py +++ b/adf_core_python/core/launcher/agent_launcher.py @@ -1,9 +1,10 @@ import importlib import threading +from typing import Optional from adf_core_python.core.component.abstract_loader import AbstractLoader -from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.config.config import Config +from adf_core_python.core.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector @@ -66,17 +67,18 @@ def launch(self) -> None: kernel_host, kernel_port, self.logger ) - gateway_host: str = self.config.get_value( - ConfigKey.KEY_GATEWAY_HOST, "localhost" - ) - gateway_port: int = self.config.get_value(ConfigKey.KEY_GATEWAY_PORT, 27930) - self.logger.info( - f"Start gateway launcher (host: {kernel_host}, port: {kernel_port})" - ) + gateway_launcher: Optional[GatewayLauncher] = None + gateway_flag: bool = self.config.get_value(ConfigKey.KEY_GATEWAY_FLAG, False) + if gateway_flag: + gateway_host: str = self.config.get_value( + ConfigKey.KEY_GATEWAY_HOST, "localhost" + ) + gateway_port: int = self.config.get_value(ConfigKey.KEY_GATEWAY_PORT, 27941) + self.logger.info( + f"Start gateway launcher (host: {gateway_host}, port: {gateway_port})" + ) - gateway_launcher: GatewayLauncher = GatewayLauncher( - gateway_host, gateway_port, self.logger - ) + gateway_launcher = GatewayLauncher(gateway_host, gateway_port, self.logger) connector_thread_list: list[threading.Thread] = [] for connector in self.connectors: diff --git a/adf_core_python/core/launcher/config_key.py b/adf_core_python/core/launcher/config_key.py index dcd987f..8253f47 100644 --- a/adf_core_python/core/launcher/config_key.py +++ b/adf_core_python/core/launcher/config_key.py @@ -27,3 +27,4 @@ class ConfigKey: # Gateway KEY_GATEWAY_HOST: Final[str] = "gateway.host" KEY_GATEWAY_PORT: Final[str] = "gateway.port" + KEY_GATEWAY_FLAG: Final[str] = "adf.gateway.flag" diff --git a/adf_core_python/core/launcher/connect/connector.py b/adf_core_python/core/launcher/connect/connector.py index 408e904..a36a04c 100644 --- a/adf_core_python/core/launcher/connect/connector.py +++ b/adf_core_python/core/launcher/connect/connector.py @@ -1,9 +1,10 @@ import threading from abc import ABC, abstractmethod +from typing import Optional from adf_core_python.core.component.abstract_loader import AbstractLoader -from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.config.config import Config +from adf_core_python.core.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher @@ -15,7 +16,7 @@ def __init__(self) -> None: def connect( self, component_launcher: ComponentLauncher, - gateway_launcher: GatewayLauncher, + gateway_launcher: Optional[GatewayLauncher], config: Config, loader: AbstractLoader, ) -> dict[threading.Thread, threading.Event]: diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_center.py b/adf_core_python/core/launcher/connect/connector_ambulance_center.py index 985bcc3..4e716a2 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_center.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_center.py @@ -1,4 +1,5 @@ import threading +from typing import Optional from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -7,8 +8,9 @@ from adf_core_python.core.component.tactics.tactics_ambulance_center import ( TacticsAmbulanceCenter, ) -from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.config.config import Config +from adf_core_python.core.gateway.gateway_agent import GatewayAgent +from adf_core_python.core.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector @@ -23,7 +25,7 @@ def __init__(self) -> None: def connect( self, component_launcher: ComponentLauncher, - gateway_launcher: GatewayLauncher, + gateway_launcher: Optional[GatewayLauncher], config: Config, loader: AbstractLoader, ) -> dict[threading.Thread, threading.Event]: @@ -59,7 +61,12 @@ def connect( finish_post_connect_event = threading.Event() request_id: int = component_launcher.generate_request_id() - thread = threading.Thread( + + gateway_agent: Optional[GatewayAgent] = None + if isinstance(gateway_launcher, GatewayLauncher): + gateway_agent = GatewayAgent(gateway_launcher) + + component_thread = threading.Thread( target=component_launcher.connect, args=( OfficeAmbulance( @@ -71,11 +78,19 @@ def connect( module_config, develop_data, finish_post_connect_event, + gateway_agent, ), request_id, ), name=f"AmbulanceCenterAgent-{request_id}", ) - threads[thread] = finish_post_connect_event + threads[component_thread] = finish_post_connect_event + + if isinstance(gateway_launcher, GatewayLauncher) and isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + threads[gateway_thread] = finish_post_connect_event return threads diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index f27bf8a..3e8d47e 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -1,15 +1,16 @@ import threading +from typing import Optional from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.platoon.platoon_ambulance import PlatoonAmbulance from adf_core_python.core.component.abstract_loader import AbstractLoader -from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent -from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.component.tactics.tactics_ambulance_team import ( TacticsAmbulanceTeam, ) from adf_core_python.core.config.config import Config +from adf_core_python.core.gateway.gateway_agent import GatewayAgent +from adf_core_python.core.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector @@ -24,7 +25,7 @@ def __init__(self) -> None: def connect( self, component_launcher: ComponentLauncher, - gateway_launcher: GatewayLauncher, + gateway_launcher: Optional[GatewayLauncher], config: Config, loader: AbstractLoader, ) -> dict[threading.Thread, threading.Event]: @@ -61,7 +62,9 @@ def connect( finish_post_connect_event = threading.Event() request_id: int = component_launcher.generate_request_id() - gateway_agent: GatewayAgent = GatewayAgent(gateway_launcher) + gateway_agent: Optional[GatewayAgent] = None + if isinstance(gateway_launcher, GatewayLauncher): + gateway_agent = GatewayAgent(gateway_launcher) component_thread = threading.Thread( target=component_launcher.connect, @@ -83,10 +86,11 @@ def connect( ) threads[component_thread] = finish_post_connect_event - gateway_thread = threading.Thread( - target=gateway_launcher.connect, - args=(gateway_agent,), - ) - threads[gateway_thread] = finish_post_connect_event + if isinstance(gateway_launcher, GatewayLauncher) and isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + threads[gateway_thread] = finish_post_connect_event return threads diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/adf_core_python/core/launcher/connect/connector_fire_brigade.py index 4cee656..df420a2 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_brigade.py +++ b/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -1,15 +1,16 @@ import threading +from typing import Optional from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.platoon.platoon_fire import PlatoonFire from adf_core_python.core.component.abstract_loader import AbstractLoader -from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent -from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.component.tactics.tactics_fire_brigade import ( TacticsFireBrigade, ) from adf_core_python.core.config.config import Config +from adf_core_python.core.gateway.gateway_agent import GatewayAgent +from adf_core_python.core.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector @@ -24,7 +25,7 @@ def __init__(self) -> None: def connect( self, component_launcher: ComponentLauncher, - gateway_launcher: GatewayLauncher, + gateway_launcher: Optional[GatewayLauncher], config: Config, loader: AbstractLoader, ) -> dict[threading.Thread, threading.Event]: @@ -59,7 +60,9 @@ def connect( finish_post_connect_event = threading.Event() request_id: int = component_launcher.generate_request_id() - gateway_agent: GatewayAgent = GatewayAgent(gateway_launcher) + gateway_agent: Optional[GatewayAgent] = None + if isinstance(gateway_launcher, GatewayLauncher): + gateway_agent = GatewayAgent(gateway_launcher) component_thread = threading.Thread( target=component_launcher.connect, @@ -81,10 +84,11 @@ def connect( ) threads[component_thread] = finish_post_connect_event - gateway_thread = threading.Thread( - target=gateway_launcher.connect, - args=(gateway_agent,), - ) - threads[gateway_thread] = finish_post_connect_event + if isinstance(gateway_launcher, GatewayLauncher) and isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + threads[gateway_thread] = finish_post_connect_event return threads diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/adf_core_python/core/launcher/connect/connector_fire_station.py index df0fdbc..5cb7d01 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_station.py +++ b/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -1,4 +1,5 @@ import threading +from typing import Optional from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -7,8 +8,9 @@ from adf_core_python.core.component.tactics.tactics_fire_station import ( TacticsFireStation, ) -from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.config.config import Config +from adf_core_python.core.gateway.gateway_agent import GatewayAgent +from adf_core_python.core.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector @@ -23,7 +25,7 @@ def __init__(self) -> None: def connect( self, component_launcher: ComponentLauncher, - gateway_launcher: GatewayLauncher, + gateway_launcher: Optional[GatewayLauncher], config: Config, loader: AbstractLoader, ) -> dict[threading.Thread, threading.Event]: @@ -55,9 +57,14 @@ def connect( precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/fire_station" - request_id: int = component_launcher.generate_request_id() finish_post_connect_event = threading.Event() - thread = threading.Thread( + request_id: int = component_launcher.generate_request_id() + + gateway_agent: Optional[GatewayAgent] = None + if isinstance(gateway_launcher, GatewayLauncher): + gateway_agent = GatewayAgent(gateway_launcher) + + component_thread = threading.Thread( target=component_launcher.connect, args=( OfficeFire( @@ -69,11 +76,19 @@ def connect( module_config, develop_data, finish_post_connect_event, + gateway_agent, ), request_id, ), name=f"FireStationAgent-{request_id}", ) - threads[thread] = finish_post_connect_event + threads[component_thread] = finish_post_connect_event + + if isinstance(gateway_launcher, GatewayLauncher) and isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + threads[gateway_thread] = finish_post_connect_event return threads diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py index 028211d..9b5e822 100644 --- a/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -1,15 +1,16 @@ import threading +from typing import Optional from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.platoon.platoon_police import PlatoonPolice from adf_core_python.core.component.abstract_loader import AbstractLoader -from adf_core_python.core.component.gateway.gateway_agent import GatewayAgent -from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.component.tactics.tactics_police_force import ( TacticsPoliceForce, ) from adf_core_python.core.config.config import Config +from adf_core_python.core.gateway.gateway_agent import GatewayAgent +from adf_core_python.core.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector @@ -24,7 +25,7 @@ def __init__(self) -> None: def connect( self, component_launcher: ComponentLauncher, - gateway_launcher: GatewayLauncher, + gateway_launcher: Optional[GatewayLauncher], config: Config, loader: AbstractLoader, ) -> dict[threading.Thread, threading.Event]: @@ -59,7 +60,9 @@ def connect( finish_post_connect_event = threading.Event() request_id: int = component_launcher.generate_request_id() - gateway_agent: GatewayAgent = GatewayAgent(gateway_launcher) + gateway_agent: Optional[GatewayAgent] = None + if isinstance(gateway_launcher, GatewayLauncher): + gateway_agent = GatewayAgent(gateway_launcher) component_thread = threading.Thread( target=component_launcher.connect, @@ -81,10 +84,13 @@ def connect( ) threads[component_thread] = finish_post_connect_event - gateway_thread = threading.Thread( - target=gateway_launcher.connect, - args=(gateway_agent,), - ) - threads[gateway_thread] = finish_post_connect_event + if isinstance(gateway_launcher, GatewayLauncher) and isinstance( + gateway_agent, GatewayAgent + ): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + threads[gateway_thread] = finish_post_connect_event return threads diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py index eea1a89..1553790 100644 --- a/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -1,4 +1,5 @@ import threading +from typing import Optional from adf_core_python.core.agent.config.module_config import ModuleConfig from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -7,8 +8,9 @@ from adf_core_python.core.component.tactics.tactics_police_office import ( TacticsPoliceOffice, ) -from adf_core_python.core.component.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.config.config import Config +from adf_core_python.core.gateway.gateway_agent import GatewayAgent +from adf_core_python.core.gateway.gateway_launcher import GatewayLauncher from adf_core_python.core.launcher.config_key import ConfigKey from adf_core_python.core.launcher.connect.component_launcher import ComponentLauncher from adf_core_python.core.launcher.connect.connector import Connector @@ -23,7 +25,7 @@ def __init__(self) -> None: def connect( self, component_launcher: ComponentLauncher, - gateway_launcher: GatewayLauncher, + gateway_launcher: Optional[GatewayLauncher], config: Config, loader: AbstractLoader, ) -> dict[threading.Thread, threading.Event]: @@ -57,9 +59,14 @@ def connect( precompute_data_dir: str = f"{config.get_value(ConfigKey.KEY_PRECOMPUTE_DATA_DIR, 'precompute')}/police_office" - request_id: int = component_launcher.generate_request_id() finish_post_connect_event = threading.Event() - thread = threading.Thread( + request_id: int = component_launcher.generate_request_id() + + gateway_agent: Optional[GatewayAgent] = None + if isinstance(gateway_launcher, GatewayLauncher): + gateway_agent = GatewayAgent(gateway_launcher) + + component_thread = threading.Thread( target=component_launcher.connect, args=( OfficePolice( @@ -71,11 +78,19 @@ def connect( module_config, develop_data, finish_post_connect_event, + gateway_agent, ), request_id, ), name=f"PoliceOfficeAgent-{request_id}", ) - threads[thread] = finish_post_connect_event + threads[component_thread] = finish_post_connect_event + + if isinstance(gateway_launcher, GatewayLauncher) and isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + threads[gateway_thread] = finish_post_connect_event return threads diff --git a/adf_core_python/implement/action/default_extend_action_clear.py b/adf_core_python/implement/action/default_extend_action_clear.py index d23c8d1..b3e3bb0 100644 --- a/adf_core_python/implement/action/default_extend_action_clear.py +++ b/adf_core_python/implement/action/default_extend_action_clear.py @@ -541,7 +541,7 @@ def _is_intersecting_blockades( if line1.intersects(line2): return True - for i in range(0, len(apexes1) - 2, 2): + for i in range(0, len(apexes2) - 2, 2): line1 = LineString([(apexes1[-2], apexes1[-1]), (apexes1[0], apexes1[1])]) line2 = LineString( [(apexes2[i], apexes2[i + 1]), (apexes2[i + 2], apexes2[i + 3])] diff --git a/adf_core_python/implement/module/complex/java_human_detector.py b/adf_core_python/implement/module/complex/java_human_detector.py deleted file mode 100644 index 47e4507..0000000 --- a/adf_core_python/implement/module/complex/java_human_detector.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import Optional - -from rcrs_core.worldmodel.entityID import EntityID - -from adf_core_python.core.agent.communication.message_manager import MessageManager -from adf_core_python.core.agent.develop.develop_data import DevelopData -from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import ScenarioInfo -from adf_core_python.core.agent.info.world_info import WorldInfo -from adf_core_python.core.agent.module.module_manager import ModuleManager -from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData -from adf_core_python.core.component.module.complex.human_detector import HumanDetector -from adf_core_python.core.component.module.complex.target_detector import ( - TargetDetector, - T, -) - - -class JavaHumanDetector(HumanDetector): - def __init__( - self, - agent_info: AgentInfo, - world_info: WorldInfo, - scenario_info: ScenarioInfo, - module_manager: ModuleManager, - develop_data: DevelopData, - ) -> None: - super().__init__( - agent_info, world_info, scenario_info, module_manager, develop_data - ) - - def precompute(self, precompute_data: PrecomputeData) -> HumanDetector: - super().precompute(precompute_data) - return self - - def resume(self, precompute_data: PrecomputeData) -> HumanDetector: - super().resume(precompute_data) - return self - - def prepare(self) -> HumanDetector: - super().prepare() - return self - - def calculate(self) -> HumanDetector: - pass - - def get_target_entity_id(self) -> Optional[EntityID]: - pass - - def update_info(self, message_manager: MessageManager) -> TargetDetector[T]: - return super().update_info(message_manager) diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 83d0cdf..0f98784 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -39,18 +39,19 @@ def initialize( message_manager, develop_data, ) + self._search: Search = cast( Search, module_manager.get_module( "DefaultTacticsAmbulanceTeam.Search", - "adf_core_python.core.component.module.complex.search.Search", + "adf_core_python.implement.module.complex.default_search.DefaultSearch", ), ) self._human_detector: HumanDetector = cast( HumanDetector, module_manager.get_module( "DefaultTacticsAmbulanceTeam.HumanDetector", - "adf_core_python.core.component.module.complex.human_detector.HumanDetector", + "adf_core_python.implement.module.complex.default_human_detector.DefaultHumanDetector", ), ) self._action_transport = module_manager.get_extend_action( @@ -61,6 +62,7 @@ def initialize( "DefaultTacticsAmbulanceTeam.ExtendActionMove", "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", ) + self.register_module(self._search) self.register_module(self._human_detector) self.register_action(self._action_transport) diff --git a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py index d0f8512..e4e09ec 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py @@ -44,14 +44,14 @@ def initialize( Search, module_manager.get_module( "DefaultTacticsFireBrigade.Search", - "adf_core_python.core.component.module.complex.search.Search", + "adf_core_python.implement.module.complex.default_search.DefaultSearch", ), ) self._human_detector: HumanDetector = cast( HumanDetector, module_manager.get_module( "DefaultTacticsFireBrigade.HumanDetector", - "adf_core_python.core.component.module.complex.human_detector.HumanDetector", + "adf_core_python.implement.module.complex.default_human_detector.DefaultHumanDetector", ), ) self._action_rescue = module_manager.get_extend_action( @@ -62,6 +62,7 @@ def initialize( "DefaultTacticsAmbulanceTeam.ExtendActionMove", "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", ) + self.register_module(self._search) self.register_module(self._human_detector) self.register_action(self._action_rescue) diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py index 63836a3..bec3434 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -47,7 +47,7 @@ def initialize( Search, module_manager.get_module( "DefaultTacticsPoliceForce.Search", - "adf_core_python.core.component.module.complex.search.Search", + "adf_core_python.implement.module.complex.default_search.DefaultSearch", ), ) self._road_detector: RoadDetector = cast( @@ -65,6 +65,7 @@ def initialize( "DefaultTacticsPoliceForce.ExtendActionMove", "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", ) + self.register_module(self._search) self.register_module(self._road_detector) self.register_action(self._action_ext_clear) diff --git a/adf_core_python/launcher.py b/adf_core_python/launcher.py index bd2025d..a34d21c 100644 --- a/adf_core_python/launcher.py +++ b/adf_core_python/launcher.py @@ -78,6 +78,11 @@ def __init__( help="precompute flag", ) parser.add_argument("--debug", action="store_true", help="debug flag") + parser.add_argument( + "--java", + action="store_true", + help="using java module flag", + ) args = parser.parse_args() config_map = { @@ -91,6 +96,7 @@ def __init__( ConfigKey.KEY_POLICE_OFFICE_COUNT: args.policeoffice, ConfigKey.KEY_PRECOMPUTE: args.precompute, ConfigKey.KEY_DEBUG_FLAG: args.debug, + ConfigKey.KEY_GATEWAY_FLAG: args.java, } for key, value in config_map.items(): diff --git a/config/launcher.yaml b/config/launcher.yaml index 54d6b53..7423a6c 100644 --- a/config/launcher.yaml +++ b/config/launcher.yaml @@ -4,7 +4,7 @@ kernel: gateway: host: localhost - port: 27920 + port: 27941 team: name: AIT-Rescue @@ -39,3 +39,6 @@ adf: count: 5 police: count: 5 + + gateway: + flag: false diff --git a/java/.gitignore b/java/.gitignore index 1b6985c..4158e07 100644 --- a/java/.gitignore +++ b/java/.gitignore @@ -3,3 +3,5 @@ # Ignore Gradle build output directory build + +!lib \ No newline at end of file diff --git a/java/lib/build.gradle b/java/lib/build.gradle new file mode 100644 index 0000000..de61a5d --- /dev/null +++ b/java/lib/build.gradle @@ -0,0 +1,57 @@ +plugins { + id('java-library') + id('com.google.protobuf') version "0.9.4" +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'com.github.roborescue:rcrs-server:master-SNAPSHOT' + implementation 'com.github.roborescue:adf-core-java:master-SNAPSHOT' + + // PrecomputeData + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.13.0' + implementation 'com.fasterxml.jackson.core:jackson-core:2.13.0' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.1' + implementation 'org.msgpack:jackson-dataformat-msgpack:0.9.0' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.1' + + // Protobuf + implementation 'com.google.protobuf:protobuf-java:4.28.3' + + // Annotation + implementation 'jakarta.annotation:jakarta.annotation-api:3.0.0' + + // Logger + implementation 'org.apache.logging.log4j:log4j-core:2.24.2' + implementation 'org.apache.logging.log4j:log4j-api:2.24.2' + + testImplementation libs.junit.jupiter + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +repositories { + mavenCentral() + + maven { + url = 'https://sourceforge.net/projects/jsi/files/m2_repo' + } + maven { + url = 'https://repo.enonic.com/public/' + } + maven { + url 'https://jitpack.io' + } +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/java/lib/src/main/java/adf_core_python/Main.java b/java/lib/src/main/java/adf_core_python/Main.java new file mode 100644 index 0000000..d6fbd5f --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/Main.java @@ -0,0 +1,10 @@ +package adf_core_python; + +import adf_core_python.gateway.Gateway; + +public class Main { + public static void main(String[] args) { + Gateway gateway = new Gateway(27941); + gateway.start(); + } +} diff --git a/java/lib/src/main/java/adf_core_python/agent/Agent.java b/java/lib/src/main/java/adf_core_python/agent/Agent.java new file mode 100644 index 0000000..07c9f0a --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/agent/Agent.java @@ -0,0 +1,116 @@ +package adf_core_python.agent; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.config.ModuleConfig; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.agent.module.ModuleManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.AbstractModule; +import adf_core_python.gateway.mapper.AbstractMapper; +import adf_core_python.gateway.mapper.MapperDict; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import rescuecore2.config.Config; +import rescuecore2.messages.Command; +import rescuecore2.standard.entities.StandardEntityURN; +import rescuecore2.standard.entities.StandardWorldModel; +import rescuecore2.worldmodel.ChangeSet; +import rescuecore2.worldmodel.Entity; +import rescuecore2.worldmodel.EntityID; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Objects; + +public class Agent { + private final AgentInfo agentInfo; + private final WorldInfo worldInfo; + private final ScenarioInfo scenarioInfo; + private final ModuleManager moduleManager; + private final DevelopData developData; + private final PrecomputeData precomputeData; + private final MessageManager messageManager; + private final HashMap modules = new HashMap<>(); + private final MapperDict mapperDict; + private final Logger logger; + + public Agent(EntityID entityID, Collection entities, ScenarioInfo scenarioInfo, DevelopData developData, ModuleConfig moduleConfig) { + StandardWorldModel worldModel = new StandardWorldModel(); + worldModel.addEntities(entities); + worldModel.index(); + + this.agentInfo = new AgentInfo(entityID, worldModel); + this.worldInfo = new WorldInfo(worldModel); + this.scenarioInfo = scenarioInfo; + this.developData = developData; + this.moduleManager = new ModuleManager(this.agentInfo, this.worldInfo, this.scenarioInfo, moduleConfig, this.developData); + + String dataStorageName = ""; + StandardEntityURN agentURN = Objects.requireNonNull(this.worldInfo.getEntity(this.agentInfo.getID())).getStandardURN(); + if (agentURN == StandardEntityURN.AMBULANCE_TEAM || agentURN == StandardEntityURN.AMBULANCE_CENTRE) { + dataStorageName = "ambulance.bin"; + } + if (agentURN == StandardEntityURN.FIRE_BRIGADE || agentURN == StandardEntityURN.FIRE_STATION) { + dataStorageName = "fire.bin"; + } + if (agentURN == StandardEntityURN.POLICE_FORCE || agentURN == StandardEntityURN.POLICE_OFFICE) { + dataStorageName = "police.bin"; + } + + this.precomputeData = new PrecomputeData(dataStorageName); + this.messageManager = new MessageManager(); + + this.mapperDict = new MapperDict(); + + logger = LogManager.getLogger(this.getClass()); + logger.debug("New Agent Created (EntityID: {}, All Entities: {})", this.agentInfo.getID(), this.worldInfo.getAllEntities()); + } + + public Class registerModule(@Nonnull String moduleID, @Nonnull String moduleName, @Nullable String defaultClassName) { + AbstractModule abstractModule = moduleManager.getModule(moduleName, defaultClassName); + if (abstractModule == null) return null; + Class clazz = abstractModule.getClass(); + while (clazz.getSuperclass() != null) { + if (mapperDict.getMapper(clazz) != null) { + Class mapperClass = mapperDict.getMapper(clazz); + try { + Constructor constructor = mapperClass.getConstructor(clazz, precomputeData.getClass(), messageManager.getClass()); + AbstractMapper mapperInstance = constructor.newInstance(abstractModule, precomputeData, messageManager); + modules.put(moduleID, mapperInstance); + logger.debug("Registered Human Detector (ModuleID: {}, Instance: {})", moduleID, modules.get(moduleID)); + return mapperInstance.getTargetClass(); + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | + InvocationTargetException e) { + throw new RuntimeException(e); + } + } + clazz = clazz.getSuperclass(); + } + return null; + } + + public void update(int time, ChangeSet changed, Collection heard) { + agentInfo.recordThinkStartTime(); + agentInfo.setTime(time); + agentInfo.setHeard(heard); + agentInfo.setChanged(changed); + worldInfo.setTime(time); + worldInfo.merge(changed); + worldInfo.setChanged(changed); + logger.debug("Agent Update (Time: {}, Changed: {}, Heard: {})", agentInfo.getTime(), agentInfo.getChanged(), agentInfo.getHeard()); + } + + public Config execModuleMethod(String moduleID, String methodName, Config arguments) { + logger.debug("Executing Method (MethodName: {}, Arguments: {}", methodName, arguments); + Config result = modules.get(moduleID).execMethod(methodName, arguments); + logger.debug("Executed Method Result (MethodName: {}, Result: {}", methodName, result); + return result; + } +} diff --git a/java/lib/src/main/java/adf_core_python/agent/config/ModuleConfig.java b/java/lib/src/main/java/adf_core_python/agent/config/ModuleConfig.java new file mode 100644 index 0000000..3232d7a --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/agent/config/ModuleConfig.java @@ -0,0 +1,39 @@ +package adf_core_python.agent.config; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class ModuleConfig { + private static final String DEFAULT_CONFIG_FILE_NAME = "../config/module.yaml"; + private final JsonNode rootNode; + + public ModuleConfig() { + this(DEFAULT_CONFIG_FILE_NAME); + } + + public ModuleConfig(String configFileName) { + try { + String yamlString = Files.readString(Paths.get(configFileName)); + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + rootNode = mapper.readTree(yamlString); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public String getValue(String moduleName) { + String[] keys = moduleName.split("\\."); + JsonNode moduleNode = rootNode; + for (String key : keys) { + if (moduleNode.has(key)) { + moduleNode = moduleNode.get(key); + } + } + return moduleNode.asText(); + } +} diff --git a/java/lib/src/main/java/adf_core_python/agent/develop/DevelopData.java b/java/lib/src/main/java/adf_core_python/agent/develop/DevelopData.java new file mode 100644 index 0000000..00652e6 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/agent/develop/DevelopData.java @@ -0,0 +1,40 @@ +package adf_core_python.agent.develop; + +import jakarta.annotation.Nonnull; + +import java.util.List; +import java.util.Map; + +public class DevelopData { + private boolean developFlag = false; + + private Map intValues; + private Map doubleValues; + private Map stringValues; + private Map boolValues; + + private Map> intLists; + private Map> doubleLists; + private Map> stringLists; + private Map> boolLists; + + @Nonnull + public Integer getInteger(@Nonnull String name, int defaultValue) { + if (this.developFlag) { + Integer value = this.intValues.get(name); + if (value == null) { + String rawData = this.stringValues.get(name); + if (rawData != null && !rawData.equals("")) { + value = Integer.valueOf(rawData); + } + if (value != null) { + this.intValues.put(name, value); + } + } + if (value != null) { + return value; + } + } + return defaultValue; + } +} diff --git a/java/lib/src/main/java/adf_core_python/agent/info/AgentInfo.java b/java/lib/src/main/java/adf_core_python/agent/info/AgentInfo.java new file mode 100644 index 0000000..e0d63d1 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/agent/info/AgentInfo.java @@ -0,0 +1,119 @@ +package adf_core_python.agent.info; + +import adf.core.agent.action.Action; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import rescuecore2.messages.Command; +import rescuecore2.standard.entities.*; +import rescuecore2.worldmodel.ChangeSet; +import rescuecore2.worldmodel.EntityID; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class AgentInfo { + private final EntityID entityID; + private final StandardWorldModel worldModel; + private int time; + private ChangeSet changed; + private Collection heard; + private long thinkStartTime; + + private Map actionHistory; + + public AgentInfo(@Nonnull EntityID entityID, @Nonnull StandardWorldModel worldModel) { + this.entityID = entityID; + this.worldModel = worldModel; + this.time = 0; + this.actionHistory = new HashMap<>(); + recordThinkStartTime(); + } + + public int getTime() { + return this.time; + } + + public void setTime(int time) { + this.time = time; + } + + @Nullable + public Collection getHeard() { + return this.heard; + } + + public void setHeard(Collection heard) { + this.heard = heard; + } + + @Nonnull + public EntityID getID() { + return this.entityID; + } + + public StandardEntity me() { + return this.worldModel.getEntity(this.getID()); + } + + public double getX() { + return this.worldModel.getEntity(this.entityID).getLocation(this.worldModel).first(); + } + + public double getY() { + return this.worldModel.getEntity(this.entityID).getLocation(this.worldModel).second(); + } + + public EntityID getPosition() { + StandardEntity entity = this.worldModel.getEntity(this.getID()); + return entity instanceof Human ? ((Human) entity).getPosition() : entity.getID(); + } + + @Nonnull + public Area getPositionArea() { + return (Area) this.worldModel.getEntity(this.getPosition()); + } + + @Nullable + public ChangeSet getChanged() { + return this.changed; + } + + public void setChanged(ChangeSet changed) { + this.changed = changed; + } + + @Nullable + public Human someoneOnBoard() { + + for (StandardEntity next : this.worldModel + .getEntitiesOfType(StandardEntityURN.CIVILIAN)) { + Human human = (Human) next; + if (human.getPosition().equals(this.entityID)) { + return human; + } + } + + return null; + } + + @Nullable + public Action getExecutedAction(int time) { + if (time > 0) + return this.actionHistory.get(time); + return this.actionHistory.get(this.getTime() + time); + } + + + public void setExecutedAction(int time, @Nullable Action action) { + this.actionHistory.put(time > 0 ? time : this.getTime() + time, action); + } + + public void recordThinkStartTime() { + this.thinkStartTime = System.currentTimeMillis(); + } + + public long getThinkTimeMillis() { + return (System.currentTimeMillis() - this.thinkStartTime); + } +} diff --git a/java/lib/src/main/java/adf_core_python/agent/module/ModuleManager.java b/java/lib/src/main/java/adf_core_python/agent/module/ModuleManager.java new file mode 100644 index 0000000..abc0621 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/agent/module/ModuleManager.java @@ -0,0 +1,427 @@ +package adf_core_python.agent.module; + +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf.core.component.centralized.CommandExecutor; +import adf.core.component.centralized.CommandPicker; +import adf.core.component.communication.ChannelSubscriber; +import adf.core.component.communication.CommunicationMessage; +import adf.core.component.communication.MessageCoordinator; +import adf.core.component.extaction.ExtAction; +import adf_core_python.agent.config.ModuleConfig; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.component.module.AbstractModule; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import rescuecore2.config.NoSuchConfigOptionException; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + +public class ModuleManager { + private final Logger logger; + private Map moduleMap; + private Map actionMap; + private Map> executorMap; + private Map pickerMap; + private Map channelSubscriberMap; + private Map messageCoordinatorMap; + private AgentInfo agentInfo; + private WorldInfo worldInfo; + private ScenarioInfo scenarioInfo; + private ModuleConfig moduleConfig; + private DevelopData developData; + + public ModuleManager(@Nonnull AgentInfo agentInfo, @Nonnull WorldInfo worldInfo, @Nonnull ScenarioInfo scenarioInfo, @Nonnull ModuleConfig moduleConfig, @Nonnull DevelopData developData) { + this.agentInfo = agentInfo; + this.worldInfo = worldInfo; + this.scenarioInfo = scenarioInfo; + this.moduleConfig = moduleConfig; + this.developData = developData; + this.moduleMap = new HashMap<>(); + this.actionMap = new HashMap<>(); + this.executorMap = new HashMap<>(); + this.pickerMap = new HashMap<>(); + this.channelSubscriberMap = new HashMap<>(1); + this.messageCoordinatorMap = new HashMap<>(1); + + logger = LogManager.getLogger(this.getClass()); + } + + @SuppressWarnings("unchecked") + @Nullable + public final T + getModule(@Nonnull String moduleName, @Nullable String defaultClassName) { + String className = moduleName; + try { + className = this.moduleConfig.getValue(moduleName); + } catch (NoSuchConfigOptionException ignored) { + } + + try { + Class moduleClass; + try { + moduleClass = Class.forName(className); + } catch (ClassNotFoundException | NullPointerException e) { + logger.warn("Module " + moduleName + " not found. Using default class " + defaultClassName); + className = defaultClassName; + moduleClass = Class.forName(className); + } + + AbstractModule instance = this.moduleMap.get(className); + if (instance != null) { + return (T) instance; + } + + if (AbstractModule.class.isAssignableFrom(moduleClass)) { + instance = this.getModule((Class) moduleClass); + this.moduleMap.put(className, instance); + return (T) instance; + } + + } catch (ClassNotFoundException | NullPointerException e) { + return null; + } + + throw new IllegalArgumentException( + "Module name is not found : " + className); + } + + + @Nonnull + public final T + getModule(@Nonnull String moduleName) { + return this.getModule(moduleName, ""); + } + + + @Nonnull + private AbstractModule getModule(@Nonnull Class moduleClass) { + try { + Constructor constructor = moduleClass.getConstructor( + AgentInfo.class, WorldInfo.class, ScenarioInfo.class, + ModuleManager.class, DevelopData.class); + AbstractModule instance = constructor.newInstance(this.agentInfo, + this.worldInfo, this.scenarioInfo, this, this.developData); + this.moduleMap.put(moduleClass.getCanonicalName(), instance); + return instance; + } catch (NoSuchMethodException | InstantiationException + | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + + @SuppressWarnings("unchecked") + @Nonnull + public final ExtAction getExtAction(String actionName, + String defaultClassName) { + String className = actionName; + try { + className = this.moduleConfig.getValue(actionName); + } catch (NoSuchConfigOptionException ignored) { + } + + try { + Class actionClass; + try { + actionClass = Class.forName(className); + } catch (ClassNotFoundException | NullPointerException e) { + className = defaultClassName; + actionClass = Class.forName(className); + } + + ExtAction instance = this.actionMap.get(className); + if (instance != null) { + return instance; + } + + if (ExtAction.class.isAssignableFrom(actionClass)) { + instance = this.getExtAction((Class) actionClass); + this.actionMap.put(className, instance); + return instance; + } + } catch (ClassNotFoundException | NullPointerException e) { + throw new RuntimeException(e); + } + throw new IllegalArgumentException( + "ExtAction name is not found : " + className); + } + + + @Nonnull + public final ExtAction getExtAction(String actionName) { + return getExtAction(actionName, ""); + } + + + @Nonnull + private ExtAction getExtAction(Class actionClass) { + try { + Constructor constructor = actionClass.getConstructor( + AgentInfo.class, WorldInfo.class, ScenarioInfo.class, + ModuleManager.class, DevelopData.class); + ExtAction instance = constructor.newInstance(this.agentInfo, + this.worldInfo, this.scenarioInfo, this, this.developData); + this.actionMap.put(actionClass.getCanonicalName(), instance); + return instance; + } catch (NoSuchMethodException | InstantiationException + | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + + @SuppressWarnings("unchecked") + @Nonnull + public final > E + getCommandExecutor(String executorName, String defaultClassName) { + String className = executorName; + try { + className = this.moduleConfig.getValue(executorName); + } catch (NoSuchConfigOptionException ignored) { + } + + try { + Class actionClass; + try { + actionClass = Class.forName(className); + } catch (ClassNotFoundException | NullPointerException e) { + className = defaultClassName; + actionClass = Class.forName(className); + } + + CommandExecutor< + CommunicationMessage> instance = this.executorMap.get(className); + if (instance != null) { + return (E) instance; + } + + if (CommandExecutor.class.isAssignableFrom(actionClass)) { + instance = this.getCommandExecutor( + (Class>) actionClass); + this.executorMap.put(className, instance); + return (E) instance; + } + } catch (ClassNotFoundException | NullPointerException e) { + throw new RuntimeException(e); + } + + throw new IllegalArgumentException( + "CommandExecutor name is not found : " + className); + } + + + @Nonnull + public final > E + getCommandExecutor(String executorName) { + return getCommandExecutor(executorName, ""); + } + + + @SuppressWarnings("unchecked") + @Nonnull + private > E + getCommandExecutor(Class actionClass) { + try { + Constructor constructor = actionClass.getConstructor(AgentInfo.class, + WorldInfo.class, ScenarioInfo.class, ModuleManager.class, + DevelopData.class); + E instance = constructor.newInstance(this.agentInfo, this.worldInfo, + this.scenarioInfo, this, this.developData); + this.executorMap.put(actionClass.getCanonicalName(), + (CommandExecutor) instance); + return instance; + } catch (NoSuchMethodException | InstantiationException + | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + + @SuppressWarnings("unchecked") + @Nonnull + public final CommandPicker getCommandPicker(String pickerName, + String defaultClassName) { + String className = pickerName; + try { + className = this.moduleConfig.getValue(pickerName); + } catch (NoSuchConfigOptionException ignored) { + } + + try { + Class actionClass; + try { + actionClass = Class.forName(className); + } catch (ClassNotFoundException | NullPointerException e) { + className = defaultClassName; + actionClass = Class.forName(className); + } + + CommandPicker instance = this.pickerMap.get(className); + if (instance != null) { + return instance; + } + + if (CommandPicker.class.isAssignableFrom(actionClass)) { + instance = this.getCommandPicker((Class) actionClass); + this.pickerMap.put(className, instance); + return instance; + } + } catch (ClassNotFoundException | NullPointerException e) { + throw new RuntimeException(e); + } + + throw new IllegalArgumentException( + "CommandExecutor name is not found : " + className); + } + + + @Nonnull + public final CommandPicker getCommandPicker(String pickerName) { + return getCommandPicker(pickerName, ""); + } + + + @Nonnull + private CommandPicker getCommandPicker(Class actionClass) { + try { + Constructor constructor = actionClass.getConstructor( + AgentInfo.class, WorldInfo.class, ScenarioInfo.class, + ModuleManager.class, DevelopData.class); + CommandPicker instance = constructor.newInstance(this.agentInfo, + this.worldInfo, this.scenarioInfo, this, this.developData); + this.pickerMap.put(actionClass.getCanonicalName(), instance); + return instance; + } catch (NoSuchMethodException | InstantiationException + | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + + @SuppressWarnings("unchecked") + public final ChannelSubscriber getChannelSubscriber(String subscriberName, + String defaultClassName) { + String className = subscriberName; + try { + className = this.moduleConfig.getValue(subscriberName); + } catch (NoSuchConfigOptionException ignored) { + } + + try { + Class actionClass; + try { + actionClass = Class.forName(className); + } catch (ClassNotFoundException | NullPointerException e) { + className = defaultClassName; + actionClass = Class.forName(className); + } + + ChannelSubscriber instance = this.channelSubscriberMap.get(className); + if (instance != null) { + return instance; + } + + if (ChannelSubscriber.class.isAssignableFrom(actionClass)) { + instance = this + .getChannelSubscriber((Class) actionClass); + this.channelSubscriberMap.put(className, instance); + return instance; + } + } catch (ClassNotFoundException | NullPointerException e) { + throw new RuntimeException(e); + } + + throw new IllegalArgumentException( + "channelSubscriber name is not found : " + className); + } + + + public final ChannelSubscriber getChannelSubscriber(String subscriberName) { + return getChannelSubscriber(subscriberName, ""); + } + + + public final ChannelSubscriber + getChannelSubscriber(Class subsClass) { + try { + Constructor constructor = subsClass.getConstructor(); + ChannelSubscriber instance = constructor.newInstance(); + this.channelSubscriberMap.put(subsClass.getCanonicalName(), instance); + return instance; + } catch (NoSuchMethodException | InstantiationException + | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + + @SuppressWarnings("unchecked") + public final MessageCoordinator getMessageCoordinator(String coordinatorName, + String defaultClassName) { + String className = coordinatorName; + try { + className = this.moduleConfig.getValue(coordinatorName); + } catch (NoSuchConfigOptionException ignored) { + } + + try { + Class actionClass; + try { + actionClass = Class.forName(className); + } catch (ClassNotFoundException | NullPointerException e) { + className = defaultClassName; + actionClass = Class.forName(className); + } + + MessageCoordinator instance = this.messageCoordinatorMap.get(className); + if (instance != null) { + return instance; + } + + if (MessageCoordinator.class.isAssignableFrom(actionClass)) { + instance = this + .getMessageCoordinator((Class) actionClass); + this.messageCoordinatorMap.put(className, instance); + return instance; + } + } catch (ClassNotFoundException | NullPointerException e) { + throw new RuntimeException(e); + } + + throw new IllegalArgumentException( + "channelSubscriber name is not found : " + className); + } + + + public final MessageCoordinator + getMessageCoordinator(String coordinatorName) { + return getMessageCoordinator(coordinatorName, ""); + } + + + public final MessageCoordinator + getMessageCoordinator(Class subsClass) { + try { + Constructor constructor = subsClass.getConstructor(); + MessageCoordinator instance = constructor.newInstance(); + this.messageCoordinatorMap.put(subsClass.getCanonicalName(), instance); + return instance; + } catch (NoSuchMethodException | InstantiationException + | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + + @Nonnull + public ModuleConfig getModuleConfig() { + return this.moduleConfig; + } +} diff --git a/java/lib/src/main/java/adf_core_python/agent/precompute/PreData.java b/java/lib/src/main/java/adf_core_python/agent/precompute/PreData.java new file mode 100644 index 0000000..847b4f7 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/agent/precompute/PreData.java @@ -0,0 +1,56 @@ +package adf_core_python.agent.precompute; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class PreData { + + public Map intValues; + public Map doubleValues; + public Map stringValues; + public Map idValues; + public Map boolValues; + + public Map> intLists; + public Map> doubleLists; + public Map> stringLists; + public Map> idLists; + public Map> boolLists; + + public boolean isReady; + public String readyID; + + public PreData() { + this.intValues = new HashMap<>(); + this.doubleValues = new HashMap<>(); + this.stringValues = new HashMap<>(); + this.idValues = new HashMap<>(); + this.boolValues = new HashMap<>(); + this.intLists = new HashMap<>(); + this.doubleLists = new HashMap<>(); + this.stringLists = new HashMap<>(); + this.idLists = new HashMap<>(); + this.boolLists = new HashMap<>(); + this.isReady = false; + this.readyID = ""; + } + + + public PreData copy() { + PreData preData = new PreData(); + preData.intValues = new HashMap<>(this.intValues); + preData.doubleValues = new HashMap<>(this.doubleValues); + preData.stringValues = new HashMap<>(this.stringValues); + preData.idValues = new HashMap<>(this.idValues); + preData.boolValues = new HashMap<>(this.boolValues); + preData.intLists = new HashMap<>(this.intLists); + preData.doubleLists = new HashMap<>(this.doubleLists); + preData.stringLists = new HashMap<>(this.stringLists); + preData.idLists = new HashMap<>(this.idLists); + preData.boolLists = new HashMap<>(this.boolLists); + preData.isReady = this.isReady; + preData.readyID = this.readyID; + return preData; + } +} diff --git a/java/lib/src/main/java/adf_core_python/agent/precompute/PrecomputeData.java b/java/lib/src/main/java/adf_core_python/agent/precompute/PrecomputeData.java new file mode 100644 index 0000000..d838c68 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/agent/precompute/PrecomputeData.java @@ -0,0 +1,400 @@ +package adf_core_python.agent.precompute; + +import adf.core.agent.info.WorldInfo; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.msgpack.jackson.dataformat.MessagePackFactory; +import rescuecore2.worldmodel.EntityID; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public final class PrecomputeData { + + public static final String DEFAULT_FILE_NAME = "data.bin"; + public static final File PRECOMP_DATA_DIR = new File("precomp_data"); + + private final String fileName; + + private PreData data; + + public PrecomputeData() { + this(DEFAULT_FILE_NAME); + } + + + public PrecomputeData(String name) { + this.fileName = name; + this.init(); + } + + + private PrecomputeData(String name, PreData precomputeDatas) { + this.fileName = name; + this.data = precomputeDatas; + } + + + public static void removeData(String name) { + if (!PRECOMP_DATA_DIR.exists()) { + return; + } + + File file = new File(PRECOMP_DATA_DIR, name); + if (!file.exists()) { + return; + } + + file.delete(); + } + + + public static void removeData() { + removeData(DEFAULT_FILE_NAME); + } + + + public PrecomputeData copy() { + return new PrecomputeData(this.fileName, this.data.copy()); + } + + + private void init() { + this.data = this.read(this.fileName); + if (this.data == null) { + this.data = new PreData(); + } + } + + + private PreData read(String name) { + try { + if (!PRECOMP_DATA_DIR.exists()) { + if (!PRECOMP_DATA_DIR.mkdir()) { + return null; + } + } + + File readFile = new File(PRECOMP_DATA_DIR, name); + if (!readFile.exists()) { + return null; + } + + FileInputStream fis = new FileInputStream(readFile); + BufferedInputStream bis = new BufferedInputStream(fis); + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + byte[] binary = new byte[1024]; + while (true) { + int len = bis.read(binary); + if (len < 0) { + break; + } + bout.write(binary, 0, len); + } + + binary = bout.toByteArray(); + ObjectMapper om = new ObjectMapper(new MessagePackFactory()); + PreData ds = om.readValue(binary, PreData.class); + bis.close(); + fis.close(); + return ds; + } catch (IOException e) { + return null; + } + } + + + public boolean write() { + try { + if (!PRECOMP_DATA_DIR.exists()) { + if (!PRECOMP_DATA_DIR.mkdir()) { + return false; + } + } + ObjectMapper om = new ObjectMapper(new MessagePackFactory()); + byte[] binary = om.writeValueAsBytes(this.data); + FileOutputStream fos = new FileOutputStream( + new File(PRECOMP_DATA_DIR, this.fileName)); + fos.write(binary); + fos.close(); + return true; + } catch (IOException e) { + e.printStackTrace(); + ; + return false; + } + } + + + public Integer setInteger(String name, int value) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.intValues.put(callClassName + ":" + name, value); + } + + + public Double setDouble(String name, double value) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.doubleValues.put(callClassName + ":" + name, value); + } + + + public Boolean setBoolean(String name, boolean value) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.boolValues.put(callClassName + ":" + name, value); + } + + + public String setString(String name, String value) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.stringValues.put(callClassName + ":" + name, value); + } + + + public EntityID setEntityID(String name, EntityID value) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + Integer id = this.data.idValues.put(callClassName + ":" + name, + value.getValue()); + return id == null ? null : new EntityID(id); + } + + + public List setIntegerList(String name, List list) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.intLists.put(callClassName + ":" + name, list); + } + + + public List setDoubleList(String name, List list) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.doubleLists.put(callClassName + ":" + name, list); + } + + + public List setStringList(String name, List list) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.stringLists.put(callClassName + ":" + name, list); + } + + + public List setEntityIDList(String name, List list) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + List cvtList = new ArrayList<>(); + for (EntityID id : list) { + cvtList.add(id.getValue()); + } + + cvtList = this.data.idLists.put(callClassName + ":" + name, cvtList); + return cvtList == null ? null + : cvtList.stream().map(EntityID::new).collect(Collectors.toList()); + } + + + public List setBooleanList(String name, List list) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.boolLists.put(callClassName + ":" + name, list); + } + + + public boolean setReady(boolean isReady, WorldInfo worldInfo) { + this.data.isReady = isReady; + this.data.readyID = makeReadyID(worldInfo); + return (this.data.isReady + && this.data.readyID.equals(this.makeReadyID(worldInfo))); + } + + + public Integer getInteger(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.intValues.get(callClassName + ":" + name); + } + + + public Double getDouble(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.doubleValues.get(callClassName + ":" + name); + } + + + public Boolean getBoolean(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.boolValues.get(callClassName + ":" + name); + } + + + public String getString(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.stringValues.get(callClassName + ":" + name); + } + + + public EntityID getEntityID(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + Integer id = this.data.idValues.get(callClassName + ":" + name); + return id == null ? null : new EntityID(id); + } + + + public List getIntegerList(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.intLists.get(callClassName + ":" + name); + } + + + public List getDoubleList(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.doubleLists.get(callClassName + ":" + name); + } + + + public List getStringList(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.stringLists.get(callClassName + ":" + name); + } + + + public List getEntityIDList(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + List cvtList = this.data.idLists.get(callClassName + ":" + name); + return cvtList == null ? null + : cvtList.stream().map(EntityID::new).collect(Collectors.toList()); + } + + + public List getBooleanList(String name) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + if (stackTraceElements.length == 0) { + return null; + } + + String callClassName = stackTraceElements[2].getClassName(); + return this.data.boolLists.get(callClassName + ":" + name); + } + + + public boolean isReady(WorldInfo worldInfo) { + return (this.data.isReady + && this.data.readyID.equals(this.makeReadyID(worldInfo))); + } + + + private String makeReadyID(WorldInfo worldInfo) { + return "" + worldInfo.getBounds().getX() + worldInfo.getBounds().getY() + + worldInfo.getAllEntities().size(); + } +} diff --git a/java/lib/src/main/java/adf_core_python/component/extaction/ExtAction.java b/java/lib/src/main/java/adf_core_python/component/extaction/ExtAction.java new file mode 100644 index 0000000..0dad82a --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/extaction/ExtAction.java @@ -0,0 +1,137 @@ +package adf_core_python.component.extaction; + +import adf.core.agent.action.Action; +import adf.core.agent.communication.MessageManager; +import adf.core.agent.develop.DevelopData; +import adf.core.agent.info.AgentInfo; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf.core.agent.module.ModuleManager; +import adf.core.agent.precompute.PrecomputeData; +import rescuecore2.worldmodel.EntityID; + +abstract public class ExtAction { + + protected ScenarioInfo scenarioInfo; + protected AgentInfo agentInfo; + protected WorldInfo worldInfo; + protected ModuleManager moduleManager; + protected DevelopData developData; + protected Action result; + private int countPrecompute; + private int countResume; + private int countPreparate; + private int countUpdateInfo; + private int countUpdateInfoCurrentTime; + + public ExtAction(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + this.worldInfo = wi; + this.agentInfo = ai; + this.scenarioInfo = si; + this.moduleManager = moduleManager; + this.developData = developData; + this.result = null; + this.countPrecompute = 0; + this.countResume = 0; + this.countPreparate = 0; + this.countUpdateInfo = 0; + this.countUpdateInfoCurrentTime = 0; + } + + + public abstract ExtAction setTarget(EntityID targets); + + + /** + * @param targets target + * @return ExtAction + * @deprecated {@link #setTarget(EntityID)} + */ + @Deprecated + public ExtAction setTarget(EntityID... targets) { + if (targets != null && targets.length > 0) { + return this.setTarget(targets[0]); + } + return this; + } + + + public abstract ExtAction calc(); + + + public Action getAction() { + return result; + } + + + public ExtAction precompute(PrecomputeData precomputeData) { + this.countPrecompute++; + return this; + } + + + public ExtAction resume(PrecomputeData precomputeData) { + this.countResume++; + return this; + } + + + public ExtAction preparate() { + this.countPreparate++; + return this; + } + + + public ExtAction updateInfo(MessageManager messageManager) { + if (this.countUpdateInfoCurrentTime != this.agentInfo.getTime()) { + this.countUpdateInfo = 0; + this.countUpdateInfoCurrentTime = this.agentInfo.getTime(); + } + this.countUpdateInfo++; + return this; + } + + + public int getCountPrecompute() { + return this.countPrecompute; + } + + + public int getCountResume() { + return this.countResume; + } + + + public int getCountPreparate() { + return this.countPreparate; + } + + + public int getCountUpdateInfo() { + if (this.countUpdateInfoCurrentTime != this.agentInfo.getTime()) { + this.countUpdateInfo = 0; + this.countUpdateInfoCurrentTime = this.agentInfo.getTime(); + } + return this.countUpdateInfo; + } + + + public void resetCountPrecompute() { + this.countPrecompute = 0; + } + + + public void resetCountResume() { + this.countResume = 0; + } + + + public void resetCountPreparate() { + this.countPreparate = 0; + } + + + public void resetCountUpdateInfo() { + this.countUpdateInfo = 0; + } +} diff --git a/java/lib/src/main/java/adf_core_python/component/module/AbstractModule.java b/java/lib/src/main/java/adf_core_python/component/module/AbstractModule.java new file mode 100644 index 0000000..830ca29 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/module/AbstractModule.java @@ -0,0 +1,138 @@ +package adf_core_python.component.module; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.agent.module.ModuleManager; +import adf_core_python.agent.precompute.PrecomputeData; + +import java.util.ArrayList; +import java.util.List; + +public abstract class AbstractModule { + + private final List subModules = new ArrayList<>(); + protected AgentInfo agentInfo; + protected WorldInfo worldInfo; + protected ScenarioInfo scenarioInfo; + protected ModuleManager moduleManager; + protected DevelopData developData; + + private int countPrecompute; + private int countResume; + private int countPreparate; + private int countUpdateInfo; + private int countUpdateInfoCurrentTime; + + public AbstractModule(AgentInfo agentInfo, WorldInfo worldInfo, ScenarioInfo scenarioInfo, ModuleManager moduleManager, DevelopData developData) { + this.agentInfo = agentInfo; + this.worldInfo = worldInfo; + this.scenarioInfo = scenarioInfo; + this.moduleManager = moduleManager; + this.developData = developData; + this.countPrecompute = 0; + this.countResume = 0; + this.countPreparate = 0; + this.countUpdateInfo = 0; + this.countUpdateInfoCurrentTime = 0; + } + + + protected void registerModule(AbstractModule module) { + subModules.add(module); + } + + + protected boolean unregisterModule(AbstractModule module) { + return subModules.remove(module); + } + + + public AbstractModule precompute(PrecomputeData precomputeData) { + this.countPrecompute++; + for (AbstractModule abstractModule : subModules) { + abstractModule.precompute(precomputeData); + } + return this; + } + + + public AbstractModule resume(PrecomputeData precomputeData) { + this.countResume++; + for (AbstractModule abstractModule : subModules) { + abstractModule.resume(precomputeData); + } + return this; + } + + + public AbstractModule preparate() { + this.countPreparate++; + for (AbstractModule abstractModule : subModules) { + abstractModule.preparate(); + } + return this; + } + + + public AbstractModule updateInfo(MessageManager messageManager) { + if (this.countUpdateInfoCurrentTime != this.agentInfo.getTime()) { + this.countUpdateInfo = 0; + this.countUpdateInfoCurrentTime = this.agentInfo.getTime(); + } + for (AbstractModule abstractModule : subModules) { + abstractModule.updateInfo(messageManager); + } + this.countUpdateInfo++; + return this; + } + + + public abstract AbstractModule calc(); + + + public int getCountPrecompute() { + return this.countPrecompute; + } + + + public int getCountResume() { + return this.countResume; + } + + + public int getCountPreparate() { + return this.countPreparate; + } + + + public int getCountUpdateInfo() { + if (this.countUpdateInfoCurrentTime != this.agentInfo.getTime()) { + this.countUpdateInfo = 0; + this.countUpdateInfoCurrentTime = this.agentInfo.getTime(); + } + return this.countUpdateInfo; + } + + + public void resetCountPrecompute() { + this.countPrecompute = 0; + } + + + public void resetCountResume() { + this.countResume = 0; + } + + + public void resetCountPreparate() { + this.countPreparate = 0; + } + + + public void resetCountUpdateInfo() { + this.countUpdateInfo = 0; + } +} diff --git a/java/lib/src/main/java/adf_core_python/component/module/algorithm/Clustering.java b/java/lib/src/main/java/adf_core_python/component/module/algorithm/Clustering.java new file mode 100644 index 0000000..aba296d --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/module/algorithm/Clustering.java @@ -0,0 +1,86 @@ +package adf_core_python.component.module.algorithm; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.agent.module.ModuleManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.AbstractModule; +import rescuecore2.standard.entities.StandardEntity; +import rescuecore2.worldmodel.EntityID; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public abstract class Clustering extends AbstractModule { + + public Clustering(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + } + + + public abstract int getClusterNumber(); + + public abstract int getClusterIndex(StandardEntity entity); + + public abstract int getClusterIndex(EntityID id); + + public abstract Collection getClusterEntities(int index); + + public abstract Collection getClusterEntityIDs(int index); + + + public List> getAllClusterEntities() { + int number = this.getClusterNumber(); + List> result = new ArrayList<>(number); + for (int i = 0; i < number; i++) { + result.add(i, this.getClusterEntities(i)); + } + return result; + } + + + public List> getAllClusterEntityIDs() { + int number = this.getClusterNumber(); + List> result = new ArrayList<>(number); + for (int i = 0; i < number; i++) { + result.add(i, this.getClusterEntityIDs(i)); + } + return result; + } + + + @Override + public Clustering precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + return this; + } + + + @Override + public Clustering resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + return this; + } + + + @Override + public Clustering preparate() { + super.preparate(); + return this; + } + + + @Override + public Clustering updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + return this; + } + + + @Override + public abstract Clustering calc(); +} diff --git a/java/lib/src/main/java/adf_core_python/component/module/algorithm/DynamicClustering.java b/java/lib/src/main/java/adf_core_python/component/module/algorithm/DynamicClustering.java new file mode 100644 index 0000000..208d200 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/module/algorithm/DynamicClustering.java @@ -0,0 +1,14 @@ +package adf_core_python.component.module.algorithm; + +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.agent.module.ModuleManager; + +public abstract class DynamicClustering extends Clustering { + + public DynamicClustering(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + } +} diff --git a/java/lib/src/main/java/adf_core_python/component/module/algorithm/PathPlanning.java b/java/lib/src/main/java/adf_core_python/component/module/algorithm/PathPlanning.java new file mode 100644 index 0000000..6101ac0 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/module/algorithm/PathPlanning.java @@ -0,0 +1,101 @@ +package adf_core_python.component.module.algorithm; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.agent.module.ModuleManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.AbstractModule; +import rescuecore2.misc.Pair; +import rescuecore2.worldmodel.EntityID; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +public abstract class PathPlanning extends AbstractModule { + + public PathPlanning(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + } + + + public abstract List getResult(); + + public abstract PathPlanning setFrom(EntityID id); + + public abstract PathPlanning setDestination(Collection targets); + + + public PathPlanning setDestination(EntityID... targets) { + return this.setDestination(Arrays.asList(targets)); + } + + + @Override + public PathPlanning precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + return this; + } + + + @Override + public PathPlanning resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + return this; + } + + + @Override + public PathPlanning preparate() { + super.preparate(); + return this; + } + + + @Override + public PathPlanning updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + return this; + } + + + @Override + public abstract PathPlanning calc(); + + + public double getDistance() { + double sum = 0.0; + List path = getResult(); + if (path == null || path.size() <= 1) { + return sum; + } + + Pair prevPoint = null; + for (EntityID id : path) { + Pair point = worldInfo.getLocation(worldInfo.getEntity(id)); + if (prevPoint != null) { + int x = prevPoint.first() - point.first(); + int y = prevPoint.second() - point.second(); + sum += x * x + y * y; + } + prevPoint = point; + } + + return Math.sqrt(sum); + } + + + // Alias + public double getDistance(EntityID from, EntityID dest) { + return this.setFrom(from).setDestination(dest).calc().getDistance(); + } + + + public List getResult(EntityID from, EntityID dest) { + return this.setFrom(from).setDestination(dest).calc().getResult(); + } +} diff --git a/java/lib/src/main/java/adf_core_python/component/module/algorithm/StaticClustering.java b/java/lib/src/main/java/adf_core_python/component/module/algorithm/StaticClustering.java new file mode 100644 index 0000000..b9647da --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/module/algorithm/StaticClustering.java @@ -0,0 +1,14 @@ +package adf_core_python.component.module.algorithm; + +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.agent.module.ModuleManager; + +public abstract class StaticClustering extends Clustering { + + public StaticClustering(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + } +} diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/AmbulanceTargetAllocator.java b/java/lib/src/main/java/adf_core_python/component/module/complex/AmbulanceTargetAllocator.java new file mode 100644 index 0000000..41f2e7b --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/module/complex/AmbulanceTargetAllocator.java @@ -0,0 +1,47 @@ +package adf_core_python.component.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.agent.module.ModuleManager; +import adf_core_python.agent.precompute.PrecomputeData; +import rescuecore2.worldmodel.EntityID; + +import java.util.Map; + +public abstract class AmbulanceTargetAllocator extends TargetAllocator { + + public AmbulanceTargetAllocator(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + } + + + @Override + public abstract Map getResult(); + + @Override + public abstract AmbulanceTargetAllocator calc(); + + + @Override + public AmbulanceTargetAllocator resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + return this; + } + + + @Override + public AmbulanceTargetAllocator preparate() { + super.preparate(); + return this; + } + + + @Override + public AmbulanceTargetAllocator updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + return this; + } +} diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/BuildingDetector.java b/java/lib/src/main/java/adf_core_python/component/module/complex/BuildingDetector.java new file mode 100644 index 0000000..34567c8 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/module/complex/BuildingDetector.java @@ -0,0 +1,49 @@ +package adf_core_python.component.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.agent.module.ModuleManager; +import adf_core_python.agent.precompute.PrecomputeData; +import rescuecore2.standard.entities.Building; + +public abstract class BuildingDetector extends TargetDetector { + + public BuildingDetector(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + } + + + @Override + public BuildingDetector precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + return this; + } + + + @Override + public BuildingDetector resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + return this; + } + + + @Override + public BuildingDetector preparate() { + super.preparate(); + return this; + } + + + @Override + public BuildingDetector updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + return this; + } + + + @Override + public abstract BuildingDetector calc(); +} diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/FireTargetAllocator.java b/java/lib/src/main/java/adf_core_python/component/module/complex/FireTargetAllocator.java new file mode 100644 index 0000000..65c757e --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/module/complex/FireTargetAllocator.java @@ -0,0 +1,47 @@ +package adf_core_python.component.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.agent.module.ModuleManager; +import adf_core_python.agent.precompute.PrecomputeData; +import rescuecore2.worldmodel.EntityID; + +import java.util.Map; + +public abstract class FireTargetAllocator extends TargetAllocator { + + public FireTargetAllocator(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + } + + + @Override + public abstract Map getResult(); + + @Override + public abstract FireTargetAllocator calc(); + + + @Override + public FireTargetAllocator resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + return this; + } + + + @Override + public FireTargetAllocator preparate() { + super.preparate(); + return this; + } + + + @Override + public FireTargetAllocator updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + return this; + } +} diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/HumanDetector.java b/java/lib/src/main/java/adf_core_python/component/module/complex/HumanDetector.java new file mode 100644 index 0000000..e99db72 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/module/complex/HumanDetector.java @@ -0,0 +1,49 @@ +package adf_core_python.component.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.agent.module.ModuleManager; +import adf_core_python.agent.precompute.PrecomputeData; +import rescuecore2.standard.entities.Human; + +public abstract class HumanDetector extends TargetDetector { + + public HumanDetector(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + } + + + @Override + public HumanDetector precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + return this; + } + + + @Override + public HumanDetector resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + return this; + } + + + @Override + public HumanDetector preparate() { + super.preparate(); + return this; + } + + + @Override + public HumanDetector updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + return this; + } + + + @Override + public abstract HumanDetector calc(); +} diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/PoliceTargetAllocator.java b/java/lib/src/main/java/adf_core_python/component/module/complex/PoliceTargetAllocator.java new file mode 100644 index 0000000..846dc06 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/module/complex/PoliceTargetAllocator.java @@ -0,0 +1,47 @@ +package adf_core_python.component.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.agent.module.ModuleManager; +import adf_core_python.agent.precompute.PrecomputeData; +import rescuecore2.worldmodel.EntityID; + +import java.util.Map; + +public abstract class PoliceTargetAllocator extends TargetAllocator { + + public PoliceTargetAllocator(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + } + + + @Override + public abstract Map getResult(); + + @Override + public abstract PoliceTargetAllocator calc(); + + + @Override + public PoliceTargetAllocator resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + return this; + } + + + @Override + public PoliceTargetAllocator preparate() { + super.preparate(); + return this; + } + + + @Override + public PoliceTargetAllocator updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + return this; + } +} diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/RoadDetector.java b/java/lib/src/main/java/adf_core_python/component/module/complex/RoadDetector.java new file mode 100644 index 0000000..9f6acac --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/module/complex/RoadDetector.java @@ -0,0 +1,49 @@ +package adf_core_python.component.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.agent.module.ModuleManager; +import adf_core_python.agent.precompute.PrecomputeData; +import rescuecore2.standard.entities.Road; + +public abstract class RoadDetector extends TargetDetector { + + public RoadDetector(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + } + + + @Override + public RoadDetector precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + return this; + } + + + @Override + public RoadDetector resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + return this; + } + + + @Override + public RoadDetector preparate() { + super.preparate(); + return this; + } + + + @Override + public RoadDetector updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + return this; + } + + + @Override + public abstract RoadDetector calc(); +} diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/Search.java b/java/lib/src/main/java/adf_core_python/component/module/complex/Search.java new file mode 100644 index 0000000..13173bb --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/module/complex/Search.java @@ -0,0 +1,49 @@ +package adf_core_python.component.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.agent.module.ModuleManager; +import adf_core_python.agent.precompute.PrecomputeData; +import rescuecore2.standard.entities.Area; + +public abstract class Search extends TargetDetector { + + public Search(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + } + + + @Override + public Search precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + return this; + } + + + @Override + public Search resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + return this; + } + + + @Override + public Search preparate() { + super.preparate(); + return this; + } + + + @Override + public Search updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + return this; + } + + + @Override + public abstract Search calc(); +} diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/TargetAllocator.java b/java/lib/src/main/java/adf_core_python/component/module/complex/TargetAllocator.java new file mode 100644 index 0000000..3a85b34 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/module/complex/TargetAllocator.java @@ -0,0 +1,54 @@ +package adf_core_python.component.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.agent.module.ModuleManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.AbstractModule; +import rescuecore2.worldmodel.EntityID; + +import java.util.Map; + +public abstract class TargetAllocator extends AbstractModule { + + public TargetAllocator(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + } + + + public abstract Map getResult(); + + @Override + public abstract TargetAllocator calc(); + + + @Override + public final TargetAllocator precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + return this; + } + + + @Override + public TargetAllocator resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + return this; + } + + + @Override + public TargetAllocator preparate() { + super.preparate(); + return this; + } + + + @Override + public TargetAllocator updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + return this; + } +} diff --git a/java/lib/src/main/java/adf_core_python/component/module/complex/TargetDetector.java b/java/lib/src/main/java/adf_core_python/component/module/complex/TargetDetector.java new file mode 100644 index 0000000..bddd091 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/component/module/complex/TargetDetector.java @@ -0,0 +1,54 @@ +package adf_core_python.component.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.ScenarioInfo; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.agent.info.AgentInfo; +import adf_core_python.agent.module.ModuleManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.AbstractModule; +import rescuecore2.standard.entities.StandardEntity; +import rescuecore2.worldmodel.EntityID; + +public abstract class TargetDetector + extends AbstractModule { + + public TargetDetector(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) { + super(ai, wi, si, moduleManager, developData); + } + + + public abstract EntityID getTarget(); + + @Override + public abstract TargetDetector calc(); + + + @Override + public TargetDetector precompute(PrecomputeData precomputeData) { + super.precompute(precomputeData); + return this; + } + + + @Override + public TargetDetector resume(PrecomputeData precomputeData) { + super.resume(precomputeData); + return this; + } + + + @Override + public TargetDetector preparate() { + super.preparate(); + return this; + } + + + @Override + public TargetDetector updateInfo(MessageManager messageManager) { + super.updateInfo(messageManager); + return this; + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/Coordinator.java b/java/lib/src/main/java/adf_core_python/gateway/Coordinator.java new file mode 100644 index 0000000..309e92d --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/Coordinator.java @@ -0,0 +1,132 @@ +package adf_core_python.gateway; + +import adf.core.agent.info.ScenarioInfo; +import adf_core_python.agent.Agent; +import adf_core_python.agent.config.ModuleConfig; +import adf_core_python.agent.develop.DevelopData; +import adf_core_python.gateway.message.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import rescuecore2.config.Config; +import rescuecore2.messages.Command; +import rescuecore2.messages.Message; +import rescuecore2.messages.protobuf.RCRSProto; +import rescuecore2.messages.protobuf.RCRSProto.MessageProto; +import rescuecore2.misc.EncodingTools; +import rescuecore2.worldmodel.ChangeSet; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.util.Collection; +import java.util.HashMap; + +public class Coordinator extends Thread { + private final InputStream inputStream; + private final OutputStream outputStream; + private final HashMap changeSetTemp = new HashMap<>(); + private final HashMap> heardTemp = new HashMap<>(); + private boolean running = true; + private Agent agent; + + public Coordinator(Socket socket) { + try { + socket.setSoTimeout(1000); + socket.setReuseAddress(true); + inputStream = socket.getInputStream(); + outputStream = socket.getOutputStream(); + } catch (IOException e) { + throw new RuntimeException(e); + } + Logger logger = LogManager.getLogger(this.getClass()); + logger.info("Connected from {} on {}", socket.getRemoteSocketAddress(), socket.getLocalSocketAddress()); + } + + @Override + public void run() { + while (running) { + RCRSProto.MessageProto messageProto = receiveMessage(); + if (messageProto == null) continue; + try { + Message message = ModuleControlMessageFactory.makeMessage(messageProto.getUrn(), messageProto); + handleMessage(message); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private void handleMessage(Message message) { + if (message instanceof AMAgent amAgent) { + ScenarioInfo.Mode mode = ScenarioInfo.Mode.NON_PRECOMPUTE; + switch (amAgent.getMode()) { + case 1: + mode = ScenarioInfo.Mode.PRECOMPUTED; + break; + case 2: + mode = ScenarioInfo.Mode.PRECOMPUTATION_PHASE; + break; + } + agent = new Agent(amAgent.getAgentID(), amAgent.getEntities(), new ScenarioInfo(amAgent.getConfig(), mode), new DevelopData(), new ModuleConfig()); + } else if (message instanceof AMModule amModule) { + if (agent == null) { + throw new IllegalStateException("Agent not found. Make sure agent has been registered."); + } + Class clazz = agent.registerModule(amModule.getModuleID(), amModule.getModuleName(), amModule.getDefaultClassName()); + String class_name = ""; + if (clazz != null) { + class_name = clazz.getName(); + } + MAModuleResponse maModuleResponse = new MAModuleResponse(amModule.getModuleID(), class_name); + sendMessage(maModuleResponse); + } else if (message instanceof AMUpdate amUpdate) { + if (agent == null) { + changeSetTemp.put(amUpdate.getTime(), amUpdate.getChanged()); + heardTemp.put(amUpdate.getTime(), amUpdate.getHeard()); + return; + } + if (!changeSetTemp.isEmpty() && !heardTemp.isEmpty()) { + for (int i = 1; i < amUpdate.getTime(); i++) { + agent.update(i, changeSetTemp.get(i), heardTemp.get(i)); + } + changeSetTemp.clear(); + heardTemp.clear(); + } + agent.update(amUpdate.getTime(), amUpdate.getChanged(), amUpdate.getHeard()); + } else if (message instanceof AMExec amExec) { + Config result = agent.execModuleMethod(amExec.getModuleID(), amExec.getMethodName(), amExec.getArguments()); + MAExecResponse maExecResponse = new MAExecResponse(amExec.getModuleID(), result); + sendMessage(maExecResponse); + } + } + + private MessageProto receiveMessage() { + try { + int size = EncodingTools.readInt32(inputStream); + byte[] bytes = inputStream.readNBytes(size); + return MessageProto.parseFrom(bytes); + } catch (IOException ignored) { + } + return null; + } + + private void sendMessage(MessageProto messageProto) { + try { + byte[] bytes = messageProto.toByteArray(); + EncodingTools.writeInt32(bytes.length, outputStream); + outputStream.write(bytes); + outputStream.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void sendMessage(Message message) { + sendMessage(message.toMessageProto()); + } + + public void shutdown() { + running = false; + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/Gateway.java b/java/lib/src/main/java/adf_core_python/gateway/Gateway.java new file mode 100644 index 0000000..2e97b29 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/Gateway.java @@ -0,0 +1,48 @@ +package adf_core_python.gateway; + +import rescuecore2.registry.Registry; +import rescuecore2.standard.entities.StandardEntityFactory; +import rescuecore2.standard.entities.StandardPropertyFactory; +import rescuecore2.standard.messages.StandardMessageFactory; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; + +public class Gateway extends Thread { + private final int port; + private final ArrayList coordinators; + private boolean running = true; + + public Gateway(int port) { + this.port = port; + this.coordinators = new ArrayList<>(); + + Registry.SYSTEM_REGISTRY.registerFactory(StandardEntityFactory.INSTANCE); + Registry.SYSTEM_REGISTRY.registerFactory(StandardMessageFactory.INSTANCE); + Registry.SYSTEM_REGISTRY.registerFactory(StandardPropertyFactory.INSTANCE); + } + + @Override + public void run() { + try (ServerSocket serverSocket = new ServerSocket(port)) { + serverSocket.setReuseAddress(true); + while (running) { + Socket socket = serverSocket.accept(); + Coordinator coordinator = new Coordinator(socket); + coordinator.start(); + coordinators.add(coordinator); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void shutdown() { + running = false; + for (Coordinator coordinator : coordinators) { + coordinator.shutdown(); + } + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/AbstractMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/AbstractMapper.java new file mode 100644 index 0000000..3a27ca9 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/AbstractMapper.java @@ -0,0 +1,21 @@ +package adf_core_python.gateway.mapper; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import rescuecore2.config.Config; + +public abstract class AbstractMapper { + protected Class targetClass; + protected Logger logger; + + public AbstractMapper() { + this.targetClass = Object.class; + logger = LogManager.getLogger(this.getClass()); + } + + public Class getTargetClass() { + return targetClass; + } + + public abstract Config execMethod(String methodName, Config arguments); +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/MapperDict.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/MapperDict.java new file mode 100644 index 0000000..28e50f3 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/MapperDict.java @@ -0,0 +1,51 @@ +package adf_core_python.gateway.mapper; + +import adf_core_python.component.module.AbstractModule; +import adf_core_python.component.module.algorithm.Clustering; +import adf_core_python.component.module.algorithm.DynamicClustering; +import adf_core_python.component.module.algorithm.PathPlanning; +import adf_core_python.component.module.algorithm.StaticClustering; +import adf_core_python.component.module.complex.*; +import adf_core_python.gateway.mapper.module.AbstractModuleMapper; +import adf_core_python.gateway.mapper.module.algorithm.ClusteringMapper; +import adf_core_python.gateway.mapper.module.algorithm.DynamicClusteringMapper; +import adf_core_python.gateway.mapper.module.algorithm.PathPlanningMapper; +import adf_core_python.gateway.mapper.module.algorithm.StaticClusteringMapper; +import adf_core_python.gateway.mapper.module.complex.*; + +import java.util.HashMap; + +public class MapperDict { + private final HashMap, Class> mapperDict; + + public MapperDict() { + mapperDict = new HashMap<>(); + registerMapper(Clustering.class, ClusteringMapper.class); + registerMapper(DynamicClustering.class, DynamicClusteringMapper.class); + registerMapper(StaticClustering.class, StaticClusteringMapper.class); + + registerMapper(PathPlanning.class, PathPlanningMapper.class); + + registerMapper(TargetDetector.class, TargetDetectorMapper.class); + registerMapper(HumanDetector.class, HumanDetectorMapper.class); + registerMapper(RoadDetector.class, RoadDetectorMapper.class); + registerMapper(BuildingDetector.class, BuildingDetectorMapper.class); + + registerMapper(Search.class, SearchMapper.class); + + registerMapper(TargetAllocator.class, TargetAllocatorMapper.class); + registerMapper(AmbulanceTargetAllocator.class, AmbulanceTargetAllocatorMapper.class); + registerMapper(FireTargetAllocator.class, FireTargetAllocatorMapper.class); + registerMapper(PoliceTargetAllocator.class, PoliceTargetAllocatorMapper.class); + + registerMapper(AbstractModule.class, AbstractModuleMapper.class); + } + + public void registerMapper(Class component, Class mapper) { + mapperDict.put(component, mapper); + } + + public Class getMapper(Class component) { + return mapperDict.get(component); + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/AbstractModuleMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/AbstractModuleMapper.java new file mode 100644 index 0000000..984d801 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/AbstractModuleMapper.java @@ -0,0 +1,64 @@ +package adf_core_python.gateway.mapper.module; + +import adf.core.agent.communication.MessageManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.AbstractModule; +import adf_core_python.gateway.mapper.AbstractMapper; +import rescuecore2.config.Config; + +public class AbstractModuleMapper extends AbstractMapper { + protected final AbstractModule abstractModule; + private final PrecomputeData precomputeData; + private final MessageManager messageManager; + + public AbstractModuleMapper(AbstractModule abstractModule, PrecomputeData precomputeData, MessageManager messageManager) { + super(); + this.targetClass = AbstractModule.class; + this.abstractModule = abstractModule; + this.precomputeData = precomputeData; + this.messageManager = messageManager; + } + + @Override + public Config execMethod(String methodName, Config arguments) { + switch (methodName) { + case "precompute": + execPrecompute(); + break; + case "resume": + execResume(); + break; + case "preparate": + execPreparate(); + break; + case "updateInfo": + execUpdateInfo(); + break; + case "calc": + execCalc(); + break; + } + + return new Config(); + } + + public void execPrecompute() { + abstractModule.precompute(precomputeData); + } + + public void execResume() { + abstractModule.resume(precomputeData); + } + + public void execPreparate() { + abstractModule.preparate(); + } + + public void execUpdateInfo() { + abstractModule.updateInfo(messageManager); + } + + public void execCalc() { + abstractModule.calc(); + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/ClusteringMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/ClusteringMapper.java new file mode 100644 index 0000000..a063c20 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/ClusteringMapper.java @@ -0,0 +1,133 @@ +package adf_core_python.gateway.mapper.module.algorithm; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.algorithm.Clustering; +import adf_core_python.gateway.mapper.module.AbstractModuleMapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import rescuecore2.config.Config; +import rescuecore2.standard.entities.StandardEntity; +import rescuecore2.worldmodel.EntityID; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class ClusteringMapper extends AbstractModuleMapper { + private final WorldInfo worldInfo; + + public ClusteringMapper(Clustering clustering, PrecomputeData precomputeData, MessageManager messageManager, WorldInfo worldInfo) { + super(clustering, precomputeData, messageManager); + this.targetClass = Clustering.class; + this.worldInfo = worldInfo; + } + + @Override + public Config execMethod(String methodName, Config arguments) { + Config result = super.execMethod(methodName, arguments); + if (methodName.equals("getClusterNumber")) { + result = execGetClusterNumber(); + } + if (methodName.equals("getClusterIndex(StandardEntity)")) { + result = execGetClusterIndex(worldInfo.getEntity(new EntityID(arguments.getIntValue("EntityID")))); + } + if (methodName.equals("getClusterIndex(EntityID)")) { + result = execGetClusterIndex(new EntityID(arguments.getIntValue("EntityID"))); + } + if (methodName.equals("getClusterEntities(int)")) { + result = execGetClusterEntities(arguments.getIntValue("Index")); + } + if (methodName.equals("getClusterEntityIDs(int)")) { + result = execGetClusterEntityIDs(arguments.getIntValue("Index")); + } + if (methodName.equals("getAllClusterEntities")) { + result = execGetAllClusterEntities(); + } + if (methodName.equals("getAllClusterEntityIDs")) { + result = execGetAllClusterEntityIDs(); + } + return result; + } + + private Config execGetClusterNumber() { + Clustering clustering = (Clustering) abstractModule; + int clusterNumber = clustering.getClusterNumber(); + Config result = new Config(); + result.setIntValue("ClusterNumber", clusterNumber); + return result; + } + + private Config execGetClusterIndex(StandardEntity standardEntity) { + Clustering clustering = (Clustering) abstractModule; + int clusterIndex = clustering.getClusterIndex(standardEntity); + Config result = new Config(); + result.setIntValue("ClusterIndex", clusterIndex); + return result; + } + + private Config execGetClusterIndex(EntityID entityID) { + Clustering clustering = (Clustering) abstractModule; + int clusterIndex = clustering.getClusterIndex(entityID); + Config result = new Config(); + result.setIntValue("ClusterIndex", clusterIndex); + return result; + } + + private Config execGetClusterEntities(int index) { + Clustering clustering = (Clustering) abstractModule; + Collection entities = clustering.getClusterEntities(index); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonStr = ""; + try { + jsonStr = objectMapper.writeValueAsString(entities.stream().map(e -> e.getID().getValue()).toArray()); + } catch (JsonProcessingException ignored) { + } + Config result = new Config(); + result.setValue("EntityIDs", jsonStr); + return result; + } + + private Config execGetClusterEntityIDs(int index) { + Clustering clustering = (Clustering) abstractModule; + Collection entities = clustering.getClusterEntityIDs(index); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonStr = ""; + try { + jsonStr = objectMapper.writeValueAsString(entities.stream().map(EntityID::getValue).toArray()); + } catch (JsonProcessingException ignored) { + } + Config result = new Config(); + result.setValue("EntityIDs", jsonStr); + return result; + } + + private Config execGetAllClusterEntities() { + Clustering clustering = (Clustering) abstractModule; + List> allClusterEntities = clustering.getAllClusterEntities(); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonStr = ""; + try { + jsonStr = objectMapper.writeValueAsString(allClusterEntities.stream().map(e -> e.stream().map(f -> f.getID().getValue()).collect(Collectors.toList())).toArray()); + } catch (JsonProcessingException ignored) { + } + Config result = new Config(); + result.setValue("EntityIDs", jsonStr); + return result; + } + + private Config execGetAllClusterEntityIDs() { + Clustering clustering = (Clustering) abstractModule; + List> allClusterEntityIDs = clustering.getAllClusterEntityIDs(); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonStr = ""; + try { + jsonStr = objectMapper.writeValueAsString(allClusterEntityIDs.stream().map(e -> e.stream().map(EntityID::getValue).collect(Collectors.toList())).toArray()); + } catch (JsonProcessingException ignored) { + } + Config result = new Config(); + result.setValue("EntityIDs", jsonStr); + return result; + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/DynamicClusteringMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/DynamicClusteringMapper.java new file mode 100644 index 0000000..3dfdb43 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/DynamicClusteringMapper.java @@ -0,0 +1,12 @@ +package adf_core_python.gateway.mapper.module.algorithm; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.algorithm.DynamicClustering; + +public class DynamicClusteringMapper extends ClusteringMapper { + public DynamicClusteringMapper(DynamicClustering dynamicClustering, PrecomputeData precomputeData, MessageManager messageManager, WorldInfo worldInfo) { + super(dynamicClustering, precomputeData, messageManager, worldInfo); + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/PathPlanningMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/PathPlanningMapper.java new file mode 100644 index 0000000..3bfb84f --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/PathPlanningMapper.java @@ -0,0 +1,108 @@ +package adf_core_python.gateway.mapper.module.algorithm; + +import adf.core.agent.communication.MessageManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.algorithm.PathPlanning; +import adf_core_python.gateway.mapper.module.AbstractModuleMapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import rescuecore2.config.Config; +import rescuecore2.worldmodel.EntityID; + +import java.util.Collection; +import java.util.List; + +public class PathPlanningMapper extends AbstractModuleMapper { + public PathPlanningMapper(PathPlanning pathPlanning, PrecomputeData precomputeData, MessageManager messageManager) { + super(pathPlanning, precomputeData, messageManager); + } + + @Override + public Config execMethod(String methodName, Config arguments) { + Config result = super.execMethod(methodName, arguments); + if (methodName.equals("getResult")) { + result = execGetResult(); + } + if (methodName.equals("setFrom(EntityID)")) { + execSetFrom(new EntityID(arguments.getIntValue("EntityID"))); + } + if (methodName.equals("setDestination(Collection)")) { + ObjectMapper objectMapper = new ObjectMapper(); + Collection targets; + try { + targets = objectMapper.readValue(arguments.getValue("Targets"), new TypeReference>() { + }); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + execSetDestination(targets); + } + if (methodName.equals("getDistance")) { + result = execGetDistance(); + } + if (methodName.equals("getDistance(EntityID, EntityID)")) { + result = execGetDistance(new EntityID(arguments.getIntValue("From")), new EntityID(arguments.getIntValue("Dest"))); + } + if (methodName.equals("getResult(EntityID, EntityID)")) { + result = execGetResult(new EntityID(arguments.getIntValue("From")), new EntityID(arguments.getIntValue("Dest"))); + } + return result; + } + + private Config execGetResult() { + PathPlanning pathPlanning = (PathPlanning) abstractModule; + List entityIDs = pathPlanning.getResult(); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonStr; + try { + jsonStr = objectMapper.writeValueAsString(entityIDs); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + Config result = new Config(); + result.setValue("EntityIDs", jsonStr); + return result; + } + + private void execSetFrom(EntityID entityID) { + PathPlanning pathPlanning = (PathPlanning) abstractModule; + pathPlanning.setFrom(entityID); + } + + private void execSetDestination(Collection targets) { + PathPlanning pathPlanning = (PathPlanning) abstractModule; + pathPlanning.setDestination(targets); + } + + private Config execGetDistance() { + PathPlanning pathPlanning = (PathPlanning) abstractModule; + Double distance = pathPlanning.getDistance(); + Config result = new Config(); + result.setValue("Distance", String.valueOf(distance)); + return result; + } + + private Config execGetDistance(EntityID from, EntityID dest) { + PathPlanning pathPlanning = (PathPlanning) abstractModule; + Double distance = pathPlanning.getDistance(from, dest); + Config result = new Config(); + result.setValue("Distance", String.valueOf(distance)); + return result; + } + + private Config execGetResult(EntityID from, EntityID dest) { + PathPlanning pathPlanning = (PathPlanning) abstractModule; + List entityIDs = pathPlanning.getResult(from, dest); + Config result = new Config(); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonStr; + try { + jsonStr = objectMapper.writeValueAsString(entityIDs.stream().map(EntityID::getValue).toArray()); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + result.setValue("Result", String.valueOf(jsonStr)); + return result; + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/StaticClusteringMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/StaticClusteringMapper.java new file mode 100644 index 0000000..daeaec3 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/algorithm/StaticClusteringMapper.java @@ -0,0 +1,12 @@ +package adf_core_python.gateway.mapper.module.algorithm; + +import adf.core.agent.communication.MessageManager; +import adf.core.agent.info.WorldInfo; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.algorithm.StaticClustering; + +public class StaticClusteringMapper extends ClusteringMapper { + public StaticClusteringMapper(StaticClustering staticClustering, PrecomputeData precomputeData, MessageManager messageManager, WorldInfo worldInfo) { + super(staticClustering, precomputeData, messageManager, worldInfo); + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/AmbulanceTargetAllocatorMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/AmbulanceTargetAllocatorMapper.java new file mode 100644 index 0000000..e7beb49 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/AmbulanceTargetAllocatorMapper.java @@ -0,0 +1,12 @@ +package adf_core_python.gateway.mapper.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.complex.AmbulanceTargetAllocator; + +public class AmbulanceTargetAllocatorMapper extends TargetAllocatorMapper { + public AmbulanceTargetAllocatorMapper(AmbulanceTargetAllocator ambulanceTargetAllocator, PrecomputeData precomputeData, MessageManager messageManager) { + super(ambulanceTargetAllocator, precomputeData, messageManager); + this.targetClass = AmbulanceTargetAllocator.class; + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/BuildingDetectorMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/BuildingDetectorMapper.java new file mode 100644 index 0000000..448dd1c --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/BuildingDetectorMapper.java @@ -0,0 +1,12 @@ +package adf_core_python.gateway.mapper.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.complex.BuildingDetector; + +public class BuildingDetectorMapper extends TargetDetectorMapper { + public BuildingDetectorMapper(BuildingDetector buildingDetector, PrecomputeData precomputeData, MessageManager messageManager) { + super(buildingDetector, precomputeData, messageManager); + this.targetClass = BuildingDetector.class; + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/FireTargetAllocatorMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/FireTargetAllocatorMapper.java new file mode 100644 index 0000000..235d3fa --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/FireTargetAllocatorMapper.java @@ -0,0 +1,12 @@ +package adf_core_python.gateway.mapper.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.complex.FireTargetAllocator; + +public class FireTargetAllocatorMapper extends TargetAllocatorMapper { + public FireTargetAllocatorMapper(FireTargetAllocator fireTargetAllocator, PrecomputeData precomputeData, MessageManager messageManager) { + super(fireTargetAllocator, precomputeData, messageManager); + this.targetClass = FireTargetAllocator.class; + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/HumanDetectorMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/HumanDetectorMapper.java new file mode 100644 index 0000000..2db6fed --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/HumanDetectorMapper.java @@ -0,0 +1,12 @@ +package adf_core_python.gateway.mapper.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.complex.HumanDetector; + +public class HumanDetectorMapper extends TargetDetectorMapper { + public HumanDetectorMapper(HumanDetector humanDetector, PrecomputeData precomputeData, MessageManager messageManager) { + super(humanDetector, precomputeData, messageManager); + this.targetClass = HumanDetector.class; + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/PoliceTargetAllocatorMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/PoliceTargetAllocatorMapper.java new file mode 100644 index 0000000..5092b72 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/PoliceTargetAllocatorMapper.java @@ -0,0 +1,12 @@ +package adf_core_python.gateway.mapper.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.complex.PoliceTargetAllocator; + +public class PoliceTargetAllocatorMapper extends TargetAllocatorMapper { + public PoliceTargetAllocatorMapper(PoliceTargetAllocator policeTargetAllocator, PrecomputeData precomputeData, MessageManager messageManager) { + super(policeTargetAllocator, precomputeData, messageManager); + this.targetClass = PoliceTargetAllocator.class; + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/RoadDetectorMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/RoadDetectorMapper.java new file mode 100644 index 0000000..4945c38 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/RoadDetectorMapper.java @@ -0,0 +1,12 @@ +package adf_core_python.gateway.mapper.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.complex.RoadDetector; + +public class RoadDetectorMapper extends TargetDetectorMapper { + public RoadDetectorMapper(RoadDetector roadDetector, PrecomputeData precomputeData, MessageManager messageManager) { + super(roadDetector, precomputeData, messageManager); + this.targetClass = RoadDetector.class; + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/SearchMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/SearchMapper.java new file mode 100644 index 0000000..94f42db --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/SearchMapper.java @@ -0,0 +1,11 @@ +package adf_core_python.gateway.mapper.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.complex.Search; + +public class SearchMapper extends TargetDetectorMapper { + public SearchMapper(Search search, PrecomputeData precomputeData, MessageManager messageManager) { + super(search, precomputeData, messageManager); + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/TargetAllocatorMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/TargetAllocatorMapper.java new file mode 100644 index 0000000..78ba9d2 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/TargetAllocatorMapper.java @@ -0,0 +1,34 @@ +package adf_core_python.gateway.mapper.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.complex.TargetAllocator; +import adf_core_python.gateway.mapper.module.AbstractModuleMapper; +import rescuecore2.config.Config; +import rescuecore2.worldmodel.EntityID; + +import java.util.Map; + +public class TargetAllocatorMapper extends AbstractModuleMapper { + public TargetAllocatorMapper(TargetAllocator targetAllocator, PrecomputeData precomputeData, MessageManager messageManager) { + super(targetAllocator, precomputeData, messageManager); + this.targetClass = TargetAllocator.class; + } + + @Override + public Config execMethod(String methodName, Config arguments) { + Config result = super.execMethod(methodName, arguments); + if (methodName.equals("getResult")) { + result = execGetResult(); + } + return result; + } + + public Config execGetResult() { + TargetAllocator targetAllocator = (TargetAllocator) abstractModule; + Map result = targetAllocator.getResult(); + Config response = new Config(); + result.forEach((k, v) -> response.setValue(String.valueOf(k.getValue()), String.valueOf(v.getValue()))); + return response; + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/TargetDetectorMapper.java b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/TargetDetectorMapper.java new file mode 100644 index 0000000..e0bb851 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/mapper/module/complex/TargetDetectorMapper.java @@ -0,0 +1,35 @@ +package adf_core_python.gateway.mapper.module.complex; + +import adf.core.agent.communication.MessageManager; +import adf_core_python.agent.precompute.PrecomputeData; +import adf_core_python.component.module.complex.TargetDetector; +import adf_core_python.gateway.mapper.module.AbstractModuleMapper; +import rescuecore2.config.Config; +import rescuecore2.worldmodel.EntityID; + +public class TargetDetectorMapper extends AbstractModuleMapper { + public TargetDetectorMapper(TargetDetector targetDetector, PrecomputeData precomputeData, MessageManager messageManager) { + super(targetDetector, precomputeData, messageManager); + this.targetClass = TargetDetector.class; + } + + @Override + public Config execMethod(String methodName, Config arguments) { + Config result = super.execMethod(methodName, arguments); + if (methodName.equals("getTarget")) { + result = execGetTarget(); + } + return result; + } + + public Config execGetTarget() { + TargetDetector targetDetector = (TargetDetector) abstractModule; + EntityID entityID = targetDetector.getTarget(); + Config result = new Config(); + result.setIntValue("EntityID", -1); + if (entityID != null) { + result.setIntValue("EntityID", entityID.getValue()); + } + return result; + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/AMAgent.java b/java/lib/src/main/java/adf_core_python/gateway/message/AMAgent.java new file mode 100644 index 0000000..a7e34a5 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/message/AMAgent.java @@ -0,0 +1,71 @@ +package adf_core_python.gateway.message; + +import adf_core_python.gateway.message.urn.ModuleMessageComponentURN; +import adf_core_python.gateway.message.urn.ModuleMessageURN; +import rescuecore2.config.Config; +import rescuecore2.messages.AbstractMessage; +import rescuecore2.messages.components.ConfigComponent; +import rescuecore2.messages.components.EntityIDComponent; +import rescuecore2.messages.components.EntityListComponent; +import rescuecore2.messages.components.IntComponent; +import rescuecore2.messages.protobuf.RCRSProto; +import rescuecore2.worldmodel.Entity; +import rescuecore2.worldmodel.EntityID; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.List; + +public class AMAgent extends AbstractMessage { + private final EntityIDComponent agentID; + private final EntityListComponent entities; + private final ConfigComponent config; + private final IntComponent mode; + + public AMAgent(EntityID agentID, Collection entities, Config config, int mode) { + this(); + this.agentID.setValue(agentID); + this.entities.setEntities(entities); + this.config.setConfig(config); + this.mode.setValue(mode); + } + + public AMAgent(InputStream inputStream) throws IOException { + this(); + read(inputStream); + } + + public AMAgent(RCRSProto.MessageProto messageProto) { + this(); + fromMessageProto(messageProto); + } + + private AMAgent() { + super(ModuleMessageURN.AM_AGENT); + this.agentID = new EntityIDComponent(ModuleMessageComponentURN.AgentID); + this.entities = new EntityListComponent(ModuleMessageComponentURN.Entities); + this.config = new ConfigComponent(ModuleMessageComponentURN.Config); + this.mode = new IntComponent(ModuleMessageComponentURN.Mode); + addMessageComponent(agentID); + addMessageComponent(entities); + addMessageComponent(config); + addMessageComponent(mode); + } + + public EntityID getAgentID() { + return this.agentID.getValue(); + } + + public List getEntities() { + return this.entities.getEntities(); + } + + public Config getConfig() { + return this.config.getConfig(); + } + + public int getMode() { + return this.mode.getValue(); + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/AMExec.java b/java/lib/src/main/java/adf_core_python/gateway/message/AMExec.java new file mode 100644 index 0000000..199f97d --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/message/AMExec.java @@ -0,0 +1,57 @@ +package adf_core_python.gateway.message; + +import adf_core_python.gateway.message.urn.ModuleMessageComponentURN; +import adf_core_python.gateway.message.urn.ModuleMessageURN; +import rescuecore2.config.Config; +import rescuecore2.messages.AbstractMessage; +import rescuecore2.messages.components.ConfigComponent; +import rescuecore2.messages.components.StringComponent; +import rescuecore2.messages.protobuf.RCRSProto; + +import java.io.IOException; +import java.io.InputStream; + +public class AMExec extends AbstractMessage { + private final StringComponent moduleID; + private final StringComponent methodName; + private final ConfigComponent arguments; + + public AMExec(String moduleID, String methodName, Config arguments) { + this(); + this.moduleID.setValue(moduleID); + this.methodName.setValue(methodName); + this.arguments.setConfig(arguments); + } + + public AMExec(InputStream inputStream) throws IOException { + this(); + read(inputStream); + } + + public AMExec(RCRSProto.MessageProto messageProto) { + this(); + fromMessageProto(messageProto); + } + + private AMExec() { + super(ModuleMessageURN.AM_MODULE); + this.moduleID = new StringComponent(ModuleMessageComponentURN.ModuleID); + this.methodName = new StringComponent(ModuleMessageComponentURN.MethodName); + this.arguments = new ConfigComponent(ModuleMessageComponentURN.Arguments); + addMessageComponent(moduleID); + addMessageComponent(methodName); + addMessageComponent(arguments); + } + + public String getModuleID() { + return this.moduleID.getValue(); + } + + public String getMethodName() { + return this.methodName.getValue(); + } + + public Config getArguments() { + return this.arguments.getConfig(); + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/AMModule.java b/java/lib/src/main/java/adf_core_python/gateway/message/AMModule.java new file mode 100644 index 0000000..a597897 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/message/AMModule.java @@ -0,0 +1,55 @@ +package adf_core_python.gateway.message; + +import adf_core_python.gateway.message.urn.ModuleMessageComponentURN; +import adf_core_python.gateway.message.urn.ModuleMessageURN; +import rescuecore2.messages.AbstractMessage; +import rescuecore2.messages.components.StringComponent; +import rescuecore2.messages.protobuf.RCRSProto; + +import java.io.IOException; +import java.io.InputStream; + +public class AMModule extends AbstractMessage { + private final StringComponent moduleID; + private final StringComponent moduleName; + private final StringComponent defaultClassName; + + public AMModule(String moduleID, String moduleName, String defaultClassName) { + this(); + this.moduleID.setValue(moduleID); + this.moduleName.setValue(moduleName); + this.defaultClassName.setValue(defaultClassName); + } + + public AMModule(InputStream inputStream) throws IOException { + this(); + read(inputStream); + } + + public AMModule(RCRSProto.MessageProto messageProto) { + this(); + fromMessageProto(messageProto); + } + + private AMModule() { + super(ModuleMessageURN.AM_MODULE); + this.moduleID = new StringComponent(ModuleMessageComponentURN.ModuleID); + this.moduleName = new StringComponent(ModuleMessageComponentURN.ModuleName); + this.defaultClassName = new StringComponent(ModuleMessageComponentURN.DefaultClassName); + addMessageComponent(moduleID); + addMessageComponent(moduleName); + addMessageComponent(defaultClassName); + } + + public String getModuleID() { + return this.moduleID.getValue(); + } + + public String getModuleName() { + return this.moduleName.getValue(); + } + + public String getDefaultClassName() { + return this.defaultClassName.getValue(); + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/AMUpdate.java b/java/lib/src/main/java/adf_core_python/gateway/message/AMUpdate.java new file mode 100644 index 0000000..ab3b03e --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/message/AMUpdate.java @@ -0,0 +1,61 @@ +package adf_core_python.gateway.message; + +import adf_core_python.gateway.message.urn.ModuleMessageComponentURN; +import adf_core_python.gateway.message.urn.ModuleMessageURN; +import rescuecore2.messages.AbstractMessage; +import rescuecore2.messages.Command; +import rescuecore2.messages.components.ChangeSetComponent; +import rescuecore2.messages.components.CommandListComponent; +import rescuecore2.messages.components.IntComponent; +import rescuecore2.messages.protobuf.RCRSProto; +import rescuecore2.worldmodel.ChangeSet; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.List; + +public class AMUpdate extends AbstractMessage { + private final IntComponent time; + private final ChangeSetComponent changed; + private final CommandListComponent heard; + + public AMUpdate(int time, ChangeSet changed, Collection heard) { + this(); + this.time.setValue(time); + this.changed.setChangeSet(changed); + this.heard.setCommands(heard); + } + + public AMUpdate(InputStream inputStream) throws IOException { + this(); + read(inputStream); + } + + public AMUpdate(RCRSProto.MessageProto messageProto) { + this(); + fromMessageProto(messageProto); + } + + private AMUpdate() { + super(ModuleMessageURN.AM_UPDATE); + this.time = new IntComponent(ModuleMessageComponentURN.Time); + this.changed = new ChangeSetComponent(ModuleMessageComponentURN.Changed); + this.heard = new CommandListComponent(ModuleMessageComponentURN.Heard); + addMessageComponent(this.time); + addMessageComponent(this.changed); + addMessageComponent(this.heard); + } + + public int getTime() { + return this.time.getValue(); + } + + public ChangeSet getChanged() { + return this.changed.getChangeSet(); + } + + public List getHeard() { + return this.heard.getCommands(); + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/MAExecResponse.java b/java/lib/src/main/java/adf_core_python/gateway/message/MAExecResponse.java new file mode 100644 index 0000000..e648838 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/message/MAExecResponse.java @@ -0,0 +1,49 @@ +package adf_core_python.gateway.message; + +import adf_core_python.gateway.message.urn.ModuleMessageComponentURN; +import adf_core_python.gateway.message.urn.ModuleMessageURN; +import rescuecore2.config.Config; +import rescuecore2.messages.AbstractMessage; +import rescuecore2.messages.components.ConfigComponent; +import rescuecore2.messages.components.StringComponent; +import rescuecore2.messages.protobuf.RCRSProto; + +import java.io.IOException; +import java.io.InputStream; + +public class MAExecResponse extends AbstractMessage { + private final StringComponent moduleID; + private final ConfigComponent result; + + public MAExecResponse(String moduleID, Config result) { + this(); + this.moduleID.setValue(moduleID); + this.result.setConfig(result); + } + + public MAExecResponse(InputStream inputStream) throws IOException { + this(); + read(inputStream); + } + + public MAExecResponse(RCRSProto.MessageProto messageProto) { + this(); + fromMessageProto(messageProto); + } + + private MAExecResponse() { + super(ModuleMessageURN.MA_EXEC_RESPONSE); + this.moduleID = new StringComponent(ModuleMessageComponentURN.ModuleID); + this.result = new ConfigComponent(ModuleMessageComponentURN.Result); + addMessageComponent(moduleID); + addMessageComponent(result); + } + + public String getModuleID() { + return this.moduleID.getValue(); + } + + public Config getResult() { + return this.result.getConfig(); + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/MAModuleResponse.java b/java/lib/src/main/java/adf_core_python/gateway/message/MAModuleResponse.java new file mode 100644 index 0000000..d568a61 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/message/MAModuleResponse.java @@ -0,0 +1,47 @@ +package adf_core_python.gateway.message; + +import adf_core_python.gateway.message.urn.ModuleMessageComponentURN; +import adf_core_python.gateway.message.urn.ModuleMessageURN; +import rescuecore2.messages.AbstractMessage; +import rescuecore2.messages.components.StringComponent; +import rescuecore2.messages.protobuf.RCRSProto; + +import java.io.IOException; +import java.io.InputStream; + +public class MAModuleResponse extends AbstractMessage { + private final StringComponent moduleID; + private final StringComponent className; + + public MAModuleResponse(String moduleID, String className) { + this(); + this.moduleID.setValue(moduleID); + this.className.setValue(className); + } + + public MAModuleResponse(InputStream inputStream) throws IOException { + this(); + read(inputStream); + } + + public MAModuleResponse(RCRSProto.MessageProto messageProto) { + this(); + fromMessageProto(messageProto); + } + + private MAModuleResponse() { + super(ModuleMessageURN.MA_MODULE_RESPONSE); + this.moduleID = new StringComponent(ModuleMessageComponentURN.ModuleID); + this.className = new StringComponent(ModuleMessageComponentURN.ClassName); + addMessageComponent(moduleID); + addMessageComponent(className); + } + + public String getModuleID() { + return this.moduleID.getValue(); + } + + public String getClassName() { + return this.className.getValue(); + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/ModuleControlMessageFactory.java b/java/lib/src/main/java/adf_core_python/gateway/message/ModuleControlMessageFactory.java new file mode 100644 index 0000000..3952131 --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/message/ModuleControlMessageFactory.java @@ -0,0 +1,42 @@ +package adf_core_python.gateway.message; + +import adf_core_python.gateway.message.urn.ModuleMessageURN; +import rescuecore2.messages.Message; +import rescuecore2.messages.protobuf.RCRSProto.MessageProto; + +import java.io.IOException; +import java.io.InputStream; + +public class ModuleControlMessageFactory { + public static Message makeMessage(int urn, InputStream inputStream) throws IOException { + return makeMessage(ModuleMessageURN.fromInt(urn), inputStream); + } + + public static Message makeMessage(ModuleMessageURN urn, InputStream inputStream) throws IOException { + switch (urn) { + case AM_AGENT -> new AMAgent(inputStream); + case AM_MODULE -> new AMModule(inputStream); + case MA_MODULE_RESPONSE -> new MAModuleResponse(inputStream); + case AM_UPDATE -> new AMUpdate(inputStream); + case AM_EXEC -> new AMExec(inputStream); + } + return null; + } + + public static Message makeMessage(int urn, MessageProto messageProto) throws IOException { + return makeMessage(ModuleMessageURN.fromInt(urn), messageProto); + } + + public static Message makeMessage(ModuleMessageURN urn, MessageProto messageProto) { + if (urn.equals(ModuleMessageURN.AM_AGENT)) { + return new AMAgent(messageProto); + } else if (urn.equals(ModuleMessageURN.AM_MODULE)) { + return new AMModule(messageProto); + } else if (urn.equals(ModuleMessageURN.AM_UPDATE)) { + return new AMUpdate(messageProto); + } else if (urn.equals(ModuleMessageURN.AM_EXEC)) { + return new AMExec(messageProto); + } + return null; + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/urn/ModuleMessageComponentURN.java b/java/lib/src/main/java/adf_core_python/gateway/message/urn/ModuleMessageComponentURN.java new file mode 100644 index 0000000..868439c --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/message/urn/ModuleMessageComponentURN.java @@ -0,0 +1,52 @@ +package adf_core_python.gateway.message.urn; + +import rescuecore2.URN; + +import java.util.Map; + +public enum ModuleMessageComponentURN implements URN { + AgentID(0x0400 | 1, "Agent ID"), + Entities(0x0400 | 2, "Entities"), + Config(0x0400 | 3, "Entities"), + Mode(0x0400 | 4, "Entities"), + ModuleID(0x0400 | 5, "Module ID"), + ModuleName(0x0400 | 6, "Module Name"), + DefaultClassName(0x0400 | 7, "Default Class Name"), + ClassName(0x0400 | 8, "Class Name"), + Time(0x0400 | 9, "Time"), + Changed(0x0400 | 10, "Changed"), + Heard(0x0400 | 11, "Heard"), + MethodName(0x0400 | 12, "Method Name"), + Arguments(0x0400 | 13, "Arguments"), + Result(0x0400 | 14, "Result"); + + + public static final Map MAP = URN.generateMap(ModuleMessageComponentURN.class); + public static final Map MAP_STR = URN + .generateMapStr(ModuleMessageComponentURN.class); + private final int urnId; + private final String urnStr; + + ModuleMessageComponentURN(int urnId, String urnStr) { + this.urnId = urnId; + this.urnStr = urnStr; + } + + public static ModuleMessageComponentURN fromInt(int urnId) { + return MAP.get(urnId); + } + + public static ModuleMessageComponentURN fromString(String urnStr) { + return MAP_STR.get(urnStr); + } + + @Override + public int getURNId() { + return urnId; + } + + @Override + public String getURNStr() { + return urnStr; + } +} diff --git a/java/lib/src/main/java/adf_core_python/gateway/message/urn/ModuleMessageURN.java b/java/lib/src/main/java/adf_core_python/gateway/message/urn/ModuleMessageURN.java new file mode 100644 index 0000000..8c90a4e --- /dev/null +++ b/java/lib/src/main/java/adf_core_python/gateway/message/urn/ModuleMessageURN.java @@ -0,0 +1,42 @@ +package adf_core_python.gateway.message.urn; + +import rescuecore2.URN; + +import java.util.Map; + +public enum ModuleMessageURN implements URN { + AM_AGENT(0x0300 | 1, "urn:adf_core_python.gateway.messages:am_agent"), + AM_MODULE(0x0300 | 2, "urn:adf_core_python.gateway.messages:am_module"), + MA_MODULE_RESPONSE(0x0300 | 3, "urn:adf_core_python.gateway.messages:am_module_response"), + AM_UPDATE(0x0300 | 4, "urn:adf_core_python.gateway.messages:am_update"), + AM_EXEC(0x0300 | 5, "urn:adf_core_python.gateway.messages:am_exec"), + MA_EXEC_RESPONSE(0x0300 | 6, "urn:adf_core_python.gateway.messages:am_exec_response"); + + public static final Map MAP = URN.generateMap(ModuleMessageURN.class); + public static final Map MAP_STR = URN.generateMapStr(ModuleMessageURN.class); + private final int urnId; + private final String urnStr; + + private ModuleMessageURN(int urnId, String urnStr) { + this.urnId = urnId; + this.urnStr = urnStr; + } + + public static ModuleMessageURN fromInt(int s) { + return MAP.get(s); + } + + public static ModuleMessageURN fromString(String urn) { + return MAP_STR.get(urn); + } + + @Override + public int getURNId() { + return urnId; + } + + @Override + public String getURNStr() { + return urnStr; + } +} diff --git a/java/lib/src/main/resources/log4j2.xml b/java/lib/src/main/resources/log4j2.xml new file mode 100644 index 0000000..893c33d --- /dev/null +++ b/java/lib/src/main/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/java/lib/src/test/java/org/example/LibraryTest.java b/java/lib/src/test/java/org/example/LibraryTest.java new file mode 100644 index 0000000..01e3af0 --- /dev/null +++ b/java/lib/src/test/java/org/example/LibraryTest.java @@ -0,0 +1,14 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package org.example; + +import org.junit.jupiter.api.Test; + +class LibraryTest { + @Test + void someLibraryMethodReturnsTrue() { +// Library classUnderTest = new Library(); +// assertTrue(classUnderTest.someLibraryMethod(), "someLibraryMethod should return 'true'"); + } +} diff --git a/poetry.lock b/poetry.lock index 11eee0e..1b520aa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "accessible-pygments" @@ -939,7 +939,7 @@ rtree = "*" type = "git" url = "https://github.com/adf-python/rcrs-core-python" reference = "HEAD" -resolved_reference = "aa661b19c98f82d8d648317f386c2f9d2c26a8fc" +resolved_reference = "42a20e312de20ea46f1e0622a82f41e81fc3514f" [[package]] name = "requests" @@ -1029,6 +1029,11 @@ files = [ {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f60021ec1574e56632be2a36b946f8143bf4e5e6af4a06d85281adc22938e0dd"}, {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:394397841449853c2290a32050382edaec3da89e35b3e03d6cc966aebc6a8ae6"}, {file = "scikit_learn-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:57cc1786cfd6bd118220a92ede80270132aa353647684efa385a74244a41e3b1"}, + {file = "scikit_learn-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9a702e2de732bbb20d3bad29ebd77fc05a6b427dc49964300340e4c9328b3f5"}, + {file = "scikit_learn-1.5.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:b0768ad641981f5d3a198430a1d31c3e044ed2e8a6f22166b4d546a5116d7908"}, + {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:178ddd0a5cb0044464fc1bfc4cca5b1833bfc7bb022d70b05db8530da4bb3dd3"}, + {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7284ade780084d94505632241bf78c44ab3b6f1e8ccab3d2af58e0e950f9c12"}, + {file = "scikit_learn-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:b7b0f9a0b1040830d38c39b91b3a44e1b643f4b36e36567b80b7c6bd2202a27f"}, {file = "scikit_learn-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:757c7d514ddb00ae249832fe87100d9c73c6ea91423802872d9e74970a0e40b9"}, {file = "scikit_learn-1.5.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:52788f48b5d8bca5c0736c175fa6bdaab2ef00a8f536cda698db61bd89c551c1"}, {file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:643964678f4b5fbdc95cbf8aec638acc7aa70f5f79ee2cdad1eec3df4ba6ead8"}, From c8d41bff044d52004463bae3c5f4f7ba920b6210 Mon Sep 17 00:00:00 2001 From: harrki Date: Sat, 14 Dec 2024 01:55:33 +0900 Subject: [PATCH 196/249] fix: gateway module error --- adf_core_python/core/agent/agent.py | 8 +++--- .../module/complex/gateway_human_detector.py | 13 +++++----- .../connect/connector_ambulance_center.py | 14 +++++----- .../connect/connector_ambulance_team.py | 14 +++++----- .../connect/connector_fire_brigade.py | 14 +++++----- .../connect/connector_fire_station.py | 14 +++++----- .../connect/connector_police_force.py | 16 +++++------- .../connect/connector_police_office.py | 14 +++++----- .../tactics/default_tactics_ambulance_team.py | 2 +- adf_core_python/launcher.py | 2 +- java/lib/build.gradle | 26 ++++++++----------- 11 files changed, 67 insertions(+), 70 deletions(-) diff --git a/adf_core_python/core/agent/agent.py b/adf_core_python/core/agent/agent.py index 7593410..6c8275b 100644 --- a/adf_core_python/core/agent/agent.py +++ b/adf_core_python/core/agent/agent.py @@ -304,9 +304,11 @@ def handler_sense(self, msg: Any) -> None: if herad_command.urn == CommandURN.AK_SPEAK: heard_commands.append( AKSpeak( - EntityID(herad_command.components[ - ComponentControlMessageID.AgentID - ].entityID), + EntityID( + herad_command.components[ + ComponentControlMessageID.AgentID + ].entityID + ), herad_command.components[ ComponentControlMessageID.Time ].intValue, diff --git a/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py b/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py index 715feba..7fc6fe7 100644 --- a/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py +++ b/adf_core_python/core/gateway/component/module/complex/gateway_human_detector.py @@ -4,6 +4,13 @@ from rcrs_core.entities.human import Human +from adf_core_python.core.component.module.complex.human_detector import ( + HumanDetector, +) +from adf_core_python.core.gateway.component.module.complex.gateway_target_detector import ( + GatewayTargetDetector, +) + if TYPE_CHECKING: from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -12,12 +19,6 @@ from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData - from adf_core_python.core.component.module.complex.human_detector import ( - HumanDetector, - ) - from adf_core_python.core.gateway.component.module.complex.gateway_target_detector import ( - GatewayTargetDetector, - ) from adf_core_python.core.gateway.gateway_module import GatewayModule diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_center.py b/adf_core_python/core/launcher/connect/connector_ambulance_center.py index 4e716a2..ac99bf7 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_center.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_center.py @@ -65,6 +65,13 @@ def connect( gateway_agent: Optional[GatewayAgent] = None if isinstance(gateway_launcher, GatewayLauncher): gateway_agent = GatewayAgent(gateway_launcher) + if isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + gateway_thread.daemon = True + gateway_thread.start() component_thread = threading.Thread( target=component_launcher.connect, @@ -86,11 +93,4 @@ def connect( ) threads[component_thread] = finish_post_connect_event - if isinstance(gateway_launcher, GatewayLauncher) and isinstance(gateway_agent, GatewayAgent): - gateway_thread = threading.Thread( - target=gateway_launcher.connect, - args=(gateway_agent,), - ) - threads[gateway_thread] = finish_post_connect_event - return threads diff --git a/adf_core_python/core/launcher/connect/connector_ambulance_team.py b/adf_core_python/core/launcher/connect/connector_ambulance_team.py index 3e8d47e..b354c7c 100644 --- a/adf_core_python/core/launcher/connect/connector_ambulance_team.py +++ b/adf_core_python/core/launcher/connect/connector_ambulance_team.py @@ -65,6 +65,13 @@ def connect( gateway_agent: Optional[GatewayAgent] = None if isinstance(gateway_launcher, GatewayLauncher): gateway_agent = GatewayAgent(gateway_launcher) + if isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + gateway_thread.daemon = True + gateway_thread.start() component_thread = threading.Thread( target=component_launcher.connect, @@ -86,11 +93,4 @@ def connect( ) threads[component_thread] = finish_post_connect_event - if isinstance(gateway_launcher, GatewayLauncher) and isinstance(gateway_agent, GatewayAgent): - gateway_thread = threading.Thread( - target=gateway_launcher.connect, - args=(gateway_agent,), - ) - threads[gateway_thread] = finish_post_connect_event - return threads diff --git a/adf_core_python/core/launcher/connect/connector_fire_brigade.py b/adf_core_python/core/launcher/connect/connector_fire_brigade.py index df420a2..6e5fda3 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_brigade.py +++ b/adf_core_python/core/launcher/connect/connector_fire_brigade.py @@ -63,6 +63,13 @@ def connect( gateway_agent: Optional[GatewayAgent] = None if isinstance(gateway_launcher, GatewayLauncher): gateway_agent = GatewayAgent(gateway_launcher) + if isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + gateway_thread.daemon = True + gateway_thread.start() component_thread = threading.Thread( target=component_launcher.connect, @@ -84,11 +91,4 @@ def connect( ) threads[component_thread] = finish_post_connect_event - if isinstance(gateway_launcher, GatewayLauncher) and isinstance(gateway_agent, GatewayAgent): - gateway_thread = threading.Thread( - target=gateway_launcher.connect, - args=(gateway_agent,), - ) - threads[gateway_thread] = finish_post_connect_event - return threads diff --git a/adf_core_python/core/launcher/connect/connector_fire_station.py b/adf_core_python/core/launcher/connect/connector_fire_station.py index 5cb7d01..2263167 100644 --- a/adf_core_python/core/launcher/connect/connector_fire_station.py +++ b/adf_core_python/core/launcher/connect/connector_fire_station.py @@ -63,6 +63,13 @@ def connect( gateway_agent: Optional[GatewayAgent] = None if isinstance(gateway_launcher, GatewayLauncher): gateway_agent = GatewayAgent(gateway_launcher) + if isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + gateway_thread.daemon = True + gateway_thread.start() component_thread = threading.Thread( target=component_launcher.connect, @@ -84,11 +91,4 @@ def connect( ) threads[component_thread] = finish_post_connect_event - if isinstance(gateway_launcher, GatewayLauncher) and isinstance(gateway_agent, GatewayAgent): - gateway_thread = threading.Thread( - target=gateway_launcher.connect, - args=(gateway_agent,), - ) - threads[gateway_thread] = finish_post_connect_event - return threads diff --git a/adf_core_python/core/launcher/connect/connector_police_force.py b/adf_core_python/core/launcher/connect/connector_police_force.py index 9b5e822..e2d379b 100644 --- a/adf_core_python/core/launcher/connect/connector_police_force.py +++ b/adf_core_python/core/launcher/connect/connector_police_force.py @@ -63,6 +63,13 @@ def connect( gateway_agent: Optional[GatewayAgent] = None if isinstance(gateway_launcher, GatewayLauncher): gateway_agent = GatewayAgent(gateway_launcher) + if isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + gateway_thread.daemon = True + gateway_thread.start() component_thread = threading.Thread( target=component_launcher.connect, @@ -84,13 +91,4 @@ def connect( ) threads[component_thread] = finish_post_connect_event - if isinstance(gateway_launcher, GatewayLauncher) and isinstance( - gateway_agent, GatewayAgent - ): - gateway_thread = threading.Thread( - target=gateway_launcher.connect, - args=(gateway_agent,), - ) - threads[gateway_thread] = finish_post_connect_event - return threads diff --git a/adf_core_python/core/launcher/connect/connector_police_office.py b/adf_core_python/core/launcher/connect/connector_police_office.py index 1553790..1d26a92 100644 --- a/adf_core_python/core/launcher/connect/connector_police_office.py +++ b/adf_core_python/core/launcher/connect/connector_police_office.py @@ -65,6 +65,13 @@ def connect( gateway_agent: Optional[GatewayAgent] = None if isinstance(gateway_launcher, GatewayLauncher): gateway_agent = GatewayAgent(gateway_launcher) + if isinstance(gateway_agent, GatewayAgent): + gateway_thread = threading.Thread( + target=gateway_launcher.connect, + args=(gateway_agent,), + ) + gateway_thread.daemon = True + gateway_thread.start() component_thread = threading.Thread( target=component_launcher.connect, @@ -86,11 +93,4 @@ def connect( ) threads[component_thread] = finish_post_connect_event - if isinstance(gateway_launcher, GatewayLauncher) and isinstance(gateway_agent, GatewayAgent): - gateway_thread = threading.Thread( - target=gateway_launcher.connect, - args=(gateway_agent,), - ) - threads[gateway_thread] = finish_post_connect_event - return threads diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 0f98784..2ae7158 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -39,7 +39,7 @@ def initialize( message_manager, develop_data, ) - + self._search: Search = cast( Search, module_manager.get_module( diff --git a/adf_core_python/launcher.py b/adf_core_python/launcher.py index a34d21c..89cb927 100644 --- a/adf_core_python/launcher.py +++ b/adf_core_python/launcher.py @@ -80,7 +80,7 @@ def __init__( parser.add_argument("--debug", action="store_true", help="debug flag") parser.add_argument( "--java", - action="store_true", + action="store_true", help="using java module flag", ) args = parser.parse_args() diff --git a/java/lib/build.gradle b/java/lib/build.gradle index de61a5d..3aae4e5 100644 --- a/java/lib/build.gradle +++ b/java/lib/build.gradle @@ -5,6 +5,16 @@ plugins { repositories { mavenCentral() + + maven { + url = 'https://sourceforge.net/projects/jsi/files/m2_repo' + } + maven { + url = 'https://repo.enonic.com/public/' + } + maven { + url 'https://jitpack.io' + } } dependencies { @@ -19,7 +29,7 @@ dependencies { implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.1' // Protobuf - implementation 'com.google.protobuf:protobuf-java:4.28.3' + implementation 'com.google.protobuf:protobuf-java:3.19.1' // Annotation implementation 'jakarta.annotation:jakarta.annotation-api:3.0.0' @@ -32,20 +42,6 @@ dependencies { testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } -repositories { - mavenCentral() - - maven { - url = 'https://sourceforge.net/projects/jsi/files/m2_repo' - } - maven { - url = 'https://repo.enonic.com/public/' - } - maven { - url 'https://jitpack.io' - } -} - java { toolchain { languageVersion = JavaLanguageVersion.of(17) From 58e2d678a513a92aeed0b54efa0710845e285fc7 Mon Sep 17 00:00:00 2001 From: harrki Date: Mon, 16 Dec 2024 13:56:34 +0900 Subject: [PATCH 197/249] feat:add java's github package ci/cd --- .github/workflows/ci.yaml | 19 +++++++++++++++++++ java/lib/build.gradle | 14 ++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9854a70..b10f9d2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -122,3 +122,22 @@ jobs: - name: Run pytest run: pytest tests/ + + publish: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Publish package + run: cd java && ./gradlew publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/java/lib/build.gradle b/java/lib/build.gradle index 3aae4e5..46f6e8d 100644 --- a/java/lib/build.gradle +++ b/java/lib/build.gradle @@ -1,6 +1,7 @@ plugins { id('java-library') id('com.google.protobuf') version "0.9.4" + id('maven-publish') } repositories { @@ -51,3 +52,16 @@ java { tasks.named('test') { useJUnitPlatform() } + +publishing { + repositories { + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/adf-python/adf-core-python" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } +} \ No newline at end of file From 56fee121f24acd1b1484b79c30d1943ce67eb747 Mon Sep 17 00:00:00 2001 From: harrki Date: Mon, 16 Dec 2024 14:50:37 +0900 Subject: [PATCH 198/249] fix: java's github package ci/cd --- java/lib/build.gradle | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/java/lib/build.gradle b/java/lib/build.gradle index 46f6e8d..386d59b 100644 --- a/java/lib/build.gradle +++ b/java/lib/build.gradle @@ -54,14 +54,24 @@ tasks.named('test') { } publishing { - repositories { - maven { - name = "GitHubPackages" - url = "https://maven.pkg.github.com/adf-python/adf-core-python" - credentials { - username = System.getenv("GITHUB_ACTOR") - password = System.getenv("GITHUB_TOKEN") - } + publications { + mavenJava(MavenPublication) { + groupId = 'com.github.adf-python' + artifactId = 'adf-core-python' + version = 'master-SNAPSHOT' + + from components.java + } } - } -} \ No newline at end of file + + repositories { + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/adf-python/adf-core-python" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } +} From 5cdc74af5b7a423445307b1eb5a49d0e0e4dc186 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 12 Dec 2024 19:22:04 +0900 Subject: [PATCH 199/249] feat: Add CommandExecutor, CommandPicker, and DefaultCommandPickerPolice classes for centralized command handling --- .../component/centralized/command_executor.py | 93 ++++++++++++++++++ .../component/centralized/command_picker.py | 95 +++++++++++++++++++ .../default_command_picker_ambulance.py | 88 +++++++++++++++++ .../default_command_picker_fire.py | 88 +++++++++++++++++ .../default_command_picker_police.py | 80 ++++++++++++++++ 5 files changed, 444 insertions(+) create mode 100644 adf_core_python/core/component/centralized/command_executor.py create mode 100644 adf_core_python/core/component/centralized/command_picker.py create mode 100644 adf_core_python/implement/centralized/default_command_picker_ambulance.py create mode 100644 adf_core_python/implement/centralized/default_command_picker_fire.py create mode 100644 adf_core_python/implement/centralized/default_command_picker_police.py diff --git a/adf_core_python/core/component/centralized/command_executor.py b/adf_core_python/core/component/centralized/command_executor.py new file mode 100644 index 0000000..80f70c5 --- /dev/null +++ b/adf_core_python/core/component/centralized/command_executor.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Generic, Optional, TypeVar + +from adf_core_python.core.agent.action.action import Action +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.communication.communication_message import ( + CommunicationMessage, +) + +T = TypeVar("T", bound=CommunicationMessage) + + +class CommandExecutor(ABC, Generic[T]): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + self._agent_info = agent_info + self._world_info = world_info + self._scenario_info = scenario_info + self._module_manager = module_manager + self._develop_data = develop_data + + self._result: Optional[Action] = None + + self._count_precompute: int = 0 + self._count_prepare: int = 0 + self._count_resume: int = 0 + self._count_update_info: int = 0 + self._count_update_info_current_time: int = 0 + + @abstractmethod + def set_command(self, command: T) -> CommandExecutor: + pass + + @abstractmethod + def calculate(self) -> CommandExecutor: + pass + + def get_action(self) -> Optional[Action]: + return self._result + + def precompute(self) -> CommandExecutor: + self._count_precompute += 1 + return self + + def prepare(self) -> CommandExecutor: + self._count_prepare += 1 + return self + + def resume(self) -> CommandExecutor: + self._count_resume += 1 + return self + + def update_info(self) -> CommandExecutor: + if self._count_update_info_current_time != self._agent_info.get_time(): + self._count_update_info_current_time = self._agent_info.get_time() + self._count_update_info += 1 + return self + + def get_count_precompute(self) -> int: + return self._count_precompute + + def get_count_prepare(self) -> int: + return self._count_prepare + + def get_count_resume(self) -> int: + return self._count_resume + + def get_count_update_info(self) -> int: + return self._count_update_info + + def reset_count_precompute(self) -> None: + self._count_precompute = 0 + + def reset_count_prepare(self) -> None: + self._count_prepare = 0 + + def reset_count_resume(self) -> None: + self._count_resume = 0 + + def reset_count_update_info(self) -> None: + self._count_update_info = 0 diff --git a/adf_core_python/core/component/centralized/command_picker.py b/adf_core_python/core/component/centralized/command_picker.py new file mode 100644 index 0000000..6e84e52 --- /dev/null +++ b/adf_core_python/core/component/centralized/command_picker.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod + +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.communication.communication_message import ( + CommunicationMessage, +) + + +class CommandPicker(ABC): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + self._agent_info = agent_info + self._world_info = world_info + self._scenario_info = scenario_info + self._module_manager = module_manager + self._develop_data = develop_data + self._count_precompute: int = 0 + self._count_prepare: int = 0 + self._count_resume: int = 0 + self._count_update_info: int = 0 + self._count_update_info_current_time: int = 0 + + @abstractmethod + def set_allocator_result( + self, allocation_data: dict[EntityID, EntityID] + ) -> CommandPicker: + pass + + @abstractmethod + def calculate(self) -> CommandPicker: + pass + + @abstractmethod + def get_result(self) -> list[CommunicationMessage]: + pass + + def precompute(self) -> CommandPicker: + self._count_precompute += 1 + return self + + def prepare(self) -> CommandPicker: + self._count_prepare += 1 + return self + + def resume(self) -> CommandPicker: + self._count_resume += 1 + return self + + def update_info(self) -> CommandPicker: + if self._count_update_info_current_time != self._agent_info.get_time(): + self._count_update_info_current_time = self._agent_info.get_time() + self._count_update_info = 0 + self._count_update_info += 1 + return self + + def get_count_precompute(self) -> int: + return self._count_precompute + + def get_count_prepare(self) -> int: + return self._count_prepare + + def get_count_resume(self) -> int: + return self._count_resume + + def get_count_update_info(self) -> int: + return self._count_update_info + + def get_count_update_info_current_time(self) -> int: + return self._count_update_info_current_time + + def reset_count_precompute(self) -> None: + self._count_precompute = 0 + + def reset_count_prepare(self) -> None: + self._count_prepare = 0 + + def reset_count_resume(self) -> None: + self._count_resume = 0 + + def reset_count_update_info(self) -> None: + self._count_update_info = 0 diff --git a/adf_core_python/implement/centralized/default_command_picker_ambulance.py b/adf_core_python/implement/centralized/default_command_picker_ambulance.py new file mode 100644 index 0000000..4092b8c --- /dev/null +++ b/adf_core_python/implement/centralized/default_command_picker_ambulance.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +from rcrs_core.entities.ambulanceTeam import AmbulanceTeam +from rcrs_core.entities.area import Area +from rcrs_core.entities.human import Human +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( + CommandAmbulance, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( + CommandScout, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.centralized.command_picker import CommandPicker +from adf_core_python.core.component.communication.communication_message import ( + CommunicationMessage, +) + + +class DefaultCommandPickerAmbulance(CommandPicker): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self.messages: list[CommunicationMessage] = [] + self.scout_distance: int = 40000 + self.allocation_data: dict[EntityID, EntityID] = {} + + def set_allocator_result( + self, allocation_data: dict[EntityID, EntityID] + ) -> CommandPicker: + self.allocation_data = allocation_data + return self + + def calculate(self) -> CommandPicker: + self.messages.clear() + if not self.allocation_data: + return self + + for ambulance_id in self.allocation_data.keys(): + agent = self._world_info.get_entity(ambulance_id) + if agent is None or not isinstance(agent, AmbulanceTeam): + continue + + target = self._world_info.get_entity(self.allocation_data[ambulance_id]) + if target is None: + continue + + if isinstance(target, Human): + command = CommandAmbulance( + True, + ambulance_id, + agent.get_id(), + CommandAmbulance.ACTION_RESCUE, + StandardMessagePriority.NORMAL, + target.get_id(), + ) + self.messages.append(command) + + if isinstance(target, Area): + command = CommandScout( + True, + ambulance_id, + agent.get_id(), + self.scout_distance, + StandardMessagePriority.NORMAL, + target.get_id(), + ) + self.messages.append(command) + return self + + def get_result(self) -> list[CommunicationMessage]: + return self.messages diff --git a/adf_core_python/implement/centralized/default_command_picker_fire.py b/adf_core_python/implement/centralized/default_command_picker_fire.py new file mode 100644 index 0000000..99f5b72 --- /dev/null +++ b/adf_core_python/implement/centralized/default_command_picker_fire.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +from rcrs_core.entities.area import Area +from rcrs_core.entities.fireBrigade import FireBrigade +from rcrs_core.entities.human import Human +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( + CommandAmbulance, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( + CommandScout, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.centralized.command_picker import CommandPicker +from adf_core_python.core.component.communication.communication_message import ( + CommunicationMessage, +) + + +class DefaultCommandPickerFire(CommandPicker): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self.messages: list[CommunicationMessage] = [] + self.scout_distance: int = 40000 + self.allocation_data: dict[EntityID, EntityID] = {} + + def set_allocator_result( + self, allocation_data: dict[EntityID, EntityID] + ) -> CommandPicker: + self.allocation_data = allocation_data + return self + + def calculate(self) -> CommandPicker: + self.messages.clear() + if not self.allocation_data: + return self + + for ambulance_id in self.allocation_data.keys(): + agent = self._world_info.get_entity(ambulance_id) + if agent is None or not isinstance(agent, FireBrigade): + continue + + target = self._world_info.get_entity(self.allocation_data[ambulance_id]) + if target is None: + continue + + if isinstance(target, Human): + command = CommandAmbulance( + True, + ambulance_id, + agent.get_id(), + CommandAmbulance.ACTION_RESCUE, + StandardMessagePriority.NORMAL, + target.get_id(), + ) + self.messages.append(command) + + if isinstance(target, Area): + command = CommandScout( + True, + ambulance_id, + agent.get_id(), + self.scout_distance, + StandardMessagePriority.NORMAL, + target.get_id(), + ) + self.messages.append(command) + return self + + def get_result(self) -> list[CommunicationMessage]: + return self.messages diff --git a/adf_core_python/implement/centralized/default_command_picker_police.py b/adf_core_python/implement/centralized/default_command_picker_police.py new file mode 100644 index 0000000..9f2d044 --- /dev/null +++ b/adf_core_python/implement/centralized/default_command_picker_police.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +from rcrs_core.entities.area import Area +from rcrs_core.entities.human import Human +from rcrs_core.entities.policeForce import PoliceForce +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( + CommandAmbulance, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_police import ( + CommandPolice, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( + CommandScout, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.component.centralized.command_picker import CommandPicker +from adf_core_python.core.component.communication.communication_message import ( + CommunicationMessage, +) + + +class DefaultCommandPickerPolice(CommandPicker): + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self.messages: list[CommunicationMessage] = [] + self.allocation_data: dict[EntityID, EntityID] = {} + + def set_allocator_result( + self, allocation_data: dict[EntityID, EntityID] + ) -> CommandPicker: + self.allocation_data = allocation_data + return self + + def calculate(self) -> CommandPicker: + self.messages.clear() + if not self.allocation_data: + return self + + for ambulance_id in self.allocation_data.keys(): + agent = self._world_info.get_entity(ambulance_id) + if agent is None or not isinstance(agent, PoliceForce): + continue + + target = self._world_info.get_entity(self.allocation_data[ambulance_id]) + if target is None: + continue + + if isinstance(target, Area): + # TODO: Implement the command + # command = CommandPolice( + # True, + # agent.get_id(), + + # target.get_id(), + # StandardMessagePriority.NORMAL, + # ) + # self.messages.append(command) + pass + return self + + def get_result(self) -> list[CommunicationMessage]: + return self.messages From 10f26d652a80cddab7c846f705b66447c1c7e9cc Mon Sep 17 00:00:00 2001 From: shima004 Date: Mon, 16 Dec 2024 01:09:40 +0900 Subject: [PATCH 200/249] fix: Update agent ID retrieval in command picker classes for consistency --- .../default_command_picker_ambulance.py | 4 ++-- .../default_command_picker_fire.py | 4 ++-- .../default_command_picker_police.py | 19 +++++++++---------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/adf_core_python/implement/centralized/default_command_picker_ambulance.py b/adf_core_python/implement/centralized/default_command_picker_ambulance.py index 4092b8c..9af9523 100644 --- a/adf_core_python/implement/centralized/default_command_picker_ambulance.py +++ b/adf_core_python/implement/centralized/default_command_picker_ambulance.py @@ -65,7 +65,7 @@ def calculate(self) -> CommandPicker: command = CommandAmbulance( True, ambulance_id, - agent.get_id(), + self._agent_info.get_entity_id(), CommandAmbulance.ACTION_RESCUE, StandardMessagePriority.NORMAL, target.get_id(), @@ -76,7 +76,7 @@ def calculate(self) -> CommandPicker: command = CommandScout( True, ambulance_id, - agent.get_id(), + self._agent_info.get_entity_id(), self.scout_distance, StandardMessagePriority.NORMAL, target.get_id(), diff --git a/adf_core_python/implement/centralized/default_command_picker_fire.py b/adf_core_python/implement/centralized/default_command_picker_fire.py index 99f5b72..435d931 100644 --- a/adf_core_python/implement/centralized/default_command_picker_fire.py +++ b/adf_core_python/implement/centralized/default_command_picker_fire.py @@ -65,7 +65,7 @@ def calculate(self) -> CommandPicker: command = CommandAmbulance( True, ambulance_id, - agent.get_id(), + self._agent_info.get_entity_id(), CommandAmbulance.ACTION_RESCUE, StandardMessagePriority.NORMAL, target.get_id(), @@ -76,7 +76,7 @@ def calculate(self) -> CommandPicker: command = CommandScout( True, ambulance_id, - agent.get_id(), + self._agent_info.get_entity_id(), self.scout_distance, StandardMessagePriority.NORMAL, target.get_id(), diff --git a/adf_core_python/implement/centralized/default_command_picker_police.py b/adf_core_python/implement/centralized/default_command_picker_police.py index 9f2d044..75b7ef7 100644 --- a/adf_core_python/implement/centralized/default_command_picker_police.py +++ b/adf_core_python/implement/centralized/default_command_picker_police.py @@ -64,16 +64,15 @@ def calculate(self) -> CommandPicker: continue if isinstance(target, Area): - # TODO: Implement the command - # command = CommandPolice( - # True, - # agent.get_id(), - - # target.get_id(), - # StandardMessagePriority.NORMAL, - # ) - # self.messages.append(command) - pass + command = CommandPolice( + True, + agent.get_id(), + self._agent_info.get_entity_id(), + CommandPolice.ACTION_AUTONOMY, + StandardMessagePriority.NORMAL, + target.get_id(), + ) + self.messages.append(command) return self def get_result(self) -> list[CommunicationMessage]: From 524b2a9b0c3dd76d79a5fc42d4d14d382089a5ec Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 18 Dec 2024 01:50:45 +0900 Subject: [PATCH 201/249] feat: Integrate CommandPicker into TacticsCenter and related classes for improved command handling --- .../core/agent/module/module_manager.py | 54 ++++ .../component/centralized/command_executor.py | 22 +- .../component/centralized/command_picker.py | 21 +- .../core/component/tactics/tactics_center.py | 4 +- .../default_command_executor_ambulance.py | 302 ++++++++++++++++++ .../default_command_executor_fire.py | 243 ++++++++++++++ .../default_command_executor_police.py | 261 +++++++++++++++ .../default_command_executor_scout.py | 158 +++++++++ .../default_command_executor_scout_police.py | 170 ++++++++++ .../default_tactics_ambulance_center.py | 21 +- .../tactics/default_tactics_ambulance_team.py | 51 ++- .../tactics/default_tactics_fire_brigade.py | 51 ++- .../tactics/default_tactics_fire_station.py | 21 +- .../tactics/default_tactics_police_force.py | 54 +++- .../tactics/default_tactics_police_office.py | 21 +- config/module.yaml | 53 ++- 16 files changed, 1453 insertions(+), 54 deletions(-) create mode 100644 adf_core_python/implement/centralized/default_command_executor_ambulance.py create mode 100644 adf_core_python/implement/centralized/default_command_executor_fire.py create mode 100644 adf_core_python/implement/centralized/default_command_executor_police.py create mode 100644 adf_core_python/implement/centralized/default_command_executor_scout.py create mode 100644 adf_core_python/implement/centralized/default_command_executor_scout_police.py diff --git a/adf_core_python/core/agent/module/module_manager.py b/adf_core_python/core/agent/module/module_manager.py index 18346aa..0d5695c 100644 --- a/adf_core_python/core/agent/module/module_manager.py +++ b/adf_core_python/core/agent/module/module_manager.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Any, Optional from adf_core_python.core.component.action.extend_action import ExtendAction +from adf_core_python.core.component.centralized.command_executor import CommandExecutor +from adf_core_python.core.component.centralized.command_picker import CommandPicker from adf_core_python.core.component.communication.channel_subscriber import ( ChannelSubscriber, ) @@ -189,6 +191,58 @@ def get_message_coordinator( f"Message coordinator {class_name} is not a subclass of MessageCoordinator" ) + def get_command_executor( + self, command_executor_name: str, default_command_executor_name: str + ) -> CommandExecutor: + class_name = self._module_config.get_value_or_default( + command_executor_name, default_command_executor_name + ) + + command_executor_class: type = self._load_module(class_name) + + instance = self._executors.get(command_executor_name) + if instance is not None: + return instance + + if issubclass(command_executor_class, CommandExecutor): + instance = command_executor_class( + self._agent_info, + self._world_info, + self._scenario_info, + self, + self._develop_data, + ) + self._executors[command_executor_name] = instance + return instance + + raise RuntimeError(f"Command executor {class_name} is not a subclass of object") + + def get_command_picker( + self, command_picker_name: str, default_command_picker_name: str + ) -> CommandPicker: + class_name = self._module_config.get_value_or_default( + command_picker_name, default_command_picker_name + ) + + command_picker_class: type = self._load_module(class_name) + + instance = self._pickers.get(command_picker_name) + if instance is not None: + return instance + + if issubclass(command_picker_class, CommandPicker): + instance = command_picker_class( + self._agent_info, + self._world_info, + self._scenario_info, + self, + self._develop_data, + ) + self._pickers[command_picker_name] = instance + return instance + + raise RuntimeError(f"Command picker {class_name} is not a subclass of object") + def _load_module(self, class_name: str) -> type: module_name, module_class_name = class_name.rsplit(".", 1) module = importlib.import_module(module_name) diff --git a/adf_core_python/core/component/centralized/command_executor.py b/adf_core_python/core/component/centralized/command_executor.py index 80f70c5..ebd501f 100644 --- a/adf_core_python/core/component/centralized/command_executor.py +++ b/adf_core_python/core/component/centralized/command_executor.py @@ -1,14 +1,18 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Generic, Optional, TypeVar +from typing import TYPE_CHECKING, Generic, Optional, TypeVar + +if TYPE_CHECKING: + from adf_core_python.core.agent.communication.message_manager import MessageManager + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.agent.action.action import Action -from adf_core_python.core.agent.develop.develop_data import DevelopData -from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import ScenarioInfo -from adf_core_python.core.agent.info.world_info import WorldInfo -from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.component.communication.communication_message import ( CommunicationMessage, ) @@ -50,7 +54,7 @@ def calculate(self) -> CommandExecutor: def get_action(self) -> Optional[Action]: return self._result - def precompute(self) -> CommandExecutor: + def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: self._count_precompute += 1 return self @@ -58,11 +62,11 @@ def prepare(self) -> CommandExecutor: self._count_prepare += 1 return self - def resume(self) -> CommandExecutor: + def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: self._count_resume += 1 return self - def update_info(self) -> CommandExecutor: + def update_info(self, message_manager: MessageManager) -> CommandExecutor: if self._count_update_info_current_time != self._agent_info.get_time(): self._count_update_info_current_time = self._agent_info.get_time() self._count_update_info += 1 diff --git a/adf_core_python/core/component/centralized/command_picker.py b/adf_core_python/core/component/centralized/command_picker.py index 6e84e52..19c869a 100644 --- a/adf_core_python/core/component/centralized/command_picker.py +++ b/adf_core_python/core/component/centralized/command_picker.py @@ -1,14 +1,19 @@ from __future__ import annotations from abc import ABC, abstractmethod +from typing import TYPE_CHECKING from rcrs_core.worldmodel.entityID import EntityID -from adf_core_python.core.agent.develop.develop_data import DevelopData -from adf_core_python.core.agent.info.agent_info import AgentInfo -from adf_core_python.core.agent.info.scenario_info import ScenarioInfo -from adf_core_python.core.agent.info.world_info import WorldInfo -from adf_core_python.core.agent.module.module_manager import ModuleManager +if TYPE_CHECKING: + from adf_core_python.core.agent.develop.develop_data import DevelopData + from adf_core_python.core.agent.info.agent_info import AgentInfo + from adf_core_python.core.agent.info.scenario_info import ScenarioInfo + from adf_core_python.core.agent.info.world_info import WorldInfo + from adf_core_python.core.agent.module.module_manager import ModuleManager + +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData from adf_core_python.core.component.communication.communication_message import ( CommunicationMessage, ) @@ -48,7 +53,7 @@ def calculate(self) -> CommandPicker: def get_result(self) -> list[CommunicationMessage]: pass - def precompute(self) -> CommandPicker: + def precompute(self, precompute_data: PrecomputeData) -> CommandPicker: self._count_precompute += 1 return self @@ -56,11 +61,11 @@ def prepare(self) -> CommandPicker: self._count_prepare += 1 return self - def resume(self) -> CommandPicker: + def resume(self, precompute_data: PrecomputeData) -> CommandPicker: self._count_resume += 1 return self - def update_info(self) -> CommandPicker: + def update_info(self, message_manager: MessageManager) -> CommandPicker: if self._count_update_info_current_time != self._agent_info.get_time(): self._count_update_info_current_time = self._agent_info.get_time() self._count_update_info = 0 diff --git a/adf_core_python/core/component/tactics/tactics_center.py b/adf_core_python/core/component/tactics/tactics_center.py index 2469cc6..0e76a79 100644 --- a/adf_core_python/core/component/tactics/tactics_center.py +++ b/adf_core_python/core/component/tactics/tactics_center.py @@ -3,6 +3,8 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Any, Optional +from adf_core_python.core.component.centralized.command_picker import CommandPicker + if TYPE_CHECKING: from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData @@ -18,7 +20,7 @@ class TacticsCenter(ABC): def __init__(self, parent: Optional[TacticsCenter] = None) -> None: self._parent = parent self._modules: list[AbstractModule] = [] - self._command_pickers: list[Any] = [] + self._command_pickers: list[CommandPicker] = [] @abstractmethod def initialize( diff --git a/adf_core_python/implement/centralized/default_command_executor_ambulance.py b/adf_core_python/implement/centralized/default_command_executor_ambulance.py new file mode 100644 index 0000000..f2c00d6 --- /dev/null +++ b/adf_core_python/implement/centralized/default_command_executor_ambulance.py @@ -0,0 +1,302 @@ +from typing import Optional, cast + +from rcrs_core.entities.ambulanceTeam import AmbulanceTeam +from rcrs_core.entities.area import Area +from rcrs_core.entities.civilian import Civilian +from rcrs_core.entities.human import Human +from rcrs_core.entities.refuge import Refuge +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.common.action_move import ActionMove +from adf_core_python.core.agent.action.common.action_rest import ActionRest +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( + CommandAmbulance, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( + MessageReport, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.centralized.command_executor import CommandExecutor +from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning + + +class DefaultCommandExecutorAmbulance(CommandExecutor): + ACTION_UNKNOWN: int = -1 + ACTION_REST = CommandAmbulance.ACTION_REST + ACTION_MOVE = CommandAmbulance.ACTION_MOVE + ACTION_RESCUE = CommandAmbulance.ACTION_RESCUE + ACTION_LOAD = CommandAmbulance.ACTION_LOAD + ACTION_UNLOAD = CommandAmbulance.ACTION_UNLOAD + ACTION_AUTONOMY = CommandAmbulance.ACTION_AUTONOMY + + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._command_type = self.ACTION_UNKNOWN + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultCommandExecutorAmbulance.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + self._action_transport = module_manager.get_extend_action( + "DefaultCommandExecutorAmbulance.ExtActionTransport", + "adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport", + ) + self._action_move = module_manager.get_extend_action( + "DefaultCommandExecutorAmbulance.ExtActionMove", + "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", + ) + + self._command_type: int = self.ACTION_UNKNOWN + self._target: Optional[EntityID] = None + self._commander: Optional[EntityID] = None + + def set_command(self, command: CommandAmbulance) -> CommandExecutor: + agent_id: EntityID = self._agent_info.get_entity_id() + if command.get_command_executor_agent_entity_id() != agent_id: + return self + + self._command_type = command.get_execute_action() or self.ACTION_UNKNOWN + self._target = command.get_command_target_entity_id() + self._commander = command.get_sender_entity_id() + return self + + def calculate(self) -> CommandExecutor: + self._result = None + match self._command_type: + case self.ACTION_REST: + position = self._agent_info.get_entity_id() + if self._target is None: + refuges = self._world_info.get_entity_ids_of_types([Refuge]) + if position in refuges: + self._result = ActionRest() + else: + path = self._path_planning.get_path(position, refuges[0]) + if path: + self._result = ActionMove(path) + else: + self._result = ActionRest() + return self + if position != self._target: + path = self._path_planning.get_path(position, self._target) + if path: + self._result = ActionMove(path) + return self + self._result = ActionRest() + return self + case self.ACTION_MOVE: + if self._target: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + case self.ACTION_RESCUE: + if self._target: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + case self.ACTION_LOAD: + if self._target: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + case self.ACTION_UNLOAD: + if self._target: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + case self.ACTION_AUTONOMY: + if self._target is None: + return self + target_entity = self._world_info.get_entity(self._target) + if isinstance(target_entity, Area): + if self._agent_info.some_one_on_board() is None: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + else: + self._result = ( + self._action_transport.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + elif isinstance(target_entity, Human): + self._result = ( + self._action_transport.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + + def update_info(self, message_manager: MessageManager) -> CommandExecutor: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self + + self._path_planning.update_info(message_manager) + self._action_transport.update_info(message_manager) + self._action_move.update_info(message_manager) + + if self._is_command_completed(): + if self._command_type == self.ACTION_UNKNOWN: + return self + if self._commander is None: + return self + + message_manager.add_message( + MessageReport( + True, + True, + False, + self._commander, + StandardMessagePriority.NORMAL, + ) + ) + + if self._command_type == self.ACTION_LOAD: + self._command_type = self.ACTION_UNLOAD + self._target = None + else: + self._command_type = self.ACTION_UNKNOWN + self._target = None + self._commander = None + + return self + + def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().precompute(precompute_data) + if self.get_count_precompute() >= 2: + return self + self._path_planning.precompute(precompute_data) + self._action_transport.precompute(precompute_data) + self._action_move.precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + self._path_planning.resume(precompute_data) + self._action_transport.resume(precompute_data) + self._action_move.resume(precompute_data) + return self + + def prepare(self) -> CommandExecutor: + super().prepare() + if self.get_count_prepare() >= 2: + return self + self._path_planning.prepare() + self._action_transport.prepare() + self._action_move.prepare() + return self + + def _is_command_completed(self) -> bool: + agent = self._agent_info.get_myself() + if not isinstance(agent, Human): + return False + + match self._command_type: + case self.ACTION_REST: + if self._target is None: + return agent.get_damage() == 0 + if (target_entity := self._world_info.get_entity(self._target)) is None: + return False + if isinstance(target_entity, Refuge): + return agent.get_damage() == 0 + return False + case self.ACTION_MOVE: + return ( + self._target is None + or self._agent_info.get_position_entity_id() == self._target + ) + case self.ACTION_RESCUE: + if self._target is None: + return True + human = self._world_info.get_entity(self._target) + if not isinstance(human, Human): + return True + return human.get_buriedness() == 0 or human.get_hp() == 0 + case self.ACTION_LOAD: + if self._target is None: + return True + human = self._world_info.get_entity(self._target) + if not isinstance(human, Human): + return True + if human.get_hp() == 0: + return True + if isinstance(human, Civilian): + self._command_type = self.ACTION_RESCUE + return self._is_command_completed() + if human.get_position() is not None: + position = human.get_position() + if position in self._world_info.get_entity_ids_of_types( + [AmbulanceTeam] + ): + return True + elif isinstance(self._world_info.get_entity(position), Refuge): + return True + return False + + case self.ACTION_UNLOAD: + if self._target is not None: + entity = self._world_info.get_entity(self._target) + if entity is not None and isinstance(entity, Refuge): + if self._target == self._agent_info.get_position_entity_id(): + return False + return self._agent_info.some_one_on_board() is None + case self.ACTION_AUTONOMY: + if self._target is not None: + target_entity = self._world_info.get_entity(self._target) + if isinstance(target_entity, Area): + self._command_type = ( + self._agent_info.some_one_on_board() is None + and self.ACTION_MOVE + or self.ACTION_UNLOAD + ) + return self._is_command_completed() + elif isinstance(target_entity, Human): + human = target_entity + if human.get_hp() == 0: + return True + self._command_type = ( + isinstance(human, Civilian) + and self.ACTION_LOAD + or self.ACTION_RESCUE + ) + return self._is_command_completed() + return True + case _: + return True diff --git a/adf_core_python/implement/centralized/default_command_executor_fire.py b/adf_core_python/implement/centralized/default_command_executor_fire.py new file mode 100644 index 0000000..abd2ebe --- /dev/null +++ b/adf_core_python/implement/centralized/default_command_executor_fire.py @@ -0,0 +1,243 @@ +from typing import Optional, cast + +from rcrs_core.entities.area import Area +from rcrs_core.entities.civilian import Civilian +from rcrs_core.entities.human import Human +from rcrs_core.entities.refuge import Refuge +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.common.action_move import ActionMove +from adf_core_python.core.agent.action.common.action_rest import ActionRest +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( + CommandAmbulance, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( + MessageReport, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.centralized.command_executor import CommandExecutor +from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning + + +class DefaultCommandExecutorFire(CommandExecutor): + ACTION_UNKNOWN: int = -1 + ACTION_REST = CommandAmbulance.ACTION_REST + ACTION_MOVE = CommandAmbulance.ACTION_MOVE + ACTION_RESCUE = CommandAmbulance.ACTION_RESCUE + ACTION_AUTONOMY = CommandAmbulance.ACTION_AUTONOMY + + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._command_type = self.ACTION_UNKNOWN + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultCommandExecutorFire.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + self._action_fire_rescue = module_manager.get_extend_action( + "DefaultCommandExecutorFire.ExtActionFireRescue", + "adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue", + ) + self._action_move = module_manager.get_extend_action( + "DefaultCommandExecutorFire.ExtActionMove", + "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", + ) + + self._command_type: int = self.ACTION_UNKNOWN + self._target: Optional[EntityID] = None + self._commander: Optional[EntityID] = None + + def set_command(self, command: CommandAmbulance) -> CommandExecutor: + agent_id: EntityID = self._agent_info.get_entity_id() + if command.get_command_executor_agent_entity_id() != agent_id: + return self + + self._command_type = command.get_execute_action() or self.ACTION_UNKNOWN + self._target = command.get_command_target_entity_id() + self._commander = command.get_sender_entity_id() + return self + + def calculate(self) -> CommandExecutor: + self._result = None + match self._command_type: + case self.ACTION_REST: + position = self._agent_info.get_entity_id() + if self._target is None: + refuges = self._world_info.get_entity_ids_of_types([Refuge]) + if position in refuges: + self._result = ActionRest() + else: + path = self._path_planning.get_path(position, refuges[0]) + if path: + self._result = ActionMove(path) + else: + self._result = ActionRest() + return self + if position != self._target: + path = self._path_planning.get_path(position, self._target) + if path: + self._result = ActionMove(path) + return self + self._result = ActionRest() + return self + case self.ACTION_MOVE: + if self._target: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + case self.ACTION_RESCUE: + if self._target: + self._result = ( + self._action_fire_rescue.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + case self.ACTION_AUTONOMY: + if self._target is None: + return self + target_entity = self._world_info.get_entity(self._target) + if isinstance(target_entity, Area): + if self._agent_info.some_one_on_board() is None: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + else: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + elif isinstance(target_entity, Human): + self._result = ( + self._action_fire_rescue.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + + def update_info(self, message_manager: MessageManager) -> CommandExecutor: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self + + self._path_planning.update_info(message_manager) + self._action_fire_rescue.update_info(message_manager) + self._action_move.update_info(message_manager) + + if self._is_command_completed(): + if self._command_type == self.ACTION_UNKNOWN: + return self + if self._commander is None: + return self + + message_manager.add_message( + MessageReport( + True, + True, + False, + self._commander, + StandardMessagePriority.NORMAL, + ) + ) + self._command_type = self.ACTION_UNKNOWN + self._target = None + self._commander = None + + return self + + def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().precompute(precompute_data) + if self.get_count_precompute() >= 2: + return self + self._path_planning.precompute(precompute_data) + self._action_fire_rescue.precompute(precompute_data) + self._action_move.precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + self._path_planning.resume(precompute_data) + self._action_fire_rescue.resume(precompute_data) + self._action_move.resume(precompute_data) + return self + + def prepare(self) -> CommandExecutor: + super().prepare() + if self.get_count_prepare() >= 2: + return self + self._path_planning.prepare() + self._action_fire_rescue.prepare() + self._action_move.prepare() + return self + + def _is_command_completed(self) -> bool: + agent = self._agent_info.get_myself() + if not isinstance(agent, Human): + return False + + match self._command_type: + case self.ACTION_REST: + if self._target is None: + return agent.get_damage() == 0 + if (target_entity := self._world_info.get_entity(self._target)) is None: + return False + if isinstance(target_entity, Refuge): + return agent.get_damage() == 0 + return False + case self.ACTION_MOVE: + return ( + self._target is None + or self._agent_info.get_position_entity_id() == self._target + ) + case self.ACTION_RESCUE: + if self._target is None: + return True + human = self._world_info.get_entity(self._target) + if not isinstance(human, Human): + return True + return human.get_buriedness() == 0 or human.get_hp() == 0 + case self.ACTION_AUTONOMY: + if self._target is not None: + target_entity = self._world_info.get_entity(self._target) + if isinstance(target_entity, Area): + self._command_type = self.ACTION_MOVE + return self._is_command_completed() + elif isinstance(target_entity, Human): + human = target_entity + if human.get_hp() == 0: + return True + if isinstance(human, Civilian): + self._command_type = self.ACTION_RESCUE + return self._is_command_completed() + return True + case _: + return True diff --git a/adf_core_python/implement/centralized/default_command_executor_police.py b/adf_core_python/implement/centralized/default_command_executor_police.py new file mode 100644 index 0000000..aecd574 --- /dev/null +++ b/adf_core_python/implement/centralized/default_command_executor_police.py @@ -0,0 +1,261 @@ +from typing import Optional, cast + +from rcrs_core.entities.area import Area +from rcrs_core.entities.blockade import Blockade +from rcrs_core.entities.human import Human +from rcrs_core.entities.refuge import Refuge +from rcrs_core.entities.road import Road +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.common.action_move import ActionMove +from adf_core_python.core.agent.action.common.action_rest import ActionRest +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_police import ( + CommandPolice, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( + MessageReport, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.centralized.command_executor import CommandExecutor +from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning + + +class DefaultCommandExecutorPolice(CommandExecutor): + ACTION_UNKNOWN: int = -1 + ACTION_REST = CommandPolice.ACTION_REST + ACTION_MOVE = CommandPolice.ACTION_MOVE + ACTION_CLEAR = CommandPolice.ACTION_CLEAR + ACTION_AUTONOMY = CommandPolice.ACTION_AUTONOMY + + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._command_type = self.ACTION_UNKNOWN + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultCommandExecutorPolice.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + self._action_clear = module_manager.get_extend_action( + "DefaultCommandExecutorPolice.ExtActionClear", + "adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear", + ) + self._action_move = module_manager.get_extend_action( + "DefaultCommandExecutorPolice.ExtActionMove", + "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", + ) + + self._command_type: int = self.ACTION_UNKNOWN + self._target: Optional[EntityID] = None + self._commander: Optional[EntityID] = None + + def set_command(self, command: CommandPolice) -> CommandExecutor: + agent_id: EntityID = self._agent_info.get_entity_id() + if command.get_command_executor_agent_entity_id() != agent_id: + return self + + self._command_type = command.get_execute_action() or self.ACTION_UNKNOWN + self._target = command.get_command_target_entity_id() + self._commander = command.get_sender_entity_id() + return self + + def calculate(self) -> CommandExecutor: + self._result = None + match self._command_type: + case self.ACTION_REST: + position = self._agent_info.get_entity_id() + if self._target is None: + refuges = self._world_info.get_entity_ids_of_types([Refuge]) + if position in refuges: + self._result = ActionRest() + else: + path = self._path_planning.get_path(position, refuges[0]) + if path: + self._result = ActionMove(path) + else: + self._result = ActionRest() + return self + if position != self._target: + path = self._path_planning.get_path(position, self._target) + if path: + self._result = ActionMove(path) + return self + self._result = ActionRest() + return self + case self.ACTION_MOVE: + if self._target: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + case self.ACTION_CLEAR: + if self._target: + self._result = ( + self._action_clear.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + case self.ACTION_AUTONOMY: + if self._target is None: + return self + target_entity = self._world_info.get_entity(self._target) + if isinstance(target_entity, Area): + if self._agent_info.some_one_on_board() is None: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + else: + self._result = ( + self._action_move.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + elif isinstance(target_entity, Human): + self._result = ( + self._action_clear.set_target_entity_id(self._target) + .calculate() + .get_action() + ) + return self + + def update_info(self, message_manager: MessageManager) -> CommandExecutor: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self + + self._path_planning.update_info(message_manager) + self._action_clear.update_info(message_manager) + self._action_move.update_info(message_manager) + + if self._is_command_completed(): + if self._command_type == self.ACTION_UNKNOWN: + return self + if self._commander is None: + return self + + message_manager.add_message( + MessageReport( + True, + True, + False, + self._commander, + StandardMessagePriority.NORMAL, + ) + ) + self._command_type = self.ACTION_UNKNOWN + self._target = None + self._commander = None + + return self + + def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().precompute(precompute_data) + if self.get_count_precompute() >= 2: + return self + self._path_planning.precompute(precompute_data) + self._action_clear.precompute(precompute_data) + self._action_move.precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + self._path_planning.resume(precompute_data) + self._action_clear.resume(precompute_data) + self._action_move.resume(precompute_data) + return self + + def prepare(self) -> CommandExecutor: + super().prepare() + if self.get_count_prepare() >= 2: + return self + self._path_planning.prepare() + self._action_clear.prepare() + self._action_move.prepare() + return self + + def _is_command_completed(self) -> bool: + agent = self._agent_info.get_myself() + if not isinstance(agent, Human): + return False + + match self._command_type: + case self.ACTION_REST: + if self._target is None: + return agent.get_damage() == 0 + if (target_entity := self._world_info.get_entity(self._target)) is None: + return False + if isinstance(target_entity, Refuge): + return agent.get_damage() == 0 + return False + case self.ACTION_MOVE: + return ( + self._target is None + or self._agent_info.get_position_entity_id() == self._target + ) + case self.ACTION_CLEAR: + if self._target is None: + return True + entity = self._world_info.get_entity(self._target) + if isinstance(entity, Road): + if entity.get_blockades is not None: + return len(entity.get_blockades()) == 0 + return self._agent_info.get_position_entity_id() == self._target + return True + case self.ACTION_AUTONOMY: + if self._target is not None: + target_entity = self._world_info.get_entity(self._target) + if isinstance(target_entity, Refuge): + self._command_type = ( + self.ACTION_REST + if agent.get_damage() > 0 + else self.ACTION_CLEAR + ) + return self._is_command_completed() + elif isinstance(target_entity, Area): + self._command_type = self.ACTION_CLEAR + return self._is_command_completed() + elif isinstance(target_entity, Human): + if target_entity.get_hp() == 0: + return True + if target_entity.get_position() is not None and isinstance( + self._world_info.get_entity(target_entity.get_position()), + Area, + ): + self._target = target_entity.get_position() + self._command_type = self.ACTION_CLEAR + return self._is_command_completed() + elif isinstance(target_entity, Blockade): + if target_entity.get_position() is not None: + self._target = target_entity.get_position() + self._command_type = self.ACTION_CLEAR + return self._is_command_completed() + return True + case _: + return True diff --git a/adf_core_python/implement/centralized/default_command_executor_scout.py b/adf_core_python/implement/centralized/default_command_executor_scout.py new file mode 100644 index 0000000..1186155 --- /dev/null +++ b/adf_core_python/implement/centralized/default_command_executor_scout.py @@ -0,0 +1,158 @@ +from typing import Optional, cast + +from rcrs_core.entities.area import Area +from rcrs_core.entities.human import Human +from rcrs_core.entities.refuge import Refuge +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.common.action_move import ActionMove +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( + CommandScout, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( + MessageReport, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.centralized.command_executor import CommandExecutor +from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning + + +class DefaultCommandExecutorScout(CommandExecutor): + ACTION_UNKNOWN: int = -1 + ACTION_SCOUT: int = 1 + + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._command_type = self.ACTION_UNKNOWN + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultCommandExecutorScout.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + + self._command_type: int = self.ACTION_UNKNOWN + self._targets: list[EntityID] = [] + self._commander: Optional[EntityID] = None + + def set_command(self, command: CommandScout) -> CommandExecutor: + agent_id: EntityID = self._agent_info.get_entity_id() + if command.get_command_executor_agent_entity_id() != agent_id: + return self + + target = command.get_command_target_entity_id() + if target is None: + target = self._agent_info.get_position_entity_id() + + self._command_type = self.ACTION_SCOUT + self._commander = command.get_sender_entity_id() + self._targets = [] + if (scout_distance := command.get_scout_range()) is None: + return self + + for entity in self._world_info.get_entities_of_types([Area]): + if isinstance(entity, Refuge): + continue + if self._world_info.get_distance(target, entity.get_id()) <= scout_distance: + self._targets.append(entity.get_id()) + + return self + + def calculate(self) -> CommandExecutor: + self._result = None + match self._command_type: + case self.ACTION_SCOUT: + if len(self._targets) == 0: + return self + path = self._path_planning.get_path( + self._agent_info.get_position_entity_id(), self._targets[0] + ) + if path is None: + return self + self._result = ActionMove(path) + case _: + return self + return self + + def update_info(self, message_manager: MessageManager) -> CommandExecutor: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self + + self._path_planning.update_info(message_manager) + + if self._is_command_completed(): + if self._command_type == self.ACTION_UNKNOWN: + return self + if self._commander is None: + return self + + message_manager.add_message( + MessageReport( + True, + True, + False, + self._commander, + StandardMessagePriority.NORMAL, + ) + ) + self._command_type = self.ACTION_UNKNOWN + self._target = None + self._commander = None + + return self + + def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().precompute(precompute_data) + if self.get_count_precompute() >= 2: + return self + self._path_planning.precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + self._path_planning.resume(precompute_data) + return self + + def prepare(self) -> CommandExecutor: + super().prepare() + if self.get_count_prepare() >= 2: + return self + self._path_planning.prepare() + return self + + def _is_command_completed(self) -> bool: + agent = self._agent_info.get_myself() + if not isinstance(agent, Human): + return False + + match self._command_type: + case self.ACTION_SCOUT: + if len(self._targets) != 0: + for entity in self._world_info.get_entities_of_types([Area]): + self._targets.remove(entity.get_id()) + return len(self._targets) == 0 + case _: + return True diff --git a/adf_core_python/implement/centralized/default_command_executor_scout_police.py b/adf_core_python/implement/centralized/default_command_executor_scout_police.py new file mode 100644 index 0000000..01f44c9 --- /dev/null +++ b/adf_core_python/implement/centralized/default_command_executor_scout_police.py @@ -0,0 +1,170 @@ +from typing import Optional, cast + +from rcrs_core.entities.area import Area +from rcrs_core.entities.human import Human +from rcrs_core.entities.refuge import Refuge +from rcrs_core.worldmodel.entityID import EntityID + +from adf_core_python.core.agent.action.common.action_move import ActionMove +from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( + CommandScout, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.message_report import ( + MessageReport, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( + StandardMessagePriority, +) +from adf_core_python.core.agent.develop.develop_data import DevelopData +from adf_core_python.core.agent.info.agent_info import AgentInfo +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo +from adf_core_python.core.agent.info.world_info import WorldInfo +from adf_core_python.core.agent.module.module_manager import ModuleManager +from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.action.extend_action import ExtendAction +from adf_core_python.core.component.centralized.command_executor import CommandExecutor +from adf_core_python.core.component.module.algorithm.path_planning import PathPlanning + + +class DefaultCommandExecutorScoutPolice(CommandExecutor): + ACTION_UNKNOWN: int = -1 + ACTION_SCOUT: int = 1 + + def __init__( + self, + agent_info: AgentInfo, + world_info: WorldInfo, + scenario_info: ScenarioInfo, + module_manager: ModuleManager, + develop_data: DevelopData, + ) -> None: + super().__init__( + agent_info, world_info, scenario_info, module_manager, develop_data + ) + self._command_type = self.ACTION_UNKNOWN + + self._path_planning: PathPlanning = cast( + PathPlanning, + module_manager.get_module( + "DefaultCommandExecutorScoutPolice.PathPlanning", + "adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning", + ), + ) + self._action_clear: ExtendAction = module_manager.get_extend_action( + "DefaultCommandExecutorScoutPolice.ExtActionClear", + "adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear", + ) + + self._command_type: int = self.ACTION_UNKNOWN + self._targets: list[EntityID] = [] + self._commander: Optional[EntityID] = None + + def set_command(self, command: CommandScout) -> CommandExecutor: + agent_id: EntityID = self._agent_info.get_entity_id() + if command.get_command_executor_agent_entity_id() != agent_id: + return self + + target = command.get_command_target_entity_id() + if target is None: + target = self._agent_info.get_position_entity_id() + + self._command_type = self.ACTION_SCOUT + self._commander = command.get_sender_entity_id() + self._targets = [] + if (scout_distance := command.get_scout_range()) is None: + return self + + for entity in self._world_info.get_entities_of_types([Area]): + if isinstance(entity, Refuge): + continue + if self._world_info.get_distance(target, entity.get_id()) <= scout_distance: + self._targets.append(entity.get_id()) + + return self + + def calculate(self) -> CommandExecutor: + self._result = None + match self._command_type: + case self.ACTION_SCOUT: + if len(self._targets) == 0: + return self + path = self._path_planning.get_path( + self._agent_info.get_position_entity_id(), self._targets[0] + ) + if path is None: + return self + action = ( + self._action_clear.set_target_entity_id(self._targets[0]) + .calculate() + .get_action() + ) + if action is None: + action = ActionMove(path) + self._result = action + case _: + return self + return self + + def update_info(self, message_manager: MessageManager) -> CommandExecutor: + super().update_info(message_manager) + if self.get_count_update_info() >= 2: + return self + + self._path_planning.update_info(message_manager) + + if self._is_command_completed(): + if self._command_type == self.ACTION_UNKNOWN: + return self + if self._commander is None: + return self + + message_manager.add_message( + MessageReport( + True, + True, + False, + self._commander, + StandardMessagePriority.NORMAL, + ) + ) + self._command_type = self.ACTION_UNKNOWN + self._target = None + self._commander = None + + return self + + def precompute(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().precompute(precompute_data) + if self.get_count_precompute() >= 2: + return self + self._path_planning.precompute(precompute_data) + return self + + def resume(self, precompute_data: PrecomputeData) -> CommandExecutor: + super().resume(precompute_data) + if self.get_count_resume() >= 2: + return self + self._path_planning.resume(precompute_data) + return self + + def prepare(self) -> CommandExecutor: + super().prepare() + if self.get_count_prepare() >= 2: + return self + self._path_planning.prepare() + return self + + def _is_command_completed(self) -> bool: + agent = self._agent_info.get_myself() + if not isinstance(agent, Human): + return False + + match self._command_type: + case self.ACTION_SCOUT: + if len(self._targets) != 0: + for entity in self._world_info.get_entities_of_types([Area]): + self._targets.remove(entity.get_id()) + return len(self._targets) == 0 + case _: + return True diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py b/adf_core_python/implement/tactics/default_tactics_ambulance_center.py index d1dd248..23a9d85 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_center.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_center.py @@ -1,5 +1,7 @@ from typing import cast +from rcrs_core.worldmodel.entityID import EntityID + from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -7,6 +9,7 @@ from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.centralized.command_picker import CommandPicker from adf_core_python.core.component.module.complex.target_allocator import ( TargetAllocator, ) @@ -33,7 +36,12 @@ def initialize( "adf_core_python.implement.module.complex.default_ambulance_target_allocator.DefaultAmbulanceTargetAllocator", ), ) + self._picker: CommandPicker = module_manager.get_command_picker( + "DefaultTacticsAmbulanceCenter.CommandPicker", + "adf_core_python.implement.centralized.default_command_picker_ambulance.DefaultCommandPickerAmbulance", + ) self.register_module(self._allocator) + self.register_command_picker(self._picker) def precompute( self, @@ -80,5 +88,14 @@ def think( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - # TODO: implement - pass + self.module_update_info(message_manager) + + allocation_result: dict[EntityID, EntityID] = ( + self._allocator.calculate().get_result() + ) + for message in ( + self._picker.set_allocator_result(allocation_result) + .calculate() + .get_result() + ): + message_manager.add_message(message) diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 2ae7158..8e7df9c 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -1,10 +1,19 @@ -from typing import cast +from typing import Optional, cast from rcrs_core.entities.ambulanceTeam import AmbulanceTeam from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.action.common.action_rest import ActionRest from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( + CommandAmbulance, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( + CommandScout, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo from adf_core_python.core.agent.info.scenario_info import ScenarioInfo @@ -62,11 +71,23 @@ def initialize( "DefaultTacticsAmbulanceTeam.ExtendActionMove", "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", ) + self._command_executor_ambulance = module_manager.get_command_executor( + "DefaultTacticsAmbulanceTeam.CommandExecutorAmbulance", + "adf_core_python.implement.centralized.default_command_executor_ambulance.DefaultCommandExecutorAmbulance", + ) + self._command_executor_scout = module_manager.get_command_executor( + "DefaultTacticsAmbulanceTeam.CommandExecutorScout", + "adf_core_python.implement.centralized.default_command_executor_scout.DefaultCommandExecutorScout", + ) self.register_module(self._search) self.register_module(self._human_detector) self.register_action(self._action_transport) self.register_action(self._action_ext_move) + self.register_command_executor(self._command_executor_ambulance) + self.register_command_executor(self._command_executor_scout) + + self._recent_command: Optional[StandardMessage] = None def precompute( self, @@ -124,6 +145,34 @@ def think( message_manager=message_manager, ) + for message in message_manager.get_received_message_list(): + if isinstance(message, CommandScout): + if ( + message.get_command_executor_agent_entity_id() + == agent_info.get_entity_id() + ): + self._recent_command = message + self._command_executor_scout.set_command(message) + if isinstance(message, CommandAmbulance): + if ( + message.get_command_executor_agent_entity_id() + == agent_info.get_entity_id() + ): + self._recent_command = message + self._command_executor_ambulance.set_command(message) + + if self._recent_command is not None: + action: Optional[Action] = None + if isinstance(self._recent_command, CommandScout): + action = self._command_executor_scout.calculate().get_action() + elif isinstance(self._recent_command, CommandAmbulance): + action = self._command_executor_ambulance.calculate().get_action() + if action is not None: + self._logger.debug( + f"action decided by command: {action}", time=agent_info.get_time() + ) + return action + target_entity_id = self._human_detector.calculate().get_target_entity_id() self._logger.debug( f"human detector target_entity_id: {target_entity_id}", diff --git a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py index e4e09ec..0d9740e 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_brigade.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_brigade.py @@ -1,10 +1,19 @@ -from typing import cast +from typing import Optional, cast from rcrs_core.entities.fireBrigade import FireBrigade from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.action.common.action_rest import ActionRest from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_fire import ( + CommandFire, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( + CommandScout, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo from adf_core_python.core.agent.info.scenario_info import ScenarioInfo @@ -62,11 +71,23 @@ def initialize( "DefaultTacticsAmbulanceTeam.ExtendActionMove", "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", ) + self._command_executor_fire = module_manager.get_command_executor( + "DefaultTacticsFireBrigade.CommandExecutorFire", + "adf_core_python.implement.centralized.default_command_executor_fire.DefaultCommandExecutorFire", + ) + self._command_executor_scout = module_manager.get_command_executor( + "DefaultTacticsAmbulanceTeam.CommandExecutorScout", + "adf_core_python.implement.centralized.default_command_executor_scout.DefaultCommandExecutorScout", + ) self.register_module(self._search) self.register_module(self._human_detector) self.register_action(self._action_rescue) self.register_action(self._action_ext_move) + self.register_command_executor(self._command_executor_fire) + self.register_command_executor(self._command_executor_scout) + + self._recent_command: Optional[StandardMessage] = None def precompute( self, @@ -119,6 +140,34 @@ def think( agent: FireBrigade = cast(FireBrigade, agent_info.get_myself()) # noqa: F841 entity_id = agent_info.get_entity_id() # noqa: F841 + for message in message_manager.get_received_message_list(): + if isinstance(message, CommandScout): + if ( + message.get_command_executor_agent_entity_id() + == agent_info.get_entity_id() + ): + self._recent_command = message + self._command_executor_scout.set_command(command=message) + if isinstance(message, CommandFire): + if ( + message.get_command_executor_agent_entity_id() + == agent_info.get_entity_id() + ): + self._recent_command = message + self._command_executor_fire.set_command(message) + + if self._recent_command is not None: + action: Optional[Action] = None + if isinstance(self._recent_command, CommandScout): + action = self._command_executor_scout.calculate().get_action() + elif isinstance(self._recent_command, CommandFire): + action = self._command_executor_fire.calculate().get_action() + if action is not None: + self._logger.debug( + f"action decided by command: {action}", time=agent_info.get_time() + ) + return action + target_entity_id = self._human_detector.calculate().get_target_entity_id() self._logger.debug( f"human detector target_entity_id: {target_entity_id}", diff --git a/adf_core_python/implement/tactics/default_tactics_fire_station.py b/adf_core_python/implement/tactics/default_tactics_fire_station.py index 6b3dcb2..0b15a3e 100644 --- a/adf_core_python/implement/tactics/default_tactics_fire_station.py +++ b/adf_core_python/implement/tactics/default_tactics_fire_station.py @@ -1,5 +1,7 @@ from typing import cast +from rcrs_core.worldmodel.entityID import EntityID + from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -7,6 +9,7 @@ from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.centralized.command_picker import CommandPicker from adf_core_python.core.component.module.complex.target_allocator import ( TargetAllocator, ) @@ -33,7 +36,12 @@ def initialize( "adf_core_python.implement.module.complex.default_fire_target_allocator.DefaultFireTargetAllocator", ), ) + self._picker: CommandPicker = module_manager.get_command_picker( + "DefaultTacticsFireStation.CommandPicker", + "adf_core_python.implement.centralized.default_command_picker_fire.DefaultCommandPickerFire", + ) self.register_module(self._allocator) + self.register_command_picker(self._picker) def resume( self, @@ -80,5 +88,14 @@ def think( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - # TODO: implement - pass + self.module_update_info(message_manager) + + allocation_result: dict[EntityID, EntityID] = ( + self._allocator.calculate().get_result() + ) + for message in ( + self._picker.set_allocator_result(allocation_result) + .calculate() + .get_result() + ): + message_manager.add_message(message) diff --git a/adf_core_python/implement/tactics/default_tactics_police_force.py b/adf_core_python/implement/tactics/default_tactics_police_force.py index bec3434..6a1f2f6 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_force.py +++ b/adf_core_python/implement/tactics/default_tactics_police_force.py @@ -1,10 +1,22 @@ -from typing import cast +from typing import Optional, cast from rcrs_core.entities.policeForce import PoliceForce from adf_core_python.core.agent.action.action import Action from adf_core_python.core.agent.action.common.action_rest import ActionRest from adf_core_python.core.agent.communication.message_manager import MessageManager +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_fire import ( + CommandFire, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_police import ( + CommandPolice, +) +from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( + CommandScout, +) +from adf_core_python.core.agent.communication.standard.bundle.standard_message import ( + StandardMessage, +) from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo from adf_core_python.core.agent.info.scenario_info import ScenarioInfo @@ -65,11 +77,23 @@ def initialize( "DefaultTacticsPoliceForce.ExtendActionMove", "adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove", ) + self._command_executor_police = module_manager.get_command_executor( + "DefaultTacticsPoliceForce.CommandExecutorPolice", + "adf_core_python.implement.centralized.default_command_executor_police.DefaultCommandExecutorPolice", + ) + self._command_executor_scout = module_manager.get_command_executor( + "DefaultTacticsPoliceForce.CommandExecutorScout", + "adf_core_python.implement.centralized.default_command_executor_scout_police.DefaultCommandExecutorScoutPolice", + ) self.register_module(self._search) self.register_module(self._road_detector) self.register_action(self._action_ext_clear) self.register_action(self._action_ext_move) + self.register_command_executor(self._command_executor_police) + self.register_command_executor(self._command_executor_scout) + + self._recent_command: Optional[StandardMessage] = None def precompute( self, @@ -122,6 +146,34 @@ def think( agent: PoliceForce = cast(PoliceForce, agent_info.get_myself()) # noqa: F841 entity_id = agent_info.get_entity_id() # noqa: F841 + for message in message_manager.get_received_message_list(): + if isinstance(message, CommandScout): + if ( + message.get_command_executor_agent_entity_id() + == agent_info.get_entity_id() + ): + self._recent_command = message + self._command_executor_scout.set_command(command=message) + if isinstance(message, CommandPolice): + if ( + message.get_command_executor_agent_entity_id() + == agent_info.get_entity_id() + ): + self._recent_command = message + self._command_executor_police.set_command(message) + + if self._recent_command is not None: + action: Optional[Action] = None + if isinstance(self._recent_command, CommandScout): + action = self._command_executor_scout.calculate().get_action() + elif isinstance(self._recent_command, CommandFire): + action = self._command_executor_police.calculate().get_action() + if action is not None: + self._logger.debug( + f"action decided by command: {action}", time=agent_info.get_time() + ) + return action + target_entity_id = self._road_detector.calculate().get_target_entity_id() self._logger.debug( f"road detector target_entity_id: {target_entity_id}", diff --git a/adf_core_python/implement/tactics/default_tactics_police_office.py b/adf_core_python/implement/tactics/default_tactics_police_office.py index 554fe53..df095cc 100644 --- a/adf_core_python/implement/tactics/default_tactics_police_office.py +++ b/adf_core_python/implement/tactics/default_tactics_police_office.py @@ -1,5 +1,7 @@ from typing import cast +from rcrs_core.worldmodel.entityID import EntityID + from adf_core_python.core.agent.communication.message_manager import MessageManager from adf_core_python.core.agent.develop.develop_data import DevelopData from adf_core_python.core.agent.info.agent_info import AgentInfo @@ -7,6 +9,7 @@ from adf_core_python.core.agent.info.world_info import WorldInfo from adf_core_python.core.agent.module.module_manager import ModuleManager from adf_core_python.core.agent.precompute.precompute_data import PrecomputeData +from adf_core_python.core.component.centralized.command_picker import CommandPicker from adf_core_python.core.component.module.complex.target_allocator import ( TargetAllocator, ) @@ -33,7 +36,12 @@ def initialize( "adf_core_python.implement.module.complex.default_police_target_allocator.DefaultPoliceTargetAllocator", ), ) + self._picker: CommandPicker = module_manager.get_command_picker( + "DefaultTacticsPoliceOffice.CommandPicker", + "adf_core_python.implement.centralized.default_command_picker_police.DefaultCommandPickerPolice", + ) self.register_module(self._allocator) + self.register_command_picker(self._picker) def resume( self, @@ -80,5 +88,14 @@ def think( message_manager: MessageManager, develop_data: DevelopData, ) -> None: - # TODO: implement - pass + self.module_update_info(message_manager) + + allocation_result: dict[EntityID, EntityID] = ( + self._allocator.calculate().get_result() + ) + for message in ( + self._picker.set_allocator_result(allocation_result) + .calculate() + .get_result() + ): + message_manager.add_message(message) diff --git a/config/module.yaml b/config/module.yaml index 75c15a0..706c82e 100644 --- a/config/module.yaml +++ b/config/module.yaml @@ -3,36 +3,36 @@ DefaultTacticsAmbulanceTeam: Search: adf_core_python.implement.module.complex.default_search.DefaultSearch ExtendActionTransport: adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove - CommandExecutorAmbulance: adf_core_python.implement.centralized.DefaultCommandExecutorAmbulance - CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout + CommandExecutorAmbulance: adf_core_python.implement.centralized.default_command_executor_ambulance.DefaultCommandExecutorAmbulance + CommandExecutorScout: adf_core_python.implement.centralized.default_command_executor_scout.DefaultCommandExecutorScout DefaultTacticsFireBrigade: HumanDetector: adf_core_python.implement.module.complex.default_human_detector.DefaultHumanDetector Search: adf_core_python.implement.module.complex.default_search.DefaultSearch ExtendActionRescue: adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove - CommandExecutorFire: adf_core_python.implement.centralized.DefaultCommandExecutorFire - CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScout + CommandExecutorFire: adf_core_python.implement.centralized.default_command_executor_fire.DefaultCommandExecutorFire + CommandExecutorScout: adf_core_python.implement.centralized.default_command_executor_scout.DefaultCommandExecutorScout DefaultTacticsPoliceForce: RoadDetector: adf_core_python.implement.module.complex.default_road_detector.DefaultRoadDetector Search: adf_core_python.implement.module.complex.default_search.DefaultSearch ExtendActionClear: adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove - CommandExecutorPolice: adf_core_python.implement.centralized.DefaultCommandExecutorPolice - CommandExecutorScout: adf_core_python.implement.centralized.DefaultCommandExecutorScoutPolice + CommandExecutorPolice: adf_core_python.implement.centralized.default_command_executor_police.DefaultCommandExecutorPolice + CommandExecutorScout: adf_core_python.implement.centralized.default_command_executor_scout.DefaultCommandExecutorScout DefaultTacticsAmbulanceCenter: TargetAllocator: adf_core_python.implement.module.complex.default_ambulance_target_allocator.DefaultAmbulanceTargetAllocator - # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerAmbulance + CommandPicker: adf_core_python.implement.centralized.default_command_picker_ambulance.DefaultCommandPickerAmbulance DefaultTacticsFireStation: TargetAllocator: adf_core_python.implement.module.complex.default_fire_target_allocator.DefaultFireTargetAllocator - # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerFire + CommandPicker: adf_core_python.implement.centralized.default_command_picker_fire.DefaultCommandPickerFire DefaultTacticsPoliceOffice: TargetAllocator: adf_core_python.implement.module.complex.default_police_target_allocator.DefaultPoliceTargetAllocator - # CommandPicker: adf_core_python.implement.centralized.DefaultCommandPickerPolice + CommandPicker: adf_core_python.implement.centralized.default_command_picker_police.DefaultCommandPickerPolice DefaultSearch: PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning @@ -56,28 +56,27 @@ DefaultExtendActionMove: DefaultExtendActionTransport: PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# DefaultCommandExecutorAmbulance: -# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ExtendActionTransport: adf_core_python.implement.action.DefaultExtendActionTransport -# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove +DefaultCommandExecutorAmbulance: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning + ExtendActionTransport: adf_core_python.implement.action.default_extend_action_transport.DefaultExtendActionTransport + ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove -# DefaultCommandExecutorFire: -# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# EtxActionFireRescue: adf_core_python.implement.action.DefaultExtendActionRescue -# EtxActionFireFighting: adf_core_python.implement.action.DefaultExtendActionFireFighting -# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove +DefaultCommandExecutorFire: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning + EtxActionFireRescue: adf_core_python.implement.action.default_extend_action_rescue.DefaultExtendActionRescue + ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove -# DefaultCommandExecutorPolice: -# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear -# ExtendActionMove: adf_core_python.implement.action.DefaultExtendActionMove +DefaultCommandExecutorPolice: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning + ExtendActionClear: adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear + ExtendActionMove: adf_core_python.implement.action.default_extend_action_move.DefaultExtendActionMove -# DefaultCommandExecutorScout: -# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning +DefaultCommandExecutorScout: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# DefaultCommandExecutorScoutPolice: -# PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning -# ExtendActionClear: adf_core_python.implement.action.DefaultExtendActionClear +DefaultCommandExecutorScoutPolice: + PathPlanning: adf_core_python.implement.module.algorithm.a_star_path_planning.AStarPathPlanning + ExtendActionClear: adf_core_python.implement.action.default_extend_action_clear.DefaultExtendActionClear MessageManager: PlatoonChannelSubscriber: adf_core_python.implement.module.communication.default_channel_subscriber.DefaultChannelSubscriber From 4c933de821cb197032a367260b05025a28794179 Mon Sep 17 00:00:00 2001 From: shima004 Date: Wed, 18 Dec 2024 02:08:44 +0900 Subject: [PATCH 202/249] refactor: Remove unused command_type initialization in command executor classes --- .../centralized/default_command_executor_ambulance.py | 1 - .../implement/centralized/default_command_executor_fire.py | 1 - .../centralized/default_command_executor_police.py | 1 - .../centralized/default_command_executor_scout.py | 1 - .../centralized/default_command_executor_scout_police.py | 1 - .../centralized/default_command_picker_ambulance.py | 1 + .../implement/centralized/default_command_picker_fire.py | 1 + .../implement/centralized/default_command_picker_police.py | 7 ------- 8 files changed, 2 insertions(+), 12 deletions(-) diff --git a/adf_core_python/implement/centralized/default_command_executor_ambulance.py b/adf_core_python/implement/centralized/default_command_executor_ambulance.py index f2c00d6..ac29680 100644 --- a/adf_core_python/implement/centralized/default_command_executor_ambulance.py +++ b/adf_core_python/implement/centralized/default_command_executor_ambulance.py @@ -49,7 +49,6 @@ def __init__( super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - self._command_type = self.ACTION_UNKNOWN self._path_planning: PathPlanning = cast( PathPlanning, diff --git a/adf_core_python/implement/centralized/default_command_executor_fire.py b/adf_core_python/implement/centralized/default_command_executor_fire.py index abd2ebe..680320b 100644 --- a/adf_core_python/implement/centralized/default_command_executor_fire.py +++ b/adf_core_python/implement/centralized/default_command_executor_fire.py @@ -46,7 +46,6 @@ def __init__( super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - self._command_type = self.ACTION_UNKNOWN self._path_planning: PathPlanning = cast( PathPlanning, diff --git a/adf_core_python/implement/centralized/default_command_executor_police.py b/adf_core_python/implement/centralized/default_command_executor_police.py index aecd574..69ad622 100644 --- a/adf_core_python/implement/centralized/default_command_executor_police.py +++ b/adf_core_python/implement/centralized/default_command_executor_police.py @@ -47,7 +47,6 @@ def __init__( super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - self._command_type = self.ACTION_UNKNOWN self._path_planning: PathPlanning = cast( PathPlanning, diff --git a/adf_core_python/implement/centralized/default_command_executor_scout.py b/adf_core_python/implement/centralized/default_command_executor_scout.py index 1186155..28fdd78 100644 --- a/adf_core_python/implement/centralized/default_command_executor_scout.py +++ b/adf_core_python/implement/centralized/default_command_executor_scout.py @@ -41,7 +41,6 @@ def __init__( super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - self._command_type = self.ACTION_UNKNOWN self._path_planning: PathPlanning = cast( PathPlanning, diff --git a/adf_core_python/implement/centralized/default_command_executor_scout_police.py b/adf_core_python/implement/centralized/default_command_executor_scout_police.py index 01f44c9..ad17d67 100644 --- a/adf_core_python/implement/centralized/default_command_executor_scout_police.py +++ b/adf_core_python/implement/centralized/default_command_executor_scout_police.py @@ -42,7 +42,6 @@ def __init__( super().__init__( agent_info, world_info, scenario_info, module_manager, develop_data ) - self._command_type = self.ACTION_UNKNOWN self._path_planning: PathPlanning = cast( PathPlanning, diff --git a/adf_core_python/implement/centralized/default_command_picker_ambulance.py b/adf_core_python/implement/centralized/default_command_picker_ambulance.py index 9af9523..b2a507d 100644 --- a/adf_core_python/implement/centralized/default_command_picker_ambulance.py +++ b/adf_core_python/implement/centralized/default_command_picker_ambulance.py @@ -61,6 +61,7 @@ def calculate(self) -> CommandPicker: if target is None: continue + command: CommunicationMessage if isinstance(target, Human): command = CommandAmbulance( True, diff --git a/adf_core_python/implement/centralized/default_command_picker_fire.py b/adf_core_python/implement/centralized/default_command_picker_fire.py index 435d931..bacb996 100644 --- a/adf_core_python/implement/centralized/default_command_picker_fire.py +++ b/adf_core_python/implement/centralized/default_command_picker_fire.py @@ -61,6 +61,7 @@ def calculate(self) -> CommandPicker: if target is None: continue + command: CommunicationMessage if isinstance(target, Human): command = CommandAmbulance( True, diff --git a/adf_core_python/implement/centralized/default_command_picker_police.py b/adf_core_python/implement/centralized/default_command_picker_police.py index 75b7ef7..bbda78e 100644 --- a/adf_core_python/implement/centralized/default_command_picker_police.py +++ b/adf_core_python/implement/centralized/default_command_picker_police.py @@ -1,19 +1,12 @@ from __future__ import annotations from rcrs_core.entities.area import Area -from rcrs_core.entities.human import Human from rcrs_core.entities.policeForce import PoliceForce from rcrs_core.worldmodel.entityID import EntityID -from adf_core_python.core.agent.communication.standard.bundle.centralized.command_ambulance import ( - CommandAmbulance, -) from adf_core_python.core.agent.communication.standard.bundle.centralized.command_police import ( CommandPolice, ) -from adf_core_python.core.agent.communication.standard.bundle.centralized.command_scout import ( - CommandScout, -) from adf_core_python.core.agent.communication.standard.bundle.standard_message_priority import ( StandardMessagePriority, ) From cad57bda93403d510c9a712018838dfb06e3cef6 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 19 Dec 2024 11:56:58 +0900 Subject: [PATCH 203/249] refactor: Replace Any with specific types in agent_info and tactics_agent classes for improved type safety --- adf_core_python/core/agent/info/agent_info.py | 5 ++- adf_core_python/core/agent/info/world_info.py | 1 - .../core/component/tactics/tactics_agent.py | 36 ++++++++++--------- adf_core_python/core/logger/logger.py | 2 +- .../action/default_extend_action_transport.py | 1 - .../tactics/default_tactics_ambulance_team.py | 3 +- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/adf_core_python/core/agent/info/agent_info.py b/adf_core_python/core/agent/info/agent_info.py index b3e78ee..23f6657 100644 --- a/adf_core_python/core/agent/info/agent_info.py +++ b/adf_core_python/core/agent/info/agent_info.py @@ -1,7 +1,7 @@ from __future__ import annotations from time import time -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from rcrs_core.commands.Command import Command from rcrs_core.entities.civilian import Civilian @@ -18,13 +18,12 @@ class AgentInfo: - # TODO: Replace Any with the actual type def __init__(self, agent: Agent, world_model: WorldModel): self._agent: Agent = agent self._world_model: WorldModel = world_model self._time: int = 0 self._action_history: dict[int, Action] = {} - self._heard_commands: list[Any] = [] + self._heard_commands: list[Command] = [] self._change_set: ChangeSet = ChangeSet() self._start_think_time: float = 0.0 diff --git a/adf_core_python/core/agent/info/world_info.py b/adf_core_python/core/agent/info/world_info.py index c447d7b..c4dec84 100644 --- a/adf_core_python/core/agent/info/world_info.py +++ b/adf_core_python/core/agent/info/world_info.py @@ -17,7 +17,6 @@ def __init__(self, world_model: WorldModel): self._rollback: dict[EntityID, dict[int, dict[int, Any]]] = {} self._change_set: ChangeSet - # TODO: Implement the worldmodel access methods def get_world_model(self) -> WorldModel: """ Get the world model diff --git a/adf_core_python/core/component/tactics/tactics_agent.py b/adf_core_python/core/component/tactics/tactics_agent.py index b092517..3f1343e 100644 --- a/adf_core_python/core/component/tactics/tactics_agent.py +++ b/adf_core_python/core/component/tactics/tactics_agent.py @@ -2,8 +2,9 @@ import time from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Optional +from adf_core_python.core.component.centralized.command_executor import CommandExecutor from adf_core_python.core.logger.logger import get_agent_logger if TYPE_CHECKING: @@ -24,7 +25,7 @@ def __init__(self, parent: Optional[TacticsAgent] = None) -> None: self._parent = parent self._modules: list[AbstractModule] = [] self._actions: list[ExtendAction] = [] - self._command_executor: Any = None + self._command_executor: list[CommandExecutor] = [] @abstractmethod def initialize( @@ -107,27 +108,27 @@ def register_action(self, action: ExtendAction) -> None: def unregister_action(self, action: ExtendAction) -> None: self._actions.remove(action) - def register_command_executor(self, command_executor: Any) -> None: - self._command_executor = command_executor + def register_command_executor(self, command_executor: CommandExecutor) -> None: + self._command_executor.append(command_executor) - def unregister_command_executor(self) -> None: - self._command_executor = None + def unregister_command_executor(self, command_executor: CommandExecutor) -> None: + self._command_executor.remove(command_executor) def module_precompute(self, precompute_data: PrecomputeData) -> None: for module in self._modules: module.precompute(precompute_data) for action in self._actions: action.precompute(precompute_data) - # for executor in self._command_executor: - # executor.precompute(precompute_data) + for executor in self._command_executor: + executor.precompute(precompute_data) def module_resume(self, precompute_data: PrecomputeData) -> None: for module in self._modules: module.resume(precompute_data) for action in self._actions: action.resume(precompute_data) - # for executor in self._command_executor: - # executor.resume(precompute_data) + for executor in self._command_executor: + executor.resume(precompute_data) def module_prepare(self) -> None: for module in self._modules: @@ -142,16 +143,16 @@ def module_prepare(self) -> None: self._logger.debug( f"module {action.__class__.__name__} prepare time: {time.time() - start_time:.3f}", ) - # for executor in self._command_executor: - # executor.prepare() + for executor in self._command_executor: + executor.prepare() def module_update_info(self, message_manager: MessageManager) -> None: for module in self._modules: module.update_info(message_manager) for action in self._actions: action.update_info(message_manager) - # for executor in self._command_executor: - # executor.update_info(message_manager) + for executor in self._command_executor: + executor.update_info(message_manager) def reset_count(self) -> None: for module in self._modules: @@ -164,5 +165,8 @@ def reset_count(self) -> None: action.reset_count_resume() action.reset_count_prepare() action.reset_count_update_info() - # for executor in self._command_executor: - # executor.reset_count() + for executor in self._command_executor: + executor.reset_count_precompute() + executor.reset_count_resume() + executor.reset_count_prepare() + executor.reset_count_update_info() diff --git a/adf_core_python/core/logger/logger.py b/adf_core_python/core/logger/logger.py index 440cef0..766e283 100644 --- a/adf_core_python/core/logger/logger.py +++ b/adf_core_python/core/logger/logger.py @@ -72,7 +72,7 @@ def configure_logger() -> None: handler_stdout.setLevel(logging.INFO) handler_file = RotatingFileHandler( - "agent.log", maxBytes=10 * 1024 * 1024, backupCount=5 + "agent.log", maxBytes=1024 * 1024 * 1024, backupCount=5 ) handler_file.doRollover() handler_file.setFormatter( diff --git a/adf_core_python/implement/action/default_extend_action_transport.py b/adf_core_python/implement/action/default_extend_action_transport.py index 8e6c71a..b1c9629 100644 --- a/adf_core_python/implement/action/default_extend_action_transport.py +++ b/adf_core_python/implement/action/default_extend_action_transport.py @@ -24,7 +24,6 @@ from adf_core_python.core.logger.logger import get_agent_logger -# TODO: refactor this class class DefaultExtendActionTransport(ExtendAction): def __init__( self, diff --git a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py index 8e7df9c..76b6c47 100644 --- a/adf_core_python/implement/tactics/default_tactics_ambulance_team.py +++ b/adf_core_python/implement/tactics/default_tactics_ambulance_team.py @@ -141,8 +141,7 @@ def think( entity_id = agent_info.get_entity_id() # noqa: F841 self._logger.debug( - f"received messages: {[str(message) for message in message_manager.get_received_message_list()]}, help: {message_manager.get_heard_agent_help_message_count()}", - message_manager=message_manager, + f"received messages: {[str(message) for message in message_manager.get_received_message_list()]}, help: {message_manager.get_heard_agent_help_message_count()}" ) for message in message_manager.get_received_message_list(): From 98c1db930d08e44048a6b1b28829177b55ca7095 Mon Sep 17 00:00:00 2001 From: shima004 Date: Thu, 19 Dec 2024 16:59:12 +0900 Subject: [PATCH 204/249] feat: Add versioning support and improve documentation localization --- .github/workflows/document.yaml | 6 +- docs/.gitignore | 1 + docs/build.py | 43 ++ docs/source/_templates/versions.html | 31 + docs/source/conf.py | 50 +- docs/source/index.rst | 5 + .../en/LC_MESSAGES/adf_core_python.cli.po | 38 ++ ...core_python.core.agent.action.ambulance.po | 52 ++ ...df_core_python.core.agent.action.common.po | 47 ++ .../adf_core_python.core.agent.action.fire.po | 52 ++ .../adf_core_python.core.agent.action.po | 46 ++ ...df_core_python.core.agent.action.police.po | 48 ++ ...df_core_python.core.agent.communication.po | 251 +++++++ ...mmunication.standard.bundle.centralized.po | 77 +++ ...mmunication.standard.bundle.information.po | 84 +++ ...ore.agent.communication.standard.bundle.po | 67 ++ ...ython.core.agent.communication.standard.po | 51 ++ ...re.agent.communication.standard.utility.po | 133 ++++ .../adf_core_python.core.agent.config.po | 42 ++ .../adf_core_python.core.agent.develop.po | 78 +++ .../adf_core_python.core.agent.info.po | 317 +++++++++ .../adf_core_python.core.agent.module.po | 42 ++ .../adf_core_python.core.agent.platoon.po | 60 ++ .../LC_MESSAGES/adf_core_python.core.agent.po | 46 ++ .../adf_core_python.core.agent.precompute.po | 93 +++ .../adf_core_python.core.component.action.po | 42 ++ ...ore_python.core.component.communication.po | 103 +++ ..._python.core.component.module.algorithm.po | 50 ++ ...re_python.core.component.module.complex.po | 122 ++++ .../adf_core_python.core.component.module.po | 46 ++ .../adf_core_python.core.component.po | 46 ++ .../adf_core_python.core.component.tactics.po | 95 +++ .../adf_core_python.core.config.po | 42 ++ .../adf_core_python.core.launcher.connect.po | 134 ++++ .../adf_core_python.core.launcher.po | 51 ++ .../adf_core_python.core.logger.po | 79 +++ .../en/LC_MESSAGES/adf_core_python.core.po | 34 + .../adf_core_python.implement.action.po | 68 ++ ..._core_python.implement.module.algorithm.po | 60 ++ ...e_python.implement.module.communication.po | 97 +++ ...df_core_python.implement.module.complex.po | 116 ++++ .../adf_core_python.implement.module.po | 34 + .../LC_MESSAGES/adf_core_python.implement.po | 48 ++ .../adf_core_python.implement.tactics.po | 112 ++++ .../locale/en/LC_MESSAGES/adf_core_python.po | 46 ++ docs/source/locale/en/LC_MESSAGES/hands-on.po | 284 ++++++++ docs/source/locale/en/LC_MESSAGES/index.po | 98 +++ docs/source/locale/en/LC_MESSAGES/install.po | 387 +++++++++++ docs/source/locale/en/LC_MESSAGES/modules.po | 26 + .../locale/en/LC_MESSAGES/quickstart.po | 77 +++ docs/source/locale/en/LC_MESSAGES/tutorial.po | 626 ++++++++++++++++++ docs/versions.yaml | 10 + poetry.lock | 62 +- pyproject.toml | 2 + 54 files changed, 4744 insertions(+), 13 deletions(-) create mode 100644 docs/build.py create mode 100644 docs/source/_templates/versions.html create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.cli.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.ambulance.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.common.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.fire.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.action.police.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.bundle.centralized.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.bundle.information.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.bundle.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.communication.standard.utility.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.config.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.develop.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.info.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.module.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.platoon.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.agent.precompute.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.action.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.communication.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.module.algorithm.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.module.complex.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.module.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.component.tactics.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.config.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.launcher.connect.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.launcher.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.logger.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.core.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.action.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.algorithm.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.communication.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.complex.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.module.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.implement.tactics.po create mode 100644 docs/source/locale/en/LC_MESSAGES/adf_core_python.po create mode 100644 docs/source/locale/en/LC_MESSAGES/hands-on.po create mode 100644 docs/source/locale/en/LC_MESSAGES/index.po create mode 100644 docs/source/locale/en/LC_MESSAGES/install.po create mode 100644 docs/source/locale/en/LC_MESSAGES/modules.po create mode 100644 docs/source/locale/en/LC_MESSAGES/quickstart.po create mode 100644 docs/source/locale/en/LC_MESSAGES/tutorial.po create mode 100644 docs/versions.yaml diff --git a/.github/workflows/document.yaml b/.github/workflows/document.yaml index bccc46d..b1b958a 100644 --- a/.github/workflows/document.yaml +++ b/.github/workflows/document.yaml @@ -54,13 +54,11 @@ jobs: - name: Build documentation run: | source .venv/bin/activate - sphinx-apidoc -f -o ./docs/source ./adf_core_python cd docs - make html - ls build/html + python build.py - name: Deploy documentation uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs/build/html + publish_dir: ./docs/pages diff --git a/docs/.gitignore b/docs/.gitignore index b9913b1..be43638 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,2 @@ source/adf_core_python.* +pages diff --git a/docs/build.py b/docs/build.py new file mode 100644 index 0000000..430bfad --- /dev/null +++ b/docs/build.py @@ -0,0 +1,43 @@ +import os +import subprocess +import yaml + +# a single build step, which keeps conf.py and versions.yaml at the main branch +# in generall we use environment variables to pass values to conf.py, see below +# and runs the build as we did locally +def build_doc(version, language, tag): + os.environ["current_version"] = version + os.environ["current_language"] = language + subprocess.run("git checkout " + tag, shell=True) + subprocess.run("git checkout main -- conf.py", shell=True) + subprocess.run("git checkout main -- versions.yaml", shell=True) + subprocess.run("doxygen Doxyfile", shell=True) + os.environ['SPHINXOPTS'] = "-D language='{}'".format(language) + subprocess.run("make html", shell=True) + +# a move dir method because we run multiple builds and bring the html folders to a +# location which we then push to github pages +def move_dir(src, dst): + subprocess.run(["mkdir", "-p", dst]) + subprocess.run("mv "+src+'* ' + dst, shell=True) + +# to separate a single local build from all builds we have a flag, see conf.py +os.environ["build_all_docs"] = str(True) +os.environ["pages_root"] = "http://127.0.0.1:5500/docs/pages" + +# manually the main branch build in the current supported languages +build_doc("latest", "en", "main") +move_dir("./build/html/", "./pages/") +build_doc("latest", "ja", "main") +move_dir("./build/html/", "./pages/ja/") + +# reading the yaml file +with open("versions.yaml", "r") as yaml_file: + docs = yaml.safe_load(yaml_file) + +# and looping over all values to call our build with version, language and its tag +# for version, details in docs.items(): +# tag = details.get('tag', '') +# for language in details.get('languages', []): +# build_doc(version, language, version) +# move_dir("./build/html/", "./pages/"+version+'/'+language+'/') diff --git a/docs/source/_templates/versions.html b/docs/source/_templates/versions.html new file mode 100644 index 0000000..7b7d0bc --- /dev/null +++ b/docs/source/_templates/versions.html @@ -0,0 +1,31 @@ +