From aeed338837633acd8126448009b3ccdef4b9e8c0 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Mon, 5 Apr 2021 20:12:07 -0500 Subject: [PATCH] Convert ConstraintFeatures to dataclass --- miplearn/components/lazy_static.py | 18 +++++----- miplearn/components/objective.py | 1 + miplearn/features.py | 24 ++++++------- miplearn/types.py | 22 +++++------- tests/components/test_lazy_static.py | 51 ++++++++++++++-------------- tests/test_features.py | 18 +++++----- 6 files changed, 64 insertions(+), 70 deletions(-) diff --git a/miplearn/components/lazy_static.py b/miplearn/components/lazy_static.py index 82ce0ff..a141670 100644 --- a/miplearn/components/lazy_static.py +++ b/miplearn/components/lazy_static.py @@ -3,11 +3,9 @@ # Released under the modified BSD license. See COPYING.md for more details. import logging -import sys -from typing import Dict, Tuple, Optional, List, Hashable, Any, TYPE_CHECKING, Set +from typing import Dict, Tuple, List, Hashable, Any, TYPE_CHECKING, Set import numpy as np -from tqdm.auto import tqdm from miplearn import Classifier from miplearn.classifiers.counting import CountingClassifier @@ -71,7 +69,7 @@ class StaticLazyConstraintsComponent(Component): logger.info("Moving lazy constraints to the pool...") self.pool = {} for (cid, cdict) in features.constraints.items(): - if cdict["Lazy"] and cid not in self.enforced_cids: + if cdict.lazy and cid not in self.enforced_cids: self.pool[cid] = LazyConstraint( cid=cid, obj=solver.internal_solver.extract_constraint(cid), @@ -152,10 +150,10 @@ class StaticLazyConstraintsComponent(Component): x, y = self.sample_xy(features, sample) category_to_cids: Dict[Hashable, List[str]] = {} - for (cid, cdict) in features.constraints.items(): - if "Category" not in cdict or cdict["Category"] is None: + for (cid, cfeatures) in features.constraints.items(): + if cfeatures.category is None: continue - category = cdict["Category"] + category = cfeatures.category if category not in category_to_cids: category_to_cids[category] = [] category_to_cids[category] += [cid] @@ -181,15 +179,15 @@ class StaticLazyConstraintsComponent(Component): x: Dict = {} y: Dict = {} for (cid, cfeatures) in features.constraints.items(): - if not cfeatures["Lazy"]: + if not cfeatures.lazy: continue - category = cfeatures["Category"] + category = cfeatures.category if category is None: continue if category not in x: x[category] = [] y[category] = [] - x[category] += [cfeatures["User features"]] + x[category] += [cfeatures.user_features] if "LazyStatic: Enforced" in sample: if cid in sample["LazyStatic: Enforced"]: y[category] += [[False, True]] diff --git a/miplearn/components/objective.py b/miplearn/components/objective.py index f77da1e..3fe4298 100644 --- a/miplearn/components/objective.py +++ b/miplearn/components/objective.py @@ -78,6 +78,7 @@ class ObjectiveValueComponent(Component): sample: TrainingSample, ) -> Tuple[Dict[Hashable, List[List[float]]], Dict[Hashable, List[List[float]]]]: assert features.instance is not None + assert features.instance.user_features is not None x: Dict[Hashable, List[List[float]]] = {} y: Dict[Hashable, List[List[float]]] = {} f = list(features.instance.user_features) diff --git a/miplearn/features.py b/miplearn/features.py index 3cb2006..438ff2f 100644 --- a/miplearn/features.py +++ b/miplearn/features.py @@ -2,9 +2,9 @@ # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Released under the modified BSD license. See COPYING.md for more details. -import numbers import collections -from typing import TYPE_CHECKING, Dict, Hashable +import numbers +from typing import TYPE_CHECKING, Dict from miplearn.types import ( Features, @@ -87,17 +87,15 @@ class FeaturesExtractor: f"Constraint features must be a list of floats. " f"Found {type(user_features[0]).__name__} instead for cid={cid}." ) - constraints[cid] = { - "RHS": self.solver.get_constraint_rhs(cid), - "LHS": self.solver.get_constraint_lhs(cid), - "Sense": self.solver.get_constraint_sense(cid), - "Category": category, - "User features": user_features, - } + constraints[cid] = ConstraintFeatures( + rhs=self.solver.get_constraint_rhs(cid), + lhs=self.solver.get_constraint_lhs(cid), + sense=self.solver.get_constraint_sense(cid), + category=category, + user_features=user_features, + ) if has_static_lazy: - constraints[cid]["Lazy"] = instance.is_constraint_lazy(cid) - else: - constraints[cid]["Lazy"] = False + constraints[cid].lazy = instance.is_constraint_lazy(cid) return constraints @staticmethod @@ -118,7 +116,7 @@ class FeaturesExtractor: ) lazy_count = 0 for (cid, cdict) in features.constraints.items(): - if cdict["Lazy"]: + if cdict.lazy: lazy_count += 1 return InstanceFeatures( user_features=user_features, diff --git a/miplearn/types.py b/miplearn/types.py index 0809ffe..50adc69 100644 --- a/miplearn/types.py +++ b/miplearn/types.py @@ -81,7 +81,7 @@ LearningSolveStats = TypedDict( @dataclass class InstanceFeatures: - user_features: List[float] + user_features: Optional[List[float]] = None lazy_constraint_count: int = 0 @@ -91,18 +91,14 @@ class VariableFeatures: user_features: Optional[List[float]] = None -ConstraintFeatures = TypedDict( - "ConstraintFeatures", - { - "RHS": float, - "LHS": Dict[str, float], - "Sense": str, - "Category": Optional[Hashable], - "User features": Optional[List[float]], - "Lazy": bool, - }, - total=False, -) +@dataclass +class ConstraintFeatures: + rhs: Optional[float] = None + lhs: Optional[Dict[str, float]] = None + sense: Optional[str] = None + category: Optional[Hashable] = None + user_features: Optional[List[float]] = None + lazy: bool = False @dataclass diff --git a/tests/components/test_lazy_static.py b/tests/components/test_lazy_static.py index 354b68b..dab62a5 100644 --- a/tests/components/test_lazy_static.py +++ b/tests/components/test_lazy_static.py @@ -17,6 +17,7 @@ from miplearn.types import ( Features, LearningSolveStats, InstanceFeatures, + ConstraintFeatures, ) @@ -35,31 +36,31 @@ def features() -> Features: 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, - }, + "c1": ConstraintFeatures( + category="type-a", + user_features=[1.0, 1.0], + lazy=True, + ), + "c2": ConstraintFeatures( + category="type-a", + user_features=[1.0, 2.0], + lazy=True, + ), + "c3": ConstraintFeatures( + category="type-a", + user_features=[1.0, 3.0], + lazy=True, + ), + "c4": ConstraintFeatures( + category="type-b", + user_features=[1.0, 4.0, 0.0], + lazy=True, + ), + "c5": ConstraintFeatures( + category="type-b", + user_features=[1.0, 5.0, 0.0], + lazy=False, + ), }, ) diff --git a/tests/test_features.py b/tests/test_features.py index f035860..60b0b9b 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -4,7 +4,7 @@ from miplearn import GurobiSolver from miplearn.features import FeaturesExtractor -from miplearn.types import VariableFeatures, InstanceFeatures +from miplearn.types import VariableFeatures, InstanceFeatures, ConstraintFeatures from tests.fixtures.knapsack import get_knapsack_instance @@ -36,19 +36,19 @@ def test_knapsack() -> None: } } assert instance.features.constraints == { - "eq_capacity": { - "LHS": { + "eq_capacity": ConstraintFeatures( + lhs={ "x[0]": 23.0, "x[1]": 26.0, "x[2]": 20.0, "x[3]": 18.0, }, - "Sense": "<", - "RHS": 67.0, - "Lazy": False, - "Category": "eq_capacity", - "User features": [0.0], - } + sense="<", + rhs=67.0, + lazy=False, + category="eq_capacity", + user_features=[0.0], + ) } assert instance.features.instance == InstanceFeatures( user_features=[67.0, 21.75],