0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-02-14

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

# 例題 タイトル テーマ
第1回 例題2.1 2つの機械工程を持つ生産ライン QUEUEノード
第2回 例題2.3 故障を伴う機械システムのシミュレーション RESOURCEブロック
第3回 例題2.5 一車線通行の信号システム GATEブロック
第4回 例題2.6 トラック運送システム SELECTノード等
第5回 例題2.7 病院内カルテ搬送システム BATCHノード等

#2.例題の概要
 例題2.5は一方通行の信号システムに関する問題です.下記図の通り片側1車線が工事中のため残り車線が交互通行になっています.工事区間の入口には信号1と信号2があり,両方赤が55秒.信号1のみ青が60秒,両方赤が55秒,信号2のみ青が45秒続き,これを繰り返します.
 一方の車線からは平均9秒の指数分布に従って車が交互通行区間に到着します.他方の車線からは平均12秒の指数分布に従って車が到着します.各車は信号が青ならばそのまま通過できますが,赤ならば待ち行列に順番に並びます.なお行列に並んでいる車を追い抜くことは出来ません.一度待ち行列に入ると次に信号が青になって信号を通過するまでに2秒かかります.
25-02.png

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

25-03.png

#4.ソースコードの説明
メニューから「File」~「New」~「OMNeT++ Project」を選択して適当なプロジェクト名を付与して下さい(今回はSimSlam2.5としています).プロジェクトを構成するソースファイルは表の通りですが,GitHub(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 信号待ちがあれば経過時間に2秒追加するモジュールの宣言
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 信号待ちがあれば経過時間に2秒追加する
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モジュールの主な機能は信号1と信号2の管理です.信号の状態は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 初期登録
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?