OMNeT++による離散シミュレーション～GATEブロック編

#1.はじめに
　第３回目となる今回は教科書である「SLAMⅡによるシステム・シミュレーション入門」から例題2.5「一車線通行の信号システム」に取り組みます．テーマは交通信号のように時間に応じて状態が変化するリソースのシミュレーションです．SLAMではこのようなリソースの管理をGATEブロックにて行います．OPENノードとCLOSEノードにてGATEリソースの状態を変更します．また前回使用したRESOURCEブロックを併用することにより複雑かつ現実的なメッセージ制御が可能になります．
　なお当面は教科書の第２章「ネットワークによるモデル化」を一通り学習しつつ，モデル化テクニックの基礎を習得したいと考えますが，今後取り扱う例題やテーマは以下の通りです．

|#|例題|タイトル|テーマ|
|---|---|---|---|
|第１回|例題2.1|[２つの機械工程を持つ生産ライン](https://qiita.com/tsugulin/items/021bbc65094baaa56ee1)|QUEUEノード|
|第２回|例題2.3|[故障を伴う機械システムのシミュレーション](https://qiita.com/tsugulin/items/021bbc65094baaa56ee1)|RESOURCEブロック|
|第３回|例題2.5|一車線通行の信号システム|GATEブロック|
|第４回|例題2.6|トラック運送システム|SELECTノード等|
|第５回|例題2.7|病院内カルテ搬送システム|BATCHノード等|

#2.例題の概要
　例題2.5は一方通行の信号システムに関する問題です．下記図の通り片側１車線が工事中のため残り車線が交互通行になっています．工事区間の入口には信号１と信号２があり，両方赤が55秒．信号１のみ青が60秒，両方赤が55秒，信号２のみ青が45秒続き，これを繰り返します．
　一方の車線からは平均9秒の指数分布に従って車が交互通行区間に到着します．他方の車線からは平均12秒の指数分布に従って車が到着します．各車は信号が青ならばそのまま通過できますが，赤ならば待ち行列に順番に並びます．なお行列に並んでいる車を追い抜くことは出来ません．一度待ち行列に入ると次に信号が青になって信号を通過するまでに２秒かかります．
![25-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507413/4bf41a28-f6f9-45e0-916b-bb905f6b12ae.png)

#3.例題のネットワーク図
　create1ノードはとcreate2ノードはそれぞれ平均9秒と平均12秒の指数分布に従う間隔にてメッセージを生成します．次のassign1ノードとassign2ノードはメッセージにそれぞれcar1とcar2という属性を付与します．メッセージはawait1ノードにて合流します．await1ノードはResouceブロックによる待ち行列です．car1のメッセージならばSTART1のリソース，car2のメッセージならばSTART2のリソースの獲得をResouceブロックに要求します．リソースを獲得できれば後工程にメッセージを送りますが，もし獲得できなければcar1とcar2それぞれ別の待ち行列にて待ちます．次のawait2ノードはGateブロックによる待ち行列です．car1のメッセージならば信号１，car2のメッセージならば信号２の色をGateブロックにて確認して．もし青ならば後工程にメッセージを送りますが，赤ならばcar1とcar2それぞれ別の待ち行列にて待ちます．このようにawait1ノードとawati2ノードの合わせ技によって車の信号待ちを表現しています．processノードは一度待ち行列に入ったメッセージに対して２秒加算します．これは一度停止して再発進するために必要な時間です．最後のreleaseノードは獲得したリソースを解放して次のSTARTポジションにいる車が通行できるようにします．
　create3ノードは信号制御のためのメッセージを生成します．このメッセージはシミュレーション開始55秒後にopen1ノードに到着すると信号１を青にするようgateブロックに指示します．その60秒後にclose1ノードに到着すると信号１を赤にするようgateブロックに指示します．その55秒後にopen2ノードに到着すると信号２を青にするようgateブロックに指示します．その45秒後にclose2ノードに到着すると信号２を赤にするようgateブロックに指示します．その55秒後にopen1ノードに戻ってきて以降はループします
　なおSLAMネットワークではresourceブロックやgateブロックに対するアクティビティの線はないのですが，OMNet++ではメソッド呼出のために必要なのでawaitやreleaseやopenやcloseから線を伸ばしています

![25-03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507413/772bc025-645d-30ce-2e02-d2e4f9ef1ec4.png)


#4.ソースコードの説明
メニューから「File」～「New」～「OMNeT++ Project」を選択して適当なプロジェクト名を付与して下さい（今回はSimSlam2.5としています）．プロジェクトを構成するソースファイルは表の通りですが，GitHub[（https://github.com/tsugulin/SimSlam2.5）](https://github.com/tsugulin/SimSlam2.5)から入手可能です．git cloneにてローカルにダウンロードし，Windowsのファイル・エクスプローラーからIDEのプロジェクト・エクスプローラーにドラッグ＆ドロップすることにより取り込み可能です．  

|フォルダ名|ファイル名|説明|
|---|---|---|
|simulations|omnetpp.ini|シミュレーションのパラメータ変数の定義|
|simulations|Simulation01.ned|ノードとモジュールの関連付け（＝ネットワーク）を定義|
|src|Create.ned|メッセージを生成するモジュールの宣言|
|src|Assign.ned|リソースに属性を付与するモジュールの宣言|
|src|AwaitResource.ned|Resourceブロックにリソースを要求する待ち行列モジュールの宣言|
|src|AwaitGate.ned|Gateブロックにリソースを要求する待ち行列モジュールの宣言|
|src|Process.ned|信号待ちがあれば経過時間に２秒追加するモジュールの宣言|
|src|Release.ned|リソースを解放するモジュールの宣言|
|src|Resource.ned|リソースを管理するモジュールの宣言|
|src|GateKeeper.ned|Gateリソースを管理するモジュールの宣言|
|src|GateControl.ned|Gateの開閉を行うモジュールの宣言|
|src|Create.cc|Createモジュールの定義|
|src|Create.h|Createモジュールのクラス宣言|
|src|Assign.cc|Assignモジュールの定義|
|src|Assign.h|Assignモジュールのクラス宣言|
|src|AwaitResoucre.cc|AwaitResourceモジュールの定義|
|src|AwaitResoucre.h|AwaitRecsourceモジュールのクラス宣言|
|src|AwaitGate.cc|AwaitGateモジュールの定義|
|src|AwaitGate.h|AwaitGateモジュールのクラス宣言|
|src|Process.cc|Processモジュールの定義|
|src|Process.h|Processモジュールのクラス宣言|
|src|Release.cc|Releaseモジュールの定義|
|src|Release.h|Releaseモジュールのクラス宣言|
|src|Resource.cc|Resourceモジュールの定義|
|src|Resource.h|Resourceモジュールのクラス宣言|
|src|GateKeeper.cc|GateKeeperモジュールの定義|
|src|GateKeeper.h|GateKeeperモジュールのクラス宣言|
|src|GateControl.cc|GateControlモジュールの定義|
|src|GateControl.h|GateControlモジュールのクラス宣言|
|src|Wavg.cc|待ち行列長さの加重平均を計算する「その他」クラス|
|src|Wavg.h|Wavgクラスのヘッダー|

　例題2.5のネットワーク定義はSimulation01.nedの内容の通りです．gateブロックはGateKeeperモジュールから，openノードやcloseノードはGateControlモジュールから生成しています．gateというキーワードはOMNet++では予約語なので名称を少し変えています．

```Simulation01.ned
network Simulation01
{
    submodules:
        create1: Create {
            parameters:
                @display("p=50,100;i=block/source");
        }
        assign1: Assign {
            parameters:
                @display("p=150,100;i=block/control");
        }
        create2: Create {
            parameters:
                @display("p=50,200;i=block/source");
        }
        assign2: Assign {
            parameters:
                @display("p=150,200;i=block/control");
        }
        await1: AwaitResource {
            parameters:
                @display("p=250,150;i=block/circle");
        }
        await2: AwaitGate {
            parameters:
                @display("p=350,150;i=block/circle");
        }
        process: Process {
            parameters:
                @display("p=450,150;i=block/process");
        }
        release: Release {
            parameters:
                @display("p=550,150;i=block/square");
        }
        resource: Resource {
            parameters:
                @display("p=250,275;i=block/table");
        }
        gate: GateKeeper {
            parameters:
                @display("p=350,275;i=block/table");
        }
        create3: Create {
            parameters:
                @display("p=50,400;i=block/source");
        }
        open1: GateControl {
            parameters:
                @display("p=150,400;i=block/switch");
        }
        close1: GateControl {
            parameters:
                @display("p=250,439;i=block/switch");
        }
        open2: GateControl {
            parameters:
                @display("p=350,439;i=block/switch");
        }
        close2: GateControl {
            parameters:
                @display("p=450,400;i=block/switch");
        }
    connections:
        create1.out --> assign1.in;
        create2.out --> assign2.in;
        assign1.out --> await1.in1;
        assign2.out --> await1.in2;
        await1.out --> await2.in;
        await2.out --> process.in;
        process.out --> release.in;
        create3.out --> open1.from_create;
        open1.out --> close1.in;
        close1.out --> open2.in;
        open2.out --> close2.in;
        close2.out --> open1.in;
        await1.res --> resource.from_await;
        release.res --> resource.from_release;
        await2.res --> gate.from_await;
        open1.res --> gate.from_open1;
        close1.res --> gate.from_close1;
        open2.res --> gate.from_open2;
        close2.res --> gate.from_close2;
}
```

　各モジュールが使用する変数と入口及び出口は以下の通りです．Create.nedの変数「datatype」はログ(*elog)にメッセージ種類（beat，car1やsignal等）を表示するために利用しています．Assign.nedの変数「datakind」は車が走っている車線の属性をメッセージに付与するために利用します．GateControl.nedの変数gatenameは操作対象の信号，actionは操作の内容（openまたはclose），intervalTimeは信号の切替時間を表します．また入口「in」はopen2ノード等では使われないためシミュレーション実行時にエラーになるのですが，@looseアノテーションにて装飾することによりエラーを回避しています．  

|宣言ファイル名|変数|入口|出口|主な役割|
|---|---|---|---|---|
|Create.ned|datatype，initialTimeおよびintervalTime|なし|out|メッセージを作成する|
|Assign.ned|datakind|in|out|メッセージに属性を付与する|
|AwaitResource.ned|なし|in1，in2|out, res|Resourceブロックからリソースを確保できるまで待つ|
|AwaitGate.ned|なし|in|out, res|Gateブロックからリソースを確保できるまで待つ|
|Process.ned|なし|in|out|信号待ちがあれば経過時間に２秒追加する|
|Release.ned|なし|in|res|リソースを解放する|
|Resouce.ned|なし|from_await, from_release|なし|Resourceブロックを管理する|
|GateKeeper.ned|なし|from_await, from_open1, from_open2, from_close1, from_close2|なし|Gateブロックを管理する|
|GateControl.ned|gatename，action，intervalTime|in，from_create|out, res|Gateを開閉する|

　主要なモジュールであるGateKeeper，GateControl，AwaitGateについて説明します．

　GateKeeperモジュールの主な機能は信号１と信号２の管理です．信号の状態はsignal配列に保管しており，initialメソッドにて赤に初期化されます．openメソッドにて青にcloseメソッドにて赤に変更することが出来ます．またcheckメソッドにて状態を確認することが出来ます（trueならば青）．  

```GateKeeper.h
#ifndef GATEKEEPER_H_
#define GATEKEEPER_H_

#include <omnetpp.h>
using namespace omnetpp;

class AwaitGate;

class GateKeeper : public cSimpleModule {
private:
    bool signal[3];             // 信号の状態を表す
    AwaitGate *awaitNode;       // AwaitGateノードへのポインタ
    simtime_t checkpoint[3];    // ゲートが開いたた時間を記録
    cStdDev stats[3];           // 信号の使用状況を記録

protected:
    virtual void initialize();  // 変数を初期化
    virtual void finish();      //　リソース使用業況を表示

public:
    virtual void open(long kind);   // 信号を青にする
    virtual void close(long kind);  // 信号を赤にする
    virtual bool check(long kind);  // 信号の色を確認する
};

#endif /* GATEKEEPER_H_ */
```


```GateKeeper.cc
#include "GateKeeper.h"
Define_Module(GateKeeper);
#include "AwaitGate.h"

void GateKeeper::initialize()
{
    // 信号を初期化
    signal[1] = false;  // 信号1は赤
    signal[2] = false;  // 信号2も赤

    // AwaitGateノードのポインタをセット
    cModule *mod = gate("from_await")->getPreviousGate()->getOwnerModule();
    awaitNode = check_and_cast<AwaitGate *>(mod);
}

// ゲートを開ける．信号を青に変える
void GateKeeper::open(long kind)
{
    Enter_Method("open");
    checkpoint[kind] = simTime();   // ゲートが空いた時刻を記録
    signal[kind] = true;            // 信号を青に変える
    awaitNode->goForward(kind);     // 信号待ちの車に知らせる
}

// ゲートを閉める．信号を赤に変える
void GateKeeper::close(long kind)
{
    Enter_Method("close");
    stats[kind].collect( simTime() - checkpoint[kind] );    // ゲートが空いていた時間を記録
    signal[kind] = false;           // 信号を赤に変える
}

// ゲートの開閉を確認する．Trueならば青
bool GateKeeper::check(long kind)
{
    Enter_Method("check");
    return signal[kind];
}

// リソース使用状況の表示
void GateKeeper::finish()
{
    EV << "LIGHT1 Utilization: " << stats[1].getSum() / simTime() << endl;
    EV << "LIGHT2 Utilization: " << stats[2].getSum() / simTime() << endl;
}
```

　GateControlモジュールの主な機能は信号の切替えです．initializeメソッドはomnetpp.iniの内容に従って対象とする信号と操作の種類を初期化します．handleMessageメソッドは前工程からメッセージが届くとGateKeeperモジュールのメソッドを呼び出して信号の切替えを行います．

```GateControl.cc
#include "GateControl.h"
Define_Module(GateControl);
#include "GateKeeper.h"

void GateControl::initialize()
{
    // 変数の初期化
    std::string param1 = par("action");         // openまたはclose
    std::string param2 = par("gatename");       // signal1またはsignal2
    mode = (param1 == "open") ? true: false;    // 当該ノードが信号を開けるのか閉めるのかをmodeに保管
    kind = (param2 == "signal1") ? 1: 2;        // 当該ノードがどの信号を操作するのかをkindに保管

    // Gateリソースのポインタを取得
    cModule *mod = gate("res")->getNextGate()->getOwnerModule();
    gkp = check_and_cast<GateKeeper *>(mod);
}

// メッセージが届いたら，omnetpp.iniにて指定されたゲート操作を行う
void GateControl::handleMessage(cMessage *msg)
{
    if ( msg->isSelfMessage() ) {
        // アクティビティの処理完了時
        proctime.collect(simTime() - msg->getTimestamp());      // アクティビティの時間を記録
        send(msg, "out");                                       // 後工程にメッセージを送る
    }
    else {
        // 前工程から届いたメッセージの場合
        if (mode) gkp->open(kind);  else  gkp->close(kind);     // 指定ゲートを開閉
        msg->setTimestamp(simTime());                           // アクティビティの開始時間をセット
        scheduleAt(simTime() + par("productionTime"), msg);     // 生産開始
    }
}

//　ノードの統計情報を表示
void GateControl::finish()
{
    EV << "Gate Jobs Count:   " << proctime.getCount() << endl;
    EV << "Gate Utilization:  " << proctime.getSum() / simTime() << endl;
    EV << "Gate AVG ProcTime: " << proctime.getMean() << endl;

    proctime.recordAs("Gate ProcTime");
}
```

　AwaitGateモジュールの主な機能は信号が青になるまで信号待ちの先頭車両を待たせることです．handleMessageメソッドは先頭の車（START1またはSTART2リソースを所有）を信号待ちの待ち行列に入れてgoForwardメソッドを呼び出します．goForwardメソッドは信号が青であれば車を進めます．  

```AwaitGate.cc
//　信号が青になるまで信号待ちの先頭車両を待たせる
#include "AwaitGate.h"
Define_Module(AwaitGate);
#include "GateKeeper.h"

void AwaitGate::initialize()
{
    // 待ち行列の平均長さを計算するクラスを初期化
    queuelen[1].init(simTime(), 100);
    queuelen[2].init(simTime(), 100);

    // Gateリソースのポインタを取得
    cModule *mod1 = gate("res")->getNextGate()->getOwnerModule();
    gkp = check_and_cast<GateKeeper *>(mod1);
}

// 先頭の車を信号待ちにする
void AwaitGate::handleMessage(cMessage *msg)
{
    int k = msg->getKind();     // メッセージの属性から車線の情報を取得
    queuelen[k].set(simTime(), queue[k].getLength());       // 待ち行列平均長さを計算
    queue[k].insert(msg);       // 先頭の車を信号待ちにする
    goForward(k);               // 信号待ちを処理する
}

// 信号待ちをしている車があり，信号が青ならば進める
void AwaitGate::goForward(long k)
{
    Enter_Method("goForward");
    if ( queue[k].getLength() > 0 ) {       // 信号待ちをしている車があるか？
        if (gkp->check(k)) {                // 信号は青か？
            queuelen[k].set(simTime(), queue[k].getLength());               // 待ち行列平均長さを計算
            cMessage *msg = check_and_cast<cMessage *>(queue[k].pop());     // 待ち行列からメッセージを取り出す
            send(msg, "out");                                               // 後工程に送る
        }
    }
}

//　信号の待ち行列の平均長さを表示する
void AwaitGate::finish()
{
    EV << "SIGNAL1 Queue AVG Length: " << queuelen[1].get(simTime()) << endl;
    EV << "SIGNAL2 Queue AVG Length: " << queuelen[2].get(simTime()) << endl;
}
```

#4.設定ファイル（omnetpp.ini）の準備
　omnetpp.iniの内容は以下の通りです．シミュレーション時間は1時間（3600秒）としています．

```omnetpp.ini
[General]
network = Simulation01
record-eventlog = true

sim-time-limit = 3600s
cpu-time-limit = 3600s
total-stack = 7MiB  # increase if necessary
cmdenv-express-mode = true
cmdenv-event-banners = true
cmdenv-performance-display = false

[Config Run1]
*.create1.datatype = "car1"
*.create1.initialTime = 0s
*.create1.intervalTime = exponential(9.0s)
*.create2.datatype = "car2"
*.create2.initialTime = 0s
*.create2.intervalTime = exponential(12.0s)
*.create3.datatype = "signal"
*.create3.initialTime = 55.0s
*.create3.intervalTime = 9999s
*.assign1.datakind = 1
*.assign2.datakind = 2
*.open1.productionTime = 60.0s
*.close1.productionTime = 55.0s
*.open2.productionTime = 45.0s
*.close2.productionTime = 55.0s
*.open1.gatename = "signal1"
*.close1.gatename = "signal1"
*.open2.gatename = "signal2"
*.close2.gatename = "signal2"
*.open1.action = "open"
*.close1.action = "close"
*.open2.action = "open"
*.close2.action = "close"
```

#5.結果の確認
　実行結果は以下の通りです．実行環境が異なるため全く同じ結果にはなりませんが，待ち行列長さやリソース使用状況を見ると同様のシミュレーション結果を得られたと考えます．

|項目|今回の結果|教科書|
|---|---|---|
|信号を通過した車の台数|674|670|
|平均リードタイム|77.3662|75.64|
|START1平均待ち行列長さ|7.351|6.3059|
|START2平均待ち行列長さ|5.84355|6.7860|
|SIGNAL1平均待ち行列長さ|0.679153|0.6598|
|SIGNAL1平均待ち行列長さ|0.717393|0.7727|
|START1平均使用状況|0.883597|0.8398|
|START2平均使用状況|0.86906|0.9366|
|SIGNAL1平均使用状況|0.283333|0.2833|
|SIGNAL2平均使用状況|0.2|0.2|

#6.参考資料
SLAMⅡによるシステム・シミュレーション入門（初版発行:1993年，著者:森戸晋，中野一夫，相沢りえ子，発行者:構造計画研究所発行，発売所:共立出版）

#7.改訂履歴
|改定日|改定内容|
|---|---|
|2021.2.14|初期登録|