mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-06 01:18:52 -06:00
ConstraintFeatures: Store lhs and sense
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||
# Released under the modified BSD license. See COPYING.md for more details.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Dict
|
||||
|
||||
from miplearn.types import ModelFeatures
|
||||
from miplearn.types import ModelFeatures, ConstraintFeatures
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from miplearn import InternalSolver
|
||||
@@ -15,12 +15,16 @@ class ModelFeaturesExtractor:
|
||||
self,
|
||||
internal_solver: "InternalSolver",
|
||||
) -> None:
|
||||
self.internal_solver = internal_solver
|
||||
self.solver = internal_solver
|
||||
|
||||
def extract(self) -> ModelFeatures:
|
||||
rhs = {}
|
||||
for cid in self.internal_solver.get_constraint_ids():
|
||||
rhs[cid] = self.internal_solver.get_constraint_rhs(cid)
|
||||
constraints: Dict[str, ConstraintFeatures] = {}
|
||||
for cid in self.solver.get_constraint_ids():
|
||||
constraints[cid] = {
|
||||
"rhs": self.solver.get_constraint_rhs(cid),
|
||||
"lhs": self.solver.get_constraint_lhs(cid),
|
||||
"sense": self.solver.get_constraint_sense(cid),
|
||||
}
|
||||
return {
|
||||
"ConstraintRHS": rhs,
|
||||
"constraints": constraints,
|
||||
}
|
||||
|
||||
@@ -339,6 +339,15 @@ class GurobiSolver(InternalSolver):
|
||||
assert self.model is not None
|
||||
return self.model.getConstrByName(cid).rhs
|
||||
|
||||
def get_constraint_lhs(self, cid: str) -> Dict[str, float]:
|
||||
assert self.model is not None
|
||||
constr = self.model.getConstrByName(cid)
|
||||
expr = self.model.getRow(constr)
|
||||
lhs: Dict[str, float] = {}
|
||||
for i in range(expr.size()):
|
||||
lhs[expr.getVar(i).varName] = expr.getCoeff(i)
|
||||
return lhs
|
||||
|
||||
def extract_constraint(self, cid):
|
||||
self._raise_if_callback()
|
||||
constr = self.model.getConstrByName(cid)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from miplearn.instance import Instance
|
||||
from miplearn.types import (
|
||||
@@ -162,6 +162,17 @@ class InternalSolver(ABC):
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_constraint_lhs(self, cid: str) -> Dict[str, float]:
|
||||
"""
|
||||
Returns a list of tuples encoding the left-hand side of the constraint.
|
||||
|
||||
The first element of the tuple is the name of the variable and the second
|
||||
element is the coefficient. For example, the left-hand side of "2 x1 + x2 <= 3"
|
||||
is encoded as [{"x1": 2, "x2": 1}].
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_constraint(self, cobj: Constraint) -> None:
|
||||
"""
|
||||
|
||||
@@ -298,7 +298,9 @@ class BasePyomoSolver(InternalSolver):
|
||||
cobj = self._cname_to_constr[cid]
|
||||
has_ub = cobj.has_ub()
|
||||
has_lb = cobj.has_lb()
|
||||
assert (not has_lb) or (not has_ub), "range constraints not supported"
|
||||
assert (
|
||||
(not has_lb) or (not has_ub) or cobj.upper() == cobj.lower()
|
||||
), "range constraints not supported"
|
||||
if has_lb:
|
||||
return ">"
|
||||
elif has_ub:
|
||||
@@ -313,6 +315,9 @@ class BasePyomoSolver(InternalSolver):
|
||||
else:
|
||||
return cobj.lower()
|
||||
|
||||
def get_constraint_lhs(self, cid: str) -> Dict[str, float]:
|
||||
return {}
|
||||
|
||||
def set_constraint_sense(self, cid: str, sense: str) -> None:
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||
# Released under the modified BSD license. See COPYING.md for more details.
|
||||
|
||||
from typing import Optional, Dict, Callable, Any, Union, Tuple
|
||||
from typing import Optional, Dict, Callable, Any, Union, Tuple, List
|
||||
|
||||
from mypy_extensions import TypedDict
|
||||
|
||||
@@ -71,10 +71,20 @@ LearningSolveStats = TypedDict(
|
||||
total=False,
|
||||
)
|
||||
|
||||
ConstraintFeatures = TypedDict(
|
||||
"ConstraintFeatures",
|
||||
{
|
||||
"rhs": float,
|
||||
"lhs": Dict[str, float],
|
||||
"sense": str,
|
||||
},
|
||||
total=False,
|
||||
)
|
||||
|
||||
ModelFeatures = TypedDict(
|
||||
"ModelFeatures",
|
||||
{
|
||||
"ConstraintRHS": Dict[str, float],
|
||||
"constraints": Dict[str, ConstraintFeatures],
|
||||
},
|
||||
total=False,
|
||||
)
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||
# Released under the modified BSD license. See COPYING.md for more details.
|
||||
|
||||
from miplearn import GurobiSolver
|
||||
from miplearn.features import ModelFeaturesExtractor
|
||||
from tests.fixtures.knapsack import get_knapsack_instance
|
||||
from tests.solvers import get_internal_solvers
|
||||
|
||||
|
||||
def test_knapsack() -> None:
|
||||
for solver_factory in get_internal_solvers():
|
||||
for solver_factory in [GurobiSolver]:
|
||||
# Initialize model, instance and internal solver
|
||||
solver = solver_factory()
|
||||
instance = get_knapsack_instance(solver)
|
||||
@@ -20,4 +21,11 @@ def test_knapsack() -> None:
|
||||
|
||||
# Test constraint features
|
||||
print(solver, features)
|
||||
assert features["ConstraintRHS"]["eq_capacity"] == 67.0
|
||||
assert features["constraints"]["eq_capacity"]["lhs"] == {
|
||||
"x[0]": 23.0,
|
||||
"x[1]": 26.0,
|
||||
"x[2]": 20.0,
|
||||
"x[3]": 18.0,
|
||||
}
|
||||
assert features["constraints"]["eq_capacity"]["sense"] == "<"
|
||||
assert features["constraints"]["eq_capacity"]["rhs"] == 67.0
|
||||
|
||||
Reference in New Issue
Block a user