ommx_pyscipopt_adapter.adapter

目次

ommx_pyscipopt_adapter.adapter#

Classes#

OMMXPySCIPOptAdapter

An abstract interface for OMMX Solver Adapters, defining how solvers should be used with OMMX.

SCIPDiagnosticsAnalyzer

Pandas-like post-processor for PySCIPOpt diagnostics.

SCIPProgressSnapshot

SCIP solve progress observed from one solver event callback.

SCIPTerminationReport

SCIP-side termination summary recorded after model.optimize().

Module Contents#

class OMMXPySCIPOptAdapter(ommx_instance: Instance, *, initial_state: ommx.v1.ToState | None = None)#

An abstract interface for OMMX Solver Adapters, defining how solvers should be used with OMMX.

See the implementation guide for more details.

Subclasses should set ADDITIONAL_CAPABILITIES to declare which non-standard constraint types they can handle. Standard constraints are always supported.

Available capabilities:

  • AdditionalCapability.Indicator: binvar = 1 → f(x) <= 0

  • AdditionalCapability.OneHot: exactly one of a set of binary variables is 1

  • AdditionalCapability.Sos1: at most one of a set of variables is non-zero

The default is an empty set (standard constraints only). Subclasses must call super().__init__(ommx_instance) so that any constraint types the adapter does not support are automatically converted into regular constraints (Big-M for indicator / SOS1, linear equality for one-hot). Conversions mutate ommx_instance in place and are emitted at INFO level as tracing events from the Rust SDK; configure a Python OpenTelemetry TracerProvider before the first call to observe them via pyo3-tracing-opentelemetry.

decode(data: pyscipopt.Model) Solution#

Convert optimized pyscipopt.Model and ommx.v1.Instance to ommx.v1.Solution.

This method is intended to be used if the model has been acquired with solver_input for further adjustment of the solver parameters, and separately optimizing the model.

Note that alterations to the model may make the decoding process incompatible -- decoding will only work if the model still describes effectively the same problem as the OMMX instance used to create the adapter.

Examples#

>>> from ommx_pyscipopt_adapter import OMMXPySCIPOptAdapter
>>> from ommx.v1 import Instance, DecisionVariable

>>> p = [10, 13, 18, 32, 7, 15]
>>> w = [11, 15, 20, 35, 10, 33]
>>> x = [DecisionVariable.binary(i) for i in range(6)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(p[i] * x[i] for i in range(6)),
...     constraints={0: sum(w[i] * x[i] for i in range(6)) <= 47},
...     sense=Instance.MAXIMIZE,
... )

>>> adapter = OMMXPySCIPOptAdapter(instance)
>>> model = adapter.solver_input
>>> # ... some modification of model's parameters
>>> model.optimize()

>>> solution = adapter.decode(model)
>>> solution.objective
42.0
decode_to_state(data: pyscipopt.Model) State#

Create an ommx.v1.State from an optimized PySCIPOpt Model.

Examples#

The following example shows how to solve an unconstrained linear optimization problem with `x1` as the objective function.

>>> from ommx_pyscipopt_adapter import OMMXPySCIPOptAdapter
>>> from ommx.v1 import Instance, DecisionVariable

>>> x1 = DecisionVariable.integer(1, lower=0, upper=5)
>>> ommx_instance = Instance.from_components(
...     decision_variables=[x1],
...     objective=x1,
...     constraints={},
...     sense=Instance.MINIMIZE,
... )
>>> adapter = OMMXPySCIPOptAdapter(ommx_instance)
>>> model = adapter.solver_input
>>> model.optimize()

>>> ommx_state = adapter.decode_to_state(model)
>>> ommx_state.entries
{1: 0.0}
classmethod solve(ommx_instance: Instance, *, initial_state: ommx.v1.ToState | None = None, diagnostics: DiagnosticsSink | None = None) Solution#

Solve the given ommx.v1.Instance using PySCIPopt, returning an ommx.v1.Solution.

パラメータ:
  • ommx_instance -- The ommx.v1.Instance to solve.

  • initial_state -- Optional initial solution state.

Examples#

KnapSack Problem

>>> from ommx.v1 import Instance, DecisionVariable
>>> from ommx.v1 import Solution
>>> from ommx_pyscipopt_adapter import OMMXPySCIPOptAdapter

>>> p = [10, 13, 18, 32, 7, 15]
>>> w = [11, 15, 20, 35, 10, 33]
>>> x = [DecisionVariable.binary(i) for i in range(6)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(p[i] * x[i] for i in range(6)),
...     constraints={0: sum(w[i] * x[i] for i in range(6)) <= 47},
...     sense=Instance.MAXIMIZE,
... )

Solve it

>>> solution = OMMXPySCIPOptAdapter.solve(instance)

Check output

>>> sorted([(id, value) for id, value in solution.state.entries.items()])
[(0, 1.0), (1, 0.0), (2, 0.0), (3, 1.0), (4, 0.0), (5, 0.0)]
>>> solution.feasible
True
>>> assert solution.optimality == Solution.OPTIMAL

p[0] + p[3] = 42
w[0] + w[3] = 46 <= 47

>>> solution.objective
42.0
>>> solution.get_constraint_value(0)
-1.0

Infeasible Problem

>>> from ommx.v1 import Instance, DecisionVariable
>>> from ommx_pyscipopt_adapter import OMMXPySCIPOptAdapter

>>> x = DecisionVariable.integer(0, upper=3, lower=0)
>>> instance = Instance.from_components(
...     decision_variables=[x],
...     objective=x,
...     constraints={0: x >= 4},
...     sense=Instance.MAXIMIZE,
... )

