Loading picos/modeling/problem.py +3 −3 Original line number Diff line number Diff line Loading @@ -1674,7 +1674,7 @@ class Problem(Valuable): "default_penalty": classmethod(lambda cls: 0), "test_availability": classmethod(lambda cls: None), "names": classmethod(lambda cls: ("Dummy Solver", "DummySolver", "Dummy Solver accepting {}".format(specification))), "Dummy Solver accepting {}".format(specification), None)), "is_free": classmethod(lambda cls: True), # Additional class methods needed for an ad-hoc solver. Loading Loading @@ -1771,9 +1771,9 @@ class Problem(Valuable): if not self._strategy: if verbose: if options.ad_hoc_solver: solverName = options.ad_hoc_solver.names()[1] solverName = options.ad_hoc_solver.get_via_name() elif options.solver: solverName = get_solver(options.solver).names()[1] solverName = get_solver(options.solver).get_via_name() else: solverName = None Loading picos/modeling/strategy.py +9 −7 Original line number Diff line number Diff line Loading @@ -185,24 +185,26 @@ class Strategy: else: raise RuntimeError( "Selected solver {} is not available on the system." .format(solver.names()[1])) .format(solver.get_via_name())) else: for solver_name in available_solvers(): solver = get_solver(solver_name) solvers.append(solver) assert solvers, "Not even CVXOPT seems to be available." if not solvers: raise RuntimeError("Not even CVXOPT seems to be available. " "Did you blacklist all available solvers?") if len(solvers) == 1 and solvers[0].supports(footprint): if options.verbosity >= 2: print("{} supports the problem directly.".format( solvers[0].names()[1])) solvers[0].get_via_name())) return cls(problem, solvers[0]) if options.verbosity >= 2: print("Selected solvers:\n {}".format(", ".join( solver.names()[1] for solver in solvers))) solver.get_via_name() for solver in solvers))) paths = OrderedDict({footprint: tuple()}) new_footprints = [footprint] Loading Loading @@ -287,7 +289,7 @@ class Strategy: if options.verbosity >= 2: print(" {} supports ({}) at cost {:.2f} + {:.2f} = " "{:.2f}.".format(solver.names()[1], num, cost, "{:.2f}.".format(solver.get_via_name(), num, cost, penalty, total_cost)) strategies.append(cls(problem, solver, *paths[footprint])) Loading Loading @@ -318,9 +320,9 @@ class Strategy: for reasons, unsupported_solvers in solver_reasons.items(): if len(unsupported_solvers) == 1: synopsis += "\n {} does not support:".format( unsupported_solvers.pop().names()[1]) unsupported_solvers.pop().get_via_name()) else: names = tuple(s.names()[1] for s in unsupported_solvers) names = tuple(s.get_via_name() for s in unsupported_solvers) synopsis += "\n {} and {} do not support:".format( ", ".join(names[:-1]), names[-1]) Loading picos/solvers/solver.py +65 −28 Original line number Diff line number Diff line Loading @@ -25,7 +25,7 @@ import time from abc import ABC, abstractmethod from contextlib import contextmanager from .. import settings from .. import glyphs, settings from ..apidoc import api_end, api_start from ..formatting import solver_box from ..modeling.solution import Solution Loading Loading @@ -173,10 +173,19 @@ class Solver(ABC): """ pass # TODO: Consider separate abstract methods for better interface validation. @classmethod @abstractmethod def names(cls): """Returns a triple ``(name, display_name, long_display_name)``.""" """Return a name sequence ``(internal, short, long, interface)``. 1. The internal name is a lowercase keyword used for solver selection. 2. The short name is a properly capitalized official solver shortand. 3. The long name is the full official name of the solver. 4. The interface name is a properly capitalized short name of the Python interface used, or :obj:`None` if the solver is Python-native or includes a unique Python interface. """ pass @classmethod Loading @@ -190,6 +199,16 @@ class Solver(ABC): """ pass # -------------------------------------------------------------------------- # Non-abstract class methods. # -------------------------------------------------------------------------- @classmethod def get_via_name(cls, interface_in_parenthesis=False): """Return the name of the solver with the Python interface used.""" _, display, _, interface = cls.names() return "{} via {}".format(display, interface) if interface else display # -------------------------------------------------------------------------- # __init__ and instance properties. # -------------------------------------------------------------------------- Loading Loading @@ -248,15 +267,25 @@ class Solver(ABC): return self.names()[0] @property def display_name(self): """Short display name of the solver.""" def short_name(self): """Short name of the solver.""" return self.names()[1] @property def long_display_name(self): """Long display name of the solver.""" def long_name(self): """Long name of the solver.""" return self.names()[2] @property def interface_name(self): """Short name of the Python interface used, or :obj:`None`.""" return self.names()[3] @property def via_name(self): """The short names of the solver and Python interface used.""" return self.get_via_name() # -------------------------------------------------------------------------- # Abstract instance methods. # -------------------------------------------------------------------------- Loading Loading @@ -349,21 +378,22 @@ class Solver(ABC): @classmethod def available(cls, verbose=False): """Whether the solver is properly installed on the system.""" name, display_name, _ = cls.names() name = cls.names()[0] via_name = cls.get_via_name() if name in settings.SOLVER_BLACKLIST: if verbose: print("The solver {} is blacklisted.".format(display_name)) print("The solver {} is blacklisted.".format(via_name)) return False if settings.SOLVER_WHITELIST and name not in settings.SOLVER_WHITELIST: if verbose: print("The solver {} is not whitelisted.".format(display_name)) print("The solver {} is not whitelisted.".format(via_name)) return False if not settings.NONFREE_SOLVERS and not cls.is_free(): if verbose: print("The solver {} is non-free.".format(display_name)) print("The solver {} is non-free.".format(via_name)) return False try: Loading @@ -389,8 +419,9 @@ class Solver(ABC): # Non-abstract instance methods (except for __init__ and properties). # ------------------------------------------------------------------------- def __str__(self): return "# wrapper around a " + self.display_name + " problem instance #" def __repr__(self): return glyphs.repr1( "Problem interface between PICOS and {}".format(self.via_name)) def reset(self): """A shorthand for :meth:`reset_problem`. Loading Loading @@ -459,7 +490,7 @@ class Solver(ABC): message = customMessage else: message = "{} does not support the '{}' option." \ .format(self.display_name, option) .format(self.via_name, option) if self.ext.options.strict_options: raise UnsupportedOptionError(message) Loading @@ -477,9 +508,9 @@ class Solver(ABC): "The PICOS option '{}' does not exist.".format(picos_option) raise OptionValueError( "Either the {} option '{}' set via '{}' does not exist or the given" " value '{}' is not valid for that option.".format( self.display_name, key, picos_option, value)) from error "Either the option '{}' set via '{}' does not exist for {} or the " "given value '{}' is not valid for that option.".format( key, picos_option, self.via_name, value)) from error def _handle_bad_solver_specific_option_key(self, key, error=None): picos_option = "{}_params".format(self.name) Loading @@ -487,8 +518,8 @@ class Solver(ABC): "The PICOS option '{}' does not exist.".format(picos_option) raise OptionValueError( "The {} option '{}' set via '{}' does not exist.".format( self.display_name, key, picos_option)) from error "The option '{}' set via '{}' does not exist for {}.".format( key, picos_option, self.via_name)) from error def _handle_bad_solver_specific_option_value(self, key, value, error=None): picos_option = "{}_params".format(self.name) Loading @@ -496,8 +527,8 @@ class Solver(ABC): "The PICOS option '{}' does not exist.".format(picos_option) raise OptionValueError( "Invalid value '{}' for {} option '{}' set via '{}'.".format( value, self.display_name, key, picos_option)) from error "The value '{}' for option '{}' set via '{}' is not valid for {}." .format(value, key, picos_option, self.via_name)) from error def _handle_continuous_nonconvex_error(self, error): """Raise a descriptive :exc:`ArithmeticError`.""" Loading @@ -507,7 +538,7 @@ class Solver(ABC): "quadratic form is numerically on the verge of being semidefinite, " "with PICOS' and {0}'s judgement differing. You could try a slight " "perturbation of your data such that all quadratic forms become " "definite.".format(self.display_name)) from error "definite.".format(self.short_name)) from error def _load_problem(self): """(Re-)import or update the solver's problem state for solving.""" Loading @@ -515,28 +546,28 @@ class Solver(ABC): footprint = self.ext.footprint assert self.supports(footprint), \ "PICOS gave {} an unsupported problem to load: {}".format( self.display_name, footprint) self.via_name, footprint) # Import or update the problem. if self.int is None: self._verbose("Building a {} problem instance." .format(self.display_name)) .format(self.short_name)) self._import_problem() else: try: self._verbose("Updating the {} problem instance." .format(self.display_name)) .format(self.short_name)) self._update_problem() except (NotImplementedError, ProblemUpdateError) as error: if type(error) is NotImplementedError: reason = "Not supported with {}.".format(self.display_name) reason = "Not supported with {}.".format(self.via_name) else: reason = str(error) if reason == "": reason = "Unknown reason." self._verbose("Update failed: {}".format(reason)) self._verbose("Rebuilding the {} problem instance." .format(self.display_name)) .format(self.short_name)) self.reset_problem() self._import_problem() Loading Loading @@ -690,8 +721,14 @@ class Solver(ABC): @contextmanager def _header(self, subsolver=None): """Print both a header and a footer.""" with solver_box(self.long_display_name, self.display_name, subsolver, self._verbose()): if subsolver: s = subsolver elif self.interface_name: s = self.interface_name else: s = None with solver_box(self.long_name, self.short_name, s, self._verbose()): yield @property Loading picos/solvers/solver_cplex.py +1 −1 Original line number Diff line number Diff line Loading @@ -161,7 +161,7 @@ class CPLEXSolver(Solver): @classmethod def names(cls): """Implement :meth:`~.solver.Solver.names`.""" return "cplex", "CPLEX", "IBM ILOG CPLEX Optimization Studio" return "cplex", "CPLEX", "IBM ILOG CPLEX Optimization Studio", None @classmethod def is_free(cls): Loading picos/solvers/solver_cvxopt.py +1 −1 Original line number Diff line number Diff line Loading @@ -85,7 +85,7 @@ class CVXOPTSolver(Solver): @classmethod def names(cls): """Implement :meth:`~.solver.Solver.names`.""" return "cvxopt", "CVXOPT", "Python Convex Optimization Solver" return "cvxopt", "CVXOPT", "Python Convex Optimization Solver", None @classmethod def is_free(cls): Loading Loading
picos/modeling/problem.py +3 −3 Original line number Diff line number Diff line Loading @@ -1674,7 +1674,7 @@ class Problem(Valuable): "default_penalty": classmethod(lambda cls: 0), "test_availability": classmethod(lambda cls: None), "names": classmethod(lambda cls: ("Dummy Solver", "DummySolver", "Dummy Solver accepting {}".format(specification))), "Dummy Solver accepting {}".format(specification), None)), "is_free": classmethod(lambda cls: True), # Additional class methods needed for an ad-hoc solver. Loading Loading @@ -1771,9 +1771,9 @@ class Problem(Valuable): if not self._strategy: if verbose: if options.ad_hoc_solver: solverName = options.ad_hoc_solver.names()[1] solverName = options.ad_hoc_solver.get_via_name() elif options.solver: solverName = get_solver(options.solver).names()[1] solverName = get_solver(options.solver).get_via_name() else: solverName = None Loading
picos/modeling/strategy.py +9 −7 Original line number Diff line number Diff line Loading @@ -185,24 +185,26 @@ class Strategy: else: raise RuntimeError( "Selected solver {} is not available on the system." .format(solver.names()[1])) .format(solver.get_via_name())) else: for solver_name in available_solvers(): solver = get_solver(solver_name) solvers.append(solver) assert solvers, "Not even CVXOPT seems to be available." if not solvers: raise RuntimeError("Not even CVXOPT seems to be available. " "Did you blacklist all available solvers?") if len(solvers) == 1 and solvers[0].supports(footprint): if options.verbosity >= 2: print("{} supports the problem directly.".format( solvers[0].names()[1])) solvers[0].get_via_name())) return cls(problem, solvers[0]) if options.verbosity >= 2: print("Selected solvers:\n {}".format(", ".join( solver.names()[1] for solver in solvers))) solver.get_via_name() for solver in solvers))) paths = OrderedDict({footprint: tuple()}) new_footprints = [footprint] Loading Loading @@ -287,7 +289,7 @@ class Strategy: if options.verbosity >= 2: print(" {} supports ({}) at cost {:.2f} + {:.2f} = " "{:.2f}.".format(solver.names()[1], num, cost, "{:.2f}.".format(solver.get_via_name(), num, cost, penalty, total_cost)) strategies.append(cls(problem, solver, *paths[footprint])) Loading Loading @@ -318,9 +320,9 @@ class Strategy: for reasons, unsupported_solvers in solver_reasons.items(): if len(unsupported_solvers) == 1: synopsis += "\n {} does not support:".format( unsupported_solvers.pop().names()[1]) unsupported_solvers.pop().get_via_name()) else: names = tuple(s.names()[1] for s in unsupported_solvers) names = tuple(s.get_via_name() for s in unsupported_solvers) synopsis += "\n {} and {} do not support:".format( ", ".join(names[:-1]), names[-1]) Loading
picos/solvers/solver.py +65 −28 Original line number Diff line number Diff line Loading @@ -25,7 +25,7 @@ import time from abc import ABC, abstractmethod from contextlib import contextmanager from .. import settings from .. import glyphs, settings from ..apidoc import api_end, api_start from ..formatting import solver_box from ..modeling.solution import Solution Loading Loading @@ -173,10 +173,19 @@ class Solver(ABC): """ pass # TODO: Consider separate abstract methods for better interface validation. @classmethod @abstractmethod def names(cls): """Returns a triple ``(name, display_name, long_display_name)``.""" """Return a name sequence ``(internal, short, long, interface)``. 1. The internal name is a lowercase keyword used for solver selection. 2. The short name is a properly capitalized official solver shortand. 3. The long name is the full official name of the solver. 4. The interface name is a properly capitalized short name of the Python interface used, or :obj:`None` if the solver is Python-native or includes a unique Python interface. """ pass @classmethod Loading @@ -190,6 +199,16 @@ class Solver(ABC): """ pass # -------------------------------------------------------------------------- # Non-abstract class methods. # -------------------------------------------------------------------------- @classmethod def get_via_name(cls, interface_in_parenthesis=False): """Return the name of the solver with the Python interface used.""" _, display, _, interface = cls.names() return "{} via {}".format(display, interface) if interface else display # -------------------------------------------------------------------------- # __init__ and instance properties. # -------------------------------------------------------------------------- Loading Loading @@ -248,15 +267,25 @@ class Solver(ABC): return self.names()[0] @property def display_name(self): """Short display name of the solver.""" def short_name(self): """Short name of the solver.""" return self.names()[1] @property def long_display_name(self): """Long display name of the solver.""" def long_name(self): """Long name of the solver.""" return self.names()[2] @property def interface_name(self): """Short name of the Python interface used, or :obj:`None`.""" return self.names()[3] @property def via_name(self): """The short names of the solver and Python interface used.""" return self.get_via_name() # -------------------------------------------------------------------------- # Abstract instance methods. # -------------------------------------------------------------------------- Loading Loading @@ -349,21 +378,22 @@ class Solver(ABC): @classmethod def available(cls, verbose=False): """Whether the solver is properly installed on the system.""" name, display_name, _ = cls.names() name = cls.names()[0] via_name = cls.get_via_name() if name in settings.SOLVER_BLACKLIST: if verbose: print("The solver {} is blacklisted.".format(display_name)) print("The solver {} is blacklisted.".format(via_name)) return False if settings.SOLVER_WHITELIST and name not in settings.SOLVER_WHITELIST: if verbose: print("The solver {} is not whitelisted.".format(display_name)) print("The solver {} is not whitelisted.".format(via_name)) return False if not settings.NONFREE_SOLVERS and not cls.is_free(): if verbose: print("The solver {} is non-free.".format(display_name)) print("The solver {} is non-free.".format(via_name)) return False try: Loading @@ -389,8 +419,9 @@ class Solver(ABC): # Non-abstract instance methods (except for __init__ and properties). # ------------------------------------------------------------------------- def __str__(self): return "# wrapper around a " + self.display_name + " problem instance #" def __repr__(self): return glyphs.repr1( "Problem interface between PICOS and {}".format(self.via_name)) def reset(self): """A shorthand for :meth:`reset_problem`. Loading Loading @@ -459,7 +490,7 @@ class Solver(ABC): message = customMessage else: message = "{} does not support the '{}' option." \ .format(self.display_name, option) .format(self.via_name, option) if self.ext.options.strict_options: raise UnsupportedOptionError(message) Loading @@ -477,9 +508,9 @@ class Solver(ABC): "The PICOS option '{}' does not exist.".format(picos_option) raise OptionValueError( "Either the {} option '{}' set via '{}' does not exist or the given" " value '{}' is not valid for that option.".format( self.display_name, key, picos_option, value)) from error "Either the option '{}' set via '{}' does not exist for {} or the " "given value '{}' is not valid for that option.".format( key, picos_option, self.via_name, value)) from error def _handle_bad_solver_specific_option_key(self, key, error=None): picos_option = "{}_params".format(self.name) Loading @@ -487,8 +518,8 @@ class Solver(ABC): "The PICOS option '{}' does not exist.".format(picos_option) raise OptionValueError( "The {} option '{}' set via '{}' does not exist.".format( self.display_name, key, picos_option)) from error "The option '{}' set via '{}' does not exist for {}.".format( key, picos_option, self.via_name)) from error def _handle_bad_solver_specific_option_value(self, key, value, error=None): picos_option = "{}_params".format(self.name) Loading @@ -496,8 +527,8 @@ class Solver(ABC): "The PICOS option '{}' does not exist.".format(picos_option) raise OptionValueError( "Invalid value '{}' for {} option '{}' set via '{}'.".format( value, self.display_name, key, picos_option)) from error "The value '{}' for option '{}' set via '{}' is not valid for {}." .format(value, key, picos_option, self.via_name)) from error def _handle_continuous_nonconvex_error(self, error): """Raise a descriptive :exc:`ArithmeticError`.""" Loading @@ -507,7 +538,7 @@ class Solver(ABC): "quadratic form is numerically on the verge of being semidefinite, " "with PICOS' and {0}'s judgement differing. You could try a slight " "perturbation of your data such that all quadratic forms become " "definite.".format(self.display_name)) from error "definite.".format(self.short_name)) from error def _load_problem(self): """(Re-)import or update the solver's problem state for solving.""" Loading @@ -515,28 +546,28 @@ class Solver(ABC): footprint = self.ext.footprint assert self.supports(footprint), \ "PICOS gave {} an unsupported problem to load: {}".format( self.display_name, footprint) self.via_name, footprint) # Import or update the problem. if self.int is None: self._verbose("Building a {} problem instance." .format(self.display_name)) .format(self.short_name)) self._import_problem() else: try: self._verbose("Updating the {} problem instance." .format(self.display_name)) .format(self.short_name)) self._update_problem() except (NotImplementedError, ProblemUpdateError) as error: if type(error) is NotImplementedError: reason = "Not supported with {}.".format(self.display_name) reason = "Not supported with {}.".format(self.via_name) else: reason = str(error) if reason == "": reason = "Unknown reason." self._verbose("Update failed: {}".format(reason)) self._verbose("Rebuilding the {} problem instance." .format(self.display_name)) .format(self.short_name)) self.reset_problem() self._import_problem() Loading Loading @@ -690,8 +721,14 @@ class Solver(ABC): @contextmanager def _header(self, subsolver=None): """Print both a header and a footer.""" with solver_box(self.long_display_name, self.display_name, subsolver, self._verbose()): if subsolver: s = subsolver elif self.interface_name: s = self.interface_name else: s = None with solver_box(self.long_name, self.short_name, s, self._verbose()): yield @property Loading
picos/solvers/solver_cplex.py +1 −1 Original line number Diff line number Diff line Loading @@ -161,7 +161,7 @@ class CPLEXSolver(Solver): @classmethod def names(cls): """Implement :meth:`~.solver.Solver.names`.""" return "cplex", "CPLEX", "IBM ILOG CPLEX Optimization Studio" return "cplex", "CPLEX", "IBM ILOG CPLEX Optimization Studio", None @classmethod def is_free(cls): Loading
picos/solvers/solver_cvxopt.py +1 −1 Original line number Diff line number Diff line Loading @@ -85,7 +85,7 @@ class CVXOPTSolver(Solver): @classmethod def names(cls): """Implement :meth:`~.solver.Solver.names`.""" return "cvxopt", "CVXOPT", "Python Convex Optimization Solver" return "cvxopt", "CVXOPT", "Python Convex Optimization Solver", None @classmethod def is_free(cls): Loading