Finish rewrite of user cuts component

This commit is contained in:
2021-04-06 16:17:05 -05:00
parent 9f2d7439dc
commit 9e7eed1dbd
25 changed files with 313 additions and 268 deletions

View File

@@ -5,8 +5,8 @@
from numpy.linalg import norm
from sklearn.svm import SVC
from miplearn import AdaptiveClassifier, ScikitLearnClassifier
from miplearn.classifiers.adaptive import CandidateClassifierSpecs
from miplearn.classifiers.adaptive import CandidateClassifierSpecs, AdaptiveClassifier
from miplearn.classifiers.sklearn import ScikitLearnClassifier
from tests.classifiers import _build_circle_training_data

View File

@@ -6,10 +6,11 @@ from unittest.mock import Mock, call
import numpy as np
from miplearn import RelaxIntegralityStep, GurobiSolver
from miplearn.classifiers import Classifier
from miplearn.components.steps.drop_redundant import DropRedundantInequalitiesStep
from miplearn.components.steps.relax_integrality import RelaxIntegralityStep
from miplearn.instance import Instance
from miplearn.solvers.gurobi import GurobiSolver
from miplearn.solvers.internal import InternalSolver
from miplearn.solvers.learning import LearningSolver
from miplearn.features import TrainingSample, Features

View File

@@ -3,7 +3,8 @@
# Released under the modified BSD license. See COPYING.md for more details.
from unittest.mock import Mock
from miplearn import Component, Instance
from miplearn.components.component import Component
from miplearn.instance import Instance
def test_xy_instance():

View File

@@ -8,16 +8,16 @@ import numpy as np
import pytest
from numpy.testing import assert_array_equal
from miplearn import Instance
from miplearn.classifiers import Classifier
from miplearn.classifiers.threshold import MinProbabilityThreshold
from miplearn.components import classifier_evaluation_dict
from miplearn.components.lazy_dynamic import DynamicLazyConstraintsComponent
from miplearn.components.dynamic_lazy import DynamicLazyConstraintsComponent
from miplearn.features import (
TrainingSample,
Features,
InstanceFeatures,
)
from miplearn.instance import Instance
E = 0.1
@@ -144,7 +144,7 @@ def test_fit(training_instances: List[Instance]) -> None:
def test_sample_predict_evaluate(training_instances: List[Instance]) -> None:
comp = DynamicLazyConstraintsComponent()
comp.known_cids = ["c1", "c2", "c3", "c4"]
comp.known_cids.extend(["c1", "c2", "c3", "c4"])
comp.thresholds["type-a"] = MinProbabilityThreshold([0.5, 0.5])
comp.thresholds["type-b"] = MinProbabilityThreshold([0.5, 0.5])
comp.classifiers["type-a"] = Mock(spec=Classifier)

View File

@@ -11,8 +11,10 @@ import pytest
from gurobipy import GRB
from networkx import Graph
from miplearn import Instance, LearningSolver, GurobiSolver
from miplearn.components.user_cuts import UserCutsComponentNG
from miplearn.components.dynamic_user_cuts import UserCutsComponent
from miplearn.instance import Instance
from miplearn.solvers.gurobi import GurobiSolver
from miplearn.solvers.learning import LearningSolver
logger = logging.getLogger(__name__)
@@ -20,12 +22,11 @@ logger = logging.getLogger(__name__)
class GurobiStableSetProblem(Instance):
def __init__(self, graph: Graph) -> None:
super().__init__()
self.graph = graph
self.nodes = list(self.graph.nodes)
self.graph: Graph = graph
def to_model(self) -> Any:
model = gp.Model()
x = [model.addVar(vtype=GRB.BINARY) for _ in range(len(self.nodes))]
x = [model.addVar(vtype=GRB.BINARY) for _ in range(len(self.graph.nodes))]
model.setObjective(gp.quicksum(x), GRB.MAXIMIZE)
for e in list(self.graph.edges):
model.addConstr(x[e[0]] + x[e[1]] <= 1)
@@ -39,16 +40,14 @@ class GurobiStableSetProblem(Instance):
vals = model.cbGetNodeRel(model.getVars())
violations = []
for clique in nx.find_cliques(self.graph):
lhs = sum(vals[i] for i in clique)
if lhs > 1:
if sum(vals[i] for i in clique) > 1:
violations += [frozenset(clique)]
return violations
def build_user_cut(self, model: Any, violation: Hashable) -> Any:
assert isinstance(violation, FrozenSet)
def build_user_cut(self, model: Any, cid: Hashable) -> Any:
assert isinstance(cid, FrozenSet)
x = model.getVars()
cut = gp.quicksum([x[i] for i in violation]) <= 1
return cut
return gp.quicksum([x[i] for i in cid]) <= 1
@pytest.fixture
@@ -62,7 +61,7 @@ def solver() -> LearningSolver:
return LearningSolver(
solver=lambda: GurobiSolver(),
components=[
UserCutsComponentNG(),
UserCutsComponent(),
],
)
@@ -71,7 +70,17 @@ def test_usage(
stab_instance: Instance,
solver: LearningSolver,
) -> None:
solver.solve(stab_instance)
stats_before = solver.solve(stab_instance)
sample = stab_instance.training_data[0]
assert sample.user_cuts_enforced is not None
assert len(sample.user_cuts_enforced) > 0
print(stats_before)
assert stats_before["UserCuts: Added ahead-of-time"] == 0
assert stats_before["UserCuts: Added in callback"] > 0
solver.fit([stab_instance])
stats_after = solver.solve(stab_instance)
assert (
stats_after["UserCuts: Added ahead-of-time"]
== stats_before["UserCuts: Added in callback"]
)

