# 実行結果の評価 {py:func}`~amplify.solve` 関数の実行結果には、解に関する情報やモデル変換の情報・実行時間情報などさまざまな情報が含まれています。このページでは、これらの情報を取得し、活用する方法について説明します。 以下は、{py:class}`~amplify.FixstarsClient` を用いて、目的関数と制約条件で構成されたモデルに対して、求解の実行結果を取得する例です。 ```{testcode} 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) ``` ## 実行結果クラス {py:func}`~amplify.solve` 関数が返す {py:class}`~amplify.Result` クラスから、解に関する情報やモデル変換の情報・実行時間情報などを取得する方法を以下に示します。 (result-info-on-solutions)= ### 解に関する情報 入力モデルに対する解は、{py:class}`~amplify.Result` クラスの {py:attr}`~amplify.Result.solutions` アトリビュートに格納されています。{py:attr}`~amplify.Result.solutions` アトリビュートは {py:class}`~amplify.Result.SolutionList` クラスのインスタンスであり、{py:class}`~amplify.Result.Solution` クラスを要素に持つリストのように振る舞います。 ```{doctest} >>> type(result.solutions[0]) ``` {py:class}`~amplify.Result.Solution` クラスは解を表すクラスであり、以下のアトリビュートを持ちます。 ```{list-table} :header-rows: 1 - * アトリビュート名 * 型 * 概要 - * {py:attr}`~amplify.Result.Solution.objective` * {py:class}`float` * 目的関数の値 - * {py:attr}`~amplify.Result.Solution.values` * {py:class}`amplify.Values` * 解における各変数の値 - * {py:attr}`~amplify.Result.Solution.feasible` * {py:class}`bool` * 制約条件がみたされているかどうか - * {py:attr}`~amplify.Result.Solution.time` * {py:class}`datetime.timedelta` * 解が得られた時刻 ``` このうち、{py:class}`~amplify.Result.Solution.values` アトリビュートの型である {py:class}`~amplify.Values` クラスは解の値を表すクラスであり、変数をキーとし解の値を値とする辞書のように振る舞います。 ```{doctest} >>> 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 ``` また、変数配列クラスや多項式クラスの {py:meth}`~amplify.Poly.evaluate` メソッドや制約条件クラスの {py:meth}`~amplify.Constraint.is_satisfied` メソッドを使用することにより、{py:class}`~amplify.Values` クラスにより表される解の値を変数配列や多項式・制約条件に代入した結果を得ることができます。詳しくは以下の [](#決定変数の評価)、[](#多項式の評価)、[](#制約条件の評価) セクションを参照してください。 {py:class}`~amplify.Result` クラスは、{py:class}`~amplify.Result.solutions` アトリビュート以外にも解にアクセスするためのショートカットをいくつか提供しています。 まず、{py:class}`~amplify.Result` クラスの {py:attr}`~amplify.Result.best` アトリビュートにより、最良解が取得できます。また、{py:class}`~amplify.Result` クラスから直接インデックスによるアクセスを行うことも可能です。 ```{doctest} >>> 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}) ``` ### モデル変換に関する情報 以下のアトリビュートにより、モデル変換に関する情報を取得できます。詳細は [](conversion.md) を参照してください。 ```{list-table} * - {py:attr}`~amplify.Result.intermediate` - 中間モデルに関する情報 * - {py:attr}`~amplify.Result.embedding` - グラフ埋め込みに関する情報 ``` ### ソルバーのレスポンスに関する情報 {py:attr}`~amplify.solve` 関数はモデルの変換やグラフ埋め込み処理を行った後、ソルバークライアントの `solve(...)` メソッドを呼び出してソルバーを実行します。このときソルバークライアントが返却したオブジェクトは、{py:attr}`~amplify.Result.client_result` アトリビュートにより取得できます。 ```{doctest} >>> type(result.client_result) ``` {py:attr}`~amplify.Result.client_result` の型はソルバークライアントの型に依存します。それぞれのソルバークライアントの結果型の詳細については、[](solvers.md) およびそのサブページを参照してください。 ### 実行時間に関する情報 以下のアトリビュートにより、実行時間に関する情報を取得できます。詳細は [](timing.md) を参照してください。 ```{list-table} * - {py:attr}`~amplify.Result.total_time` - {py:func}`~amplify.solve` にかかった時間 * - {py:attr}`~amplify.Result.response_time` - ソルバーにリクエストを送ってからレスポンスが返ってくるまでの時間 * - {py:attr}`~amplify.Result.execution_time` - ソルバーが求解に費やした時間 ``` ## 決定変数の評価 定式化に使用した決定変数配列に対して、ソルバーの実行結果を決定変数の配列に代入した結果を取得できます。このとき、代入結果は決定変数配列と同じ {py:attr}`~numpy.ndarray.shape` を持つ NumPy 配列として得られます。 次のようにして、{py:class}`~amplify.PolyArray` の {py:meth}`~amplify.PolyArray.evaluate` メソッドに {py:class}`~amplify.Result.Solution` オブジェクトの {py:attr}`~amplify.Result.Solution.values` アトリビュートを渡すことで、実行結果に含まれる最良解の値を決定変数の配列で評価できます。 ```{tip} 実行結果に含まれる解は、実行結果の最良解を {py:attr}`~amplify.Result.best` アトリビュートで取得するか、あるいはリストと同じように要素アクセスを行うことで取得できます。 ``` ```{doctest} >>> 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]`{l=python} や `q[4]`{l=python} のように、定式化に使われなかった変数が含まれている場合、デフォルトではその変数がとりうる値から適当に一つ選んで代入されます。上記ではバイナリ変数のデフォルト値として `0`{l=python} が代入されています。 {py:meth}`~amplify.PolyArray.evaluate` メソッドに `default` キーワード引数を与えることで、評価が行われない場合に用いられる値を変更できます。`default` キーワード引数に数値を与えた場合、ソルバーに渡されなかった変数にはその値が代入されます。 ```{testcode} :hide: q_values = q.evaluate(result.best.values, default=3) ``` ```{doctest} >>> q_values = q.evaluate(result.best.values, default=3) # doctest: +SKIP 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.] ``` ```{note} 上記で与えた初期値 `default=3`{l=python} のように、変数の範囲外の値を指定すると警告が表示されます。 ``` `default` キーワード引数に {py:obj}`None` を与えた場合、ソルバーに渡されなかった変数はそのまま残ります。この場合のみ、{py:meth}`~amplify.PolyArray.evaluate` メソッドは {py:class}`~amplify.PolyArray` を返します。 ```{doctest} >>> q_values = q.evaluate(result.best.values, default=None) >>> print(q_values) [ 0, 0, 1, q_3, q_4] ``` ```{attention} モデルに含まれる変数であってもソルバーに渡されない場合があります。ペナルティ関数の生成やグラフ埋め込みなどといったモデル変換により、項同士が相殺される可能性があるためです。 ``` ## 多項式の評価 入力モデルの目的関数を {py:func}`~amplify.solve` 関数が返した解で評価した結果は、{py:class}`~amplify.Result.Solution` オブジェクトの {py:attr}`~amplify.Result.Solution.objective` アトリビュートで取得できます。 ```{doctest} >>> solution = result.best >>> solution.objective -1.0 ``` 一方で、例えば目的関数がいくつかの多項式の和として表されるときなど、目的関数以外の多項式をソルバーが返した解で評価したい場合があります。この場合、変数の配列の評価と同様に、{py:class}`~amplify.Poly` の {py:meth}`~amplify.Poly.evaluate` メソッドに {py:class}`~amplify.Result.Solution` オブジェクトの {py:attr}`~amplify.Result.Solution.values` アトリビュートを渡します。 ```{doctest} >>> objective_1 = q[0] * q[1] # 目的関数の第 1 項 >>> objective_1.evaluate(solution.values) 0.0 ``` ```{hint} 多項式にソルバーに渡されなかった変数が含まれている場合の動作は、{py:class}`~amplify.PolyArray` クラスの {py:meth}`~amplify.PolyArray.evaluate` メソッドと同様です。`default` キーワード引数によってこの動作を変更できます。 ``` ## 制約条件の評価 実行結果の解が制約条件が満たされているかどうかを知りたい場合は、{py:class}`~amplify.Result.Solution` クラスの {py:attr}`~amplify.Result.Solution.feasible` アトリビュートで確認できます。 ```{doctest} >>> solution.feasible True ``` デフォルトでは、{py:func}`~amplify.solve` 関数はモデルに含まれる全ての制約条件を満たす解だけが取得できるようにフィルタされているため、上記は必ず {py:obj}`True` になります。この動作を変更して、ソルバーの出力する解で制約条件を満たさないものも取得できるようにするには、前もって {py:func}`~amplify.solve` 関数の `filter_solution` キーワード引数に {py:class}`bool` を渡すか、あるいは後から {py:class}`~amplify.Result` クラスの {py:attr}`~amplify.Result.filter_solution` に {py:obj}`False` を設定します。 あえてモデルに矛盾して満たせない制約条件を追加して、このことを確認してみます。 ```{testcode} # 目的関数と制約条件を作成 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) ``` 解のフィルタが有効な場合は {py:class}`~amplify.Result` から解が取得できません。 ```python >>> result.best.feasible Traceback (most recent call last): File "", line 1, in RuntimeError: result has no feasible solution ``` 解のフィルタを無効にすると、制約条件を満たさない解も取得できるようになります。 ```{doctest} >>> result.filter_solution = False >>> result.best.feasible False ``` モデルの構成、ペナルティ関数の重みやソルバーの設定など、何らかの理由で制約条件を満たさない解が取得された場合、どの制約条件が満たされなかったのかを特定したいことがあります。 {py:class}`~amplify.Constraint` クラスの {py:meth}`~amplify.Constraint.is_satisfied` メソッドに {py:func}`~amplify.solve` 関数が返した解を渡すと制約条件を満たしているかどうかが確認できます。 ```python >>> constraint1.is_satisfied(result.best.values) True >>> constraint2.is_satisfied(result.best.values) False ``` 上記の例では `constraint2` が満たせなかったことがわかります。 次のようにして、モデルに含まれる制約条件のリストから機械的に制約条件を満たさないものを特定することもできます。これはペナルティ関数の重みを調整して再実行するときに利用すると便利です。 ```python >>> 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"})] ```