mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-06 09:28:51 -06:00
ConvertTight: Always check feasibility
This commit is contained in:
@@ -20,8 +20,10 @@ class ConvertTightIneqsIntoEqsStep(Component):
|
|||||||
"""
|
"""
|
||||||
Component that predicts which inequality constraints are likely to be binding in
|
Component that predicts which inequality constraints are likely to be binding in
|
||||||
the LP relaxation of the problem and converts them into equality constraints.
|
the LP relaxation of the problem and converts them into equality constraints.
|
||||||
Optionally double checks that the conversion process did not affect feasibility
|
|
||||||
or optimality of the problem.
|
This component always makes sure that the conversion process does not affect the
|
||||||
|
feasibility of the problem. It can also, optionally, make sure that it does not affect
|
||||||
|
the optimality, but this may be expensive.
|
||||||
|
|
||||||
This component does not work on MIPs. All integrality constraints must be relaxed
|
This component does not work on MIPs. All integrality constraints must be relaxed
|
||||||
before this component is used.
|
before this component is used.
|
||||||
@@ -32,13 +34,13 @@ class ConvertTightIneqsIntoEqsStep(Component):
|
|||||||
classifier=CountingClassifier(),
|
classifier=CountingClassifier(),
|
||||||
threshold=0.95,
|
threshold=0.95,
|
||||||
slack_tolerance=0.0,
|
slack_tolerance=0.0,
|
||||||
check_converted=False,
|
check_optimality=False,
|
||||||
):
|
):
|
||||||
self.classifiers = {}
|
self.classifiers = {}
|
||||||
self.classifier_prototype = classifier
|
self.classifier_prototype = classifier
|
||||||
self.threshold = threshold
|
self.threshold = threshold
|
||||||
self.slack_tolerance = slack_tolerance
|
self.slack_tolerance = slack_tolerance
|
||||||
self.check_converted = check_converted
|
self.check_optimality = check_optimality
|
||||||
self.converted = []
|
self.converted = []
|
||||||
self.original_sense = {}
|
self.original_sense = {}
|
||||||
|
|
||||||
@@ -66,8 +68,10 @@ class ConvertTightIneqsIntoEqsStep(Component):
|
|||||||
solver.internal_solver.set_constraint_sense(cid, "=")
|
solver.internal_solver.set_constraint_sense(cid, "=")
|
||||||
self.converted += [cid]
|
self.converted += [cid]
|
||||||
self.n_converted += 1
|
self.n_converted += 1
|
||||||
|
print(cid)
|
||||||
else:
|
else:
|
||||||
self.n_kept += 1
|
self.n_kept += 1
|
||||||
|
|
||||||
logger.info(f"Converted {self.n_converted} inequalities")
|
logger.info(f"Converted {self.n_converted} inequalities")
|
||||||
|
|
||||||
def after_solve(self, solver, instance, model, results):
|
def after_solve(self, solver, instance, model, results):
|
||||||
@@ -173,10 +177,6 @@ class ConvertTightIneqsIntoEqsStep(Component):
|
|||||||
return classifier_evaluation_dict(tp, tn, fp, fn)
|
return classifier_evaluation_dict(tp, tn, fp, fn)
|
||||||
|
|
||||||
def iteration_cb(self, solver, instance, model):
|
def iteration_cb(self, solver, instance, model):
|
||||||
if not self.check_converted:
|
|
||||||
return False
|
|
||||||
|
|
||||||
logger.debug("Checking converted inequalities...")
|
|
||||||
is_infeasible, is_suboptimal = False, False
|
is_infeasible, is_suboptimal = False, False
|
||||||
restored = []
|
restored = []
|
||||||
|
|
||||||
@@ -206,7 +206,7 @@ class ConvertTightIneqsIntoEqsStep(Component):
|
|||||||
if abs(pi) > 0:
|
if abs(pi) > 0:
|
||||||
is_infeasible = True
|
is_infeasible = True
|
||||||
restore(cid)
|
restore(cid)
|
||||||
else:
|
elif self.check_optimality:
|
||||||
for cid in self.converted:
|
for cid in self.converted:
|
||||||
pi = solver.internal_solver.get_dual(cid)
|
pi = solver.internal_solver.get_dual(cid)
|
||||||
csense = self.original_sense[cid]
|
csense = self.original_sense[cid]
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class DropRedundantInequalitiesStep(Component):
|
|||||||
classifier=CountingClassifier(),
|
classifier=CountingClassifier(),
|
||||||
threshold=0.95,
|
threshold=0.95,
|
||||||
slack_tolerance=1e-5,
|
slack_tolerance=1e-5,
|
||||||
check_dropped=False,
|
check_feasibility=False,
|
||||||
violation_tolerance=1e-5,
|
violation_tolerance=1e-5,
|
||||||
max_iterations=3,
|
max_iterations=3,
|
||||||
):
|
):
|
||||||
@@ -41,7 +41,7 @@ class DropRedundantInequalitiesStep(Component):
|
|||||||
self.threshold = threshold
|
self.threshold = threshold
|
||||||
self.slack_tolerance = slack_tolerance
|
self.slack_tolerance = slack_tolerance
|
||||||
self.pool = []
|
self.pool = []
|
||||||
self.check_dropped = check_dropped
|
self.check_feasibility = check_feasibility
|
||||||
self.violation_tolerance = violation_tolerance
|
self.violation_tolerance = violation_tolerance
|
||||||
self.max_iterations = max_iterations
|
self.max_iterations = max_iterations
|
||||||
self.current_iteration = 0
|
self.current_iteration = 0
|
||||||
@@ -175,7 +175,7 @@ class DropRedundantInequalitiesStep(Component):
|
|||||||
return classifier_evaluation_dict(tp, tn, fp, fn)
|
return classifier_evaluation_dict(tp, tn, fp, fn)
|
||||||
|
|
||||||
def iteration_cb(self, solver, instance, model):
|
def iteration_cb(self, solver, instance, model):
|
||||||
if not self.check_dropped:
|
if not self.check_feasibility:
|
||||||
return False
|
return False
|
||||||
if self.current_iteration >= self.max_iterations:
|
if self.current_iteration >= self.max_iterations:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -30,10 +30,12 @@ def test_convert_tight_usage():
|
|||||||
|
|
||||||
# Fit and resolve
|
# Fit and resolve
|
||||||
solver.fit([instance])
|
solver.fit([instance])
|
||||||
solver.solve(instance)
|
stats = solver.solve(instance)
|
||||||
|
|
||||||
# Objective value should be the same
|
# Objective value should be the same
|
||||||
assert instance.upper_bound == original_upper_bound
|
assert instance.upper_bound == original_upper_bound
|
||||||
|
assert stats["ConvertTight: Inf iterations"] == 0
|
||||||
|
assert stats["ConvertTight: Subopt iterations"] == 0
|
||||||
|
|
||||||
|
|
||||||
class TestInstance(Instance):
|
class TestInstance(Instance):
|
||||||
@@ -51,6 +53,21 @@ class TestInstance(Instance):
|
|||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
class TestInstanceMin(Instance):
|
||||||
|
def to_model(self):
|
||||||
|
import gurobipy as grb
|
||||||
|
from gurobipy import GRB
|
||||||
|
|
||||||
|
m = grb.Model("model")
|
||||||
|
x1 = m.addVar(name="x1")
|
||||||
|
x2 = m.addVar(name="x2")
|
||||||
|
m.setObjective(x1 + 2 * x2, grb.GRB.MAXIMIZE)
|
||||||
|
m.addConstr(x1 <= 2, name="c1")
|
||||||
|
m.addConstr(x2 <= 2, name="c2")
|
||||||
|
m.addConstr(x1 + x2 <= 3, name="c2")
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
def test_convert_tight_infeasibility():
|
def test_convert_tight_infeasibility():
|
||||||
comp = ConvertTightIneqsIntoEqsStep(
|
comp = ConvertTightIneqsIntoEqsStep(
|
||||||
check_converted=True,
|
check_converted=True,
|
||||||
@@ -70,8 +87,10 @@ def test_convert_tight_infeasibility():
|
|||||||
solve_lp_first=False,
|
solve_lp_first=False,
|
||||||
)
|
)
|
||||||
instance = TestInstance()
|
instance = TestInstance()
|
||||||
solver.solve(instance)
|
stats = solver.solve(instance)
|
||||||
assert instance.lower_bound == 5.0
|
assert instance.lower_bound == 5.0
|
||||||
|
assert stats["ConvertTight: Inf iterations"] == 1
|
||||||
|
assert stats["ConvertTight: Subopt iterations"] == 0
|
||||||
|
|
||||||
|
|
||||||
def test_convert_tight_suboptimality():
|
def test_convert_tight_suboptimality():
|
||||||
@@ -93,5 +112,32 @@ def test_convert_tight_suboptimality():
|
|||||||
solve_lp_first=False,
|
solve_lp_first=False,
|
||||||
)
|
)
|
||||||
instance = TestInstance()
|
instance = TestInstance()
|
||||||
solver.solve(instance)
|
stats = solver.solve(instance)
|
||||||
assert instance.lower_bound == 5.0
|
assert instance.lower_bound == 5.0
|
||||||
|
assert stats["ConvertTight: Inf iterations"] == 0
|
||||||
|
assert stats["ConvertTight: Subopt iterations"] == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_convert_tight_optimal():
|
||||||
|
comp = ConvertTightIneqsIntoEqsStep(
|
||||||
|
check_converted=True,
|
||||||
|
)
|
||||||
|
comp.classifiers = {
|
||||||
|
"c1": Mock(spec=Classifier),
|
||||||
|
"c2": Mock(spec=Classifier),
|
||||||
|
"c3": Mock(spec=Classifier),
|
||||||
|
}
|
||||||
|
comp.classifiers["c1"].predict_proba = Mock(return_value=[[1, 0]])
|
||||||
|
comp.classifiers["c2"].predict_proba = Mock(return_value=[[0, 1]])
|
||||||
|
comp.classifiers["c3"].predict_proba = Mock(return_value=[[0, 1]])
|
||||||
|
|
||||||
|
solver = LearningSolver(
|
||||||
|
solver=GurobiSolver(params={}),
|
||||||
|
components=[comp],
|
||||||
|
solve_lp_first=False,
|
||||||
|
)
|
||||||
|
instance = TestInstance()
|
||||||
|
stats = solver.solve(instance)
|
||||||
|
assert instance.lower_bound == 5.0
|
||||||
|
assert stats["ConvertTight: Inf iterations"] == 0
|
||||||
|
assert stats["ConvertTight: Subopt iterations"] == 0
|
||||||
|
|||||||
Reference in New Issue
Block a user