100本ノック / 数理最適化 / 数理最適化100本ノック
製造業で学ぶ数理最適化入門|生産配分をPythonで解く10本ノック
限られた設備能力から利益と納期を両立する:製造業の数理最適化入門10本ノック
本Notebookは、製造業データ分析100本ノック「数理最適化編」の第1回です。全100本を通して、線形計画、整数計画、ネットワーク、非線形・凸最適化、動的計画、不確実性、メタヒューリスティクス、そして生産計画・在庫・設備投資への応用までを、意思決定に使える形で段階的に扱います。
今回は No.001〜No.010 として、数理最適化の基本語彙を架空工場の生産配分に結びつけます。狙いはアルゴリズム名を覚えることではなく、「何を決めるのか」「何を良くするのか」「何を守るのか」を分離し、現場の判断を再現可能なモデルへ翻訳することです。
[!NOTE] 本資料は、数理工房 (もしくは代表である和山個人) が過去に企業研修において使用した notebook を企業様の許可を得て再構成・編集のうえ公開しています。
掲載データはすべて架空のものであり、実在する企業・工場・数値とは一切関係ありません。
はじめに:この記事で扱う製造業の実務課題
月次の生産会議では、需要の大きい製品を増やしたくても、加工機、組立、人員、材料など複数の能力が同時に上限へ近づきます。本稿では、主力製品Aと高付加価値製品Bの生産量を決める架空の工場を考えます。
判断したいのは、設備能力と需要上限を守りながら限界利益を最大化する生産配分です。ただし、利益だけでなく、納期、品質、安全、段取り、在庫といった現場条件をどこまでモデルへ含めるかも重要な論点です。
現場でよくある状況
- 前月実績を起点に、担当者が表計算ソフト上で数量を増減する
- ボトルネック設備だけを見て決めた結果、別工程で残業や滞留が発生する
- 「利益最大」「売上最大」「稼働率最大」が混同され、会議ごとに評価軸が変わる
- 制約の根拠や例外処理が属人化し、計画案を再現できない
なぜこの問題は判断が難しいのか
製品Aを1単位増やすと、利益は増えますが複数工程の時間を同時に消費します。製品Bも同様です。限られた資源を複数製品が取り合うため、個別製品の採算だけでは全体最適になりません。また、実務では数量がロット単位、停止時間が非線形、需要が不確実など、単純な比例関係から外れる条件もあります。
今回扱うノックの全体像
No.001〜No.006で最適化モデルの骨格を作り、No.007〜No.009で解の性質とモデル分類を理解します。No.010では、同じ考え方が生産・物流・在庫・保全・人員配置にどう展開されるかを整理します。
Python 環境の準備
numpy と pandas でデータを扱い、線形計画は scipy.optimize.linprog で解きます。可視化は matplotlib のみを利用します。乱数は再現可能性のため固定します。
import sys
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from scipy.optimize import linprog
SEED = 42
rng = np.random.default_rng(SEED)
pd.set_option("display.float_format", lambda x: f"{x:,.2f}")
plt.rcParams["figure.figsize"] = (7.2, 4.4)
plt.rcParams["axes.axisbelow"] = True
print(f"Python : {sys.version.split()[0]}")
print(f"NumPy : {np.__version__}")
print(f"pandas : {pd.__version__}")
print(f"matplotlib : {matplotlib.__version__}")
Python : 3.13.1
NumPy : 2.5.1
pandas : 3.0.3
matplotlib : 3.11.0
架空データの作成
製品A・Bについて、1単位当たり限界利益、加工時間、組立時間、需要上限を設定します。能力値には固定 seed の小さな変動を加え、実績から翌月能力を見積もったという想定にします。金額単位は千円、時間単位は時間です。
products = pd.DataFrame({
"product": ["A", "B"],
"margin_kJPY": [5.0, 4.0],
"machining_h": [2.0, 1.0],
"assembly_h": [1.0, 2.0],
"demand_limit": [50, 45],
}).set_index("product")
capacity = pd.Series({
"machining_h": int(100 + rng.integers(-2, 3)),
"assembly_h": int(80 + rng.integers(-2, 3)),
}, name="capacity")
display(products)
display(capacity.to_frame())
| margin_kJPY | machining_h | assembly_h | demand_limit | |
|---|---|---|---|---|
| product | ||||
| A | 5.00 | 2.00 | 1.00 | 50 |
| B | 4.00 | 1.00 | 2.00 | 45 |
| capacity | |
|---|---|
| machining_h | 98 |
| assembly_h | 81 |
No.001:数理最適化とは何か
実務での意味
数理最適化とは、候補を単に比較するのではなく、目的と現場条件を数式で表し、条件を満たす候補の中から最も望ましい案を選ぶ方法です。ここでは製品A・Bの月間生産量を決めます。
分析・モデル化の考え方
基本構造は「決定変数」「目的関数」「制約条件」の3点です。
ただし、加工・組立能力と需要上限を守ります。最適化は意思決定を自動化する魔法ではなく、判断基準と守る条件を明文化する仕組みです。
Pythonで確認する
まず候補計画を複数作り、能力を満たすか、利益はいくらかを一覧にします。
candidates = pd.DataFrame({"plan": ["現行踏襲", "A重視", "B重視", "均衡案"],
"A": [25, 40, 20, 32], "B": [20, 15, 30, 24]})
candidates["machining_h"] = 2*candidates["A"] + candidates["B"]
candidates["assembly_h"] = candidates["A"] + 2*candidates["B"]
candidates["profit_kJPY"] = 5*candidates["A"] + 4*candidates["B"]
candidates["feasible"] = ((candidates["machining_h"] <= capacity["machining_h"]) &
(candidates["assembly_h"] <= capacity["assembly_h"]))
display(candidates)
colors = candidates["feasible"].map({True: "tab:blue", False: "tab:red"})
plt.scatter(candidates["machining_h"], candidates["profit_kJPY"], c=colors, s=90)
for i, (_, r) in enumerate(candidates.iterrows(), start=1):
plt.annotate(f"P{i}", (r["machining_h"], r["profit_kJPY"]), xytext=(4,4), textcoords="offset points")
plt.axvline(capacity["machining_h"], color="black", linestyle="--", label="Machining capacity")
plt.title("Candidate Plans: Resource Use and Profit")
plt.xlabel("Machining hours")
plt.ylabel("Contribution margin (kJPY)")
plt.grid(True, alpha=.3)
plt.legend()
plt.tight_layout()
plt.show()
| plan | A | B | machining_h | assembly_h | profit_kJPY | feasible | |
|---|---|---|---|---|---|---|---|
| 0 | 現行踏襲 | 25 | 20 | 70 | 65 | 205 | True |
| 1 | A重視 | 40 | 15 | 95 | 70 | 260 | True |
| 2 | B重視 | 20 | 30 | 70 | 80 | 220 | True |
| 3 | 均衡案 | 32 | 24 | 88 | 80 | 256 | True |

