OR-Toolsで生産計画を自動化する|生産スケジューラ自作の記録

多品種少量の工場では、毎日の生産計画を一人のベテランが頭の中やホワイトボードで組んでいることがよくあります。どの注文を、どの機械で、何時から作るか。注文と機械が増えるほど、組み合わせは人の手に負えなくなります。

この計画づくりを手伝う仕組みが、生産スケジューラです。注文ごとの作業を機械の空き時間に並べ、納期に間に合う順番を探します。

製品として売られている生産スケジューラは、数十万円から数百万円かかるものも少なくありません。では、自分で作ることはできるのでしょうか?

当社では、架空の金属加工工場のデータを作り、受注20件を7台の機械に割り当てる計画を、Pythonで自動で組んでみました。使ったのは、Googleが公開している最適化のライブラリ OR-Tools の CP-SAT です。

最適化は、AIのように答えを予想するのではなく、決めた条件をすべて守る中から一番よい答えを計算で探す方法です。納期遅れが最小になる順番を探すと、遅れた注文は0件でした。 人がよく使う「納期の早い順に詰める」方法で組むと、遅れた注文が1件出ました。

この記事では、手順とコードの要点、結果、自作して分かった難しいところと、製品との使い分けをまとめます。

目次

生産スケジューラを自作する前に決めたこと

自作を始める前に、解く問題の形と、どこまで作るかを決めました。最初から現場のすべてを入れようとすると、完成しない危険性があるためです。

この記事で使う製造業の言葉

この記事では、金属加工の工場を例にしています。製造業の現場の言葉を知らない方のために、よく出てくる言葉を先にまとめておきます。

言葉意味
NC旋盤材料を回しながら刃物で削り、軸や円盤のような丸い部品を作る機械
旋削旋盤で材料を削ること
マシニングセンタ材料を固定し、回る刃物で穴あけや削りをする機械。MCと略す
レーザー加工機レーザーで金属の板を切り抜く機械
ベンディングマシン金属の板を曲げる機械
工程部品が完成するまでの作業の一つひとつ(例:削る→穴をあける)
段取り作業の前の準備(刃物や治具の付け替えなど)。数量に関係なく毎回かかる時間
稼働日・稼働工場が動く日と、機械が動いている時間
多品種少量種類が多く、一度に作る数が少ない作り方。注文ごとに作業が変わる
ボトルネック一番混んでいて、全体の進み具合を決めてしまう機械

ジョブショップスケジューリングとは?

ジョブショップスケジューリングは、注文ごとに工程の順番が違い、限られた機械を取り合う作業を、どの順番で、いつ機械に載せるかを決める問題です。多品種少量の加工工場の計画は、ほぼこの形になります。

たとえば、フランジは「旋盤 → マシニングセンタ」、カバーは「レーザー加工 → 曲げ → 溶接」の順に作ります。1台の機械は同時に1つの作業しかできないため、どの注文を先に流すかで、納期に間に合うかどうかが変わります。

組み合わせの数は、注文と機械が増えると急に大きくなります。そのため、すべての並べ方を試すのではなく、条件を満たす中からよい答えを探すソルバー(解く道具)を使います。

最小の形で作る範囲

最初は、計画の自動化の効果を確かめることを目的に、次の範囲に絞りました。

  • 機械1台は、同時に1つの作業しかしない
  • 同じ注文の工程は、前の工程が終わってから始める
  • 1日の稼働は480分で、作業は夕方で止まり翌朝に続く
  • 土日と祝日は動かさない

最小の形が動いてから、次の条件を一つずつ足しました。結果は「現場の条件を一つずつ足す」の節に書きます。

  • 代わりの機械(旋盤1号機の作業を2号機でもできる)
  • 機械を止める日(故障や点検)
  • 日をまたがない作業(夕方で止められない加工)
  • 作業者の人数(誰がどの機械を動かすか)
  • 決まった作業の固定(もう始めた作業)

製品の並べ方で変わる段取り時間は、書き方が難しく計算も重くなるため、今回は入れていません。

検証に使ったサンプル工場データ

検証には、当社が作った架空の工場のデータを使いました。実在の会社・製品のデータではありません。

項目内容
工場の想定金属部品の加工と板金、簡単な組立をする工場
機械NC旋盤2台・マシニングセンタ・レーザー加工機・ベンディングマシン・溶接ブース・組立台の7台
計画の期間2026年10月26日〜11月6日の稼働日9日(11月3日は祝日)
受注20件。工程に分けると作業は39件
混み具合一番混むNC旋盤1号機で、期間の稼働の約85%を使う

データは、Excelの3つのシートだけで持っています。

  • 説明:計画の期間、1日の稼働、休日、機械の一覧
  • 受注:指示番号・品番・数量・受注日・納期
  • 工程表:品番ごとの工程の順番、使う機械、段取り時間、1個あたりの時間

