Mímisbrunnr知恵の泉

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

🎓 レベル:標準 | 重要度:A(必須)

📎 前提:オペレーションズ・マネジメントとは(L=λW の導入・稼働率100%の危険)・プロセス分析とリトルの法則(I=R·T の面積論) | 確率の土台:確率過程(マルコフ連鎖・ポアソン過程)(ポアソン到着・指数サービス・無記憶性) | 次:M/M/1 待ち行列モデル

要点(BLUF)

1. ケンドール記法と性能指標

到着もサービスもばらつくのに窓口は限られている――この三すくみが待ち行列を生みます。世界中の待ち行列を共通の型で表すのがケンドール記法で、A/S/c の3要素(必要なら /K/N/D を足す)で書きます。

記号位置意味代表的な値
A到着過程M(ポアソン到着=到着間隔が指数)・D(一定)・G(一般)
Sサービス時間分布M(指数)・D(一定)・G(一般)
c窓口(サーバ)数1, 2, 3, …

ここで M は Markovian(無記憶) の頭文字です。到着がポアソン過程(到着間隔が指数分布)、サービス時間が指数分布だと、過去を忘れる無記憶性のおかげで「系内数」だけで将来が決まるマルコフ過程になり、解析が一気に解けます。この「なぜ指数・なぜポアソンか」の確率的土台は 確率過程(マルコフ連鎖・ポアソン過程) に譲り(到着間隔=指数・無記憶性・到着数=ポアソンの導出はそこにあります)、本章はそれを待ち行列の性能設計に使います。本ノートは記法と指標、次の M/M/1 待ち行列モデル で M/M/1 を解き切ります。

flowchart LR
  ARR["到着<br/>(率 λ・ランダム)"] --> Q["待ち行列<br/>(待っている Lq 人)"]
  Q --> SRV["窓口(c 個・率 μ)<br/>サービス中 ρ·c 人"]
  SRV --> OUT["退去"]

測りたい性能は4つです。系全体(待ち+サービス中)で数えるか、待っている部分だけで数えるかで、**数(L 系)と時間(W 系)**がそれぞれ2つに分かれます。

指標読み何を数える/測るか
LL系内数いま系の中にいる平均人数(待ち+サービス中)
LqL_q待ち行列内の数待っているだけの平均人数(サービス中は除く)
WW系内時間並んでからサービスを終えて出るまでの平均時間
WqW_q待ち時間並んでからサービスが始まるまでの平均時間
ρ\rho稼働率窓口が塞がっている割合 =λcμ=\dfrac{\lambda}{c\mu}

稼働率 ρ\rho は「持ち込まれる仕事量 ÷ さばける能力」です。到着率 λ\lambda(単位時間に来る数)を、総サービス能力 cμc\mu(窓口 cc 個 × 1個あたり処理率 μ\mu)で割ります。

ρ=λcμ\rho=\frac{\lambda}{c\mu}

ρ=0.8\rho=0.8 なら「窓口は平均して8割の時間ふさがっている」。直感に反しますが、この ρ\rho が 1 に近いほど待ちは穏やかに増えるのではなく急騰します(第4節)。

2. リトルの法則を待ち行列に適用する

プロセス分析とリトルの法則 で、定常なら分布によらず I=RTI=R\,T(在庫=フローレート×フロータイム)が成り立つことを面積論で導きました。待ち行列の言葉に置き換えれば、これは L=λWL=\lambda W そのものです。重要なのは、「系」をどこに引くかを変えると、別々の指標の関係式が芋づる式に出ることです。

**系全体(窓口まで含む)**にリトルの法則を当てると、平均人数 LL =到着率 λ\lambda ×平均滞在 WW

L=λWL=\lambda W

待ち行列の部分だけ(窓口の手前まで)を「系」とみなすと、そこにいる平均人数 LqL_q =到着率 λ\lambda ×そこでの平均滞在 WqW_q

Lq=λWqL_q=\lambda W_q

同じ法則を引く線を変えて2回使っただけです。さらに、系内時間 WW は「待ち行列で待つ時間 WqW_q」+「サービスを受ける時間」に分かれます。サービス時間は平均 1/μ1/\mu(サービス率 μ\mu の逆数)なので、

