Source code for pybnb.solver_results

"""
Branch-and-bound solver results object.

Copyright by Gabriel A. Hackebeil (gabe.hackebeil@gmail.com).
"""

# recognized by the pytest-doctestplus plugin,
# not the standard doctest
__doctest_requires__ = {"SolverResults.write": ["yaml"]}

from typing import Union, IO, Optional
import sys
import base64

from pybnb.common import SolutionStatus, TerminationCondition
from pybnb.misc import time_format, as_stream
from pybnb.node import dumps, Node

import six


[docs]class SolverResults(object): """Stores the results of a branch-and-bound solve. Attributes ---------- solution_status : string The solution status will be set to one of the strings documented by the :class:`SolutionStatus <pybnb.common.SolutionStatus>` enum. termination_condition : string The solve termination condition, as determined by the dispatcher, will be set to one of the strings documented by the :class:`TerminationCondition <pybnb.common.TerminationCondition>` enum. objective : float The best objective found. bound : float The global optimality bound. absolute_gap : float or None The absolute gap between the objective and bound. This will only be set when the solution status sf "optimal" or "feasible"; otherwise, it will be None. relative_gap : float or None The relative gap between the objective and bound. This will only be set when the solution status sf "optimal" or "feasible"; otherwise, it will be None. nodes : int The total number of nodes processes by all workers. wall_time : float The process-local wall time (seconds). This is the only value on the results object that varies between processes. best_node : :class:`Node <pybnb.node.Node>` The node with the best objective obtained during the solve. Note that if the best_objective solver option was used, the best_node on the results object may have an objective that is worse than the objective stored on the results (or may be None). """ def __init__(self): # type: () -> None self.solution_status = None # type: Optional[SolutionStatus] self.termination_condition = None # type: Optional[TerminationCondition] self.objective = None # type: Optional[Union[int, float]] self.bound = None # type: Optional[Union[int, float]] self.absolute_gap = None # type: Optional[Union[int, float]] self.relative_gap = None # type: Optional[Union[int, float]] self.nodes = None # type: Optional[int] self.wall_time = None # type: Optional[float] self.best_node = None # type: Optional[Node]
[docs] def pprint(self, stream=sys.stdout): # type: (Union[IO, str]) -> None """Prints a nicely formatted representation of the results. Parameters ---------- stream : file-like object or string, optional A file-like object or a filename where results should be written to. (default: ``sys.stdout``) """ with as_stream(stream) as out: out.write("solver results:\n") self.write(out, prefix=" - ", pretty=True)
[docs] def write(self, stream, prefix="", pretty=False): # type: (Union[IO, str], str, bool) -> None """Writes results in YAML format to a stream or file. Changing the parameter values from their defaults may result in the output becoming non-compatible with the YAML format. Parameters ---------- stream : file-like object or string A file-like object or a filename where results should be written to. prefix : string, optional A string to use as a prefix for each line that is written. (default: '') pretty : bool, optional Indicates whether or not certain recognized attributes should be formatted for more human-readable output. (default: False) Example ------- >>> import six >>> import pybnb >>> results = pybnb.SolverResults() >>> results.best_node = pybnb.Node() >>> results.best_node.objective = 123 >>> out = six.StringIO() >>> # the best_node is serialized >>> results.write(out) >>> del results >>> import yaml >>> results_dict = yaml.safe_load(out.getvalue()) >>> # de-serialize the best_node >>> best_node = pybnb.node.loads(results_dict['best_node']) >>> assert best_node.objective == 123 """ with as_stream(stream) as out: attrs = vars(self) names = sorted(list(attrs.keys())) first = ( "solution_status", "termination_condition", "objective", "bound", "absolute_gap", "relative_gap", "nodes", "wall_time", "best_node", ) for cnt, name in enumerate(first): if not hasattr(self, name): continue names.remove(name) val = getattr(self, name) if val is not None: if name in ("solution_status", "termination_condition"): if type(val) in (SolutionStatus, TerminationCondition): val = val.value elif pretty: if name == "wall_time": val = time_format(val, digits=2) elif name in ( "objective", "bound", "absolute_gap", "relative_gap", ): val = "%.7g" % (val) elif name == "best_node": assert isinstance(val, Node) if val.objective is not None: val = "Node(objective=%.7g)" % (val.objective) else: val = "Node(objective=None)" else: if name == "best_node": val = dumps(val) if not six.PY2: val = base64.encodebytes(val).decode("ascii") else: val = base64.encodestring(val).decode("ascii") val = "\n ".join(val.splitlines()) val = "!!binary |\n %s" % (val) else: val_ = "%r" % (val) if type(val) is float: if val_ == "inf": val_ = ".inf" elif val_ == "-inf": val_ = "-.inf" elif val_ == "nan": val_ = ".nan" val = val_ del val_ if pretty or (val is not None): out.write(prefix + "%s: %s\n" % (name, val)) else: assert val is None out.write(prefix + "%s: null\n" % (name)) for name in names: val = getattr(self, name) if pretty: out.write(prefix + "%s: %r\n" % (name, val)) else: if val is None: out.write(prefix + "%s: null\n" % (name)) else: val_ = "%r" % (val) if type(val) is float: if val_ == "inf": val_ = ".inf" elif val_ == "-inf": val_ = "-.inf" elif val_ == "nan": val_ = ".nan" val = val_ del val_ out.write(prefix + "%s: %s\n" % (name, val))
def __str__(self): # type: () -> str """Represents the results as a string.""" tmp = six.StringIO() self.pprint(stream=tmp) return tmp.getvalue()