作業時間は「段取り時間 + 数量 × 1個あたりの時間」で、工程表の値から出します。作業台は、脚の切断・脚の溶接・組立の3工程として工程表に書きました。

なお、スケジューラの作った計画を検証する目的でExcelで「手組み計画」シートも用意しました

Pythonで組んだプログラム

プログラムは、ExcelのシートからデータをPythonに読み込み、二つの方法で計画を作って、結果をExcelとガントチャートの画像に出します。用意するファイルは、プログラム(schedule.py)とデータ(サンプル工場.xlsx)の二つだけです。

ここでは、計画を組む部分の要点を説明します。

OR-ToolsのCP-SATを使う

CP-SATは、OR-Toolsに含まれる制約プログラミングのソルバーです。「機械は同時に1つ」「前の工程の後に始める」といった条件を書くと、それを満たす答えの中から、決めた基準で一番よいものを探します。

インストールは次の一行で済みます。

Bash
py -m pip install ortools openpyxl matplotlib

工程を機械の上に並べる

作業一つひとつを、開始と終了を持つ「区間」として表します。同じ機械の区間は重ならないように、同じ注文の工程は順番どおりになるように条件を書きます。

Python
from ortools.sat.python import cp_model
from collections import defaultdict

m = cp_model.CpModel()
start, end, itv = {}, {}, {}
by_machine = defaultdict(list)

for k, o in enumerate(ops):
    start[k] = m.NewIntVar(0, horizon, f"s{k}")
    end[k] = m.NewIntVar(0, horizon, f"e{k}")
    itv[k] = m.NewIntervalVar(start[k], o["dur"], end[k], f"i{k}")
    by_machine[o["machine"]].append(itv[k])

# 機械1台は同時に1つの作業だけ
for intervals in by_machine.values():
    m.AddNoOverlap(intervals)

# 同じ注文の工程は、前の工程が終わってから
for k in range(1, len(ops)):
    if ops[k]["order"] == ops[k - 1]["order"]:
        m.Add(start[k] >= end[k - 1])

時間は、稼働日だけを並べた「稼働の分」で数えています。1日目の朝が0分、2日目の朝が480分という数え方です。土日や祝日を時間の数え方から外しておくと、休日をまたぐ作業の条件を書かずに済みます。

納期遅れを最小にする

各注文の最後の工程が、納期の日の終わりを過ぎた分を「遅れ」とし、遅れの合計が最小になるように解かせます。遅れが同じなら、全部の作業が早く終わる答えを選ぶよう、終わる時刻も小さく加えています。

Python
tardiness = []
for order, k in last_op_of_order.items():
    t = m.NewIntVar(0, horizon, f"t_{order}")
    m.Add(t >= end[k] - due_minute[order])  # 納期を過ぎた分
    tardiness.append(t)

makespan = m.NewIntVar(0, horizon, "makespan")
m.AddMaxEquality(makespan, list(end.values()))

m.Minimize(1000 * sum(tardiness) + makespan)

solver = cp_model.CpSolver()
solver.parameters.max_time_in_seconds = 30
status = solver.Solve(m)

比べるために納期の早い順でも組む

最適化の効果を見るために、人がよく使う決め方でも計画を作りました。作れる作業の中から納期が一番早いものを選び、その機械の空いている一番早い時間に入れる、という手順を繰り返す方法です。

Python
while remaining:
    ready = [k for k in remaining if previous_step_done(k)]
    k = min(ready, key=lambda k: ops[k]["due"])   # 納期の早い作業から
    s = max(previous_step_end(k), 0)
    for busy_start, busy_end in sorted(busy[ops[k]["machine"]]):
        if s + ops[k]["dur"] <= busy_start:
            break                                  # すき間に入る
        s = max(s, busy_end)
    busy[ops[k]["machine"]].append((s, s + ops[k]["dur"]))

この方法は、ホワイトボードやExcelで計画を組むときの考え方に近く、計算もすぐに終わります。

実行した結果

同じデータで二つの方法を実行した結果です。

スクロールできます
方法遅れた注文遅れの合計全部の作業が終わる日時計算の時間
最適化(CP-SAT)0件0稼働日11月6日 9:151秒未満
納期の早い順1件1稼働日11月9日 13:301秒未満

最適化は「これ以上よい答えはない」(最適)と判定されました。この規模(作業39件・機械7台)なら、最適な計画が一瞬で見つかります。

納期の早い順で遅れた注文

遅れたのは、治具プレート10枚の注文(24-0120)です。納期は計画の期間の最終日の11月6日で、20件の中で一番遅い組でした。

この注文は、マシニングセンタで1,260分(約2.6日)かかる、期間の中で一番長い作業です。納期の早い順に並べると、納期の遅いこの注文は最後に回され、期間に収まりきらずに11月9日まではみ出しました。

