psim言語講座(第12回)チュートリアル編(3)

  • HOME
  • psim言語講座(第12回)チュートリアル編(3)

本記事は当社が発行しているシミュレーションメールマガジンVol.12の記事です。
シミュレーションメールマガジンの詳細・購読申込はこちら のサポートページから

はじめに

前回は、psim におけるプロセスの制御方法を説明しました。

今回は、Facility と呼ばれる資源の様々な利用方法を説明していきます。

ファシリティ

離散イベントシミュレーションでは有限の資源を利用する時に発生する待ち行列をシミュレートする事ができます。

その「資源」のひとつとして、psim には、ファシリティという概念があります。

ファシリティは排他的に利用可能な複数の離散資源を持つものを表します。例えば、「銀行の窓口」などです。銀行の窓口がひとつしかないとすると、ある人がその窓口を占有していると、他の人はその窓口が空くのを待たなければなりません。窓口を占有していた人の処理が終了すればその窓口を開放し、待ち行列に並んでいた次の人が窓口を占有します。

以下のようにして、ファシリティを定義できます。

f = Facility(1, monitor = True)

ファシリティの基本

ファシリティの基本的な使い方は以下のようになります。

    lock = (yield f.request(name = "lock"))["lock"]

で、ロックを取得し、

    lock.release()

で、ロックを開放します。

例えば、以下のようなコードを作成します。

initialize()
f = Facility(1, monitor = True)

def service(item):
    print(f"時間 {now()} {item} 利用待ち")
    lock = (yield f.request(name = "lock"))["lock"]
    print(f"時間 {now()} {item} 利用開始")
    yield pause(30)
    print(f"時間 {now()} {item} 利用終了")
    lock.release()

def order():
    yield subactivate(service)("注文番号1")
    yield pause(10)
    yield subactivate(service)("注文番号2")
    yield pause(30)
    yield subactivate(service)("注文番号3")
    yield alwaysFalse()

activate(order)()
start()

この結果は、以下のようになります。

時間 0.0 注文番号1 利用待ち
時間 0.0 注文番号1 利用開始
時間 10.0 注文番号2 利用待ち
時間 30.0 注文番号1 利用終了
時間 30.0 注文番号2 利用開始
時間 40.0 注文番号3 利用待ち
時間 60.0 注文番号2 利用終了
時間 60.0 注文番号3 利用開始
時間 90.0 注文番号3 利用終了

複数の資源を持つファシリティ

次に、ファシリティの容量を増やしてみます。

f = Facility(3, monitor = True)

この例では、容量を 3 しています。

initialize()
f = Facility(3, monitor = True)

def service(item):
    print(f"時間 {now()} {item} 利用待ち")
    lock = (yield f.request(name = "lock"))["lock"]
    print(f"時間 {now()} {item} 利用開始")
    yield pause(30)
    print(f"時間 {now()} {item} 利用終了")
    lock.release()

def order():
    yield subactivate(service)("注文番号1")
    yield pause(10)
    yield subactivate(service)("注文番号2")
    yield pause(30)
    yield subactivate(service)("注文番号3")
    yield alwaysFalse()

activate(order)()
start()

資源に余裕があるため、待ち行列は抑えられて以下のような結果になります。

時間 0.0 注文番号1 利用待ち
時間 0.0 注文番号1 利用開始
時間 10.0 注文番号2 利用待ち
時間 10.0 注文番号2 利用開始
時間 30.0 注文番号1 利用終了
時間 40.0 注文番号2 利用終了
時間 40.0 注文番号3 利用待ち
時間 40.0 注文番号3 利用開始
時間 70.0 注文番号3 利用終了

ファシリティ取得のタイムアウト

psim では任意の待ち受け式は OR 結合する事ができます。

result = yield A(..., name = "nameA") | B(..., name = "nameB")

と書くと、OR 待ち受けを表現します。

この OR 待ち受けは、A と B を同時に待ち受け、先に発火したものが結果になります。それぞれの待ち受けには名前を定義する事ができ、どちらの待ち受けが発火したのかを結果から取得する事ができます。

