Mímisbrunnr知恵の泉

← オペレーションズマネジメント 一覧

🎓 レベル:応用 | 重要度:A(必須)

📎 前提:M/M/1 待ち行列モデル(birth-death・定常分布 PnP_nL,Lq,W,WqL,L_q,W,W_q)・待ち行列の基礎とリトルの法則(稼働率 ρ・L=λW) | 確率の土台:確率過程(マルコフ連鎖・ポアソン過程) | 次:第5章 生産計画/第6章 リスクプーリング

要点(BLUF)

1. M/M/c:状態依存サービス率の birth-death

M/M/1 待ち行列モデル の M/M/1 を窓口 cc 個に一般化します。客は共通の1列に並び、空いた窓口へ順に進みます(各窓口のサービス率は同じ μ\mu)。系内数 N(t)N(t) はやはり birth-death 過程ですが、下りの率が状態で変わるのが新しい点です。

系内に nn 人いるとき、実際に動いている窓口は min(n,c)\min(n,c) 個(nncc 未満なら nn 個、cc 以上なら全 cc 個が稼働)。1個あたり完了率 μ\mu なので、サービス完了の合計率(下り)は min(n,c)μ\min(n,c)\mu。上り(到着)はどの状態でも λ\lambda のままです。

flowchart LR
  S0["0"] -->|"λ"| S1["1"]
  S1 -->|"1·μ"| S0
  S1 -->|"λ"| S2["2"]
  S2 -->|"2·μ"| S1
  S2 -->|"λ"| Sc["c"]
  Sc -->|"c·μ"| S2
  Sc -->|"λ"| Sc1["c+1"]
  Sc1 -->|"c·μ"| Sc

提供負荷を a=λμa=\dfrac{\lambda}{\mu}(単位アーラン=平均して何個分の窓口が仕事で埋まるか)、窓口あたり稼働率を ρ=ac=λcμ\rho=\dfrac{a}{c}=\dfrac{\lambda}{c\mu} とおきます。安定条件は ρ<1\rho<1λ<cμ\lambda<c\mu)。流量の釣り合い λPn1=min(n,c)μPn\lambda P_{n-1}=\min(n,c)\mu\,P_n を解くと、定常分布は2つの領域に分かれます。

Pn={ann!P0(0nc)acc!ρncP0(n>c)P_n= \begin{cases} \dfrac{a^n}{n!}\,P_0 & (0\le n\le c)\\[2mm] \dfrac{a^c}{c!}\,\rho^{\,n-c}\,P_0 & (n> c) \end{cases}

ncn\le c では下りの率が nμn\mu と増えていくので Pn=(a/n)Pn1P_n=(a/n)P_{n-1} となり an/n!a^n/n! 型(ポアソン型)、全窓口が埋まる n>cn>c では下りが cμc\mu で頭打ちになり Pn=ρPn1P_n=\rho P_{n-1} の幾何型(M/M/1 と同じ尻尾)です。P0P_0 は規格化 nPn=1\sum_n P_n=1 で決まります。

2. アーランC公式と性能指標

新規客が待たされるのは「到着時に全窓口が埋まっている」=NcN\ge c のとき。その確率がアーランC公式です。n>cn>c の幾何尻尾を ncρnc=1/(1ρ)\sum_{n\ge c}\rho^{n-c}=1/(1-\rho) で足し上げて規格化すると、

C(c,a)=Pr(Nc)=acc!11ρk=0c1akk!+acc!11ρC(c,a)=\Pr(N\ge c)=\frac{\dfrac{a^c}{c!}\dfrac{1}{1-\rho}}{\displaystyle\sum_{k=0}^{c-1}\frac{a^k}{k!}+\frac{a^c}{c!}\dfrac{1}{1-\rho}}

待っている客の平均数 LqL_q は、待ち領域 n>cn>c の超過人数 (nc)(n-c) の期待値で、幾何分布の平均(M/M/1 待ち行列モデルjρj=ρ/(1ρ)2\sum j\rho^j=\rho/(1-\rho)^2)を使うと CC の簡潔な倍になります。

Lq=C(c,a)ρ1ρ,Wq=Lqλ=C(c,a)cμλL_q=C(c,a)\,\frac{\rho}{1-\rho},\qquad W_q=\frac{L_q}{\lambda}=\frac{C(c,a)}{c\mu-\lambda}

WqW_qLqL_q にリトルの法則 Wq=Lq/λW_q=L_q/\lambda を当て、ρ=λ/(cμ)\rho=\lambda/(c\mu) を入れて整理すると分母が cμλc\mu-\lambda になります。)系内時間とその他は M/M/1 同様 W=Wq+1/μW=W_q+1/\muL=λW=Lq+aL=\lambda W=L_q+a で出ます。待たされる確率 C(c,a)C(c,a) さえ計算できれば、Lq,WqL_q,W_q は機械的です。

