Mímisbrunnr知恵の泉

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

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

📎 前提:ボトルネックとキャパシティ(ボトルネック=最小キャパシティの資源が律速) | 制約の限界価値:線形計画による生産計画(シャドープライス) | 次:リーン生産・JIT・かんばん

要点(BLUF)

1. スループット会計:T・I・OE で測る

会計の常識では、製品ごとに材料費だけでなく労務費や間接費(工場の電気代・管理者の給料・減価償却)を「配賦」して原価を出し、売価との差を製品利益と呼びます。TOC はこの原価計算(コストワールド)を意思決定の物差しにすることを拒否します。理由は、間接費の多くは生産量を1個増減しても変わらない(その期の固定費)のに、配賦は「1個あたり○円」とあたかも変動費のように見せかけ、判断を誤らせるからです。

代わりに使うのが**スループット会計(スループットワールド)**の3つの量です。

指標読み定義
TTスループット販売を通じてお金を生む率 = 売上 − 真に変動する費用(資材費・外注費)。労務費は含めない
II在庫(投資)売るために購入したものに縛られたお金(原材料・仕掛・製品の購入価額)
OEOE業務費用IITT に変えるために使うお金(労務費・経費など、ほぼ期間固定)

ここで肝心なのは TT の定義です。1個売ったときのスループットは「価格 − その1個に直接かかった資材費」だけで、人件費は引きません。なぜなら工員の給料は1個多く作っても変わらない(OEOE の一部)から。利益とキャッシュは次の関係になります。

純利益=TOE=(i(pimi)xi)OE\text{純利益} = T_{\text{総}} - OE = \Bigl(\sum_i (p_i - m_i)\,x_i\Bigr) - OE

pip_i は製品 ii の価格、mim_i は資材費、xix_i は販売量。OEOE が期間固定なら、利益を最大化することは総スループット i(pimi)xi\sum_i(p_i-m_i)x_i を最大化することと同じです。この単純な置き換えが、次節の「制約1分あたりT」という意思決定基準を生みます。

2. 集中の5ステップとドラム-バッファ-ロープ

制約は1つ(または少数)なので、改善努力をそこに集中します。TOC はこれを5つの集中ステップ(five focusing steps)として手順化しました。

flowchart LR
  S1["1. 制約を特定する"] --> S2["2. 制約を徹底活用する<br/>(1秒も遊ばせない)"]
  S2 --> S3["3. 他をすべて従属させる<br/>(非制約を制約の歩調に合わせる)"]
  S3 --> S4["4. 制約を強化する<br/>(能力を足す・外注する)"]
  S4 --> S5["5. 制約が動いたら繰り返す<br/>(惰性に戻らない)"]
  S5 -. "新しい制約へ" .-> S1

この③④をラインで実装する仕組みが**ドラム-バッファ-ロープ(DBR)**です。

flowchart LR
  REL["資材投入"] --> NB1["非制約工程<br/>(速い・余力あり)"]
  NB1 --> BUF["バッファ<br/>(制約の手前に在庫を置く)"]
  BUF --> DRUM["制約=ドラム<br/>(最も遅い・常に稼働させる)"]
  DRUM --> NB2["非制約工程<br/>(速い・余力あり)"]
  NB2 --> OUT["出荷"]
  DRUM -. "ロープ:ドラムの歩調でだけ投入する" .-> REL

3. 数式:制約1分あたりスループットで順位付け

制約の能力(たとえばボトルネック工程の1週間ぶんの分)は有限です。複数製品がこの同じ希少資源を奪い合うとき、どの製品を優先すべきか。素朴な答えは「1個あたりの利益(pimip_i-m_i)が大きい製品から」ですが、これは間違いです。

正しい基準は、希少な制約時間を1分使ったときに得られるスループットで順位を付けること。

制約1分あたりTi=pimiti\text{制約1分あたりT}_i = \frac{p_i - m_i}{t_i}

tit_i は製品 ii が制約で消費する時間(分/個)。なぜこれが正しいか。制約の総時間 CC(分)を配分する問題は

maxx i(pimi)xis.t.itixiC,  0xidi\max_{x}\ \sum_i (p_i-m_i)\,x_i \quad\text{s.t.}\quad \sum_i t_i x_i \le C,\ \ 0\le x_i\le d_i

