From 9ddda7e1e2774336af6fc8608ba41b8fe1e221f5 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Mon, 9 Aug 2021 05:41:01 -0500 Subject: [PATCH] Use np.ndarray for constraint names --- miplearn/features/extractor.py | 10 +++++----- miplearn/solvers/gurobi.py | 2 +- miplearn/solvers/internal.py | 8 ++++---- miplearn/solvers/pyomo/base.py | 4 ++-- miplearn/solvers/tests/__init__.py | 12 ++++++------ tests/components/test_static_lazy.py | 10 +++++----- tests/features/test_extractor.py | 14 ++++++++++---- 7 files changed, 33 insertions(+), 27 deletions(-) diff --git a/miplearn/features/extractor.py b/miplearn/features/extractor.py index 9649991..f4bceaa 100644 --- a/miplearn/features/extractor.py +++ b/miplearn/features/extractor.py @@ -200,11 +200,11 @@ class FeaturesExtractor: ) -> None: has_static_lazy = instance.has_static_lazy_constraints() user_features: List[Optional[List[float]]] = [] - categories: List[Optional[str]] = [] + categories: List[Optional[bytes]] = [] lazy: List[bool] = [] constr_categories_dict = instance.get_constraint_categories() constr_features_dict = instance.get_constraint_features() - constr_names = sample.get_vector("static_constr_names") + constr_names = sample.get_array("static_constr_names") assert constr_names is not None for (cidx, cname) in enumerate(constr_names): @@ -215,8 +215,8 @@ class FeaturesExtractor: user_features.append(None) categories.append(None) continue - assert isinstance(category, str), ( - f"Constraint category must be a string. " + assert isinstance(category, bytes), ( + f"Constraint category must be bytes. " f"Found {type(category).__name__} instead for cname={cname}.", ) categories.append(category) @@ -242,7 +242,7 @@ class FeaturesExtractor: lazy.append(False) sample.put_vector_list("static_constr_features", user_features) sample.put_vector("static_constr_lazy", lazy) - sample.put_vector("static_constr_categories", categories) + sample.put_array("static_constr_categories", np.array(categories, dtype="S")) def _extract_user_features_instance( self, diff --git a/miplearn/solvers/gurobi.py b/miplearn/solvers/gurobi.py index 1dcadc0..c5705e8 100644 --- a/miplearn/solvers/gurobi.py +++ b/miplearn/solvers/gurobi.py @@ -211,7 +211,7 @@ class GurobiSolver(InternalSolver): raise Exception(f"unknown cbasis: {v}") gp_constrs = model.getConstrs() - constr_names = model.getAttr("constrName", gp_constrs) + constr_names = np.array(model.getAttr("constrName", gp_constrs), dtype="S") 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 diff --git a/miplearn/solvers/internal.py b/miplearn/solvers/internal.py index 4f48432..6ea8b2d 100644 --- a/miplearn/solvers/internal.py +++ b/miplearn/solvers/internal.py @@ -72,7 +72,7 @@ class Constraints: dual_values: Optional[np.ndarray] = None lazy: Optional[List[bool]] = None lhs: Optional[List[List[Tuple[bytes, float]]]] = None - names: Optional[List[str]] = None + names: Optional[np.ndarray] = None rhs: Optional[np.ndarray] = None sa_rhs_down: Optional[np.ndarray] = None sa_rhs_up: Optional[np.ndarray] = None @@ -86,7 +86,7 @@ class Constraints: dual_values=sample.get_vector("lp_constr_dual_values"), lazy=sample.get_vector("static_constr_lazy"), # lhs=sample.get_vector("static_constr_lhs"), - names=sample.get_vector("static_constr_names"), + names=sample.get_array("static_constr_names"), rhs=sample.get_vector("static_constr_rhs"), sa_rhs_down=sample.get_vector("lp_constr_sa_rhs_down"), sa_rhs_up=sample.get_vector("lp_constr_sa_rhs_up"), @@ -100,7 +100,7 @@ class Constraints: dual_values=( None if self.dual_values is None else self.dual_values[selected] ), - names=self._filter(self.names, selected), + names=(None if self.names is None else self.names[selected]), lazy=self._filter(self.lazy, selected), lhs=self._filter(self.lhs, selected), rhs=(None if self.rhs is None else self.rhs[selected]), @@ -254,7 +254,7 @@ class InternalSolver(ABC): pass @abstractmethod - def remove_constraints(self, names: List[str]) -> None: + def remove_constraints(self, names: np.ndarray) -> None: """ Removes the given constraints from the model. """ diff --git a/miplearn/solvers/pyomo/base.py b/miplearn/solvers/pyomo/base.py index 4ccfc17..5e35d57 100644 --- a/miplearn/solvers/pyomo/base.py +++ b/miplearn/solvers/pyomo/base.py @@ -96,7 +96,7 @@ class BasePyomoSolver(InternalSolver): else: expr = lhs >= cf.rhs[i] cl = pe.Constraint(expr=expr, name=name) - self.model.add_component(name, cl) + self.model.add_component(name.decode(), cl) self._pyomo_solver.add_constraint(cl) self._cname_to_constr[name] = cl self._termination_condition = "" @@ -233,7 +233,7 @@ class BasePyomoSolver(InternalSolver): _parse_constraint(constr) return Constraints( - names=_none_if_empty(names), + names=_none_if_empty(np.array(names, dtype="S")), rhs=_none_if_empty(np.array(rhs, dtype=float)), senses=_none_if_empty(senses), lhs=_none_if_empty(lhs), diff --git a/miplearn/solvers/tests/__init__.py b/miplearn/solvers/tests/__init__.py index 7c21d69..9524c54 100644 --- a/miplearn/solvers/tests/__init__.py +++ b/miplearn/solvers/tests/__init__.py @@ -53,7 +53,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None: assert_equals( solver.get_constraints(), Constraints( - names=["eq_capacity"], + names=np.array(["eq_capacity"], dtype="S"), rhs=np.array([0.0]), lhs=[ [ @@ -110,7 +110,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None: Constraints( basis_status=["N"], dual_values=np.array([13.538462]), - names=["eq_capacity"], + names=np.array(["eq_capacity"], dtype="S"), sa_rhs_down=np.array([-24.0]), sa_rhs_up=np.array([2.0]), slacks=np.array([0.0]), @@ -153,7 +153,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None: _filter_attrs( solver.get_constraint_attrs(), Constraints( - names=["eq_capacity"], + names=np.array(["eq_capacity"], dtype="S"), slacks=np.array([0.0]), ), ), @@ -161,7 +161,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None: # Build new constraint and verify that it is violated cf = Constraints( - names=["cut"], + names=np.array(["cut"], dtype="S"), lhs=[[(b"x[0]", 1.0)]], rhs=np.array([0.0]), senses=["<"], @@ -175,7 +175,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None: _filter_attrs( solver.get_constraint_attrs(), Constraints( - names=["eq_capacity", "cut"], + names=np.array(["eq_capacity", "cut"], dtype="S"), rhs=np.array([0.0, 0.0]), lhs=[ [ @@ -198,7 +198,7 @@ def run_basic_usage_tests(solver: InternalSolver) -> None: assert_equals(solver.are_constraints_satisfied(cf), [True]) # Remove the new constraint - solver.remove_constraints(["cut"]) + solver.remove_constraints(np.array(["cut"], dtype="S")) # New constraint should no longer affect solution stats = solver.solve() diff --git a/tests/components/test_static_lazy.py b/tests/components/test_static_lazy.py index 385c7cc..1950786 100644 --- a/tests/components/test_static_lazy.py +++ b/tests/components/test_static_lazy.py @@ -32,9 +32,9 @@ def sample() -> Sample: "type-b", ], "static_constr_lazy": [True, True, True, True, False], - "static_constr_names": ["c1", "c2", "c3", "c4", "c5"], + "static_constr_names": np.array(["c1", "c2", "c3", "c4", "c5"], dtype="S"), "static_instance_features": [5.0], - "mip_constr_lazy_enforced": {"c1", "c2", "c4"}, + "mip_constr_lazy_enforced": {b"c1", b"c2", b"c4"}, "lp_constr_features": [ [1.0, 1.0], [1.0, 2.0], @@ -110,7 +110,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([b"c3"])]) # LearningSolver calls after_iteration (first time) should_repeat = component.iteration_cb(solver, instance, None) @@ -142,7 +142,7 @@ def test_usage_with_solver(instance: Instance) -> None: ) # Should update training sample - assert sample.get_set("mip_constr_lazy_enforced") == {"c1", "c2", "c3", "c4"} + assert sample.get_set("mip_constr_lazy_enforced") == {b"c1", b"c2", b"c3", b"c4"} # # Should update stats assert stats["LazyStatic: Removed"] == 1 @@ -170,7 +170,7 @@ def test_sample_predict(sample: Sample) -> None: ] ) pred = comp.sample_predict(sample) - assert pred == ["c1", "c2", "c4"] + assert pred == [b"c1", b"c2", b"c4"] def test_fit_xy() -> None: diff --git a/tests/features/test_extractor.py b/tests/features/test_extractor.py index c5d448e..3203aaf 100644 --- a/tests/features/test_extractor.py +++ b/tests/features/test_extractor.py @@ -53,7 +53,10 @@ def test_knapsack() -> None: ["default", "default", "default", "default", None], ) assert sample.get_vector_list("static_var_features") is not None - assert_equals(sample.get_vector("static_constr_names"), ["eq_capacity"]) + assert_equals( + sample.get_vector("static_constr_names"), + np.array(["eq_capacity"], dtype="S"), + ) # assert_equals( # sample.get_vector("static_constr_lhs"), # [ @@ -69,7 +72,10 @@ def test_knapsack() -> None: assert_equals(sample.get_vector("static_constr_rhs"), [0.0]) assert_equals(sample.get_vector("static_constr_senses"), ["="]) assert_equals(sample.get_vector("static_constr_features"), [None]) - assert_equals(sample.get_vector("static_constr_categories"), ["eq_capacity"]) + assert_equals( + sample.get_vector("static_constr_categories"), + np.array(["eq_capacity"], dtype="S"), + ) assert_equals(sample.get_vector("static_constr_lazy"), [False]) assert_equals(sample.get_vector("static_instance_features"), [67.0, 21.75]) assert_equals(sample.get_scalar("static_constr_lazy_count"), 0) @@ -124,7 +130,7 @@ def test_knapsack() -> None: def test_constraint_getindex() -> None: cf = Constraints( - names=["c1", "c2", "c3"], + names=np.array(["c1", "c2", "c3"], dtype="S"), rhs=np.array([1.0, 2.0, 3.0]), senses=["=", "<", ">"], lhs=[ @@ -145,7 +151,7 @@ def test_constraint_getindex() -> None: assert_equals( cf[[True, False, True]], Constraints( - names=["c1", "c3"], + names=np.array(["c1", "c3"], dtype="S"), rhs=np.array([1.0, 3.0]), senses=["=", ">"], lhs=[