JijModelingを用いた数理モデルの構築とOpenJijでの最適化計算#
このチュートリアルでは、JijModelingを用いて数理モデルを定式化し、得られた数理モデルをQUBOに変換し、OpenJijで解くという流れを説明したいと思います。
まず初めに、必要なパッケージをインストールします。
数理モデルを簡単に構築するためのモジュールであるJijModelingとJijModelingで記述された数理モデルをQUBOに変換するためのモジュールであるjijmodeling-transpilerをインストールします。
これらは、pip
を使ってインストールすることができます。
!pip install jijmodeling
!pip install jijmodeling-transpiler
Collecting jijmodeling
Obtaining dependency information for jijmodeling from https://files.pythonhosted.org/packages/54/49/6bde5452e9ae5ff4dba5eb090ce433a7e855a0912c0f73f5dac78b3d032e/jijmodeling-1.0.3-cp39-cp39-manylinux_2_28_x86_64.whl.metadata
Downloading jijmodeling-1.0.3-cp39-cp39-manylinux_2_28_x86_64.whl.metadata (12 kB)
Requirement already satisfied: numpy in /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages (from jijmodeling) (1.25.2)
Collecting pandas (from jijmodeling)
Obtaining dependency information for pandas from https://files.pythonhosted.org/packages/83/f0/2765daac3c58165460b127df5c0ef7b3a039f3bfe7ea7a51f3d20b01371b/pandas-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
Downloading pandas-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (18 kB)
Collecting orjson<4.0.0,>=3.8.0 (from jijmodeling)
Obtaining dependency information for orjson<4.0.0,>=3.8.0 from https://files.pythonhosted.org/packages/0e/31/49db18d0728852eea1f633e92cf189acf819508da9ff1b30c99baf401c85/orjson-3.9.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
Downloading orjson-3.9.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (49 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 49.2/49.2 kB 2.3 MB/s eta 0:00:00
?25hRequirement already satisfied: python-dateutil>=2.8.2 in /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages (from pandas->jijmodeling) (2.8.2)
Collecting pytz>=2020.1 (from pandas->jijmodeling)
Obtaining dependency information for pytz>=2020.1 from https://files.pythonhosted.org/packages/32/4d/aaf7eff5deb402fd9a24a1449a8119f00d74ae9c2efa79f8ef9994261fc2/pytz-2023.3.post1-py2.py3-none-any.whl.metadata
Downloading pytz-2023.3.post1-py2.py3-none-any.whl.metadata (22 kB)
Collecting tzdata>=2022.1 (from pandas->jijmodeling)
Downloading tzdata-2023.3-py2.py3-none-any.whl (341 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 341.8/341.8 kB 15.9 MB/s eta 0:00:00
?25hRequirement already satisfied: six>=1.5 in /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages (from python-dateutil>=2.8.2->pandas->jijmodeling) (1.16.0)
Downloading jijmodeling-1.0.3-cp39-cp39-manylinux_2_28_x86_64.whl (959 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 959.6/959.6 kB 87.8 MB/s eta 0:00:00
?25hDownloading orjson-3.9.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (138 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 138.6/138.6 kB 42.5 MB/s eta 0:00:00
?25hDownloading pandas-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (12.7 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.7/12.7 MB 105.9 MB/s eta 0:00:00
?25hDownloading pytz-2023.3.post1-py2.py3-none-any.whl (502 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 502.5/502.5 kB 85.8 MB/s eta 0:00:00
?25hInstalling collected packages: pytz, tzdata, orjson, pandas, jijmodeling
Successfully installed jijmodeling-1.0.3 orjson-3.9.7 pandas-2.1.0 pytz-2023.3.post1 tzdata-2023.3
Collecting jijmodeling-transpiler
Obtaining dependency information for jijmodeling-transpiler from https://files.pythonhosted.org/packages/54/49/6ec8c714caf980b41d8b5e18313a216be4f5b2f4bde915a833b9151c7b01/jijmodeling_transpiler-0.6.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl.metadata
Downloading jijmodeling_transpiler-0.6.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl.metadata (3.9 kB)
Requirement already satisfied: jijmodeling>=1.0.0 in /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages (from jijmodeling-transpiler) (1.0.3)
Collecting numpy<1.25.0,>=1.17.0 (from jijmodeling-transpiler)
Obtaining dependency information for numpy<1.25.0,>=1.17.0 from https://files.pythonhosted.org/packages/7a/7c/d7b2a0417af6428440c0ad7cb9799073e507b1a465f827d058b826236964/numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
Downloading numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (5.6 kB)
Collecting typeguard (from jijmodeling-transpiler)
Obtaining dependency information for typeguard from https://files.pythonhosted.org/packages/18/01/5fc45558268ced46d86292763477996a3cdd505567cd590a688e8cdc386e/typeguard-4.1.5-py3-none-any.whl.metadata
Downloading typeguard-4.1.5-py3-none-any.whl.metadata (3.7 kB)
Collecting pydantic (from jijmodeling-transpiler)
Obtaining dependency information for pydantic from https://files.pythonhosted.org/packages/82/06/fafdc75e48b248eff364b4249af4bcc6952225e8f20e8205820afc66e88e/pydantic-2.3.0-py3-none-any.whl.metadata
Downloading pydantic-2.3.0-py3-none-any.whl.metadata (148 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 148.8/148.8 kB 4.3 MB/s eta 0:00:00
?25hCollecting mip (from jijmodeling-transpiler)
Downloading mip-1.15.0-py3-none-any.whl (15.3 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 15.3/15.3 MB 89.4 MB/s eta 0:00:00
?25hRequirement already satisfied: dimod in /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages (from jijmodeling-transpiler) (0.12.12)
Requirement already satisfied: pandas in /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages (from jijmodeling>=1.0.0->jijmodeling-transpiler) (2.1.0)
Requirement already satisfied: orjson<4.0.0,>=3.8.0 in /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages (from jijmodeling>=1.0.0->jijmodeling-transpiler) (3.9.7)
Collecting cffi==1.15.* (from mip->jijmodeling-transpiler)
Downloading cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (441 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 441.2/441.2 kB 76.2 MB/s eta 0:00:00
?25hCollecting pycparser (from cffi==1.15.*->mip->jijmodeling-transpiler)
Downloading pycparser-2.21-py2.py3-none-any.whl (118 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.7/118.7 kB 34.9 MB/s eta 0:00:00
?25hCollecting annotated-types>=0.4.0 (from pydantic->jijmodeling-transpiler)
Obtaining dependency information for annotated-types>=0.4.0 from https://files.pythonhosted.org/packages/d8/f0/a2ee543a96cc624c35a9086f39b1ed2aa403c6d355dfe47a11ee5c64a164/annotated_types-0.5.0-py3-none-any.whl.metadata
Downloading annotated_types-0.5.0-py3-none-any.whl.metadata (11 kB)
Collecting pydantic-core==2.6.3 (from pydantic->jijmodeling-transpiler)
Obtaining dependency information for pydantic-core==2.6.3 from https://files.pythonhosted.org/packages/18/54/6d64dff3e49e7faf4f5b989b49e46dd8b592d1e3f3db2113f4aaf1defdd3/pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
Downloading pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.5 kB)
Requirement already satisfied: typing-extensions>=4.6.1 in /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages (from pydantic->jijmodeling-transpiler) (4.7.1)
Requirement already satisfied: importlib-metadata>=3.6 in /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages (from typeguard->jijmodeling-transpiler) (6.8.0)
Requirement already satisfied: zipp>=0.5 in /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages (from importlib-metadata>=3.6->typeguard->jijmodeling-transpiler) (3.16.2)
Requirement already satisfied: python-dateutil>=2.8.2 in /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages (from pandas->jijmodeling>=1.0.0->jijmodeling-transpiler) (2.8.2)
Requirement already satisfied: pytz>=2020.1 in /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages (from pandas->jijmodeling>=1.0.0->jijmodeling-transpiler) (2023.3.post1)
Requirement already satisfied: tzdata>=2022.1 in /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages (from pandas->jijmodeling>=1.0.0->jijmodeling-transpiler) (2023.3)
Requirement already satisfied: six>=1.5 in /opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages (from python-dateutil>=2.8.2->pandas->jijmodeling>=1.0.0->jijmodeling-transpiler) (1.16.0)
Downloading jijmodeling_transpiler-0.6.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl (7.5 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7.5/7.5 MB 105.9 MB/s eta 0:00:00
?25hDownloading numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (17.3 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 17.3/17.3 MB 87.7 MB/s eta 0:00:00
?25hDownloading pydantic-2.3.0-py3-none-any.whl (374 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 374.5/374.5 kB 75.4 MB/s eta 0:00:00
?25hDownloading pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.9 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.9/1.9 MB 108.7 MB/s eta 0:00:00
?25hDownloading typeguard-4.1.5-py3-none-any.whl (34 kB)
Downloading annotated_types-0.5.0-py3-none-any.whl (11 kB)
Installing collected packages: pydantic-core, pycparser, numpy, annotated-types, typeguard, pydantic, cffi, mip, jijmodeling-transpiler
Attempting uninstall: numpy
Found existing installation: numpy 1.25.2
Uninstalling numpy-1.25.2:
Successfully uninstalled numpy-1.25.2
Successfully installed annotated-types-0.5.0 cffi-1.15.1 jijmodeling-transpiler-0.6.5 mip-1.15.0 numpy-1.24.4 pycparser-2.21 pydantic-2.3.0 pydantic-core-2.6.3 typeguard-4.1.5
import jijmodeling as jm
import numpy as np
import matplotlib.pyplot as plt
巡回セールスマン問題#
制約条件付き最適化問題の例として巡回セールスマン問題を解いていきたいと思います。 巡回セールスマン問題は、一人のセールスマンが決められた都市を全て一度づつ訪問し、最終的に元の都市に帰ってくる時に、都市を巡回する最短経路を求めろという問題です。
制約条件#
この問題では、セールスマンは一つの地点に一度しか訪れることができないという位置に関する制約条件と、セールスマンが一人なのである時刻では一つの都市にしか存在しないという時間に関する制約条件が存在します。
番目に都市を訪れるとき、それ以外ではとするバイナリ変数を用いると、上記の二つの制約条件は、
と書くことができます。
目的関数#
巡回セールスマン問題は、都市を巡回する最短経路を求めろという問題でした。 そこで、地点との間の距離をとすると、時刻で都市を訪れ、時刻で都市を訪れた時の移動距離は、
と書くことができます。 これを合計したもの、
が今回最小化したい目的関数である、合計移動距離になります。
これまでのチュートリアルで述べたようにイジング最適化を行うためには、このような制約条件を持つ数理モデルをIsingハミルトニアンやQUBOハミルトニアンに変換する必要があります。 このような作業を手で行うと面倒ですし、実際に構築した数理モデルとQUBOの間にバグが入り込む可能性があります。 そこで、このような作業を全て自動でおこなってくれるのがJijModelingです。 JijModelingを用いることで、上記のように構築した数理モデルをコーディングし、それを自動的にQUBOに変換してくれます。 ここでは、上記で説明した巡回セールスマン問題を例にとって、JijModelingの使い方について説明していきます。
JijModelingを用いた巡回セールスマン問題の数理モデルの構築#
まず初めに、JijModelingを用いて、問題の数式を記述していきます。 JijModelingでは、通常の数理最適化計算用のモデラーとは異なり、問題インスタンスのデータとは独立に数理モデルを構築していきます。 このように数理モデルを構築することで、数理モデルの汎用性が担保でき、かつ、紙の上で数式を書くように直感的に数式を記述することができます。 さらに、JijModelingで記述された数理モデルは、notebook上ではLaTeXで確認することができます。
ここでは、JijModelingを用いた数理モデルの構築についてひとつづつ見ていきたいと思います。
まずは、問題を記述するための変数と定数を表現しましょう。
dist = jm.Placeholder("dist", dim=2)
N = jm.Placeholder("N")
x = jm.Binary("x", shape=(N, N))
i = jm.Element("i", (0,N))
j = jm.Element("j", (0,N))
t = jm.Element("t", (0,N))
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[3], line 1
----> 1 dist = jm.Placeholder("dist", dim=2)
2 N = jm.Placeholder("N")
3 x = jm.Binary("x", shape=(N, N))
TypeError: Placeholder.__new__() got an unexpected keyword argument 'dim'
ここで、jm.Placeholder
は定数を表現しており、ここでは距離行列と都市数を表現するのに用いています。
巡回セールスマン問題においては、この距離行列と都市数によってさまざまな問題が表現されることになります。
バイナリ変数を表現するのが、jm.Binary
です。
ここでは、のバイナリ変数を定義しています。
次に、総和などで添字の範囲を表現するためにjm.Element
を用いて、i
,j
,t
という添字を定義しています。
JijModelingでは、jm.Problem
インスタンスを作成し、それに目的関数や制約条件を追加していきます。
では次に、定義した変数を用いて目的関数を定義していきます。
problem = jm.Problem("TSP")
obj = jm.Sum([t, i, j], dist[i, j] * x[t, i] * x[(t + 1) % N, j])
problem += obj
problem
総和はjm.Sum
を用いて表現することができます。
jm.Sum
の最初の引数は総和をとる添字で、TSPの目的関数では、3つの添字について総和を取るので、それらの添字(jm.element
)をリストで渡しています。
次に、制約条件を追加していきます。
# 制約条件1 : 位置に関するonehot制約
problem += jm.Constraint(
"onehot_location",
x[:, i] == 1,
forall=i,
)
# 制約条件2 : 時間に関するonehot制約
problem += jm.Constraint(
"onehot_time",
x[t, :] == 1,
forall=t,
)
jm.Constraint
を用いて制約条件表現することができます。
最初の引数は制約条件の名前、2つ目の引数が制約条件を表現した数式になります。
この制約条件には、オプション引数として、forall
というものがあります。
これは、数理モデルにおいて、「任意のについて」や、「」というように表現されているものをJijModelingで表現するための引数です。
また、制約条件の中に現れているx[:,i]
という記法は、jm.Sum(t,x[t,i])
という書き方の糖衣構文になっています。
最後に、今記述した数理モデルを確認してみましょう。
problem
説明で用いた数理モデルと同じ数式が表示されていることがわかります。
以上で、数理モデルの構築は終わりです。 このようにJijModelingを用いると、手元の数式と見比べながら数理モデルをコーディングしていくことができます。
問題データの作成#
問題の数理モデルができたので、次に問題に使うデータを作成します。 ここでは、単純な都市数10で、都市間の距離をランダムにした問題を解いていきます。
N = 5
np.random.seed(3)
x_pos = np.random.rand(N)
y_pos = np.random.rand(N)
plt.plot(x_pos, y_pos, 'o')
plt.xlim(0, 1)
plt.ylim(0, 1)
(0.0, 1.0)

数理モデルを作る際に用いたjm.Placeholder
にデータを代入するので、Placeholderの名前をkeyに持つ辞書でデータを渡す必要があります。
今回の問題では、N
とdist
に値を渡す必要があります。
XX, XX_T = np.meshgrid(x_pos, x_pos)
YY, YY_T = np.meshgrid(y_pos, y_pos)
distance = np.sqrt((XX - XX_T)**2 + (YY - YY_T)**2)
instance_data = {
"N":N,"dist": distance
}
instance_data
{'N': 5,
'dist': array([[0. , 0.7866063 , 0.73643374, 0.84577089, 0.56967619],
[0.7866063 , 0. , 0.4251585 , 0.21078131, 0.36540009],
[0.73643374, 0.4251585 , 0. , 0.26950348, 0.64576184],
[0.84577089, 0.21078131, 0.26950348, 0. , 0.54552992],
[0.56967619, 0.36540009, 0.64576184, 0.54552992, 0. ]])}
JijModeling-Transpilerを用いた数理モデルのQUBOへの変換#
数理モデルとデータの用意ができたので、次にJijModeling-Transpilerを用いて数理モデルとデータをQUBOに変換します。
まずは、QUBOに変換するための関数であるto_pyqubo
をimportします。
from jijmodeling.transpiler.pyqubo import to_pyqubo
このto_pyqubo
に作成した数理モデルと問題のデータを与えることでpyquboオブジェクトを作ることができます。
ここでは用いませんでしたが、変数の一部をある値に固定したいという場合には、3番目の変数に固定したい変数と値を辞書として与えることで変数の固定化をすることができます。
model,cache = to_pyqubo(problem,instance_data,{})
得られたpyquboオブジェクトに制約条件の係数の大きさを与え、to_qubo
を行うことでQUBOを生成することができます。
multipliers = {"onehot_location":1.0,"onehot_time":1.0}
Q,offset = model.compile().to_qubo(feed_dict = multipliers)
これでQUBOを生成することができました。
OpenJijを用いた最適化の実行#
ここでまでQUBOが得られているので、このQUBOを用いてこれまでと同様に最適化計算を行うことができます。
import openjij as oj
sampler = oj.SASampler(num_reads=1)
res = sampler.sample_qubo(Q=Q)
JijModeling-Transpilerの機能を用いれば、最適化によって得られた結果をよりみやすい形に整形してくれます。
そこで、decode
機能を使ってみましょう。
result = cache.decode(res)
result
SampleSet(record=Record(solution={'x': [(([0, 1, 2, 3, 4], [0, 2, 3, 1, 4]), [1, 1, 1, 1, 1], ())]}, num_occurrences=[1]), evaluation=Evaluation(energy=[-7.848205186671935], objective=[2.151794813328065], constraint_violations={'onehot_location': [0.0], 'onehot_time': [0.0]}, penalty={}), measuring_time=MeasuringTime(solve=SolvingTime(preprocess=None, solve=None, postprocess=None), system=SystemTime(post_problem_and_instance_data=None, request_queue=None, fetch_problem_and_instance_data=None, fetch_result=None, deserialize_solution=None), total=None))
得られた解はresult.record
のsolution
の中に入っています。
この中に結果は疎行列の形で入っています。
大事なのが最初の二つの要素で、ひとつ目が行列の中のindexそして、二つ目がそのindexにおける値が入っています。
バイナリ変数の場合には、1となった値のみが入っているので、通常、値には1しか入っていません。
sparse_index,value,_ = result.record.solution['x'][0]
sparse_index
([0, 1, 2, 3, 4], [0, 2, 3, 1, 4])
result.evaluation
の中には、最適化によって得られた評価値として、エネルギーや目的関数、そして、制約条件の破れが入っています。
ここで、制約条件を満たした解が得られたかをチェックしてみます。
result.evaluation.constraint_violations
{'onehot_location': [0.0], 'onehot_time': [0.0]}
制約条件を満たした解が得られているようです。
そこで、この解を可視化してみましょう。 ルートをプロットする場合には、時間に関するindexを用いて、都市のindexをソートしてあげることで、どの順番に都市を回るかという順序を得ることができます。
time_indices, city_indices = zip(*sorted(zip(*sparse_index)))
time_indices, city_indices
((0, 1, 2, 3, 4), (0, 2, 3, 1, 4))
plt.plot(x_pos, y_pos, 'o',markersize=12)
plt.xlim(0, 1)
plt.ylim(0, 1)
for i, city_index in enumerate(city_indices[:-1]):
next_city_index = city_indices[i+1]
plt.plot([x_pos[city_index],x_pos[next_city_index ]],[y_pos[city_index],y_pos[next_city_index ]],c = "blue")
plt.plot([x_pos[city_indices[-1]],x_pos[city_indices[0]]],[y_pos[city_indices[-1]],y_pos[city_indices[0]]],c = "blue")
[<matplotlib.lines.Line2D at 0x7f73366257f0>]