という線形計画線形計画による生産計画)で、制約が1本だけのときの最適解は「単位資源あたりの価値が高い順に、需要上限 did_i まで詰める」という貪欲法(分数ナップサック)で厳密に与えられます。そして最適でのボトルネックのシャドープライス(制約を1分広げたときの利益増)は、ちょうど最後に部分生産された「限界製品」の制約1分あたりTに等しくなります。つまり「制約1分あたりT」は TOC の優先順位基準であると同時に、LP の双対価格(制約の限界価値)そのものなのです。次のコードでこれを数値で確かめます。

4. 単位利益順 vs 制約1分あたりT順(コード)

製品A・B・Cが1つのボトルネック(週2400分)を奪い合います。**A は単位利益が最大(60円/個)だが制約を重く食う(30分/個)**ので制約1分あたりは2.0円と最低。逆に **C は単位利益が最小(30円)だが軽い(6分)**ので制約1分あたり5.0円と最高。「単位利益順」と「制約1分あたりT順」で生産を割り当て、総スループットと純利益(OEOE を引く)を比べます。最後に scipy.optimize.linprog で真の最適と突き合わせます。

import numpy as np
import pandas as pd
from scipy.optimize import linprog

# 3製品・1つのボトルネック資源(週2400分)。各製品の
#   価格・資材費 -> スループット T = 価格 - 資材費(円/個)
#   ボトルネックでの所要時間(分/個)・週次需要上限(個)
prod = pd.DataFrame({
    "製品": ["A", "B", "C"],
    "価格": [120, 95, 70],
    "資材費": [60, 50, 40],
    "制約時間_分": [30, 15, 6],
    "需要上限": [100, 100, 100],
})
prod["T_単位"] = prod["価格"] - prod["資材費"]            # 単位スループット(円/個)
prod["T_制約1分"] = prod["T_単位"] / prod["制約時間_分"]  # 制約1分あたりスループット

CAP = 2400.0   # ボトルネック能力(分/週)
OE = 5000.0    # 業務費用 OE(円/週・固定)

def allocate(df, order_col):
    """order_col の降順に貪欲にボトルネック時間を割り当て、生産量と総Tを返す。"""
    d = df.sort_values(order_col, ascending=False).copy()
    remain = CAP
    qty = []
    for _, r in d.iterrows():
        take = min(r["需要上限"], remain / r["制約時間_分"])  # 残り時間で作れる上限
        qty.append(take)
        remain -= take * r["制約時間_分"]
    d["生産量"] = qty
    d["獲得T"] = d["生産量"] * d["T_単位"]
    return d, d["獲得T"].sum()

by_profit, T_profit = allocate(prod, "T_単位")     # 単位利益が高い順に作る
by_ratio,  T_ratio  = allocate(prod, "T_制約1分")  # 制約1分あたりTが高い順に作る

print(prod[["製品", "T_単位", "制約時間_分", "T_制約1分"]].to_string(
    index=False, float_format=lambda x: f"{x:.2f}"))
print()
print("--- 方針1:単位利益(T_単位)が高い順に生産 ---")
print(by_profit[["製品", "生産量", "獲得T"]].to_string(
    index=False, float_format=lambda x: f"{x:.1f}"))
print(f"総スループット T = {T_profit:.0f} 円/週,  純利益 T-OE = {T_profit-OE:.0f} 円/週")
print()
print("--- 方針2:制約1分あたりT(T_制約1分)が高い順に生産 ---")
print(by_ratio[["製品", "生産量", "獲得T"]].to_string(
    index=False, float_format=lambda x: f"{x:.1f}"))
print(f"総スループット T = {T_ratio:.0f} 円/週,  純利益 T-OE = {T_ratio-OE:.0f} 円/週")

# LP(05-01 と同じ linprog)で真の最適を確認:max sum(T_i*x_i) s.t. sum(時間_i*x_i)<=CAP, 0<=x<=需要
c = -prod["T_単位"].to_numpy(dtype=float)
A_ub = prod["制約時間_分"].to_numpy(dtype=float).reshape(1, -1)
res = linprog(c=c, A_ub=A_ub, b_ub=[CAP],
              bounds=[(0, u) for u in prod["需要上限"]], method="highs")
print()
print(f"LP 最適スループット = {-res.fun:.0f} 円/週(制約1分あたりT順と一致)")
print(f"ボトルネックのシャドープライス = {-res.ineqlin.marginals[0]:.2f} 円/分")
print(f"  -> 限界製品A の T_制約1分 = {prod['T_制約1分'].min():.2f} 円/分 に一致")

