1.はじめに
今回はサービスが2つで待ち行列が1つの場合のシミュレーションを紹介します。例題は前回および前々回と同様に平鍋氏の「サルでもわかる待ち行列」からの引用です。
2.シミュレーションモデルの説明
今回は待ち行列は「M/M/2」と言われるタイプです。待合室は1つであり、患者は空いた診療室に先着順に案内されます。
3.ソースコードの説明
ソースコードをGitHubから入手可能ですので、以前の説明を参考にOMNeT++ IDEに取り込んで下さい。前々回(SimLesson01A)からの変更点について以下に説明します。
ファイル名 | 内容 | 主な変更点 |
---|---|---|
Simulation.ned | ノード及びノード間のネットワークを定義 | 追加したノードとノード間接続の定義 |
Queue.ned | 待合室モジュールのパラメータやコネクションの定義 | 分岐に関するパラメータの追加 |
Queue.h | 待合室モジュールのヘッダー | 分岐に関するクラスプロパティやメソッドの追加 |
Queue.cc | 待合室モジュールのソース | 分岐に関するロジックの実装 |
Simlation.nedの変更点は、(1)医師ノード(doctor)を配列に変更、(2)ノード追加に伴う経路追加の2点です。
network Simulation01
{
(中略)
submodules:
(中略)
- doctor: Sink {中略}
+ doctor[2]: Sink {中略} //---(1)
connections:
(中略)
- wait.out --> doctor.in;
+ wait.out++ --> doctor[0].in; //---(2)
+ wait.out++ --> doctor[1].in; //---(2)
- doctor.out --> wait.in++;
+ doctor[0].out --> wait.in++; //---(2)
+ doctor[1].out --> wait.in++; //---(2)
}
Queue.nedの変更点は、(1)最大分岐数を指定するためのforkNumberパラメータ追加、(2)outゲートを配列化の2点です。
simple Queue
{
parameters:
+ int forkNumber = default(0); //---(1)
volatile double intervalTime @unit(s) = default(0);
gates:
input in[];
- output out;
+ output out[]; //---(2)
}
Queue.hの変更点は、(1)最大分岐数を格納するmaxプロパティの追加、(2)診療室の空き状況を格納するnxtプロパティの追加、(3)患者を診療室に案内するforwardメソッド追加の3点です。
(前略)
class Queue : public cSimpleModule
private:
+ int max; //---(1)
+ int ntx; //---(2)
cQueue queue;
cLongHistogram waitTime;
protected:
virtual void initialize();
virtual void handleMessage(cMessage *msg);
+ virtual void forward(int); //---(3)
virtual void finish();
};
(後略)
Queue.ccの変更点は(1)各プロパティの初期化、(2)WATCHマクロによる監視設定、(3)患者が到着した際に診療室にrequestメッセージを送る処理を削除、(4)患者が到着した際に変数nxtの状況に応じてforwardメソッドを呼び出す処理を追加、(5)待合室からcallメッセージが届いた際に患者を案内する処理を削除、(6)待合室からcallメッセージが届いた際に変数nxtを更新してforwardメソッドを呼び出す処理を追加、(7)患者を診療室に案内するforwardメソッドの追加の7点です。
(前略)
void Queue::initialize()
{
queue.setName("queue");
+ max = par("forkNumber").intValue(); //---(1)
+ nxt = 0;
+ WATCH(nxt); //---(2)
scheduleAt(simTime() + par("intervalTime"), new cMessage("beat"));
}
void Queue::handleMessage(cMessage *msg)
{
if (strcmp(msg->getName(), "patient") == 0) {
msg->setTimestamp(simTime());
queue.insert(msg);
- send(new cMessage("request"), "out"); //---(3)
+ if (nxt < 3) { //---(4)
+ if (nxt == 0) j = intuniform(0, max-1);
+ else j = (nxt == 1) ? 1: 0;
+ forward(j);
+ }
} else if (strcmp(msg->getName(), "call") == 0) {
- if (queue.getLength() > 0) { //---(5)
- cMessage *patient = check_and_cast<cMessage *>(queue.pop());
- waitTime.collect(simTime() - patient->getTimestamp());
- send(patient, "out");
- }
+ int j = (strcmp(msg->getArrivalGate()->getFullName(), "in[1]") == 0) ? 0: 1; //---(6)
+ nxt ^= j + 1;
+ forward(j);
delete msg;
}
}
+ void Queue::forward(int doctor) //---(7)
+ {
+ if (queue.getLength() > 0) {
+ cMessage *patient = check_and_cast<cMessage *>(queue.pop());
+ waitTime.collect(simTime() - patient->getTimestamp());
+ send(patient, "out", doctor);
+ nxt ^= doctor + 1;
+ }
+ }
なお変数nxtは0から3の値を取り、その意味は以下の通りです。
値 | 意味 |
---|---|
0 | 診療室は両方空いている |
1 | 診療室0は使用中だが、診療室1は空いている |
2 | 診療室0は空いているが、診療室1は使用中 |
3 | 診療室は両方使用中 |
omnetpp.iniの変更点は、(1)分岐数の設定、(2)医師ノードの配列化、(3)乱数設定の変更の3点です。
[General]
(前略)
network = Simulation01
*.enter.intervalTime = exponential(10.0s)
+*.wait.forkNumber = 2 //---(1)
-*.doctor.serviceTime = exponential(8.0s)
+*.doctor[*].serviceTime = exponential(8.0s) //---(2)
num-rngs = 1
-Config Run2]
-*.doctor.serviceTime = exponential(4.0s)
+[Config Run1]
+num-rngs = 2 //---(3)
+*.enter[*].rng-0 = 0
+*.doctor[*].rng-0 = 1
4.例題の実行
シミレーションのConfigurationを「General」として実行すると待ち行列の長さが1.21分となりました。公式(M/M/2)の解は1.5分なので約2割の誤差があります。このように結果が想定と異なる場合、原因としてはロジックの設計ミス、実装のバグ、パラメータが乱数に依拠することに起因する誤差等が考えられます。
そこでConfigurationを「Run1」として乱数体系を変更して実行すると結果は1.49分となり、公式の解とほぼ同じ結果となりました。
5.Tips
シミュレーションの乱数体系はomnetpp.iniの設定により簡単に変更できます。Generalでは(1)にて乱数生成器が1つしか定義されていないので、全てのノードは同じ乱数生成器を使用します。いっぽうRun1では(2)にて乱数生成器を2つ定義しており、(3)にてenterノードが乱数生成器0、(4)にてdoctorノードが乱数生成器1を利用するよう設定しています。
[General]
(前略)
num-rngs = 1 //---(1)
[Config Run1]
num-rngs = 2 //---(2)
*.enter[*].rng-0 = 0 //---(3)
*.doctor[*].rng-0 = 1 //---(4)
6.改訂履歴
改定日 | 改定前 | 改定後 |
---|---|---|
2022.4.30 | - | 初期登録 |