3. アーランCで要員設計:目標を満たす最小窓口数(コード)

C(c,a)C(c,a) を実装し、コールセンターの要員設計をします。到着 λ=40\lambda=40 件/時、平均処理 6 分(μ=10\mu=10 件/時/人)で提供負荷 a=4a=4 アーラン。窓口 cc を増やすと待ち確率・平均待ち時間・稼働率がどう動くかを表にし、「平均待ち <1<1 分」「待つ確率 <5%<5\%」という2つの目標それぞれを満たす最小の cc を探します。

import math
import numpy as np
import pandas as pd

def erlang_c(c, a):
    """M/M/c の待つ確率 C(c,a)(アーランC)。a=λ/μ(提供負荷)、要 a/c<1。"""
    rho = a / c
    s = sum(a**k / math.factorial(k) for k in range(c))   # k=0..c-1
    last = a**c / math.factorial(c) / (1.0 - rho)         # k=c の項 ×1/(1-ρ)
    return last / (s + last)

# コールセンター:到着 λ=40 件/時、平均処理 6 分 → μ=10 件/時/人。提供負荷 a=λ/μ=4
lam = 40.0   # 件/時
mu = 10.0    # 件/時/人(1件6分)
a = lam / mu
print(f"到着 lambda={lam} 件/時, サービス mu={mu} 件/時/人, 提供負荷 a=lambda/mu={a:.1f} アーラン")
print(f"安定には c > a = {a:.0f} が必要(c>={int(a)+1}\n")

rows = []
for c in range(int(a) + 1, 11):       # c=5..10
    rho = a / c
    Cw = erlang_c(c, a)
    Wq_min = Cw / (c * mu - lam) * 60   # 平均待ち時間(分)
    Lq = Cw * rho / (1 - rho)
    rows.append({"窓口c": c, "稼働率rho": rho, "待つ確率C": Cw, "Wq(分)": Wq_min, "Lq": Lq})
df = pd.DataFrame(rows)
print(df.to_string(index=False, float_format=lambda x: f"{x:.4f}"))
print()

# 目標を満たす最小 c を探索
def min_c(predicate):
    c = int(a) + 1
    while not predicate(c):
        c += 1
    return c

c_wait = min_c(lambda c: erlang_c(c, a) / (c * mu - lam) * 60 < 1.0)   # 平均待ち<1分
c_prob = min_c(lambda c: erlang_c(c, a) < 0.05)                        # 待つ確率<5%
print(f"目標A 平均待ち Wq<1分 を満たす最小窓口数 c = {c_wait}"
      f"(Wq={erlang_c(c_wait, a) / (c_wait * mu - lam) * 60:.3f}分)")
print(f"目標B 待つ確率 C<5%  を満たす最小窓口数 c = {c_prob}"
      f"(C={erlang_c(c_prob, a):.4f})")

出力:

到着 lambda=40.0 件/時, サービス mu=10.0 件/時/人, 提供負荷 a=lambda/mu=4.0 アーラン
安定には c > a = 4 が必要(c>=5)

 窓口c  稼働率rho  待つ確率C  Wq(分)     Lq
   5  0.8000 0.5541 3.3247 2.2165
   6  0.6667 0.2848 0.8543 0.5695
   7  0.5714 0.1351 0.2702 0.1801
   8  0.5000 0.0590 0.0886 0.0590
   9  0.4444 0.0238 0.0285 0.0190
  10  0.4000 0.0088 0.0088 0.0059

目標A 平均待ち Wq<1分 を満たす最小窓口数 c = 6(Wq=0.854分)
目標B 待つ確率 C<5%  を満たす最小窓口数 c = 9(C=0.0238)

出力の意味:提供負荷 a=4a=4 なので c=5c=5 が最小限の窓口数ですが、そのときは稼働率 0.80・待つ確率 55%・平均待ち 3.3 分とかなり混みます。窓口を1個ずつ足すと効果は劇的で、c=6c=6 で待つ確率 28%・平均待ち 0.85 分、c=8c=8 で待つ確率 6%まで下がります。注目は2つの目標で必要な窓口数が違うこと——「平均待ち <1<1 分」は c=6c=6 で足りる(0.85分)のに、「待つ確率 <5%<5\%」という厳しい約束は c=9c=9 必要(c=8c=8 の 5.9% では届かない)。どのサービス水準を顧客に約束するかで要員数が変わるわけです。また c=610c=6\to10 で待ち確率が 0.280.0090.28\to0.009 と下がる一方、改善幅は1窓口あたり逓減しており(c=910c=9\to10 では 0.024→0.009 とわずか)、過剰な窓口は費用対効果が落ちます。

