モデルのファイル入出力

LP ファイルもしくは QPLIB ファイルから Amplify SDK の Model を作成する機能と、逆に Amplify SDK の Model を保存して LP ファイルもしくは QPLIB ファイルを出力する機能が提供されます。

これらは、他の数理最適化ソルバーとの連携や、Model の保存や再利用に用いると便利です。

LP ファイルフォーマット

Amplify SDK が扱える LP ファイルのフォーマットは、Gurobi LP Format に従います。

LP ファイル形式では、以下のモデルのファイル入出力に対応します。

  • バイナリ変数・整数変数・実数変数を含む問題、およびこれらの変数を組み合わせた問題

  • 二次以下の目的関数と制約条件で構成された問題

  • LP フォーマットで表現可能な目的関数・制約を持つ問題

制限事項

LP ファイルの入出力において以下の制限があります。

  • LP ファイル読み込みにおいて以下の形式およびセクションには対応しません

    • Multi-Objective Case

    • Indicator Constraints

    • Lazy Constraints Section

    • User Cuts Section

    • SOS Section

    • PWLObj Section

    • General Constraint Section

    • Scenario Section

  • LP ファイル読み込みにおいて semi-continuous 変数には対応しません

  • Amplify SDK で設定した変数名が以下の条件に当てはまる場合には正常な LP ファイルとして出力されません

    • 記号 +, -, *, ^, :, /, [, ] を含む

    • 上記の他に、記号 <, >, =, ,, (, ) から始まる

  • Amplify SDK で設定した制約条件の重みは LP ファイルに保存されません

QPLIB ファイルフォーマット

Amplify SDK が扱える QPLIB ファイルのフォーマットは、QPLIB: a library of quadratic programming instances 及びその Supplementary material 1 に従います。

QPLIB ファイル形式では、以下のモデルのファイル入出力に対応します。

  • バイナリ変数・整数変数・実数変数を含む問題、およびこれらの変数を組み合わせた問題

  • 二次以下の目的関数と制約条件で構成された問題

  • QPLIB フォーマットで表現可能な目的関数・制約をもつ問題

制限事項

QPLIB ファイルの入出力において以下の制限があります。

  • Amplify SDK で設定した変数名や制約条件の名前が改行文字に類する文字 (\n, \v, \f, \r) を含む場合に、正常な QPLIB ファイルとして出力されません

  • Amplify SDK で設定した制約条件の重みは QPLIB ファイルに保存されません

  • QPLIB ファイルの読み込みにおいて、変数の初期値に関する記述は無視されます

LP/QPLIB ファイルへの出力

Amplify SDK で作成したモデルをファイルに出力するには、 save_lp() または save_qplib() の引数に Model とファイルパスを与えます。

from amplify import VariableGenerator, Model, one_hot, save_lp

gen = VariableGenerator()
q = gen.array("Binary", 4)
f = 2 * q[0] * q[1] + q[2] * q[3] + q[0] + q[1] + q[2] + q[3] - 1
c = one_hot(q[:3])  # q_0 + q_1 + q_2 == 1

model = Model(f, c)
save_lp(model, "model.lp")

このとき、model.lp がカレントディレクトリに保存されます。

model.lp
Minimize
1 q_0 + 1 q_1 + 1 q_2 + 1 q_3 + [ 4 q_0 * q_1 + 2 q_2 * q_3 ] / 2 - 1
Subject To
1 q_0 + 1 q_1 + 1 q_2 = 1
Bounds
q_0 free
q_1 free
q_2 free
q_3 free
Binaries
q_0 q_1 q_2 q_3
Generals
End

同様にして、QPLIB ファイルに保存する場合は以下のようにします。

from amplify import save_qplib

save_qplib(model, "model.qplib")

このとき、model.qplib がカレントディレクトリに保存されます。

model.qplib
! -------------------
! problem information
! -------------------

# problem name
Amplify

# problem type
QBL

# problem sense
minimize

# number of variables
4

# number of constraints
1

! ------------------
! objective function
! ------------------

# quadratic terms
2
1 2 4
3 4 2

# linear terms
0
4
1 1
2 1
3 1
4 1

# constant
-1

! -----------
! constraints
! -----------

# linear terms
3
1 1 1
1 2 1
1 3 1

# infinity value
1.0e+20

# lower bounds
-2.0e+20
1
1 1

# upper bounds
2.0e+20
1
1 1

! ---------------
! starting points
! ---------------

# starting point for variables
0
0

# starting point for Lagrange multipliers
0
0

# starting point for dual variables
0
0

! -----------------------------
! variable and constraint names
! -----------------------------

# variable names
4
1 q_0
2 q_1
3 q_2
4 q_3

# constraint names
0

注釈

LP ファイルと QPLIB ファイルは、変数を名前で識別します。
そのため、Amplify SDK での定式化において、モデルに含まれる変数の名前が重複しないように注意してください。

LP/QPLIB ファイルからの読み込み