結果の読み取り
赤い候補は利益が高く見えても能力超過なら採用できません。最適化の第一歩は、案の良し悪しと実行可能性を分けることです。人が作った少数候補だけでは最良案を見落とすため、以降では候補空間全体を調べます。
No.002:目的関数とは何か
実務での意味
目的関数は「何を良くしたいか」を数値化したものです。売上ではなく限界利益を採用すれば、変動費を差し引いた生産判断になります。ただし、納期遵守や品質を目的に含める設計もあり、KPIの選択自体が経営判断です。
分析・モデル化の考え方
製品別限界利益を 、生産量を とすると、総限界利益は です。係数は「1単位増産したとき目的値がいくら増えるか」を表します。
Pythonで確認する
利益係数の違いが目的値へ与える影響を、同じ総生産量で比較します。
mix = pd.DataFrame({"A": np.arange(0, 51, 5)})
mix["B"] = 50 - mix["A"]
mix["profit_kJPY"] = 5*mix["A"] + 4*mix["B"]
display(mix)
plt.plot(mix["A"], mix["profit_kJPY"], marker="o")
plt.title("Objective Value for a Fixed Total Volume")
plt.xlabel("Product A units (A + B = 50)")
plt.ylabel("Contribution margin (kJPY)")
plt.grid(True, alpha=.3)
plt.tight_layout()
plt.show()
| A | B | profit_kJPY | |
|---|---|---|---|
| 0 | 0 | 50 | 200 |
| 1 | 5 | 45 | 205 |
| 2 | 10 | 40 | 210 |
| 3 | 15 | 35 | 215 |
| 4 | 20 | 30 | 220 |
| 5 | 25 | 25 | 225 |
| 6 | 30 | 20 | 230 |
| 7 | 35 | 15 | 235 |
| 8 | 40 | 10 | 240 |
| 9 | 45 | 5 | 245 |
| 10 | 50 | 0 | 250 |