A が発火した場合は、

"nameA" in result

が真になります。

ファシリティの待ち受けと、pause を OR 待ち受けすると、タイムアウトを表現できます。

    result = yield f.request(name = "lock") | pause(7, name = "timed out")

は、ファシリティ f を待ち受けますが、7 秒でタイムアウトする待ち受けを意味します。

initialize()
f = Facility(1, monitor = True)

def service(item):
    print(f"時間 {now()} {item} 利用待ち")
    result = yield f.request(name = "lock") | pause(7, name = "timed out")
    if "timed out" in result:
        print(f"時間 {now()} {item} 利用待ちキャンセル(タイムアウト)")
    else:
        lock = result["lock"]
        print(f"時間 {now()} {item} 利用開始")
        yield pause(30)
        print(f"時間 {now()} {item} 利用終了")
        lock.release()

def order():
    yield subactivate(service)("注文番号1")
    yield pause(10)
    yield subactivate(service)("注文番号2")
    yield pause(30)
    yield subactivate(service)("注文番号3")
    yield alwaysFalse()

activate(order)()
start()

結果は以下のようになります。

時間 0.0 注文番号1 利用待ち
時間 0.0 注文番号1 利用開始
時間 10.0 注文番号2 利用待ち
時間 17.0 注文番号2 利用待ちキャンセル(タイムアウト)
時間 30.0 注文番号1 利用終了
時間 40.0 注文番号3 利用待ち
時間 40.0 注文番号3 利用開始
時間 70.0 注文番号3 利用終了

ファシリティのキャンセル

ファシリティの待ち受けと、イベントを OR 待ち受けすると、外部からのキャンセルを表現できます。

イベントも psim の資源のひとつです。

e = Event()

は、イベント資源を定義します。

yield e.wait()

は、イベントを待ち受けます。

e.signal()

は、イベントを発火させます。

    result = yield f.request(name = "lock") | e1.wait(name = "signal")

は、ファシリティ f を待ち受けますが、e1 が発火するとキャンセルされるような待ち受けを意味します。

        result = yield pause(30) | e2.wait(name = "signal")

は、30秒間待ちますが、e2 が発火するとキャンセルされるような待ち受けを意味します。

initialize()
f = Facility(1, monitor = True)
e1 = Event()
e2 = Event()

def service(item):
    print(f"時間 {now()} {item} 利用待ち")
    result = yield f.request(name = "lock") | e1.wait(name = "signal")
    if "signal" in result:
        print(f"時間 {now()} {item} 利用待ちキャンセル(割り込み)")
    else:
        lock = result["lock"]
        print(f"時間 {now()} {item} 利用開始")
        result = yield pause(30) | e2.wait(name = "signal")
        if "signal" in result:
            print(f"時間 {now()} {item} 利用キャンセル(割り込み)")
        else:
            print(f"時間 {now()} {item} 利用終了")
        lock.release()

def order():
    yield subactivate(service)("注文番号1")
    yield pause(10)
    yield subactivate(service)("注文番号2")
    yield pause(30)
    yield subactivate(service)("注文番号3")
    yield alwaysFalse()

def cancel():
    yield pause(11)
    e1.signal()
    yield pause(3)
    e2.signal()

activate(order)()
activate(cancel)()
start()

結果は以下のようになります。

時間 0.0 注文番号1 利用待ち
時間 0.0 注文番号1 利用開始
時間 10.0 注文番号2 利用待ち
時間 11.0 注文番号2 利用待ちキャンセル(割り込み)
時間 14.0 注文番号1 利用キャンセル(割り込み)
時間 40.0 注文番号3 利用待ち
時間 40.0 注文番号3 利用開始
時間 70.0 注文番号3 利用終了

ファシリティのキャンセル(2)

先程の例では、どの注文がキャンセルされるかは分かりませんでした。

注文毎に、イベントを用意すれば、任意の注文をキャンセルする事を表現できます。

