Mímisbrunnr知恵の泉

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

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

📎 前提:安全在庫と発注点(発注点 ROP・安全在庫 zσdLz\sigma_d\sqrt{L})・経済的発注量EOQ(発注量 Q) | 応用:第6章 サプライチェーン(在庫集約・共同発注)

要点(BLUF)

1. 2つの方式:監視のしかたが違う

両方式の違いは「何を固定し、何を動かすか」に尽きます。

定量発注方式 (Q,r)(Q,r)定期発注方式 (P,S)(P,S)
監視連続(常時)定期(TT ごと)
発注の引き金在庫ポジション r\le r時間(TT 経過)
発注量一定 QQ(≒EOQ)可変(SS まで補充)
タイミング不定期一定間隔
保護期間LLL+TL+T
向く場面重要・高額品(個別管理)多品目の共同発注・棚卸が定期
flowchart TB
  C0["定量発注方式 (Q,r):連続監視"] --> C1["在庫を常時監視"]
  C1 --> C2{"在庫ポジションが r 以下か"}
  C2 -->|"はい"| C3["定量 Q を発注(EOQ相当)"]
  C2 -->|"いいえ"| C1
  C3 --> C4["保護期間=リードタイム L"]
  P0["定期発注方式 (P,S):定期監視"] --> P1["T 日ごとに在庫を確認"]
  P1 --> P2["目標在庫 S まで発注(量は毎回変わる)"]
  P2 --> P3["保護期間=L+T(次の発注機会まで守る)"]
  P3 --> P1

2. なぜ定期は「L+T」を守るのか

ここが本稿の核心です。安全在庫は「補充が届くまで在庫が尽きないように」積むもの(安全在庫と発注点)。守るべき期間=保護期間が方式で変わります。

保護期間が LL から L+TL+T に伸びると、リードタイム需要のばらつきは σdLσdL+T\sigma_d\sqrt{L}\to\sigma_d\sqrt{L+T}。安全在庫も同じ比で増えます。

r=Lμd平均+zσdL安全在庫,S=(L+T)μd平均+zσdL+T安全在庫r=\underbrace{L\mu_d}_{\text{平均}}+\underbrace{z\,\sigma_d\sqrt{L}}_{\text{安全在庫}},\qquad S=\underbrace{(L+T)\mu_d}_{\text{平均}}+\underbrace{z\,\sigma_d\sqrt{L+T}}_{\text{安全在庫}} SS定期SS定量=zσdL+TzσdL=L+TL\frac{SS_{\text{定期}}}{SS_{\text{定量}}}=\frac{z\,\sigma_d\sqrt{L+T}}{z\,\sigma_d\sqrt{L}}=\sqrt{\frac{L+T}{L}}

安全在庫の比は需要のばらつき σd\sigma_d にもサービス水準 zz にもよらず、保護期間の比の平方根だけで決まります。これが「定期は連続より在庫が要る」ことの定量的な正体です。

3. 達成サービス・在庫・発注頻度を比べる(コード)

需要は1日平均 μd=50\mu_d=50・標準偏差 σd=12\sigma_d=12、リードタイム L=4L=4 日、定期の発注間隔 T=14T=14 日、目標 CSL=95%。発注量 Q=700Q=700(≒EOQ)にすると連続監視の発注間隔も Q/μd=14Q/\mu_d=14 日になり、発注頻度をそろえた公平比較ができます。各方式の発注サイクルを多数シミュレートして保護期間需要が発注点/目標在庫を超える割合(達成欠品率)を測り、平均在庫はサイクル在庫+安全在庫で示します。保護期間を取り違えて L\sqrt{L} のままにした「素朴な定期」も並べます。

import numpy as np
import pandas as pd
from scipy.stats import norm

mu_d, sigma_d = 50.0, 12.0      # 日次需要 N(μ_d, σ_d)
L, T = 4, 14                     # リードタイム L 日/定期発注の間隔 T 日
z = norm.ppf(0.95)               # 目標CSL=95%
Q = 700                          # 定量発注量(≒EOQ)
D = mu_d * 365                   # 年間需要

# 定量(Q,r):保護期間 L。     安全在庫 SS=z·σ_d·sqrt(L)、発注点 r=L·μ_d+SS
SS_cont = z * sigma_d * np.sqrt(L)
r_point = L * mu_d + SS_cont
# 定期(P,S):保護期間 L+T。   安全在庫 SS=z·σ_d·sqrt(L+T)、目標在庫 S=(L+T)·μ_d+SS
SS_peri = z * sigma_d * np.sqrt(L + T)
S_level = (L + T) * mu_d + SS_peri
# 素朴な定期:保護期間を取り違え sqrt(L) のまま → 安全在庫が不足
S_naive = (L + T) * mu_d + SS_cont