W=Wq+1μW=W_q+\frac{1}{\mu}

この一本があれば、WqW_qWW は自由に行き来できます。両辺に λ\lambda を掛ければ L=Lq+λ/μ=Lq+ρL=L_q+\lambda/\mu=L_q+\rhoc=1c=1 のとき)――系内数と待ち人数の差は、サービス中の平均人数 ρ\rho、という関係も出ます。

安定条件 ρ<1\rho<1:到着が能力を超える(λ>cμ\lambda>c\mu、すなわち ρ>1\rho>1)と、行列は時間とともに無限に伸び、定常状態が存在しません。ρ<1\rho<1 ではじめて L,WL,W が有限に収まります。ρ=1\rho=1 ちょうどは「平均では釣り合うが、ばらつきのせいで行列が発散する」境界で、ここも不安定です。

リトルの法則は分布によらない恒等式プロセス分析とリトルの法則 で実証済み)なので、上の L=λWL=\lambda WLq=λWqL_q=\lambda W_q は M/M/1 に限らずあらゆる安定な待ち行列で成立します。各指標の絶対値LL がいくつか)を分布から求めるのが次章 M/M/1 待ち行列モデル の仕事です。

3. 離散事象シミュレーションで L=λW を確かめる(コード)

理屈だけでは腑に落ちません。単一窓口(c=1)に指数到着・指数サービスを流す離散事象シミュレーションを回し、L,Lq,W,WqL,L_q,W,W_q を別々に測って、L=λWL=\lambda WLq=λWqL_q=\lambda W_qW=Wq+1/μW=W_q+1/\mu が本当に成り立つかを確かめます。

サービス開始と退去はLindley 再帰で決まります。客 ii の到着を AiA_i、サービス時間を SiS_i、退去を DiD_i とすると、窓口が空くのは「自分が着いた時刻」か「前の客が出た時刻」の遅いほう。だから

Di=max(Ai,Di1)+Si,Wq,i=max(Ai,Di1)AiD_i=\max(A_i,\,D_{i-1})+S_i,\qquad W_{q,i}=\max(A_i,\,D_{i-1})-A_i

です(max(Ai,Di1)\max(A_i,D_{i-1}) がサービス開始時刻)。系内数 LL は、到着で +1・退去で −1 する階段関数 n(t)n(t)時間平均として直接測ります(プロセス分析とリトルの法則 と同じ面積法)。

import numpy as np

rng = np.random.default_rng(42)

lam = 0.8   # 平均到着率(人/分):到着間隔 ~ Exp(1/λ)
mu = 1.0    # 平均サービス率(人/分):サービス時間 ~ Exp(1/μ)
N = 2_000_000  # 客数(十分長く回す)

# 指数到着・指数サービスを生成(M/M/1)
inter = rng.exponential(1.0 / lam, N)   # 到着間隔
arrivals = np.cumsum(inter)             # 到着時刻 A_i
service = rng.exponential(1.0 / mu, N)  # サービス時間 S_i

# Lindley 再帰 D_i = max(A_i, D_{i-1}) + S_i を厳密にベクトル化
# D_i = cumS_i + cummax(A_i - cumS_{i-1})  (cumS_0=0)
cumS = np.cumsum(service)
cumS_prev = cumS - service                       # = cumS_{i-1}
dep = cumS + np.maximum.accumulate(arrivals - cumS_prev)  # 退去時刻 D_i
start = dep - service                            # サービス開始 = max(A_i, D_{i-1})

wait_q = start - arrivals      # 待ち行列での待ち時間 Wq_i
sojourn = dep - arrivals       # 系内時間 W_i = Wq_i + S_i

# --- 客平均(W, Wq)---
W_sim = sojourn.mean()
Wq_sim = wait_q.mean()

# --- 時間平均(L, Lq):系内数 n(t) の階段関数を積分 ---
def time_average(up, down):
    """up で +1, down で -1 する階段関数の時間平均を返す。"""
    t = np.concatenate([up, down])
    d = np.concatenate([np.ones(up.size), -np.ones(down.size)])
    order = np.argsort(t, kind="mergesort")
    t, d = t[order], d[order]
    level = np.cumsum(d)
    dt = np.diff(t)
    horizon = t[-1] - t[0]
    return float(np.sum(level[:-1] * dt) / horizon), horizon

