100本ノック / シミュレーション / シミュレーション100本ノック
離散イベントシミュレーション入門|Python・SimPyで製造ラインを改善
変種変量工場の流れを止めないための離散イベントシミュレーション
No.041〜No.050:受注から完成までを仮想工場で再現する
本記事では、架空の精密部品工場を題材に、**離散イベントシミュレーション(DES)**の基本構造から、生産ライン、工程、ジョブショップ、フローショップの比較までを一貫して扱います。平均加工時間だけでは見落としやすい「待ち」「競合」「順序」の影響を、実行可能なPythonコードで可視化し、納期・仕掛品・設備投資の判断材料へ変換します。
[!NOTE] 本資料は、数理工房 (もしくは代表である和山個人) が過去に企業研修において使用した notebook を企業様の許可を得て再構成・編集のうえ公開しています。
掲載データはすべて架空のものであり、実在する企業・工場・数値とは一切関係ありません。
はじめに:この記事で扱う製造業の実務課題
対象工場は複数品種の金属部品を受注生産し、切削・研削・検査を経て出荷します。現場では設備稼働率が高い一方、納期遅延と仕掛品が増えています。本記事の問いは、どこへ能力を足し、どの順序で仕事を流せば、納期を安定させられるかです。
DESでは、注文到着、加工開始、加工完了、故障など、状態が変わる瞬間だけ時刻を進めます。これにより、秒刻みで全設備を計算せず、現実の競合と待ちを再現できます。
現場でよくある状況
- 月次平均では能力に余裕があるのに、特定の時間帯だけ滞留する
- 稼働率を上げた結果、仕掛品とリードタイムが増える
- 品種ごとの工程順序が違い、表計算だけでは設備競合を追えない
- 設備増強案を試したいが、実ラインでの実験は高コストである
なぜこの問題は判断が難しいのか
加工時間のばらつき、同時到着、設備の排他利用、優先順位が相互作用するためです。平均値だけで作る静的な能力計算は、「いつ混むか」と「誰が待つか」を表現できません。DESはこの時間依存性をモデル化します。ただし、入力分布や運用ルールが不正確なら結果も不正確になるため、感度分析と現場検証が不可欠です。
今回扱うノックの全体像
| No. | テーマ | 実務で得る視点 |
|---|---|---|
| 041 | DESとは何か | 状態変化だけを追うモデル |
| 042 | イベントリスト | 時系列ログによる説明可能性 |
| 043 | Future Event List | 次に起こる事象の管理 |
| 044 | イベントスケジューリング法 | イベント中心の実装 |
| 045 | プロセスインタラクション法 | 製品の流れ中心の実装 |
| 046 | SimPy入門 | 資源競合を簡潔に表現 |
| 047 | 生産ライン | 能力変更のWhat-if比較 |
| 048 | 工程シミュレーション | 滞留と稼働率の診断 |
| 049 | ジョブショップ | 品種別ルートの競合 |
| 050 | フローショップ | 投入順序とメイクスパン |
Python環境の準備
乱数生成器は再現性のため固定します。以下では外部データを使わず、numpy、pandas、matplotlib、simpyだけを利用します。
import sys
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import simpy
from IPython.display import display
SEED = 42
np.random.seed(SEED)
pd.set_option("display.max_columns", 20)
print(f"Python {sys.version.split()[0]}")
print(f"numpy {np.__version__}, pandas {pd.__version__}, matplotlib {matplotlib.__version__}, simpy {simpy.__version__}")
Python 3.13.1
numpy 2.5.1, pandas 3.0.3, matplotlib 3.11.0, simpy 4.1.2
架空データの作成
3品種について、工程ごとの標準加工時間と1日分の受注を生成します。単位は分です。到着間隔と加工時間は実際には実績データから分布を推定しますが、ここでは説明のため既知の分布を置きます。
rng = np.random.default_rng(SEED)
routing = pd.DataFrame({
"product": ["A", "B", "C"],
"route": [["Cut", "Grind", "Inspect"], ["Cut", "Inspect"], ["Grind", "Cut", "Inspect"]],
"Cut": [18, 24, 15], "Grind": [14, 0, 22], "Inspect": [8, 10, 12]
})
arrival = np.cumsum(rng.exponential(18, size=24))
orders = pd.DataFrame({
"job": [f"J{i:02d}" for i in range(1, 25)],
"product": rng.choice(["A", "B", "C"], 24, p=[0.45, 0.35, 0.20]),
"arrival": arrival.round(1),
"due": (arrival + rng.integers(90, 181, 24)).round(1),
})
display(routing)
display(orders.head(8))
| product | route | Cut | Grind | Inspect | |
|---|---|---|---|---|---|
| 0 | A | [Cut, Grind, Inspect] | 18 | 14 | 8 |
| 1 | B | [Cut, Inspect] | 24 | 0 | 10 |
| 2 | C | [Grind, Cut, Inspect] | 15 | 22 | 12 |
| job | product | arrival | due | |
|---|---|---|---|---|
| 0 | J01 | B | 43.3 | 154.3 |
| 1 | J02 | A | 85.3 | 237.3 |
| 2 | J03 | B | 128.3 | 275.3 |
| 3 | J04 | A | 133.3 | 235.3 |
| 4 | J05 | A | 134.8 | 299.8 |
| 5 | J06 | B | 161.0 | 269.0 |
| 6 | J07 | B | 186.4 | 349.4 |
| 7 | J08 | C | 242.6 | 332.6 |
No.041:DESとは何か
実務での意味
設備や製品の状態が変わる時点だけを追跡すると、待ち時間や仕掛品の増減を計算量を抑えて再現できます。設備能力・投入間隔・運用ルールを変更する仮想実験の土台です。
分析・モデル化の考え方
システム状態を 、イベント時刻を とすると、状態はイベント間で一定で、 と更新されます。ここでは1台設備への3ジョブ到着を手計算可能な最小モデルで確認します。
Pythonで確認する
jobs = pd.DataFrame({"job": ["J1", "J2", "J3"], "arrival": [0, 4, 9], "process": [7, 5, 6]})
available = 0
rows = []
for r in jobs.itertuples():
start = max(r.arrival, available); finish = start + r.process
rows.append([r.job, r.arrival, start, finish, start-r.arrival])
available = finish
des_basic = pd.DataFrame(rows, columns=["job", "arrival", "start", "finish", "wait"])
display(des_basic)
| job | arrival | start | finish | wait | |
|---|---|---|---|---|---|
| 0 | J1 | 0 | 0 | 7 | 0 |
| 1 | J2 | 4 | 7 | 12 | 3 |
| 2 | J3 | 9 | 12 | 18 | 3 |
結果の読み取り
J2は設備が空く7分まで3分待ちます。平均処理時間だけでなく、到着と設備解放の時刻関係が待ち時間を決めることが分かります。
No.042:イベントリスト
実務での意味
イベントログは、納期遅延がどの工程・時刻で発生したかを説明する監査証跡です。現場ヒアリングや実績ログとの突合にも使えます。
分析・モデル化の考え方
イベントは最低限、時刻、対象、種別、状態変化を持たせます。開始・終了を対にして記録すれば、設備占有時間、待ち時間、仕掛品推移を後から再集計できます。
Pythonで確認する
event_log = []
for r in des_basic.itertuples():
event_log += [(r.arrival, r.job, "ARRIVAL"), (r.start, r.job, "START"), (r.finish, r.job, "FINISH")]
event_log = pd.DataFrame(event_log, columns=["time", "job", "event"]).sort_values(["time", "event"]).reset_index(drop=True)
display(event_log)
| time | job | event | |
|---|---|---|---|
| 0 | 0 | J1 | ARRIVAL |
| 1 | 0 | J1 | START |
| 2 | 4 | J2 | ARRIVAL |
| 3 | 7 | J1 | FINISH |
| 4 | 7 | J2 | START |
| 5 | 9 | J3 | ARRIVAL |
| 6 | 12 | J2 | FINISH |
| 7 | 12 | J3 | START |
| 8 | 18 | J3 | FINISH |
結果の読み取り
時刻順のログにより、J2が到着してから開始するまで設備待ちだったことを再現できます。本番では理由コードや設備IDも残すと改善会議で使いやすくなります。
No.043:Future Event List
実務での意味
将来イベントリスト(FEL)は、次に起こる事象を時刻順に管理するDESの中核です。故障や段取り完了など異なる事象を一つの時間軸で扱えます。
分析・モデル化の考え方
FELは優先度付きキューで実装します。最小時刻のイベントを取り出し、状態を更新し、必要な将来イベントを追加する操作を繰り返します。同時刻イベントの優先順位は結果へ影響するため明文化します。
Pythonで確認する
import heapq
fel = [(0, 1, "J1", "ARRIVAL"), (4, 2, "J2", "ARRIVAL"), (9, 3, "J3", "ARRIVAL")]
heapq.heapify(fel); fel_trace = []
while fel:
time, seq, job, event = heapq.heappop(fel)
fel_trace.append((time, job, event, len(fel)))
if event == "ARRIVAL":
p = int(jobs.loc[jobs.job.eq(job), "process"].iloc[0])
heapq.heappush(fel, (time + p, seq + 100, job, "PLANNED_FINISH"))
display(pd.DataFrame(fel_trace, columns=["time", "job", "event", "events_remaining_after_pop"]))
| time | job | event | events_remaining_after_pop | |
|---|---|---|---|---|
| 0 | 0 | J1 | ARRIVAL | 2 |
| 1 | 4 | J2 | ARRIVAL | 2 |
| 2 | 7 | J1 | PLANNED_FINISH | 2 |
| 3 | 9 | J3 | ARRIVAL | 1 |
| 4 | 9 | J2 | PLANNED_FINISH | 1 |
| 5 | 15 | J3 | PLANNED_FINISH | 0 |
結果の読み取り
FELは常に最小時刻を取り出します。この簡略例は設備競合をまだ扱いませんが、イベント追加と時刻進行の仕組みを明示しています。
No.044:イベントスケジューリング法
実務での意味
設備中心のロジックや複雑なイベント規則を厳密に制御したい場合に適します。実装者はイベントごとの状態遷移を明示します。
分析・モデル化の考え方
イベントスケジューリング法では「到着」「完了」を処理する関数を考え、完了時に待ち行列から次ジョブを開始します。主要KPIは平均待ち時間、最大待ち時間、終了時刻です。
Pythonで確認する
def event_scheduling(jobs_df):
waiting, busy, seq, out, q = [], False, 0, {}, []
for r in jobs_df.itertuples(): heapq.heappush(q, (r.arrival, seq, "ARRIVAL", r.job, r.process)); seq += 1
while q:
t, _, ev, job, p = heapq.heappop(q)
if ev == "ARRIVAL": waiting.append((job, p, t))
else: busy = False; out[job]["finish"] = t
if not busy and waiting:
j, pt, a = waiting.pop(0); busy = True
out[j] = {"start": t, "wait": t-a}; heapq.heappush(q, (t+pt, seq, "FINISH", j, pt)); seq += 1
return pd.DataFrame(out).T.reset_index(names="job")
event_result = event_scheduling(jobs)
display(event_result)
| job | start | wait | finish | |
|---|---|---|---|---|
| 0 | J1 | 0 | 0 | 7 |
| 1 | J2 | 7 | 3 | 12 |
| 2 | J3 | 12 | 3 | 18 |
結果の読み取り
イベント規則を明示した結果はNo.041の計算と一致します。規則が増えると状態管理が複雑になるため、テスト可能な小さなイベント関数へ分けることが重要です。
No.045:プロセスインタラクション法
実務での意味
製品や作業者が工程を順に通る姿をそのまま記述でき、工程設計者とモデルの対応を確認しやすい方法です。
分析・モデル化の考え方
各ジョブを独立したプロセスとして表し、必要な設備を要求し、確保できるまで待ち、加工後に解放します。内部的にはイベント処理ですが、コードは業務フローに近くなります。
Pythonで確認する
def process_interaction(jobs_df):
env = simpy.Environment(); machine = simpy.Resource(env, capacity=1); rec = []
def job_proc(row):
yield env.timeout(row.arrival)
with machine.request() as req:
yield req; start = env.now; yield env.timeout(row.process)
rec.append((row.job, row.arrival, start, env.now, start-row.arrival))
for row in jobs_df.itertuples(): env.process(job_proc(row))
env.run(); return pd.DataFrame(rec, columns=["job", "arrival", "start", "finish", "wait"])
process_result = process_interaction(jobs)
display(process_result.sort_values("job"))
| job | arrival | start | finish | wait | |
|---|---|---|---|---|---|
| 0 | J1 | 0 | 0 | 7 | 0 |
| 1 | J2 | 4 | 7 | 12 | 3 |
| 2 | J3 | 9 | 12 | 18 | 3 |
結果の読み取り
同じ設備待ちを、ジョブの行動として短く表現できました。工程ルートが長い場合、モデルと標準作業票を対応付けやすい利点があります。
No.046:SimPy入門
実務での意味
SimPyのResourceを使うと、設備台数、作業者、検査台などの有限資源を表現できます。設備追加案の比較をコード変更1か所で行えます。
分析・モデル化の考え方
Environmentが時刻、Resourceが排他資源、timeoutが所要時間を表します。能力1と2を比較し、待ち時間への効果を確認します。
Pythonで確認する
simpy_compare = []
for capacity in [1, 2]:
env = simpy.Environment(); machine = simpy.Resource(env, capacity=capacity); waits = []
def part(env, arrival, duration):
yield env.timeout(arrival)
with machine.request() as req:
yield req; waits.append(env.now-arrival); yield env.timeout(duration)
for r in jobs.itertuples(): env.process(part(env, r.arrival, r.process))
env.run(); simpy_compare.append((capacity, np.mean(waits), max(waits)))
display(pd.DataFrame(simpy_compare, columns=["capacity", "mean_wait", "max_wait"]))
| capacity | mean_wait | max_wait | |
|---|---|---|---|
| 0 | 1 | 2.0 | 3 |
| 1 | 2 | 0.0 | 0 |
結果の読み取り
この小規模例では2台化で待ちが解消します。ただし実務では後工程へ滞留が移る可能性があるため、ライン全体で評価します。
No.047:生産ラインシミュレーション
実務での意味
直列ラインでは1工程の改善が必ずしも完成数増加につながりません。切削・研削・検査の資源競合を同時に再現し、設備増強案を比較します。
分析・モデル化の考え方
ジョブ のリードタイムは 、納期遅れは です。完成数だけでなく、平均リードタイムと納期遵守率を併記します。
Pythonで確認する
def simulate_factory(capacity=None, horizon=600, seed=SEED):
capacity = capacity or {"Cut": 1, "Grind": 1, "Inspect": 1}
env = simpy.Environment(); rng_local = np.random.default_rng(seed)
machines = {m: simpy.Resource(env, capacity=capacity[m]) for m in capacity}
records, operations = [], []
route_map = routing.set_index("product").to_dict("index")
def job_proc(row):
yield env.timeout(row.arrival)
for machine in route_map[row.product]["route"]:
queued = env.now
with machines[machine].request() as req:
yield req; start = env.now
mean = route_map[row.product][machine]
duration = max(1, rng_local.normal(mean, mean*0.15))
yield env.timeout(duration)
operations.append((row.job, row.product, machine, queued, start, env.now))
records.append((row.job, row.product, row.arrival, env.now, row.due))
for row in orders.itertuples(): env.process(job_proc(row))
env.run(until=horizon)
res = pd.DataFrame(records, columns=["job", "product", "arrival", "finish", "due"])
if len(res):
res["lead_time"] = res.finish-res.arrival; res["tardiness"] = (res.finish-res.due).clip(lower=0)
ops = pd.DataFrame(operations, columns=["job", "product", "machine", "queued", "start", "finish"])
return res, ops
scenarios = {"現状": {"Cut":1,"Grind":1,"Inspect":1}, "切削2台": {"Cut":2,"Grind":1,"Inspect":1}, "研削2台": {"Cut":1,"Grind":2,"Inspect":1}}
summary=[]
for name, cap in scenarios.items():
r, _ = simulate_factory(cap)
summary.append((name, len(r), r.lead_time.mean(), (r.tardiness==0).mean()))
line_summary=pd.DataFrame(summary, columns=["scenario","completed","mean_lead_time","on_time_rate"])
display(line_summary.round(2))
line_summary.set_index("scenario")[["mean_lead_time"]].plot(kind="bar", color="#2878B5", legend=False)
plt.title("Capacity scenario and mean lead time"); plt.xlabel("Scenario"); plt.ylabel("Mean lead time [min]"); plt.grid(axis="y", alpha=.3); plt.tight_layout(); plt.show()
| scenario | completed | mean_lead_time | on_time_rate | |
|---|---|---|---|---|
| 0 | 現状 | 24 | 89.00 | 0.67 |
| 1 | 切削2台 | 24 | 48.36 | 1.00 |
| 2 | 研削2台 | 23 | 87.20 | 0.78 |
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_20161/4039603803.py:34: UserWarning: Glyph 29694 (\N{CJK UNIFIED IDEOGRAPH-73FE}) missing from font(s) DejaVu Sans.
plt.title("Capacity scenario and mean lead time"); plt.xlabel("Scenario"); plt.ylabel("Mean lead time [min]"); plt.grid(axis="y", alpha=.3); plt.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_20161/4039603803.py:34: UserWarning: Glyph 29366 (\N{CJK UNIFIED IDEOGRAPH-72B6}) missing from font(s) DejaVu Sans.
plt.title("Capacity scenario and mean lead time"); plt.xlabel("Scenario"); plt.ylabel("Mean lead time [min]"); plt.grid(axis="y", alpha=.3); plt.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_20161/4039603803.py:34: UserWarning: Glyph 20999 (\N{CJK UNIFIED IDEOGRAPH-5207}) missing from font(s) DejaVu Sans.
plt.title("Capacity scenario and mean lead time"); plt.xlabel("Scenario"); plt.ylabel("Mean lead time [min]"); plt.grid(axis="y", alpha=.3); plt.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_20161/4039603803.py:34: UserWarning: Glyph 21066 (\N{CJK UNIFIED IDEOGRAPH-524A}) missing from font(s) DejaVu Sans.
plt.title("Capacity scenario and mean lead time"); plt.xlabel("Scenario"); plt.ylabel("Mean lead time [min]"); plt.grid(axis="y", alpha=.3); plt.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_20161/4039603803.py:34: UserWarning: Glyph 21488 (\N{CJK UNIFIED IDEOGRAPH-53F0}) missing from font(s) DejaVu Sans.
plt.title("Capacity scenario and mean lead time"); plt.xlabel("Scenario"); plt.ylabel("Mean lead time [min]"); plt.grid(axis="y", alpha=.3); plt.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_20161/4039603803.py:34: UserWarning: Glyph 30740 (\N{CJK UNIFIED IDEOGRAPH-7814}) missing from font(s) DejaVu Sans.
plt.title("Capacity scenario and mean lead time"); plt.xlabel("Scenario"); plt.ylabel("Mean lead time [min]"); plt.grid(axis="y", alpha=.3); plt.tight_layout(); plt.show()
/Users/hiroshi/private/kobo/notebook/.venv/lib/python3.13/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 29694 (\N{CJK UNIFIED IDEOGRAPH-73FE}) missing from font(s) DejaVu Sans.
fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/private/kobo/notebook/.venv/lib/python3.13/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 29366 (\N{CJK UNIFIED IDEOGRAPH-72B6}) missing from font(s) DejaVu Sans.
fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/private/kobo/notebook/.venv/lib/python3.13/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 20999 (\N{CJK UNIFIED IDEOGRAPH-5207}) missing from font(s) DejaVu Sans.
fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/private/kobo/notebook/.venv/lib/python3.13/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 21066 (\N{CJK UNIFIED IDEOGRAPH-524A}) missing from font(s) DejaVu Sans.
fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/private/kobo/notebook/.venv/lib/python3.13/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 21488 (\N{CJK UNIFIED IDEOGRAPH-53F0}) missing from font(s) DejaVu Sans.
fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/private/kobo/notebook/.venv/lib/python3.13/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 30740 (\N{CJK UNIFIED IDEOGRAPH-7814}) missing from font(s) DejaVu Sans.
fig.canvas.print_figure(bytes_io, **kw)

