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"]