mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-06 17:38:51 -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.
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
# 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:
|
if TYPE_CHECKING:
|
||||||
from miplearn import InternalSolver
|
from miplearn import InternalSolver
|
||||||
@@ -15,12 +15,16 @@ class ModelFeaturesExtractor:
|
|||||||
self,
|
self,
|
||||||
internal_solver: "InternalSolver",
|
internal_solver: "InternalSolver",
|
||||||
) -> None:
|
) -> None:
|
||||||
self.internal_solver = internal_solver
|
self.solver = internal_solver
|
||||||
|
|
||||||
def extract(self) -> ModelFeatures:
|
def extract(self) -> ModelFeatures:
|
||||||
rhs = {}
|
constraints: Dict[str, ConstraintFeatures] = {}
|
||||||
for cid in self.internal_solver.get_constraint_ids():
|
for cid in self.solver.get_constraint_ids():
|
||||||
rhs[cid] = self.internal_solver.get_constraint_rhs(cid)
|
constraints[cid] = {
|
||||||
|
"rhs": self.solver.get_constraint_rhs(cid),
|
||||||
|
"lhs": self.solver.get_constraint_lhs(cid),
|
||||||
|
"sense": self.solver.get_constraint_sense(cid),
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
"ConstraintRHS": rhs,
|
"constraints": constraints,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -339,6 +339,15 @@ class GurobiSolver(InternalSolver):
|
|||||||
assert self.model is not None
|
assert self.model is not None
|
||||||
return self.model.getConstrByName(cid).rhs
|
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):
|
def extract_constraint(self, cid):
|
||||||
self._raise_if_callback()
|
self._raise_if_callback()
|
||||||
constr = self.model.getConstrByName(cid)
|
constr = self.model.getConstrByName(cid)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from abc import ABC, abstractmethod
|
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.instance import Instance
|
||||||
from miplearn.types import (
|
from miplearn.types import (
|
||||||
@@ -162,6 +162,17 @@ class InternalSolver(ABC):
|
|||||||
"""
|
"""
|
||||||
pass
|
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
|
@abstractmethod
|
||||||
def add_constraint(self, cobj: Constraint) -> None:
|
def add_constraint(self, cobj: Constraint) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -298,7 +298,9 @@ class BasePyomoSolver(InternalSolver):
|
|||||||
cobj = self._cname_to_constr[cid]
|
cobj = self._cname_to_constr[cid]
|
||||||
has_ub = cobj.has_ub()
|
has_ub = cobj.has_ub()
|
||||||
has_lb = cobj.has_lb()
|
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:
|
if has_lb:
|
||||||
return ">"
|
return ">"
|
||||||
elif has_ub:
|
elif has_ub:
|
||||||
@@ -313,6 +315,9 @@ class BasePyomoSolver(InternalSolver):
|
|||||||
else:
|
else:
|
||||||
return cobj.lower()
|
return cobj.lower()
|
||||||
|
|
||||||
|
def get_constraint_lhs(self, cid: str) -> Dict[str, float]:
|
||||||
|
return {}
|
||||||
|
|
||||||
def set_constraint_sense(self, cid: str, sense: str) -> None:
|
def set_constraint_sense(self, cid: str, sense: str) -> None:
|
||||||
raise Exception("Not implemented")
|
raise Exception("Not implemented")
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
# 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
|
from mypy_extensions import TypedDict
|
||||||
|
|
||||||
@@ -71,10 +71,20 @@ LearningSolveStats = TypedDict(
|
|||||||
total=False,
|
total=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ConstraintFeatures = TypedDict(
|
||||||
|
"ConstraintFeatures",
|
||||||
|
{
|
||||||
|
"rhs": float,
|
||||||
|
"lhs": Dict[str, float],
|
||||||
|
"sense": str,
|
||||||
|
},
|
||||||
|
total=False,
|
||||||
|
)
|
||||||
|
|
||||||
ModelFeatures = TypedDict(
|
ModelFeatures = TypedDict(
|
||||||
"ModelFeatures",
|
"ModelFeatures",
|
||||||
{
|
{
|
||||||
"ConstraintRHS": Dict[str, float],
|
"constraints": Dict[str, ConstraintFeatures],
|
||||||
},
|
},
|
||||||
total=False,
|
total=False,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
||||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
# Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
|
||||||
|
from miplearn import GurobiSolver
|
||||||
from miplearn.features import ModelFeaturesExtractor
|
from miplearn.features import ModelFeaturesExtractor
|
||||||
from tests.fixtures.knapsack import get_knapsack_instance
|
from tests.fixtures.knapsack import get_knapsack_instance
|
||||||
from tests.solvers import get_internal_solvers
|
|
||||||
|
|
||||||
|
|
||||||
def test_knapsack() -> None:
|
def test_knapsack() -> None:
|
||||||
for solver_factory in get_internal_solvers():
|
for solver_factory in [GurobiSolver]:
|
||||||
# Initialize model, instance and internal solver
|
# Initialize model, instance and internal solver
|
||||||
solver = solver_factory()
|
solver = solver_factory()
|
||||||
instance = get_knapsack_instance(solver)
|
instance = get_knapsack_instance(solver)
|
||||||
@@ -20,4 +21,11 @@ def test_knapsack() -> None:
|
|||||||
|
|
||||||
# Test constraint features
|
# Test constraint features
|
||||||
print(solver, 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