diff --git a/miplearn/components/component.py b/miplearn/components/component.py index 714ec4e..1c3687c 100644 --- a/miplearn/components/component.py +++ b/miplearn/components/component.py @@ -136,8 +136,8 @@ class Component(ABC): ) -> None: return + @staticmethod def xy( - self, instance: Any, training_sample: TrainingSample, ) -> Tuple[Dict, Dict]: diff --git a/miplearn/components/objective.py b/miplearn/components/objective.py index a70d243..a49c961 100644 --- a/miplearn/components/objective.py +++ b/miplearn/components/objective.py @@ -3,7 +3,7 @@ # Released under the modified BSD license. See COPYING.md for more details. import logging -from typing import List, Dict, Union, Callable, Optional, Any, TYPE_CHECKING +from typing import List, Dict, Union, Callable, Optional, Any, TYPE_CHECKING, Tuple import numpy as np from sklearn.linear_model import LinearRegression @@ -161,3 +161,21 @@ class ObjectiveValueComponent(Component): }, } return ev + + @staticmethod + def xy( + instance: Any, + sample: TrainingSample, + ) -> Tuple[Dict, Dict]: + x: Dict = {} + y: Dict = {} + if "Lower bound" not in sample: + return x, y + features = instance.get_instance_features() + if "LP value" in sample and sample["LP value"] is not None: + features += [sample["LP value"]] + x["Lower bound"] = [features] + x["Upper bound"] = [features] + y["Lower bound"] = [[sample["Lower bound"]]] + y["Upper bound"] = [[sample["Upper bound"]]] + return x, y diff --git a/miplearn/components/primal.py b/miplearn/components/primal.py index 357e59c..d2b3377 100644 --- a/miplearn/components/primal.py +++ b/miplearn/components/primal.py @@ -297,8 +297,8 @@ class PrimalSolutionComponent(Component): ) return [opt_value < 0.5, opt_value > 0.5] + @staticmethod def xy( - self, instance: Any, sample: TrainingSample, ) -> Tuple[Dict, Dict]: diff --git a/tests/components/test_objective.py b/tests/components/test_objective.py index 0093df0..88865e3 100644 --- a/tests/components/test_objective.py +++ b/tests/components/test_objective.py @@ -11,9 +11,33 @@ from numpy.testing import assert_array_equal from miplearn.instance import Instance from miplearn.classifiers import Regressor from miplearn.components.objective import ObjectiveValueComponent +from miplearn.types import TrainingSample from tests.fixtures.knapsack import get_test_pyomo_instances +def test_xy() -> None: + instance = cast(Instance, Mock(spec=Instance)) + instance.get_instance_features = Mock( # type: ignore + return_value=[1.0, 2.0], + ) + sample: TrainingSample = { + "Lower bound": 1.0, + "Upper bound": 2.0, + "LP value": 3.0, + } + x_expected = { + "Lower bound": [[1.0, 2.0, 3.0]], + "Upper bound": [[1.0, 2.0, 3.0]], + } + y_expected = { + "Lower bound": [[1.0]], + "Upper bound": [[2.0]], + } + x_actual, y_actual = ObjectiveValueComponent.xy(instance, sample) + assert x_actual == x_expected + assert y_actual == y_expected + + def test_x_y_predict() -> None: # Construct instance instance = cast(Instance, Mock(spec=Instance)) diff --git a/tests/components/test_primal.py b/tests/components/test_primal.py index c8a13db..cb53fab 100644 --- a/tests/components/test_primal.py +++ b/tests/components/test_primal.py @@ -69,7 +69,7 @@ def test_xy_with_lp_solution() -> None: ] ) } - x_actual, y_actual = comp.xy(instance, sample) + x_actual, y_actual = PrimalSolutionComponent.xy(instance, sample) assert len(x_actual.keys()) == 1 assert len(y_actual.keys()) == 1 assert_array_equal(x_actual["default"], x_expected["default"])