結果の読み取り
総数量が同じでも、利益係数の高いAを増やすほど目的値は上がります。しかし、Aは加工時間を多く使うため、目的関数だけを見て全量Aにすることはできません。目的と制約を同時に見る必要があります。
No.003:制約条件とは何か
実務での意味
制約条件は、設備能力、要員、材料、需要、安全基準など、計画が必ず守る条件です。「できれば守る」目標と「必ず守る」上限を区別しないと、解が現場で使えません。
分析・モデル化の考え方
加工と組立の制約は次式です。
左辺は資源使用量、右辺は利用可能量です。差 は余力(slack)であり、制約の逼迫度を示します。
Pythonで確認する
plan = pd.Series({"A": 32, "B": 24})
usage = pd.Series({"machining_h": 2*plan["A"] + plan["B"],
"assembly_h": plan["A"] + 2*plan["B"]})
constraint_check = pd.DataFrame({"usage": usage, "capacity": capacity})
constraint_check["slack"] = constraint_check["capacity"] - constraint_check["usage"]
constraint_check["utilization_pct"] = 100*constraint_check["usage"]/constraint_check["capacity"]
display(constraint_check)
constraint_check[["usage", "capacity"]].plot(kind="bar")
plt.title("Resource Usage versus Capacity")
plt.xlabel("Resource")
plt.ylabel("Hours")
plt.grid(True, axis="y", alpha=.3)
plt.tight_layout()
plt.show()
| usage | capacity | slack | utilization_pct | |
|---|---|---|---|---|
| machining_h | 88 | 98 | 10 | 89.80 |
| assembly_h | 80 | 81 | 1 | 98.77 |

結果の読み取り
余力が負なら能力超過、ゼロに近ければボトルネック候補です。制約は「稼働率を上げる」ためだけでなく、どの資源を増強すれば計画の自由度が上がるかを議論する材料になります。
No.004:決定変数とは何か
実務での意味
決定変数は、会議やシステムが実際に変更できる値です。需要や設備能力は通常、入力条件であって決定変数ではありません。ここでは を月間生産量とします。
分析・モデル化の考え方
変数の粒度は重要です。製品別数量だけなら2変数ですが、日別・ライン別・シフト別に分けると急増します。意思決定に不要な粒度は計算量と保守負担を増やします。
Pythonで確認する
変数を製品別から「製品×シフト」へ細分化した場合の規模を比較します。
scales = pd.DataFrame({
"model": ["Product", "Product x Day", "Product x Day x Shift", "SKU x Day x Line x Shift"],
"variables": [2, 2*20, 2*20*2, 120*20*3*2],
})
display(scales)
plt.bar(scales["model"], scales["variables"], color="tab:purple")
plt.yscale("log")
plt.title("Decision Granularity and Model Size")
plt.xlabel("Decision granularity")
plt.ylabel("Number of variables (log scale)")
plt.xticks(rotation=20, ha="right")
plt.grid(True, axis="y", alpha=.3)
plt.tight_layout()
plt.show()
| model | variables | |
|---|---|---|
| 0 | Product | 2 |
| 1 | Product x Day | 40 |
| 2 | Product x Day x Shift | 80 |
| 3 | SKU x Day x Line x Shift | 14400 |

結果の読み取り
粒度を上げると現実を詳しく表現できますが、データ整備、計算、説明が難しくなります。最初は経営判断に必要な最小粒度で価値を確認し、段取りやシフト制約が必要になった段階で変数を増やすのが現実的です。
No.005:実行可能解とは何か
実務での意味
実行可能解とは、すべての制約を同時に満たす計画です。利益が高くても制約を一つでも破れば実行可能ではありません。実行可能解が一つもない場合は、納期、能力、需要下限などの条件が矛盾している可能性があります。
分析・モデル化の考え方
制約を満たす点の集合を実行可能領域と呼びます。線形制約では領域が多角形になり、最適解は通常その境界、特に頂点に現れます。
Pythonで確認する
a_grid = np.arange(0, 51)
b_grid = np.arange(0, 46)
grid = pd.DataFrame([(a,b) for a in a_grid for b in b_grid], columns=["A","B"])
grid["feasible"] = ((2*grid["A"] + grid["B"] <= capacity["machining_h"]) &
(grid["A"] + 2*grid["B"] <= capacity["assembly_h"]))
print(f"Grid candidates: {len(grid):,}; feasible: {grid['feasible'].sum():,}")
plt.scatter(grid.loc[grid.feasible,"A"], grid.loc[grid.feasible,"B"], s=8, alpha=.45, label="Feasible")
plt.scatter(grid.loc[~grid.feasible,"A"], grid.loc[~grid.feasible,"B"], s=5, alpha=.08, color="gray", label="Infeasible")
plt.title("Feasible Region of Production Plans")
plt.xlabel("Product A units")
plt.ylabel("Product B units")
plt.grid(True, alpha=.3)
plt.legend()
plt.tight_layout()
plt.show()
Grid candidates: 2,346; feasible: 1,359

