式の命名とインスタンスへの保存#
JijModeling では、名前つきの式を表すクラスとして NamedExpr クラスが提供されており、
決定変数やプレースホルダーと同様に、 Problem.NamedExpr() メソッドを使って宣言することができます。
Problem.NamedExpr() の引数は以下の通りです。
引数 |
型 |
説明 |
|---|---|---|
|
|
名前つき式の名前。Decorator API では省略可能。 |
|
名前つき式の定義。JijModeling の式オブジェクトや、Python の数値、文字列、タプル、リスト、辞書、NumPy 配列など、式に変換可能なオブジェクトを指定できます。 |
|
|
|
省略可。名前つき式の説明。数式出力や OMMX に保存される式の説明に使用されます。 |
|
|
省略可。名前つき式の \(\LaTeX\) 表現。数式出力時に使用されます。 |
|
|
省略可(デフォルト: |
NamedExpr には、以下の 2 つの使い方があります。
特定の式に対して名前をつけて \(\LaTeX\) 表示を見やすくする
特定の式を OMMX インスタンスに保存して求解後にその式の値を評価する
本ドキュメントでは、 NamedExpr のこれらの使い方について具体例を交えながら説明していきます。
式の命名#
特定の式に対して名前をつけて \(\LaTeX\) 表示を見やすくする例を見てみましょう。ナップサック問題において、アイテム数 \(N\) をインスタンスデータとして与えるのではなく、各アイテムの重さを表すプレースホルダー配列 \(w\) の長さから推論することを考えます。
まずは、 NamedExpr() を使わずに定式化すると以下のようになります。
import jijmodeling as jm
@jm.Problem.define("Knapsack (Unnamed)", sense=jm.ProblemSense.MAXIMIZE)
def knapsack_unnamed(problem: jm.DecoratedProblem):
W = problem.Float(description="maximum weight capacity of the knapsack")
w = problem.Float(ndim=1, description="weight of each item")
# w の長さから N を推論させる
N = w.len_at(0)
v = problem.Float(shape=(N,), description="value of each item")
x = problem.BinaryVar(
shape=(N,), description="$x_i = 1$ if item i is put in the knapsack"
)
problem += jm.sum(v[i] * x[i] for i in N)
problem += problem.Constraint("Weight", jm.sum(w[i] * x[i] for i in N) <= W)
knapsack_unnamed
\(\LaTeX\) 表示を見るとわかる通り、\(N\) の定義式 len_at(w, 0) が定義中で展開されてしまっており、特に総和の範囲などがみづらくなっています。
そこで、\(N\) を NamedExpr() を使って定義してみましょう。
@jm.Problem.define("Knapsack", sense=jm.ProblemSense.MAXIMIZE)
def knapsack(problem: jm.DecoratedProblem):
W = problem.Float(description="maximum weight capacity of the knapsack")
w = problem.Float(ndim=1, description="weight of each item")
# w の長さに対して NamedExpr を利用して N という名前をつける
N = problem.NamedExpr(w.len_at(0), description="Length of w")
v = problem.Float(shape=(N,), description="value of each item")
x = problem.BinaryVar(
shape=(N,), description="$x_i = 1$ if item i is put in the knapsack"
)
problem += jm.sum(v[i] * x[i] for i in N)
problem += problem.Constraint("Weight", jm.sum(w[i] * x[i] for i in N) <= W)
knapsack
末尾の Named Expressions 節に \(N\) の定義式が現れ、残りの数式中でも \(N\) として表示されるようになり、\(\LaTeX\) 表示としても見やすくなりました。
また、 NamedExpr() で定義された \(N\) は JijModeling の数理モデルの中では変数の一種として扱われますが、コンパイル時に自動で展開されるため、 NamedExpr() の有無で OMMX インスタンスが変わることはありません。
knapsack_instance_data = {
"v": [10, 13, 18, 31, 7, 15],
"w": [11, 15, 20, 35, 10, 33],
"W": 47,
}
instance_named = knapsack.eval(knapsack_instance_data)
instance_unnamed = knapsack_unnamed.eval(knapsack_instance_data)
assert instance_named.objective.almost_equal(instance_unnamed.objective)
assert instance_named.constraints[0].function.almost_equal(
instance_unnamed.constraints[0].function
)
Tip
数理モデルに登録されている NamedExpr の一覧は、 jijmodeling.Problem.named_exprs() で確認できます。
インタンスへの保存#
NamedExpr の save_in_ommx 引数に True を設定することで、以下の条件を満たす場合に限り、その式を OMMX インスタンスに保存することができます。
取りうる値がスカラーである式
取りうる値がスカラーである式の配列
取りうる値がスカラーである式の辞書
具体的には、以下のような式が OMMX インスタンスに保存できます。
# 取りうる値がスカラーである式(例: バイナリ変数の和)
problem = jm.Problem("Scalar")
x = problem.BinaryVar("x", shape=(5,))
S = problem.NamedExpr("scalar", x.sum(), save_in_ommx=True)
problem
# 取りうる値がスカラーである式の配列(例: 整数変数の配列の差)
problem = jm.Problem("Tensor of Scalars")
y = problem.IntegerVar("y", shape=(5,), lower_bound=0, upper_bound=10)
z = problem.IntegerVar("z", shape=(5,), lower_bound=0, upper_bound=10)
T = problem.NamedExpr("tensor_of_scalars", y - z, save_in_ommx=True)
problem
# 取りうる値がスカラーである式の辞書(例: プレースホルダと実数変数の辞書の積)
problem = jm.Problem("Dict of Scalars")
K = problem.CategoryLabel("K")
a = problem.Float("a", dict_keys=K)
w = problem.ContinuousVar("w", dict_keys=K, lower_bound=0, upper_bound=10)
U = problem.NamedExpr("dict_of_scalars", a * w, save_in_ommx=True)
problem
一方で、以下のような式は OMMX インスタンスに保存できません。
problem = jm.Problem("Errornous Problem")
# 比較式は保存できない
a = problem.IntegerVar("a", lower_bound=0, upper_bound=10)
try:
problem.NamedExpr("comparison", a == 2, save_in_ommx=True)
except Exception as e:
print(e)
Named expression `comparison' has type `Comparison[int!, int!]' which cannot be stored as an OMMX NamedFunction (only scalar, tensor-of-scalars, and dict-of-scalars are supported)
# カテゴリラベルは保存できない
L = problem.CategoryLabel("L")
try:
problem.NamedExpr("category_labels", L, save_in_ommx=True)
except Exception as e:
print(e)
Named expression `category_labels' has type `Set[CategoryLabel("L")]' which cannot be stored as an OMMX NamedFunction (only scalar, tensor-of-scalars, and dict-of-scalars are supported)
# rows() は配列の配列を返すので保存できない
x = problem.BinaryVar("M", shape=(5, 5))
try:
problem.NamedExpr("array_of_array", x.rows(), save_in_ommx=True)
except Exception as e:
print(e)
Named expression `array_of_array' has type `Array[5; Array[5; binary!]]' which cannot be stored as an OMMX NamedFunction (only scalar, tensor-of-scalars, and dict-of-scalars are supported)
Tip
これらの OMMX インスタンスに保存できない式についても、 save_in_ommx=False(あるいは、未指定)にすれば NamedExpr として宣言することができます。
では、特定の式を OMMX インスタンスに保存して求解後にその式の値を評価する例を見てみましょう。ナップサック問題において、目的関数であるアイテムの価値の合計だけでなく、アイテムの総重量を知りたいというケースを考えます。
@jm.Problem.define("Knapsack", sense=jm.ProblemSense.MAXIMIZE)
def knapsack_weight(problem: jm.DecoratedProblem):
W = problem.Float(description="maximum weight capacity of the knapsack")
w = problem.Float(ndim=1, description="weight of each item")
N = problem.NamedExpr(w.len_at(0), description="Length of w")
v = problem.Float(shape=(N,), description="value of each item")
x = problem.BinaryVar(
shape=(N,), description="$x_i = 1$ if item i is put in the knapsack"
)
total_weight = problem.NamedExpr(
jm.sum(w[i] * x[i] for i in N),
description="Total weight of items in the knapsack",
save_in_ommx=True,
)
problem += jm.sum(v[i] * x[i] for i in N)
problem += problem.Constraint("Weight", total_weight <= W)
knapsack_weight
上記のコードでは、総重量の式に total_weight という名前をつけ、save_in_ommx=True により OMMX インスタンスの保存を有効にしています。さて、この数理モデルをコンパイルして OMMX インスタンスを生成してみましょう。
instance = knapsack_weight.eval(knapsack_instance_data)
OMMX インスタンスに保存された式は、 ommx.v1.Instance.named_functions() や ommx.v1.Instance.named_functions_df() プロパティで確認することができます。
instance.named_functions_df
| type | function | used_ids | name | subscripts | description | parameters.subscripts | |
|---|---|---|---|---|---|---|---|
| id | |||||||
| 0 | Linear | Function(11*x0 + 15*x1 + 20*x2 + 35*x3 + 10*x4... | {0, 1, 2, 3, 4, 5} | total_weight | [] | Total weight of items in the knapsack | [] |
Tip
OMMX インスタンスに保存された式に対応する NamedFunction の ID を得るには、Compiler.get_named_function_id_by_name() メソッドを利用してください。
それでは、この OMMX インスタンスを OpenJij で解き、得られた解における total_weight の値を確認してみましょう。
from ommx_openjij_adapter import OMMXOpenJijSAAdapter
solution = OMMXOpenJijSAAdapter.solve(
instance,
num_reads=100,
num_sweeps=10,
uniform_penalty_weight=1.6,
)
solution.named_functions_df
| value | used_ids | name | subscripts | description | parameters.subscripts | |
|---|---|---|---|---|---|---|
| id | ||||||
| 0 | 46.0 | {0, 1, 2, 3, 4, 5} | total_weight | [] | Total weight of items in the knapsack | [] |
確かに OMMX インスタンスに保存した式 total_weight の値を評価することができました。
このような用法以外にも、特定の式を OMMX インスタンスに保存する機能は、加重方式の多目的最適化を扱う場合などに利用できる便利なものとなっています。