# 系内数 L:到着で +1、退去で -1
L_sim, horizon = time_average(arrivals, dep)
# 待ち行列内 Lq:到着で +1、サービス開始で -1(サービス中の1人を除く)
Lq_sim, _ = time_average(arrivals, start)

lam_meas = N / horizon   # 実測の到着率(≈ λ)

# --- 理論 M/M/1 ---
rho = lam / mu
L_th = rho / (1 - rho)
Lq_th = rho**2 / (1 - rho)
W_th = 1 / (mu - lam)
Wq_th = rho / (mu - lam)

print(f"lambda={lam}, mu={mu}, rho=lambda/mu={rho:.3f}, N={N}")
print(f"実測到着率 lambda_meas = N/horizon = {lam_meas:.4f}")
print()
print("指標     シミュレーション   理論M/M/1")
print(f"L  (系内数)    {L_sim:8.4f}      {L_th:8.4f}")
print(f"Lq (待ち人数)  {Lq_sim:8.4f}      {Lq_th:8.4f}")
print(f"W  (系内時間)  {W_sim:8.4f}      {W_th:8.4f}")
print(f"Wq (待ち時間)  {Wq_sim:8.4f}      {Wq_th:8.4f}")
print()
print("リトルの法則・関係式の検算(シミュレーション値で)")
print(f"  L  vs lambda*W   : {L_sim:.4f}  vs {lam_meas * W_sim:.4f}")
print(f"  Lq vs lambda*Wq  : {Lq_sim:.4f}  vs {lam_meas * Wq_sim:.4f}")
print(f"  W  vs Wq + 1/mu  : {W_sim:.4f}  vs {Wq_sim + 1.0 / mu:.4f}")

出力:

lambda=0.8, mu=1.0, rho=lambda/mu=0.800, N=2000000
実測到着率 lambda_meas = N/horizon = 0.8005

指標     シミュレーション   理論M/M/1
L  (系内数)      3.9807        4.0000
Lq (待ち人数)    3.1805        3.2000
W  (系内時間)    4.9726        5.0000
Wq (待ち時間)    3.9731        4.0000

リトルの法則・関係式の検算(シミュレーション値で)
  L  vs lambda*W   : 3.9807  vs 3.9807
  Lq vs lambda*Wq  : 3.1805  vs 3.1805
  W  vs Wq + 1/mu  : 4.9726  vs 4.9731

出力の意味:到着もサービスもいっさい理論式を使わず、ただ指数乱数を流して測っただけの値が、理論 M/M/1(ρ=0.8\rho=0.8L=4, Lq=3.2, W=5, Wq=4L=4,\ L_q=3.2,\ W=5,\ W_q=4)と小数第2位までほぼ一致しました(残差は有限長のシミュレーション誤差)。注目すべきは下段の検算で、LLλW\lambda W3.9807 で完全一致LqL_qλWq\lambda W_q3.1805 で完全一致しています。これは偶然ではなく、観測窓を「最後の退去まで」に取ると面積 ∫n(t)dt が全客の系内時間の総和にちょうど等しくなるためで、リトルの法則は恒等式だという プロセス分析とリトルの法則 の主張が待ち行列でも厳密に効いていることの現れです。WWWq+1/μW_q+1/\mu も 4.97 でそろい、「系内時間=待ち時間+サービス時間」の分解も確認できました。

4. 稼働率を上げると待ちが爆発する(コード)

オペレーションズ・マネジメントとは で「稼働率100%は危険」と予告しました。なぜ危険かを M/M/1 の理論値で定量化します。サービス率 μ=1\mu=1 に固定し、到着率 λ\lambda を上げて稼働率 ρ=λ/μ\rho=\lambda/\mu0.50.990.5\to0.99 と動かすと、待ち時間 Wq=ρ/(μλ)W_q=\rho/(\mu-\lambda) がどう増えるかを見ます。

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