結果の読み取り
設備追加の効果は、完成数・リードタイム・納期遵守率を組み合わせて判断します。効果の小さい増設案は、真の制約工程ではない可能性があります。投資額を加えれば、1分短縮当たり費用や遅延削減額による比較へ拡張できます。
No.048:工程シミュレーション
実務での意味
工程別の待ち時間と稼働率を分解すると、仕掛品が溜まる場所と設備負荷を結び付けられます。単純な高稼働率を目標にせず、納期とのバランスを見ます。
分析・モデル化の考え方
設備 の観測期間 における稼働率は です。待ち時間が急増する高負荷域では、小さな変動が大きな滞留を生みます。
Pythonで確認する
base_res, base_ops = simulate_factory(scenarios["現状"])
base_ops["wait"] = base_ops.start-base_ops.queued
process_kpi = base_ops.groupby("machine").agg(operations=("job","size"), mean_wait=("wait","mean"), busy_time=("finish", lambda s: 0.0))
busy = (base_ops.finish-base_ops.start).groupby(base_ops.machine).sum()
process_kpi["utilization"] = busy/600
display(process_kpi.round(3))
fig, ax = plt.subplots(figsize=(7,4)); process_kpi.mean_wait.plot(kind="bar", ax=ax, color="#F28E2B")
ax.set_title("Mean waiting time by process"); ax.set_xlabel("Process"); ax.set_ylabel("Mean wait [min]"); ax.grid(axis="y", alpha=.3); plt.tight_layout(); plt.show()
| operations | mean_wait | busy_time | utilization | |
|---|---|---|---|---|
| machine | ||||
| Cut | 24 | 44.516 | 0.0 | 0.779 |
| Grind | 16 | 5.002 | 0.0 | 0.429 |
| Inspect | 24 | 1.526 | 0.0 | 0.377 |