initialize()
f = Facility(1, monitor = True)

def service(item, e):
    print(f"時間 {now()} {item} 利用待ち")
    result = yield f.request(name = "lock") | e.wait(name = "signal")
    if "signal" in result:
        print(f"時間 {now()} {item} 利用待ちキャンセル(割り込み)")
    else:
        lock = result["lock"]
        print(f"時間 {now()} {item} 利用開始")
        result = yield pause(30) | e.wait(name = "signal")
        if "signal" in result:
            print(f"時間 {now()} {item} 利用キャンセル(割り込み)")
        else:
            print(f"時間 {now()} {item} 利用終了")
        lock.release()

def order():
    e = Event()
    yield subactivate(service)("注文番号1", e)
    yield pause(3)
    e.signal()
    yield pause(7)
    e = Event()
    yield subactivate(service)("注文番号2", e)
    yield pause(10)
    e = Event()
    yield subactivate(service)("注文番号3", e)
    yield pause(5)
    e.signal()
    yield alwaysFalse()

activate(order)()
start()

結果は以下のようになります。

時間 0.0 注文番号1 利用待ち
時間 0.0 注文番号1 利用開始
時間 3.0 注文番号1 利用キャンセル(割り込み)
時間 10.0 注文番号2 利用待ち
時間 10.0 注文番号2 利用開始
時間 20.0 注文番号3 利用待ち
時間 25.0 注文番号3 利用待ちキャンセル(割り込み)
時間 40.0 注文番号2 利用終了

複数のファシリティの並行待ち受け

複数のファシリティを OR 待ち受けする事も可能です。

    result = yield (f.request(name = "lock-f") |
                    g.request(name = "lock-g"))

は、ファシリティ f と ファシリティ g を同時に待ち受けます。

initialize()
f = Facility(1, monitor = True)
g = Facility(1, monitor = True)

def service(item):
    print(f"時間 {now()} {item} 利用待ち")
    result = yield (f.request(name = "lock-f") |
                    g.request(name = "lock-g"))
    if "lock-f" in result:
        lock = result["lock-f"]
        print(f"時間 {now()} {item} ファシリティ f 利用開始")
        yield pause(30)
        print(f"時間 {now()} {item} ファシリティ f 利用終了")
        lock.release()
    if "lock-g" in result:
        lock = result["lock-g"]
        print(f"時間 {now()} {item} ファシリティ g 利用開始")
        yield pause(30)
        print(f"時間 {now()} {item} ファシリティ g 利用終了")
        lock.release()

def order():
    e = Event()
    yield subactivate(service)("注文番号1")
    yield pause(10)
    yield subactivate(service)("注文番号2")
    yield pause(30)
    yield subactivate(service)("注文番号3")
    yield alwaysFalse()

activate(order)()
start()

結果は以下のようになります。

時間 0.0 注文番号1 利用待ち
時間 0.0 注文番号1 ファシリティ f 利用開始
時間 10.0 注文番号2 利用待ち
時間 10.0 注文番号2 ファシリティ g 利用開始
時間 30.0 注文番号1 ファシリティ f 利用終了
時間 40.0 注文番号2 ファシリティ g 利用終了
時間 40.0 注文番号3 利用待ち
時間 40.0 注文番号3 ファシリティ f 利用開始
時間 70.0 注文番号3 ファシリティ f 利用終了

まとめ

今回は、psim の Facility 資源の様々な利用方法を説明しました。

更に OR 待ち受けする事で、様々な待ち受けを表現できる事がお分かりになったかと思います。

次回も、待ち受け式について説明していこうと思います。

監修:株式会社NTTデータ数理システム 機械学習、統計解析、数理計画、シミュレーションなどの数理科学を 背景とした技術を活用し、業種・テーマを問わず幅広く仕事をしています。
http://www.msi.co.jp NTTデータ数理システムができること
「数理科学の基礎知識」e-book無料ダウンロードはこちら

関連記事