From 01949b47955296d30e31ad9cc1e19cae80b7269b Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 28 Nov 2025 13:44:43 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E5=86=85=E5=AE=B9=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/source/hands-on/clustering.md | 56 ++++++++++++--------- docs/source/hands-on/search.md | 32 ++++++------ docs/source/tutorial/agent/agent_control.md | 33 ++++++------ 3 files changed, 62 insertions(+), 59 deletions(-) diff --git a/docs/source/hands-on/clustering.md b/docs/source/hands-on/clustering.md index df3962ab..b843467e 100644 --- a/docs/source/hands-on/clustering.md +++ b/docs/source/hands-on/clustering.md @@ -2,7 +2,7 @@ ## クラスタリングモジュールの目的 -複数のエージェントを動かす場合は、それらのエージェントにどのように協調させるかが重要になります。RRSでは多くのチームが、エージェントに各々の担当地域を持たせ役割分担をおこなう協調を取り入れています(他の手段による協調も取り入れています)。担当地域を割り振るためには、地図上のオブジェクトをいくつかのグループに分ける必要があります。このようなグループ分けをしてそれらを管理する場合には、クラスタリングモジュールと呼ばれるモジュールを用います。 +複数のエージェントを動かす場合は、それらのエージェントにどのように協調させるかが重要になります。RRS では多くのチームが、エージェントに各々の担当地域を持たせ役割分担をおこなう協調を取り入れています(他の手段による協調も取り入れています)。担当地域を割り振るためには、地図上のオブジェクトをいくつかのグループに分ける必要があります。このようなグループ分けをしてそれらを管理する場合には、クラスタリングモジュールと呼ばれるモジュールを用います。 本資料では、多くの世界大会参加チームが使用しているアルゴリズムを用いたクラスタリングモジュールの実装をおこないます。 @@ -11,7 +11,7 @@ 本資料で開発するモジュールは下の画像のように、 1. k-means++アルゴリズムによって地図上のオブジェクトをエージェント数分の区画に分けます。 -1. Hungarianアルゴリズムによってそれらの区画とエージェントを (間の距離の総和が最も小さくなるように)1対1で結びつけます。 +1. Hungarian アルゴリズムによってそれらの区画とエージェントを (間の距離の総和が最も小さくなるように)1 対 1 で結びつけます。 ![クラスタリングの画像](./../images/clustering_image.jpg) @@ -39,17 +39,19 @@ 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 rcrscore.entities import ( + AmbulanceCenter, + Building, + Entity, + EntityID, + FireStation, + GasStation, + Hydrant, + PoliceOffice, + Refuge, + Road, +) +from rcrscore.urn import EntityURN from scipy.optimize import linear_sum_assignment from sklearn.cluster import KMeans @@ -74,7 +76,13 @@ class KMeansPPClustering(Clustering): # クラスター数の設定 self._cluster_number: int = 1 - match agent_info.get_myself().get_urn(): + + me = agent_info.get_myself() + if not me: + self._logger.error("Myself entity is None.") + return + + match me.get_urn(): # エージェントのクラスに応じてクラスター数を設定 case EntityURN.AMBULANCE_TEAM: self._cluster_number = scenario_info.get_value( @@ -95,7 +103,7 @@ class KMeansPPClustering(Clustering): # 自分と同じクラスのエージェントのリストを取得 self._agents: list[Entity] = world_info.get_entities_of_types( [ - agent_info.get_myself().__class__, + me.__class__, ] ) @@ -105,7 +113,7 @@ class KMeansPPClustering(Clustering): # クラスタリング対象のエンティティのリストを取得 self._entities: list[Entity] = world_info.get_entities_of_types( [ - AmbulanceCentre, + AmbulanceCenter, FireStation, GasStation, Hydrant, @@ -180,7 +188,9 @@ class KMeansPPClustering(Clustering): """ 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: """ @@ -205,17 +215,17 @@ class KMeansPPClustering(Clustering): # エージェントとクラスターの対応付け結果を保持 self._agent_cluster_indices = { - entity.get_id(): cluster_index + entity.get_entity_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]}" + f"Clustered entities: {[[entity.get_entity_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()]}" + f"Agent cluster indices: {[([e.get_x(), e.get_y()] if (e is not None and e.get_x() is not None and e.get_y() is not None) else [None, None], int(cluster_index)) for entity_id, cluster_index in self._agent_cluster_indices.items() for e in (self._world_info.get_entity(entity_id),)]}" ) return self @@ -292,9 +302,9 @@ class KMeansPPClustering(Clustering): return col_ind ``` -k-means++の実装は、scikit-learnの`KMeans`クラスを使用しています。`KMeans`クラスは、`n_clusters`で指定したクラスター数によって地図上のオブジェクトをクラスタリングします。クラスタリング結果は、`labels_`属性に格納されます。また、`cluster_centers_`属性には各クラスターの中心座標が格納されます。 +k-means++の実装は、scikit-learn の`KMeans`クラスを使用しています。`KMeans`クラスは、`n_clusters`で指定したクラスター数によって地図上のオブジェクトをクラスタリングします。クラスタリング結果は、`labels_`属性に格納されます。また、`cluster_centers_`属性には各クラスターの中心座標が格納されます。 -hungarianアルゴリズムの実装は、scipyの`linear_sum_assignment`関数を使用しています。`linear_sum_assignment`関数は、コスト行列を引数として受け取り、最適な割り当てを行います。 +hungarian アルゴリズムの実装は、scipy の`linear_sum_assignment`関数を使用しています。`linear_sum_assignment`関数は、コスト行列を引数として受け取り、最適な割り当てを行います。 次に、作成したモジュールを登録します。`config/module.yaml` を以下のように編集してください。 @@ -307,7 +317,7 @@ SampleHumanDetector: Clustering: src..module.algorithm.k_means_pp_clustering.KMeansPPClustering ``` -ターミナルを2つ起動します。 +ターミナルを 2 つ起動します。 片方のターミナルを開き、シミュレーションサーバーを以下のコマンドで起動します: diff --git a/docs/source/hands-on/search.md b/docs/source/hands-on/search.md index 8bd4ba50..6d44ad05 100644 --- a/docs/source/hands-on/search.md +++ b/docs/source/hands-on/search.md @@ -23,11 +23,6 @@ touch src//module/complex/k_means_pp_search.py 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.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 @@ -36,6 +31,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.search import Search from adf_core_python.core.logger.logger import get_agent_logger +from rcrscore.entities import Building, Entity, Refuge, EntityID class KMeansPPSearch(Search): @@ -109,7 +105,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__}", @@ -148,16 +144,16 @@ 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 ``` 以上で、`KMeansPPClustering` モジュールを用いた `KMeansPPSearch` モジュールの実装が完了しました。 -ターミナルを2つ起動します。 +ターミナルを 2 つ起動します。 片方のターミナルを開き、シミュレーションサーバーを以下のコマンドで起動します: @@ -208,7 +204,7 @@ python main.py 一度選択した探索対象に到達するまで、探索対象を変更しないようにする ``` -```{admonition} プログラム例 +`````{admonition} プログラム例 :class: hint dropdown ````python @@ -236,12 +232,12 @@ 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 -``` +````` ### すでに探索したエンティティを再度探索対象として選択してしまう問題 @@ -251,7 +247,7 @@ python main.py すでに探索したエンティティを何かしらの方法で記録し、再度探索対象として選択しないようにする ``` -```{admonition} プログラム例 +`````{admonition} プログラム例 :class: hint dropdown ````python @@ -319,12 +315,12 @@ 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 -``` +````` ### 近くに未探索のエンティティがあるのに、遠くのエンティティを探索対象として選択してしまう @@ -334,7 +330,7 @@ python main.py エンティティ間の距離を計算し、もっとも近いエンティティを探索対象として選択する ``` -```{admonition} プログラム例 +`````{admonition} プログラム例 :class: hint dropdown ````python @@ -375,9 +371,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 -``` +````` diff --git a/docs/source/tutorial/agent/agent_control.md b/docs/source/tutorial/agent/agent_control.md index 089fcdce..6ec4cdba 100644 --- a/docs/source/tutorial/agent/agent_control.md +++ b/docs/source/tutorial/agent/agent_control.md @@ -4,7 +4,7 @@ ## エージェントの制御について -RRSの災害救助エージェントは3種類あり、種類毎にそれぞれ異なるプログラムを書く必要があります。しかし、初めから全てのプログラムを書くことは困難です。ここではまず初めに、消防隊エージェントを操作するプログラムの一部を書いてみましょう。 +RRS の災害救助エージェントは 3 種類あり、種類毎にそれぞれ異なるプログラムを書く必要があります。しかし、初めから全てのプログラムを書くことは困難です。ここではまず初めに、消防隊エージェントを操作するプログラムの一部を書いてみましょう。 ```{note} エージェントを操作するプログラムは、エージェントの種類毎に同一です。 プログラムは各エージェントに配られ、そのエージェントのみの操作を担います。 消防隊エージェントを操作するプログラムを書けば、それがすべての消防隊エージェント上でそれぞれ動作します。 @@ -14,9 +14,9 @@ RRSの災害救助エージェントは3種類あり、種類毎にそれぞれ ## エージェントの動作フロー -エージェントの動作を決めているのはTacticsというプログラムです。 +エージェントの動作を決めているのは Tactics というプログラムです。 -消防隊の思考ルーチンは下の図の通りにおおよそおこなわれます。 消防隊の動作としては、まず救助対象の市民を捜索し(`Human Detector`)、見つかった市民を救助します(`Action Rescue`)。 救助対象の市民が見つからない場合は、探索場所を変更して市民を捜索するため、次の捜索場所を決定して(`Search`)移動します(`Action Ext move`)。 なお、エージェントは1ステップ内で、移動と救助活動を同時におこなうことが出来ません。つまり、ステップごとに更新される自身の周辺情報を確認して、動作の対象と動作内容を決定していくということになります。 これらそれぞれの機能が、モジュールと呼ばれるプログラムとして分割して表現されています。 +消防隊の思考ルーチンは下の図の通りにおおよそおこなわれます。 消防隊の動作としては、まず救助対象の市民を捜索し(`Human Detector`)、見つかった市民を救助します(`Action Rescue`)。 救助対象の市民が見つからない場合は、探索場所を変更して市民を捜索するため、次の捜索場所を決定して(`Search`)移動します(`Action Ext move`)。 なお、エージェントは 1 ステップ内で、移動と救助活動を同時におこなうことが出来ません。つまり、ステップごとに更新される自身の周辺情報を確認して、動作の対象と動作内容を決定していくということになります。 これらそれぞれの機能が、モジュールと呼ばれるプログラムとして分割して表現されています。 今回はこの中で、救助(掘り起こし)対象を決定する `Human Detector` モジュールを開発します。 @@ -36,8 +36,6 @@ touch src//module/complex/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 @@ -45,6 +43,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.component.module.complex.human_detector import HumanDetector from adf_core_python.core.logger.logger import get_agent_logger +from rcrscore.entities import EntityID class FireBrigadeHumanDetector(HumanDetector): @@ -70,7 +69,7 @@ class FireBrigadeHumanDetector(HumanDetector): def calculate(self) -> HumanDetector: """ 行動対象を決定する - + Returns ------- HumanDetector: 自身のインスタンス @@ -81,7 +80,7 @@ class FireBrigadeHumanDetector(HumanDetector): def get_target_entity_id(self) -> Optional[EntityID]: """ 行動対象のEntityIDを取得する - + Returns ------- Optional[EntityID]: 行動対象のEntityID @@ -107,7 +106,7 @@ DefaultTacticsFireBrigade: HumanDetector: src..module.complex.fire_brigade_human_detector.FireBrigadeHumanDetector ``` -ターミナルを2つ起動します。 +ターミナルを 2 つ起動します。 片方のターミナルを開き、シミュレーションサーバーを以下のコマンドで起動します: @@ -145,7 +144,7 @@ python main.py `Entity` クラスは、エンティティの基底クラスです。 このクラスは、エンティティの基本情報を保持します。 -RRS上のエンティティは下図のように `Entity` を継承したクラスで表現されています。 赤枠で囲まれたクラスは、クラスの意味がそのままRRSの直接的な構成要素を表しています。 +RRS 上のエンティティは下図のように `Entity` を継承したクラスで表現されています。 赤枠で囲まれたクラスは、クラスの意味がそのまま RRS の直接的な構成要素を表しています。 例: Road クラスのインスタンスの中には、 Hydrant クラスを継承してない通常の道路を表すものも存在しています。 @@ -153,7 +152,7 @@ RRS上のエンティティは下図のように `Entity` を継承したクラ ### EntityID -`EntityID` クラスは、全てのエージェント/オブジェクトを一意に識別するためのID(識別子)を表すクラスです。 RRSではエージェントとオブジェクトをまとめて、エンティティと呼んでいます。 +`EntityID` クラスは、全てのエージェント/オブジェクトを一意に識別するための ID(識別子)を表すクラスです。 RRS ではエージェントとオブジェクトをまとめて、エンティティと呼んでいます。 ### Civilian @@ -165,7 +164,7 @@ RRS上のエンティティは下図のように `Entity` を継承したクラ is_civilian: bool = isinstance(entity, Civilian) ``` -- エンティティIDを取得する +- エンティティ ID を取得する ```python entity_id: EntityID = entity.get_id() @@ -193,7 +192,7 @@ if buriedness is None or buriedness <= 0: モジュール内では、`WorldInfo` クラスのインスタンスを `self._world_info` として保持しています。 -- エンティティIDからエンティティを取得する +- エンティティ ID からエンティティを取得する ```python entity: Entity = self._world_info.get_entity(entity_id) @@ -219,7 +218,7 @@ distance: float = self._world_info.get_distance(me, civilian.get_id()) モジュール内では、`AgentInfo` クラスのインスタンスを `self._agent_info` として保持しています。 -- 自分自身のエンティティIDを取得する +- 自分自身のエンティティ ID を取得する ```python my_entity_id: EntityID = self._agent_info.get_entity_id() @@ -242,9 +241,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.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 +from rcrscore.entities import Civilian, Entity, EntityID class FireBrigadeHumanDetector(HumanDetector): @@ -310,7 +307,7 @@ class FireBrigadeHumanDetector(HumanDetector): # 計算結果を格納 self._result = nearest_civilian - + # ロガーに出力 self._logger.info(f"Target: {self._result}") @@ -327,7 +324,7 @@ class FireBrigadeHumanDetector(HumanDetector): return self._result ``` -ターミナルを2つ起動します。 +ターミナルを 2 つ起動します。 片方のターミナルを開き、シミュレーションサーバーを以下のコマンドで起動します: From 0005cb7ee12bf3e90c1db4bbb8e71bf8981e4d43 Mon Sep 17 00:00:00 2001 From: shima004 Date: Fri, 28 Nov 2025 13:46:26 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20adf-core-python=E3=81=AE=E3=83=90?= =?UTF-8?q?=E3=83=BC=E3=82=B8=E3=83=A7=E3=83=B3=E3=82=920.2.1=E3=81=8B?= =?UTF-8?q?=E3=82=890.2.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 --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 6d5e8d8c..113b158a 100644 --- a/uv.lock +++ b/uv.lock @@ -16,7 +16,7 @@ wheels = [ [[package]] name = "adf-core-python" -version = "0.2.1" +version = "0.2.3" source = { editable = "." } dependencies = [ { name = "bitarray" },