|
|
@ -1,144 +1,119 @@
|
|
|
|
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
|
|
|
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
|
|
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
|
|
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
|
|
|
# Released under the modified BSD license. See COPYING.md for more details.
|
|
|
|
# Released under the modified BSD license. See COPYING.md for more details.
|
|
|
|
|
|
|
|
from typing import Dict, cast, Hashable
|
|
|
|
from unittest.mock import Mock, call
|
|
|
|
from unittest.mock import Mock, call
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
from numpy.testing import assert_array_equal
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from miplearn import LearningSolver, InternalSolver, Instance
|
|
|
|
from miplearn.classifiers import Classifier
|
|
|
|
from miplearn.classifiers import Classifier
|
|
|
|
from miplearn.components.lazy_static import StaticLazyConstraintsComponent
|
|
|
|
from miplearn.components.lazy_static import StaticLazyConstraintsComponent
|
|
|
|
from miplearn.instance import Instance
|
|
|
|
from miplearn.types import TrainingSample, Features, LearningSolveStats
|
|
|
|
from miplearn.solvers.internal import InternalSolver
|
|
|
|
|
|
|
|
from miplearn.solvers.learning import LearningSolver
|
|
|
|
|
|
|
|
from miplearn.types import TrainingSample, Features
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
|
|
def sample() -> TrainingSample:
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
"LazyStatic: Enforced": {"c1", "c2", "c4"},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
|
|
def features() -> Features:
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
"Instance": {
|
|
|
|
|
|
|
|
"Lazy constraint count": 4,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
"Constraints": {
|
|
|
|
|
|
|
|
"c1": {
|
|
|
|
|
|
|
|
"Category": "type-a",
|
|
|
|
|
|
|
|
"User features": [1.0, 1.0],
|
|
|
|
|
|
|
|
"Lazy": True,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
"c2": {
|
|
|
|
|
|
|
|
"Category": "type-a",
|
|
|
|
|
|
|
|
"User features": [1.0, 2.0],
|
|
|
|
|
|
|
|
"Lazy": True,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
"c3": {
|
|
|
|
|
|
|
|
"Category": "type-a",
|
|
|
|
|
|
|
|
"User features": [1.0, 3.0],
|
|
|
|
|
|
|
|
"Lazy": True,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
"c4": {
|
|
|
|
|
|
|
|
"Category": "type-b",
|
|
|
|
|
|
|
|
"User features": [1.0, 4.0, 0.0],
|
|
|
|
|
|
|
|
"Lazy": True,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
"c5": {
|
|
|
|
|
|
|
|
"Category": "type-b",
|
|
|
|
|
|
|
|
"User features": [1.0, 5.0, 0.0],
|
|
|
|
|
|
|
|
"Lazy": False,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_usage_with_solver():
|
|
|
|
def test_usage_with_solver(features: Features) -> None:
|
|
|
|
solver = Mock(spec=LearningSolver)
|
|
|
|
solver = Mock(spec=LearningSolver)
|
|
|
|
solver.use_lazy_cb = False
|
|
|
|
solver.use_lazy_cb = False
|
|
|
|
solver.gap_tolerance = 1e-4
|
|
|
|
solver.gap_tolerance = 1e-4
|
|
|
|
|
|
|
|
|
|
|
|
internal = solver.internal_solver = Mock(spec=InternalSolver)
|
|
|
|
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.extract_constraint = Mock(side_effect=lambda cid: "<%s>" % cid)
|
|
|
|
internal.is_constraint_satisfied = Mock(return_value=False)
|
|
|
|
internal.is_constraint_satisfied = Mock(return_value=False)
|
|
|
|
|
|
|
|
|
|
|
|
instance = Mock(spec=Instance)
|
|
|
|
instance = Mock(spec=Instance)
|
|
|
|
instance.has_static_lazy_constraints = Mock(return_value=True)
|
|
|
|
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(
|
|
|
|
component = StaticLazyConstraintsComponent(
|
|
|
|
threshold=0.90,
|
|
|
|
threshold=0.50,
|
|
|
|
use_two_phase_gap=False,
|
|
|
|
|
|
|
|
violation_tolerance=1.0,
|
|
|
|
violation_tolerance=1.0,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
component.classifiers = {
|
|
|
|
component.classifiers = {
|
|
|
|
"type-a": Mock(spec=Classifier),
|
|
|
|
"type-a": Mock(spec=Classifier),
|
|
|
|
"type-b": Mock(spec=Classifier),
|
|
|
|
"type-b": Mock(spec=Classifier),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
component.classifiers["type-a"].predict_proba = Mock(
|
|
|
|
component.classifiers["type-a"].predict_proba = Mock( # type: ignore
|
|
|
|
return_value=[
|
|
|
|
return_value=np.array(
|
|
|
|
[0.20, 0.80],
|
|
|
|
[
|
|
|
|
[0.05, 0.95],
|
|
|
|
[0.00, 1.00], # c1
|
|
|
|
]
|
|
|
|
[0.20, 0.80], # c2
|
|
|
|
)
|
|
|
|
[0.99, 0.01], # c3
|
|
|
|
component.classifiers["type-b"].predict_proba = Mock(
|
|
|
|
]
|
|
|
|
return_value=[
|
|
|
|
)
|
|
|
|
[0.02, 0.98],
|
|
|
|
)
|
|
|
|
]
|
|
|
|
component.classifiers["type-b"].predict_proba = Mock( # type: ignore
|
|
|
|
)
|
|
|
|
return_value=np.array(
|
|
|
|
|
|
|
|
[
|
|
|
|
# LearningSolver calls before_solve
|
|
|
|
[0.02, 0.98], # c4
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sample: TrainingSample = {}
|
|
|
|
|
|
|
|
stats: LearningSolveStats = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# LearningSolver calls before_solve_mip
|
|
|
|
component.before_solve_mip(
|
|
|
|
component.before_solve_mip(
|
|
|
|
solver=solver,
|
|
|
|
solver=solver,
|
|
|
|
instance=instance,
|
|
|
|
instance=instance,
|
|
|
|
model=None,
|
|
|
|
model=None,
|
|
|
|
stats=None,
|
|
|
|
stats=stats,
|
|
|
|
features=None,
|
|
|
|
features=features,
|
|
|
|
training_data=None,
|
|
|
|
training_data=sample,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
# Should ask ML to predict whether each lazy constraint should be enforced
|
|
|
|
component.classifiers["type-a"].predict_proba.assert_called_once_with(
|
|
|
|
component.classifiers["type-a"].predict_proba.assert_called_once()
|
|
|
|
[[1.0, 0.0], [0.5, 0.5]]
|
|
|
|
component.classifiers["type-b"].predict_proba.assert_called_once()
|
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
# Should ask internal solver to remove some constraints
|
|
|
|
# to the formulation. The remaining ones should remain in the pool.
|
|
|
|
assert internal.extract_constraint.call_count == 1
|
|
|
|
assert internal.add_constraint.call_count == 2
|
|
|
|
internal.extract_constraint.assert_has_calls([call("c3")])
|
|
|
|
internal.add_constraint.assert_has_calls(
|
|
|
|
|
|
|
|
[
|
|
|
|
|
|
|
|
call("<c3>"),
|
|
|
|
|
|
|
|
call("<c4>"),
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
internal.add_constraint.reset_mock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# LearningSolver calls after_iteration (first time)
|
|
|
|
# LearningSolver calls after_iteration (first time)
|
|
|
|
should_repeat = component.iteration_cb(solver, instance, None)
|
|
|
|
should_repeat = component.iteration_cb(solver, instance, None)
|
|
|
@ -146,9 +121,9 @@ def test_usage_with_solver():
|
|
|
|
|
|
|
|
|
|
|
|
# Should ask internal solver to verify if constraints in the pool are
|
|
|
|
# Should ask internal solver to verify if constraints in the pool are
|
|
|
|
# satisfied and add the ones that are not
|
|
|
|
# satisfied and add the ones that are not
|
|
|
|
internal.is_constraint_satisfied.assert_called_once_with("<c2>", tol=1.0)
|
|
|
|
internal.is_constraint_satisfied.assert_called_once_with("<c3>", tol=1.0)
|
|
|
|
internal.is_constraint_satisfied.reset_mock()
|
|
|
|
internal.is_constraint_satisfied.reset_mock()
|
|
|
|
internal.add_constraint.assert_called_once_with("<c2>")
|
|
|
|
internal.add_constraint.assert_called_once_with("<c3>")
|
|
|
|
internal.add_constraint.reset_mock()
|
|
|
|
internal.add_constraint.reset_mock()
|
|
|
|
|
|
|
|
|
|
|
|
# LearningSolver calls after_iteration (second time)
|
|
|
|
# LearningSolver calls after_iteration (second time)
|
|
|
@ -159,139 +134,88 @@ def test_usage_with_solver():
|
|
|
|
internal.is_constraint_satisfied.assert_not_called()
|
|
|
|
internal.is_constraint_satisfied.assert_not_called()
|
|
|
|
internal.add_constraint.assert_not_called()
|
|
|
|
internal.add_constraint.assert_not_called()
|
|
|
|
|
|
|
|
|
|
|
|
# Should update instance object
|
|
|
|
# LearningSolver calls after_solve_mip
|
|
|
|
assert instance.found_violated_lazy_constraints == ["c3", "c4", "c2"]
|
|
|
|
component.after_solve_mip(
|
|
|
|
|
|
|
|
solver=solver,
|
|
|
|
|
|
|
|
instance=instance,
|
|
|
|
def test_fit():
|
|
|
|
model=None,
|
|
|
|
instance_1 = Mock(spec=Instance)
|
|
|
|
stats=stats,
|
|
|
|
instance_1.found_violated_lazy_constraints = ["c1", "c2", "c4", "c5"]
|
|
|
|
features=features,
|
|
|
|
instance_1.get_constraint_category = Mock(
|
|
|
|
training_data=sample,
|
|
|
|
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)
|
|
|
|
# Should update training sample
|
|
|
|
instance_2.found_violated_lazy_constraints = ["c2", "c3", "c4"]
|
|
|
|
assert sample["LazyStatic: Enforced"] == {"c1", "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]
|
|
|
|
# Should update stats
|
|
|
|
component = StaticLazyConstraintsComponent()
|
|
|
|
assert stats["LazyStatic: Removed"] == 1
|
|
|
|
component.classifiers = {
|
|
|
|
assert stats["LazyStatic: Kept"] == 3
|
|
|
|
"type-a": Mock(spec=Classifier),
|
|
|
|
assert stats["LazyStatic: Restored"] == 1
|
|
|
|
"type-b": Mock(spec=Classifier),
|
|
|
|
assert stats["LazyStatic: Iterations"] == 1
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
def test_sample_predict(
|
|
|
|
component.classifiers["type-a"].fit.assert_called_once_with(
|
|
|
|
features: Features,
|
|
|
|
expected_x["type-a"],
|
|
|
|
sample: TrainingSample,
|
|
|
|
expected_y["type-a"],
|
|
|
|
) -> None:
|
|
|
|
|
|
|
|
comp = StaticLazyConstraintsComponent(threshold=0.5)
|
|
|
|
|
|
|
|
comp.classifiers["type-a"] = Mock(spec=Classifier)
|
|
|
|
|
|
|
|
comp.classifiers["type-a"].predict_proba = lambda _: np.array( # type:ignore
|
|
|
|
|
|
|
|
[
|
|
|
|
|
|
|
|
[0.0, 1.0], # c1
|
|
|
|
|
|
|
|
[0.0, 0.9], # c2
|
|
|
|
|
|
|
|
[0.9, 0.1], # c3
|
|
|
|
|
|
|
|
]
|
|
|
|
)
|
|
|
|
)
|
|
|
|
component.classifiers["type-b"].fit.assert_called_once_with(
|
|
|
|
comp.classifiers["type-b"] = Mock(spec=Classifier)
|
|
|
|
expected_x["type-b"],
|
|
|
|
comp.classifiers["type-b"].predict_proba = lambda _: np.array( # type:ignore
|
|
|
|
expected_y["type-b"],
|
|
|
|
[
|
|
|
|
|
|
|
|
[0.0, 1.0], # c4
|
|
|
|
|
|
|
|
]
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
pred = comp.sample_predict(features, sample)
|
|
|
|
|
|
|
|
assert pred == ["c1", "c2", "c4"]
|
|
|
|
def test_xy_sample() -> None:
|
|
|
|
|
|
|
|
sample: TrainingSample = {
|
|
|
|
|
|
|
|
"LazyStatic: Enforced": {"c1", "c2", "c4"},
|
|
|
|
def test_fit_xy() -> None:
|
|
|
|
}
|
|
|
|
x = cast(
|
|
|
|
features: Features = {
|
|
|
|
Dict[Hashable, np.ndarray],
|
|
|
|
"Constraints": {
|
|
|
|
{
|
|
|
|
"c1": {
|
|
|
|
"type-a": np.array([[1.0, 1.0], [1.0, 2.0], [1.0, 3.0]]),
|
|
|
|
"Category": "type-a",
|
|
|
|
"type-b": np.array([[1.0, 4.0, 0.0]]),
|
|
|
|
"User features": [1.0, 1.0],
|
|
|
|
},
|
|
|
|
"Lazy": True,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
y = cast(
|
|
|
|
"c2": {
|
|
|
|
Dict[Hashable, np.ndarray],
|
|
|
|
"Category": "type-a",
|
|
|
|
{
|
|
|
|
"User features": [1.0, 2.0],
|
|
|
|
"type-a": np.array([[False, True], [False, True], [True, False]]),
|
|
|
|
"Lazy": True,
|
|
|
|
"type-b": np.array([[False, True]]),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"c3": {
|
|
|
|
)
|
|
|
|
"Category": "type-a",
|
|
|
|
clf = Mock(spec=Classifier)
|
|
|
|
"User features": [1.0, 3.0],
|
|
|
|
clf.clone = Mock(side_effect=lambda: Mock(spec=Classifier))
|
|
|
|
"Lazy": True,
|
|
|
|
comp = StaticLazyConstraintsComponent(classifier=clf)
|
|
|
|
},
|
|
|
|
comp.fit_xy(x, y)
|
|
|
|
"c4": {
|
|
|
|
assert clf.clone.call_count == 2
|
|
|
|
"Category": "type-b",
|
|
|
|
clf_a = comp.classifiers["type-a"]
|
|
|
|
"User features": [1.0, 4.0, 0.0],
|
|
|
|
clf_b = comp.classifiers["type-b"]
|
|
|
|
"Lazy": True,
|
|
|
|
assert clf_a.fit.call_count == 1 # type: ignore
|
|
|
|
},
|
|
|
|
assert clf_b.fit.call_count == 1 # type: ignore
|
|
|
|
"c5": {
|
|
|
|
assert_array_equal(clf_a.fit.call_args[0][0], x["type-a"]) # type: ignore
|
|
|
|
"Category": "type-b",
|
|
|
|
assert_array_equal(clf_b.fit.call_args[0][0], x["type-b"]) # type: ignore
|
|
|
|
"User features": [1.0, 5.0, 0.0],
|
|
|
|
|
|
|
|
"Lazy": False,
|
|
|
|
|
|
|
|
},
|
|
|
|
def test_sample_xy(
|
|
|
|
}
|
|
|
|
features: Features,
|
|
|
|
}
|
|
|
|
sample: TrainingSample,
|
|
|
|
|
|
|
|
) -> None:
|
|
|
|
x_expected = {
|
|
|
|
x_expected = {
|
|
|
|
"type-a": [
|
|
|
|
"type-a": [[1.0, 1.0], [1.0, 2.0], [1.0, 3.0]],
|
|
|
|
[1.0, 1.0],
|
|
|
|
"type-b": [[1.0, 4.0, 0.0]],
|
|
|
|
[1.0, 2.0],
|
|
|
|
|
|
|
|
[1.0, 3.0],
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
"type-b": [
|
|
|
|
|
|
|
|
[1.0, 4.0, 0.0],
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
y_expected = {
|
|
|
|
y_expected = {
|
|
|
|
"type-a": [
|
|
|
|
"type-a": [[False, True], [False, True], [True, False]],
|
|
|
|
[False, True],
|
|
|
|
"type-b": [[False, True]],
|
|
|
|
[False, True],
|
|
|
|
|
|
|
|
[True, False],
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
"type-b": [
|
|
|
|
|
|
|
|
[False, True],
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
xy = StaticLazyConstraintsComponent.sample_xy(features, sample)
|
|
|
|
xy = StaticLazyConstraintsComponent.sample_xy(features, sample)
|
|
|
|
assert xy is not None
|
|
|
|
assert xy is not None
|
|
|
|