load_lp() または load_qplib() の引数に LP/QPLIB ファイルのパスを与えると、(Model, VariableGenerator) のタプルが得られます。要素はそれぞれ、ファイルに記述された定式化モデルと、変数の発行に使われた変数ジェネレータを表します。

たとえば、上の save_lp() の例で保存した model.lp は、以下のように読み込まれます。

from amplify import load_lp

model, gen = load_lp("model.lp")
>>> print(model)
minimize:
  2 q_0 q_1 + q_2 q_3 + q_0 + q_1 + q_2 + q_3 - 1
subject to:
  q_0 + q_1 + q_2 == 1 (weight: 1)

ファイルに定義されている決定変数は以下のように確認できます。

>>> model.variables 
[Variable({name: q_0, id: 0, type: Binary}), Variable({name: q_1, id: 1, type: Binary}),
 Variable({name: q_2, id: 2, type: Binary}), Variable({name: q_3, id: 3, type: Binary})]

読み込まれたモデルは通常通り solve() に渡すことで求解が実行されます。

from amplify import solve, FixstarsClient

client = FixstarsClient()
client.token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

result = solve(model, client)

変数名と得られた解の値の対応関係は次のようにして辞書で取得できます。

>>> {k.name: v for k,v in result.best.values.items()}
{'q_0': 0.0, 'q_1': 1.0, 'q_2': 0.0, 'q_3': 0.0}

読み込み後のモデルの変更

ファイルから読み込まれたモデルに対して、モデルに含まれる変数から多項式を作成し、モデルを変更し目的関数の改変や制約条件の追加が可能です。

次のようにして、モデルに含まれる変数を Poly のリストに変換します。

from amplify import Poly

# モデルの読み込み
model, gen = load_lp("model.lp")

# モデルに含まれる変数を Poly のリストに変換
vars = [Poly(v) for v in model.variables]

# モデルの目的関数の変更と制約条件の追加
model += vars[1] * vars[2]
model += one_hot(sum(vars[1:]))

モデル読み込み時に得られた変数ジェネレータを使って新しい変数を生成し、新たな項を追加することもできます。

ここで新しく発行する変数には、既存のモデルに含まれる変数の名前とは重複しないようしてください。

# 変数ジェネレータで新しく整数変数 "n" を生成
add_vars = gen.array("Integer", 2, bounds=(1, 4), name="n")

# モデルの目的関数を変更
model += 2 * add_vars[0] - add_vars[1]

モデルが変更されたことが確認できます。

>>> print(model)
minimize:
  2 q_0 q_1 + q_1 q_2 + q_2 q_3 + q_0 + q_1 + q_2 + q_3 + 2 n_0 - n_1 - 1
subject to:
  q_0 + q_1 + q_2 == 1 (weight: 1),
  q_1 + q_2 + q_3 == 1 (weight: 1)

制約条件の重みを設定する

LP ファイルおよび QPLIB ファイルには、制約条件の重みを記述することができません。 そのため、必要であれば、モデルをロードした後に制約の重みを設定します。 (参考:「制約条件の構築/制約条件の重みを設定する」)

先ほど変更したモデルを一度ファイルに出力し、再度読み込んで例として使用します。

# モデルの出力
save_lp(model, "model.lp")

# モデルの再読み込み
model, gen = load_lp("model.lp")

# モデルに含まれる制約条件リストを取り出す
constraints = model.constraints
>>> print(constraints)
[q_0 + q_1 + q_2 == 1 (weight: 1),
 q_1 + q_2 + q_3 == 1 (weight: 1)]

>>> constraints *= 2
>>> print(constraints)
[q_0 + q_1 + q_2 == 1 (weight: 2),
 q_1 + q_2 + q_3 == 1 (weight: 2)]

>>> constraints[0].weight = 5
>>> print(constraints)
[q_0 + q_1 + q_2 == 1 (weight: 5),
 q_1 + q_2 + q_3 == 1 (weight: 2)]

ペナルティ関数の生成アルゴリズムの指定

load_lp()load_qplib() は、第二引数に PenaltyFormulation を与えることができます。 これは、不等式制約のペナルティを生成する際に使用するアルゴリズムであり、デフォルトは Default です。(参考:「制約条件とペナルティ関数/不等式制約」)

from amplify import VariableGenerator, Model, save_lp, load_lp, less_equal

gen = VariableGenerator()
q = gen.array("Binary", 3)
c = less_equal(q[0] + q[1] + q[2], 2)

model = Model(c)
save_lp(model, "model.lp")
>>> model, gen = load_lp("model.lp", "Default")
>>> print(model.constraints[0].penalty)
2 q_0 q_1 + 2 q_0 q_2 - 2 q_0 n_0 + 2 q_1 q_2 - 2 q_1 n_0 - 2 q_2 n_0 + n_0^2 + q_0 + q_1 + q_2

>>> model, gen = load_lp("model.lp", "LinearRelaxation")
>>> print(model.constraints[0].penalty)
0.5 q_0 + 0.5 q_1 + 0.5 q_2