このページは 利用手順書 の「もっと詳しく知りたい」に応える解説です。 flplab が内部でどのような数理モデルを組み立て、どういう考え方で計算・表示しているかを、 設計方針と施設配置問題の解説レポートをもとにまとめています。ソースコードやファイル構成には 触れず、考え方と数式だけを説明します。
施設配置問題(Facility Location Problem, FLP)は、「需要が存在する地点に対して、どこに施設を 配置すれば、所定の制約を満たしながらコストを最小化/サービスを最大化できるか」を求める数理最適化 問題です。倉庫・工場・店舗・病院・消防署・データセンター・EV充電ステーションなど、対象は幅広く 共通しています。
基本的には次の2つを同時に決めます。
flplab は、需要地点と施設候補を地図上に置き、目的関数と制約を選んで 最適化実行 を押すと、HiGHS というソルバ(数理最適化の計算エンジン、 ブラウザの中だけで動くよう変換されたもの)がこの2つを厳密解として求める、 ブラウザ内だけで完結するデモ・学習用ツールです。
flplab は UFLP・CFLP・p-Median・p-Center・Set Covering・Maximal Covering(MCLP)の 6モデルに対応します。ただしこれらは「6本の別プログラム」ではなく、 目的関数の選択と制約のON/OFFという組合せの中の代表点です(詳しい理由は 7章)。まず、それぞれが何を計算しているかを確認します。
| モデル | 何を最小化/最大化するか | 特徴 |
|---|---|---|
| UFLP 容量なし施設配置 | 総コスト最小 (固定費+輸送費) | 最も基本的な型。施設の容量制約なし |
| CFLP 容量制約付き施設配置 | 総コスト最小 | 各施設が扱える需要量に上限がある |
| p-Median | 総距離最小 | 開設施設数をちょうど p 個に決めたうえで、移動距離の合計(重み付き)を最小化 |
| p-Center | 最大距離最小 | 最も不利な需要地点の距離を最小化する。公平性を重視 |
| Set Covering | 総コスト最小 | 全需要地点を半径内に収める配置のうち、最小コストのものを求める |
| Maximal Covering MCLP | カバー需要最大 | 施設数の上限内で、カバーできる需要量(重み付き)を最大化 |
p-Median と UFLP の違いは「施設数を最適化するか、指定するか」「固定費を持つか」「主目的が コストか距離か」に集約されます。p-Center は p-Median の「総距離最小」を「最悪地点の距離最小」に 置き換えたものです。Set Covering と MCLP は「どの施設が担当するか」ではなく「カバーできているか」 だけを問う、構造的に別のファミリーです(この違いが計算の組み立てにどう表れるかは 7章)。
以下、需要地点を添字 i、施設候補を添字 j で表します。 fj は施設 j の固定費、 cij は需要 i を施設 j が担当するコスト、dij は両者の距離、 yj は施設 j を開設するかどうか、 xij は需要 i を施設 j に割り当てるかどうかを表します。
予算制約・容量制約・最大距離制約・需要分割可否は、UFLP/CFLP/p-Median/p-Center の4モデルに対して 個別にON/OFFできる横断的な設定です。6モデルはこの組合せの中の代表点であり、ユーザーは6モデルから 選ぶだけでなく、制約を個別に足し引きして「6モデルのどれとも一致しないカスタムな設定」を作れます。 たとえば予算制約は次のように追加します。
FLPは非常に大きな問題ファミリーで、複数施設タイプ・多階層・複数期間・不確実性・競合施設配置・ Location-Routing・実道路距離・準最適解の列挙など、拡張の方向は数十通りあります。 flplab はデモ・啓蒙用途にスコープを絞るため、これらを明示的に対象外としています。
flplab が扱う状態はすべて「シナリオ」という1つのまとまりに集約されます。計算結果(解)は シナリオを書き換えたものではなく、シナリオから毎回作り直される派生物です。
保存・比較・再現はすべて、このシナリオだけを見れば足ります。シナリオが持つ情報は次の通りです。
| 情報 | 内容 |
|---|---|
| 需要地点 | 位置、需要量、目的関数上の重み |
| 施設候補 | 位置、開設した場合の固定費、容量 |
| 距離の扱い | 2点間の直線距離に掛ける迂回係数 |
| 目的関数 | 総コスト・総距離・最大距離・カバー需要のどれを対象にするか |
| 制約 | 施設数のモード(自由/固定/範囲)、容量・予算・最大距離・カバー制約のON/OFFと値、需要分割の可否 |
| 強制開設・強制閉鎖 | 施設ごとの「自由(ソルバに任せる)/必ず開設/必ず閉鎖」の記録 |
| ソルバ設定 | 制限時間、許容ギャップ(0に近いほど厳密だが時間がかかる) |
容量制約が消費する「量」(需要量)と、目的関数が重視する「重要度」(重み)は概念上別物です。 既定値は同じにしておき、必要なら編集で分離できるようにすることで、どちらの意味の「需要」も 表現できるようにしています。
「この施設は必ず開ける/絶対に開けない」という条件は、計算結果を直接書き換えるのではなく、 シナリオ側に「この施設は開設固定/閉鎖固定」という記録として持ちます。計算するときにこの記録を 読み取り、対応する開設変数の値を1つに固定するという形で制約に反映します。こうすることで、 「本当の状態」がシナリオと計算結果の2か所に分かれてしまう事態を避けています。
flplab の内部は、「画面(ブラウザの表示)に直接触れるかどうか」を境界にして、大きく2つの役割に 分かれています。
依存の向きは一方通行(状態管理 → 画面表示 → 計算ロジック)で、逆方向の呼び出しはありません。 この境界を厳密に引いているのは、画面を一切開かずに計算ロジックだけを自動テストできる ようにするためです。新しい計算ロジックを追加するときは、画面表示のコードに計算を 混ぜ込まず、計算ロジックの層に置くという原則を守っています。
これは、姉妹ツールである別の配送計画用アプリからの最大の逸脱です。そちらのアプリは 「パラメータを動かすと一瞬で結果が変わる」体感を優先し、厳密解ではなく近似的な計算手法を 採用していました。flplab は厳密解を採用したため、この体感は成立しません。問題の規模次第で 計算に数百ミリ秒〜数十秒かかるためです。
flplab の最も重要な設計思想は、「UFLP・CFLP・p-Medianという固定的な計算を個別に用意するのではなく、 パラメータの組合せから数理モデルを生成する」ことです。
Set Covering の目的関数は「固定費の総和の最小化」で、UFLP・CFLPと同じ形をしています。そのため 目的関数だけでは、どちらの数式グループ(割当を扱う4モデルか、カバーだけを扱う2モデルか)を 使うべきかを判別できません。この判定は目的関数ではなく、カバー制約(半径以内に施設が 1つ以上あることを要求するチェックボックス)がONになっているかどうかで行います。 ONならSet Covering/MCLPの数式グループ、OFFならUFLP/CFLP/p-Median/p-Centerの数式グループを 使う、という単純な切り替えです。
| 要素 | 内容 |
|---|---|
| 決めること | 各施設 j を開設するか(yj)、 各需要 i をどの施設に割り当てるか(xij) |
| 基本の制約 |
各需要地点は必ず1つの施設に割り当てる:
Σjxij
= 1
開設していない施設には割り当てられない:
xij
≤
yj
|
| 任意で追加できる制約 | 容量制約・予算制約・最大距離制約(超える組合せはそもそも割当の選択肢に含めない)・需要分割の許可 |
最大距離制約を厳しく設定すると、ある需要地点にとって到達可能な施設が1つもなくなり、 「必ず1つの施設に割り当てる」を満たせなくなることがあります。flplab のこのバージョンでは、 これを何らかのペナルティで救済することはせず、解が存在しない(実行不可能) という結果をそのまま表示し、「制約を緩めてください」と案内するだけに留めています。 未対応の需要を許容する仕組みは複雑さが増すため、将来の拡張候補として意図的に見送っています。
この2モデルは「どの施設が担当するか」を決めません。決めるのは施設の開設 (yj)と、カバーされているかどうか (MCLPの zi)だけです。割当を決める変数を持たないため、 同じ規模の問題でも割当系より変数の数が少なく、計算が軽くなります。設定画面側では、目的関数が カバー系のときは容量・予算・需要分割の項目を非表示にします(割当を前提とする設定のため、 割当変数を持たないこの2モデルには意味を持たないため)。
6モデルの選択は、選んだ瞬間に目的関数と制約をその型の標準的な組合せへ一括で上書きする 操作です。排他的な「モード」ではないため、選択後も個々の制約チェックボックスで自由に調整でき、 その場合は表示が「(カスタム)」に切り替わります。これは仕様です。
厳密解を求める計算エンジン(HiGHS)は、ブラウザの中で直接動くようにコンパイルされたものを 使っています。重い計算で画面が固まらないよう、ブラウザのバックグラウンド実行の仕組み (Web Worker)の中でこのエンジンを動かします。
6章で触れたとおり、このエンジンは1度起動したら使い続けます。 最適化実行 を押すたびに、その時点のシナリオを数式に変換して エンジンに渡し、計算が終わると結果を受け取って画面に反映します。制限時間を超えても応答が なければ、安全装置がエンジンを強制終了して再起動し、「制限時間を超えたため打ち切りました」と 案内します。
「施設数を変えたら総コストがどう変わるか」を調べる感度分析は、新しい仕組みを追加するのではなく、 施設数を1件から指定した最大数まで1件ずつ増やしたシナリオを順番に同じエンジンへ渡し、 1件計算が終わるたびにグラフへ1点ずつ追記する、という繰り返し処理です。エンジンを使い回すため、 エンジンの再読み込みは発生しません。途中で中断したいときは、次の計算に進まないよう内部の カウンタを進めるだけで止められます(エンジン自体は終了させません)。
計算エンジンが返す生の結果(内部的な変数の値の一覧)から、「どの施設が開設されたか」 「どの需要地点がどの施設に割り当てられたか」を復元し、コスト・距離・カバー率・稼働率を 独立に再計算する処理があります。この部分が最も注意が必要な場所です。
flplab では計算エンジンが厳密解を返すため、「エンジンが最適化した目的関数の値」 と「結果から独立に再計算した値」は、数理モデルへの変換が正しければ必ず一致します (わずかな浮動小数点の誤差の範囲内で)。この一致を毎回確認することで、 「数理モデルへの変換」と「結果の復元・再計算」の両方の正しさを同時に守れます。
数理モデルへの変換や結果の復元処理に手を入れるときは、必ずこの一致を確認する自動テストが 通ることを運用の前提にしています。
計算エンジンに渡す条件式を組み立てる際、「0または1しか取らない」という種類の変数は、 通常は上限・下限を明示せず「0/1しか取らない」という前提だけで扱われます。ところが、 強制開設・強制閉鎖のように、この種の変数を「必ず1つの値に固定したい」という指示を出したい場面 では、その前提のままだと固定の指示が反映されず、エンジンは通常どおり自由に0か1を選んでしまいます。
flplab では、値を固定したい変数だけ扱いを変えて、固定したい値が確実にエンジンへ伝わるようにする ことでこれを避けています。同様に「0/1の変数を特定の値に固定したい」という場面を新たに作るときは、 この落とし穴を思い出す必要があります。
画面の再構築は、パネル内で編集中の入力欄があるとスキップされる仕組みになっています (編集中の値が再描画で消えてしまう事故を防ぐため)。ただし 最適化実行/中断 はボタン です。ボタンをクリックすると、クリックしたボタン自身にフォーカスが移るため、「パネル内に フォーカスがあるかどうか」だけで判定すると、クリックした瞬間からパネル全体 (ボタンが押せない状態の見た目を含む)が固まって見える事故になります。このため、 再構築をスキップする判定は「入力欄・選択欄・テキストエリアにフォーカスがあるかどうか」に 絞ってあります。新しい編集用の部品を追加するときは、この判定に注意が必要です。
| 保持する情報 | 役割 |
|---|---|
| シナリオ本体 | 唯一の正本 |
| 直前の計算結果 | パラメータを変更しても消さず、「古くなった」という印だけを付ける |
| 結果が古いかどうかの印 | 「再実行してください」の案内を出す判断に使う |
| 比較用の2つの結果 | 案A/B比較ビューで使う |
| 感度分析の途中結果 | 1点ずつ追加していくグラフの点 |
| 計算・感度分析の世代を数えるカウンタ | 中断や、想定外の連続クリックを見分けるために使う |
| 計算中かどうかの印 | ボタンの二重クリックによる不具合を防ぐ |
| 画面上の一時的な状態 | ホバー中の施設・需要地点、ドロワーや比較ビューの開閉など |
画面に触れない計算ロジックはすべて自動テストで確認し、画面表示そのものは自動テストの対象にしません (手動確認、または画面操作を自動化するツールでの確認に委ねます)。テストが確認する内容は次の カテゴリに分かれます。
設計時点では想定していなかったものの、実際にブラウザで動かして初めて見つかった問題が いくつかありました。いずれも修正済みで、再発を防ぐ確認も追加しています。
これらのうち②③④は、自動テストだけでは検出できない種類の不整合でした。実際に画面を操作して 初めて見つかったため、以後は計算エンジンを実際に動かして結果まで確認する統合的なテストも 追加し、再発を防いでいます。
flplab は、配送計画を扱う姉妹アプリの画面設計・状態管理・地図描画の流儀を引き継ぎつつ、 計算エンジンだけは、別の汎用最適化アプリの資産(ブラウザで動く厳密解ソルバ)を移植して 厳密解に置き換えた構成です。
| 項目 | 配送計画アプリ | 汎用最適化アプリ | flplab | 採用理由 |
|---|---|---|---|---|
| 計算方式 | 近似的な計算手法 | 厳密解 | 厳密解 | 制約を付けたときの違いを、正しい最適解同士で比較できることに価値があるため |
| データの正本 | シナリオ | 表形式のデータ | シナリオ | 保存・比較・再現をシナリオ1つに集約するため |
| 計算エンジンの生存期間 | 毎回作り直す | 起動したまま使い続ける | 起動したまま使い続ける | 再読み込みの遅延を避けるため |
| 計算のトリガー | パラメータ変更で即時 | 実行ボタン | 実行ボタン | 厳密解の計算時間に画面が引っ張られないようにするため |
| 未対応ケースの扱い | 「未対応」を通常の状態として扱う | — | 実行不可能という結果をそのまま表示 | MVPでは複雑さを増やさない簡略化 |
| 計算の改善過程の再生 | あり | — | なし | 厳密解ソルバの一発計算という特性上、途中経過を取り出せないため |
| 感度分析 | なし | なし | あり | 新規に追加した機能 |
| 案A/B比較 | あり | — | あり | 引き継いだ機能 |