実行結果の評価

solve() 関数の実行結果には、解に関する情報やモデル変換の情報・実行時間情報などさまざまな情報が含まれています。このページでは、これらの情報を取得し、活用する方法について説明します。

以下は、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)

実行結果クラス

solve() 関数が返す Result クラスから、解に関する情報やモデル変換の情報・実行時間情報などを取得する方法を以下に示します。

解に関する情報

入力モデルに対する解は、Result クラスの solutions アトリビュートに格納されています。solutions アトリビュートは SolutionList クラスのインスタンスであり、Solution クラスを要素に持つリストのように振る舞います。

>>> type(result.solutions[0])
<class 'amplify.Result.Solution'>

Solution クラスは解を表すクラスであり、以下のアトリビュートを持ちます。

アトリビュート名

概要

objective

float

目的関数の値

values

amplify.Values

解における各変数の値

feasible

bool

制約条件がみたされているかどうか

time

datetime.timedelta

解が得られた時刻

このうち、values アトリビュートの型である Values クラスは解の値を表すクラスであり、変数をキーとし解の値を値とする辞書のように振る舞います。

>>> solution = result.solutions[0]
>>> solution.values
Values({Poly(q_0): 0, Poly(q_1): 0, Poly(q_2): 1})
>>> solution.values[q[0]]
0.0
>>> solution.values[q[2]]
1.0

また、変数配列クラスや多項式クラスの evaluate() メソッドや制約条件クラスの is_satisfied() メソッドを使用することにより、Values クラスにより表される解の値を変数配列や多項式・制約条件に代入した結果を得ることができます。詳しくは以下の 決定変数の評価多項式の評価制約条件の評価 セクションを参照してください。

Result クラスは、solutions アトリビュート以外にも解にアクセスするためのショートカットをいくつか提供しています。 まず、Result クラスの best アトリビュートにより、最良解が取得できます。また、Result クラスから直接インデックスによるアクセスを行うことも可能です。

>>> result.best.values
Values({Poly(q_0): 0, Poly(q_1): 0, Poly(q_2): 1})
>>> len(result)
1
>>> result[0].values
Values({Poly(q_0): 0, Poly(q_1): 0, Poly(q_2): 1})

モデル変換に関する情報

以下のアトリビュートにより、モデル変換に関する情報を取得できます。詳細は モデルの変換 を参照してください。

intermediate

中間モデルに関する情報

embedding

グラフ埋め込みに関する情報

ソルバーのレスポンスに関する情報

solve 関数はモデルの変換やグラフ埋め込み処理を行った後、ソルバークライアントの solve(...) メソッドを呼び出してソルバーを実行します。このときソルバークライアントが返却したオブジェクトは、client_result アトリビュートにより取得できます。

>>> type(result.client_result)
<class 'amplify.FixstarsClient.Result'>

client_result の型はソルバークライアントの型に依存します。それぞれのソルバークライアントの結果型の詳細については、クライアントの詳細 およびそのサブページを参照してください。

実行時間に関する情報

以下のアトリビュートにより、実行時間に関する情報を取得できます。詳細は 実行時間情報の取得 を参照してください。

total_time

solve() にかかった時間

response_time

ソルバーにリクエストを送ってからレスポンスが返ってくるまでの時間

execution_time

ソルバーが求解に費やした時間

決定変数の評価

定式化に使用した決定変数配列に対して、ソルバーの実行結果を決定変数の配列に代入した結果を取得できます。このとき、代入結果は決定変数配列と同じ shape を持つ NumPy 配列として得られます。

次のようにして、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_solution キーワード引数に bool を渡すか、あるいは後から Result クラスの filter_solutionFalse を設定します。

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

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

# モデルの定義 (矛盾する制約条件が含まれる)
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 not c.is_satisfied(result.best.values))
[Constraint({conditional: q_0 + q_1 + q_2 == 2, weight: 1, label: "sum equals two"})]