Source code for lumix.solvers.capabilities
"""Solver capability detection and management."""
from dataclasses import dataclass
from enum import Enum, Flag, auto
[docs]
class LXSolverFeature(Flag):
"""
Solver features/capabilities (bit flags for combinations).
Basic:
LINEAR: Linear programming
INTEGER: Integer variables
BINARY: Binary variables
MIXED_INTEGER: Mixed-integer programming (LP + INTEGER)
Advanced:
QUADRATIC_CONVEX: Convex quadratic programming
QUADRATIC_NONCONVEX: Non-convex quadratic programming
SOCP: Second-order cone programming
SDP: Semidefinite programming
Special:
SOS1: Special Ordered Set type 1
SOS2: Special Ordered Set type 2
INDICATOR: Indicator constraints
CARDINALITY: Cardinality constraints
Non-linear:
PWL: Piecewise-linear functions
EXPONENTIAL_CONE: Exponential cone constraints
LOG: Logarithmic constraints
Features:
LAZY_CONSTRAINTS: Lazy constraint callbacks
USER_CUTS: User cut callbacks
HEURISTICS: Custom heuristics
IIS: Irreducible Inconsistent Subsystem
CONFLICT_REFINEMENT: Conflict refinement
"""
# Basic
LINEAR = auto()
INTEGER = auto()
BINARY = auto()
MIXED_INTEGER = LINEAR | INTEGER
# Advanced
QUADRATIC_CONVEX = auto()
QUADRATIC_NONCONVEX = auto()
SOCP = auto()
SDP = auto()
# Special
SOS1 = auto()
SOS2 = auto()
INDICATOR = auto()
CARDINALITY = auto()
# Non-linear
PWL = auto()
EXPONENTIAL_CONE = auto()
LOG = auto()
# Features
LAZY_CONSTRAINTS = auto()
USER_CUTS = auto()
HEURISTICS = auto()
IIS = auto()
CONFLICT_REFINEMENT = auto()
SENSITIVITY_ANALYSIS = auto()
[docs]
@dataclass
class LXSolverCapability:
"""
Describes a solver's capabilities.
Used to:
- Query what features a solver supports
- Automatically select appropriate linearization methods
- Provide meaningful errors when features are unavailable
"""
name: str
features: LXSolverFeature
max_variables: int = 2**31 - 1
max_constraints: int = 2**31 - 1
supports_warmstart: bool = False
supports_parallel: bool = False
supports_callbacks: bool = False
[docs]
def has_feature(self, feature: LXSolverFeature) -> bool:
"""
Check if solver has a specific feature.
Args:
feature: Feature to check
Returns:
True if solver supports the feature
"""
return bool(self.features & feature)
[docs]
def can_solve_quadratic(self) -> bool:
"""Check if solver can handle quadratic objectives."""
return self.has_feature(LXSolverFeature.QUADRATIC_CONVEX) or self.has_feature(
LXSolverFeature.QUADRATIC_NONCONVEX
)
[docs]
def can_solve_integer(self) -> bool:
"""Check if solver can handle integer variables."""
return self.has_feature(LXSolverFeature.INTEGER) or self.has_feature(LXSolverFeature.BINARY)
[docs]
def can_use_sos2(self) -> bool:
"""Check if solver has native SOS2 support."""
return self.has_feature(LXSolverFeature.SOS2)
[docs]
def can_use_indicator(self) -> bool:
"""Check if solver has native indicator constraint support."""
return self.has_feature(LXSolverFeature.INDICATOR)
[docs]
def needs_linearization_for_bilinear(self) -> bool:
"""
Check if solver needs linearization for bilinear products (x * y).
Returns:
True if solver lacks native quadratic support
"""
return not (
self.has_feature(LXSolverFeature.QUADRATIC_CONVEX)
or self.has_feature(LXSolverFeature.QUADRATIC_NONCONVEX)
)
[docs]
def needs_linearization_for_abs(self) -> bool:
"""
Check if solver needs linearization for absolute value |x|.
Returns:
True if solver lacks native piecewise-linear support
"""
return not self.has_feature(LXSolverFeature.PWL)
[docs]
def needs_linearization_for_minmax(self) -> bool:
"""
Check if solver needs linearization for min/max functions.
Returns:
True if solver lacks native piecewise-linear support
"""
return not self.has_feature(LXSolverFeature.PWL)
[docs]
def needs_linearization_for_nonlinear(self) -> bool:
"""
Check if solver needs linearization for general nonlinear functions.
Returns:
True if solver lacks native exponential cone or PWL support
"""
return not (
self.has_feature(LXSolverFeature.EXPONENTIAL_CONE)
or self.has_feature(LXSolverFeature.PWL)
)
[docs]
def description(self) -> str:
"""Get human-readable capability description."""
capabilities = []
if self.has_feature(LXSolverFeature.LINEAR):
capabilities.append("Linear Programming")
if self.has_feature(LXSolverFeature.MIXED_INTEGER):
capabilities.append("Mixed-Integer Programming")
if self.can_solve_quadratic():
capabilities.append("Quadratic Programming")
if self.has_feature(LXSolverFeature.SOCP):
capabilities.append("Second-Order Cone Programming")
if self.has_feature(LXSolverFeature.SDP):
capabilities.append("Semidefinite Programming")
return f"{self.name}: {', '.join(capabilities)}"
# Pre-defined capabilities for common solvers
ORTOOLS_CAPABILITIES = LXSolverCapability(
name="OR-Tools",
features=(
LXSolverFeature.LINEAR
| LXSolverFeature.INTEGER
| LXSolverFeature.BINARY
| LXSolverFeature.SOS1
| LXSolverFeature.SOS2
| LXSolverFeature.INDICATOR
),
supports_warmstart=True,
supports_parallel=True,
)
GUROBI_CAPABILITIES = LXSolverCapability(
name="Gurobi",
features=(
LXSolverFeature.LINEAR
| LXSolverFeature.INTEGER
| LXSolverFeature.BINARY
| LXSolverFeature.QUADRATIC_CONVEX
| LXSolverFeature.QUADRATIC_NONCONVEX
| LXSolverFeature.SOCP
| LXSolverFeature.SOS1
| LXSolverFeature.SOS2
| LXSolverFeature.INDICATOR
| LXSolverFeature.PWL
| LXSolverFeature.LAZY_CONSTRAINTS
| LXSolverFeature.USER_CUTS
| LXSolverFeature.IIS
| LXSolverFeature.CONFLICT_REFINEMENT
| LXSolverFeature.SENSITIVITY_ANALYSIS
),
supports_warmstart=True,
supports_parallel=True,
supports_callbacks=True,
)
CPLEX_CAPABILITIES = LXSolverCapability(
name="CPLEX",
features=(
LXSolverFeature.LINEAR
| LXSolverFeature.INTEGER
| LXSolverFeature.BINARY
| LXSolverFeature.QUADRATIC_CONVEX
| LXSolverFeature.QUADRATIC_NONCONVEX
| LXSolverFeature.SOCP
| LXSolverFeature.SOS1
| LXSolverFeature.SOS2
| LXSolverFeature.INDICATOR
| LXSolverFeature.PWL
| LXSolverFeature.LAZY_CONSTRAINTS
| LXSolverFeature.USER_CUTS
| LXSolverFeature.IIS
| LXSolverFeature.CONFLICT_REFINEMENT
| LXSolverFeature.SENSITIVITY_ANALYSIS
),
supports_warmstart=True,
supports_parallel=True,
supports_callbacks=True,
)
CPSAT_CAPABILITIES = LXSolverCapability(
name="OR-Tools CP-SAT",
features=(
LXSolverFeature.INTEGER
| LXSolverFeature.BINARY
# Note: CP-SAT does NOT support continuous variables (no LINEAR flag)
# CP-SAT is designed for integer and boolean variables only
# TODO: Add CP-specific features when library supports them:
# - AllDifferent constraints
# - Circuit constraints (for routing/TSP)
# - Table constraints
# - Interval variables (for scheduling)
# - NoOverlap constraints
# - Cumulative constraints
),
supports_warmstart=True, # Via solution hints
supports_parallel=True, # Multi-threaded search
supports_callbacks=False, # No callbacks in current implementation
)
GLPK_CAPABILITIES = LXSolverCapability(
name="GLPK",
features=(
LXSolverFeature.LINEAR
| LXSolverFeature.INTEGER
| LXSolverFeature.BINARY
| LXSolverFeature.SENSITIVITY_ANALYSIS
),
supports_warmstart=False,
supports_parallel=False, # GLPK is single-threaded
supports_callbacks=False,
)
__all__ = [
"LXSolverFeature",
"LXSolverCapability",
"ORTOOLS_CAPABILITIES",
"GUROBI_CAPABILITIES",
"CPLEX_CAPABILITIES",
"CPSAT_CAPABILITIES",
"GLPK_CAPABILITIES",
]