|
| 1 | +# エージェントの制御 |
| 2 | +このセクションでは、エージェントの制御用のプログラムを作成する方法について説明します。 |
| 3 | + |
| 4 | +## エージェントの制御 |
| 5 | + |
| 6 | +RRSの災害救助エージェントは3種類あり、種類毎にそれぞれ異なるプログラムを書く必要があります。しかし、初めから全てのプログラムを書くことは困難です。ここではまず初めに、消防隊エージェントを操作するプログラムの一部を書いてみましょう. |
| 7 | + |
| 8 | +```{note} |
| 9 | +エージェントを操作するプログラムは,エージェントの種類毎に同一です. プログラムは各エージェントに配られ,そのエージェントのみの操作を担います. 消防隊エージェントを操作するプログラムを書けば,それがすべての消防隊エージェント上でそれぞれ動作します. |
| 10 | +``` |
| 11 | + |
| 12 | + |
| 13 | + |
| 14 | +## エージェントの動作フロー |
| 15 | + |
| 16 | +エージェントの動作を決めているのはTacticsというプログラムです. |
| 17 | + |
| 18 | +消防隊の思考ルーチンは下の図の通りにおおよそおこなわれます. 消防隊の動作としては,まず救助対象の市民を捜索し(`Human Detector`),見つかった市民を救助します(`Action Rescue`). 救助対象の市民が見つからない場合は,探索場所を変更して市民を捜索するため,次の捜索場所を決定して(`Search`)移動します(`Action Ext move`). なお,エージェントは1ステップ内で,移動と救助活動を同時におこなうことが出来ません.つまり,ステップごとに更新される自身の周辺情報を確認して,動作の対象と動作内容を決定していくということになります. これらそれぞれの機能が,モジュールと呼ばれるプログラムとして分割して表現されています. |
| 19 | + |
| 20 | +今回はこの中で,救助(掘り起こし)対象を決定する `Human Detector` モジュールを開発します. |
| 21 | + |
| 22 | + |
| 23 | + |
| 24 | +## `Human Detector` モジュールの実装の準備 |
| 25 | + |
| 26 | +まず、`Human Detector` モジュールを記述するためのファイルを作成します. |
| 27 | + |
| 28 | +```bash |
| 29 | +cd WORKING_DIR/<your_team_name> |
| 30 | +touch src/<your_team_name>/module/complex/fire_brigade_human_detector.py |
| 31 | +``` |
| 32 | + |
| 33 | +次に、`Human Detector` モジュールの雛形の実装を行います. 以下のコードを`fire_brigade_human_detector.py` に記述してください. |
| 34 | + |
| 35 | +```python |
| 36 | +from typing import Optional |
| 37 | + |
| 38 | +from rcrs_core.worldmodel.entityID import EntityID |
| 39 | + |
| 40 | +from adf_core_python.core.agent.develop.develop_data import DevelopData |
| 41 | +from adf_core_python.core.agent.info.agent_info import AgentInfo |
| 42 | +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo |
| 43 | +from adf_core_python.core.agent.info.world_info import WorldInfo |
| 44 | +from adf_core_python.core.agent.module.module_manager import ModuleManager |
| 45 | +from adf_core_python.core.component.module.complex.human_detector import HumanDetector |
| 46 | +from adf_core_python.core.logger.logger import get_agent_logger |
| 47 | + |
| 48 | + |
| 49 | +class FireBrigadeHumanDetector(HumanDetector): |
| 50 | + def __init__( |
| 51 | + self, |
| 52 | + agent_info: AgentInfo, |
| 53 | + world_info: WorldInfo, |
| 54 | + scenario_info: ScenarioInfo, |
| 55 | + module_manager: ModuleManager, |
| 56 | + develop_data: DevelopData, |
| 57 | + ) -> None: |
| 58 | + super().__init__( |
| 59 | + agent_info, world_info, scenario_info, module_manager, develop_data |
| 60 | + ) |
| 61 | + # 計算結果を格納する変数 |
| 62 | + self._result: Optional[EntityID] = None |
| 63 | + # ロガーの取得 |
| 64 | + self._logger = get_agent_logger( |
| 65 | + f"{self.__class__.__module__}.{self.__class__.__qualname__}", |
| 66 | + self._agent_info, |
| 67 | + ) |
| 68 | + |
| 69 | + def calculate(self) -> HumanDetector: |
| 70 | + """ |
| 71 | + 行動対象を決定する |
| 72 | + |
| 73 | + Returns |
| 74 | + ------- |
| 75 | + HumanDetector: 自身のインスタンス |
| 76 | + """ |
| 77 | + self._logger.info("Calculate FireBrigadeHumanDetector") |
| 78 | + return self |
| 79 | + |
| 80 | + def get_target_entity_id(self) -> Optional[EntityID]: |
| 81 | + """ |
| 82 | + 行動対象のEntityIDを取得する |
| 83 | + |
| 84 | + Returns |
| 85 | + ------- |
| 86 | + Optional[EntityID]: 行動対象のEntityID |
| 87 | + """ |
| 88 | + return self._result |
| 89 | +``` |
| 90 | + |
| 91 | +## モジュールの登錍 |
| 92 | + |
| 93 | +次に、作成したモジュールを登録します. |
| 94 | + |
| 95 | +`WORKING_DIR/<your_team_name>/config/module.yaml` ファイルを開き,以下の部分を |
| 96 | +```yaml |
| 97 | +DefaultTacticsFireBrigade: |
| 98 | + HumanDetector: src.<your_team_name>.module.complex.sample_human_detector.SampleHumanDetector |
| 99 | +``` |
| 100 | +
|
| 101 | +以下のように変更してください. |
| 102 | +
|
| 103 | +```yaml |
| 104 | +DefaultTacticsFireBrigade: |
| 105 | + HumanDetector: src.<your_team_name>.module.complex.fire_brigade_human_detector.FireBrigadeHumanDetector |
| 106 | +``` |
| 107 | +
|
| 108 | +シミュレーションサーバーを起動し、エージェントを実行してみましょう. |
| 109 | +
|
| 110 | +```bash |
| 111 | +cd WORKING_DIR/<your_team_name> |
| 112 | +python main.py |
| 113 | +``` |
| 114 | + |
| 115 | +標準出力に `Calculate FireBrigadeHumanDetector` と表示されれば成功です. |
| 116 | + |
| 117 | +## `Human Detector` モジュールの設計 |
| 118 | + |
| 119 | +救助対象選択プログラムの中身を書き,消防隊エージェントが救助活動をおこなえるように修正します. |
| 120 | + |
| 121 | +消防隊エージェントの救助対象を最も簡単に選択する方法は,埋没している市民の中で最も自身に近い市民を選択する方法です. 今回は,この方法を採用した消防隊エージェントの行動対象決定モジュールを書いてみましょう. |
| 122 | + |
| 123 | +埋没している市民の中で最も自身に近い市民を救助対象として選択する方法は,フローチャートで表すと下図のようになります. |
| 124 | + |
| 125 | + |
| 126 | + |
| 127 | +このフローチャート中の各処理を,次小節で紹介する各クラス・メソッド等で置き換えたものを,`fire_brigade_human_detector.py` に記述していくことで,救助対象選択プログラムを完成させます. |
| 128 | + |
| 129 | + |
| 130 | +## `Human Detector` モジュールの実装で使用するクラス・メソッド |
| 131 | + |
| 132 | +### WorldInfo |
| 133 | + |
| 134 | +`WorldInfo` クラスは,エージェントが把握している情報とそれに関する操作をおこなうメソッドをもったクラスです. エージェントはこのクラスのインスタンスを通して,他のエージェントやオブジェクトの状態を確認します. |
| 135 | + |
| 136 | +モジュール内では,`WorldInfo` クラスのインスタンスを `self._world_info` として保持しています. |
| 137 | + |
| 138 | +### AgentInfo |
| 139 | + |
| 140 | +`AgentInfo` クラスは,エージェント自身の情報とそれに関する操作をおこなうメソッドをもったクラスです. エージェントはこのクラスのインスタンスを通して,エージェント自身の状態を取得します. |
| 141 | + |
| 142 | +モジュール内では,`AgentInfo` クラスのインスタンスを `self._agent_info` として保持しています. |
| 143 | + |
| 144 | +### EntityID |
| 145 | + |
| 146 | +`EntityID` クラスは,全てのエージェント/オブジェクトを一意に識別するためのID(識別子)を表すクラスです. RRSではエージェントとオブジェクトをまとめて,エンティティと呼んでいます. |
| 147 | + |
| 148 | +- 自分自身のエンティティIDを取得する |
| 149 | +```python |
| 150 | +self._agent_info.get_entity_id() |
| 151 | +``` |
| 152 | + |
| 153 | +### Entity |
| 154 | + |
| 155 | +`Entity` クラスは,エンティティの基底クラスです. このクラスは,エンティティの基本情報を保持します. |
| 156 | + |
| 157 | +- エンティティIDからエンティティを取得する |
| 158 | +```python |
| 159 | +self._world_info.get_entity(entity_id) |
| 160 | +``` |
| 161 | + |
| 162 | +- 同じクラスのエンティティを全て取得する |
| 163 | +```python |
| 164 | +self._world_info.get_entities_by_type([Building, Road]) |
| 165 | +``` |
| 166 | + |
| 167 | +### Civilian |
| 168 | + |
| 169 | +`Civilian` クラスは,市民を表すクラスです.このクラスからは,エージェントの位置や負傷の進行状況を取得することができます. |
| 170 | + |
| 171 | +- `entity` が市民であるかどうかを判定する |
| 172 | +```python |
| 173 | +isinstance(entity, Civilian) |
| 174 | +``` |
| 175 | + |
| 176 | +- 市民が生きているかどうかを判定する |
| 177 | +```python |
| 178 | +hp: Optional[int] = entity.get_hp() |
| 179 | +if hp is None or hp <= 0: |
| 180 | + return False |
| 181 | +``` |
| 182 | + |
| 183 | +- 市民が埋まっているかどうかを判定する |
| 184 | +```python |
| 185 | +buriedness: Optional[int] = entity.get_buriedness() |
| 186 | +if buriedness is None or buriedness <= 0: |
| 187 | + return False |
| 188 | +``` |
| 189 | + |
| 190 | +### entityの継承 |
| 191 | + |
| 192 | +RRS上のエンティティは下図のように Entity を継承したクラスで表現されています. 赤枠で囲まれたクラスは,クラスの意味がそのままRRSの直接的な構成要素を表しています. |
| 193 | + |
| 194 | +例: Road クラスのインスタンスの中には, Hydrant クラスを継承してない通常の道路を表すものも存在しています. |
| 195 | + |
| 196 | + |
| 197 | + |
| 198 | + |
| 199 | +## `Human Detector` モジュールの実装 |
| 200 | + |
| 201 | +`Human Detector` モジュールの実装を行います. |
| 202 | + |
| 203 | +```python |
| 204 | +from typing import Optional |
| 205 | + |
| 206 | +from rcrs_core.worldmodel.entityID import EntityID |
| 207 | +from rcrs_core.entities.civilian import Civilian |
| 208 | +from rcrs_core.entities.entity import Entity |
| 209 | + |
| 210 | +from adf_core_python.core.agent.develop.develop_data import DevelopData |
| 211 | +from adf_core_python.core.agent.info.agent_info import AgentInfo |
| 212 | +from adf_core_python.core.agent.info.scenario_info import ScenarioInfo |
| 213 | +from adf_core_python.core.agent.info.world_info import WorldInfo |
| 214 | +from adf_core_python.core.agent.module.module_manager import ModuleManager |
| 215 | +from adf_core_python.core.component.module.complex.human_detector import HumanDetector |
| 216 | +from adf_core_python.core.logger.logger import get_agent_logger |
| 217 | + |
| 218 | + |
| 219 | +class SampleHumanDetector(HumanDetector): |
| 220 | + def __init__( |
| 221 | + self, |
| 222 | + agent_info: AgentInfo, |
| 223 | + world_info: WorldInfo, |
| 224 | + scenario_info: ScenarioInfo, |
| 225 | + module_manager: ModuleManager, |
| 226 | + develop_data: DevelopData, |
| 227 | + ) -> None: |
| 228 | + super().__init__( |
| 229 | + agent_info, world_info, scenario_info, module_manager, develop_data |
| 230 | + ) |
| 231 | + # 計算結果を格納する変数 |
| 232 | + self._result: Optional[EntityID] = None |
| 233 | + # ロガーの取得 |
| 234 | + self._logger = get_agent_logger( |
| 235 | + f"{self.__class__.__module__}.{self.__class__.__qualname__}", |
| 236 | + self._agent_info, |
| 237 | + ) |
| 238 | + |
| 239 | + def calculate(self) -> HumanDetector: |
| 240 | + """ |
| 241 | + 行動対象を決定する |
| 242 | + |
| 243 | + Returns |
| 244 | + ------- |
| 245 | + HumanDetector: 自身のインスタンス |
| 246 | + """ |
| 247 | + me: EntityID = self._agent_info.get_entity_id() |
| 248 | + civilians: list[Entity] = self._world_info.get_entities_of_types( |
| 249 | + [ |
| 250 | + Civilian, |
| 251 | + ] |
| 252 | + ) |
| 253 | + |
| 254 | + nearest_civilian: Optional[EntityID] = None |
| 255 | + nearest_distance: Optional[float] = None |
| 256 | + for civilian in civilians: |
| 257 | + if not isinstance(civilian, Civilian): |
| 258 | + continue |
| 259 | + |
| 260 | + if civilian.get_hp() <= 0: |
| 261 | + continue |
| 262 | + |
| 263 | + if civilian.get_buriedness() <= 0: |
| 264 | + continue |
| 265 | + |
| 266 | + distance: float = self._world_info.get_distance(me, civilian.get_id()) |
| 267 | + |
| 268 | + if nearest_distance is None or distance < nearest_distance: |
| 269 | + nearest_civilian = civilian.get_id() |
| 270 | + nearest_distance = distance |
| 271 | + |
| 272 | + self._result = nearest_civilian |
| 273 | + |
| 274 | + return self |
| 275 | + |
| 276 | + def get_target_entity_id(self) -> Optional[EntityID]: |
| 277 | + """ |
| 278 | + 行動対象のEntityIDを取得する |
| 279 | + |
| 280 | + Returns |
| 281 | + ------- |
| 282 | + Optional[EntityID]: 行動対象のEntityID |
| 283 | + """ |
| 284 | + return self._result |
| 285 | +``` |
0 commit comments