mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-06 09:28:51 -06:00
Extract more features to ConstraintFeatures
This commit is contained in:
@@ -148,6 +148,7 @@ class Constraint:
|
|||||||
class Features:
|
class Features:
|
||||||
instance: Optional[InstanceFeatures] = None
|
instance: Optional[InstanceFeatures] = None
|
||||||
variables: Optional[VariableFeatures] = None
|
variables: Optional[VariableFeatures] = None
|
||||||
|
constraints: Optional[ConstraintFeatures] = None
|
||||||
constraints_old: Optional[Dict[str, Constraint]] = None
|
constraints_old: Optional[Dict[str, Constraint]] = None
|
||||||
lp_solve: Optional["LPSolveStats"] = None
|
lp_solve: Optional["LPSolveStats"] = None
|
||||||
mip_solve: Optional["MIPSolveStats"] = None
|
mip_solve: Optional["MIPSolveStats"] = None
|
||||||
@@ -179,6 +180,10 @@ class FeaturesExtractor:
|
|||||||
with_static=with_static,
|
with_static=with_static,
|
||||||
with_sa=self.with_sa,
|
with_sa=self.with_sa,
|
||||||
)
|
)
|
||||||
|
features.constraints = solver.get_constraints(
|
||||||
|
with_static=with_static,
|
||||||
|
with_sa=self.with_sa,
|
||||||
|
)
|
||||||
features.constraints_old = solver.get_constraints_old(
|
features.constraints_old = solver.get_constraints_old(
|
||||||
with_static=with_static,
|
with_static=with_static,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -149,29 +149,40 @@ class GurobiSolver(InternalSolver):
|
|||||||
def get_constraint_attrs(self) -> List[str]:
|
def get_constraint_attrs(self) -> List[str]:
|
||||||
return [
|
return [
|
||||||
"basis_status",
|
"basis_status",
|
||||||
"category",
|
"categories",
|
||||||
"dual_value",
|
"dual_values",
|
||||||
"lazy",
|
"lazy",
|
||||||
"lhs",
|
"lhs",
|
||||||
|
"names",
|
||||||
"rhs",
|
"rhs",
|
||||||
"sa_rhs_down",
|
"sa_rhs_down",
|
||||||
"sa_rhs_up",
|
"sa_rhs_up",
|
||||||
"sense",
|
"senses",
|
||||||
"slack",
|
"slacks",
|
||||||
"user_features",
|
"user_features",
|
||||||
]
|
]
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def get_constraints(self, with_static: bool = True) -> ConstraintFeatures:
|
def get_constraints(
|
||||||
|
self,
|
||||||
|
with_static: bool = True,
|
||||||
|
with_sa: bool = True,
|
||||||
|
) -> ConstraintFeatures:
|
||||||
model = self.model
|
model = self.model
|
||||||
assert model is not None
|
assert model is not None
|
||||||
assert model.numVars == len(self._gp_vars)
|
assert model.numVars == len(self._gp_vars)
|
||||||
|
|
||||||
|
def _parse_gurobi_cbasis(v: int) -> str:
|
||||||
|
if v == 0:
|
||||||
|
return "B"
|
||||||
|
if v == -1:
|
||||||
|
return "N"
|
||||||
|
raise Exception(f"unknown cbasis: {v}")
|
||||||
|
|
||||||
gp_constrs = model.getConstrs()
|
gp_constrs = model.getConstrs()
|
||||||
constr_names = tuple(model.getAttr("constrName", gp_constrs))
|
constr_names = tuple(model.getAttr("constrName", gp_constrs))
|
||||||
rhs = None
|
rhs, lhs, senses, slacks, basis_status = None, None, None, None, None
|
||||||
senses = None
|
dual_value, basis_status, sa_rhs_up, sa_rhs_down = None, None, None, None
|
||||||
lhs = None
|
|
||||||
|
|
||||||
if with_static:
|
if with_static:
|
||||||
rhs = tuple(model.getAttr("rhs", gp_constrs))
|
rhs = tuple(model.getAttr("rhs", gp_constrs))
|
||||||
@@ -185,11 +196,31 @@ class GurobiSolver(InternalSolver):
|
|||||||
)
|
)
|
||||||
lhs = tuple(lhs_l)
|
lhs = tuple(lhs_l)
|
||||||
|
|
||||||
|
if self._has_lp_solution:
|
||||||
|
dual_value = tuple(model.getAttr("pi", gp_constrs))
|
||||||
|
basis_status = tuple(
|
||||||
|
map(
|
||||||
|
_parse_gurobi_cbasis,
|
||||||
|
model.getAttr("cbasis", gp_constrs),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if with_sa:
|
||||||
|
sa_rhs_up = tuple(model.getAttr("saRhsUp", gp_constrs))
|
||||||
|
sa_rhs_down = tuple(model.getAttr("saRhsLow", gp_constrs))
|
||||||
|
|
||||||
|
if self._has_lp_solution or self._has_mip_solution:
|
||||||
|
slacks = tuple(model.getAttr("slack", gp_constrs))
|
||||||
|
|
||||||
return ConstraintFeatures(
|
return ConstraintFeatures(
|
||||||
|
basis_status=basis_status,
|
||||||
|
dual_values=dual_value,
|
||||||
|
lhs=lhs,
|
||||||
names=constr_names,
|
names=constr_names,
|
||||||
rhs=rhs,
|
rhs=rhs,
|
||||||
|
sa_rhs_down=sa_rhs_down,
|
||||||
|
sa_rhs_up=sa_rhs_up,
|
||||||
senses=senses,
|
senses=senses,
|
||||||
lhs=lhs,
|
slacks=slacks,
|
||||||
)
|
)
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
|
|||||||
@@ -169,7 +169,11 @@ class InternalSolver(ABC, EnforceOverrides):
|
|||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_constraints(self, with_static: bool = True) -> ConstraintFeatures:
|
def get_constraints(
|
||||||
|
self,
|
||||||
|
with_static: bool = True,
|
||||||
|
with_sa: bool = True,
|
||||||
|
) -> ConstraintFeatures:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|||||||
@@ -128,15 +128,23 @@ class BasePyomoSolver(InternalSolver):
|
|||||||
self._pyomo_solver.update_var(var)
|
self._pyomo_solver.update_var(var)
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def get_constraints(self, with_static: bool = True) -> ConstraintFeatures:
|
def get_constraints(
|
||||||
assert self.model is not None
|
self,
|
||||||
|
with_static: bool = True,
|
||||||
|
with_sa: bool = True,
|
||||||
|
) -> ConstraintFeatures:
|
||||||
|
model = self.model
|
||||||
|
assert model is not None
|
||||||
|
|
||||||
names: List[str] = []
|
names: List[str] = []
|
||||||
rhs: List[float] = []
|
rhs: List[float] = []
|
||||||
lhs: List[Tuple[Tuple[str, float], ...]] = []
|
lhs: List[Tuple[Tuple[str, float], ...]] = []
|
||||||
senses: List[str] = []
|
senses: List[str] = []
|
||||||
|
dual_values: List[float] = []
|
||||||
|
slacks: List[float] = []
|
||||||
|
|
||||||
def _parse_constraint(c: pe.Constraint) -> None:
|
def _parse_constraint(c: pe.Constraint) -> None:
|
||||||
|
assert model is not None
|
||||||
if with_static:
|
if with_static:
|
||||||
# Extract RHS and sense
|
# Extract RHS and sense
|
||||||
has_ub = c.has_ub()
|
has_ub = c.has_ub()
|
||||||
@@ -175,7 +183,15 @@ class BasePyomoSolver(InternalSolver):
|
|||||||
)
|
)
|
||||||
lhs.append(tuple(lhsc))
|
lhs.append(tuple(lhsc))
|
||||||
|
|
||||||
for constr in self.model.component_objects(pyomo.core.Constraint):
|
# Extract dual values
|
||||||
|
if self._has_lp_solution:
|
||||||
|
dual_values.append(model.dual[c])
|
||||||
|
|
||||||
|
# Extract slacks
|
||||||
|
if self._has_mip_solution or self._has_lp_solution:
|
||||||
|
slacks.append(model.slack[c])
|
||||||
|
|
||||||
|
for constr in model.component_objects(pyomo.core.Constraint):
|
||||||
if isinstance(constr, pe.ConstraintList):
|
if isinstance(constr, pe.ConstraintList):
|
||||||
for idx in constr:
|
for idx in constr:
|
||||||
names.append(f"{constr.name}[{idx}]")
|
names.append(f"{constr.name}[{idx}]")
|
||||||
@@ -185,16 +201,23 @@ class BasePyomoSolver(InternalSolver):
|
|||||||
_parse_constraint(constr)
|
_parse_constraint(constr)
|
||||||
|
|
||||||
rhs_t, lhs_t, senses_t = None, None, None
|
rhs_t, lhs_t, senses_t = None, None, None
|
||||||
|
slacks_t, dual_values_t = None, None
|
||||||
if with_static:
|
if with_static:
|
||||||
rhs_t = tuple(rhs)
|
rhs_t = tuple(rhs)
|
||||||
lhs_t = tuple(lhs)
|
lhs_t = tuple(lhs)
|
||||||
senses_t = tuple(senses)
|
senses_t = tuple(senses)
|
||||||
|
if self._has_lp_solution:
|
||||||
|
dual_values_t = tuple(dual_values)
|
||||||
|
if self._has_lp_solution or self._has_mip_solution:
|
||||||
|
slacks_t = tuple(slacks)
|
||||||
|
|
||||||
return ConstraintFeatures(
|
return ConstraintFeatures(
|
||||||
names=tuple(names),
|
names=tuple(names),
|
||||||
rhs=rhs_t,
|
rhs=rhs_t,
|
||||||
senses=senses_t,
|
senses=senses_t,
|
||||||
lhs=lhs_t,
|
lhs=lhs_t,
|
||||||
|
slacks=slacks_t,
|
||||||
|
dual_values=dual_values_t,
|
||||||
)
|
)
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
@@ -224,12 +247,12 @@ class BasePyomoSolver(InternalSolver):
|
|||||||
@overrides
|
@overrides
|
||||||
def get_constraint_attrs(self) -> List[str]:
|
def get_constraint_attrs(self) -> List[str]:
|
||||||
return [
|
return [
|
||||||
"dual_value",
|
"dual_values",
|
||||||
"lazy",
|
|
||||||
"lhs",
|
"lhs",
|
||||||
|
"names",
|
||||||
"rhs",
|
"rhs",
|
||||||
"sense",
|
"senses",
|
||||||
"slack",
|
"slacks",
|
||||||
]
|
]
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
|
|||||||
@@ -30,16 +30,12 @@ def _round(obj: Any) -> Any:
|
|||||||
return tuple([_round(v) for v in obj])
|
return tuple([_round(v) for v in obj])
|
||||||
if isinstance(obj, list):
|
if isinstance(obj, list):
|
||||||
return [_round(v) for v in obj]
|
return [_round(v) for v in obj]
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return {key: _round(value) for (key, value) in obj.items()}
|
||||||
if isinstance(obj, VariableFeatures):
|
if isinstance(obj, VariableFeatures):
|
||||||
obj.reduced_costs = _round(obj.reduced_costs)
|
obj.__dict__ = _round(obj.__dict__)
|
||||||
obj.sa_obj_up = _round(obj.sa_obj_up)
|
if isinstance(obj, ConstraintFeatures):
|
||||||
obj.sa_obj_down = _round(obj.sa_obj_down)
|
obj.__dict__ = _round(obj.__dict__)
|
||||||
obj.sa_lb_up = _round(obj.sa_lb_up)
|
|
||||||
obj.sa_lb_down = _round(obj.sa_lb_down)
|
|
||||||
obj.sa_ub_up = _round(obj.sa_ub_up)
|
|
||||||
obj.sa_ub_down = _round(obj.sa_ub_down)
|
|
||||||
obj.values = _round(obj.values)
|
|
||||||
obj.alvarez_2017 = _round(obj.alvarez_2017)
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
@@ -110,18 +106,6 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert_equals(
|
|
||||||
_round_constraints(solver.get_constraints_old()),
|
|
||||||
{
|
|
||||||
"eq_capacity": Constraint(
|
|
||||||
lazy=False,
|
|
||||||
lhs={"x[0]": 23.0, "x[1]": 26.0, "x[2]": 20.0, "x[3]": 18.0, "z": -1.0},
|
|
||||||
rhs=0.0,
|
|
||||||
sense="=",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Solve linear programming relaxation
|
# Solve linear programming relaxation
|
||||||
lp_stats = solver.solve_lp()
|
lp_stats = solver.solve_lp()
|
||||||
assert not solver.is_infeasible()
|
assert not solver.is_infeasible()
|
||||||
@@ -154,28 +138,17 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
|
|||||||
|
|
||||||
# Fetch constraints (after-lp)
|
# Fetch constraints (after-lp)
|
||||||
assert_equals(
|
assert_equals(
|
||||||
_round_constraints(solver.get_constraints_old()),
|
_round(solver.get_constraints(with_static=False)),
|
||||||
_remove_unsupported_constr_attrs(
|
_filter_attrs(
|
||||||
solver,
|
solver.get_constraint_attrs(),
|
||||||
{
|
ConstraintFeatures(
|
||||||
"eq_capacity": Constraint(
|
basis_status=("N",),
|
||||||
basis_status="N",
|
dual_values=(13.538462,),
|
||||||
dual_value=13.538462,
|
names=("eq_capacity",),
|
||||||
lazy=False,
|
sa_rhs_down=(-24.0,),
|
||||||
lhs={
|
sa_rhs_up=(2.0,),
|
||||||
"x[0]": 23.0,
|
slacks=(0.0,),
|
||||||
"x[1]": 26.0,
|
),
|
||||||
"x[2]": 20.0,
|
|
||||||
"x[3]": 18.0,
|
|
||||||
"z": -1.0,
|
|
||||||
},
|
|
||||||
rhs=0.0,
|
|
||||||
sa_rhs_down=-24.0,
|
|
||||||
sa_rhs_up=1.9999999999999987,
|
|
||||||
sense="=",
|
|
||||||
slack=0.0,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user