Chapter 9

本番に乗せる — 実装と運用のリアル

ここまでで、必要な道具はすべて手に入った。
最後の章は、それを本物のシステムにする話。
コードは動く。でも本番にデプロイするまでには、まだ景色が広がっている。

この章は他の章より泥臭い
ツール選び、アーキテクチャ、性能調整、観測 (observability)、12ヶ月の導入計画 —— 教科書では拾い切れない実戦知だ。

9.1 まずアーキテクチャの全体像

本番システムの参照アーキテクチャを 1 枚にすると、こうなる。

アプリ層 UI / API / ジョブキュー / ワークフロー FastAPI · Temporal · React ドメイン層 解の表現 / 制約 / 評価 / 説明用メタデータ SchedulingPlan, Job, Machine, Operation, Calendar 構築層 初期解 / フォールバック Dispatching rules Clarke-Wright NEH, Insertion メタヒューリスティクス層 改善の主体 ALNS (alns lib) Tabu / SA (自作) LKH-3 (TSP) 厳密層 サブ問題 / 検証 OR-Tools CP-SAT Gurobi / HiGHS CPMpy (DSL) データ層 マスタ / オーダー / 実績 / 計画スナップショット PostgreSQL · Redis · Parquet (履歴) 観測層 下界・上界・収束カーブ・制約違反ログ・解の差分
図 9.1 — 参照アーキテクチャ。最適化エンジンは中心だが、それを取り巻く層がシステム全体を動かす。

9.2 ライブラリ早見表

ツール系統特徴料金
🌟 OR-Tools / CP-SATCP+SATスケジューリング最強。Python から触れる無料 (Apache)
GurobiMIP世界最速の商用 MIP有料
HiGHSLP/MIP純 OSS で高速。SciPy 標準搭載無料 (MIT)
SCIPMIP/CP研究志向のフレーム学術無料
CPMpyCP DSLPython で書いて複数ソルバーに流す無料
🌟 alns (N-Wouda)ALNS フレームdestroy/repair を差し替えるだけで使える無料 (MIT)
Pyomoモデリング言語多ソルバー対応、研究で頻出無料
Timefold (OptaPlanner 後継)制約ベースメタJava、ドメイン駆動 API無料
Hexaly (旧 LocalSolver)商用メタ「解けてしまう」と評判有料
LKH-3TSP/VRP の Lin-Kernighan世界記録級研究用

🌟 マークが私の最初のおすすめ
CP-SAT + alns + Python の組み合わせで、ほぼあらゆる実問題が叩ける。

9.3 CP-SAT を効かせる小技

並列ワーカーを忘れない

solver = cp_model.CpSolver()
solver.parameters.max_time_in_seconds = 60
solver.parameters.num_search_workers = 8       # ← これ一行で何倍も速くなる
solver.parameters.log_search_progress = True

num_search_workers は CP-SAT が並列に 異なる戦略で探索するワーカー数。 CPU コア数程度に上げるだけで、シングルスレッドの 3〜10 倍速くなる。これを忘れる人が多い

ヒント (warm start) を渡す

# ヒューリスティクス層が作った初期解 schedule_init を渡す
for (j, o), start_value in schedule_init.items():
    model.AddHint(start_vars[(j, o)], start_value)

良いヒントがあると CP-SAT は序盤で良い UB を取れる → 枝刈りが効く → ぐっと速くなる。

収束カーブを記録する

class Logger(cp_model.CpSolverSolutionCallback):
    def __init__(self, makespan):
        super().__init__()
        self.makespan = makespan
        self.history = []
    def on_solution_callback(self):
        self.history.append((self.WallTime(), self.Value(self.makespan)))

logger = Logger(makespan)
solver.SolveWithSolutionCallback(model, logger)
# あとで history を眺めて「どこで止まったか」を見る

9.4 ALNS の実装テンプレ

Python の alns ライブラリを使うと、フレームワークが全部用意されている。
あなたがやるのは「destroy 関数」「repair 関数」を書くだけ。

from alns import ALNS, State
from alns.accept import RecordToRecordTravel
from alns.select import RouletteWheel
from alns.stop import MaxIterations
import numpy.random as rnd

class ScheduleState(State):
    def __init__(self, schedule):
        self.schedule = schedule
    def objective(self):
        return makespan(self.schedule)

# destroy: 関連の深いジョブを一括除去
def related_removal(state, rng, frac=0.2):
    s = copy.deepcopy(state)
    k = int(len(s.schedule.jobs) * frac)
    for j in pick_related_jobs(s.schedule, k, rng):
        s.schedule.remove(j)
    return s

# repair: 後悔値で挿入
def regret_insertion(state, rng):
    s = copy.deepcopy(state)
    while s.schedule.has_unscheduled():
        j = pick_max_regret(s.schedule)
        s.schedule.insert_at_best(j)
    return s

alns = ALNS(rnd.default_rng(42))
alns.add_destroy_operator(related_removal)
alns.add_destroy_operator(random_removal)
alns.add_repair_operator(regret_insertion)
alns.add_repair_operator(greedy_insertion)

result = alns.iterate(
    build_initial_state(problem),
    select=RouletteWheel(scores=[5, 2, 1, 0.5], decay=0.8, num_destroy=2, num_repair=2),
    accept=RecordToRecordTravel(0.05, 0.0, 1e-4),
    stop=MaxIterations(20_000),
)

