Skip to content

Commit

Permalink
Add branch and bound solver, for fun
Browse files Browse the repository at this point in the history
  • Loading branch information
NikolajBjorner committed Dec 23, 2023
1 parent ad07e0e commit ebe5ebf
Showing 1 changed file with 99 additions and 5 deletions.
104 changes: 99 additions & 5 deletions examples/python/bincover.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,56 @@ def set_var(self, num_bits):

def __repr__(self):
return f"binof-{self.index}:weight-{self.weight}"


class BranchAndBound:
"""Branch and Bound solver.
It keeps track of a current best score and a slack that tracks bins that are set unfilled.
It blocks branches that are worse than the current best score.
In Final check it blocks the current assignment.
"""
def __init__(self, user_propagator):
self.up = user_propagator

def init(self, soft_literals):
self.value = 0
self.best = 0
self.slack = 0
self.id2weight = {}
self.assigned_to_false = []
for p, weight in soft_literals:
self.slack += weight
self.id2weight[p.get_id()] = weight

def fixed(self, p, value):
weight = self.id2weight[p.get_id()]
if is_true(value):
old_value = self.value
self.up.trail += [lambda : self._undo_value(old_value)]
self.value += weight
elif self.best > self.slack - weight:
self.assigned_to_false += [ p ]
self.up.conflict(self.assigned_to_false)
self.assigned_to_false.pop(-1)
else:
old_slack = self.slack
self.up.trail += [lambda : self._undo_slack(old_slack)]
self.slack -= weight
self.assigned_to_false += [p]

def final(self):
if self.value > self.best:
self.best = self.value
print("Number of bins filled", self.value)
for bin in self.up.bins:
print(bin.var, bin.added)
self.up.conflict(self.assigned_to_false)

def _undo_value(self, old_value):
self.value = old_value

def _undo_slack(self, old_slack):
self.slack = old_slack
self.assigned_to_false.pop(-1)

class BinCoverSolver(UserPropagateBase):
"""Represent a bin-covering problem by associating each bin with a variable
Expand All @@ -65,6 +114,7 @@ def __init__(self, s=None, ctx=None):
self.solver = s
self.initialized = False
self.add_fixed(lambda x, v : self._fixed(x, v))
self.branch_and_bound = None


# Initialize bit-vector variables for items.
Expand All @@ -86,8 +136,19 @@ def init(self):
ineq = ULT(item.var, bound)
self.solver.add(ineq)
total_weight = sum(item.weight for item in self.items)
for i in range(len(self.bins)):
self.bins[i].slack = total_weight
for bin in self.bins:
bin.slack = total_weight

#
# Register optional branch and bound weighted solver.
# If it is registered, it
def init_branch_and_bound(self):
soft = [(bin.var, 1) for bin in self.bins]
self.branch_and_bound = BranchAndBound(self)
self.branch_and_bound.init(soft)
for bin in self.bins:
self.add(bin.var)
self.add_final(lambda : self.branch_and_bound.final())

def add_bin(self, min_bound):
assert not self.initialized
Expand Down Expand Up @@ -165,6 +226,9 @@ def _exclude_item2bin(self, item, bin):

# Callback from Z3 when an item gets fixed.
def _fixed(self, _item, value):
if self.branch_and_bound and is_bool(value):
self.branch_and_bound.fixed(_item, value)
return
item = self._itemvar2item(_item)
if item is None:
print("no item for ", _item)
Expand Down Expand Up @@ -271,7 +335,6 @@ def optimize(self):
print(bin, bin.added)



def example1():
s = OptimizeBinCoverSolver()
i1 = s.add_item(2)
Expand All @@ -283,6 +346,37 @@ def example1():
b3 = s.add_bin(1)
s.optimize()

example1()
#example1()


class BranchAndBoundCoverSolver:
def __init__(self):
self.solver = Solver()
self.bin_solver = BinCoverSolver(self.solver)

def init(self):
self.bin_solver.init()
self.bin_solver.init_branch_and_bound()

def add_item(self, weight):
return self.bin_solver.add_item(weight)

def add_bin(self, min_bound):
return self.bin_solver.add_bin(min_bound)

def optimize(self):
self.init()
self.solver.check()

def example2():
s = BranchAndBoundCoverSolver()
i1 = s.add_item(2)
i2 = s.add_item(4)
i3 = s.add_item(5)
i4 = s.add_item(2)
b1 = s.add_bin(3)
b2 = s.add_bin(6)
b3 = s.add_bin(1)
s.optimize()

example2()

0 comments on commit ebe5ebf

Please sign in to comment.