結果の読み取り
平均待ちが長く、かつ稼働率が高い工程が改善候補です。一方、平均値は一部の長時間待ちを隠すため、実務では95パーセンタイル、時間帯別仕掛品、停止理由も追加します。
No.049:ジョブショップシミュレーション
実務での意味
ジョブショップでは品種ごとに工程順序が異なり、設備を奪い合います。優先規則の選択は、平均滞留時間だけでなく納期遅れの偏りを変えます。
分析・モデル化の考え方
ここでは共通設備を通る3品種の結果をガントチャートで確認します。厳密な規則比較ではFIFO、最短処理時間(SPT)、最早納期(EDD)を同一乱数系列で反復比較します。
Pythonで確認する
sample_jobs = orders.job.iloc[:10]
gantt = base_ops[base_ops.job.isin(sample_jobs)].copy()
machine_y = {m:i for i,m in enumerate(["Cut","Grind","Inspect"])}
colors = {"A":"#4E79A7", "B":"#F28E2B", "C":"#59A14F"}
fig, ax = plt.subplots(figsize=(10,4.5))
for r in gantt.itertuples():
ax.barh(machine_y[r.machine], r.finish-r.start, left=r.start, height=.55, color=colors[r.product], edgecolor="white")
ax.text((r.start+r.finish)/2, machine_y[r.machine], r.job, ha="center", va="center", fontsize=7)
ax.set_yticks(list(machine_y.values()), list(machine_y.keys()))
ax.set_title("Job-shop schedule (first 10 jobs)"); ax.set_xlabel("Simulation time [min]"); ax.set_ylabel("Machine"); ax.grid(axis="x", alpha=.3); plt.tight_layout(); plt.show()

