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()
SCIPDiagnosticsAnalyzer で読み出した
SCIP の primal / dual bound の推移。#
progress_history_df は solving_time_sec を index にした pandas DataFrame です。
dual_bound、gap、incumbent_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 です。
現在は BESTSOLFOUND と DUALBOUNDIMPROVED を監視し、最後に TERMINATION snapshot を追加します。
progress snapshot には solving_time_sec、node_count、primal_bound、dual_bound、
gap、incumbent_objective などが含まれます。
SCIPTerminationReport は、model.optimize() が終了した後、
PySCIPOpt model を OMMX Solution に decode する前に記録される最終 report です。
status、primal_bound、dual_bound、gap、objective_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 の
SCIPProgressSnapshot、
SCIPTerminationReport、
SCIPDiagnosticsAnalyzer を参照してください。
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_df は solving_time_sec を index にした pandas DataFrame です。
dual_bound、gap、primal_bound などの Series property も同じ time index を使うので、
そのまま時間軸の plot に使えます。
HighsProgressSnapshot は、HiGHS の MIP logging callback から
記録される progress sample です。progress snapshot には solving_time_sec、
mip_node_count、primal_bound、dual_bound、gap などが含まれます。
HighsTerminationReport は、model.run() が終了した後、
HiGHS model を OMMX Solution に decode する前に記録される最終 report です。
status、objective_value、mip_dual_bound、mip_gap、mip_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 を記録するため、
InfeasibleDetected や UnboundedDetected などの
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 の
DiagnosticsSink、
DiagnosticCollector、
solve() または
sample() を参照してください。