Refactor StaticLazy; remove old constraint methods

This commit is contained in:
2021-05-15 14:15:48 -05:00
parent 53d3e9d98a
commit 91c8db2225
10 changed files with 343 additions and 583 deletions

View File

@@ -16,6 +16,7 @@ from miplearn.features import (
Features,
Constraint,
Sample,
ConstraintFeatures,
)
from miplearn.instance.base import Instance
from miplearn.solvers.internal import InternalSolver
@@ -32,23 +33,21 @@ def sample() -> Sample:
instance=InstanceFeatures(
lazy_constraint_count=4,
),
constraints_old={
"c1": Constraint(category="type-a", lazy=True),
"c2": Constraint(category="type-a", lazy=True),
"c3": Constraint(category="type-a", lazy=True),
"c4": Constraint(category="type-b", lazy=True),
"c5": Constraint(category="type-b", lazy=False),
},
constraints=ConstraintFeatures(
names=("c1", "c2", "c3", "c4", "c5"),
categories=(
"type-a",
"type-a",
"type-a",
"type-b",
"type-b",
),
lazy=(True, True, True, True, False),
),
),
after_lp=Features(
instance=InstanceFeatures(),
constraints_old={
"c1": Constraint(),
"c2": Constraint(),
"c3": Constraint(),
"c4": Constraint(),
"c5": Constraint(),
},
constraints=ConstraintFeatures(names=("c1", "c2", "c3", "c4", "c5")),
),
after_mip=Features(
extra={
@@ -57,17 +56,14 @@ def sample() -> Sample:
),
)
sample.after_lp.instance.to_list = Mock(return_value=[5.0]) # type: ignore
sample.after_lp.constraints_old["c1"].to_list = Mock( # type: ignore
return_value=[1.0, 1.0]
)
sample.after_lp.constraints_old["c2"].to_list = Mock( # type: ignore
return_value=[1.0, 2.0]
)
sample.after_lp.constraints_old["c3"].to_list = Mock( # type: ignore
return_value=[1.0, 3.0]
)
sample.after_lp.constraints_old["c4"].to_list = Mock( # type: ignore
return_value=[1.0, 4.0, 0.0]
sample.after_lp.constraints.to_list = Mock( # type: ignore
side_effect=lambda idx: {
0: [1.0, 1.0],
1: [1.0, 2.0],
2: [1.0, 3.0],
3: [1.0, 4.0, 0.0],
4: None,
}[idx]
)
return sample
@@ -87,6 +83,9 @@ def test_usage_with_solver(instance: Instance) -> None:
internal = solver.internal_solver = Mock(spec=InternalSolver)
internal.is_constraint_satisfied_old = Mock(return_value=False)
internal.are_constraints_satisfied = Mock(
side_effect=lambda cf, tol=1.0: [False for i in range(len(cf.names))]
)
component = StaticLazyConstraintsComponent(violation_tolerance=1.0)
component.thresholds["type-a"] = MinProbabilityThreshold([0.5, 0.5])
@@ -115,7 +114,6 @@ def test_usage_with_solver(instance: Instance) -> None:
stats: LearningSolveStats = {}
sample = instance.samples[0]
assert sample.after_load is not None
assert sample.after_load.constraints_old is not None
assert sample.after_mip is not None
assert sample.after_mip.extra is not None
del sample.after_mip.extra["lazy_enforced"]
@@ -134,8 +132,8 @@ def test_usage_with_solver(instance: Instance) -> None:
component.classifiers["type-b"].predict_proba.assert_called_once()
# Should ask internal solver to remove some constraints
assert internal.remove_constraint.call_count == 1
internal.remove_constraint.assert_has_calls([call("c3")])
assert internal.remove_constraints.call_count == 1
internal.remove_constraints.assert_has_calls([call(("c3",))])
# LearningSolver calls after_iteration (first time)
should_repeat = component.iteration_cb(solver, instance, None)
@@ -143,19 +141,20 @@ 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
c3 = sample.after_load.constraints_old["c3"]
internal.is_constraint_satisfied_old.assert_called_once_with(c3, tol=1.0)
internal.is_constraint_satisfied_old.reset_mock()
internal.add_constraint.assert_called_once_with(c3, name="c3")
internal.add_constraint.reset_mock()
assert sample.after_load.constraints is not None
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)
internal.add_constraints.reset_mock()
# LearningSolver calls after_iteration (second time)
should_repeat = component.iteration_cb(solver, instance, None)
assert not should_repeat
# The lazy constraint pool should be empty by now, so no calls should be made
internal.is_constraint_satisfied_old.assert_not_called()
internal.add_constraint.assert_not_called()
internal.are_constraints_satisfied.assert_not_called()
internal.add_constraints.assert_not_called()
# LearningSolver calls after_solve_mip
component.after_solve_mip(

View File

@@ -50,7 +50,6 @@ def test_redundancy() -> None:
solver.relax_constraints(["c1"])
stats = solver.solve_lp()
assert stats.lp_value == 2.0
assert solver.is_constraint_satisfied(["c1"]) == [False]
solver.enforce_constraints(["c1"])
stats = solver.solve_lp()

View File

@@ -28,7 +28,6 @@ def test_knapsack() -> None:
features = FeaturesExtractor().extract(instance, solver)
assert features.variables is not None
assert features.constraints_old is not None
assert features.instance is not None
assert_equals(
@@ -66,22 +65,29 @@ def test_knapsack() -> None:
),
)
assert_equals(
_round_constraints(features.constraints_old),
{
"eq_capacity": Constraint(
basis_status="N",
category="eq_capacity",
dual_value=13.538462,
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=1.9999999999999987,
sense="=",
slack=0.0,
user_features=[0.0],
)
},
_round(features.constraints),
ConstraintFeatures(
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,),),
),
)
assert_equals(
features.instance,