はじめに
数理最適化の実務適用において、設定した条件間の矛盾による実行不可能(Infeasible)は非常に厄介です。ソルバーから既約矛盾部分集合(IIS)を抽出することはできますが、それは内部で展開された数式そのもの。現場の業務要件とは粒度が乖離しており、これを読み解いて「どの設定がぶつかっているのか」を突き止めるには、結局は数理最適化に詳しい社員による「翻訳」が必要になります。矛盾のたびに専門の人へHelpを出していては、属人的業務の置き場所が変わっただけとも言えます。
数理モデルと現場の意図(条件)には意味的なギャップが存在しておりIISから矛盾を説明する場合その翻訳が必要になります。当然、現場の担当者は数理最適化に詳しいわけではありませんので、その翻訳作業をする人が必要になります。
この記事では、その「翻訳」の部分をLLMに任せることで、現場が自分たちだけで矛盾に対処できる範囲を広げてみた話をします。
背景:現場で完結させたい
シフト表の制約条件は、突き詰めるとドメイン知識のかたまりです。「夜勤の翌日は必ず休む」「この日は3名以上いないと現場が回らない」といった条件は現場を知る人でなければ書けず、スタッフの入れ替わりでも変わっていきます。とすれば、専門スタッフが毎回ヒアリングして数理モデルに落とす運用は筋が悪く、専門スタッフがボトルネックになります。条件を一番よく分かっている現場の人自身が、制約条件を書き換えられる状態が理想です。
そこで弊社では、スプレッドシート上に独自のDSL(ドメイン固有言語)で条件を記述できる仕組みを用意しています。現場担当者が表形式のUIで条件を設定し、Google Colab上で最適化を実行して結果を返す。
ただし、担当者が無邪気に条件を足すと矛盾が乱立します。自力で見つけて直せる担当者もいますが、全員ではありません。「矛盾の発見・説明・修正提案」をLLMに任せられれば、現場完結の理想形に近づけると思います。
矛盾の発見と説明
矛盾の発見(IIS抽出) はソルバー任せです。ソルバーには、実行不可能なとき「どの制約同士が両立しないか」を教えてくれる機能があり、それをそのまま使っています。
矛盾の説明 にはGeminiのGemを使いました。Colabがスプレッドシートを読み、Colab環境でソルバーを実行します。実行不可能なら矛盾情報を整形してテキスト出力する。ユーザーはそれをコピペしてGemに投げるだけ、というものです。
工夫したところ
① 矛盾を「人間の設定粒度」まで引き上げる。
ソルバーが返す数式レベルの矛盾を、スプレッドシート上で設定した「どの条件」にあたるかまで引き上げて返します。内部でいくつの数式に展開されていようと、矛盾は「担当者が実際に編集する単位」の粒度でまとめます。数式の矛盾ではなく「このルールとこのルールがぶつかっている」という形で示してGem(LLM)に渡します。
② 関連する入力データをセットにする。
矛盾に関与した条件と条件が参照しているメンバーやその属性情報などを一緒に添付します。また、入力シートには計算には使わない「意味」列があり、担当者がどんな意図でその条件を設定したかを日本語でメモできるようになっています。この設定意図もGemに渡すことで、Gemは数理的な事実だけでなく「担当者が何をしたかったのか」まで踏まえて説明できます。
③ サポート用の資料をGemに格納する。
システムの仕様やシート構造の説明書に加え、ふるまいの指示も入れています。条件Aと条件Bが矛盾するなら、Aを直す以外にBを直す手もあります。各条件の修正案に対して現場の意図をヒアリングするよう調整しました。
この格納資料はいろいろ試して調整しました。結構なボリュームになっています。
実際に矛盾を作って説明させてみた
ぱっと見では分からない矛盾を作ります。ターゲットは6/6(土)。設定した条件は次の3つです。
- 夜勤シフトの前日は必ず休みにする
- 佐藤さんは6/6を休み希望にする(田中さんは6/7に夜勤を希望)
- 6/6は出勤者を3名以上確保する
条件1と条件3は、単独では衝突しません。ところが「田中さんが6/7に夜勤を希望 → 条件1により6/6は休みである必要がある」という一段の推論が挟まると、6/6は佐藤さん・田中さんの2人が休むことになり、3名以上を確保できなくなります。
ちなみに実行不可能になったとき、ソルバーが返すIISはこんな数式です。
x_佐藤_6/6_休 = 1
x_田中_6/7_夜勤 = 1
x_田中_6/6_夜勤 + x_田中_6/6_日勤 <= 1 - x_田中_6/7_夜勤
x_田中_6/6_出勤 + x_佐藤_6/6_出勤 + x_鈴木_6/6_出勤 + ... >= 3
間違ってはいませんが、担当者がこれを読んで「どのセルを直せばいいか」を判断するのは酷です。そこでColabは、この数式を担当者の設定粒度まで引き上げ、関連データや、担当者がメモした「意味」欄まで添えて、次のようなテキストに整形します。
【シフト最適化 矛盾診断結果】
── 矛盾 1 ──
◆ 希望:必ずこの中から 佐藤
└ 該当: 06/06
◆ 希望:必ずこの中から 田中
└ 該当: 06/07
◆ 必要人数[ID:0] 今月/出勤/全員 「勤務に3名以上入る」 の最小(3)を下回らない
◆ 禁止パターン『出勤>夜勤』(対象:全員)── 関連データ(システムが自動添付・現在の設定値)──
希望シート 佐藤 × 06/06 のセル内容 → 「$休」
希望シート 田中 × 06/07 のセル内容 → 「$夜勤」
シフトパターン ID:1 → 対応:禁止 / メンバー:全員 / パターン:出勤>夜勤 /
意味:夜勤シフトの前日は必ず休みである必要がある
必要人数範囲 ID:0 → 日付:今月 / シフト:出勤 / メンバー:全員 / 最小:3 / 最大:(空) /
意味:勤務に3名以上入る
このテキストをコピペしてGemに投げると、こう返ってきます。
【Gem出力(一部抜粋)】
シフト表は作成できていません。確認してほしいことが 1 件あります。
① 佐藤さんの6/6の希望、田中さんの6/7の希望、および必要人数の件
田中さんが 06/07 に "夜勤" を入れるため、シート「シフトパターン」の ID:1 のルール(夜勤の前日は休み)により前日の 06/06 は休みである必要があります。しかし 06/06 は佐藤さんも "$休" を希望しており、2人が同時に休むと 06/06 の必要人数(最小3名)を満たせなくなります。
決め手の質問: 佐藤さんの6/6の休み、または田中さんの6/7の夜勤のどちらかの希望を変更することは可能ですか?
- 「佐藤さんが6/6に出勤してもよい」場合: 希望シートの 佐藤さん × 06/06 の "$休" を消して空欄にしてください。
- 「田中さんが6/7の夜勤を変更できる」場合: 希望シートの 田中さん × 06/07 の "$夜勤" を消して空欄にしてください。
- 「6/6は2名に減ってもよい」場合(応急処置): シート「必要人数範囲」の ID:0 の「日付」列の 今月 を 今月-06/06 に書き換えてください。あわせて「意味」欄も実態に合わせて書き直してください。
- 「できるだけ3名以上を守りたいが、無理な日は下回ってもよい」場合: ID:0 の「最小外を許容」列に ● を入れてください。
矛盾の連鎖を正しくたどり、「夜勤の前日は休み」というルール経由で6/6が塞がることまで説明できています。解決策も、複数パターン提示してくれました。
文量が多いと感じたら、対話で絞り込むこともできます。Gemはチャットベースなので、そのまま要望を投げるだけです。
ユーザー: 6/6は特別な日だから3人いなくてもいいよ
Gem: 承知いたしました!それであれば、6/6だけ必要人数のルール(3人以上)を外す設定に修正しましょう。(以下略)
このように、対話の中で意図を汲み取って解決策を絞り込んでくれます。
おわりに
これまでは数理最適化に詳しい社員が引き受けていた「矛盾の翻訳と修正案の提示」を、LLMが一次対応として肩代わりしてくれるようになりました。
これによって現場は現場完結で計画を作成することが可能になり、数理最適化に詳しい社員は別の業務により専念することができるようになります。
今回はシフト表に特化しましたが、矛盾の解説と修正提案は他の最適化システムにも同じくニーズがあります。引き続き試していこうと思います。
