diff --git a/miplearn/__init__.py b/miplearn/__init__.py index 692281f..cb32eea 100644 --- a/miplearn/__init__.py +++ b/miplearn/__init__.py @@ -27,3 +27,6 @@ from .solvers.learning import LearningSolver from .solvers.pyomo.base import BasePyomoSolver from .solvers.pyomo.cplex import CplexPyomoSolver from .solvers.pyomo.gurobi import GurobiPyomoSolver + +# noinspection PyUnresolvedReferences +from overrides import overrides diff --git a/miplearn/components/component.py b/miplearn/components/component.py index 92ddc06..6f4d87c 100644 --- a/miplearn/components/component.py +++ b/miplearn/components/component.py @@ -5,6 +5,7 @@ from typing import Any, List, TYPE_CHECKING, Tuple, Dict, Hashable import numpy as np +from overrides import EnforceOverrides from miplearn.features import TrainingSample, Features from miplearn.instance.base import Instance @@ -15,7 +16,7 @@ if TYPE_CHECKING: # noinspection PyMethodMayBeStatic -class Component: +class Component(EnforceOverrides): """ A Component is an object which adds functionality to a LearningSolver. diff --git a/miplearn/components/dynamic_common.py b/miplearn/components/dynamic_common.py index 3381936..6ae21af 100644 --- a/miplearn/components/dynamic_common.py +++ b/miplearn/components/dynamic_common.py @@ -5,6 +5,7 @@ from typing import Dict, Hashable, List, Tuple, TYPE_CHECKING import numpy as np +from overrides import overrides from miplearn.classifiers import Classifier from miplearn.classifiers.threshold import Threshold @@ -73,6 +74,7 @@ class DynamicConstraintsComponent(Component): y[category] += [[True, False]] return x, y, cids + @overrides def sample_xy( self, instance: "Instance", @@ -101,6 +103,7 @@ class DynamicConstraintsComponent(Component): pred += [cids[category][i]] return pred + @overrides def fit(self, training_instances: List["Instance"]) -> None: collected_cids = set() for instance in training_instances: @@ -114,6 +117,7 @@ class DynamicConstraintsComponent(Component): self.known_cids.extend(sorted(collected_cids)) super().fit(training_instances) + @overrides def fit_xy( self, x: Dict[Hashable, np.ndarray], @@ -127,6 +131,7 @@ class DynamicConstraintsComponent(Component): self.classifiers[category].fit(npx, npy) self.thresholds[category].fit(self.classifiers[category], npx, npy) + @overrides def sample_evaluate( self, instance: "Instance", diff --git a/miplearn/components/dynamic_lazy.py b/miplearn/components/dynamic_lazy.py index 4b2af2a..a63f811 100644 --- a/miplearn/components/dynamic_lazy.py +++ b/miplearn/components/dynamic_lazy.py @@ -6,6 +6,7 @@ import logging from typing import Dict, List, TYPE_CHECKING, Hashable, Tuple, Any import numpy as np +from overrides import overrides from miplearn.instance.base import Instance from miplearn.classifiers import Classifier @@ -53,6 +54,7 @@ class DynamicLazyConstraintsComponent(Component): cobj = instance.build_lazy_constraint(model, cid) solver.internal_solver.add_constraint(cobj) + @overrides def before_solve_mip( self, solver: "LearningSolver", @@ -68,6 +70,7 @@ class DynamicLazyConstraintsComponent(Component): logger.info("Enforcing %d lazy constraints..." % len(cids)) self.enforce(cids, instance, model, solver) + @overrides def iteration_cb( self, solver: "LearningSolver", @@ -89,6 +92,7 @@ class DynamicLazyConstraintsComponent(Component): # Delegate ML methods to self.dynamic # ------------------------------------------------------------------- + @overrides def sample_xy( self, instance: "Instance", @@ -103,9 +107,11 @@ class DynamicLazyConstraintsComponent(Component): ) -> List[Hashable]: return self.dynamic.sample_predict(instance, sample) + @overrides def fit(self, training_instances: List["Instance"]) -> None: self.dynamic.fit(training_instances) + @overrides def fit_xy( self, x: Dict[Hashable, np.ndarray], @@ -113,6 +119,7 @@ class DynamicLazyConstraintsComponent(Component): ) -> None: self.dynamic.fit_xy(x, y) + @overrides def sample_evaluate( self, instance: "Instance", diff --git a/miplearn/components/dynamic_user_cuts.py b/miplearn/components/dynamic_user_cuts.py index 03dd8ed..c273b75 100644 --- a/miplearn/components/dynamic_user_cuts.py +++ b/miplearn/components/dynamic_user_cuts.py @@ -6,6 +6,7 @@ import logging from typing import Any, TYPE_CHECKING, Hashable, Set, Tuple, Dict, List import numpy as np +from overrides import overrides from miplearn.classifiers import Classifier from miplearn.classifiers.counting import CountingClassifier @@ -35,6 +36,7 @@ class UserCutsComponent(Component): self.enforced: Set[Hashable] = set() self.n_added_in_callback = 0 + @overrides def before_solve_mip( self, solver: "LearningSolver", @@ -55,6 +57,7 @@ class UserCutsComponent(Component): solver.internal_solver.add_constraint(cobj) stats["UserCuts: Added ahead-of-time"] = len(cids) + @overrides def user_cut_cb( self, solver: "LearningSolver", @@ -78,6 +81,7 @@ class UserCutsComponent(Component): if len(cids) > 0: logger.debug(f"Added {len(cids)} violated user cuts") + @overrides def after_solve_mip( self, solver: "LearningSolver", @@ -93,6 +97,7 @@ class UserCutsComponent(Component): # Delegate ML methods to self.dynamic # ------------------------------------------------------------------- + @overrides def sample_xy( self, instance: "Instance", @@ -107,9 +112,11 @@ class UserCutsComponent(Component): ) -> List[Hashable]: return self.dynamic.sample_predict(instance, sample) + @overrides def fit(self, training_instances: List["Instance"]) -> None: self.dynamic.fit(training_instances) + @overrides def fit_xy( self, x: Dict[Hashable, np.ndarray], @@ -117,6 +124,7 @@ class UserCutsComponent(Component): ) -> None: self.dynamic.fit_xy(x, y) + @overrides def sample_evaluate( self, instance: "Instance", diff --git a/miplearn/components/objective.py b/miplearn/components/objective.py index 1424d37..729263a 100644 --- a/miplearn/components/objective.py +++ b/miplearn/components/objective.py @@ -6,6 +6,7 @@ import logging from typing import List, Dict, Any, TYPE_CHECKING, Tuple, Hashable import numpy as np +from overrides import overrides from sklearn.linear_model import LinearRegression from miplearn.classifiers import Regressor @@ -34,6 +35,7 @@ class ObjectiveValueComponent(Component): self.regressors: Dict[str, Regressor] = {} self.regressor_prototype = regressor + @overrides def before_solve_mip( self, solver: "LearningSolver", @@ -49,6 +51,7 @@ class ObjectiveValueComponent(Component): logger.info(f"Predicted {c.lower()}: %.6e" % v) stats[f"Objective: Predicted {c.lower()}"] = v # type: ignore + @overrides def fit_xy( self, x: Dict[Hashable, np.ndarray], @@ -73,6 +76,7 @@ class ObjectiveValueComponent(Component): logger.info(f"{c} regressor not fitted. Skipping.") return pred + @overrides def sample_xy( self, instance: Instance, @@ -94,6 +98,7 @@ class ObjectiveValueComponent(Component): y["Upper bound"] = [[sample.upper_bound]] return x, y + @overrides def sample_evaluate( self, instance: Instance, diff --git a/miplearn/components/primal.py b/miplearn/components/primal.py index f6b05e6..4c0a73f 100644 --- a/miplearn/components/primal.py +++ b/miplearn/components/primal.py @@ -13,6 +13,7 @@ from typing import ( ) import numpy as np +from overrides import overrides from miplearn.classifiers import Classifier from miplearn.classifiers.adaptive import AdaptiveClassifier @@ -58,6 +59,7 @@ class PrimalSolutionComponent(Component): self.threshold_prototype = threshold self.classifier_prototype = classifier + @overrides def before_solve_mip( self, solver: "LearningSolver", @@ -137,6 +139,7 @@ class PrimalSolutionComponent(Component): return solution + @overrides def sample_xy( self, instance: Instance, @@ -172,6 +175,7 @@ class PrimalSolutionComponent(Component): y[category] += [[opt_value < 0.5, opt_value >= 0.5]] return x, y + @overrides def sample_evaluate( self, instance: Instance, @@ -212,6 +216,7 @@ class PrimalSolutionComponent(Component): ), } + @overrides def fit_xy( self, x: Dict[Hashable, np.ndarray], diff --git a/miplearn/components/static_lazy.py b/miplearn/components/static_lazy.py index 7a3f2db..2b05ec2 100644 --- a/miplearn/components/static_lazy.py +++ b/miplearn/components/static_lazy.py @@ -6,6 +6,7 @@ import logging from typing import Dict, Tuple, List, Hashable, Any, TYPE_CHECKING, Set import numpy as np +from overrides import overrides from miplearn.classifiers import Classifier from miplearn.classifiers.counting import CountingClassifier @@ -49,6 +50,7 @@ class StaticLazyConstraintsComponent(Component): self.n_restored: int = 0 self.n_iterations: int = 0 + @overrides def before_solve_mip( self, solver: "LearningSolver", @@ -84,6 +86,7 @@ class StaticLazyConstraintsComponent(Component): self.n_restored = 0 self.n_iterations = 0 + @overrides def after_solve_mip( self, solver: "LearningSolver", @@ -97,6 +100,7 @@ class StaticLazyConstraintsComponent(Component): stats["LazyStatic: Restored"] = self.n_restored stats["LazyStatic: Iterations"] = self.n_iterations + @overrides def iteration_cb( self, solver: "LearningSolver", @@ -108,6 +112,7 @@ class StaticLazyConstraintsComponent(Component): else: return self._check_and_add(solver) + @overrides def lazy_cb( self, solver: "LearningSolver", @@ -170,6 +175,7 @@ class StaticLazyConstraintsComponent(Component): enforced_cids += [category_to_cids[category][i]] return enforced_cids + @overrides def sample_xy( self, instance: "Instance", @@ -195,6 +201,7 @@ class StaticLazyConstraintsComponent(Component): y[category] += [[True, False]] return x, y + @overrides def fit_xy( self, x: Dict[Hashable, np.ndarray],