Source code for qplex.utils.model_utils
from dataclasses import dataclass
from typing import Dict, Optional, List, Set
import docplex.mp.constr
import docplex.mp.constants
import docplex.mp.model
from qplex.model.constants import ConstraintType
[docs]
@dataclass
class ConstraintInfo:
"""Stores information about detected constraints"""
type: ConstraintType
parameters: Optional[Dict] = None
additional_constraints: Optional[List[ConstraintType]] = None
[docs]
def get_model_constraint_info(model: docplex.mp.model.Model) -> ConstraintInfo:
"""
Analyzes a DOcplex model to determine constraint types.
Parameters
----------
model : Model
The DOcplex model to analyze
Returns
-------
ConstraintInfo
Information about detected constraints
"""
constraints: List[docplex.mp.constr.LinearConstraint] = list(
model.iter_constraints())
detected_constraints: Set[ConstraintType] = set()
parameters = {}
# Helper functions for coefficient analysis
def get_coefficients(const: docplex.mp.constr.LinearConstraint) -> Dict:
return {var.name: coef for var, coef in
const.get_left_expr().iter_terms()}
def get_unique_coefs(const: docplex.mp.constr.LinearConstraint) -> Set:
return set(coef for _, coef in const.get_left_expr().iter_terms())
# Check for cardinality constraints (sum x_i = k)
cardinality_constraints = [
const for const in constraints
if const.sense == docplex.mp.constants.ComparisonType.EQ and all(
coef == 1 for coef
in get_coefficients(const)
.values()) and isinstance(
const.get_right_expr(), (int, float))
]
if cardinality_constraints:
detected_constraints.add(ConstraintType.CARDINALITY)
parameters["cardinality_k"] = cardinality_constraints[
0].get_right_expr()
# Check for partition constraints (sum x_i - sum y_i = 0)
partition_constraints = [
const for const in constraints
if const.sense == docplex.mp.constants.ComparisonType.EQ
and const.get_right_expr() == 0
and get_unique_coefs(const) == {1, -1}
]
if partition_constraints:
detected_constraints.add(ConstraintType.PARTITION)
# Check for inequality constraints
inequality_constraints = [const for const in constraints
if (
const.sense == docplex.mp.constants.ComparisonType.LE or
const.sense == docplex.mp.constants.ComparisonType.GE)
and not all(coef == 1 for coef in
get_coefficients(const).values())
]
if inequality_constraints:
detected_constraints.add(ConstraintType.INEQUALITY)
parameters["inequality_bounds"] = [
(const.sense, const.get_right_expr())
for const in inequality_constraints
]
# Multiple constraints handling
if len(detected_constraints) > 1:
return ConstraintInfo(
type=ConstraintType.MULTIPLE,
parameters=parameters,
additional_constraints=list(detected_constraints)
)
elif len(detected_constraints) == 1:
constraint_type = detected_constraints.pop()
return ConstraintInfo(
type=constraint_type,
parameters=parameters
)
return ConstraintInfo(type=ConstraintType.UNCONSTRAINED)