Remove tuples from ConstraintFeatures

This commit is contained in:
2021-05-20 10:23:53 -05:00
parent f9ac65bf9c
commit c494f3e804
9 changed files with 139 additions and 147 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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.
"""

View File

@@ -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]

View File

@@ -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()