Source code for sdpb_python.handle
"""A solver handle: keep SDPB's state alive between runs."""
from __future__ import annotations
import os
from typing import Any, Sequence
import mpmath
from . import numbers as _num
from . import _mpi
from .errors import SDPBError, wrap_cpp_error
from .options import SolverOptions, effective_precision, resolve
from .solution import Solution, TerminateReason
[docs]
class SolverInterrupted(KeyboardInterrupt):
"""Ctrl-C during a run: SDPB stopped cleanly at the end of an iteration.
``solution`` holds the state at that point; the :class:`Solver` can be run
again to continue.
"""
def __init__(self, solution: Solution):
super().__init__("SDPB run interrupted")
self.solution = solution
def _ext():
from . import _sdpb
return _sdpb
[docs]
class Solver:
"""Owns SDPB's `Block_Info`, `SDP` and `SDP_Solver` for one problem.
Created by :meth:`PMP.solver` or :meth:`LMI.solver`. :meth:`run` continues
the interior-point iteration from the current ``x, X, y, Y`` and may be
called repeatedly, e.g. with tighter thresholds. Use as a context manager
or call :meth:`close` to release the C++ objects.
"""
def __init__(self, kind: str, spec: dict, options: SolverOptions):
self._options = options
self._precision = effective_precision(options.precision)
_mpi.check_single_process(_ext().mpi_size())
try:
self._impl = _ext().Solver(kind, spec, options._to_dict())
except Exception as exc:
raise wrap_cpp_error(exc) from None
self.blocks_dims = self._impl.dims()
self.blocks_num_points = self._impl.num_points()
# status of the last run; "not converged" until run() is called
self._last_status = TerminateReason.MAX_ITERATIONS_EXCEEDED
# -- lifecycle
[docs]
def close(self) -> None:
if getattr(self, "_impl", None) is not None:
self._impl.close()
@property
def closed(self) -> bool:
return self._impl.closed
def __enter__(self) -> "Solver":
return self
def __exit__(self, *exc) -> None:
self.close()
def __del__(self):
try:
self.close()
except Exception:
pass
# -- running
@property
def options(self) -> SolverOptions:
return self._options
@property
def precision(self) -> int:
return self._precision
@property
def num_variables(self) -> int:
return self._impl.num_variables()
@property
def total_iterations(self) -> int:
"""Iterations completed over all calls of :meth:`run`."""
return self._impl.total_iterations()
[docs]
def run(self, options: SolverOptions | None = None, **overrides: Any) -> Solution:
"""Continue iterating. Raises :class:`SolverInterrupted` on Ctrl-C."""
opts = resolve(self._options if options is None else options, overrides)
try:
d = self._impl.run(opts._to_dict())
except Exception as exc:
raise wrap_cpp_error(exc) from None
solution = Solution._from_dict(d)
self._last_status = solution.status
if solution.status is TerminateReason.SIGTERM_RECEIVED and _ext().last_run_interrupted():
raise SolverInterrupted(solution)
return solution
[docs]
def state(self, want: Sequence[str] = ("y", "z", "x", "X", "Y")) -> Solution:
"""The current iterate without running (objectives are those of the last run)."""
opts = self._options.replace(want=tuple(want))
try:
d = self._impl.state(opts._to_dict())
except Exception as exc:
raise wrap_cpp_error(exc) from None
d["terminate_reason"] = self._last_status.value
return Solution._from_dict(d)
# -- warm start
[docs]
def warm_start(self, y: Sequence[Any] | None = None, X: Sequence[Any] | None = None,
Y: Sequence[Any] | None = None) -> "Solver":
"""Overwrite the iterate before :meth:`run`.
``y`` has length N; ``X`` / ``Y`` are per-block pairs ``(even, odd)`` of
matrices in the shapes SDPB uses (see ``Solution.X``).
"""
bits = self._precision
try:
if y is not None:
self._impl.set_y(_num.strs(y, bits))
if X is not None:
self._impl.set_X(_flatten_blocks(X, bits))
if Y is not None:
self._impl.set_Y(_flatten_blocks(Y, bits))
except Exception as exc:
raise wrap_cpp_error(exc) from None
return self
[docs]
def save_checkpoint(self, directory: str | os.PathLike) -> None:
"""Write SDPB's binary checkpoint (``checkpoint_<g>_<rank>``, ``checkpoint.json``)."""
try:
self._impl.save_checkpoint(os.fspath(directory))
except Exception as exc:
raise wrap_cpp_error(exc) from None
def _flatten_blocks(blocks: Sequence[Any], bits: int) -> list[list[list[str]]]:
out = []
for pair in blocks:
even, odd = pair
out.append(_num.matrix_strs(even, bits))
out.append(_num.matrix_strs(odd, bits))
return out