納期の早い順という決め方は、作業の長さを見ていません。長い作業ほど後に回すと危ない、という点は、人が手で組むときにも起きやすい落とし穴です。

最適化が出した答え

最適化は、この治具プレートを早めに流し、11月2日に終わらせていました。その分、マシニングセンタのほかの注文の順番を入れ替え、それぞれの納期に間に合わせています。

機械ごとの負荷を見ると、NC旋盤1号機とマシニングセンタは、期間のほとんどの日で稼働が100%になっていました。空き時間のない計画を、納期を守りながら組めている状態です。

現場の条件を一つずつ足す

ここまでで作ったプログラムは、作業者も機械の故障なども考慮していません。そこで、現場でよくある条件を一つずつ足し、そのつど解き直して計画の変わり方を確かめました。

スクロールできます
足した条件遅れた注文遅れの合計全部の作業が終わる日時計算の時間
なし(最小の形)0件0稼働日11月6日 9:151秒未満
代わりの機械0件0稼働日11月5日 9:001秒未満
機械を1日止める2件2稼働日11月9日 9:151秒未満
日をまたがない作業0件0稼働日11月6日 13:151秒未満
作業者の人数4件12稼働日11月13日 9:1520秒ほど
作業の固定0件0稼働日11月6日 9:151秒未満
5つすべて6件13稼働日11月13日 9:1530秒(上限で打ち切り)

どの条件も、プログラムに数行から十数行を足すだけで入りました。上の5行は、それぞれの条件を一つだけ足したときの値です。最後の行は、5つの条件を順に足していき、すべて入れた状態で実行した値です。

作業者の条件が一番効いた

作業者の条件は、「NC旋盤の1号機と2号機は担当者A、レーザー・曲げ・溶接は担当者B、マシニングセンタと組立は担当者C」のように、7台の機械を3人に割り当てるものです。作業者1人は、同時に1つの機械しか動かせません。

Python
# 作業者1人は、同時に1つの機械しか動かさない
by_worker = defaultdict(list)
for mc, intervals in by_machine.items():
    # 機械を止める時間(down_…)は、作業者の予定に数えない
    by_worker[WORKER[mc]].extend(iv for iv in intervals if not iv.Name().startswith("down_"))

for intervals in by_worker.values():
    m.AddNoOverlap(intervals)

この条件を足すと、遅れた注文は0件から4件に増え、遅れの合計は12稼働日になりました。一番遅れたのは、NC旋盤2号機で作るスペーサー1,200個の注文(24-0112)で、5稼働日の遅れです。

担当者Aが1号機と2号機の両方を受け持つため、2台の旋盤を同時に動かせなくなったことが原因です。機械の台数が足りていても、動かす人が足りなければ計画は回らないことが、数字ではっきり出ました。計算にも20秒ほどかかるようになり、条件の重さが計算の時間にも表れています。

機械の停止と代わりの機械

NC旋盤1号機を10月28日に1日止めると、遅れた注文が2件出ました。どちらも1号機で旋削するシャフトの注文(24-0109・24-0116)で、それぞれ1稼働日の遅れです。

逆に、1号機の作業を2号機でもできるようにすると、遅れは0件のまま、全部の作業が終わる日が1日近く早まりました。ボトルネックの機械に代わりがあるかどうかで、計画の余裕が大きく変わります。

止める時間は、動かない作業として機械に入れるだけで表せます。

Python
# NC旋盤1号機を、3日目(10月28日)に1日止める
by_machine["NL-1"].append(m.NewFixedSizeIntervalVar(2 * 480, 480, "down_NL-1"))

決まった作業を固定して組み直す

もう始めた作業や、取引先と約束した日の作業は、開始の時刻を固定してから解きます。翌日に組み直すときは、前日までに始めた作業をすべて固定し、残りだけを解き直す使い方になります。

日をまたがない条件と作業の固定は、今回のデータでは遅れを生みませんでした。ただし、日をまたがない条件を足すと、全部の作業が終わる時刻は4時間遅くなりました。夕方で止められない作業が多い工場ほど、この条件が計画を圧迫します。

条件をすべて足した計画

5つの条件をすべて入れて実行すると、遅れた注文は6件、遅れの合計は13稼働日になり、全部の作業が終わるのは11月13日の朝までずれ込みました。

計算は、時間の上限にした30秒で打ち切られ、「30秒で見つかった一番よい答え」という表示になりました。条件が増えると、最適だと確かめきれないまま答えを出すことになります。時間を延ばせばもう少しよい答えが見つかる可能性はありますが、毎朝計画を作り直す使い方では、どこまで待てるかを決めておく必要があります。

遅れた6件のうち、一番大きいのはスペーサーの注文(24-0112)の5稼働日でした。条件を一つだけ足したときと同じく、旋盤を受け持つ担当者Aの手が回らないことが、遅れの中心になっています。

