Adapter-specific Diagnostics#
Adapter diagnostics preserve solver-side information that does not fit in the
portable Solution. Use Solution for the
decoded OMMX result. Use diagnostics when you need to inspect what the backend
solver observed, reported, or proved.
Record Diagnostics with the PySCIPOpt Adapter#
The PySCIPOpt Adapter records SCIP progress and termination information when you
pass a DiagnosticCollector to solve(). The usual way to
read that data is through
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 and dual bound history read through
SCIPDiagnosticsAnalyzer.#
progress_history_df is a pandas DataFrame indexed by solving_time_sec.
Series properties such as dual_bound, gap, and incumbent_objective use the
same time index, so they are ready for time-based plots. The history includes a
final TERMINATION row when diagnostics include a SCIP termination report.
termination_result is a dictionary containing that final SCIP report.
dual_bound = analyze.dual_bound
gap = analyze.gap
incumbents = analyze.incumbent_objective
termination = analyze.termination_result
The DataFrame and Series helpers require pandas. When pandas is not available,
use progress_history_records for the progress history, including the final
TERMINATION row when present, and termination_result for the final report.
What PySCIPOpt Records#
The PySCIPOpt Adapter records two kinds of SCIP diagnostics.
SCIPProgressSnapshot is a progress sample
recorded from SCIP event callbacks and from the final termination report. The
adapter currently listens for BESTSOLFOUND and DUALBOUNDIMPROVED, then adds a
final TERMINATION snapshot. A progress snapshot includes fields such as
solving_time_sec, node_count, primal_bound, dual_bound, gap, and
incumbent_objective.
SCIPTerminationReport is the final SCIP report
recorded after model.optimize() finishes and before the PySCIPOpt model is
decoded back into an OMMX Solution. It includes fields such as status,
primal_bound, dual_bound, gap, objective_value, node counts, LP and cut
counters, primal-dual integral, timings, and SCIP/PySCIPOpt version metadata.
Callback progress snapshots are callback-time observations. SCIP may call a
BESTSOLFOUND callback before every aggregate statistic has been updated, so
use the TERMINATION row or the termination report for terminal values.
For the complete member lists, see the API Reference for
SCIPProgressSnapshot,
SCIPTerminationReport, and
SCIPDiagnosticsAnalyzer.
Record Diagnostics with the HiGHS Adapter#
The HiGHS Adapter records MIP progress and termination information when you
pass a DiagnosticCollector to solve(). Read that data
through 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 is a pandas DataFrame indexed by solving_time_sec.
Series properties such as dual_bound, gap, and primal_bound use the same
time index, so they are ready for time-based plots.
HighsProgressSnapshot is recorded from HiGHS MIP
logging callbacks. A progress snapshot includes fields such as
solving_time_sec, mip_node_count, primal_bound, dual_bound, and gap.
HighsTerminationReport is recorded after
model.run() finishes and before the HiGHS model is decoded back into an OMMX
Solution. It includes fields such as status, objective_value,
mip_dual_bound, mip_gap, mip_node_count, iteration counts, feasibility
violation summaries, runtime, and HiGHS version metadata. Use
termination_result or the termination_* properties when you need terminal
scalar values.
Failure Handling#
Direct collection is useful when OMMX Solution decoding fails. The PySCIPOpt
and HiGHS Adapters record the termination report before decoding, so the
collector can still contain the final solver status and bounds when the solve
raises an adapter exception such as InfeasibleDetected or
UnboundedDetected.
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 Integration#
When using log_solve() or
log_sample(), do not pass the diagnostics
keyword yourself. The logging method owns that reserved keyword,
and diagnostics collection is disabled by default. Set
store_diagnostics=True to pass a diagnostics sink to the adapter and store
recorded diagnostics with the corresponding Solve or Sampling entry in the
Experiment Artifact.
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)
Diagnostics loaded from an Experiment through
diagnostics are dictionaries, not the original
dataclass instances. This keeps stored Artifacts independent of the Python class
definitions used when the solve was recorded. Pass that list directly to
SCIPDiagnosticsAnalyzer when you want the same
records, DataFrame, or Series views as direct collection.
If solve() raises before returning an OMMX
Solution, Run.log_solve still records a failed Solve entry when possible. That
entry has status == "failed" or "interrupted", no output Solution, and any
diagnostics collected before the failure when store_diagnostics=True.
Run.log_sample records sampler failures separately as Sampling entries with
no output SampleSet.
See the API Reference for the adapter diagnostics contract:
DiagnosticsSink,
DiagnosticCollector, and
solve() or
sample().