結果の読み取り
色の濃い領域が、両工程能力と需要上限を同時に満たす計画です。現在案がこの領域内のどこにあるかを可視化すると、増産余地とトレードオフを関係者へ説明しやすくなります。
No.006:最適解とは何か
実務での意味
最適解は、実行可能解の中で目的関数が最も良い解です。「計算上の最適」と「現場で採用すべき計画」は同義ではなく、モデル外の事情やデータ誤差を確認したうえで採用します。
分析・モデル化の考え方
linprog は最小化を解くため、利益係数の符号を反転して を最小化します。連続変数として解くので端数が出る点にも注意します。
Pythonで確認する
c = -products["margin_kJPY"].to_numpy()
A_ub = np.array([[2,1], [1,2]])
b_ub = capacity[["machining_h","assembly_h"]].to_numpy()
bounds = [(0, products.loc["A","demand_limit"]), (0, products.loc["B","demand_limit"])]
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method="highs")
optimal = pd.Series(res.x, index=["A","B"], name="optimal_units")
summary = pd.DataFrame({"optimal_units": optimal,
"unit_margin_kJPY": products["margin_kJPY"]})
summary["margin_kJPY"] = summary["optimal_units"]*summary["unit_margin_kJPY"]
display(summary)
print(f"Maximum contribution margin: {-res.fun:,.2f} kJPY")
feasible_grid = grid[grid.feasible].copy()
feasible_grid["profit"] = 5*feasible_grid["A"] + 4*feasible_grid["B"]
plt.scatter(feasible_grid["A"], feasible_grid["B"], c=feasible_grid["profit"], s=12, cmap="viridis")
plt.scatter(*res.x, marker="*", s=220, color="red", label="Continuous optimum")
plt.colorbar(label="Contribution margin (kJPY)")
plt.title("Objective Value over the Feasible Region")
plt.xlabel("Product A units")
plt.ylabel("Product B units")
plt.grid(True, alpha=.3)
plt.legend()
plt.tight_layout()
plt.show()
| optimal_units | unit_margin_kJPY | margin_kJPY | |
|---|---|---|---|
| A | 38.33 | 5.00 | 191.67 |
| B | 21.33 | 4.00 | 85.33 |
Maximum contribution margin: 277.00 kJPY

結果の読み取り
星印が連続モデルの最適解です。両資源の使い方と製品別利益のバランスから決まるため、「単価が高いAだけ作る」案とは異なります。端数生産が不可能なら、No.009の離散モデルが必要です。
No.007:局所最適と大域最適
実務での意味
局所最適は近くの候補より良い解、大域最適は全候補の中で最良の解です。段取り回数、保全周期、温度条件のような非線形問題では、初期案の近くにある改善点で探索が止まることがあります。
分析・モデル化の考え方
複数の谷を持つ非凸関数を考えます。局所探索は開始点に依存するため、複数初期値、領域分割、大域探索、下界評価などが必要です。
Pythonで確認する
t = np.linspace(1, 30, 1000)
cost = 0.035*(t-18)**2 + 2.2*np.sin(t/2.2) + 12
local_idx = np.where((cost[1:-1] < cost[:-2]) & (cost[1:-1] < cost[2:]))[0] + 1
mins = pd.DataFrame({"interval_days": t[local_idx], "cost_index": cost[local_idx]}).sort_values("cost_index")
display(mins.round(2))
plt.plot(t, cost)
plt.scatter(t[local_idx], cost[local_idx], color="tab:red", zorder=3, label="Local minima")
g = local_idx[np.argmin(cost[local_idx])]
plt.scatter(t[g], cost[g], marker="*", s=220, color="gold", edgecolor="black", label="Global minimum")
plt.title("Non-convex Maintenance Interval Cost")
plt.xlabel("Maintenance interval (days)")
plt.ylabel("Cost index")
plt.grid(True, alpha=.3)
plt.legend()
plt.tight_layout()
plt.show()
| interval_days | cost_index | |
|---|---|---|
| 1 | 23.35 | 10.96 |
| 0 | 11.42 | 11.56 |