結果の読み取り
ガントチャートは、品種Cが研削から切削へ進む一方、品種Aは逆順で進む競合を示します。局所的な設備効率だけで順序を決めると、別設備の空きや納期を悪化させるため、全工程を通したKPIで規則を選びます。
No.050:フローショップシミュレーション
実務での意味
全製品が同じ工程順序を通るフローショップでは、投入順序が総完了時間(メイクスパン)と設備の遊休を左右します。段取り替えがある場合は品種のまとめ方も重要です。
分析・モデル化の考え方
2機械フローショップでは、Johnson法がメイクスパン最小化に有効です。前半機械の短いジョブを前へ、後半機械の短いジョブを後ろへ配置します。ここでは全順列との比較で確認します。
Pythonで確認する
import itertools
flow = pd.DataFrame({"job":["P1","P2","P3","P4","P5"], "M1":[8,14,6,11,9], "M2":[12,7,15,9,10]}).set_index("job")
def makespan(seq):
t1=t2=0
for j in seq:
t1 += flow.loc[j,"M1"]; t2 = max(t1,t2)+flow.loc[j,"M2"]
return t2
best_seq = min(itertools.permutations(flow.index), key=makespan)
fifo_seq = tuple(flow.index)
comparison = pd.DataFrame({"sequence":[" → ".join(fifo_seq), " → ".join(best_seq)], "makespan":[makespan(fifo_seq), makespan(best_seq)]}, index=["FIFO","最小"])
display(flow); display(comparison)
comparison.makespan.plot(kind="bar", color=["#9C9C9C","#59A14F"])
plt.title("Flow-shop sequencing comparison"); plt.xlabel("Rule"); plt.ylabel("Makespan [min]"); plt.grid(axis="y", alpha=.3); plt.tight_layout(); plt.show()
| M1 | M2 | |
|---|---|---|
| job | ||
| P1 | 8 | 12 |
| P2 | 14 | 7 |
| P3 | 6 | 15 |
| P4 | 11 | 9 |
| P5 | 9 | 10 |
| sequence | makespan | |
|---|---|---|
| FIFO | P1 → P2 → P3 → P4 → P5 | 63 |
| 最小 | P3 → P1 → P2 → P4 → P5 | 59 |
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_20161/4160123644.py:13: UserWarning: Glyph 26368 (\N{CJK UNIFIED IDEOGRAPH-6700}) missing from font(s) DejaVu Sans.
plt.title("Flow-shop sequencing comparison"); plt.xlabel("Rule"); plt.ylabel("Makespan [min]"); plt.grid(axis="y", alpha=.3); plt.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_20161/4160123644.py:13: UserWarning: Glyph 23567 (\N{CJK UNIFIED IDEOGRAPH-5C0F}) missing from font(s) DejaVu Sans.
plt.title("Flow-shop sequencing comparison"); plt.xlabel("Rule"); plt.ylabel("Makespan [min]"); plt.grid(axis="y", alpha=.3); plt.tight_layout(); plt.show()
/Users/hiroshi/private/kobo/notebook/.venv/lib/python3.13/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 26368 (\N{CJK UNIFIED IDEOGRAPH-6700}) missing from font(s) DejaVu Sans.
fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/private/kobo/notebook/.venv/lib/python3.13/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 23567 (\N{CJK UNIFIED IDEOGRAPH-5C0F}) missing from font(s) DejaVu Sans.
fig.canvas.print_figure(bytes_io, **kw)

