Python 정수 프로그래밍 - 분기 및 바인딩 방법

        분기 및 경계 방법은 순수 정수 또는 혼합 정수 프로그래밍 문제를 해결하는 데 사용할 수 있습니다. 1960년대 초 Land Doig와 Dakin et al.에 의해 제안되었습니다. 이 방법은 유연하고 컴퓨터로 풀기 쉽기 때문에 이제 정수 계획법을 푸는 중요한 방법입니다. 생산 일정 문제, 출장 판매원 문제, 공장 위치 문제, 배낭 문제 및 할당 문제를 해결하는 데 성공적으로 적용되었습니다.


분기 및 바인딩 방법

1. 정의

        제한된 최적화 문제(가능한 솔루션이 유한한)의 모든 가능한 솔루션 공간에 대해 체계적인 탐색이 적절하게 수행되며, 이는 분기 및 경계의 내용입니다. 일반적으로 전체 실현 가능한 솔루션 공간을 더 작은 부분 집합과 더 작은 부분 집합으로 반복적으로 나누어 분기(branching)라고 하고 각 부분 집합의 솔루션 집합에 대해 목적의 하한(최소 문제에 대한)을 계산하는 것을 경계(bounding)라고 합니다. 각 분기 후에 경계가 알려진 실행 가능한 솔루션 세트의 목표 값을 초과하는 하위 집합은 더 이상 분기되지 않으므로 많은 하위 집합을 무시할 수 있으며 이를 가지치기라고 합니다. 이것은 분기 및 바인딩 방법의 주요 아이디어입니다.

        최대화 정수 계획법 문제 A가 있으며 이에 대응하는 선형 계획법은 문제 B입니다. 문제 B를 푸는 것부터 시작하여 최적 솔루션이 A의 정수 조건을 충족하지 않는 경우 B의 최적 목적 함수는 최적 목적 함수여야 합니다. 최적 목적 함수의 상한 ^ {*}에서 은 로 표시되며 \overline{z} A의 실현 가능한 솔루션의 목적 함수 값은 ^ {*}에서의 하한 \ 밑줄{z}이 됩니다. 분기 및 경계 방법은 B의 실행 가능 영역을 하위 영역으로 나누는 방법입니다. 점차적으로 감소 \overline{z}및 증가 \ 밑줄{z}, 그리고 마침내 찾습니다 ^ {*}에서. 이제 다음 예를 사용하여 설명합니다.

2. 예시 설명

예제 3 다음 정수 프로그램을 풉니다.

 \begin{정렬} &\operatorname{최대} \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{배열}\right.  \end{정렬}이고 정수입니다 

        (i) 정수 제한을 무시하고, 즉 해당 선형 계획법 B를 풀면 최적의 솔루션은 다음과 같습니다.

파이썬 심플렉스 테이블을 사용하면 다음을 얻을 수 있습니다.

        objctive        |                   355.88                   |
        solution        |  4.81  |  1.82  |   0    |   0    |

최적의 솔루션은 다음과 같습니다.x_{1}=4.81, x_{2}=1.82, z=355.88

                        정수 조건을 만족하지 않음을 알 수 있다. 와 함께때 문제 ㅏ의 최적 목적 함수 값 ^ {*}에서의  로 표시됩니다 \overline{z}. 그리고  x_{1}=0,x_{2}=0분명히 ㅏ이 문제의 실현 가능한 정수 솔루션은 ,z = 0 즉 로 표시되는 의 하한입니다 .^ {*}에서\ 밑줄{z}0\leq z^{*}\leq 356

        (ii) x_{1},x_{2}현재는 모두 정수가 아니므로 정수 요구 사항을 충족하지 않으므로 그 중 하나를 선택하여 분기합니다. 분기를 위해 1 x를 선택하고 실행 가능한 집합을 2개의 하위 집합으로 나눕니다.

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

4와 5 사이에는 정수가 없으므로 이 두 부분집합의 정수 해는 원래의 가능한 집합 정수 해와 일치해야 합니다. 이 단계를 분기라고 합니다. 이 두 하위 집합의 계획 및 솔루션은 다음과 같습니다.

질문 나_{1}:

\begin{정렬} &\quad \operatorname{최대} \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{배열}\right .  \end{정렬}

