mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-06 01:18:52 -06:00
Remove tuples from ConstraintFeatures
This commit is contained in:
@@ -36,3 +36,10 @@ class _RedirectOutput:
|
||||
) -> None:
|
||||
sys.stdout = self._original_stdout
|
||||
sys.stderr = self._original_stderr
|
||||
|
||||
|
||||
def _none_if_empty(obj: Any) -> Any:
|
||||
if len(obj) == 0:
|
||||
return None
|
||||
else:
|
||||
return obj
|
||||
|
||||
@@ -122,7 +122,7 @@ class GurobiSolver(InternalSolver):
|
||||
self,
|
||||
cf: ConstraintFeatures,
|
||||
tol: float = 1e-5,
|
||||
) -> Tuple[bool, ...]:
|
||||
) -> List[bool]:
|
||||
assert cf.names is not None
|
||||
assert cf.senses is not None
|
||||
assert cf.lhs is not None
|
||||
@@ -141,7 +141,7 @@ class GurobiSolver(InternalSolver):
|
||||
result.append(lhs >= cf.rhs[i] - tol)
|
||||
else:
|
||||
result.append(abs(cf.rhs[i] - lhs) <= tol)
|
||||
return tuple(result)
|
||||
return result
|
||||
|
||||
@overrides
|
||||
def build_test_instance_infeasible(self) -> Instance:
|
||||
@@ -209,37 +209,37 @@ class GurobiSolver(InternalSolver):
|
||||
raise Exception(f"unknown cbasis: {v}")
|
||||
|
||||
gp_constrs = model.getConstrs()
|
||||
constr_names = tuple(model.getAttr("constrName", gp_constrs))
|
||||
rhs, lhs, senses, slacks, basis_status = None, None, None, None, None
|
||||
constr_names = model.getAttr("constrName", gp_constrs)
|
||||
lhs: Optional[List] = None
|
||||
rhs, senses, slacks, basis_status = None, None, None, None
|
||||
dual_value, basis_status, sa_rhs_up, sa_rhs_down = None, None, None, None
|
||||
|
||||
if with_static:
|
||||
rhs = tuple(model.getAttr("rhs", gp_constrs))
|
||||
senses = tuple(model.getAttr("sense", gp_constrs))
|
||||
rhs = model.getAttr("rhs", gp_constrs)
|
||||
senses = model.getAttr("sense", gp_constrs)
|
||||
if with_lhs:
|
||||
lhs_l: List = [None for _ in gp_constrs]
|
||||
lhs = [None for _ in gp_constrs]
|
||||
for (i, gp_constr) in enumerate(gp_constrs):
|
||||
expr = model.getRow(gp_constr)
|
||||
lhs_l[i] = tuple(
|
||||
lhs[i] = [
|
||||
(self._var_names[expr.getVar(j).index], expr.getCoeff(j))
|
||||
for j in range(expr.size())
|
||||
)
|
||||
lhs = tuple(lhs_l)
|
||||
]
|
||||
|
||||
if self._has_lp_solution:
|
||||
dual_value = tuple(model.getAttr("pi", gp_constrs))
|
||||
basis_status = tuple(
|
||||
dual_value = model.getAttr("pi", gp_constrs)
|
||||
basis_status = list(
|
||||
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))
|
||||
sa_rhs_up = model.getAttr("saRhsUp", gp_constrs)
|
||||
sa_rhs_down = model.getAttr("saRhsLow", gp_constrs)
|
||||
|
||||
if self._has_lp_solution or self._has_mip_solution:
|
||||
slacks = tuple(model.getAttr("slack", gp_constrs))
|
||||
slacks = model.getAttr("slack", gp_constrs)
|
||||
|
||||
return ConstraintFeatures(
|
||||
basis_status=basis_status,
|
||||
@@ -370,7 +370,7 @@ class GurobiSolver(InternalSolver):
|
||||
return self.model.status in [self.gp.GRB.INFEASIBLE, self.gp.GRB.INF_OR_UNBD]
|
||||
|
||||
@overrides
|
||||
def remove_constraints(self, names: Tuple[str, ...]) -> None:
|
||||
def remove_constraints(self, names: List[str]) -> None:
|
||||
assert self.model is not None
|
||||
constrs = [self.model.getConstrByName(n) for n in names]
|
||||
self.model.remove(constrs)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, List, Optional, Tuple
|
||||
from typing import Any, List, Optional, List
|
||||
|
||||
from miplearn.features import VariableFeatures, ConstraintFeatures
|
||||
from miplearn.instance.base import Instance
|
||||
@@ -59,7 +59,7 @@ class InternalSolver(ABC):
|
||||
self,
|
||||
cf: ConstraintFeatures,
|
||||
tol: float = 1e-5,
|
||||
) -> Tuple[bool, ...]:
|
||||
) -> List[bool]:
|
||||
"""
|
||||
Checks whether the current solution satisfies the given constraints.
|
||||
"""
|
||||
@@ -176,7 +176,7 @@ class InternalSolver(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def remove_constraints(self, names: Tuple[str, ...]) -> None:
|
||||
def remove_constraints(self, names: List[str]) -> None:
|
||||
"""
|
||||
Removes the given constraints from the model.
|
||||
"""
|
||||
|
||||
@@ -21,7 +21,7 @@ from pyomo.opt.base.solvers import SolverFactory
|
||||
|
||||
from miplearn.features import VariableFeatures, ConstraintFeatures
|
||||
from miplearn.instance.base import Instance
|
||||
from miplearn.solvers import _RedirectOutput
|
||||
from miplearn.solvers import _RedirectOutput, _none_if_empty
|
||||
from miplearn.solvers.internal import (
|
||||
InternalSolver,
|
||||
LPSolveStats,
|
||||
@@ -113,7 +113,7 @@ class BasePyomoSolver(InternalSolver):
|
||||
self,
|
||||
cf: ConstraintFeatures,
|
||||
tol: float = 1e-5,
|
||||
) -> Tuple[bool, ...]:
|
||||
) -> List[bool]:
|
||||
assert cf.names is not None
|
||||
assert cf.lhs is not None
|
||||
assert cf.rhs is not None
|
||||
@@ -130,7 +130,7 @@ class BasePyomoSolver(InternalSolver):
|
||||
result.append(lhs >= cf.rhs[i] - tol)
|
||||
else:
|
||||
result.append(abs(cf.rhs[i] - lhs) < tol)
|
||||
return tuple(result)
|
||||
return result
|
||||
|
||||
@overrides
|
||||
def build_test_instance_infeasible(self) -> Instance:
|
||||
@@ -165,7 +165,7 @@ class BasePyomoSolver(InternalSolver):
|
||||
|
||||
names: List[str] = []
|
||||
rhs: List[float] = []
|
||||
lhs: List[Tuple[Tuple[str, float], ...]] = []
|
||||
lhs: List[List[Tuple[str, float]]] = []
|
||||
senses: List[str] = []
|
||||
dual_values: List[float] = []
|
||||
slacks: List[float] = []
|
||||
@@ -214,7 +214,7 @@ class BasePyomoSolver(InternalSolver):
|
||||
raise Exception(
|
||||
f"Unknown expression type: {expr.__class__.__name__}"
|
||||
)
|
||||
lhs.append(tuple(lhsc))
|
||||
lhs.append(lhsc)
|
||||
|
||||
# Extract dual values
|
||||
if self._has_lp_solution:
|
||||
@@ -233,24 +233,13 @@ class BasePyomoSolver(InternalSolver):
|
||||
names.append(constr.name)
|
||||
_parse_constraint(constr)
|
||||
|
||||
rhs_t, lhs_t, senses_t = None, None, None
|
||||
slacks_t, dual_values_t = None, None
|
||||
if with_static:
|
||||
rhs_t = tuple(rhs)
|
||||
lhs_t = tuple(lhs)
|
||||
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(
|
||||
names=tuple(names),
|
||||
rhs=rhs_t,
|
||||
senses=senses_t,
|
||||
lhs=lhs_t,
|
||||
slacks=slacks_t,
|
||||
dual_values=dual_values_t,
|
||||
names=_none_if_empty(names),
|
||||
rhs=_none_if_empty(rhs),
|
||||
senses=_none_if_empty(senses),
|
||||
lhs=_none_if_empty(lhs),
|
||||
slacks=_none_if_empty(slacks),
|
||||
dual_values=_none_if_empty(dual_values),
|
||||
)
|
||||
|
||||
@overrides
|
||||
@@ -337,12 +326,6 @@ class BasePyomoSolver(InternalSolver):
|
||||
if self._has_lp_solution or self._has_mip_solution:
|
||||
values.append(v.value)
|
||||
|
||||
def _none_if_empty(obj: Any) -> Any:
|
||||
if len(obj) == 0:
|
||||
return None
|
||||
else:
|
||||
return obj
|
||||
|
||||
return VariableFeatures(
|
||||
names=_none_if_empty(names),
|
||||
types=_none_if_empty(types),
|
||||
@@ -379,7 +362,7 @@ class BasePyomoSolver(InternalSolver):
|
||||
return self._termination_condition == TerminationCondition.infeasible
|
||||
|
||||
@overrides
|
||||
def remove_constraints(self, names: Tuple[str, ...]) -> None:
|
||||
def remove_constraints(self, names: List[str]) -> None:
|
||||
assert self.model is not None
|
||||
for name in names:
|
||||
constr = self._cname_to_constr[name]
|
||||
|
||||
@@ -69,18 +69,18 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
|
||||
assert_equals(
|
||||
solver.get_constraints(),
|
||||
ConstraintFeatures(
|
||||
names=("eq_capacity",),
|
||||
rhs=(0.0,),
|
||||
lhs=(
|
||||
(
|
||||
names=["eq_capacity"],
|
||||
rhs=[0.0],
|
||||
lhs=[
|
||||
[
|
||||
("x[0]", 23.0),
|
||||
("x[1]", 26.0),
|
||||
("x[2]", 20.0),
|
||||
("x[3]", 18.0),
|
||||
("z", -1.0),
|
||||
),
|
||||
),
|
||||
senses=("=",),
|
||||
],
|
||||
],
|
||||
senses=["="],
|
||||
),
|
||||
)
|
||||
|
||||
@@ -120,12 +120,12 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
|
||||
_filter_attrs(
|
||||
solver.get_constraint_attrs(),
|
||||
ConstraintFeatures(
|
||||
basis_status=("N",),
|
||||
dual_values=(13.538462,),
|
||||
names=("eq_capacity",),
|
||||
sa_rhs_down=(-24.0,),
|
||||
sa_rhs_up=(2.0,),
|
||||
slacks=(0.0,),
|
||||
basis_status=["N"],
|
||||
dual_values=[13.538462],
|
||||
names=["eq_capacity"],
|
||||
sa_rhs_down=[-24.0],
|
||||
sa_rhs_up=[2.0],
|
||||
slacks=[0.0],
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -165,20 +165,20 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
|
||||
_filter_attrs(
|
||||
solver.get_constraint_attrs(),
|
||||
ConstraintFeatures(
|
||||
names=("eq_capacity",),
|
||||
slacks=(0.0,),
|
||||
names=["eq_capacity"],
|
||||
slacks=[0.0],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Build new constraint and verify that it is violated
|
||||
cf = ConstraintFeatures(
|
||||
names=("cut",),
|
||||
lhs=((("x[0]", 1.0),),),
|
||||
rhs=(0.0,),
|
||||
senses=("<",),
|
||||
names=["cut"],
|
||||
lhs=[[("x[0]", 1.0)]],
|
||||
rhs=[0.0],
|
||||
senses=["<"],
|
||||
)
|
||||
assert_equals(solver.are_constraints_satisfied(cf), (False,))
|
||||
assert_equals(solver.are_constraints_satisfied(cf), [False])
|
||||
|
||||
# Add constraint and verify it affects solution
|
||||
solver.add_constraints(cf)
|
||||
@@ -187,28 +187,30 @@ def run_basic_usage_tests(solver: InternalSolver) -> None:
|
||||
_filter_attrs(
|
||||
solver.get_constraint_attrs(),
|
||||
ConstraintFeatures(
|
||||
names=("eq_capacity", "cut"),
|
||||
rhs=(0.0, 0.0),
|
||||
lhs=(
|
||||
(
|
||||
names=["eq_capacity", "cut"],
|
||||
rhs=[0.0, 0.0],
|
||||
lhs=[
|
||||
[
|
||||
("x[0]", 23.0),
|
||||
("x[1]", 26.0),
|
||||
("x[2]", 20.0),
|
||||
("x[3]", 18.0),
|
||||
("z", -1.0),
|
||||
),
|
||||
(("x[0]", 1.0),),
|
||||
),
|
||||
senses=("=", "<"),
|
||||
],
|
||||
[
|
||||
("x[0]", 1.0),
|
||||
],
|
||||
],
|
||||
senses=["=", "<"],
|
||||
),
|
||||
),
|
||||
)
|
||||
stats = solver.solve()
|
||||
assert_equals(stats.mip_lower_bound, 1030.0)
|
||||
assert_equals(solver.are_constraints_satisfied(cf), (True,))
|
||||
assert_equals(solver.are_constraints_satisfied(cf), [True])
|
||||
|
||||
# Remove the new constraint
|
||||
solver.remove_constraints(("cut",))
|
||||
solver.remove_constraints(["cut"])
|
||||
|
||||
# New constraint should no longer affect solution
|
||||
stats = solver.solve()
|
||||
|
||||
Reference in New Issue
Block a user