結果の読み取り
赤点はどれも近傍では改善できない局所最適ですが、星印だけが探索範囲全体の大域最適です。現場の「少しずつ条件を変える」改善は有効でも、開始条件によって別の谷を見落とす可能性があります。
No.008:凸最適化と非凸最適化
実務での意味
凸最適化では局所最適が大域最適になるため、解の信頼性を説明しやすくなります。非凸性は固定費、段取り、複雑な物理特性、整数条件などから生じます。
分析・モデル化の考え方
凸関数は任意の2点を結ぶ線分が関数の上側にあり、単一の谷を持ちます。一方、非凸関数は複数の谷を持ち得ます。モデル化では、現実を損なわない範囲で凸化・線形化できるかを検討します。
Pythonで確認する
x = np.linspace(-4, 6, 600)
convex = (x-1)**2 + 2
nonconvex = 0.08*(x-1)**4 - 1.4*(x-1)**2 + 5
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
axes[0].plot(x, convex, color="tab:blue")
axes[0].set_title("Convex Objective")
axes[1].plot(x, nonconvex, color="tab:orange")
axes[1].set_title("Non-convex Objective")
for ax in axes:
ax.set_xlabel("Decision variable")
ax.set_ylabel("Objective value")
ax.grid(True, alpha=.3)
fig.tight_layout()
plt.show()
print("Convex minimum x =", x[np.argmin(convex)].round(3))
print("Non-convex grid minimum x =", x[np.argmin(nonconvex)].round(3))

Convex minimum x = 0.992
Non-convex grid minimum x = -1.963
結果の読み取り
左はどこから下っても同じ谷へ向かいます。右は複数の谷があり、解法と初期値に注意が必要です。実務では「最適値」だけでなく、最適性保証、計算時間、近似誤差を合わせて報告します。
No.009:連続最適化と離散最適化
実務での意味
生産量を自由な実数で扱える場合は連続最適化、ロット数、設備の採否、担当者の割当のように整数・0/1で決める場合は離散最適化です。連続解を単純に丸めると、制約違反や利益機会の損失が起こります。
分析・モデル化の考え方
ここではAを5個ロット、Bを4個ロットで作ると仮定します。整数ロット数 を用いて と表します。
Pythonで確認する
lot_a, lot_b = 5, 4
lot_plans = pd.DataFrame([(na, nb, lot_a*na, lot_b*nb)
for na in range(11) for nb in range(12)],
columns=["lots_A","lots_B","A","B"])
lot_plans = lot_plans[(2*lot_plans.A + lot_plans.B <= capacity["machining_h"]) &
(lot_plans.A + 2*lot_plans.B <= capacity["assembly_h"])]
lot_plans["profit_kJPY"] = 5*lot_plans.A + 4*lot_plans.B
best_lot = lot_plans.nlargest(5, "profit_kJPY")
display(best_lot)
comparison = pd.DataFrame({
"plan": ["Continuous optimum", "Best lot plan"],
"A": [res.x[0], best_lot.iloc[0].A],
"B": [res.x[1], best_lot.iloc[0].B],
"profit_kJPY": [-res.fun, best_lot.iloc[0].profit_kJPY],
})
display(comparison)
plt.bar(comparison["plan"], comparison["profit_kJPY"], color=["tab:blue","tab:green"])
plt.title("Continuous versus Lot-based Plan")
plt.xlabel("Model type")
plt.ylabel("Contribution margin (kJPY)")
plt.grid(True, axis="y", alpha=.3)
plt.tight_layout()
plt.show()
| lots_A | lots_B | A | B | profit_kJPY | |
|---|---|---|---|---|---|
| 100 | 8 | 4 | 40 | 16 | 264 |
| 110 | 9 | 2 | 45 | 8 | 257 |
| 89 | 7 | 5 | 35 | 20 | 255 |
| 99 | 8 | 3 | 40 | 12 | 248 |
| 78 | 6 | 6 | 30 | 24 | 246 |
| plan | A | B | profit_kJPY | |
|---|---|---|---|---|
| 0 | Continuous optimum | 38.33 | 21.33 | 277.00 |
| 1 | Best lot plan | 40.00 | 16.00 | 264.00 |