최적의 솔루션은 다음과 같습니다.x_{2}=4.0,x_{2}=2.1,z_{1}=349

질문 B_{2}:

\begin{정렬} & \quad \text { 최대 } \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{배열}\right.  \end{정렬}

 최적의 솔루션은 다음과 같습니다.x_{2}=5.0,x_{2}=1.57,z_{1}=341.4

 재정의:0\leq z^{*}\leq 349

        (iii) 문제 B1을 분기하여 문제 B11과 B12를 구하고 최적의 솔루션은 다음과 같습니다.

\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{배열}

반동: 340\leq z^{*}\leq 341, ㄴ_{12}제거합니다.

        (iv B_{2}) 문제를 분기하여 문제 ㄴ_{21}ㄴ_{22}, 및 이들의 최적 솔루션은 다음과 같습니다.

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

B_{22}:  ​​​​​​​가능한 해결책                                         이 없습니다  

정리 B_{21},B_{22} 됩니다.

따라서 원래 문제에 대한 최적의 솔루션은 다음과 같다고 결론지을 수 있습니다.

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

3. 수학적 모델링 프로세스:

분기 및 경계 방법은 다음과 같이 정수 계획법(최대화) 문제를 해결합니다.

처음에 해결해야 할 정수 계획법 문제를 문제 A라고 하고 해당 선형 계획법 문제를 문제 B라고 합니다.

문제 B를 풀면 다음 상황 중 하나가 발생할 수 있습니다.

(a) B에 실현 가능한 해가 없으면 A도 실현 가능한 해가 없으면 중지합니다.

(b) B가 최적해를 갖고 문제 A의 정수 조건을 충족하면 B의 최적해가 A의 최적해가 되고 중단됩니다.

(c) B는 최적의 솔루션을 가지고 있지만 문제 A의 정수 조건을 충족하지 않는 경우 목적 함수 값을 기록합니다 \overline{z}.

관찰을 사용하여 문제 A에 대한 실행 가능한 정수 솔루션을 찾습니다. 일반적으로 x_{j}=0,j=1,...,n,  목적 함수 값을 찾아 로 기록하는 것이 좋습니다 \ 밑줄{z}. ​​​​​​​​​​​​​​​​을 사용하여  ^ {*}에서목적 함수의 값을 찾고 그것을 \underline{z}\leq z^{*}\leq \overline{z}반복으로 기록하십시오.

모델링 과정

x_{j}첫 번째 단계: 분기, B의 최적 솔루션에서 정수 조건을 충족하지 않는         변수를 선택 하고 그 값 b_{j}을 [  b_{j}]로 표시 b_{j}하여 보다 작은 가장 큰 정수를 나타냅니다. 두 개의 제약 조건 생성

 x_{j}\leq [b_{j}]그리고x_{j}\geq [b_{j}]+1

이 두 제약 조건을 각각 문제 B에 추가하고 두 개의 후속 프로그래밍 문제 나_{1}B_{2}. 정수 조건을 고려하지 않고 이 두 개의 후속 문제를 풉니다.

        구분, 각 후속 문제를 분기로 사용하여 솔루션의 결과를 표시하고 최적 목적 함수의 값이 가장 큰 문제를 다른 문제의 솔루션 결과 중 새로운 상한으로 찾습니다 \overline{z}. 정수 조건을 충족하는 각 분기에서 목적 함수의 최대값을 새로운 하한값으로 찾으십시오. \ 밑줄{z}효과가 없으면 \ 밑줄{z}변경되지 않은 상태로 유지 됩니다.

         2단계: 비교 및 ​​제거 각 분기의 최적 목적 함수가 z보다 작은 경우 이 분기를 제거합니다. 보다 크고 \ 밑줄{z}정수 조건을 충족하지 않는 경우 첫 번째 단계를 반복합니다. ^ {*}에서마지막에 = 를 얻을 때까지 \ 밑줄{z}. 최적의 정수 솔루션을 얻으려면 x_{j}^{*}, j=1,...,n.

 

 

 4. 프로그래밍 구현

Python을 사용하여 분기 및 바인딩된 알고리즘을 구현합니다.

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

 출력 결과:

####################### 开始 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.])

추천

출처blog.csdn.net/qq_21402983/article/details/126388515