実行結果の評価¶
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
クラスは解を表すクラスであり、以下のアトリビュートを持ちます。
アトリビュート名 |
型 |
概要 |
---|---|---|
目的関数の値 |
||
解における各変数の値 |
||
制約条件がみたされているかどうか |
||
解が得られた時刻 |
このうち、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})
モデル変換に関する情報¶
以下のアトリビュートにより、モデル変換に関する情報を取得できます。詳細は モデルの変換 を参照してください。
中間モデルに関する情報 |
|
グラフ埋め込みに関する情報 |
ソルバーのレスポンスに関する情報¶
solve
関数はモデルの変換やグラフ埋め込み処理を行った後、ソルバークライアントの solve(...)
メソッドを呼び出してソルバーを実行します。このときソルバークライアントが返却したオブジェクトは、client_result
アトリビュートにより取得できます。
>>> type(result.client_result)
<class 'amplify.FixstarsClient.Result'>
client_result
の型はソルバークライアントの型に依存します。それぞれのソルバークライアントの結果型の詳細については、クライアントの詳細 およびそのサブページを参照してください。
実行時間に関する情報¶
以下のアトリビュートにより、実行時間に関する情報を取得できます。詳細は 実行時間情報の取得 を参照してください。
|
|
ソルバーにリクエストを送ってからレスポンスが返ってくるまでの時間 |
|
ソルバーが求解に費やした時間 |
決定変数の評価¶
定式化に使用した決定変数配列に対して、ソルバーの実行結果を決定変数の配列に代入した結果を取得できます。このとき、代入結果は決定変数配列と同じ shape
を持つ NumPy 配列として得られます。
次のようにして、PolyArray
の evaluate()
メソッドに 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
一方で、例えば目的関数がいくつかの多項式の和として表されるときなど、目的関数以外の多項式をソルバーが返した解で評価したい場合があります。この場合、変数の配列の評価と同様に、Poly
の evaluate()
メソッドに 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_solution
に False
を設定します。
あえてモデルに矛盾して満たせない制約条件を追加して、このことを確認してみます。
# 目的関数と制約条件を作成
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"})]