You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MIPLearn/tests/components/test_primal.py

233 lines
6.4 KiB

# 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
import numpy as np
from numpy.testing import assert_array_equal
from scipy.stats import randint
from miplearn import Classifier, LearningSolver, GurobiSolver, GurobiPyomoSolver
from miplearn.classifiers.threshold import Threshold
from miplearn.components.primal import PrimalSolutionComponent
from miplearn.problems.tsp import TravelingSalesmanGenerator
from miplearn.types import TrainingSample, Features
from tests.fixtures.knapsack import get_knapsack_instance
def test_xy_sample_with_lp_solution() -> None:
features: Features = {
"Variables": {
"x": {
0: {
"Category": "default",
"User features": [0.0, 0.0],
},
1: {
"Category": None,
},
2: {
"Category": "default",
"User features": [1.0, 0.0],
},
3: {
"Category": "default",
"User features": [1.0, 1.0],
},
}
}
}
sample: TrainingSample = {
"Solution": {
"x": {
0: 0.0,
1: 1.0,
2: 1.0,
3: 0.0,
}
},
"LP solution": {
"x": {
0: 0.1,
1: 0.1,
2: 0.1,
3: 0.1,
}
},
}
x_expected = {
"default": [
[0.0, 0.0, 0.1],
[1.0, 0.0, 0.1],
[1.0, 1.0, 0.1],
]
}
y_expected = {
"default": [
[True, False],
[False, True],
[True, False],
]
}
xy = PrimalSolutionComponent.xy_sample(features, sample)
assert xy is not None
x_actual, y_actual = xy
assert x_actual == x_expected
assert y_actual == y_expected
def test_xy_sample_without_lp_solution() -> None:
features: Features = {
"Variables": {
"x": {
0: {
"Category": "default",
"User features": [0.0, 0.0],
},
1: {
"Category": None,
},
2: {
"Category": "default",
"User features": [1.0, 0.0],
},
3: {
"Category": "default",
"User features": [1.0, 1.0],
},
}
}
}
sample: TrainingSample = {
"Solution": {
"x": {
0: 0.0,
1: 1.0,
2: 1.0,
3: 0.0,
}
},
}
x_expected = {
"default": [
[0.0, 0.0],
[1.0, 0.0],
[1.0, 1.0],
]
}
y_expected = {
"default": [
[True, False],
[False, True],
[True, False],
]
}
xy = PrimalSolutionComponent.xy_sample(features, sample)
assert xy is not None
x_actual, y_actual = xy
assert x_actual == x_expected
assert y_actual == y_expected
def test_predict() -> None:
clf = Mock(spec=Classifier)
clf.predict_proba = Mock(
return_value=np.array(
[
[0.9, 0.1],
[0.5, 0.5],
[0.1, 0.9],
]
)
)
thr = Mock(spec=Threshold)
thr.predict = Mock(return_value=[0.75, 0.75])
features: Features = {
"Variables": {
"x": {
0: {
"Category": "default",
"User features": [0.0, 0.0],
},
1: {
"Category": "default",
"User features": [0.0, 2.0],
},
2: {
"Category": "default",
"User features": [2.0, 0.0],
},
}
}
}
sample: TrainingSample = {
"LP solution": {
"x": {
0: 0.1,
1: 0.5,
2: 0.9,
}
}
}
x = PrimalSolutionComponent.x_sample(features, sample)
comp = PrimalSolutionComponent()
comp.classifiers = {"default": clf}
comp.thresholds = {"default": thr}
solution_actual = comp.predict(features, sample)
clf.predict_proba.assert_called_once()
assert_array_equal(x["default"], clf.predict_proba.call_args[0][0])
thr.predict.assert_called_once()
assert_array_equal(x["default"], thr.predict.call_args[0][0])
assert solution_actual == {
"x": {
0: 0.0,
1: None,
2: 1.0,
}
}
def test_fit_xy():
clf = Mock(spec=Classifier)
clf.clone = lambda: Mock(spec=Classifier)
thr = Mock(spec=Threshold)
thr.clone = lambda: Mock(spec=Threshold)
comp = PrimalSolutionComponent(classifier=clf, threshold=thr)
x = {
"type-a": np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]),
"type-b": np.array([[7.0, 8.0, 9.0]]),
}
y = {
"type-a": np.array([[True, False], [False, True]]),
"type-b": np.array([[True, False]]),
}
comp.fit_xy(x, y)
for category in ["type-a", "type-b"]:
assert category in comp.classifiers
assert category in comp.thresholds
clf = comp.classifiers[category]
clf.fit.assert_called_once()
assert_array_equal(x[category], clf.fit.call_args[0][0])
assert_array_equal(y[category], clf.fit.call_args[0][1])
thr = comp.thresholds[category]
thr.fit.assert_called_once()
assert_array_equal(x[category], thr.fit.call_args[0][1])
assert_array_equal(y[category], thr.fit.call_args[0][2])
def test_usage():
solver = LearningSolver(
components=[
PrimalSolutionComponent(),
]
)
gen = TravelingSalesmanGenerator(n=randint(low=5, high=6))
instance = gen.generate(1)[0]
solver.solve(instance)
solver.fit([instance])
stats = solver.solve(instance)
assert stats["Primal: free"] == 0
assert stats["Primal: one"] + stats["Primal: zero"] == 10
assert stats["Lower bound"] == stats["Warm start value"]