モデルの定式化#
モデルとは何か? なぜ直接ソルバーを使わないのか?#
jijmodeling
は、人間が読みやすい数理モデルをコンピュータが読み取れるデータ形式に変換する「モデラー」ライブラリです。最適化問題にはいくつかの種類があり、それに対応する問題固有のソルバーはソルバー固有のデータ形式しか受け付けません。そのため、数理モデルをソルバー固有のデータ形式に変換する必要があります。jijmodeling
を使用すると数理モデルを単一の数学的な方法で記述することができ、その後、ソルバーやインスタンス固有の詳細に適合させることもできます。
数理モデルの例#
ここでは\(N\)個の実数係数\(d_n\)を持つ単純なバイナリ線形最小化問題を考えます。
この問題はjijmodeling
の基本的な使用方法を学べる良い例です。具体的には、
決定変数\(x_n\)とプレースホルダー\(N\)および\(d_n\)を定義する
\(\sum_{n=0}^{N-1} d_n x_n\)の最小化を目的関数として設定する
等式制約\(\sum_{n=0}^{N-1} x_n = 1\)を設定する
という内容を学ぶことができます。より実践的で包括的な例は、JijZeptのドキュメントサイトのLearnを確認してください。
Problem
オブジェクトを作る#
実際にjijmodeling
を使っていきましょう。まずはインポートする必要があります。
import jijmodeling as jm
jm.__version__ # 1.8.0
'1.10.0'
Caution
以下のコードを実行する前に、あなたの環境のjijmodeling
のバージョンがこのドキュメントと一致していることを確認することを強く推奨します。
では、jijmodeling
を用いて数理モデルを構築してみましょう。
# 「パラメータ」を定義
d = jm.Placeholder("d", ndim=1)
N = d.len_at(0, latex="N")
# 「決定変数」を定義
x = jm.BinaryVar("x", shape=(N,))
# 総和をするためのインデックスを準備
n = jm.Element('n', belong_to=(0, N))
# 数理モデルを管理するオブジェクトを生成
problem = jm.Problem('my_first_problem')
# 目的関数を設定
problem += jm.sum(n, d[n] * x[n])
# 制約条件を設定
problem += jm.Constraint("onehot", jm.sum(n, x[n]) == 1)
# 数理モデルを表示
problem
コードのどの部分が上記の数理モデルに対応するか分かりましたか? このページではコードの各部分の内容と操作についてもう少し深く説明していきます。
Jupyter環境
Jupyter Notebook等の環境では、以下の画像のようにProblem
オブジェクトの内容を表示できます。これにより、モデルを対話的にデバッグすることができます。
決定変数とパラメータ#
上記の数理モデルには「決定変数」と「パラメータ」という2種類の「変数」があります。jijmodeling
では、「変数」を宣言する際に使用するクラスによってこれを識別します。
\(x_n\)の値は問題を解くことで決まるもののため、これを「決定変数」と呼びます。
今回の問題では、バイナリ変数\(x_n \in \{0, 1\}\)を
BinaryVar
で宣言しています。決定変数を定義できるクラスとして他にもIntegerVar
やContinuousVar
があります。さらに詳しい説明は決定変数の種類を参照してください。
\(N\)と\(d\)の値はユーザーによって指定される「パラメータ」です。
この問題は\(N\)と\(d\)によってパラメータ化されていると言います。
「パラメータ」の実際の値は
Problem
オブジェクト内では指定しません。「パラメータ」は問題の「インスタンスデータ」の入れ物と見なすことができます。特定のインスタンスは異なる値を持ちますが、
jijmodeling
は特定の値に依存しない方法で数理モデルを記述することができます。ほとんどの「パラメータ」は、上記のコードの
d
のように明示的に定義されたPlaceholder
オブジェクトで表されます。\(N\)は\(d\)の要素数として定義されており、\(N\)は「暗黙的なパラメータ」として扱われます。これにより、数理モデル内の\(N\)の意味がコード上で明確になり、インスタンスを生成するためには\(d\)を指定するだけで良くなります。
オブジェクトとは何か?
Pythonではすべての値には型があります。例えば、1
はint
型であり、1.0
はfloat
型です。組み込み関数type
を使用して、type(1.0)
のように型を取得できます。ある型A
に対して、型Aの値を「A
オブジェクト」と呼びます。
多次元変数#
配列や行列のようなインデックスを使用できる変数を定義することができます。
上記の数理モデルでは、\(N\)個の係数\(d_n\)と\(N\)個の決定変数\(x_n\)を定義したいので、1次元のPlaceholder
オブジェクトd
と1次元のBinaryVar
オブジェクトx
を定義しましょう。Placeholder
の場合は値がいくつかあるかを指定せずに、ただ1次元であることを指定すれば十分です。一方、決定変数の場合は次元数とともにその長さを指定しなければなりません。ですが、jijmodeling
ではその長さも「パラメータ」として定義することができるので、数値を使わずに以下のように書くことができます。
# 係数dの定義
d = jm.Placeholder("d", ndim=1)
# $d$の長さとして$N$を定義
N = d.len_at(0, latex="N")
# $N$を用いて決定変数$x$を定義
x = jm.BinaryVar("x", shape=(N,))
N
はArrayLength
型であり、Placeholder
オブジェクトd
の要素数を表しています。len_at
の第1引数に与えられた0
は0次元方向の要素数をカウントすることを意味しており、これはPlaceholder
が任意の次元数を持つことができるために指定が必要になっています。
Note
インデックスと総和については、次のページでさらに詳しく説明します。
目的関数#
次に、最小化する目的関数として\(\sum_{n=0}^{N-1} d_n x_n\)をProblem
オブジェクトに設定しましょう。しかし、\(N\)はProblem
オブジェクトの構築段階では固定されていないため、Pythonのfor
ループを書くことはできません。では、どのように総和を行えば良いでしょうか?
この疑問を解決するためにjijmodeling
専用のsum
が存在します。
n = jm.Element('n', belong_to=(0, N))
sum_dx = jm.sum(n, d[n] * x[n])
Element
は、ある範囲内のインデックスに対応する新しいタイプの変数です。以下のようなケースを考えてみましょう。
与えられた\(n \in [0, N-1]\)に対し、\(d \in \mathbb{R}^N\)の\(n\)番目の要素\(d_n\)を取る
jijmodeling
では、\(n \in [0, N-1]\)に対応するElement
オブジェクトn
と\(d\)に対応するPlaceholder
オブジェクトd
を用いて、\(d_n\)をd[n]
で表現します。Element
オブジェクトに有効な範囲が指定されていることに注意してください。
そして、総和\(\sum_{n} d_n x_n\)を表現するためにjijmodeling
専用のsum
を利用します。第1引数として和を取る範囲を表すElement
オブジェクトn
を指定し、第2引数として和を取る式d[n] * x[n]
を指定します。これにより、\(\sum_{n} d_n x_n\)を表現する式sum_dx
が定義できます。
Note
「式」については次のページで詳しく説明します。
そして、以下のようにProblem
インスタンスを作成して、sum_dx
を加えることで\(\sum_n d_n x_n\)を目的関数として設定できます。
problem = jm.Problem('my_first_problem')
problem += sum_dx
Problem
オブジェクトはデフォルトでは最小化問題になります。目的関数を最大化したい場合はProblem
を構築する際にsense
引数を以下のように指定してください。
problem = jm.Problem('my_first_problem', sense=jm.ProblemSense.MAXIMIZE)
等式制約#
最後に、等式制約に対応するConstraint
オブジェクトを作成しましょう。
上記で説明したsum
を使用して、この制約を次のように記述できます。
jm.sum(n, x[n]) == 1
jijmodeling
の式における==
は、通常のPythonとは異なり、新しいjijmodeling
の式を返すことに注意してください。また、Constraint
オブジェクトは第1引数に制約の名前を指定し、第2引数に比較式を指定する必要があります。(jijmodeling
では比較式として==
、<=
、>=
を利用できます)
このようにして構築したConstraint
オブジェクトをProblem
オブジェクトに追加することで数理モデルに制約を加えることができます。
problem += jm.Constraint("onehot", jm.sum(n, x[n]) == 1)
Note
「制約条件とペナルティ」については次のページでさらに詳しく説明します。