Module miplearn.components.tests.test_lazy_static
Expand source code
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
from unittest.mock import Mock, call
from miplearn.classifiers import Classifier
from miplearn.components.lazy_static import StaticLazyConstraintsComponent
from miplearn.instance import Instance
from miplearn.solvers.internal import InternalSolver
from miplearn.solvers.learning import LearningSolver
def test_usage_with_solver():
solver = Mock(spec=LearningSolver)
solver.use_lazy_cb = False
solver.gap_tolerance = 1e-4
internal = solver.internal_solver = Mock(spec=InternalSolver)
internal.get_constraint_ids = Mock(return_value=["c1", "c2", "c3", "c4"])
internal.extract_constraint = Mock(side_effect=lambda cid: "<%s>" % cid)
internal.is_constraint_satisfied = Mock(return_value=False)
instance = Mock(spec=Instance)
instance.has_static_lazy_constraints = Mock(return_value=True)
instance.is_constraint_lazy = Mock(
side_effect=lambda cid: {
"c1": False,
"c2": True,
"c3": True,
"c4": True,
}[cid]
)
instance.get_constraint_features = Mock(
side_effect=lambda cid: {
"c2": [1.0, 0.0],
"c3": [0.5, 0.5],
"c4": [1.0],
}[cid]
)
instance.get_constraint_category = Mock(
side_effect=lambda cid: {
"c2": "type-a",
"c3": "type-a",
"c4": "type-b",
}[cid]
)
component = StaticLazyConstraintsComponent(
threshold=0.90,
use_two_phase_gap=False,
violation_tolerance=1.0,
)
component.classifiers = {
"type-a": Mock(spec=Classifier),
"type-b": Mock(spec=Classifier),
}
component.classifiers["type-a"].predict_proba = Mock(
return_value=[
[0.20, 0.80],
[0.05, 0.95],
]
)
component.classifiers["type-b"].predict_proba = Mock(
return_value=[
[0.02, 0.98],
]
)
# LearningSolver calls before_solve
component.before_solve(solver, instance, None)
# Should ask if instance has static lazy constraints
instance.has_static_lazy_constraints.assert_called_once()
# Should ask internal solver for a list of constraints in the model
internal.get_constraint_ids.assert_called_once()
# Should ask if each constraint in the model is lazy
instance.is_constraint_lazy.assert_has_calls(
[
call("c1"),
call("c2"),
call("c3"),
call("c4"),
]
)
# For the lazy ones, should ask for features
instance.get_constraint_features.assert_has_calls(
[
call("c2"),
call("c3"),
call("c4"),
]
)
# Should also ask for categories
assert instance.get_constraint_category.call_count == 3
instance.get_constraint_category.assert_has_calls(
[
call("c2"),
call("c3"),
call("c4"),
]
)
# Should ask internal solver to remove constraints identified as lazy
assert internal.extract_constraint.call_count == 3
internal.extract_constraint.assert_has_calls(
[
call("c2"),
call("c3"),
call("c4"),
]
)
# Should ask ML to predict whether each lazy constraint should be enforced
component.classifiers["type-a"].predict_proba.assert_called_once_with(
[[1.0, 0.0], [0.5, 0.5]]
)
component.classifiers["type-b"].predict_proba.assert_called_once_with([[1.0]])
# For the ones that should be enforced, should ask solver to re-add them
# to the formulation. The remaining ones should remain in the pool.
assert internal.add_constraint.call_count == 2
internal.add_constraint.assert_has_calls(
[
call("<c3>"),
call("<c4>"),
]
)
internal.add_constraint.reset_mock()
# LearningSolver calls after_iteration (first time)
should_repeat = component.iteration_cb(solver, instance, None)
assert should_repeat
# Should ask internal solver to verify if constraints in the pool are
# satisfied and add the ones that are not
internal.is_constraint_satisfied.assert_called_once_with("<c2>", tol=1.0)
internal.is_constraint_satisfied.reset_mock()
internal.add_constraint.assert_called_once_with("<c2>")
internal.add_constraint.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.assert_not_called()
internal.add_constraint.assert_not_called()
# Should update instance object
assert instance.found_violated_lazy_constraints == ["c3", "c4", "c2"]
def test_fit():
instance_1 = Mock(spec=Instance)
instance_1.found_violated_lazy_constraints = ["c1", "c2", "c4", "c5"]
instance_1.get_constraint_category = Mock(
side_effect=lambda cid: {
"c1": "type-a",
"c2": "type-a",
"c3": "type-a",
"c4": "type-b",
"c5": "type-b",
}[cid]
)
instance_1.get_constraint_features = Mock(
side_effect=lambda cid: {
"c1": [1, 1],
"c2": [1, 2],
"c3": [1, 3],
"c4": [1, 4, 0],
"c5": [1, 5, 0],
}[cid]
)
instance_2 = Mock(spec=Instance)
instance_2.found_violated_lazy_constraints = ["c2", "c3", "c4"]
instance_2.get_constraint_category = Mock(
side_effect=lambda cid: {
"c1": "type-a",
"c2": "type-a",
"c3": "type-a",
"c4": "type-b",
"c5": "type-b",
}[cid]
)
instance_2.get_constraint_features = Mock(
side_effect=lambda cid: {
"c1": [2, 1],
"c2": [2, 2],
"c3": [2, 3],
"c4": [2, 4, 0],
"c5": [2, 5, 0],
}[cid]
)
instances = [instance_1, instance_2]
component = StaticLazyConstraintsComponent()
component.classifiers = {
"type-a": Mock(spec=Classifier),
"type-b": Mock(spec=Classifier),
}
expected_constraints = {
"type-a": ["c1", "c2", "c3"],
"type-b": ["c4", "c5"],
}
expected_x = {
"type-a": [[1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3]],
"type-b": [[1, 4, 0], [1, 5, 0], [2, 4, 0], [2, 5, 0]],
}
expected_y = {
"type-a": [[0, 1], [0, 1], [1, 0], [1, 0], [0, 1], [0, 1]],
"type-b": [[0, 1], [0, 1], [0, 1], [1, 0]],
}
assert component._collect_constraints(instances) == expected_constraints
assert component.x(instances) == expected_x
assert component.y(instances) == expected_y
component.fit(instances)
component.classifiers["type-a"].fit.assert_called_once_with(
expected_x["type-a"],
expected_y["type-a"],
)
component.classifiers["type-b"].fit.assert_called_once_with(
expected_x["type-b"],
expected_y["type-b"],
)
Functions
def test_fit()
-
Expand source code
def test_fit(): instance_1 = Mock(spec=Instance) instance_1.found_violated_lazy_constraints = ["c1", "c2", "c4", "c5"] instance_1.get_constraint_category = Mock( side_effect=lambda cid: { "c1": "type-a", "c2": "type-a", "c3": "type-a", "c4": "type-b", "c5": "type-b", }[cid] ) instance_1.get_constraint_features = Mock( side_effect=lambda cid: { "c1": [1, 1], "c2": [1, 2], "c3": [1, 3], "c4": [1, 4, 0], "c5": [1, 5, 0], }[cid] ) instance_2 = Mock(spec=Instance) instance_2.found_violated_lazy_constraints = ["c2", "c3", "c4"] instance_2.get_constraint_category = Mock( side_effect=lambda cid: { "c1": "type-a", "c2": "type-a", "c3": "type-a", "c4": "type-b", "c5": "type-b", }[cid] ) instance_2.get_constraint_features = Mock( side_effect=lambda cid: { "c1": [2, 1], "c2": [2, 2], "c3": [2, 3], "c4": [2, 4, 0], "c5": [2, 5, 0], }[cid] ) instances = [instance_1, instance_2] component = StaticLazyConstraintsComponent() component.classifiers = { "type-a": Mock(spec=Classifier), "type-b": Mock(spec=Classifier), } expected_constraints = { "type-a": ["c1", "c2", "c3"], "type-b": ["c4", "c5"], } expected_x = { "type-a": [[1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3]], "type-b": [[1, 4, 0], [1, 5, 0], [2, 4, 0], [2, 5, 0]], } expected_y = { "type-a": [[0, 1], [0, 1], [1, 0], [1, 0], [0, 1], [0, 1]], "type-b": [[0, 1], [0, 1], [0, 1], [1, 0]], } assert component._collect_constraints(instances) == expected_constraints assert component.x(instances) == expected_x assert component.y(instances) == expected_y component.fit(instances) component.classifiers["type-a"].fit.assert_called_once_with( expected_x["type-a"], expected_y["type-a"], ) component.classifiers["type-b"].fit.assert_called_once_with( expected_x["type-b"], expected_y["type-b"], )
def test_usage_with_solver()
-
Expand source code
def test_usage_with_solver(): solver = Mock(spec=LearningSolver) solver.use_lazy_cb = False solver.gap_tolerance = 1e-4 internal = solver.internal_solver = Mock(spec=InternalSolver) internal.get_constraint_ids = Mock(return_value=["c1", "c2", "c3", "c4"]) internal.extract_constraint = Mock(side_effect=lambda cid: "<%s>" % cid) internal.is_constraint_satisfied = Mock(return_value=False) instance = Mock(spec=Instance) instance.has_static_lazy_constraints = Mock(return_value=True) instance.is_constraint_lazy = Mock( side_effect=lambda cid: { "c1": False, "c2": True, "c3": True, "c4": True, }[cid] ) instance.get_constraint_features = Mock( side_effect=lambda cid: { "c2": [1.0, 0.0], "c3": [0.5, 0.5], "c4": [1.0], }[cid] ) instance.get_constraint_category = Mock( side_effect=lambda cid: { "c2": "type-a", "c3": "type-a", "c4": "type-b", }[cid] ) component = StaticLazyConstraintsComponent( threshold=0.90, use_two_phase_gap=False, violation_tolerance=1.0, ) component.classifiers = { "type-a": Mock(spec=Classifier), "type-b": Mock(spec=Classifier), } component.classifiers["type-a"].predict_proba = Mock( return_value=[ [0.20, 0.80], [0.05, 0.95], ] ) component.classifiers["type-b"].predict_proba = Mock( return_value=[ [0.02, 0.98], ] ) # LearningSolver calls before_solve component.before_solve(solver, instance, None) # Should ask if instance has static lazy constraints instance.has_static_lazy_constraints.assert_called_once() # Should ask internal solver for a list of constraints in the model internal.get_constraint_ids.assert_called_once() # Should ask if each constraint in the model is lazy instance.is_constraint_lazy.assert_has_calls( [ call("c1"), call("c2"), call("c3"), call("c4"), ] ) # For the lazy ones, should ask for features instance.get_constraint_features.assert_has_calls( [ call("c2"), call("c3"), call("c4"), ] ) # Should also ask for categories assert instance.get_constraint_category.call_count == 3 instance.get_constraint_category.assert_has_calls( [ call("c2"), call("c3"), call("c4"), ] ) # Should ask internal solver to remove constraints identified as lazy assert internal.extract_constraint.call_count == 3 internal.extract_constraint.assert_has_calls( [ call("c2"), call("c3"), call("c4"), ] ) # Should ask ML to predict whether each lazy constraint should be enforced component.classifiers["type-a"].predict_proba.assert_called_once_with( [[1.0, 0.0], [0.5, 0.5]] ) component.classifiers["type-b"].predict_proba.assert_called_once_with([[1.0]]) # For the ones that should be enforced, should ask solver to re-add them # to the formulation. The remaining ones should remain in the pool. assert internal.add_constraint.call_count == 2 internal.add_constraint.assert_has_calls( [ call("<c3>"), call("<c4>"), ] ) internal.add_constraint.reset_mock() # LearningSolver calls after_iteration (first time) should_repeat = component.iteration_cb(solver, instance, None) assert should_repeat # Should ask internal solver to verify if constraints in the pool are # satisfied and add the ones that are not internal.is_constraint_satisfied.assert_called_once_with("<c2>", tol=1.0) internal.is_constraint_satisfied.reset_mock() internal.add_constraint.assert_called_once_with("<c2>") internal.add_constraint.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.assert_not_called() internal.add_constraint.assert_not_called() # Should update instance object assert instance.found_violated_lazy_constraints == ["c3", "c4", "c2"]