Implement iteration_cb for LearningSolver; reactivate TSP

pull/3/head
Alinson S. Xavier 5 years ago
parent 5390a5b656
commit e527e75481

@ -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()

@ -23,52 +23,52 @@ def test_generator():
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

Loading…
Cancel
Save