Adapter 固有 diagnostics#

Adapter diagnostics は、portable な Solution には入らない solver 側の情報を残すための仕組みです。decode 済みの OMMX 結果を見るときは Solution を使います。backend solver が何を観測し、何を報告し、 どこまで証明したかを確認したい場合に diagnostics を使います。

PySCIPOpt で diagnostics を記録する#

PySCIPOpt Adapter は、solve()DiagnosticCollector を渡すと SCIP の progress と termination 情報を記録します。通常は SCIPDiagnosticsAnalyzer を通して読みます。

from ommx import adapter, dataset
from ommx_pyscipopt_adapter import (
    OMMXPySCIPOptAdapter as Adapter,
    SCIPDiagnosticsAnalyzer,
)

instance = dataset.miplib2017("air05")

diag = adapter.DiagnosticCollector()
solution = Adapter.solve(instance, diagnostics=diag)

analyze = SCIPDiagnosticsAnalyzer(diag.diagnostics)

analyze.progress_history_df[["primal_bound", "dual_bound"]].loc[5:].plot()
SCIP の primal bound と dual bound の推移

SCIPDiagnosticsAnalyzer で読み出した SCIP の primal / dual bound の推移。#

progress_history_dfsolving_time_sec を index にした pandas DataFrame です。 dual_boundgapincumbent_objective などの Series property も同じ time index を使うので、 そのまま時間軸の plot に使えます。termination_result は最終的な SCIP report を表す dictionary です。

dual_bound = analyze.dual_bound
gap = analyze.gap
incumbents = analyze.incumbent_objective
termination = analyze.termination_result

DataFrame / Series helper は pandas を必要とします。pandas が使えない環境では、 progress sample には progress_history_records、最終 report には termination_result を使ってください。

PySCIPOpt が記録するもの#

PySCIPOpt Adapter は 2 種類の SCIP diagnostics を記録します。

SCIPProgressSnapshot は、SCIP event callback から記録される progress sample です。現在は BESTSOLFOUNDDUALBOUNDIMPROVED を監視しています。 progress snapshot には solving_time_secnode_countprimal_bounddual_boundgapincumbent_objective などが含まれます。

SCIPTerminationReport は、model.optimize() が終了した後、 PySCIPOpt model を OMMX Solution に decode する前に記録される最終 report です。 statusprimal_bounddual_boundgapobjective_value、node 数、LP / cut counter、 primal-dual integral、timing、SCIP / PySCIPOpt version metadata などが含まれます。

progress snapshot は callback 時点の観測値です。SCIP は BESTSOLFOUND callback を、 集計済みの統計がすべて更新される前に呼ぶことがあります。終了時点の値は termination report を参照してください。

完全な member list は API Reference の SCIPProgressSnapshotSCIPTerminationReportSCIPDiagnosticsAnalyzer を参照してください。

失敗時の処理#

直接取得は、OMMX Solution への decode が失敗する場合にも有用です。PySCIPOpt Adapter は decode の前に termination report を記録するため、InfeasibleDetectedUnboundedDetected などの adapter exception が発生しても、collector には SCIP の最終 status や bound が残ります。

from ommx.adapter import DiagnosticCollector, UnboundedDetected
from ommx_pyscipopt_adapter import OMMXPySCIPOptAdapter, SCIPDiagnosticsAnalyzer

collector = DiagnosticCollector()

try:
    OMMXPySCIPOptAdapter.solve(instance, diagnostics=collector)
except UnboundedDetected:
    analysis = SCIPDiagnosticsAnalyzer(collector.diagnostics)
    print(analysis.termination_result)

Experiment 連携#

log_solve() を使う場合、ユーザー側から diagnostics keyword を 渡さないでください。この keyword は Run.log_solve が予約しています。diagnostics 収集は デフォルトでは無効なので、Experiment Artifact の Solve entry に diagnostics を保存したい場合は store_diagnostics=True を指定してください。このとき adapter に diagnostics sink が渡されます。

from ommx.experiment import Experiment
from ommx_pyscipopt_adapter import OMMXPySCIPOptAdapter, SCIPDiagnosticsAnalyzer

with Experiment() as experiment:
    with experiment.run() as run:
        solution = run.log_solve(
            OMMXPySCIPOptAdapter,
            instance,
            store_diagnostics=True,
        )

solve = experiment.runs[0].solves[0]
analysis = SCIPDiagnosticsAnalyzer(solve.diagnostics)

print(analysis.dual_bound)
print(analysis.termination_result)

Experiment から diagnostics で読み出した diagnostics は、 元の dataclass instance ではなく dictionary です。これにより、保存済み Artifact は求解時に使われた Python class 定義から独立して読めます。直接取得した場合と同じ records / DataFrame / Series view が 必要な場合は、その list をそのまま SCIPDiagnosticsAnalyzer に渡してください。

solve() が OMMX Solution を返す前に例外を投げた場合でも、 可能な限り Run.log_solve は failed Solve entry を記録します。この entry は status == "failed" または "interrupted"、output Solution なし、失敗前に収集済みの diagnostics あり、という形で保存されます。diagnostics が保存されるのは store_diagnostics=True の場合です。

adapter diagnostics の contract は API Reference の DiagnosticsSinkDiagnosticCollectorsolve() を参照してください。