自作して分かった難しいところ

計画の計算そのものは、短いコードで動きました。一方で、実際の工場で使うことを考えると、計算以外の所に難しさがあると分かりました。

計算より元のデータづくりが重い

今回は、すべての工程の段取り時間と1個あたりの時間を、架空の値として決めてから計算しました。実際の工場では、工程ごとの作業時間がそろっていないことが多く、この表を作るのが一番時間のかかる作業になります。

作業時間が実際とずれていれば、どれほどよい計算をしても、計画は現場と合いません。作業日報から工程ごとの時間を集め直すところから始める必要があります。

条件を足すほど確かめが重くなる

条件を書き足すこと自体は、数行で済みました。難しいのは、書いた条件が現場の決まりと合っているかを確かめることです。

たとえば作業者の条件と機械の停止を一緒に使うときは、機械を止める時間を作業者の予定から外す必要があります。外さないと、機械が止まっているだけの時間にも作業者が働いていることになり、計画が実際より窮屈になります。条件どうしが影響し合う所は、一つずつ足して結果を確かめないと気づけません。

まだ入れていない条件もあります。

  • 段取りの順番:同じ材料を続けると段取りが短くなる
  • 夜間の無人運転:人がいない時間だけ機械が動く
  • 機械ごとの速さの違い:代わりの機械のほうが遅い

特に段取りの順番は、作業の並び方で時間が変わるため、書き方が難しく計算も重くなります。

計画を直す画面が要る

計算で出した計画も、特急の注文や機械の故障があれば、その日のうちに直す必要があります。今回のプログラムは、作業を固定して解き直すことはできますが、画面の上で作業を動かして直すことはできません。

現場では、ガントチャートの上で作業を動かし、影響を見ながら直す画面が欠かせません。製品の生産スケジューラの価値の多くは、この「人が直すための画面」にあると感じました。

自作と市販製品の使い分け

自作が向くのは、条件が少なく、社内にコードを書いて直せる人がいる場合です。条件が多い工場や、毎日計画を直す工場では、製品を使うほうが早く確実です。

場面向く方法
機械が少なく、まず自動化の効果を試したい自作(今回の形)
Excelの計画表を少しだけ楽にしたいExcelの工夫、または安いクラウドの計画ボード
作業者や段取りの条件が多い製品の生産スケジューラ
特急や故障で毎日のように組み直す画面で直せる製品の生産スケジューラ
自社の特殊な条件が製品で表せない製品と自作の組み合わせ、または開発の外注

自作で一番の価値は、「自社の計画のどこが詰まっているか」を数字で確かめられることです。今回も、旋盤とマシニングセンタがほぼ満杯であること、長い作業を後回しにすると遅れること、旋盤を動かす人が足りないと遅れが一気に増えることが、計算から分かりました。

製品を選ぶ前に、受注・在庫・原価まで含めた生産管理の仕組みから見直したい場合は、次の記事で中小製造業向けの生産管理システムを比べています。

まとめ

PythonとOR-ToolsのCP-SATを使うと、受注20件・機械7台のジョブショップスケジューリングを、短いコードで解けました。最適化の計画は遅れが0件で、納期の早い順に詰めた計画より、遅れが1件少なくなりました。

現場の条件を足すと、計画ははっきり変わりました。中でも作業者の条件は、遅れを0件から4件に増やし、機械より人がボトルネックになることを示しました。5つの条件をすべて入れると遅れは6件になり、計算も30秒の上限で打ち切られました。

一方で、実際の工場で使うには、工程ごとの作業時間をそろえるデータづくりと、条件が現場と合っているかの確かめ、計画を直す画面が必要です。自作は「自社の計画がどこで詰まるか」を確かめる道具として始め、毎日の計画は製品と組み合わせるのが現実的だと考えます。

過去の案件のデータを業務に生かす仕組みとしては、「AI見積りは下書きまで|過去の似た案件を引く見積り自動化の設計」も参考にしてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

1981年生まれ、名古屋出身。株式会社ニューロシンク代表。

2008年に渡独し、ベルリンで約18年間エンジニアとして活動。ドイツの国家資格である職業訓練プログラム「アプリケーション開発専門IT技術者」を修了後、医療系の自社開発企業でデスクトップ・Webアプリケーションの開発に4年間従事しました。2022年からはドイツの大手SIer「Adesso SE」で、フルスタックエンジニアとしてリードポジションを務めました。

2026年に日本へ帰国し、同年9月に株式会社ニューロシンクを設立。愛知・岐阜・三重の町工場を主な対象に、AI導入と業務システムの改善を支援しています。提案だけで終わらせず、工場に足を運んで一緒に手を動かす。そんな現場に立つAIエンジニアでありたいと考えています。

著書「AI時代の海外移住戦略」

コメント

コメントする

目次