Commit 2f7ba459 authored by Maximilian Stahlberg's avatar Maximilian Stahlberg
Browse files

Merge branch 'master' into performance

parents 7ca7932b f0c7dd91
Loading
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -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.
@@ -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

+9 −7
Original line number Diff line number Diff line
@@ -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]
@@ -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]))
@@ -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])

+65 −28
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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.
    # --------------------------------------------------------------------------
@@ -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.
    # --------------------------------------------------------------------------
@@ -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:
@@ -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`.
@@ -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)
@@ -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)
@@ -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)
@@ -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`."""
@@ -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."""
@@ -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()

@@ -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
+1 −1
Original line number Diff line number Diff line
@@ -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):
+1 −1
Original line number Diff line number Diff line
@@ -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