4. プーリング効果:統合すると待ちが激減する(コード)

待ち行列設計で最も反直感的で強力なのがプーリング効果です。同じ総負荷・同じ稼働率でも、cc 個の独立した列(それぞれ M/M/1)」より「客も窓口も1つに統合した M/M/c」のほうが待ちが圧倒的に短い。各窓口 μ=1\mu=1/分・稼働率 ρ=0.8\rho=0.8 をそろえて、規模 cc を変えながら両者の WqW_q を比べます。

import math
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

def erlang_c(c, a):
    rho = a / c
    s = sum(a**k / math.factorial(k) for k in range(c))
    last = a**c / math.factorial(c) / (1.0 - rho)
    return last / (s + last)

# 同じ稼働率 rho=0.8 で「c個の独立 M/M/1」vs「1つの M/M/c」を比較
# 各窓口 mu=1/分。独立系は各列に lambda=0.8/分(rho=0.8)。
# プール系は到着を集約 Lambda=c*0.8、サーバ c 個(a=Lambda/mu=0.8c, rho=a/c=0.8)
mu = 1.0
lam_each = 0.8           # 独立系の1列あたり到着率
rho = lam_each / mu      # = 0.8(両系で共通)

# 独立 M/M/1 の待ち時間は窓口数によらず一定
Wq_single = rho / (mu - lam_each)   # = 4.0 分

rows = []
for c in [2, 4, 8, 16]:
    Lambda = c * lam_each            # プール系の総到着率
    a = Lambda / mu                  # 提供負荷
    Cw = erlang_c(c, a)
    Wq_pool = Cw / (c * mu - Lambda) # プール M/M/c の平均待ち時間(分)
    rows.append({
        "規模c": c, "稼働率rho": a / c,
        "独立M/M/1のWq": Wq_single,
        "プールM/M/cのWq": Wq_pool,
        "削減率": Wq_single / Wq_pool,
    })
df = pd.DataFrame(rows)
print(f"各窓口 mu={mu}/分、稼働率 rho={rho}(両系で同一)。待ち時間の単位=分")
print(df.to_string(index=False, float_format=lambda x: f"{x:.4f}"))

# 図:c に対する Wq(独立 vs プール)
cs = np.arange(1, 21)
wq_pool = []
for c in cs:
    Lambda = c * lam_each
    a = Lambda / mu
    wq_pool.append(erlang_c(c, a) / (c * mu - Lambda))
plt.figure(figsize=(9, 5.5))
plt.axhline(Wq_single, color="#d62728", lw=2, ls="--",
            label=f"c個の独立M/M/1(各Wq={Wq_single:.1f}分・一定)")
plt.plot(cs, wq_pool, "o-", color="#1f77b4", lw=2, label="1つのプールM/M/c")
plt.xlabel("窓口数 c(総負荷は c に比例・稼働率 ρ=0.8 一定)")
plt.ylabel("平均待ち時間 Wq(分)")
plt.title("プーリング効果:同じ稼働率でも統合すると待ちが激減する")
plt.legend(); plt.grid(alpha=0.3); plt.tight_layout()
plt.show()

出力:

各窓口 mu=1.0/分、稼働率 rho=0.8(両系で同一)。待ち時間の単位=分
 規模c  稼働率rho  独立M/M/1のWq  プールM/M/cのWq     削減率
   2  0.8000      4.0000       1.7778  2.2500
   4  0.8000      4.0000       0.7455  5.3652
   8  0.8000      4.0000       0.2860 13.9846
  16  0.8000      4.0000       0.0953 41.9832

出力の意味:稼働率はどの行も 0.80.8完全に同じ――それでも待ち時間は大違いです。客を別々の列に並ばせる独立 M/M/1 は、規模によらず常に Wq=4.0W_q=4.0 分。ところが同じ客と窓口を1つのプールに統合すると、c=2c=2 で 1.78 分、c=4c=4 で 0.75 分、c=16c=16 では 0.10 分 まで縮みます(削減率は c=4c=4 で 5.4 倍、c=16c=1642 倍)。しかも規模が大きいほどプーリングの利得は大きい(青い曲線がどんどん下がる)。理由は単純で、独立な列では「自分の窓口は空いているのに、隣の列で客が待っている」という遊休と待ちの共存が起きるのに対し、統合すれば空いた窓口がどの客でも拾えるので、能力の取りこぼしが消えるからです。これは在庫を1拠点に集約すると安全在庫が n\sqrt{n} で減るリスクプーリング(第6章 サプライチェーン)とまったく同じ原理――ばらつきは束ねるほど相対的に小さくなるのです。

⚠️ よくある誤解

関連ノート