結果の読み取り
ロット制約を入れると候補が飛び飛びになり、連続モデルより目的値が下がる場合があります。この差は現実性の代償です。連続解は上限値や目安として有用ですが、実行計画は離散条件を含めて再計算します。
No.010:製造業で数理最適化が使われる場面
実務での意味
最適化は生産数量だけでなく、順序、物流、在庫、要員、保全、投資の選択に使えます。重要なのは解法から始めず、意思決定の頻度、責任者、入力データ、出力後の業務を明確にすることです。
分析・モデル化の考え方
適用候補は「経済効果」「データ準備度」「意思決定頻度」「制約の明確さ」で評価できます。効果だけが大きくても、データや運用が未整備なら短期導入には向きません。
Pythonで確認する
use_cases = pd.DataFrame({
"use_case": ["Production mix", "Scheduling", "Inventory", "Workforce", "Maintenance", "Capital investment"],
"business_value": [9, 9, 8, 7, 7, 9],
"data_readiness": [8, 6, 8, 7, 6, 5],
"decision_frequency": [8, 9, 8, 9, 7, 3],
})
use_cases["priority_score"] = (0.45*use_cases.business_value +
0.35*use_cases.data_readiness +
0.20*use_cases.decision_frequency)
display(use_cases.sort_values("priority_score", ascending=False))
plt.scatter(use_cases.data_readiness, use_cases.business_value,
s=60*use_cases.decision_frequency, alpha=.7)
for _, r in use_cases.iterrows():
plt.annotate(r.use_case, (r.data_readiness, r.business_value), xytext=(4,4), textcoords="offset points")
plt.title("Optimization Use-case Portfolio")
plt.xlabel("Data readiness score")
plt.ylabel("Business value score")
plt.xlim(0, 10); plt.ylim(0, 10)
plt.grid(True, alpha=.3)
plt.tight_layout()
plt.show()
| use_case | business_value | data_readiness | decision_frequency | priority_score | |
|---|---|---|---|---|---|
| 0 | Production mix | 9 | 8 | 8 | 8.45 |
| 2 | Inventory | 8 | 8 | 8 | 8.00 |
| 1 | Scheduling | 9 | 6 | 9 | 7.95 |
| 3 | Workforce | 7 | 7 | 9 | 7.40 |
| 4 | Maintenance | 7 | 6 | 7 | 6.65 |
| 5 | Capital investment | 9 | 5 | 3 | 6.40 |

結果の読み取り
この架空評価では、生産配分と在庫が効果と準備度の両面で着手しやすい候補です。点の大きさは判断頻度で、日次・週次の反復判断は仕組み化の効果が蓄積しやすいことを示します。実案件では評価基準と重みを関係者で合意します。
対象ノックを通して見える実務上の示唆
数理最適化の価値は、最適解そのものだけではありません。目的関数を決める過程で部門間の評価軸を揃え、制約を列挙する過程で暗黙の運用ルールを可視化できます。また、余力や目的値差を確認すれば、能力増強、外注、需要調整の議論を定量化できます。
一方、モデルは現実の写像です。品質リスク、設備故障、技能差、緊急注文などが入力されていなければ、解にも反映されません。「最適」という言葉を過信せず、採用可能な提案と、その根拠・前提・感度を提示する意思決定支援として設計することが重要です。
実務導入する場合に必要なこと
- 意思決定の定義:誰が、いつ、何を決め、どのKPIで評価するかを決める
- データ定義:能力、標準時間、利益、需要、在庫の粒度・更新頻度・責任部署を揃える
- 制約の棚卸し:物理制約、契約条件、品質・安全、現場慣行を分けて記録する
- 現行計画との比較:目的値だけでなく、実行可能性、安定性、説明可能性を比較する
- 感度分析と例外運用:需要・能力が変わった場合と、緊急時の人による修正手順を決める
- 小さな実証:対象製品や期間を限定し、計画作成時間と実績KPIの改善を検証する
まとめ
No.001〜No.010では、数理最適化を構成する目的関数、制約条件、決定変数、実行可能解、最適解を、架空工場の生産配分で確認しました。さらに局所/大域、凸/非凸、連続/離散という分類により、問題の性質によって解法と最適性保証が変わることを見ました。
実務での出発点は高度な解法ではなく、意思決定をこの3要素へ翻訳できるかです。次の段階では、現行業務との比較、データ品質、例外処理まで含め、小さく検証することが成功確率を高めます。
法人向けのご相談
数理工房では、生産計画、スケジューリング、在庫・物流、要員配置、設備投資など、製造業の意思決定を対象としたデータ分析・数理最適化の設計、PoC、業務実装をご支援します。課題がまだ数式になっていない段階でも、業務ヒアリングとデータ棚卸しからご相談いただけます。
📩 お問い合わせ: surikobo.co.jp/contact
まずはお気軽にご相談ください。