QUBO行列の中身を見てみよう
はじめに
こんにちは!
普段,アニーリングマシン(厳密にはイジングマシンですが)を使って数理最適化を研究しているkeisukeと申します!
今回は初投稿となるのですが,自分が研究の過程で「これは美しい…!」と感嘆したものについて投稿したいと思います!
TL;DR
- 巡回セールスマン問題とは?
- 巡回セールスマン問題をQUBOモデルに落とし込む
- QUBO行列はどういうものなのか,実際に覗いてみる
- QUBO行列は美しい!
巡回セールスマン問題(TSP)とは
巡回セールスマン問題,通称TSPとは,複数の都市が与えられており,全ての都市を一度だけ訪問して出発地点に戻ってくるような巡回路の中で,総移動距離が最小となるような都市の訪問順序を求める問題です.
TSPは組合せ最適化問題としては一番有名な問題なので,ご存知の方も多いのではないでしょうか.
ここからは,TSPを定式化していきます.
個の都市の集合を とします.
任意の都市 間には距離が定義されており,その距離を とします.また, 番目の都市 , 番目の都市 に関するバイナリ変数を を以下のように定義します.
以上より,全ての都市を一度だけ訪問して出発地点に戻って くるような巡回路の総距離 は,次の式のように表せます.
なお,TSPには以下の2つの制約があります.
- 同時訪問制約:一度に訪れることのできる都市はただ一つのみ
- 訪問回数制約:任意の都市にはただ一度しか訪問しない
以上がTSPの問題定義および定式化(とその制約)です.
QUBOモデルに落とし込む
その前に
今回は,QUBOがどういったモデルなのかは割愛させていただきます.
多分,僕がここで書くよりググった方が遥かに分かりやすいから…泣
TSPをQUBOモデルにマッピングする際,以下のようなバイナリ変数を定義します.
コスト関数
コスト関数は,ある地点から出発して全ての都市を一度だけ訪問し,元の出発地点に戻ってくるような巡回路の総移動距離です.
エネルギー関数は次のように書けます.
の最小値は をとります.
は問題に依存します.
制約項1:同時訪問制約
これは,時刻 に訪問する都市はただ一つのみであるという制約です.
本制約をある時刻 について定式化すると,次の式のようになります.
全ての時刻 が上の式を満たすときに,最小値をとるようなエネルギー関数 を導入すると,エネルギー関数は次のように書けます.
の最小値は 0 をとります.
制約項2:訪問回数制約
これは, 番目の都市 はただ一度しか訪問しないという制約です.
本制約を 番目の都市 について定式化すると,次の式のようになります.
全ての都市 が上の式を満たすときに,最小値をとるようなエネルギー関数 を導入すると,エネルギー関数は次のように書けます.
の最小値は 0 をとります.
最終的なエネルギー関数
以上より,最終的なエネルギー関数は,正のハイパーパラメータα を用いて次のように表せます.
エネルギー関数 は最小値 を取り,このとき基底解となります.
基底解が得られたときのスピンが最適解となります.
なお,エネルギー関数 には定数項が出現しますが,本質的にはあってもなくても変わりません.
QUBO行列の中身を見ていく
さて,最終的なエネルギー関数 をQUBO行列として取得します.
QUBO行列の中身はどのようになっているのか見ていきましょう.
今回は,都市数 ,全都市間の距離 ,ハイパーパラメータ の場合でやってみます.
import numpy as np
import plotly.express as px
class TSP:
def __init__(self):
self.NUM_CITY = 5 # 都市数
self.NUM_SPIN = self.NUM_CITY * self.NUM_CITY # QUBO行列に必要なスピン数
self.distance_matrix = np.array([[0,3,3,3,3],[3,0,3,3,3],[3,3,0,3,3],[3,3,3,0,3],[3,3,3,3,0]]) # 都市間の距離行列
self.ALPHA = 1 # ハイパーパラメータ α
# 各スピンに番号を割り振る
self.spin_index = np.arange(self.NUM_CITY * self.NUM_CITY).reshape(self.NUM_CITY, self.NUM_CITY)
# コスト関数用のQUBO行列
self.cost_weight = np.zeros((self.NUM_SPIN, self.NUM_SPIN))
self.cost_bias = np.zeros((self.NUM_SPIN))
self.cost_const = np.zeros((1))
# 制約項のQUBO行列
self.penalty_weight = np.zeros((self.NUM_SPIN, self.NUM_SPIN))
self.penalty_bias = np.zeros((self.NUM_SPIN))
self.penalty_const = np.zeros((1))
# QUBO行列を作る
def make_qubo_matrix(self):
# ========== ここから式(4) ==========
for t in range(self.NUM_CITY):
for u in range(self.NUM_CITY):
for v in range(self.NUM_CITY):
if t < self.NUM_CITY-1:
idx_i = self.spin_index[t, u]
idx_j = self.spin_index[t+1, v]
elif t == self.NUM_CITY-1:
idx_i = self.spin_index[t, u]
idx_j = self.spin_index[0, v]
coef = self.distance_matrix[u, v]
if coef == 0:
continue
self.cost_weight[idx_i, idx_j] += coef
self.cost_weight[idx_j, idx_i] = self.cost_weight[idx_i, idx_j]
# ========== ここから式(6) ==========
# 二次の項
for t in range(self.NUM_CITY):
for u in range(self.NUM_CITY-1):
for v in range(u+1, self.NUM_CITY):
idx_i = self.spin_index[t, u]
idx_j = self.spin_index[t, v]
coef = 2
self.penalty_weight[idx_i, idx_j] += self.ALPHA * coef
self.penalty_weight[idx_j, idx_i] = self.penalty_weight[idx_i, idx_j]
# 一次の項
for t in range(self.NUM_CITY):
for u in range(self.NUM_CITY):
idx = self.spin_index[t, u]
coef = -1
self.penalty_bias[idx] += self.ALPHA * coef
# 定数項
self.penalty_const[0] += self.ALPHA * self.NUM_CITY
# ========== ここから式(8) ==========
# 二次の項
for u in range(self.NUM_CITY):
for t in range(self.NUM_CITY-1):
for tt in range(t+1, self.NUM_CITY):
idx_i = self.spin_index[t, u]
idx_j = self.spin_index[tt, u]
coef = 2
self.penalty_weight[idx_i, idx_j] += self.ALPHA * coef
self.penalty_weight[idx_j, idx_i] = self.penalty_weight[idx_i, idx_j]
# 一次の項
for u in range(self.NUM_CITY):
for t in range(self.NUM_CITY):
idx = self.spin_index[t, u]
coef = -1
self.penalty_bias[idx] += self.ALPHA * coef
# 定数項
self.penalty_const[0] += self.ALPHA * self.NUM_CITY
# 式(9)のHのQUBO行列を作る
qubo_matrix = np.zeros((self.NUM_SPIN, self.NUM_SPIN))
qubo_matrix = self.cost_weight + self.penalty_weight
for i in range(self.NUM_SPIN):
qubo_matrix[i, i] = self.cost_bias[i] + self.penalty_bias[i]
fig = px.imshow(qubo_matrix)
fig.show()
上記のコードを実行すると,下記のような結果が得られます.
(ちなみにplotlyというpythonライブラリ,ものすごく便利なのでぜひご活用ください!)
各セル(マス?)は各スピン間の相互作用に対応しています.
今回は都市数が5ということで,QUBOモデルに必要なスピン数は となります.
ですので,QUBO行列は の行列となるわけです.
なお,下図では色と各値が対応しており,その関係は以下のようになっています.
- 紺色:-2
- 紫色:0
- 黄色:2
- オレンジ色:3
QUBO行列は対称行列となっていることが分かりますね.
これは当然といえば当然です.
なぜなら,スピン から見たスピン との間の相互作用と,スピン から見たスピン との間の相互作用は同じになるからです(逆に違ったらおかしいですよね).
それにしてもQUBO,う,美しい…!!!
おわりに
いかがでしたでしょうか?
QUBOは対称行列となっており,それを可視化すると美しいものになるということについて投稿させていただきました.
皆さんも,TSPに限らずいろんなQUBOモデルを可視化してみて,その美しさを味わってみてください!
このコンテンツにコメントはありません。