Skip to content
Open
57 changes: 57 additions & 0 deletions pyomo/contrib/solver/common/availability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2025
# National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
# rights in this software.
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________

from pyomo.common.enums import IntEnum


class SolverAvailability(IntEnum):
"""
Class to capture different statuses in which a solver can exist in
order to record its availability for use.
"""

Available = 1
NotFound = 0
BadVersion = -1
NeedsCompiledExtension = -2

def __bool__(self):
return self._value_ > 0

def __format__(self, format_spec):
return format(self.name, format_spec)

def __str__(self):
return self.name


class LicenseAvailability(IntEnum):
"""
Runtime status for licensing. Independent from
overall solver availability. A return value > 0 is "usable in some form".
"""

FullLicense = 3
LimitedLicense = 2
NotApplicable = 1
NotAvailable = 0
BadLicense = -1
Timeout = -2
Unknown = -3

def __bool__(self):
return self._value_ > 0

def __format__(self, format_spec):
return format(self.name, format_spec)

def __str__(self):
return self.name
123 changes: 83 additions & 40 deletions pyomo/contrib/solver/common/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from pyomo.core.base.block import BlockData
from pyomo.core.base.objective import Objective, ObjectiveData
from pyomo.common.config import ConfigValue, ConfigDict
from pyomo.common.enums import IntEnum, SolverAPIVersion
from pyomo.common.enums import SolverAPIVersion
from pyomo.common.errors import ApplicationError
from pyomo.common.deprecation import deprecation_warning
from pyomo.common.modeling import NOTSET
Expand All @@ -29,6 +29,10 @@
from pyomo.core.base.label import NumericLabeler
from pyomo.core.staleflag import StaleFlagManager
from pyomo.scripting.solve_config import default_config_block
from pyomo.contrib.solver.common.availability import (
SolverAvailability,
LicenseAvailability,
)
from pyomo.contrib.solver.common.config import SolverConfig, PersistentSolverConfig
from pyomo.contrib.solver.common.util import get_objective
from pyomo.contrib.solver.common.results import (
Expand All @@ -39,39 +43,17 @@
)


class Availability(IntEnum):
"""
Class to capture different statuses in which a solver can exist in
order to record its availability for use.
"""

FullLicense = 2
LimitedLicense = 1
NotFound = 0
BadVersion = -1
BadLicense = -2
NeedsCompiledExtension = -3

def __bool__(self):
return self._value_ > 0

def __format__(self, format_spec):
return format(self.name, format_spec)

def __str__(self):
return self.name


class SolverBase:
"""The base class for "new-style" Pyomo solver interfaces.

This base class defines the methods all derived solvers are expected
to implement:

- :py:meth:`available`
- :py:meth:`solver_available`
- :py:meth:`license_available`
- :py:meth:`is_persistent`
- :py:meth:`solve`
- :py:meth:`version`
- :py:meth:`solve`

**Class Configuration**

Expand Down Expand Up @@ -138,31 +120,92 @@ def solve(self, model: BlockData, **kwargs) -> Results:
f"Derived class {self.__class__.__name__} failed to implement required method 'solve'."
)

def available(self) -> Availability:
"""Test if the solver is available on this system.
def available(self, recheck: bool = False, timeout: Optional[float] = 0) -> bool:
"""Test if a solver is both available and licensed on this system.

This function, which does not need to be implemented by any derived
class, returns a bool that represents if a solver is both
available to run (``solver_available``) and if it is properly
licensed to run (``license_available``).

Parameters
----------
recheck: bool
A flag to trigger whether the overall availability should be
rechecked. Default behavior is to use the cached availability.
timeout: float
How long to wait for a license before declaring a timeout.
Default behavior is to not wait (i.e., a license either
needs to be available immediately or will return False)

"""
return (
self.solver_available(recheck=recheck).__bool__()
and self.license_available(recheck=recheck, timeout=timeout).__bool__()
)

def solver_available(self, recheck: bool = False) -> SolverAvailability:
"""Test if the solver is available/findable on this system.

Nominally, this will return ``True`` if the solver interface is
valid and findable (e.g., executable is on the path, solver is
importable), and will return ``False`` otherwise.

Nominally, this will return `True` if the solver interface is
valid and can be used to solve problems and `False` if it cannot.
Parameters
----------
recheck: bool
A flag to trigger whether the availability should be
rechecked. Default behavior is to use the cached availability.

Returns
-------
solver_available: SolverAvailability
An enum that indicates "how available" the solver is.
Note that the enum can be cast to bool, which will
be True if the solver is accessible and False
otherwise.
"""
raise NotImplementedError(
f"Derived class {self.__class__.__name__} failed to implement required method 'solver_available'."
)

def license_available(
self, recheck: bool = False, timeout: Optional[float] = 0
) -> LicenseAvailability:
"""Test if licensed solver has an available and usable license.

The default behavior of this for solvers without licenses should be
to return ``True``.
Note that for licensed solvers there are a number of "levels" of
available: depending on the license, the solver may be available
with limitations on problem size or runtime (e.g., 'demo'
vs. 'community' vs. 'full'). In these cases, the solver may
return a subclass of enum.IntEnum, with members that resolve to
True if the solver is available (possibly with limitations).
The Enum may also have multiple members that all resolve to
False indicating the reason why the interface is not available
(not found, bad license, unsupported version, etc).
vs. 'community' vs. 'full').
Some solvers may also want to consider implementing
``acquire_license`` and ``release_license`` if the license
needs to be checked out (e.g., gurobi), whereas others
may simply need to check for the existence of a
license file (e.g., BARON).

Parameters
----------
recheck: bool
A flag to trigger whether the license availability should be
rechecked. Default behavior is to use the cached availability.
timeout: float
How long to wait for a license before declaring a timeout.
Default behavior is to not wait (i.e., a license either
needs to be available immediately or will return False)

Returns
-------
available: Availability
An enum that indicates "how available" the solver is.
license_available: LicenseAvailability
An enum that indicates the license availability of a solver.
Note that the enum can be cast to bool, which will
be True if the solver is runable at all and False
be True if the license is valid at all and False
otherwise.
"""
raise NotImplementedError(
f"Derived class {self.__class__.__name__} failed to implement required method 'available'."
f"Derived class {self.__class__.__name__} failed to implement required method 'license_available'."
)

def version(self) -> Tuple:
Expand Down
Loading
Loading