Python integer programming - branch and bound method

        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 from ^ {*} is denoted as \overline{z} ; and the objective function value of any feasible solution of A will from ^ {*}be a lower bound \ underline {z}of . The branch and bound method is a method of dividing the feasible region of B into sub-regions. Gradually decrease \overline{z}and increase \ underline {z}, and finally find from ^ {*}. Now use the following example to illustrate:

2. Explanation of examples

Example 3 Solve the following integer program:

 \begin{aligned} &\operatorname{Max} \quad z=40 x_{1}+90 x_{2} \\ &\left\{\begin{array}{l} 9 x_{1}+7 x_{2} \leq 56 \\ 7 x_{1}+20 x_{2} \leq 70 \\ x_{1}, x_{2} \geq 0 \ \end{array}\right. \end{aligned}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:x_{1}=4.81, x_{2}=1.82, z=355.88

                        It can be seen that it does not meet the integer condition. At this time Withis the A 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 .from ^ {*}\overline{z}x_{1}=0,x_{2}=0Az = 0from ^ {*}\ underline {z}0\leq z^{*}\leq 356

        (ii) Because x_{1},x_{2}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:

x_{1}\leq [4.81]=4,x_{1}\geq [4.81]+1=5

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 B_{1}:

\begin{aligned} &\quad \operatorname{Max} \quad z=40 x_{1}+90 x_{2} \\ &\left\{\begin{array}{l} 9 x_{1}+7 x_{2} \leq 56 \\ 7 x_{1}+20 x_{2} \leq 70 \\ 0 \leq x_{1} \leq 4, x_{2} \geq 0 \end{array}\right. \end{aligned}

The optimal solution is:x_{2}=4.0,x_{2}=2.1,z_{1}=349

Question B_{2}:

\begin{aligned} & \quad \text { Max } \quad z=40 x_{1}+90 x_{2} \\ &\left\{\begin{array}{l} 9 x_{1}+7 x_{2} \leq 56 \\ 7 x_{1}+20 x_{2} \leq 70 \\ x_{1} \geq 5, x_{2} \geq 0 \end{array}\right. \end{aligned}

 The optimal solution is:x_{2}=5.0,x_{2}=1.57,z_{1}=341.4

 Redefine:0\leq z^{*}\leq 349

        (iii) Branch the problem B1 to obtain the problems B11 and B12, and their optimal solutions are

\begin{array}{ll} B_{11}: & x_{1}=4, x_{2}=2, z_{11}=340 \\ B_{12}: & x_{1}=1.43, \mathrm{x}_{2}=3.00, z_{12}=327.14 \end{array}

Rebound: 340\leq z^{*}\leq 341, and will B_{12}prune.

        (iv B_{2}) Branch the problem to obtain the problem B_{21}and B_{22}, and their optimal solutions are

B_{21}:x_{1}=5.44,x_{2}=1.00,z_{22}=308.

                                        ​​​​​​​No  B_{22}:  feasible solution.

Will be B_{21},B_{22} pruned.

So it can be concluded that the optimal solution to the original problem is:

x_{1}=4,x_{2}=2,z^{*}=340
 

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 \overline{z}.

Use observation to find an integer feasible solution to problem A. It is generally advisable to x_{j}=0,j=1,...,n,  try to find its objective function value and record it as \ underline {z}. Using  from ^ {*}​​​​​​​try to find out the value of its objective function, and record it as \underline{z}\leq z^{*}\leq \overline{z}iterating.

modeling process

        The first step: branch, choose a variable x_{j}that does not meet the integer condition in the optimal solution of B, and its value is represented b_{j}by [  b_{j}] to represent b_{j}the largest integer less than . Construct two constraints

 x_{j}\leq [b_{j}]andx_{j}\geq [b_{j}]+1

Add these two constraints to problem B respectively, and find two successor programming problems B_{1}and B_{2}. 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 \overline{z}. From each branch that has met the integer condition, find out the maximum value of the objective function as the new lower bound \ underline {z}, if there is no effect, it \ underline {z}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 \ underline {z}and does not meet the integer condition, repeat the first step. until you get from ^ {*}= at the \ underline {z}end. to obtain the optimal integer solution x_{j}^{*}, j=1,...,n.

 

 

 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.])

Guess you like

Origin blog.csdn.net/qq_21402983/article/details/126388515