出力:

製品  T_単位  制約時間_分  T_制約1分
 A    60      30    2.00
 B    45      15    3.00
 C    30       6    5.00

--- 方針1:単位利益(T_単位)が高い順に生産 ---
製品  生産量    獲得T
 A 80.0 4800.0
 B  0.0    0.0
 C  0.0    0.0
総スループット T = 4800 円/週,  純利益 T-OE = -200 円/週

--- 方針2:制約1分あたりT(T_制約1分)が高い順に生産 ---
製品   生産量    獲得T
 C 100.0 3000.0
 B 100.0 4500.0
 A  10.0  600.0
総スループット T = 8100 円/週,  純利益 T-OE = 3100 円/週

LP 最適スループット = 8100 円/週(制約1分あたりT順と一致)
ボトルネックのシャドープライス = 2.00 円/分
  -> 限界製品A の T_制約1分 = 2.00 円/分 に一致

出力の意味:A は単位利益が最大(60円)なので、「儲かる製品から作れ」という直感に従うと A だけを80個作って制約を使い切り、総スループット4800円・OEOE を引くと純利益 −200円の赤字です。ところが制約1分あたりTは A が最低(2.0円/分)。軽くて回転の速い C → B → A の順に詰めると、C を100個・B を100個・A を10個作れて総スループット8100円・純利益3100円の黒字。同じ制約・同じ需要なのに、順位の付け方だけで赤字と黒字が入れ替わりました。そして「制約1分あたりT順」の8100円は、linprog が出したLP 最適スループット8100円とぴたり一致。さらにボトルネックのシャドープライスは2.00円/分で、これは最後に部分生産された限界製品 A の制約1分あたりT(2.0円/分)と一致します。「制約1分あたりT」は TOC の優先順位であり、同時に 線形計画による生産計画 で見た制約の限界価値(双対価格)そのものだと数値で確認できました。

受注可否もこの物差しで決まります。新規の特注が来たとき、(a) その注文が制約を使わないなら、スループット T=T= 価格 − 資材費 >0>0 でありさえすれば受けるべき(OEOE は固定なので TT が丸ごと利益に乗る。原価計算が「配賦原価割れ」と言って断るのは誤り)。(b) その注文が制約を使うなら、注文の制約1分あたりT が、押しのける限界製品の値(ここでは A の2.0円/分=シャドープライス)を上回るときだけ受ける。たとえば制約を20分使い T=120T=120円の特注は 120/20=6.0120/20=6.0円/分 >2.0>2.0 なので、A を減らしてでも受けるべき。逆に同じ20分で T=30T=30円(1.51.5円/分 <2.0<2.0)なら断る——シャドープライスが受注のハードルレートになります。

5. 非制約を速くしても無駄・バッファが制約を守る(コード)

ボトルネックとキャパシティ で「非ボトルネックを速くしても産出は増えない」と学びました。TOC の言葉では「局所効率を上げても、制約でないところを強化(elevate)しても全体スループットは1ミリも増えない」。さらに DBR のバッファが、上流のばらつきから制約を守る効果も確かめます。段1(供給・速い μ1=1.2\mu_1=1.2)→ バッファ(容量 KK)→ 段2(ドラム=制約・遅い μ2=1.0\mu_2=1.0)の2段ラインを離散事象シミュレーションで回し、ブロッキング(バッファ満杯で段1が止まる)とスターベーション(バッファ空で段2が止まる)を陽に扱います。

import numpy as np