9.5 王道のハイブリッド: LNS × CP-SAT

本書の現代的なクライマックス: ALNS の repair を CP-SAT で厳密に解く

def hybrid_lns(problem, initial, time_budget):
    current = initial
    best = current
    end = time.time() + time_budget
    while time.time() < end:
        # 1) 一部 (例: 機械 1 台 / ある日) を抜く
        fixed, free = pick_neighborhood(current)
        # 2) free 部分だけ CP-SAT で厳密最適化 (時間制限 30 秒)
        model = build_cp_model(problem, fixed=fixed)
        solver = cp_model.CpSolver()
        solver.parameters.max_time_in_seconds = 30
        solver.Solve(model)
        candidate = extract_schedule(model, solver)
        if candidate.makespan < best.makespan:
            best = candidate; current = candidate
        elif accept(current, candidate): current = candidate
    return best

全体は重すぎて厳密に解けない。でも 10% だけなら CP-SAT が一瞬で解く。
これを何度も繰り返す。
これが今の生産スケジューリングの王道

9.6 計画を一級市民にする

これは本書でいちばん声を大にして言いたい部分。

本番運用では、計画は単なる「ジョブ列」ではない。
生成過程・根拠・前計画への参照を含む、オブジェクトとして保存すべきだ。

SchedulingPlan {
    id: UUID
    created_at: timestamp
    horizon: TimeWindow
    inputs_snapshot_id: UUID        # 入力データの凍結ID
    parameters: { ... }              # ソルバ設定
    objective_values: {
        makespan: 132.5,
        total_tardiness: 18.0,
        total_setup: 24.0
    }
    solver_metadata: {
        solver: "cp-sat",
        wall_time: 28.4,
        lower_bound: 128.0,
        gap_percent: 3.5,
        status: "FEASIBLE"
    }
    decisions: [...]                 # 各ジョブの割当て・開始・終了
    parent_plan_id: UUID?            # 前計画への参照
    explanations: [...]              # クリティカル工程・選択根拠
}

こうしておくと、後でこういうことができる:

Terraform の Plan/Apply、CockroachDB Optgen、Kubernetes Scheduling Framework と同じ哲学だ。 計画はオブジェクトであって、ログじゃない

9.7 ハマる症状と対策

症状たぶんこれやること
INFEASIBLE が出る制約矛盾、ホライズン短い制約を 1 つずつ無効化、ホライズン倍に
ソルバー黙る変数 / ホライズン超巨大時間粒度見直し、固定範囲を広げる
初期 UB 遅いヒントなしヒューリスティクス層からヒント供給
下界が伸びないLP 緩和弱い、対称性多い対称性除去、強い大域制約
毎日解が変わる同値最適が多い、過剰最適化二次目的に「前計画との距離」
制約足したら遅くなったビッグ M デカすぎ、対称性増加定式化見直し、グルーピング
本番だけ遅いマスタデータの外れ値本番データをベンチに混ぜる

9.8 観測 (observability) は最初から

最適化エンジンをブラックボックスにしないこと。 最低限ロギングすべき:

9.9 機械学習を、どこに、どれくらい入れるか

2025〜2026 年の文献を眺めると、ML を組合せ最適化に入れる場所は 3 つに収まる。

大胆派: ML が計画する

GNN + PPO で end-to-end に解を構築。
🟢 強い
🔴 分布シフトに弱い、説明できない

慎重派: ML が探索を導く

ALNS のオペレータ選択を Q-learning など。
🟢 古典手法を下回らない
🔴 派手さはない

そして最も効くのは地味なやつ:

パターン3: ML がパラメータを予測する —— 処理時間予測、納期予測、不良率予測。
最適化の前段のデータの質を上げる方が、アルゴリズム改良より遥かに効くことが多い。

9.10 12 ヶ月導入のたたき台

時期やること
M1〜M2業務ヒアリング、α
M3データ整備、CP-SAT で MVP 定式化
M4ベンチマーク用データ生成、初期解 + CP-SAT で「動く計画」
M5〜M6制約を実業務に合わせる、性能チューニング、ALNS 層
M7UI 試作 (ガントチャート、What-if)
M8〜M9Rolling Horizon、再計画運用、SLA テスト
M10シャドー運用 (人間と並列に出力 → 比較)
M11本番並行運用、L2 説明可能性
M12切替、KPI モニタリング、機能拡張

9.11 最後に伝えたいこと

組合せ最適化は、見方によっては絶望的な分野だ。
NP困難、指数爆発、保証なし、現場では使ってもらえない…

だが本書を読んでくれた今、わかってもらえるはず —— そんなに絶望でもない

本書を通じて繰り返してきたメッセージ:

  • 📌 アルゴリズムより、問題の表現を先に考える
  • 📌 アルゴリズムの汎用性より、問題の構造を使う
  • 📌 厳密 vs 発見的じゃない。協調させる
  • 📌 一発で完璧な計画より、更新可能な計画
  • 📌 最適化の品質より、現場の信頼が本番投入を決める

そして最大の合言葉は ——

「そもそも、この問題は最適化で解くべきなのか?」
「そもそも、この制約は本当に必要か?」
「そもそも、この目的関数で合っているか?」

最も鋭い最適化は、しばしば**「解かない」という選択**から始まる。

読了ありがとう。
最後は付録 (用語集と参考文献)。ここから先の旅の地図として使ってほしい。