サンプション・アーキテクチャをC++で実装
サンプション・アーキテクチャとは
ロボットの制御などに用いられ、それぞれのモジュールの階層構造を管理するアーキテクチャです。
ある目標に向かって行動するために最も重要なモジュールが下層にあり、上位層になればなるほどより抽象的なモジュールが構成される形が一般的ですね。
自動運転で例えると、道順通り運転する最上位層もモジュールがあって、下層モジュールには、渋滞してたら回避するモジュールや、車が突っ込んで来たら回避するモジュール…というような構成です。
実際に私の研究のラジコンカーにも実装されております。
日本語wikipediaより英語のほうが詳しい説明があります。
ja.wikipedia.org en.wikipedia.org
また、今回の実装は以下のサイトにあるjavaのソースをc++に翻訳しました。 www.ibm.com
実装クラス
大きくわけて3つあります。 1. Behavior(基底クラス) 2. BehaviorBasedAgent(実行クラス) 3. main
注意点 - これから説明するソースにはCarというクラスがありますが、車の情報を管理するクラスだと思ってください(アクチュエータとかのアクセスもここから) - すべてヘッダオンリークラスです - 元々ソースにあったコメントは英語、処理の代わりとして日本語のコメント入れてます
ファイル構成
. ├── main.cpp ├── Car.h ├── BehaviorBasedAgent.h ├── subsumption │ ├── Behavior.h │ ├── Drive.h │ └── Stop.h
- Drive.h コース通り運転するモジュール
- Stop.h 停止処理があるモジュール
として説明していきます。
Behavior 基底クラス
Behavior.h
#pragma once #include "../Car.h" // Must add "override" modifier ! class Behavior { protected: Car& car; public: // This constructor is supposed to be called from child class Behavior(Car& car_) : car(car_){}; // virtual function virtual bool isActive() { throw std::runtime_error("Called base class @ Behavior.h isActive()"); return false; } virtual void act() { throw std::runtime_error("Called base class @ Behavior.h act()"); } };
実装はかんたんでisActive()
にmoduleがactiveになる条件を記述して、act()
に行動を記述します。
Behavior実装例(Stopクラス)
Stop.h
#pragma once #include "../Car.h" #include "Behavior.h" class Stop : public Behavior { public: Stop(Car& car_) : Behavior(car_){} bool isActive() override { // ストップする条件を満たしたら(信号、コースアウト…) return true; } void act() override { // Carクラスを使って停止処理 } };
BehaviorBasedAgent クラス
BehaviorBasedAgent.h
#pragma once #include "subsumption/Behavior.h" #include "subsumption/Stop.h" // - RelationMat // BehaviorRelationMat[i][j] : 行動優先度がi<jならば[i][j]=false using BehaviorRelationMat = std::vector<std::vector<bool>>; using Behaviors = std::vector<std::unique_ptr<Behavior>>; class BehaviorBasedAgent { private: Behaviors& behaviors; BehaviorRelationMat suppresses; int current_behavior_index; public: BehaviorBasedAgent(Behaviors& behaviors_, BehaviorRelationMat suppresses_) : behaviors(behaviors_), suppresses(suppresses_), current_behavior_index(0) {} void step() { std::vector<bool> is_active(behaviors.size()); for (size_t i = 0; i < behaviors.size(); i++) { is_active[i] = behaviors[i]->isActive(); } bool ran_behavior = false; size_t counter = 0; while (!ran_behavior) { bool run_current_behavior = is_active[current_behavior_index]; if (run_current_behavior) { for (size_t i = 0; i < suppresses[0].size(); i++) { if (is_active[i] && suppresses[i][current_behavior_index]) { run_current_behavior = false; break; } } } if (run_current_behavior) { if (current_behavior_index < static_cast<int>(behaviors.size())) { // do action behaviors[current_behavior_index]->act(); } ran_behavior = true; } if (behaviors.size() > 0) { current_behavior_index = (current_behavior_index + 1) % behaviors.size(); counter++; if(counter == behaviors.size()) break; } } } };
main.cpp(インスタンスを持つ)
#include "BehaviorBasedAgent.h" int main(int argc, const char** argv) { //他の処理 ... // This instance deals with modules that act as actuators Behaviors actuator_module; actuator_module.push_back(std::unique_ptr<Behavior>(new Stop(car))); actuator_module.push_back(std::unique_ptr<Behavior>(new Drive(car))); BehaviorBasedAgent actuator_agent(actuator_module, {{false, true}, {false, false}}); // control loop while(1){ subsumption.step(); } ... //他の処理 ... return EXIT_SUCCESS; }
- BehaviorBasedAgentに他のファイルをincludeしてるので、mainではこのファイルだけで大丈夫です
- あとは制御処理のループでstep()を呼んで上げれば勝手に動作します
-
actuator_agent
を初期化するときに、関係の行列を一緒に定義します
ibmのサイトに詳しい説明があるのですが、それぞれモジュールの関係性を、行動優先度がi<jならば[i][j]=falseと定義します。
なのでこの行列の対角成分は自身との比較なのでfalse
ですね。
もしすべてのモジュールの関係性が、下層モジュール < 上層モジュルならば、下false
の三角行列になります。