def sim_line(mu1, mu2, K, n, seed):
    """飽和入力の2段直列ライン。段1=供給(速い), 段2=ドラム/制約(遅い),
    その間のバッファ容量 K。ブロッキング(段1)とスターベーション(段2)を陽に扱い、
    段2の完成からスループットを測る。"""
    rng = np.random.default_rng(seed)
    S1 = rng.exponential(1.0 / mu1, n)   # 段1のサービス時間
    S2 = rng.exponential(1.0 / mu2, n)   # 段2のサービス時間
    s2 = np.zeros(n)   # 段2が部品 i の加工を始める時刻
    c2 = np.zeros(n)   # 段2が部品 i を完成(退去)させる時刻
    prev_e1 = 0.0      # 段1が直前の部品を払い出した時刻 e1[i-1]
    for i in range(n):
        c1 = prev_e1 + S1[i]                       # 段1が部品 i を作り終える
        prev_c2 = c2[i - 1] if i >= 1 else 0.0     # 段2が直前を終えた時刻
        if K == 0:                                  # バッファ無し=直送(段1はブロック)
            ei = max(c1, prev_c2)
            si = ei
        else:                                       # バッファ K:i-K を段2が始めれば空く
            room = s2[i - K] if i - K >= 0 else 0.0
            ei = max(c1, room)
            si = max(prev_c2, ei)
        s2[i] = si
        c2[i] = si + S2[i]
        prev_e1 = ei
    warm = n // 10                                  # 立ち上がりを捨てる
    thru = (n - warm) / (c2[-1] - c2[warm - 1])     # スループット(個/時)
    return thru

mu1, mu2 = 1.2, 1.0   # 段1(非ボトルネック・速い), 段2(ドラム=制約・遅い)
n = 200_000

print(f"段1(供給)率 mu1={mu1}, 段2(ドラム=制約)率 mu2={mu2}")
print("無限バッファなら スループット = 制約 mu2 = 1.000(段2が律速)")
print()
print("バッファK   スループット   制約能力に対する達成率")
for K in [0, 1, 2, 5, 10, 30]:
    thru = sim_line(mu1, mu2, K, n, seed=42)
    print(f"{K:6d}   {thru:11.4f}   {thru/mu2*100:8.1f}%")

print()
print("=== 集中の第4ステップ「制約を強化」:どこを速くすべきか(K=30 で比較)===")
base = sim_line(1.2, 1.0, 30, n, seed=42)
nonbn = sim_line(2.0, 1.0, 30, n, seed=42)   # 非ボトルネック(段1)を 1.2->2.0 に強化
bn = sim_line(1.2, 1.15, 30, n, seed=42)     # ボトルネック(段2)を 1.0->1.15 に強化
print(f"現状           (mu1=1.2, mu2=1.00): スループット {base:.4f}")
print(f"非制約を強化   (mu1=2.0, mu2=1.00): スループット {nonbn:.4f}  (ほぼ不変)")
print(f"制約を強化     (mu1=1.2, mu2=1.15): スループット {bn:.4f}  (上がる)")

出力:

段1(供給)率 mu1=1.2, 段2(ドラム=制約)率 mu2=1.0
無限バッファなら スループット = 制約 mu2 = 1.000(段2が律速)

バッファK   スループット   制約能力に対する達成率
     0        0.7265       72.6%
     1        0.8152       81.5%
     2        0.8674       86.7%
     5        0.9408       94.1%
    10        0.9815       98.1%
    30        1.0018      100.2%

=== 集中の第4ステップ「制約を強化」:どこを速くすべきか(K=30 で比較)===
現状           (mu1=1.2, mu2=1.00): スループット 1.0018
非制約を強化   (mu1=2.0, mu2=1.00): スループット 1.0024  (ほぼ不変)
制約を強化     (mu1=1.2, mu2=1.15): スループット 1.1358  (上がる)

出力の意味:まずバッファの効果。制約(段2・能力1.0)の手前にバッファが無い(K=0)と、スループットは0.7265=制約能力の72.6%しか出ません。段1がたまたま遅れると段2が即スターベーション(手待ち)になり、止まった時間は二度と取り戻せないからです。バッファを K=1,2,5,10K=1,2,5,10 と増やすと81.5% → 86.7% → 94.1% → 98.1% と回復し、K=30K=30ほぼ100%(1.0018、0.2%の超過は有限長シミュの誤差で、漸近的な上限は制約能力1.000)。制約の手前のわずかな在庫が、ばらつきによる枯渇を吸収して産出を守る——これが DBR のバッファです。次にどこを強化すべきか。バッファ十分(K=30K=30)の状態から、非制約の段1を 1.22.01.2\to2.0 と大幅に速くしても、スループットは 1.00181.00241.0018\to1.0024 とほぼ不変。律速は段2のままだからです。一方制約の段2を 1.01.151.0\to1.15(15%)速くすると、スループットは 1.00181.13581.0018\to1.1358 へ素直に上がります改善のリターンは制約に投資したときだけ得られる。「全工程をまんべんなく効率化」は、制約以外への投資ぶんがそっくり無駄になるのです。

⚠️ よくある誤解

関連ノート