実行結果の評価#

solve() 関数の返却するソルバーの実行結果から、決定変数や目的関数、制約条件を評価する方法を解説します。

決定変数の評価#

定式化に使用した決定変数配列に対して、ソルバーの実行結果に含まれる解における変数の値を決定変数の配列と同じ shape を持つ NumPy 配列として得たい場合があります。まず、例として、目的関数と制約条件で構成されたモデルに対して、FixstarsClient を用いてを求解を実行します。

from datetime import timedelta
from amplify import VariableGenerator, equal_to, FixstarsClient, solve

# 決定変数の配列を生成
gen = VariableGenerator()
q = gen.array("Binary", 5)

# 目的関数と制約条件を作成
objective = q[0] * q[1] - q[2]
constraint = equal_to(q[0] + q[1] + q[2], 1)

# モデルの定義
model = objective + constraint

# ソルバークライアントの作成
client = FixstarsClient()
# client.token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
client.parameters.timeout = timedelta(milliseconds=1000)

# 実行結果の取得
result = solve(model, client)

次のようにして、PolyArrayevaluate() メソッドに Solution オブジェクトの values アトリビュートを渡すことで、実行結果に含まれる最良解の値を決定変数の配列で評価できます。

Tip

実行結果に含まれる解は、実行結果の最良解を best アトリビュートで取得するか、あるいはリストと同じように要素アクセスを行うことで取得できます。

>>> print(result.best.values)
{q_0: 0, q_1: 0, q_2: 1}
>>> q_values = q.evaluate(result.best.values)
>>> print(q_values)
[0. 0. 1. 0. 0.]

もし、上記の q[3]q[4] のように、定式化に使われなかった変数が含まれている場合、デフォルトではその変数がとりうる値から適当に一つ選んで代入されます。上記ではバイナリ変数の仮の値として 0 が代入されています。

evaluate() メソッドに default キーワード引数を与えることで、評価が行われない場合に用いられる値を変更できます。default キーワード引数に数値を与えた場合、ソルバーに渡されなかった変数にはその値が代入されます。

>>> q_values = q.evaluate(result.best.values, default=3)    
Warning: Substituting variable q_3 with 3 is out of bounds.
Warning: Substituting variable q_4 with 3 is out of bounds.
>>> print(q_values)
[0. 0. 1. 3. 3.]

注釈

上記で与えた初期値 default=3 のように、変数の範囲外の値を指定すると警告が表示されます。

default キーワード引数に None を与えた場合、ソルバーに渡されなかった変数はそのまま残ります。この場合のみ、evaluate() メソッドは PolyArray を返します。

>>> q_values = q.evaluate(result.best.values, default=None)
>>> print(q_values)
[  0,   0,   1, q_3, q_4]

注意

モデルに含まれる変数であってもソルバーに渡されない場合があります。ペナルティ関数の生成やグラフ埋め込みなどといったモデル変換により、項同士が相殺される可能性があるためです。

多項式の評価#

入力モデルの目的関数を solve() 関数が返した解で評価した結果は、Solution オブジェクトの objective アトリビュートで取得できます。

>>> solution = result.best
>>> solution.objective
-1.0

一方で、例えば目的関数がいくつかの多項式の和として表されるときなど、目的関数以外の多項式をソルバーが返した解で評価したい場合があります。この場合、変数の配列の評価と同様に、Polyevaluate() メソッドに Solution オブジェクトの values アトリビュートを渡します。

>>> objective_1 = q[0] * q[1] # 目的関数の第 1 項
>>> objective_1.evaluate(solution.values)
0.0

ヒント

多項式にソルバーに渡されなかった変数が含まれている場合の動作は、PolyArray クラスの evaluate() メソッドと同様です。default キーワード引数によってこの動作を変更できます。

制約条件の評価#

実行結果の解が制約条件が満たされているかどうかを知りたい場合は、Solution クラスの feasible アトリビュートで確認できます。

>>> solution.feasible
True

デフォルトでは、solve() 関数はモデルに含まれる全ての制約条件を満たす解だけが取得できるようにフィルタされているため、上記は必ず True になります。この動作を変更して、ソルバーの出力する解で制約条件を満たさないものも取得できるようにするには、前もって solve() 関数の filter キーワード引数に bool を渡すか、あるいは後から Result クラスの filter_solutionFalse を設定します。

あえてモデルに矛盾して満たせない制約条件を追加して、このことを確認してみます。

# 目的関数と制約条件を作成
objective = q[0] * q[1] - q[2]
constraint1 = equal_to(q[0] + q[1] + q[2], 1)
constraint2 = equal_to(q[0] + q[1] + q[2], 2)

# モデルの定義 (矛盾する制約条件が含まれる)
model = objective + constraint1 + constraint2

# 実行結果の取得
result = solve(model, client)

解のフィルタが有効な場合は Result から解が取得できません。

>>> result.best.feasible
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: result has no feasible solution

解のフィルタを無効にすると、制約条件を満たさない解も取得できるようになります。

>>> result.filter_solution = False
>>> result.best.feasible
False

モデルの構成、ペナルティ関数の重みやソルバーの設定など、何らかの理由で制約条件を満たさない解が取得された場合、どの制約条件が満たされなかったのかを特定したいことがあります。

Constraint クラスの is_satisfied() メソッドに solve() 関数が返した解を渡すと制約条件を満たしているかどうかが確認できます。

>>> constraint1.is_satisfied(result.best.values)
True
>>> constraint2.is_satisfied(result.best.values)
False

上記の例では constraint2 が満たせなかったことがわかります。

次のようにして、モデルに含まれる制約条件のリストから機械的に制約条件を満たさないものを特定することもできます。これはペナルティ関数の重みを調整して再実行するときに利用すると便利です。

>>> list(c for c in model.constraints if c.is_satisfied(result.best.values))
[Constraint({conditional: q_0 + q_1 + q_2 == 1, weight: 1, label: ""})]