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 に使えます。diagnostics に SCIP の termination report が含まれる場合、 この history には最後の TERMINATION row も含まれます。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 が使えない環境では、 存在する場合は最後の TERMINATION row も含む progress history には progress_history_records、 最終 report には termination_result を使ってください。

PySCIPOpt が記録するもの#

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

SCIPProgressSnapshot は、SCIP event callback から記録される progress sample と、最終 termination report から記録される progress sample です。 現在は BESTSOLFOUNDDUALBOUNDIMPROVED を監視し、最後に TERMINATION snapshot を追加します。 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 などが含まれます。

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

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

HiGHS で diagnostics を記録する#

HiGHS Adapter は、solve()DiagnosticCollector を渡すと MIP progress と termination 情報を記録します。通常は HighsDiagnosticsAnalyzer を通して読みます。

from ommx import adapter
from ommx_highs_adapter import OMMXHighsAdapter, HighsDiagnosticsAnalyzer

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

analysis = HighsDiagnosticsAnalyzer(diag.diagnostics)

analysis.progress_history_df[["primal_bound", "dual_bound"]].plot()
print(analysis.dual_bound)
print(analysis.termination_result)

progress_history_dfsolving_time_sec を index にした pandas DataFrame です。 dual_boundgapprimal_bound などの Series property も同じ time index を使うので、 そのまま時間軸の plot に使えます。

HighsProgressSnapshot は、HiGHS の MIP logging callback から 記録される progress sample です。progress snapshot には solving_time_secmip_node_countprimal_bounddual_boundgap などが含まれます。

HighsTerminationReport は、model.run() が終了した後、 HiGHS model を OMMX Solution に decode する前に記録される最終 report です。 statusobjective_valuemip_dual_boundmip_gapmip_node_count、 iteration count、feasibility violation summary、runtime、HiGHS version metadata などが含まれます。 終了時点の scalar 値が必要な場合は、termination_result または termination_* property を使ってください。

失敗時の処理#

直接取得は、OMMX Solution への decode が失敗する場合にも有用です。PySCIPOpt Adapter と HiGHS Adapter は decode の前に termination report を記録するため、 InfeasibleDetectedUnboundedDetected などの adapter exception が発生しても、collector には solver の最終 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() または log_sample() を使う場合、ユーザー側から diagnostics keyword を 渡さないでください。この keyword は logging method が予約しています。diagnostics 収集は デフォルトでは無効なので、Experiment Artifact の対応するSolveまたはSampling 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 の場合です。 Run.log_sample でsamplerが失敗した場合は、output SampleSetを持たないSampling entryとして Solveとは分けて記録されます。

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