使い方
- Sample data で 12 件の注文を読み込みます。Random は、すべての納期を守る割当てが存在するような注文を、 指定した件数(3〜50)だけ作ります。 表では、各注文の納期と、機械ごとの処理時間を編集できます (単位は分、5 の倍数。その注文を処理できない機械は空欄にします)。+ Add order で注文を加えます。
- チャート上で注文をドラッグすると、別の機械や別の位置に移せます。表で機械を選ぶこともできます。 Fastest machines はすべての注文を最も速い機械に置き、Unassign all は割当てをすべて外します。
- Time(1〜10 秒)を選んで Solve を押します。 探索中は、それまでに見つかった最良のスケジュールがチャートに表示されます。
- 納期に遅れた注文は、納期の線とともに赤で表示されます。 集計欄には、処理時間の合計、遅れた注文の数と遅れの合計、メイクスパン、下界(最速の処理時間の和)が表示されます。 下の QUBO++ model で、解いたモデルを見られます。
このデモは資源の限られた AWS Lambda 上で動いています。 デスクトップ PC 上の QUBO++ は数倍速く動きます。
スケジュールを QUBO にする
注文 $j$ には納期 $d_j$ と、処理できる機械 $m$ ごとの処理時間 $p_{j,m}$ があります。 このデモでは、ありうる組ごとに 1 つのバイナリ変数を使います。 $y_{j,m}=1$ は、注文 $j$ を機械 $m$ で処理することを表します。
各機械では、注文を納期の早い順に処理します。 ある機械の注文をどう並べればすべての納期に間に合うなら、納期の早い順でも間に合います。 したがって、すべての納期を守るスケジュールを取りこぼすことはなく、決めるのは割当てだけになります。 注文を納期の早い順に番号付けすると、注文 $j$ が機械 $m$ で終わる時刻は次のとおりです。
\[C_{j,m} = \sum_{i \le j} p_{i,m}\, y_{i,m}\]モデルは次のとおりです。
\[\text{最小化}\quad \sum_{j,m} p_{j,m}\, y_{j,m} \;+\; W \sum_{j,m} \mathrm{relu}\big(C_{j,m} - d_j - H_{j,m}(1-y_{j,m})\big) \qquad\text{制約}\quad \sum_{m} y_{j,m} = 1 \ \text{(すべての注文 } j\text{)}\]ここで $\mathrm{relu}(z)=\max(z,0)$ です。 relu の項は、注文 $j$ を機械 $m$ で処理したときの遅れです。 $H_{j,m}$ はありうる最大の遅れなので、注文が機械 $m$ にないときこの項は 0 になります。 重み $W$ は、5 分の遅れが処理時間の合計のどんな変化よりも大きくなるように選んであります。 そのため、まず納期を守ることが優先され、その次に処理時間の合計が小さくなります。
QUBO++ では、遅れを relu() で、割当てを cons() で書きます。ソルバーはどちらも直接扱います。
そのため、モデルの変数は割当ての変数だけで、スラック変数はありません。
このモデルを、QUBO++ の ABS3 ソルバーが CPU 上で解きます。
探索が下界に達すると、そのスケジュールが最適であることが示されるので、その時点で止まります。
係数が大きくなることがあるので、このデモでは 64 ビットの係数(c64e64)を使っています。
自分のコンピュータで動かす
QUBO++ の Python 版である PyQBPP は、Linux(x86-64・ARM64)と、WSL 経由の Windows で動きます。
pip install pyqbpp
relu() と cons() は 制約のページ で説明しています。
詳しくは インストール を見てください。
QUBO++ で解く問題は、ほかのデモ にもあります。