print(f"定量(Q,r): 保護 L={L}日,    SS=z·σ_d·sqrt(L)   = {SS_cont:.2f}, r={r_point:.2f}")
print(f"定期(P,S): 保護 L+T={L+T}日, SS=z·σ_d·sqrt(L+T) = {SS_peri:.2f}, S={S_level:.2f}")
print(f"安全在庫比 SS_定期/SS_定量 = {SS_peri / SS_cont:.4f}  (理論 sqrt((L+T)/L) = {np.sqrt((L + T) / L):.4f})")
print()

# 発注サイクルを多数シミュレート:各サイクルで保護期間の需要を合計し、
# 発注点 r(定量)/目標在庫 S(定期)を超えたら欠品 → 達成欠品率を測る
rng = np.random.default_rng(20)
n = 400000
DL = rng.normal(mu_d, sigma_d, (n, L)).sum(axis=1)         # 定量:保護 L 日の需要
DLT = rng.normal(mu_d, sigma_d, (n, L + T)).sum(axis=1)    # 定期:保護 L+T 日の需要
so_cont = np.mean(DL > r_point)
so_peri = np.mean(DLT > S_level)
so_naive = np.mean(DLT > S_naive)

# 平均在庫=サイクル在庫+安全在庫、発注頻度=年間需要/1回発注量
rows = [
    {"方策": "定量(Q,r) 保護L", "安全在庫": SS_cont,
     "平均在庫": Q / 2 + SS_cont, "発注/年": D / Q, "達成欠品率": so_cont},
    {"方策": "定期(P,S) 保護L+T", "安全在庫": SS_peri,
     "平均在庫": mu_d * T / 2 + SS_peri, "発注/年": 365 / T, "達成欠品率": so_peri},
    {"方策": "定期(素朴) √Lのまま", "安全在庫": SS_cont,
     "平均在庫": mu_d * T / 2 + SS_cont, "発注/年": 365 / T, "達成欠品率": so_naive},
]
print(pd.DataFrame(rows).to_string(index=False, float_format=lambda x: f"{x:.2f}"))
print("\n目標欠品率は 5%(CSL=95%)。定量・定期(正しいSS)はともに約5%を達成。")
print("定期は保護期間 L+T のぶん安全在庫が 2.12 倍・平均在庫も多い。")
print("√L のまま(素朴)だと保護期間を取り違え、欠品率が約22%に跳ね上がる。")

出力:

定量(Q,r): 保護 L=4日,    SS=z·σ_d·sqrt(L)   = 39.48, r=239.48
定期(P,S): 保護 L+T=18日, SS=z·σ_d·sqrt(L+T) = 83.74, S=983.74
安全在庫比 SS_定期/SS_定量 = 2.1213  (理論 sqrt((L+T)/L) = 2.1213)

           方策  安全在庫   平均在庫  発注/年  達成欠品率
  定量(Q,r) 保護L 39.48 389.48 26.07   0.05
定期(P,S) 保護L+T 83.74 433.74 26.07   0.05
 定期(素朴) √Lのまま 39.48 389.48 26.07   0.22

目標欠品率は 5%(CSL=95%)。定量・定期(正しいSS)はともに約5%を達成。
定期は保護期間 L+T のぶん安全在庫が 2.12 倍・平均在庫も多い。
√L のまま(素朴)だと保護期間を取り違え、欠品率が約22%に跳ね上がる。

出力の意味:安全在庫は定量 39.48 個に対し定期は 83.74 個と 2.12 倍——理論値 (4+14)/4=4.5=2.1213\sqrt{(4+14)/4}=\sqrt{4.5}=2.1213 にぴたり一致します。シミュレーションでは、定量も「正しい SS の定期」もともに達成欠品率 約5%(0.05)で目標 CSL=95% を満たしました。発注頻度は両方とも年26.07回でそろえてあるのに、定期は平均在庫が多い(389.48 → 433.74 個)——これが「定期は管理が楽だが在庫が増える」のコストです。圧巻は最下行の素朴な定期:保護期間が L+TL+T に伸びることを見落として L\sqrt{L} のまま(安全在庫 39.48)にすると、達成欠品率が0.22=約22%に跳ね上がります。在庫を 433.74 → 389.48 と削って一見お得に見えて、実は5サイクルに1回以上品切れ——「定期では保護期間 L+TL+T を守る」を外すと、サービスが崩壊することが数値で分かります。

