mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-06 09:28:51 -06:00
Implement iteration_cb for LearningSolver; reactivate TSP
This commit is contained in:
@@ -21,3 +21,6 @@ class Component(ABC):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
def fit(self, training_instances):
|
def fit(self, training_instances):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def after_iteration(self, solver, instance, model):
|
||||||
|
return False
|
||||||
|
|||||||
@@ -38,6 +38,18 @@ class DynamicLazyConstraintsComponent(Component):
|
|||||||
cut = instance.build_lazy_constraint(model, v)
|
cut = instance.build_lazy_constraint(model, v)
|
||||||
solver.internal_solver.add_constraint(cut)
|
solver.internal_solver.add_constraint(cut)
|
||||||
|
|
||||||
|
def after_iteration(self, solver, instance, model):
|
||||||
|
logger.debug("Finding violated (dynamic) lazy constraints...")
|
||||||
|
violations = instance.find_violated_lazy_constraints(model)
|
||||||
|
if len(violations) == 0:
|
||||||
|
return False
|
||||||
|
instance.found_violated_lazy_constraints += violations
|
||||||
|
logger.debug(" %d violations found" % len(violations))
|
||||||
|
for v in violations:
|
||||||
|
cut = instance.build_lazy_constraint(model, v)
|
||||||
|
solver.internal_solver.add_constraint(cut)
|
||||||
|
return True
|
||||||
|
|
||||||
def after_solve(self, solver, instance, model, results):
|
def after_solve(self, solver, instance, model, results):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -35,13 +35,20 @@ class StaticLazyConstraintsComponent(Component):
|
|||||||
def after_solve(self, solver, instance, model, results):
|
def after_solve(self, solver, instance, model, results):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def on_callback(self, solver, instance, model):
|
def after_iteration(self, solver, instance, model):
|
||||||
print(self.pool)
|
logger.debug("Finding violated (static) lazy constraints...")
|
||||||
|
n_added = 0
|
||||||
for c in self.pool:
|
for c in self.pool:
|
||||||
if not solver.internal_solver.is_constraint_satisfied(c.obj):
|
if not solver.internal_solver.is_constraint_satisfied(c.obj):
|
||||||
self.pool.remove(c)
|
self.pool.remove(c)
|
||||||
solver.internal_solver.add_constraint(c.obj)
|
solver.internal_solver.add_constraint(c.obj)
|
||||||
instance.found_violated_lazy_constraints += [c.cid]
|
instance.found_violated_lazy_constraints += [c.cid]
|
||||||
|
n_added += 1
|
||||||
|
if n_added > 0:
|
||||||
|
logger.debug(" %d violations found" % n_added)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
def fit(self, training_instances):
|
def fit(self, training_instances):
|
||||||
logger.debug("Extracting x and y...")
|
logger.debug("Extracting x and y...")
|
||||||
|
|||||||
@@ -95,8 +95,9 @@ def test_usage_with_solver():
|
|||||||
])
|
])
|
||||||
internal.add_constraint.reset_mock()
|
internal.add_constraint.reset_mock()
|
||||||
|
|
||||||
# LearningSolver calls callback (first time)
|
# LearningSolver calls after_iteration (first time)
|
||||||
component.on_callback(solver, instance, None)
|
should_repeat = component.after_iteration(solver, instance, None)
|
||||||
|
assert should_repeat
|
||||||
|
|
||||||
# Should ask internal solver to verify if constraints in the pool are
|
# Should ask internal solver to verify if constraints in the pool are
|
||||||
# satisfied and add the ones that are not
|
# satisfied and add the ones that are not
|
||||||
@@ -105,8 +106,9 @@ def test_usage_with_solver():
|
|||||||
internal.add_constraint.assert_called_once_with("<c2>")
|
internal.add_constraint.assert_called_once_with("<c2>")
|
||||||
internal.add_constraint.reset_mock()
|
internal.add_constraint.reset_mock()
|
||||||
|
|
||||||
# LearningSolver calls callback (second time)
|
# LearningSolver calls after_iteration (second time)
|
||||||
component.on_callback(solver, instance, None)
|
should_repeat = component.after_iteration(solver, instance, None)
|
||||||
|
assert not should_repeat
|
||||||
|
|
||||||
# The lazy constraint pool should be empty by now, so no calls should be made
|
# The lazy constraint pool should be empty by now, so no calls should be made
|
||||||
internal.is_constraint_satisfied.assert_not_called()
|
internal.is_constraint_satisfied.assert_not_called()
|
||||||
|
|||||||
@@ -19,56 +19,56 @@ def test_generator():
|
|||||||
assert len(instances) == 100
|
assert len(instances) == 100
|
||||||
assert instances[0].n_cities == 100
|
assert instances[0].n_cities == 100
|
||||||
assert norm(instances[0].distances - instances[0].distances.T) < 1e-6
|
assert norm(instances[0].distances - instances[0].distances.T) < 1e-6
|
||||||
d = [instance.distances[0,1] for instance in instances]
|
d = [instance.distances[0, 1] for instance in instances]
|
||||||
assert np.std(d) > 0
|
assert np.std(d) > 0
|
||||||
|
|
||||||
|
|
||||||
# def test_instance():
|
def test_instance():
|
||||||
# n_cities = 4
|
n_cities = 4
|
||||||
# distances = np.array([
|
distances = np.array([
|
||||||
# [0., 1., 2., 1.],
|
[0., 1., 2., 1.],
|
||||||
# [1., 0., 1., 2.],
|
[1., 0., 1., 2.],
|
||||||
# [2., 1., 0., 1.],
|
[2., 1., 0., 1.],
|
||||||
# [1., 2., 1., 0.],
|
[1., 2., 1., 0.],
|
||||||
# ])
|
])
|
||||||
# instance = TravelingSalesmanInstance(n_cities, distances)
|
instance = TravelingSalesmanInstance(n_cities, distances)
|
||||||
# for solver_name in ['gurobi', 'cplex']:
|
for solver_name in ['gurobi', 'cplex']:
|
||||||
# solver = LearningSolver(solver=solver_name)
|
solver = LearningSolver(solver=solver_name)
|
||||||
# solver.solve(instance)
|
solver.solve(instance)
|
||||||
# x = instance.solution["x"]
|
x = instance.solution["x"]
|
||||||
# assert x[0,1] == 1.0
|
assert x[0, 1] == 1.0
|
||||||
# assert x[0,2] == 0.0
|
assert x[0, 2] == 0.0
|
||||||
# assert x[0,3] == 1.0
|
assert x[0, 3] == 1.0
|
||||||
# assert x[1,2] == 1.0
|
assert x[1, 2] == 1.0
|
||||||
# assert x[1,3] == 0.0
|
assert x[1, 3] == 0.0
|
||||||
# assert x[2,3] == 1.0
|
assert x[2, 3] == 1.0
|
||||||
# assert instance.lower_bound == 4.0
|
assert instance.lower_bound == 4.0
|
||||||
# assert instance.upper_bound == 4.0
|
assert instance.upper_bound == 4.0
|
||||||
#
|
|
||||||
#
|
|
||||||
# def test_subtour():
|
def test_subtour():
|
||||||
# n_cities = 6
|
n_cities = 6
|
||||||
# cities = np.array([
|
cities = np.array([
|
||||||
# [0., 0.],
|
[0., 0.],
|
||||||
# [1., 0.],
|
[1., 0.],
|
||||||
# [2., 0.],
|
[2., 0.],
|
||||||
# [3., 0.],
|
[3., 0.],
|
||||||
# [0., 1.],
|
[0., 1.],
|
||||||
# [3., 1.],
|
[3., 1.],
|
||||||
# ])
|
])
|
||||||
# distances = squareform(pdist(cities))
|
distances = squareform(pdist(cities))
|
||||||
# instance = TravelingSalesmanInstance(n_cities, distances)
|
instance = TravelingSalesmanInstance(n_cities, distances)
|
||||||
# for solver_name in ['gurobi', 'cplex']:
|
for solver_name in ['gurobi', 'cplex']:
|
||||||
# solver = LearningSolver(solver=solver_name)
|
solver = LearningSolver(solver=solver_name)
|
||||||
# solver.solve(instance)
|
solver.solve(instance)
|
||||||
# assert hasattr(instance, "found_violated_lazy_constraints")
|
assert hasattr(instance, "found_violated_lazy_constraints")
|
||||||
# assert hasattr(instance, "found_violated_user_cuts")
|
assert hasattr(instance, "found_violated_user_cuts")
|
||||||
# x = instance.solution["x"]
|
x = instance.solution["x"]
|
||||||
# assert x[0,1] == 1.0
|
assert x[0, 1] == 1.0
|
||||||
# assert x[0,4] == 1.0
|
assert x[0, 4] == 1.0
|
||||||
# assert x[1,2] == 1.0
|
assert x[1, 2] == 1.0
|
||||||
# assert x[2,3] == 1.0
|
assert x[2, 3] == 1.0
|
||||||
# assert x[3,5] == 1.0
|
assert x[3, 5] == 1.0
|
||||||
# assert x[4,5] == 1.0
|
assert x[4, 5] == 1.0
|
||||||
# solver.fit([instance])
|
solver.fit([instance])
|
||||||
# solver.solve(instance)
|
solver.solve(instance)
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class InternalSolver(ABC):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
iteration_cb: function
|
iteration_cb: () -> Bool
|
||||||
By default, InternalSolver makes a single call to the native `solve`
|
By default, InternalSolver makes a single call to the native `solve`
|
||||||
method and returns the result. If an iteration callback is provided
|
method and returns the result. If an iteration callback is provided
|
||||||
instead, InternalSolver enters a loop, where `solve` and `iteration_cb`
|
instead, InternalSolver enters a loop, where `solve` and `iteration_cb`
|
||||||
|
|||||||
@@ -171,8 +171,15 @@ class LearningSolver:
|
|||||||
if relaxation_only:
|
if relaxation_only:
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
def iteration_cb():
|
||||||
|
should_repeat = False
|
||||||
|
for component in self.components.values():
|
||||||
|
if component.after_iteration(self, instance, model):
|
||||||
|
should_repeat = True
|
||||||
|
return should_repeat
|
||||||
|
|
||||||
logger.info("Solving MILP...")
|
logger.info("Solving MILP...")
|
||||||
results = self.internal_solver.solve(tee=tee)
|
results = self.internal_solver.solve(tee=tee, iteration_cb=iteration_cb)
|
||||||
results["LP value"] = instance.lp_value
|
results["LP value"] = instance.lp_value
|
||||||
|
|
||||||
# Read MIP solution and bounds
|
# Read MIP solution and bounds
|
||||||
|
|||||||
@@ -64,4 +64,4 @@ def test_add_components():
|
|||||||
solver.add(DynamicLazyConstraintsComponent())
|
solver.add(DynamicLazyConstraintsComponent())
|
||||||
solver.add(DynamicLazyConstraintsComponent())
|
solver.add(DynamicLazyConstraintsComponent())
|
||||||
assert len(solver.components) == 1
|
assert len(solver.components) == 1
|
||||||
assert "LazyConstraintsComponent" in solver.components
|
assert "DynamicLazyConstraintsComponent" in solver.components
|
||||||
|
|||||||
Reference in New Issue
Block a user