# サービス率 mu=1 に固定し、到着率 lambda を上げて稼働率 rho=lambda/mu を 0.5→0.99 に
# M/M/1 の理論値で待ちが非線形に爆発する様子を表・図にする
mu = 1.0
rhos = np.array([0.50, 0.70, 0.80, 0.90, 0.95, 0.98, 0.99])

rows = []
for rho in rhos:
    lam = rho * mu
    Lq = rho**2 / (1 - rho)       # 待ち行列内の平均人数
    Wq = rho / (mu - lam)         # 平均待ち時間
    L = rho / (1 - rho)           # 系内の平均人数
    rows.append({"稼働率rho": rho, "Lq(待ち人数)": Lq, "Wq(待ち時間)": Wq, "L(系内数)": L})

df = pd.DataFrame(rows)
print(df.to_string(index=False, float_format=lambda x: f"{x:.3f}"))

# 稼働率を 0.9→0.95→0.99 と上げると Wq がどれだけ増えるか(倍率)
base = 0.90
Wq90 = base / (mu - base)
for rho in [0.95, 0.98, 0.99]:
    Wq = rho / (mu - rho)
    print(f"rho 0.90 -> {rho:.2f}: Wq {Wq90:.2f} -> {Wq:.2f}{Wq / Wq90:.1f} 倍)")

# 図:rho に対する Wq の 1/(1-rho) 爆発
rr = np.linspace(0.01, 0.985, 400)
Wq_curve = rr / (mu - rr)
plt.figure(figsize=(9, 5.5))
plt.plot(rr, Wq_curve, color="#d62728", lw=2, label=r"$W_q=\rho/(\mu-\lambda)$")
plt.scatter(rhos, rhos / (mu - rhos), color="#1f77b4", zorder=5, label="表の各点")
for rho in rhos:
    plt.annotate(f"{rho:.2f}", (rho, rho / (mu - rho)),
                 textcoords="offset points", xytext=(4, 6), fontsize=9)
plt.axvline(1.0, color="gray", ls="--", label=r"$\rho=1$(発散)")
plt.xlabel("稼働率 ρ=λ/μ"); plt.ylabel("平均待ち時間 Wq(分)")
plt.title("稼働率を上げると待ち時間が非線形に爆発する(M/M/1, μ=1)")
plt.ylim(0, 60); plt.legend(); plt.tight_layout()
plt.show()

出力:

 稼働率rho  Lq(待ち人数)  Wq(待ち時間)  L(系内数)
  0.500     0.500     1.000   1.000
  0.700     1.633     2.333   2.333
  0.800     3.200     4.000   4.000
  0.900     8.100     9.000   9.000
  0.950    18.050    19.000  19.000
  0.980    48.020    49.000  49.000
  0.990    98.010    99.000  99.000
rho 0.90 -> 0.95: Wq 9.00 -> 19.00  (2.1 倍)
rho 0.90 -> 0.98: Wq 9.00 -> 49.00  (5.4 倍)
rho 0.90 -> 0.99: Wq 9.00 -> 99.00  (11.0 倍)

出力の意味:稼働率を 0.50.80.5\to0.8 と上げる間、待ち時間 WqW_q1.04.01.0\to4.0 とおとなしい増え方です。ところが 0.90.9 を超えてからが本番で、0.90.950.9\to0.95 と稼働率をたった5ポイント上げただけで WqW_q は 9 分から 19 分へ約2.1倍0.990.99 では 99 分にまで膨れ上がります。稼働率の差は「0.900.900.990.99 で9ポイント」しかないのに、待ちは11倍。原因は Wq=ρ/(μλ)W_q=\rho/(\mu-\lambda) の分母 μλ=μ(1ρ)\mu-\lambda=\mu(1-\rho) で、ρ1\rho\to1 で分母が 0 に近づくため、待ちが 1/(1ρ)1/(1-\rho) で発散するからです(赤い曲線が ρ=1\rho=1 の縦線で天井知らずに跳ね上がる)。だから現場の稼働率を 95% や 99% に「効率化」すると、ばらつきがある限り待ち時間(=リードタイム・顧客満足・仕掛在庫)が崩壊します。余力(バッファ)を残すことには、待ちを抑えるという明確な数理的価値がある――これが「稼働率100%は危険」の中身です。

⚠️ よくある誤解

関連ノート