The branch and bound method can be used to solve pure integer or mixed integer programming problems. Proposed by Land Doig and Dakin et al. in the early 1960s. Because this method is flexible and easy to solve by computer, it is now an important method for solving integer programming. It has been successfully applied to solve the production schedule problem, the traveling salesman problem, the factory location problem, the knapsack problem and the allocation problem.
branch and bound method
1. Definition
A systematic search is properly performed on all feasible solution spaces of a constrained optimization problem (whose feasible solutions are finite), which is the content of branching and bounding. Usually, the whole feasible solution space is repeatedly divided into smaller and smaller subsets, which is called branching; and a lower bound of the objective (for the minima problem) is calculated for the solution set in each subset, which is called bounding. After each branch, those subsets whose bounds exceed the target value of the known feasible solution set will not be further branched, so that many subsets can be ignored, which is called pruning. This is the main idea of the branch and bound method.
There is a maximizing integer programming problem A, and its corresponding linear programming is problem B. Starting from solving problem B, if its optimal solution does not meet the integer conditions of A, then the optimal objective function of B must be the optimal objective function of A. The upper bound of the optimal objective function is denoted as ; and the objective function value of any feasible solution of A will be a lower bound of . The branch and bound method is a method of dividing the feasible region of B into sub-regions. Gradually decrease and increase , and finally find . Now use the following example to illustrate:
2. Explanation of examples
Example 3 Solve the following integer program:
and is an integer
(i) Disregarding the integer restriction, that is, solving the corresponding linear programming B, the optimal solution is:
Using the python simplex table yields:
objctive | 355.88 |
solution | 4.81 | 1.82 | 0 | 0 |
The optimal solution is:
It can be seen that it does not meet the integer condition. At this time is the upper bound of the optimal objective function value of the problem, denoted as . And obviously is an integer feasible solution of the problem , at this time , is a lower bound of , denoted by , ie .
(ii) Because the current ones are all non-integers, they do not meet the integer requirements, so choose one of them to branch. Let 1 x be chosen for branching and divide the feasible set into 2 subsets:
Since there are no integers between 4 and 5, the integer solutions for these two subsets must agree with the original feasible set integer solutions. This step is called branching. The planning and solutions of these two subsets are as follows:
Question :
The optimal solution is:
Question :
The optimal solution is:
Redefine:
(iii) Branch the problem B1 to obtain the problems B11 and B12, and their optimal solutions are
Rebound: , and will prune.
(iv ) Branch the problem to obtain the problem and , and their optimal solutions are
.
No feasible solution.
Will be pruned.
So it can be concluded that the optimal solution to the original problem is:
3. Mathematical modeling process:
The branch and bound method solves the integer programming (maximization) problem as follows:
Initially, the integer programming problem to be solved is called problem A, and the corresponding linear programming problem is called problem B.
Solving problem B may yield one of the following situations:
(a) If B has no feasible solution, then A also has no feasible solution, then stop.
(b) B has an optimal solution and meets the integer condition of problem A, the optimal solution of B is the optimal solution of A, then stop.
(c) B has an optimal solution, but does not meet the integer condition of problem A, record its objective function value .
Use observation to find an integer feasible solution to problem A. It is generally advisable to try to find its objective function value and record it as . Using try to find out the value of its objective function, and record it as iterating.
modeling process
The first step: branch, choose a variable that does not meet the integer condition in the optimal solution of B, and its value is represented by [ ] to represent the largest integer less than . Construct two constraints
and
Add these two constraints to problem B respectively, and find two successor programming problems and . Solve these two successor problems without considering the integer condition.
Delimitation, take each subsequent problem as a branch to indicate the result of the solution, and find the one with the largest value of the optimal objective function as the new upper bound among the results of the solutions of other problems . From each branch that has met the integer condition, find out the maximum value of the objective function as the new lower bound , if there is no effect, it will remain unchanged .
Step 2: Compare and prune. If any optimal objective function of each branch is less than z, prune this branch, that is, it will not be considered in the future. If it is greater than and does not meet the integer condition, repeat the first step. until you get = at the end. to obtain the optimal integer solution , .
4. Programming implementation
Use python to implement the branch and bound algorithm:
from scipy.optimize import linprog
import numpy as np
from math import floor, ceil
import copy
class Node(object):
def __init__(self, x_bounds=[], freeze_var_list=[], index=0, upper_or_lower=0):
self._x_bounds = x_bounds
self._freeze_var_list = freeze_var_list
self._index = index
self._upper_or_lower = upper_or_lower
print("创建节点: {}".format(index))
print('')
def freeze_var(self, index, val):
self._x_bounds[index][0] = val
self._x_bounds[index][1] = val
self._freeze_var_list.append(index)
def set_lp_res(self, res):
self._res = res
s = ""
for l in range(len(self._res['x'])):
if l in self._freeze_var_list:
s += "[" + str(self._res['x'][l]) + "]"
else:
s += " " + str(self._res['x'][l])
print("x: ", s)
def check_integer_val_solved(self, m):
return True if m == len(self._freeze_var_list) else False
class BbAlgorithm(object):
def __init__(self, c, a_ub, b_ub, x_b, integer_val):
self.c = c
self.a_ub = a_ub
self.b_ub = b_ub
self.x_b = x_b
self._integer_val = integer_val
self.best_solution = float('inf')
self.best_node = None
self.nodes = []
self.nodes_solution = []
def solve_lp(self, cur_x_b):
return linprog(self.c, A_ub=self.a_ub, b_ub=self.b_ub, bounds=cur_x_b)
def check_fessible(self, res):
if res['status'] == 0:
return True
elif res['status'] == 2:
return False
else:
raise ("问题无界")
def add_node(self, node):
res = self.solve_lp(node._x_bounds)
if self.check_fessible(res) and res['fun'] < self.best_solution:
node.set_lp_res(res)
self.nodes_solution.append(res['fun'])
self.nodes.append(node)
if node.check_integer_val_solved(len(self._integer_val)):
self.best_solution = res['fun']
self.best_node = node
print("----------------当前解决方案-------------------")
print("x: ", node._res['x'])
print("z: ", node._res['fun'])
print("---------------------------------------------------\n")
print("==> 将节点添加到树列表: ", node._index)
print("==> 当前节点: ", self.nodes_solution)
print("")
return True
else:
print("==> 节点不可行: ", node._index)
print("==> 当前节点: ", self.nodes_solution)
print("")
return False
def del_higher_val_node(self, z_s):
del_list = []
for i in range(len(self.nodes_solution)):
if self.nodes_solution[i] >= z_s:
del_list.append(i)
s = ""
for i in del_list:
s += " " + str(self.nodes[i]._index)
print("删除节点: ", s)
self.nodes = list(np.delete(self.nodes, del_list))
self.nodes_solution = list(np.delete(self.nodes_solution, del_list))
print("当前节点: ", self.nodes_solution)
print("")
def del_item(self, index):
print("删除节点: ", self.nodes[index]._index)
self.nodes = list(np.delete(self.nodes, index))
self.nodes_solution = list(np.delete(self.nodes_solution, index))
print("当前节点: ", self.nodes_solution)
print("")
def check_bounds(self, temp_x_b, index, u_or_l):
if u_or_l == 1:
if self.x_b[index][0] is not None and temp_x_b[index][0] is None:
return False
elif self.x_b[index][0] is None and temp_x_b[index][0] is not None:
return True
elif self.x_b[index][0] is not None and temp_x_b[index][0] is not None:
return False if(self.x_b[index][0] > temp_x_b[index][0]) else True
elif u_or_l == 2:
if self.x_b[index][1] is not None and temp_x_b[index][1] is None:
return False
elif self.x_b[index][1] is None and temp_x_b[index][1] is not None:
return True
elif self.x_b[index][1] is not None and temp_x_b[index][1] is not None:
return False if(self.x_b[index][1] < temp_x_b[index][1]) else True
else:
print("界限误差")
exit()
def run(self):
print("####################### 开始 B & B #####################\n")
node_count = 0
node = Node(copy.deepcopy(self.x_b), [], node_count)
node_count += 1
res = self.solve_lp(self.x_b)
lower = floor(res['x'][self._integer_val[0]])
upper = lower + 1
lower_node = Node(copy.deepcopy(self.x_b), [], node_count, 1)
lower_node.freeze_var(self._integer_val[0], lower)
self.add_node(lower_node)
node_count += 1
upper_node = Node(copy.deepcopy(self.x_b), [], node_count, 2)
upper_node.freeze_var(self._integer_val[0], upper)
self.add_node(upper_node)
node_count += 1
while len(self.nodes) > 0:
index = np.argmin(self.nodes_solution)
x_b = self.nodes[index]._x_bounds
freeze_list = self.nodes[index]._freeze_var_list
res = self.nodes[index]._res
freeze_var_index = len(freeze_list)
lower = floor(res['x'][self._integer_val[freeze_var_index]])
upper = lower + 1
lower_node = Node(copy.deepcopy(x_b), copy.deepcopy(freeze_list), node_count, 1)
lower_node.freeze_var(self._integer_val[freeze_var_index], lower)
self.add_node(lower_node)
node_count += 1
upper_node = Node(copy.deepcopy(x_b), copy.deepcopy(freeze_list), node_count, 2)
upper_node.freeze_var(self._integer_val[freeze_var_index], upper)
self.add_node(upper_node)
node_count += 1
self.del_item(index)
self.del_higher_val_node(self.best_solution)
print("############################################################")
print("")
print("######################### 最佳解决方案 #######################")
print(self.best_node._res)
if __name__ == "__main__":
integer_val = [0,1]
c = [-40, -90]
A = [[9, 7], [7, 20]]
b = [56,70]
x_bounds = [[0, None] for _ in range(len(c))]
bb_algorithm = BbAlgorithm(c, A, b, x_bounds, integer_val)
bb_algorithm.run()
Output result:
####################### 开始 B & B #####################
创建节点: 0
创建节点: 1
x: [4.0] 2.0999999999901706
==> 将节点添加到树列表: 1
==> 当前节点: [-348.99999999911535]
创建节点: 2
x: [5.0] 1.5714285714280996
==> 将节点添加到树列表: 2
==> 当前节点: [-348.99999999911535, -341.4285714285289]
创建节点: 3
x: [4.0][2.0]
----------------当前解决方案-------------------
x: [4. 2.]
z: -340.0
---------------------------------------------------
==> 将节点添加到树列表: 3
==> 当前节点: [-348.99999999911535, -341.4285714285289, -340.0]
创建节点: 4
==> 节点不可行: 4
==> 当前节点: [-348.99999999911535, -341.4285714285289, -340.0]
删除节点: 1
当前节点: [-341.4285714285289, -340.0]
删除节点: 3
当前节点: [-341.4285714285289]
############################################################
创建节点: 5
==> 节点不可行: 5
==> 当前节点: [-341.4285714285289]
创建节点: 6
==> 节点不可行: 6
==> 当前节点: [-341.4285714285289]
删除节点: 2
当前节点: []
删除节点:
当前节点: []
############################################################
######################### 最佳解决方案 #######################
con: array([], dtype=float64)
fun: -340.0
message: 'The solution was determined in presolve as there are no non-trivial constraints.'
nit: 0
slack: array([6., 2.])
status: 0
success: True
x: array([4., 2.])