View File

@@ -7,9 +7,12 @@ from unittest.mock import Mock
import pytest
from numpy.testing import assert_array_equal
from miplearn import GurobiPyomoSolver, LearningSolver, Regressor, Instance
from miplearn.classifiers import Regressor
from miplearn.components.objective import ObjectiveValueComponent
from miplearn.features import TrainingSample, InstanceFeatures, Features
from miplearn.instance import Instance
from miplearn.solvers.learning import LearningSolver
from miplearn.solvers.pyomo.gurobi import GurobiPyomoSolver
from tests.fixtures.knapsack import get_knapsack_instance
import numpy as np

View File

@@ -8,12 +8,14 @@ import numpy as np
from numpy.testing import assert_array_equal
from scipy.stats import randint
from miplearn import Classifier, LearningSolver, Instance
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.instance import Instance
from miplearn.problems.tsp import TravelingSalesmanGenerator
from miplearn.features import TrainingSample, VariableFeatures, Features
from miplearn.solvers.learning import LearningSolver
def test_xy() -> None:

View File

@@ -8,10 +8,12 @@ import numpy as np
import pytest
from numpy.testing import assert_array_equal
from miplearn import LearningSolver, InternalSolver, Instance
from miplearn.classifiers import Classifier
from miplearn.classifiers.threshold import Threshold, MinProbabilityThreshold
from miplearn.components.lazy_static import StaticLazyConstraintsComponent
from miplearn.components.static_lazy import StaticLazyConstraintsComponent
from miplearn.instance import Instance
from miplearn.solvers.internal import InternalSolver
from miplearn.solvers.learning import LearningSolver
from miplearn.types import (
LearningSolveStats,
)

View File

@@ -1,10 +1,12 @@
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
from miplearn import BasePyomoSolver, GurobiSolver, InternalSolver, Instance
from miplearn.instance import Instance
from miplearn.problems.knapsack import KnapsackInstance, GurobiKnapsackInstance
from miplearn.solvers.gurobi import GurobiSolver
from miplearn.solvers.internal import InternalSolver
from miplearn.solvers.learning import LearningSolver
from miplearn.solvers.pyomo.base import BasePyomoSolver
from tests.solvers import _is_subclass_or_instance

View File

@@ -3,9 +3,11 @@
# Released under the modified BSD license. See COPYING.md for more details.
from typing import Any
from miplearn import Instance, BasePyomoSolver, GurobiSolver
import pyomo.environ as pe
from miplearn.instance import Instance
from miplearn.solvers.gurobi import GurobiSolver
from miplearn.solvers.pyomo.base import BasePyomoSolver
from tests.solvers import _is_subclass_or_instance

View File

@@ -67,7 +67,6 @@ def test_subtour():
solver = LearningSolver()
solver.solve(instance)
assert len(instance.training_data[0].lazy_enforced) > 0
assert hasattr(instance, "found_violated_user_cuts")
x = instance.training_data[0].solution["x"]
assert x[0, 1] == 1.0
assert x[0, 4] == 1.0

View File

@@ -30,7 +30,7 @@ def test_benchmark():
benchmark = BenchmarkRunner(test_solvers)
benchmark.fit(train_instances)
benchmark.parallel_solve(test_instances, n_jobs=n_jobs, n_trials=2)
assert benchmark.results.values.shape == (12, 18)
assert benchmark.results.values.shape == (12, 20)
benchmark.write_csv("/tmp/benchmark.csv")
assert os.path.isfile("/tmp/benchmark.csv")

View File

@@ -2,13 +2,13 @@
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
from miplearn import GurobiSolver
from miplearn.features import (
FeaturesExtractor,
InstanceFeatures,
VariableFeatures,
ConstraintFeatures,
)
from miplearn.solvers.gurobi import GurobiSolver
from tests.fixtures.knapsack import get_knapsack_instance

View File

@@ -3,8 +3,8 @@
# Released under the modified BSD license. See COPYING.md for more details.
import tempfile
from miplearn import GurobiSolver
from miplearn.instance import write_pickle_gz, PickleGzInstance
from miplearn.solvers.gurobi import GurobiSolver
from tests.fixtures.knapsack import get_knapsack_instance