4. 在庫推移を重ね描きする(コード)

最後に、両方式の在庫の動き方の違いを目で見ます。同一の擬似需要系列に対して定量 (Q,r)(Q,r) と定期 (P,S)(P,S) を日次でシミュレートし、手持ち在庫ののこぎり波を重ねます。定量は発注点 rr を割るたびに定量 QQ を不定期に、定期は**TT 日ごとに量を変えて**発注する様子に注目してください。

import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
from scipy.stats import norm

rng = np.random.default_rng(33)

mu_d, sigma_d = 50.0, 12.0
L, T = 4, 14
z = norm.ppf(0.95)
Q = 700
r_point = L * mu_d + z * sigma_d * np.sqrt(L)            # 定量の発注点
S_level = (L + T) * mu_d + z * sigma_d * np.sqrt(L + T)  # 定期の目標在庫

days = 120
demand = np.maximum(rng.normal(mu_d, sigma_d, days), 0.0)  # 同一の需要系列

def simulate(policy):
    net = S_level                          # 同じ初期在庫から開始
    pipeline = {}
    hist, orders = [], []
    for t in range(days):
        if t in pipeline:
            net += pipeline.pop(t)
        net -= demand[t]
        IP = net + sum(pipeline.values())
        if policy == "continuous":
            if IP <= r_point:              # 発注点で定量 Q を不定期に発注
                pipeline[t + L] = pipeline.get(t + L, 0.0) + Q
                orders.append(t)
        else:
            if t % T == 0:                 # T 日ごとに S まで(発注量は可変)
                q = max(S_level - IP, 0.0)
                if q > 0:
                    pipeline[t + L] = pipeline.get(t + L, 0.0) + q
                    orders.append(t)
        hist.append(max(net, 0.0))
    return np.array(hist), orders

oh_c, ord_c = simulate("continuous")
oh_p, ord_p = simulate("periodic")
print(f"定量(Q,r): 平均在庫 {oh_c.mean():.1f} 個, 発注回数 {len(ord_c)} 回(不定期・定量{Q}個)")
print(f"定期(P,S): 平均在庫 {oh_p.mean():.1f} 個, 発注回数 {len(ord_p)} 回({T}日ごと・発注量可変)")

fig, ax = plt.subplots(figsize=(11, 5.5))
ax.plot(oh_c, color="#1f77b4", lw=1.8, label="定量(Q,r) 在庫")
ax.plot(oh_p, color="#d62728", lw=1.8, label="定期(P,S) 在庫")
ax.axhline(r_point, ls="--", color="#1f77b4", alpha=0.7, label=f"発注点 r={r_point:.0f}")
ax.plot(ord_c, oh_c[ord_c], "v", color="#1f77b4", ms=8)
ax.plot(ord_p, oh_p[ord_p], "v", color="#d62728", ms=8)
for t in ord_p:
    ax.axvline(t, color="#d62728", ls=":", alpha=0.25)
ax.set_xlabel("日"); ax.set_ylabel("手持ち在庫(個)")
ax.set_title("在庫推移:定量=発注点で不定期に定量発注/定期=一定間隔で量を変えて発注")
ax.legend(loc="upper right"); plt.tight_layout(); plt.show()

出力:

定量(Q,r): 平均在庫 412.3 個, 発注回数 8 回(不定期・定量700個)
定期(P,S): 平均在庫 473.3 個, 発注回数 9 回(14日ごと・発注量可変)

出力の意味:この120日窓では定量が8回・定期が9回の発注(おおむね14日に1回でそろう)。定期のほうが平均在庫が高い(412 → 473 個)のは、第3節で見た「保護期間 L+TL+T ぶんの厚い安全在庫」を抱えるからです(窓が短いので絶対値は第3節の定常値とは少しずれますが、定期>定量の大小は一致)。図を見ると性格の違いが一目瞭然——青い定量は、在庫が発注点 rr(青い破線)を割るたびに同じ高さ(Q=700Q=700)だけ跳ね上がり、谷の深さ(残量)に応じて山の間隔が伸び縮みします(不定期・定量)。赤い定期は、赤い点線の等間隔(14日ごと)に発注し、そのときの在庫の減り具合で山の高さがまちまち(一定間隔・可変量)。「定量は山の高さが一定で間隔が可変/定期は間隔が一定で高さが可変」——これが2方式の見分け方です。

⚠️ よくある誤解

関連ノート