Source code for cpmpy.solvers.utils

#!/usr/bin/env python
#-*- coding:utf-8 -*-
##
## utils.py
##
"""
    Utilities for handling solvers

    =================
    List of functions
    =================

    .. autosummary::
        :nosignatures:

        param_combinations
"""

import warnings # for deprecation warning

from .gurobi import CPM_gurobi
from .ortools import CPM_ortools
from .minizinc import CPM_minizinc
from .pysat import CPM_pysat
from .z3 import CPM_z3
from .gcs import CPM_gcs
from .pysdd import CPM_pysdd
from .exact import CPM_exact
from .choco import CPM_choco
from .cpo   import CPM_cpo

[docs]def param_combinations(all_params, remaining_keys=None, cur_params=None): """ Recursively yield all combinations of param values For example usage, see `examples/advanced/hyperparameter_search.py` https://github.com/CPMpy/cpmpy/blob/master/examples/advanced/hyperparameter_search.py - all_params is a dict of `{key: list}` items, e.g.: ``{'val': [1,2], 'opt': [True,False]}`` - output is an generator over all `{key:value}` combinations of the keys and values. For the example above: ``generator([{'val':1,'opt':True},{'val':1,'opt':False},{'val':2,'opt':True},{'val':2,'opt':False}])`` """ if remaining_keys is None or cur_params is None: # init remaining_keys = list(all_params.keys()) cur_params = dict() cur_key = remaining_keys[0] for cur_value in all_params[cur_key]: cur_params[cur_key] = cur_value if len(remaining_keys) == 1: # terminal, return copy yield dict(cur_params) else: # recursive call yield from param_combinations(all_params, remaining_keys=remaining_keys[1:], cur_params=cur_params)
[docs]class SolverLookup():
[docs] @classmethod def base_solvers(cls): """ Return ordered list of (name, class) of base CPMpy solvers First one is default """ return [("ortools", CPM_ortools), ("z3", CPM_z3), ("minizinc", CPM_minizinc), ("gcs", CPM_gcs), ("gurobi", CPM_gurobi), ("pysat", CPM_pysat), ("pysdd", CPM_pysdd), ("exact", CPM_exact), ("choco", CPM_choco), ("cpo", CPM_cpo), ]
[docs] @classmethod def solvernames(cls): names = [] for (basename, CPM_slv) in cls.base_solvers(): if CPM_slv.supported(): names.append(basename) if hasattr(CPM_slv, "solvernames"): subnames = CPM_slv.solvernames() for subn in subnames: names.append(basename+":"+subn) return names
[docs] @classmethod def get(cls, name=None, model=None): """ get a specific solver (by name), with 'model' passed to its constructor This is the preferred way to initialise a solver from its name """ solver_cls = cls.lookup(name=name) # check for a 'solver:subsolver' name subname = None if name is not None and ':' in name: _,subname = name.split(':',maxsplit=1) return solver_cls(model, subsolver=subname)
[docs] @classmethod def lookup(cls, name=None): """ lookup a solver _class_ by its name warning: returns a 'class', not an object! see get() for normal uses """ if name is None: # first solver class return cls.base_solvers()[0][1] # split name if relevant solvername = name subname = None if ':' in solvername: solvername,_ = solvername.split(':',maxsplit=1) for (basename, CPM_slv) in cls.base_solvers(): if basename == solvername: # found the right solver return CPM_slv raise ValueError(f"Unknown solver '{name}', chose from {cls.solvernames()}")
[docs] @classmethod def status(cls): """ Returns the status of all solvers supported by CPMpy as a list of tuples. Each tuple consists of: - solver name: <base_solver> or <base_solver>:<subsolver> - install status - version of solver's Python library """ result = [] for (basename, CPM_slv) in cls.base_solvers(): installed = CPM_slv.supported() version = CPM_slv.version() if installed and hasattr(CPM_slv, 'version') else None # Collect main solver status result.append((basename, installed, version)) # TODO: Can add subsolver status report once pull request #623 has been resolved # # Handle subsolvers if applicable # if installed and hasattr(CPM_slv, 'solvernames'): # subnames = CPM_slv.solvernames() # installed_subnames = CPM_slv.solvernames(installed=True) # for subn in subnames: # is_installed = subn in installed_subnames # subsolver_status = (basename + ":" + subn, is_installed, None) # No version information for subsolvers in this example # result.append(subsolver_status) # Append subsolver status return result
[docs] @classmethod def print_status(cls): """ Prints a tabulated status report of the different solvers, i.e. whether they are installed on the system and if so which version. """ # Get the status information using the status() method solver_status = cls.status() # Print the header print(f"{'Solver':<25} {'Installed':<10} {'Version':<15}") print("-" * 50) # Iterate over the solver status for basename, installed, version in solver_status: # TODO: Can add subsolver status report once pull request #623 has been resolved # # If this is a subsolver (indicated by a ':' in the name), indent the output # if ':' in basename: # print(f" ↪ {basename.split(":")[-1]:<22} {'Yes' if installed else 'No':<10} {' ':<15}") # Subsolver with indentation # else: # For main solvers, show version if available version = version if version else "Not found" if installed else "-" print(f"{basename:<25} {'Yes' if installed else 'No':<10} {version:<15}")
# using `builtin_solvers` is DEPRECATED, use `SolverLookup` object instead # Order matters! first is default, then tries second, etc... builtin_solvers = [CPM_ortools, CPM_gurobi, CPM_minizinc, CPM_pysat, CPM_exact, CPM_choco]
[docs]def get_supported_solvers(): """ Returns a list of solvers supported on this machine. .. deprecated:: 0.9.4 Please use :class:`SolverLookup` object instead. :return: a list of SolverInterface sub-classes :list[SolverInterface]: """ warnings.warn("Deprecated, use Model.solvernames() instead, will be removed in stable version", DeprecationWarning) return [sv for sv in builtin_solvers if sv.supported()]