diff --git a/miplearn/__init__.py b/miplearn/__init__.py index d7e6a9a..daf611e 100644 --- a/miplearn/__init__.py +++ b/miplearn/__init__.py @@ -12,6 +12,8 @@ from .components.branching import BranchPriorityComponent from .extractors import (UserFeaturesExtractor, SolutionExtractor, CombinedExtractor, + InstanceFeaturesExtractor, + ObjectiveValueExtractor, ) from .benchmark import BenchmarkRunner from .instance import Instance diff --git a/miplearn/extractors.py b/miplearn/extractors.py index 2803c7f..08cec37 100644 --- a/miplearn/extractors.py +++ b/miplearn/extractors.py @@ -103,3 +103,28 @@ class CombinedExtractor(Extractor): def extract(self, instances, models): return self.merge([ex.extract(instances, models) for ex in self.extractors]) + + +class InstanceFeaturesExtractor(Extractor): + def extract(self, instances, models=None): + return np.vstack([ + np.hstack([ + instance.get_instance_features(), + instance.lp_value, + ]) + for instance in instances + ]) + + +class ObjectiveValueExtractor(Extractor): + def __init__(self, kind="lp"): + assert kind in ["lower bound", "upper bound", "lp"] + self.kind = kind + + def extract(self, instances, models=None): + if self.kind == "lower bound": + return np.array([[instance.lower_bound] for instance in instances]) + if self.kind == "upper bound": + return np.array([[instance.upper_bound] for instance in instances]) + if self.kind == "lp": + return np.array([[instance.lp_value] for instance in instances]) diff --git a/miplearn/tests/test_extractors.py b/miplearn/tests/test_extractors.py index 915a602..9a172b3 100644 --- a/miplearn/tests/test_extractors.py +++ b/miplearn/tests/test_extractors.py @@ -7,13 +7,14 @@ from miplearn import (LearningSolver, UserFeaturesExtractor, SolutionExtractor, CombinedExtractor, + InstanceFeaturesExtractor ) import numpy as np import pyomo.environ as pe def _get_instances(): - return [ + instances = [ KnapsackInstance(weights=[1., 2., 3.], prices=[10., 20., 30.], capacity=2.5, @@ -23,10 +24,15 @@ def _get_instances(): capacity=4.5, ), ] + models = [instance.to_model() for instance in instances] + solver = LearningSolver() + for (i, instance) in enumerate(instances): + solver.solve(instances[i], models[i]) + return instances, models -def test_user_features(): - instances = _get_instances() +def test_user_features_extractor(): + instances, models = _get_instances() extractor = UserFeaturesExtractor() features = extractor.extract(instances) assert isinstance(features, dict) @@ -36,12 +42,7 @@ def test_user_features(): def test_solution_extractor(): - instances = _get_instances() - models = [instance.to_model() for instance in instances] - solver = LearningSolver() - for (i, instance) in enumerate(instances): - solver.solve(instances[i], models[i]) - + instances, models = _get_instances() features = SolutionExtractor().extract(instances, models) assert isinstance(features, dict) assert "default" in features.keys() @@ -58,12 +59,7 @@ def test_solution_extractor(): def test_combined_extractor(): - instances = _get_instances() - models = [instance.to_model() for instance in instances] - solver = LearningSolver() - for (i, instance) in enumerate(instances): - solver.solve(instances[i], models[i]) - + instances, models = _get_instances() extractor = CombinedExtractor(extractors=[UserFeaturesExtractor(), SolutionExtractor()]) features = extractor.extract(instances, models) @@ -72,3 +68,8 @@ def test_combined_extractor(): assert isinstance(features["default"], np.ndarray) assert features["default"].shape == (6, 6) + +def test_instance_features_extractor(): + instances, models = _get_instances() + features = InstanceFeaturesExtractor().extract(instances) + assert features.shape == (2,3) \ No newline at end of file