結果の読み取り
同じ加工時間でも投入順序だけでメイクスパンが変わります。実務では3工程以上、納期、段取り、搬送ロットが加わるため、シミュレーションと探索アルゴリズムを組み合わせ、現行順序との差を複数日で検証します。
対象ノックを通して見える実務上の示唆
- 平均能力ではなく時間軸を見る:到着の集中とばらつきが待ちを作ります。
- ボトルネックは移動する:設備を増強すると制約が後工程へ移るため、ライン全体で比較します。
- 稼働率だけを最大化しない:高負荷は仕掛品と納期変動を増やします。
- 順序は追加投資なしの改善余地:ジョブショップ・フローショップでは優先規則がKPIを変えます。
- ログを説明責任に使う:イベントリストにより、シミュレーション結果の発生理由を追跡できます。
実務導入する場合に必要なこと
- MES・PLC・作業実績から、到着、加工、段取り、停止、搬送の時刻を定義する
- 欠測・丸め・計画時刻と実績時刻の混在を点検し、入力分布を品種・設備別に推定する
- 現場担当者と工程ルート、優先規則、休憩、故障時運用を確認する
- 過去期間を再現し、完成数、仕掛品、リードタイム分布を実績と照合する
- 複数seed・複数反復で信頼区間を出し、単一試行の偶然を意思決定に使わない
- 投資費、要員制約、品質リスクも含むシナリオ評価表を作る
モデルは現実の完全な複製ではなく、意思決定に必要な解像度の仮説です。目的、対象範囲、許容誤差、更新責任者を最初に合意することが、継続利用の鍵になります。
まとめ
No.041〜No.050では、DESのイベント管理から、SimPyによる資源競合、生産ラインの能力比較、工程別KPI、ジョブショップの可視化、フローショップの順序比較までを確認しました。DESの価値は精巧な動画ではなく、現場の運用案を安全に比較し、納期・仕掛品・投資を同じ時間軸で議論できることにあります。
法人向けのご相談
数理工房では、製造実績データの整備、ボトルネック分析、離散イベントシミュレーション、設備投資・人員配置のWhat-if分析、現場で継続運用できる分析基盤の構築まで支援しています。課題がまだ定量化されていない段階でも、対象工程と意思決定の整理からご一緒できます。
📩 お問い合わせ: surikobo.co.jp/contact
まずはお気軽にご相談ください。