🎓 レベル:応用 | 重要度:A(必須)
📎 前提:M/M/1 待ち行列モデル(birth-death・定常分布 ・)・待ち行列の基礎とリトルの法則(稼働率 ρ・L=λW) | 確率の土台:確率過程(マルコフ連鎖・ポアソン過程) | 次:第5章 生産計画/第6章 リスクプーリング
要点(BLUF)
- M/M/c はポアソン到着・指数サービスで窓口を 個並べた待ち行列(共通の1列に並び、空いた窓口へ進む)。銀行・コールセンター・レジの基本形です。
- 個の窓口だとサービス率が状態依存になります——系内 人なら稼働中の窓口は 個なので、下り(サービス完了)の率は 。これを birth-death で解くと定常分布が二段( と )になります。
- 客が待たされる確率がアーランC公式 ( が提供負荷、)。ここから 、。要員設計は「目標サービス水準を満たす最小の 」を探す問題になります。
- プーリング効果:同じ稼働率でも、客と窓口を1つのプールに統合(1つの M/M/c)したほうが、別々の列( 個の独立 M/M/1)より待ちが大幅に短い。空いた窓口が誰の客でも取れるからで、これは第6章のリスクプーリング(在庫集約)と同じ原理です。
1. M/M/c:状態依存サービス率の birth-death
M/M/1 待ち行列モデル の M/M/1 を窓口 個に一般化します。客は共通の1列に並び、空いた窓口へ順に進みます(各窓口のサービス率は同じ )。系内数 はやはり birth-death 過程ですが、下りの率が状態で変わるのが新しい点です。
系内に 人いるとき、実際に動いている窓口は 個( が 未満なら 個、 以上なら全 個が稼働)。1個あたり完了率 なので、サービス完了の合計率(下り)は 。上り(到着)はどの状態でも のままです。
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
提供負荷を (単位アーラン=平均して何個分の窓口が仕事で埋まるか)、窓口あたり稼働率を とおきます。安定条件は ()。流量の釣り合い を解くと、定常分布は2つの領域に分かれます。
では下りの率が と増えていくので となり 型(ポアソン型)、全窓口が埋まる では下りが で頭打ちになり の幾何型(M/M/1 と同じ尻尾)です。 は規格化 で決まります。
2. アーランC公式と性能指標
新規客が待たされるのは「到着時に全窓口が埋まっている」= のとき。その確率がアーランC公式です。 の幾何尻尾を で足し上げて規格化すると、
待っている客の平均数 は、待ち領域 の超過人数 の期待値で、幾何分布の平均(M/M/1 待ち行列モデル の )を使うと の簡潔な倍になります。
( は にリトルの法則 を当て、 を入れて整理すると分母が になります。)系内時間とその他は M/M/1 同様 、 で出ます。待たされる確率 さえ計算できれば、 は機械的です。
3. アーランCで要員設計:目標を満たす最小窓口数(コード)
を実装し、コールセンターの要員設計をします。到着 件/時、平均処理 6 分( 件/時/人)で提供負荷 アーラン。窓口 を増やすと待ち確率・平均待ち時間・稼働率がどう動くかを表にし、「平均待ち 分」「待つ確率 」という2つの目標それぞれを満たす最小の を探します。
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)
出力の意味:提供負荷 なので が最小限の窓口数ですが、そのときは稼働率 0.80・待つ確率 55%・平均待ち 3.3 分とかなり混みます。窓口を1個ずつ足すと効果は劇的で、 で待つ確率 28%・平均待ち 0.85 分、 で待つ確率 6%まで下がります。注目は2つの目標で必要な窓口数が違うこと——「平均待ち 分」は で足りる(0.85分)のに、「待つ確率 」という厳しい約束は 必要( の 5.9% では届かない)。どのサービス水準を顧客に約束するかで要員数が変わるわけです。また で待ち確率が と下がる一方、改善幅は1窓口あたり逓減しており( では 0.024→0.009 とわずか)、過剰な窓口は費用対効果が落ちます。
4. プーリング効果:統合すると待ちが激減する(コード)
待ち行列設計で最も反直感的で強力なのがプーリング効果です。同じ総負荷・同じ稼働率でも、「 個の独立した列(それぞれ M/M/1)」より「客も窓口も1つに統合した M/M/c」のほうが待ちが圧倒的に短い。各窓口 /分・稼働率 をそろえて、規模 を変えながら両者の を比べます。
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
出力の意味:稼働率はどの行も で完全に同じ――それでも待ち時間は大違いです。客を別々の列に並ばせる独立 M/M/1 は、規模によらず常に 分。ところが同じ客と窓口を1つのプールに統合すると、 で 1.78 分、 で 0.75 分、 では 0.10 分 まで縮みます(削減率は で 5.4 倍、 で 42 倍)。しかも規模が大きいほどプーリングの利得は大きい(青い曲線がどんどん下がる)。理由は単純で、独立な列では「自分の窓口は空いているのに、隣の列で客が待っている」という遊休と待ちの共存が起きるのに対し、統合すれば空いた窓口がどの客でも拾えるので、能力の取りこぼしが消えるからです。これは在庫を1拠点に集約すると安全在庫が で減るリスクプーリング(第6章 サプライチェーン)とまったく同じ原理――ばらつきは束ねるほど相対的に小さくなるのです。
⚠️ よくある誤解
- 「窓口を増やすほど効果は一定」ではない:待ち確率・待ち時間の改善は逓減します(第3節: は大きいが は小さい)。要員は人件費と待ちコストの綱引きで、目標サービス水準を満たす最小の 付近が費用対効果のよい設計です。やみくもな増員は無駄になります。
- 「プールするほど良いから常に統合せよ」ではない:プーリング効果は強力ですが、現実には**切替・移動の時間、スキルの違い(誰でも全業務をこなせない)、心理(1本の長い列は不安)**などの制約があります。専門特化(待ち行列を分ける)が処理率 を上げてプーリング損を上回ることもあり、統合と特化はトレードオフです(要最新確認)。
- 「アーランC は呼損も扱える」ではない:アーランC(M/M/c)は待ち行列が無限に伸びられる前提(待たされた客は帰らず並ぶ・FIFO)。電話回線のように**満杯なら接続を断る(呼損)**系は、待ち行列のない M/M/c/c=アーランB公式で別に扱います。C は「待たせる」系、B は「あふれたら捨てる」系で、目的関数も式も別物です。混同しないこと。
- 「稼働率が同じなら待ちも同じ」ではない:第4節の通り、同じ でも窓口の構成(独立 列か統合 1 プールか、 がいくつか)で待ちは桁違いです。稼働率は混雑の一面にすぎず、待ち時間は と構成に強く依存します。
関連ノート
- M/M/1 待ち行列モデル(前提・ の特殊形。birth-death と の導出法)
- 待ち行列の基礎とリトルの法則(稼働率 ρ・・ から )
- ボトルネックとキャパシティ(窓口数=サーバ能力の設計・稼働率との関係)
- 第6章 サプライチェーン(リスクプーリング:在庫集約で安全在庫が 減。プーリング効果と同一原理)
- 確率過程(マルコフ連鎖・ポアソン過程)(統計・連続時間マルコフ/birth-death の確率的土台)
- オペレーションズ・マネジメント 全体目次