From c8c29138cae7189c3954be2c19b6456aa102e1ba Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Tue, 6 Jul 2021 17:04:32 -0500 Subject: [PATCH] Remove unused classes and functions --- miplearn/features.py | 295 +------------------------- miplearn/solvers/learning.py | 1 - tests/components/test_dynamic_lazy.py | 6 +- tests/components/test_primal.py | 7 +- tests/components/test_static_lazy.py | 7 +- tests/test_features.py | 10 - 6 files changed, 4 insertions(+), 322 deletions(-) diff --git a/miplearn/features.py b/miplearn/features.py index c12b426..1927d9c 100644 --- a/miplearn/features.py +++ b/miplearn/features.py @@ -4,7 +4,6 @@ import collections import numbers -from copy import copy from dataclasses import dataclass from math import log, isfinite from typing import TYPE_CHECKING, Dict, Optional, List, Hashable, Tuple, Any @@ -12,28 +11,14 @@ from typing import TYPE_CHECKING, Dict, Optional, List, Hashable, Tuple, Any import numpy as np if TYPE_CHECKING: - from miplearn.solvers.internal import InternalSolver, LPSolveStats, MIPSolveStats + from miplearn.solvers.internal import InternalSolver from miplearn.instance.base import Instance -@dataclass -class InstanceFeatures: - user_features: Optional[List[float]] = None - lazy_constraint_count: int = 0 - - def to_list(self) -> List[float]: - features: List[float] = [] - if self.user_features is not None: - features.extend(self.user_features) - _clip(features) - return features - - @dataclass class VariableFeatures: names: Optional[List[str]] = None basis_status: Optional[List[str]] = None - categories: Optional[List[Optional[Hashable]]] = None lower_bounds: Optional[List[float]] = None obj_coeffs: Optional[List[float]] = None reduced_costs: Optional[List[float]] = None @@ -45,42 +30,12 @@ class VariableFeatures: sa_ub_up: Optional[List[float]] = None types: Optional[List[str]] = None upper_bounds: Optional[List[float]] = None - user_features: Optional[List[Optional[List[float]]]] = None values: Optional[List[float]] = None - # Alvarez, A. M., Louveaux, Q., & Wehenkel, L. (2017). A machine learning-based - # approximation of strong branching. INFORMS Journal on Computing, 29(1), 185-195. - alvarez_2017: Optional[List[List[float]]] = None - - def to_list(self, index: int) -> List[float]: - features: List[float] = [] - for attr in [ - "lower_bounds", - "obj_coeffs", - "reduced_costs", - "sa_lb_down", - "sa_lb_up", - "sa_obj_down", - "sa_obj_up", - "sa_ub_down", - "sa_ub_up", - "upper_bounds", - "values", - ]: - if getattr(self, attr) is not None: - features.append(getattr(self, attr)[index]) - for attr in ["user_features", "alvarez_2017"]: - if getattr(self, attr) is not None: - if getattr(self, attr)[index] is not None: - features.extend(getattr(self, attr)[index]) - _clip(features) - return features - @dataclass class ConstraintFeatures: basis_status: Optional[List[str]] = None - categories: Optional[List[Optional[Hashable]]] = None dual_values: Optional[List[float]] = None lazy: Optional[List[bool]] = None lhs: Optional[List[List[Tuple[str, float]]]] = None @@ -90,13 +45,11 @@ class ConstraintFeatures: sa_rhs_up: Optional[List[float]] = None senses: Optional[List[str]] = None slacks: 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"), @@ -106,29 +59,11 @@ class ConstraintFeatures: 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]: - features: List[float] = [] - for attr in [ - "dual_values", - "rhs", - "slacks", - ]: - if getattr(self, attr) is not None: - features.append(getattr(self, attr)[index]) - for attr in ["user_features"]: - if getattr(self, attr) is not None: - if getattr(self, attr)[index] is not None: - features.extend(getattr(self, attr)[index]) - _clip(features) - return features - def __getitem__(self, selected: List[bool]) -> "ConstraintFeatures": return ConstraintFeatures( basis_status=self._filter(self.basis_status, selected), - categories=self._filter(self.categories, selected), dual_values=self._filter(self.dual_values, selected), names=self._filter(self.names, selected), lazy=self._filter(self.lazy, selected), @@ -138,7 +73,6 @@ class ConstraintFeatures: sa_rhs_up=self._filter(self.sa_rhs_up, selected), senses=self._filter(self.senses, selected), slacks=self._filter(self.slacks, selected), - user_features=self._filter(self.user_features, selected), ) def _filter( @@ -151,15 +85,6 @@ class ConstraintFeatures: return [obj[i] for (i, selected_i) in enumerate(selected) if selected_i] -@dataclass -class Features: - instance: Optional[InstanceFeatures] = None - variables: Optional[VariableFeatures] = None - constraints: Optional[ConstraintFeatures] = None - lp_solve: Optional["LPSolveStats"] = None - mip_solve: Optional["MIPSolveStats"] = None - - class Sample: def __init__( self, @@ -300,29 +225,6 @@ class FeaturesExtractor: sample.put("mip_var_values", variables.values) sample.put("mip_constr_slacks", constraints.slacks) - def extract( - self, - instance: "Instance", - solver: "InternalSolver", - with_static: bool = True, - ) -> Features: - features = Features() - features.variables = solver.get_variables( - with_static=with_static, - with_sa=self.with_sa, - ) - features.constraints = solver.get_constraints( - with_static=with_static, - with_sa=self.with_sa, - with_lhs=self.with_lhs, - ) - if with_static: - self._extract_user_features_vars_old(instance, features) - self._extract_user_features_constrs_old(instance, features) - self._extract_user_features_instance_old(instance, features) - self._extract_alvarez_2017_old(features) - return features - def _extract_user_features_vars( self, instance: "Instance", @@ -417,101 +319,6 @@ class FeaturesExtractor: sample.put("constr_lazy", lazy) sample.put("constr_categories", categories) - def _extract_user_features_vars_old( - self, - instance: "Instance", - features: Features, - ) -> None: - assert features.variables is not None - assert features.variables.names is not None - categories: List[Optional[Hashable]] = [] - user_features: List[Optional[List[float]]] = [] - var_features_dict = instance.get_variable_features() - var_categories_dict = instance.get_variable_categories() - - for (i, var_name) in enumerate(features.variables.names): - if var_name not in var_categories_dict: - user_features.append(None) - categories.append(None) - continue - category: Hashable = var_categories_dict[var_name] - assert isinstance(category, collections.Hashable), ( - f"Variable category must be be hashable. " - f"Found {type(category).__name__} instead for var={var_name}." - ) - categories.append(category) - user_features_i: Optional[List[float]] = None - if var_name in var_features_dict: - user_features_i = var_features_dict[var_name] - if isinstance(user_features_i, np.ndarray): - user_features_i = user_features_i.tolist() - assert isinstance(user_features_i, list), ( - f"Variable features must be a list. " - f"Found {type(user_features_i).__name__} instead for " - f"var={var_name}." - ) - for v in user_features_i: - assert isinstance(v, numbers.Real), ( - f"Variable features must be a list of numbers. " - f"Found {type(v).__name__} instead " - f"for var={var_name}." - ) - user_features_i = list(user_features_i) - user_features.append(user_features_i) - features.variables.categories = categories - features.variables.user_features = user_features - - def _extract_user_features_constrs_old( - self, - instance: "Instance", - features: Features, - ) -> None: - assert features.constraints is not None - assert features.constraints.names is not None - has_static_lazy = instance.has_static_lazy_constraints() - user_features: List[Optional[List[float]]] = [] - categories: List[Optional[Hashable]] = [] - lazy: List[bool] = [] - constr_categories_dict = instance.get_constraint_categories() - constr_features_dict = instance.get_constraint_features() - - for (cidx, cname) in enumerate(features.constraints.names): - category: Optional[Hashable] = cname - if cname in constr_categories_dict: - category = constr_categories_dict[cname] - if category is None: - user_features.append(None) - categories.append(None) - continue - assert isinstance(category, collections.Hashable), ( - f"Constraint category must be hashable. " - f"Found {type(category).__name__} instead for cname={cname}.", - ) - categories.append(category) - cf: Optional[List[float]] = None - if cname in constr_features_dict: - cf = constr_features_dict[cname] - if isinstance(cf, np.ndarray): - cf = cf.tolist() - assert isinstance(cf, list), ( - f"Constraint features must be a list. " - f"Found {type(cf).__name__} instead for cname={cname}." - ) - for f in cf: - assert isinstance(f, numbers.Real), ( - f"Constraint features must be a list of numbers. " - f"Found {type(f).__name__} instead for cname={cname}." - ) - cf = list(cf) - user_features.append(cf) - if has_static_lazy: - lazy.append(instance.is_constraint_lazy(cname)) - else: - lazy.append(False) - features.constraints.user_features = user_features - features.constraints.lazy = lazy - features.constraints.categories = categories - def _extract_user_features_instance( self, instance: "Instance", @@ -534,106 +341,6 @@ class FeaturesExtractor: sample.put("instance_features_user", user_features) sample.put("static_lazy_count", sum(constr_lazy)) - def _extract_user_features_instance_old( - self, - instance: "Instance", - features: Features, - ) -> None: - user_features = instance.get_instance_features() - if isinstance(user_features, np.ndarray): - user_features = user_features.tolist() - assert isinstance(user_features, list), ( - f"Instance features must be a list. " - f"Found {type(user_features).__name__} instead." - ) - for v in user_features: - assert isinstance(v, numbers.Real), ( - f"Instance features must be a list of numbers. " - f"Found {type(v).__name__} instead." - ) - assert features.constraints is not None - assert features.constraints.lazy is not None - features.instance = InstanceFeatures( - user_features=user_features, - lazy_constraint_count=sum(features.constraints.lazy), - ) - - def _extract_alvarez_2017_old(self, features: Features) -> None: - assert features.variables is not None - assert features.variables.names is not None - - obj_coeffs = features.variables.obj_coeffs - obj_sa_down = features.variables.sa_obj_down - obj_sa_up = features.variables.sa_obj_up - values = features.variables.values - - pos_obj_coeff_sum = 0.0 - neg_obj_coeff_sum = 0.0 - if obj_coeffs is not None: - for coeff in obj_coeffs: - if coeff > 0: - pos_obj_coeff_sum += coeff - if coeff < 0: - neg_obj_coeff_sum += -coeff - - features.variables.alvarez_2017 = [] - for i in range(len(features.variables.names)): - f: List[float] = [] - if obj_coeffs is not None: - # Feature 1 - f.append(np.sign(obj_coeffs[i])) - - # Feature 2 - if pos_obj_coeff_sum > 0: - f.append(abs(obj_coeffs[i]) / pos_obj_coeff_sum) - else: - f.append(0.0) - - # Feature 3 - if neg_obj_coeff_sum > 0: - f.append(abs(obj_coeffs[i]) / neg_obj_coeff_sum) - else: - f.append(0.0) - - if values is not None: - # Feature 37 - f.append( - min( - values[i] - np.floor(values[i]), - np.ceil(values[i]) - values[i], - ) - ) - - if obj_sa_up is not None: - assert obj_sa_down is not None - assert obj_coeffs is not None - - # Convert inf into large finite numbers - sd = max(-1e20, obj_sa_down[i]) - su = min(1e20, obj_sa_up[i]) - obj = obj_coeffs[i] - - # Features 44 and 46 - f.append(np.sign(obj_sa_up[i])) - f.append(np.sign(obj_sa_down[i])) - - # Feature 47 - csign = np.sign(obj) - if csign != 0 and ((obj - sd) / csign) > 0.001: - f.append(log((obj - sd) / csign)) - else: - f.append(0.0) - - # Feature 48 - if csign != 0 and ((su - obj) / csign) > 0.001: - f.append(log((su - obj) / csign)) - else: - f.append(0.0) - - for v in f: - assert isfinite(v), f"non-finite elements detected: {f}" - features.variables.alvarez_2017.append(f) - # Alvarez, A. M., Louveaux, Q., & Wehenkel, L. (2017). A machine learning-based # approximation of strong branching. INFORMS Journal on Computing, 29(1), 185-195. def _extract_var_features_AlvLouWeh2017( diff --git a/miplearn/solvers/learning.py b/miplearn/solvers/learning.py index 240cf43..04b8f89 100644 --- a/miplearn/solvers/learning.py +++ b/miplearn/solvers/learning.py @@ -171,7 +171,6 @@ class LearningSolver: self.extractor.extract_after_load_features( instance, self.internal_solver, sample ) - features = self.extractor.extract(instance, self.internal_solver) logger.info( "Features (after-load) extracted in %.2f seconds" % (time.time() - initial_time) diff --git a/tests/components/test_dynamic_lazy.py b/tests/components/test_dynamic_lazy.py index 27b182d..3963df6 100644 --- a/tests/components/test_dynamic_lazy.py +++ b/tests/components/test_dynamic_lazy.py @@ -11,11 +11,7 @@ from miplearn.classifiers import Classifier from miplearn.classifiers.threshold import MinProbabilityThreshold from miplearn.components import classifier_evaluation_dict from miplearn.components.dynamic_lazy import DynamicLazyConstraintsComponent -from miplearn.features import ( - Features, - InstanceFeatures, - Sample, -) +from miplearn.features import Sample from miplearn.instance.base import Instance from miplearn.solvers.tests import assert_equals diff --git a/tests/components/test_primal.py b/tests/components/test_primal.py index 5925ee7..e00ece3 100644 --- a/tests/components/test_primal.py +++ b/tests/components/test_primal.py @@ -12,12 +12,7 @@ from miplearn.classifiers import Classifier from miplearn.classifiers.threshold import Threshold from miplearn.components import classifier_evaluation_dict from miplearn.components.primal import PrimalSolutionComponent -from miplearn.features import ( - Features, - Sample, - InstanceFeatures, - VariableFeatures, -) +from miplearn.features import Sample from miplearn.problems.tsp import TravelingSalesmanGenerator from miplearn.solvers.learning import LearningSolver from miplearn.solvers.tests import assert_equals diff --git a/tests/components/test_static_lazy.py b/tests/components/test_static_lazy.py index a43954f..c43e0bf 100644 --- a/tests/components/test_static_lazy.py +++ b/tests/components/test_static_lazy.py @@ -11,12 +11,7 @@ from numpy.testing import assert_array_equal from miplearn.classifiers import Classifier from miplearn.classifiers.threshold import Threshold, MinProbabilityThreshold from miplearn.components.static_lazy import StaticLazyConstraintsComponent -from miplearn.features import ( - InstanceFeatures, - Features, - Sample, - ConstraintFeatures, -) +from miplearn.features import Sample, ConstraintFeatures from miplearn.instance.base import Instance from miplearn.solvers.internal import InternalSolver from miplearn.solvers.learning import LearningSolver diff --git a/tests/test_features.py b/tests/test_features.py index f324a5a..48947b4 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -6,7 +6,6 @@ import numpy as np from miplearn.features import ( FeaturesExtractor, - InstanceFeatures, VariableFeatures, ConstraintFeatures, Sample, @@ -128,15 +127,6 @@ def test_knapsack() -> None: assert_equals(sample.get("mip_var_values"), [1.0, 0.0, 1.0, 1.0, 61.0]) assert_equals(sample.get("mip_constr_slacks"), [0.0]) - features = extractor.extract(instance, solver) - assert_equals( - features.instance, - InstanceFeatures( - user_features=[67.0, 21.75], - lazy_constraint_count=0, - ), - ) - def test_constraint_getindex() -> None: cf = ConstraintFeatures(