🎓 レベル:基礎 | 重要度:A(必須)
📎 前提:プロセス分析とリトルの法則(=フローレート) | 深掘り:第8章 制約理論・第4章 待ち行列
要点(BLUF)
- プロセスの能力は、足し算ではなく最も遅い資源=ボトルネックで決まります。工程キャパシティは各ステージのキャパシティの min。
- 資源キャパシティ = 並列サーバ数 ÷ アクティビティ時間 。サイクルタイム = 1 / 工程キャパシティ。
- 稼働率 (実スループット基準)と、インプライド稼働率 = 需要/cap(>1 ならその需要では律速)を区別します。
- 非ボトルネックをいくら速くしても は上がりません。ボトルネックにだけ手を入れると律速が移動し、工程キャパシティが上がります——これを What-if で見ます。
1. キャパシティは「min」で決まる
直列プロセスでは、すべてのフローユニットが全ステージを順に通過します。だから工程全体が出せる速さは、どんなに速いステージがあってもいちばん遅いステージに合わせられてしまいます。これがボトルネックです。
flowchart LR A["切断<br/>cap=0.50"] --> B["加工<br/>cap=0.40"] --> C["組立<br/>cap=0.50"] --> D["検査<br/>cap=0.33 (最小=律速)"] --> E["梱包<br/>cap=0.67"]
各ステージの資源キャパシティは、並列サーバ数 (人や機械の台数)を、1ユニットあたりのアクティビティ時間 で割ったものです。サーバが 台あれば 倍の速さで処理できます。
2. 数式:キャパシティ・サイクルタイム・稼働率
工程キャパシティが min になるのは、フローレート がどのステージのキャパシティも超えられない( がすべての で必要)ため、 だからです。そしてボトルネックを上流が十分に供給できれば、この上限は実際に達成できます。最大速度で流したときの出力どうしの間隔がサイクルタイム、各資源の稼働率は実スループット に対する忙しさです。
需要が与えられたとき、各資源が需要をさばけるかを表すのがインプライド稼働率です。1 を超える資源があれば、その需要水準ではそこが律速になります(実スループットは需要とキャパシティの小さい方)。
需要の歩調そのものを時間で表したのがタクトタイム(需要を満たすために1ユニットを仕上げるべき間隔)です。サイクルタイム ≤ タクトタイムなら需要を満たせます(これは「ボトルネックのインプライド稼働率 ≤ 1」と同じことです)。
3. 工程のキャパシティ分析(コード)
5ステージの工程で、各キャパシティ・ボトルネック・工程キャパシティ・サイクルタイム・各稼働率を計算します。
import numpy as np
import pandas as pd
# 各アクティビティ:1個あたり処理時間 t(分/個・1サーバ)と並列サーバ数 m
stages = pd.DataFrame({
"ステージ": ["切断", "加工", "組立", "検査", "梱包"],
"処理時間_t": [2.0, 5.0, 4.0, 3.0, 1.5], # 分/個(1サーバあたり)
"サーバ数_m": [1, 2, 2, 1, 1], # 並列数
})
# 資源キャパシティ cap_i = m_i / t_i (個/分)
stages["キャパ_個毎分"] = stages["サーバ数_m"] / stages["処理時間_t"]
cap_proc = stages["キャパ_個毎分"].min() # 工程キャパ = min(cap_i)
b_idx = stages["キャパ_個毎分"].idxmin()
bottleneck = stages.loc[b_idx, "ステージ"]
cycle_time = 1.0 / cap_proc # サイクルタイム(分/個)
# スループット R(cap_proc 以下)を与えて稼働率 rho_i = R / cap_i
R = 0.30 # 個/分
stages["稼働率_rho"] = R / stages["キャパ_個毎分"]
print(stages.to_string(index=False, float_format=lambda x: f"{x:.4f}"))
print()
print(f"工程キャパシティ = min(cap_i) = {cap_proc:.4f} 個/分")
print(f"ボトルネック = {bottleneck}")
print(f"サイクルタイム = 1/cap = {cycle_time:.4f} 分/個")
print(f"R = {R} 個/分 のとき 最大稼働率 = {stages['稼働率_rho'].max():.4f}({bottleneck})")
出力:
ステージ 処理時間_t サーバ数_m キャパ_個毎分 稼働率_rho
切断 2.0000 1 0.5000 0.6000
加工 5.0000 2 0.4000 0.7500
組立 4.0000 2 0.5000 0.6000
検査 3.0000 1 0.3333 0.9000
梱包 1.5000 1 0.6667 0.4500
工程キャパシティ = min(cap_i) = 0.3333 個/分
ボトルネック = 検査
サイクルタイム = 1/cap = 3.0000 分/個
R = 0.3 個/分 のとき 最大稼働率 = 0.9000(検査)
出力の意味:処理時間が最長なのは加工(5 分)ですが、加工はサーバが 2 台あるため cap=0.40。ボトルネックは検査(1 台・3 分 → cap=0.333)です。キャパシティは時間の長さでなく で決まることに注意してください。工程全体は **0.333 個/分(サイクルタイム 3 分/個)**しか出せません。スループット で流すと、ボトルネックの検査が 稼働率 0.90 と最も逼迫し、梱包は 0.45 と余裕——非ボトルネックには遊びがあります。
4. What-if:ボトルネックを動かす(コード+図)
需要が工程キャパシティを超えるとき、インプライド稼働率で律速資源を特定し、そこにサーバを1台足すとどうなるかを before / after で比較します。
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
# code1 と同じ工程
names = ["切断", "加工", "組立", "検査", "梱包"]
t = np.array([2.0, 5.0, 4.0, 3.0, 1.5]) # 処理時間(分/個)
m = np.array([1, 2, 2, 1, 1]) # サーバ数
def analyze(m):
cap = m / t
return cap, cap.min(), names[int(np.argmin(cap))]
demand = 0.45 # 個/分(工程キャパを超える需要を想定)
# before:現状
cap0, cap_proc0, bn0 = analyze(m)
impl0 = demand / cap0
thru0 = min(demand, cap_proc0)
# after:ボトルネックに並列サーバを1台追加
m2 = m.copy()
m2[int(np.argmin(cap0))] += 1
cap1, cap_proc1, bn1 = analyze(m2)
impl1 = demand / cap1
thru1 = min(demand, cap_proc1)
print(f"需要 = {demand} 個/分")
print("--- before(現状)---")
print(f" ボトルネック = {bn0}")
print(f" 工程キャパ = {cap_proc0:.4f} 個/分")
print(f" 最大インプライド稼働率 = {impl0.max():.4f}({bn0})")
print(f" 達成スループット = {thru0:.4f} 個/分")
print(f"--- after({bn0}に +1 サーバ)---")
print(f" ボトルネック = {bn1}")
print(f" 工程キャパ = {cap_proc1:.4f} 個/分")
print(f" 最大インプライド稼働率 = {impl1.max():.4f}({bn1})")
print(f" 達成スループット = {thru1:.4f} 個/分")
# 図:資源別キャパシティ(before/after)。ボトルネックを赤で色分け
fig, axes = plt.subplots(1, 2, figsize=(11, 4), sharey=True)
panels = [
(axes[0], cap0, cap_proc0, bn0, "before(現状)"),
(axes[1], cap1, cap_proc1, bn1, f"after({bn0}に +1 サーバ)"),
]
for ax, cap, cap_proc, bn, title in panels:
colors = ["#d62728" if nm == bn else "#1f77b4" for nm in names]
ax.bar(names, cap, color=colors)
ax.axhline(cap_proc, ls="--", color="gray", label=f"工程キャパ={cap_proc:.3f}")
ax.axhline(demand, ls=":", color="green", label=f"需要={demand}")
ax.set_title(title)
ax.legend(fontsize=9)
axes[0].set_ylabel("資源キャパシティ(個/分)")
fig.suptitle("ボトルネックは最小キャパシティの資源。改善すると律速が移動する", fontsize=12)
plt.tight_layout()
plt.show()
出力:
需要 = 0.45 個/分
--- before(現状)---
ボトルネック = 検査
工程キャパ = 0.3333 個/分
最大インプライド稼働率 = 1.3500(検査)
達成スループット = 0.3333 個/分
--- after(検査に +1 サーバ)---
ボトルネック = 加工
工程キャパ = 0.4000 個/分
最大インプライド稼働率 = 1.1250(加工)
達成スループット = 0.4000 個/分
出力の意味:需要 0.45 に対し、現状はボトルネック検査のインプライド稼働率が 1.35——需要をさばけず、実際に出せるのは工程キャパシティの 0.333 個/分どまり(タクトタイム 分/個に対しサイクルタイム 3 分/個で間に合わない)。検査に 1 台足すと検査の cap は 0.667 に跳ね上がり、今度は加工(0.40)が新しいボトルネックになります。律速が移動し、工程キャパシティは 0.333 → 0.400、達成スループットも同じく改善しました。図の赤い棒が左(検査)から右(加工)へ動き、灰色の破線(工程キャパ)が上がっているのが、ボトルネックの移動とキャパシティ増を表します。さらに増やすには、次は加工に手を入れます——改善は常に「現在のボトルネック」を追いかけるのです。
⚠️ よくある誤解
- 「どの工程を速くしてもスループットが上がる」ではない:非ボトルネックの改善は を1ミリも上げません(遊びが増えるだけ)。律速はボトルネックだけ。これを徹底するのが第8章の**制約理論(TOC)**です。
- 「キャパシティは各ステージの和」ではない:直列工程では**最小値(min)**です。足し算でも平均でもありません。並列に同じ資源を増やしたときだけ、その資源のキャパが で増えます。
- 「全資源を高稼働にするのが効率的」ではない:ばらつきがある現実の系では、稼働率を 1 に近づけると待ち時間が急増します(第4章の待ち行列で定量化)。非ボトルネックの遊びは無駄ではなく、ボトルネックを枯渇させないためのバッファでもあります。
関連ノート
- プロセス分析とリトルの法則(前提・=フローレートの定義)
- オペレーションズ・マネジメントとは(稼働率と待ちのトレードオフ)
- 第5章 生産計画(複数製品・制約下の最適配分。線形計画は機械学習テキストの最適化)/第8章 制約理論(TOC・5ステップ)/第4章 待ち行列(高稼働で待ちが急増する数理)
- オペレーションズ・マネジメント 全体目次