mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-06 09:28:51 -06:00
Remove sample.after_load
This commit is contained in:
@@ -52,6 +52,8 @@ class DynamicConstraintsComponent(Component):
|
|||||||
cids: Dict[Hashable, List[str]] = {}
|
cids: Dict[Hashable, List[str]] = {}
|
||||||
constr_categories_dict = instance.get_constraint_categories()
|
constr_categories_dict = instance.get_constraint_categories()
|
||||||
constr_features_dict = instance.get_constraint_features()
|
constr_features_dict = instance.get_constraint_features()
|
||||||
|
instance_features = sample.get("instance_features_user")
|
||||||
|
assert instance_features is not None
|
||||||
for cid in self.known_cids:
|
for cid in self.known_cids:
|
||||||
# Initialize categories
|
# Initialize categories
|
||||||
if cid in constr_categories_dict:
|
if cid in constr_categories_dict:
|
||||||
@@ -66,10 +68,8 @@ class DynamicConstraintsComponent(Component):
|
|||||||
cids[category] = []
|
cids[category] = []
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
features = []
|
features: List[float] = []
|
||||||
assert sample.after_load is not None
|
features.extend(instance_features)
|
||||||
assert sample.after_load.instance is not None
|
|
||||||
features.extend(sample.after_load.instance.to_list())
|
|
||||||
if cid in constr_features_dict:
|
if cid in constr_features_dict:
|
||||||
features.extend(constr_features_dict[cid])
|
features.extend(constr_features_dict[cid])
|
||||||
for ci in features:
|
for ci in features:
|
||||||
|
|||||||
@@ -103,8 +103,10 @@ class PrimalSolutionComponent(Component):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def sample_predict(self, sample: Sample) -> Solution:
|
def sample_predict(self, sample: Sample) -> Solution:
|
||||||
assert sample.after_load is not None
|
var_names = sample.get("var_names")
|
||||||
assert sample.after_load.variables is not None
|
var_categories = sample.get("var_categories")
|
||||||
|
assert var_names is not None
|
||||||
|
assert var_categories is not None
|
||||||
|
|
||||||
# Compute y_pred
|
# Compute y_pred
|
||||||
x, _ = self.sample_xy(None, sample)
|
x, _ = self.sample_xy(None, sample)
|
||||||
@@ -125,12 +127,10 @@ class PrimalSolutionComponent(Component):
|
|||||||
).T
|
).T
|
||||||
|
|
||||||
# Convert y_pred into solution
|
# Convert y_pred into solution
|
||||||
assert sample.after_load.variables.names is not None
|
solution: Solution = {v: None for v in var_names}
|
||||||
assert sample.after_load.variables.categories is not None
|
|
||||||
solution: Solution = {v: None for v in sample.after_load.variables.names}
|
|
||||||
category_offset: Dict[Hashable, int] = {cat: 0 for cat in x.keys()}
|
category_offset: Dict[Hashable, int] = {cat: 0 for cat in x.keys()}
|
||||||
for (i, var_name) in enumerate(sample.after_load.variables.names):
|
for (i, var_name) in enumerate(var_names):
|
||||||
category = sample.after_load.variables.categories[i]
|
category = var_categories[i]
|
||||||
if category not in category_offset:
|
if category not in category_offset:
|
||||||
continue
|
continue
|
||||||
offset = category_offset[category]
|
offset = category_offset[category]
|
||||||
@@ -150,24 +150,21 @@ class PrimalSolutionComponent(Component):
|
|||||||
) -> Tuple[Dict[Category, List[List[float]]], Dict[Category, List[List[float]]]]:
|
) -> Tuple[Dict[Category, List[List[float]]], Dict[Category, List[List[float]]]]:
|
||||||
x: Dict = {}
|
x: Dict = {}
|
||||||
y: Dict = {}
|
y: Dict = {}
|
||||||
assert sample.after_load is not None
|
|
||||||
assert sample.after_load.instance is not None
|
|
||||||
assert sample.after_load.variables is not None
|
|
||||||
assert sample.after_load.variables.names is not None
|
|
||||||
assert sample.after_load.variables.categories is not None
|
|
||||||
|
|
||||||
instance_features = sample.get("instance_features_user")
|
instance_features = sample.get("instance_features_user")
|
||||||
mip_var_values = sample.get("mip_var_values")
|
mip_var_values = sample.get("mip_var_values")
|
||||||
var_features = sample.get("lp_var_features")
|
var_features = sample.get("lp_var_features")
|
||||||
|
var_names = sample.get("var_names")
|
||||||
|
var_categories = sample.get("var_categories")
|
||||||
if var_features is None:
|
if var_features is None:
|
||||||
var_features = sample.get("var_features")
|
var_features = sample.get("var_features")
|
||||||
|
|
||||||
assert instance_features is not None
|
assert instance_features is not None
|
||||||
assert var_features is not None
|
assert var_features is not None
|
||||||
|
assert var_names is not None
|
||||||
|
assert var_categories is not None
|
||||||
|
|
||||||
for (i, var_name) in enumerate(sample.after_load.variables.names):
|
for (i, var_name) in enumerate(var_names):
|
||||||
# Initialize categories
|
# Initialize categories
|
||||||
category = sample.after_load.variables.categories[i]
|
category = var_categories[i]
|
||||||
if category is None:
|
if category is None:
|
||||||
continue
|
continue
|
||||||
if category not in x.keys():
|
if category not in x.keys():
|
||||||
|
|||||||
@@ -74,16 +74,15 @@ class StaticLazyConstraintsComponent(Component):
|
|||||||
sample: Sample,
|
sample: Sample,
|
||||||
) -> None:
|
) -> None:
|
||||||
assert solver.internal_solver is not None
|
assert solver.internal_solver is not None
|
||||||
assert sample.after_load is not None
|
static_lazy_count = sample.get("static_lazy_count")
|
||||||
assert sample.after_load.instance is not None
|
assert static_lazy_count is not None
|
||||||
|
|
||||||
logger.info("Predicting violated (static) lazy constraints...")
|
logger.info("Predicting violated (static) lazy constraints...")
|
||||||
if sample.after_load.instance.lazy_constraint_count == 0:
|
if static_lazy_count == 0:
|
||||||
logger.info("Instance does not have static lazy constraints. Skipping.")
|
logger.info("Instance does not have static lazy constraints. Skipping.")
|
||||||
self.enforced_cids = set(self.sample_predict(sample))
|
self.enforced_cids = set(self.sample_predict(sample))
|
||||||
logger.info("Moving lazy constraints to the pool...")
|
logger.info("Moving lazy constraints to the pool...")
|
||||||
constraints = sample.after_load.constraints
|
constraints = ConstraintFeatures.from_sample(sample)
|
||||||
assert constraints is not None
|
|
||||||
assert constraints.lazy is not None
|
assert constraints.lazy is not None
|
||||||
assert constraints.names is not None
|
assert constraints.names is not None
|
||||||
selected = [
|
selected = [
|
||||||
|
|||||||
@@ -82,9 +82,9 @@ class ConstraintFeatures:
|
|||||||
basis_status: Optional[List[str]] = None
|
basis_status: Optional[List[str]] = None
|
||||||
categories: Optional[List[Optional[Hashable]]] = None
|
categories: Optional[List[Optional[Hashable]]] = None
|
||||||
dual_values: Optional[List[float]] = None
|
dual_values: Optional[List[float]] = None
|
||||||
names: Optional[List[str]] = None
|
|
||||||
lazy: Optional[List[bool]] = None
|
lazy: Optional[List[bool]] = None
|
||||||
lhs: Optional[List[List[Tuple[str, float]]]] = None
|
lhs: Optional[List[List[Tuple[str, float]]]] = None
|
||||||
|
names: Optional[List[str]] = None
|
||||||
rhs: Optional[List[float]] = None
|
rhs: Optional[List[float]] = None
|
||||||
sa_rhs_down: Optional[List[float]] = None
|
sa_rhs_down: Optional[List[float]] = None
|
||||||
sa_rhs_up: Optional[List[float]] = None
|
sa_rhs_up: Optional[List[float]] = None
|
||||||
@@ -92,6 +92,23 @@ class ConstraintFeatures:
|
|||||||
slacks: Optional[List[float]] = None
|
slacks: Optional[List[float]] = None
|
||||||
user_features: Optional[List[Optional[List[float]]]] = None
|
user_features: Optional[List[Optional[List[float]]]] = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_sample(sample: "Sample") -> "ConstraintFeatures":
|
||||||
|
return ConstraintFeatures(
|
||||||
|
basis_status=sample.get("lp_constr_basis_status"),
|
||||||
|
categories=sample.get("constr_categories"),
|
||||||
|
dual_values=sample.get("lp_constr_dual_values"),
|
||||||
|
lazy=sample.get("constr_lazy"),
|
||||||
|
lhs=sample.get("constr_lhs"),
|
||||||
|
names=sample.get("constr_names"),
|
||||||
|
rhs=sample.get("constr_rhs"),
|
||||||
|
sa_rhs_down=sample.get("lp_constr_sa_rhs_down"),
|
||||||
|
sa_rhs_up=sample.get("lp_constr_sa_rhs_up"),
|
||||||
|
senses=sample.get("constr_senses"),
|
||||||
|
slacks=sample.get("lp_constr_slacks"),
|
||||||
|
user_features=sample.get("constr_features_user"),
|
||||||
|
)
|
||||||
|
|
||||||
def to_list(self, index: int) -> List[float]:
|
def to_list(self, index: int) -> List[float]:
|
||||||
features: List[float] = []
|
features: List[float] = []
|
||||||
for attr in [
|
for attr in [
|
||||||
@@ -146,13 +163,11 @@ class Features:
|
|||||||
class Sample:
|
class Sample:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
after_load: Optional[Features] = None,
|
|
||||||
data: Optional[Dict[str, Any]] = None,
|
data: Optional[Dict[str, Any]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if data is None:
|
if data is None:
|
||||||
data = {}
|
data = {}
|
||||||
self._data: Dict[str, Any] = data
|
self._data: Dict[str, Any] = data
|
||||||
self.after_load = after_load
|
|
||||||
|
|
||||||
def get(self, key: str) -> Optional[Any]:
|
def get(self, key: str) -> Optional[Any]:
|
||||||
if key in self._data:
|
if key in self._data:
|
||||||
|
|||||||
@@ -176,7 +176,6 @@ class LearningSolver:
|
|||||||
"Features (after-load) extracted in %.2f seconds"
|
"Features (after-load) extracted in %.2f seconds"
|
||||||
% (time.time() - initial_time)
|
% (time.time() - initial_time)
|
||||||
)
|
)
|
||||||
sample.after_load = features
|
|
||||||
|
|
||||||
callback_args = (
|
callback_args = (
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -27,16 +27,18 @@ def training_instances() -> List[Instance]:
|
|||||||
instances = [cast(Instance, Mock(spec=Instance)) for _ in range(2)]
|
instances = [cast(Instance, Mock(spec=Instance)) for _ in range(2)]
|
||||||
samples_0 = [
|
samples_0 = [
|
||||||
Sample(
|
Sample(
|
||||||
after_load=Features(instance=InstanceFeatures()),
|
{
|
||||||
data={"lazy_enforced": {"c1", "c2"}},
|
"lazy_enforced": {"c1", "c2"},
|
||||||
|
"instance_features_user": [5.0],
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Sample(
|
Sample(
|
||||||
after_load=Features(instance=InstanceFeatures()),
|
{
|
||||||
data={"lazy_enforced": {"c2", "c3"}},
|
"lazy_enforced": {"c2", "c3"},
|
||||||
|
"instance_features_user": [5.0],
|
||||||
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
samples_0[0].after_load.instance.to_list = Mock(return_value=[5.0]) # type: ignore
|
|
||||||
samples_0[1].after_load.instance.to_list = Mock(return_value=[5.0]) # type: ignore
|
|
||||||
instances[0].get_samples = Mock(return_value=samples_0) # type: ignore
|
instances[0].get_samples = Mock(return_value=samples_0) # type: ignore
|
||||||
instances[0].get_constraint_categories = Mock( # type: ignore
|
instances[0].get_constraint_categories = Mock( # type: ignore
|
||||||
return_value={
|
return_value={
|
||||||
@@ -56,11 +58,12 @@ def training_instances() -> List[Instance]:
|
|||||||
)
|
)
|
||||||
samples_1 = [
|
samples_1 = [
|
||||||
Sample(
|
Sample(
|
||||||
after_load=Features(instance=InstanceFeatures()),
|
{
|
||||||
data={"lazy_enforced": {"c3", "c4"}},
|
"lazy_enforced": {"c3", "c4"},
|
||||||
|
"instance_features_user": [8.0],
|
||||||
|
},
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
samples_1[0].after_load.instance.to_list = Mock(return_value=[8.0]) # type: ignore
|
|
||||||
instances[1].get_samples = Mock(return_value=samples_1) # type: ignore
|
instances[1].get_samples = Mock(return_value=samples_1) # type: ignore
|
||||||
instances[1].get_constraint_categories = Mock( # type: ignore
|
instances[1].get_constraint_categories = Mock( # type: ignore
|
||||||
return_value={
|
return_value={
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ from numpy.testing import assert_array_equal
|
|||||||
|
|
||||||
from miplearn.classifiers import Regressor
|
from miplearn.classifiers import Regressor
|
||||||
from miplearn.components.objective import ObjectiveValueComponent
|
from miplearn.components.objective import ObjectiveValueComponent
|
||||||
from miplearn.features import InstanceFeatures, Features, Sample
|
from miplearn.features import Sample
|
||||||
from miplearn.solvers.internal import MIPSolveStats, LPSolveStats
|
|
||||||
from miplearn.solvers.learning import LearningSolver
|
from miplearn.solvers.learning import LearningSolver
|
||||||
from miplearn.solvers.pyomo.gurobi import GurobiPyomoSolver
|
from miplearn.solvers.pyomo.gurobi import GurobiPyomoSolver
|
||||||
|
|
||||||
@@ -19,7 +18,7 @@ from miplearn.solvers.pyomo.gurobi import GurobiPyomoSolver
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def sample() -> Sample:
|
def sample() -> Sample:
|
||||||
sample = Sample(
|
sample = Sample(
|
||||||
data={
|
{
|
||||||
"mip_lower_bound": 1.0,
|
"mip_lower_bound": 1.0,
|
||||||
"mip_upper_bound": 2.0,
|
"mip_upper_bound": 2.0,
|
||||||
"lp_instance_features": [1.0, 2.0, 3.0],
|
"lp_instance_features": [1.0, 2.0, 3.0],
|
||||||
|
|||||||
@@ -26,14 +26,7 @@ from miplearn.solvers.tests import assert_equals
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def sample() -> Sample:
|
def sample() -> Sample:
|
||||||
sample = Sample(
|
sample = Sample(
|
||||||
after_load=Features(
|
{
|
||||||
instance=InstanceFeatures(),
|
|
||||||
variables=VariableFeatures(
|
|
||||||
names=["x[0]", "x[1]", "x[2]", "x[3]"],
|
|
||||||
categories=["default", None, "default", "default"],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
data={
|
|
||||||
"var_names": ["x[0]", "x[1]", "x[2]", "x[3]"],
|
"var_names": ["x[0]", "x[1]", "x[2]", "x[3]"],
|
||||||
"var_categories": ["default", None, "default", "default"],
|
"var_categories": ["default", None, "default", "default"],
|
||||||
"mip_var_values": [0.0, 1.0, 1.0, 0.0],
|
"mip_var_values": [0.0, 1.0, 1.0, 0.0],
|
||||||
@@ -52,15 +45,6 @@ def sample() -> Sample:
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
sample.after_load.instance.to_list = Mock(return_value=[5.0]) # type: ignore
|
|
||||||
sample.after_load.variables.to_list = Mock( # type:ignore
|
|
||||||
side_effect=lambda i: [
|
|
||||||
[0.0, 0.0],
|
|
||||||
None,
|
|
||||||
[1.0, 0.0],
|
|
||||||
[1.0, 1.0],
|
|
||||||
][i]
|
|
||||||
)
|
|
||||||
return sample
|
return sample
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,23 +28,7 @@ from miplearn.types import (
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def sample() -> Sample:
|
def sample() -> Sample:
|
||||||
sample = Sample(
|
sample = Sample(
|
||||||
after_load=Features(
|
{
|
||||||
instance=InstanceFeatures(
|
|
||||||
lazy_constraint_count=4,
|
|
||||||
),
|
|
||||||
constraints=ConstraintFeatures(
|
|
||||||
names=["c1", "c2", "c3", "c4", "c5"],
|
|
||||||
categories=[
|
|
||||||
"type-a",
|
|
||||||
"type-a",
|
|
||||||
"type-a",
|
|
||||||
"type-b",
|
|
||||||
"type-b",
|
|
||||||
],
|
|
||||||
lazy=[True, True, True, True, False],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
data={
|
|
||||||
"constr_categories": [
|
"constr_categories": [
|
||||||
"type-a",
|
"type-a",
|
||||||
"type-a",
|
"type-a",
|
||||||
@@ -139,9 +123,7 @@ def test_usage_with_solver(instance: Instance) -> None:
|
|||||||
|
|
||||||
# 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
|
||||||
assert sample.after_load is not None
|
c = ConstraintFeatures.from_sample(sample)[[False, False, True, False, False]]
|
||||||
assert sample.after_load.constraints is not None
|
|
||||||
c = sample.after_load.constraints[[False, False, True, False, False]]
|
|
||||||
internal.are_constraints_satisfied.assert_called_once_with(c, tol=1.0)
|
internal.are_constraints_satisfied.assert_called_once_with(c, tol=1.0)
|
||||||
internal.are_constraints_satisfied.reset_mock()
|
internal.are_constraints_satisfied.reset_mock()
|
||||||
internal.add_constraints.assert_called_once_with(c)
|
internal.add_constraints.assert_called_once_with(c)
|
||||||
|
|||||||
Reference in New Issue
Block a user