>>> OMMXPySCIPOptAdapter.solve(instance)
Traceback (most recent call last):
    ...
ommx.adapter.InfeasibleDetected: Model was infeasible

Unbounded Problem

>>> from ommx.v1 import Instance, DecisionVariable
>>> from ommx_pyscipopt_adapter import OMMXPySCIPOptAdapter

>>> x = DecisionVariable.integer(0, lower=0)
>>> instance = Instance.from_components(
...     decision_variables=[x],
...     objective=x,
...     constraints={},
...     sense=Instance.MAXIMIZE,
... )

>>> OMMXPySCIPOptAdapter.solve(instance)
Traceback (most recent call last):
    ...
ommx.adapter.UnboundedDetected: Model was unbounded
ADDITIONAL_CAPABILITIES#
property solver_input: pyscipopt.Model#

The PySCIPOpt model generated from this OMMX instance

class SCIPDiagnosticsAnalyzer(diagnostics: Iterable[Any])#

Pandas-like post-processor for PySCIPOpt diagnostics.

The analyzer accepts either typed diagnostics collected by ommx.adapter.DiagnosticCollector or dictionaries loaded from ommx.experiment.Solve.diagnostics.

*_records() methods return list[dict[str, object]] and do not require pandas. *_df() methods return pandas DataFrames with stable columns and import pandas lazily. If pandas is unavailable, use the corresponding records method.

gap_evolution() tuple[tuple[float, float, float, float, str], Ellipsis]#

Return (time, gap, primal_bound, dual_bound, event) samples.

gap_evolution_df() Any#

Return SCIP gap evolution as a pandas DataFrame.

gap_evolution_records() list[dict[str, object]]#

Return records for plotting SCIP gap evolution.

incumbent_evolution() tuple[tuple[float, float, str], Ellipsis]#

Return (time, incumbent_objective, event) samples.

incumbent_evolution_df() Any#

Return incumbent objective evolution as a pandas DataFrame.

incumbent_evolution_records() list[dict[str, object]]#

Return records for plotting incumbent objective evolution.

progress_df() Any#

Return progress snapshots as a pandas DataFrame.

progress_records() list[dict[str, object]]#

Return one dictionary per SCIP progress snapshot.

termination_df() Any#

Return the terminal SCIP report as a pandas DataFrame.

termination_record() dict[str, object] | None#

Return the terminal SCIP report as one dictionary, if present.

termination_records() list[dict[str, object]]#

Return the terminal SCIP report as a one-row record list.

property progress_snapshots: tuple[SCIPProgressSnapshot, Ellipsis]#

Progress snapshots in the order they were recorded.

property termination_report: SCIPTerminationReport | None#

Final termination report, or None when none was recorded.

class SCIPProgressSnapshot#

SCIP solve progress observed from one solver event callback.

The PySCIPOpt adapter records this snapshot for each tracked SCIP event. It currently listens for BESTSOLFOUND and DUALBOUNDIMPROVED. Each snapshot is the model state visible from that callback. SCIP may call a BESTSOLFOUND callback before every aggregate model statistic has been updated, so use SCIPTerminationReport for terminal values.

classmethod from_event(model: pyscipopt.Model, event: pyscipopt.scip.Event) SCIPProgressSnapshot#
dual_bound: float#

SCIP dual bound reported at the callback.

event: str#

SCIP event name, currently "BESTSOLFOUND" or "DUALBOUNDIMPROVED".

gap: float#

SCIP relative gap reported at the callback.

incumbent_objective: float | None#

Objective value of SCIP's current best solution.

This is None when PySCIPOpt cannot read the incumbent objective at that callback.

lp_iteration_count: int#

LP iterations at the callback.

node_count: int#

Processed branch-and-bound nodes at the callback.

primal_bound: float#

SCIP primal bound reported at the callback.

solution_count: int#

Number of solutions stored by SCIP at the callback.

solving_time_sec: float#

SCIP solving time when the callback ran.

total_node_count: int#

Total processed nodes including restarts at the callback.

class SCIPTerminationReport#

SCIP-side termination summary recorded after model.optimize().

The PySCIPOpt adapter records this report before decoding the optimized SCIP model back into an OMMX solution. It is therefore available even when decoding raises an adapter exception such as infeasible or unbounded detection.

classmethod from_model(model: pyscipopt.Model) SCIPTerminationReport#
applied_cut_count: int#

Number of cuts applied by SCIP.

best_solution_count: int#

Number of new incumbent solutions SCIP found.

cut_count: int#

Number of cuts available in SCIP's cut pool.

dual_bound: float#

SCIP dual bound at termination.

gap: float#

SCIP relative gap reported by getGap().

lp_iteration_count: int#

Total LP iterations.

lp_solve_count: int#

Number of solved LPs.

max_depth: int#

Maximum branch-and-bound depth.

SCIP may report -1 when no branching occurred.

node_count: int#

Number of branch-and-bound nodes processed by SCIP.

objective_value: float | None#

Incumbent objective value, or None when SCIP has no solution.

presolving_time_sec: float#

SCIP presolving time in seconds.

primal_bound: float#

SCIP primal bound at termination.

primal_dual_integral: float#

SCIP primal-dual integral at termination.

pyscipopt_version: str | None#

PySCIPOpt package version, if available.

reading_time_sec: float#

SCIP reading time in seconds.

scip_version: str#

SCIP version used through PySCIPOpt.

solution_count: int#

Number of solutions stored by SCIP at termination.

solution_found_count: int#

Number of solutions SCIP found during the solve.

solving_time_sec: float#

SCIP solving time in seconds.

status: str#

SCIP termination status, such as "optimal", "infeasible", or "unbounded".

total_node_count: int#

Total processed nodes including restarts.