Remove tuples from ConstraintFeatures

master
Alinson S. Xavier 4 years ago
parent f9ac65bf9c
commit c494f3e804
No known key found for this signature in database
GPG Key ID: DCA0DAD4D2F58624

@ -88,10 +88,10 @@ class StaticLazyConstraintsComponent(Component):
assert constraints is not None
assert constraints.lazy is not None
assert constraints.names is not None
selected = tuple(
selected = [
(constraints.lazy[i] and constraints.names[i] not in self.enforced_cids)
for i in range(len(constraints.lazy))
)
]
n_removed = sum(selected)
n_kept = sum(constraints.lazy) - n_removed
self.pool = constraints[selected]
@ -174,7 +174,7 @@ class StaticLazyConstraintsComponent(Component):
self.pool,
tol=self.violation_tolerance,
)
is_violated = tuple(not i for i in is_satisfied)
is_violated = [not i for i in is_satisfied]
violated_constraints = self.pool[is_violated]
satisfied_constraints = self.pool[is_satisfied]
self.pool = satisfied_constraints

@ -78,18 +78,18 @@ class VariableFeatures:
@dataclass
class ConstraintFeatures:
basis_status: Optional[Tuple[str, ...]] = None
categories: Optional[Tuple[Optional[Hashable], ...]] = None
dual_values: Optional[Tuple[float, ...]] = None
names: Optional[Tuple[str, ...]] = None
lazy: Optional[Tuple[bool, ...]] = None
lhs: Optional[Tuple[Tuple[Tuple[str, float], ...], ...]] = None
rhs: Optional[Tuple[float, ...]] = None
sa_rhs_down: Optional[Tuple[float, ...]] = None
sa_rhs_up: Optional[Tuple[float, ...]] = None
senses: Optional[Tuple[str, ...]] = None
slacks: Optional[Tuple[float, ...]] = None
user_features: Optional[Tuple[Optional[Tuple[float, ...]], ...]] = None
basis_status: Optional[List[str]] = None
categories: Optional[List[Optional[Hashable]]] = None
dual_values: Optional[List[float]] = None
names: Optional[List[str]] = None
lazy: Optional[List[bool]] = None
lhs: Optional[List[List[Tuple[str, float]]]] = None
rhs: Optional[List[float]] = None
sa_rhs_down: Optional[List[float]] = None
sa_rhs_up: Optional[List[float]] = None
senses: Optional[List[str]] = None
slacks: Optional[List[float]] = None
user_features: Optional[List[Optional[List[float]]]] = None
def to_list(self, index: int) -> List[float]:
features: List[float] = []
@ -107,7 +107,7 @@ class ConstraintFeatures:
_clip(features)
return features
def __getitem__(self, selected: Tuple[bool, ...]) -> "ConstraintFeatures":
def __getitem__(self, selected: List[bool]) -> "ConstraintFeatures":
return ConstraintFeatures(
basis_status=self._filter(self.basis_status, selected),
categories=self._filter(self.categories, selected),
@ -125,12 +125,12 @@ class ConstraintFeatures:
def _filter(
self,
obj: Optional[Tuple],
selected: Tuple[bool, ...],
) -> Optional[Tuple]:
obj: Optional[List],
selected: List[bool],
) -> Optional[List]:
if obj is None:
return None
return tuple(obj[i] for (i, selected_i) in enumerate(selected) if selected_i)
return [obj[i] for (i, selected_i) in enumerate(selected) if selected_i]
@dataclass
@ -217,7 +217,7 @@ class FeaturesExtractor:
if user_features_i is None:
user_features.append(None)
else:
user_features.append(user_features_i)
user_features.append(list(user_features_i))
features.variables.categories = categories
features.variables.user_features = user_features
@ -229,7 +229,7 @@ class FeaturesExtractor:
assert features.constraints is not None
assert features.constraints.names is not None
has_static_lazy = instance.has_static_lazy_constraints()
user_features: List[Optional[Tuple[float, ...]]] = []
user_features: List[Optional[List[float]]] = []
categories: List[Optional[Hashable]] = []
lazy: List[bool] = []
for (cidx, cname) in enumerate(features.constraints.names):
@ -253,7 +253,7 @@ class FeaturesExtractor:
f"Constraint features must be a list of numbers. "
f"Found {type(f).__name__} instead for cname={cname}."
)
user_features.append(tuple(cf))
user_features.append(list(cf))
else:
user_features.append(None)
categories.append(None)
@ -261,9 +261,9 @@ class FeaturesExtractor:
lazy.append(instance.is_constraint_lazy(cname))
else:
lazy.append(False)
features.constraints.user_features = tuple(user_features)
features.constraints.lazy = tuple(lazy)
features.constraints.categories = tuple(categories)
features.constraints.user_features = user_features
features.constraints.lazy = lazy
features.constraints.categories = categories
def _extract_user_features_instance(
self,

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

@ -33,20 +33,20 @@ def sample() -> Sample:
lazy_constraint_count=4,
),
constraints=ConstraintFeatures(
names=("c1", "c2", "c3", "c4", "c5"),
categories=(
names=["c1", "c2", "c3", "c4", "c5"],
categories=[
"type-a",
"type-a",
"type-a",
"type-b",
"type-b",
),
lazy=(True, True, True, True, False),
],
lazy=[True, True, True, True, False],
),
),
after_lp=Features(
instance=InstanceFeatures(),
constraints=ConstraintFeatures(names=("c1", "c2", "c3", "c4", "c5")),
constraints=ConstraintFeatures(names=["c1", "c2", "c3", "c4", "c5"]),
),
after_mip=Features(
extra={
@ -132,7 +132,7 @@ def test_usage_with_solver(instance: Instance) -> None:
# Should ask internal solver to remove some constraints
assert internal.remove_constraints.call_count == 1
internal.remove_constraints.assert_has_calls([call(("c3",))])
internal.remove_constraints.assert_has_calls([call(["c3"])])
# LearningSolver calls after_iteration (first time)
should_repeat = component.iteration_cb(solver, instance, None)
@ -141,7 +141,7 @@ def test_usage_with_solver(instance: Instance) -> None:
# Should ask internal solver to verify if constraints in the pool are
# satisfied and add the ones that are not
assert sample.after_load.constraints is not None
c = sample.after_load.constraints[False, False, True, False, False]
c = sample.after_load.constraints[[False, False, True, False, False]]
internal.are_constraints_satisfied.assert_called_once_with(c, tol=1.0)
internal.are_constraints_satisfied.reset_mock()
internal.add_constraints.assert_called_once_with(c)

@ -65,26 +65,26 @@ def test_knapsack() -> None:
assert_equals(
_round(features.constraints),
ConstraintFeatures(
basis_status=("N",),
categories=("eq_capacity",),
dual_values=(13.538462,),
names=("eq_capacity",),
lazy=(False,),
lhs=(
(
basis_status=["N"],
categories=["eq_capacity"],
dual_values=[13.538462],
names=["eq_capacity"],
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,),
sa_rhs_down=(-24.0,),
sa_rhs_up=(2.0,),
senses=("=",),
slacks=(0.0,),
user_features=((0.0,),),
],
],
rhs=[0.0],
sa_rhs_down=[-24.0],
sa_rhs_up=[2.0],
senses=["="],
slacks=[0.0],
user_features=[[0.0]],
),
)
assert_equals(
@ -98,39 +98,39 @@ def test_knapsack() -> None:
def test_constraint_getindex() -> None:
cf = ConstraintFeatures(
names=("c1", "c2", "c3"),
rhs=(1.0, 2.0, 3.0),
senses=("=", "<", ">"),
lhs=(
(
names=["c1", "c2", "c3"],
rhs=[1.0, 2.0, 3.0],
senses=["=", "<", ">"],
lhs=[
[
("x1", 1.0),
("x2", 1.0),
),
(
],
[
("x2", 2.0),
("x3", 2.0),
),
(
],
[
("x3", 3.0),
("x4", 3.0),
),
),
],
],
)
assert_equals(
cf[True, False, True],
cf[[True, False, True]],
ConstraintFeatures(
names=("c1", "c3"),
rhs=(1.0, 3.0),
senses=("=", ">"),
lhs=(
(
names=["c1", "c3"],
rhs=[1.0, 3.0],
senses=["=", ">"],
lhs=[
[
("x1", 1.0),
("x2", 1.0),
),
(
],
[
("x3", 3.0),
("x4", 3.0),
),
),
],
],
),
)

Loading…
Cancel
Save