Skip to content

Commit a3e701e

Browse files
committed
feat: add agent control tutorial and integrate sphinxcontrib-mermaid extension
1 parent 0fd6f4c commit a3e701e

File tree

7 files changed

+287
-0
lines changed

7 files changed

+287
-0
lines changed

docs/source/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"sphinx.ext.autosummary",
2626
"sphinx_copybutton",
2727
"myst_parser",
28+
"sphinxcontrib.mermaid",
2829
]
2930

3031
templates_path = ["_templates"]
38.7 KB
Loading

docs/source/images/agent_flow.png

627 KB
Loading

docs/source/images/entity.png

39.7 KB
Loading
75.3 KB
Loading

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ ADF Core Python を始めるには、インストール手順に従い、この
3838
tutorial/environment/environment
3939
tutorial/install/install
4040
tutorial/agent/agent
41+
tutorial/agent/agent_control
4142
tutorial/config/config
4243
tutorial/module/module
4344

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
# エージェントの制御
2+
このセクションでは、エージェントの制御用のプログラムを作成する方法について説明します。
3+
4+
## エージェントの制御
5+
6+
RRSの災害救助エージェントは3種類あり、種類毎にそれぞれ異なるプログラムを書く必要があります。しかし、初めから全てのプログラムを書くことは困難です。ここではまず初めに、消防隊エージェントを操作するプログラムの一部を書いてみましょう.
7+
8+
```{note}
9+
エージェントを操作するプログラムは,エージェントの種類毎に同一です. プログラムは各エージェントに配られ,そのエージェントのみの操作を担います. 消防隊エージェントを操作するプログラムを書けば,それがすべての消防隊エージェント上でそれぞれ動作します.
10+
```
11+
12+
![消防隊エージェント](./../../images/agent_control.jpg)
13+
14+
## エージェントの動作フロー
15+
16+
エージェントの動作を決めているのはTacticsというプログラムです.
17+
18+
消防隊の思考ルーチンは下の図の通りにおおよそおこなわれます. 消防隊の動作としては,まず救助対象の市民を捜索し(`Human Detector`),見つかった市民を救助します(`Action Rescue`). 救助対象の市民が見つからない場合は,探索場所を変更して市民を捜索するため,次の捜索場所を決定して(`Search`)移動します(`Action Ext move`). なお,エージェントは1ステップ内で,移動と救助活動を同時におこなうことが出来ません.つまり,ステップごとに更新される自身の周辺情報を確認して,動作の対象と動作内容を決定していくということになります. これらそれぞれの機能が,モジュールと呼ばれるプログラムとして分割して表現されています.
19+
20+
今回はこの中で,救助(掘り起こし)対象を決定する `Human Detector` モジュールを開発します.
21+
22+
![消防隊エージェントの動作フロー](./../../images/agent_flow.png)
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+
![救助対象選択フローチャート](./../../images/human_detector_flow.png)
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+
![エンティティの継承関係](./../../images/entity.png)
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

Comments
 (0)