Python 整数プログラミング - 分枝限定法

        分枝限定法は、純粋整数または混合整数計画問題を解くために使用できます。1960 年代初頭に Land Doig と Dakin らによって提案されました。この方法は柔軟性があり、コンピュータで簡単に解くことができるため、現在では整数計画法を解くための重要な方法となっています。生産スケジュール問題、巡回セールスマン問題、工場立地問題、ナップザック問題、配置問題の解決にうまく適用されています。


分枝限定法

1. 定義

        分岐と境界の内容である制約付き最適化問題(実行可能解が有限)のすべての実行可能解空間に対して、系統的探索が適切に実行されます。通常、実行可能な解空間全体が、分岐と呼ばれる、より小さなサブセットに繰り返し分割され、(最小問題の) 目的の下限が、各サブセット内の解セットに対して計算されます (これは、境界と呼ばれます)。各分岐の後、境界が既知の実行可能解セットのターゲット値を超えるサブセットは、それ以上分岐されないため、多くのサブセットを無視できます。これはプルーニングと呼ばれます。これが分枝限定法の主な考え方です。

        最大化整数計画問題 A があり、それに対応する線形計画法が問題 B です。問題 B を解くことから始めて、その最適解が A の整数条件を満たさない場合、B の最適目的関数は最適目的関数でなければなりません。最適な目的関数の上限から ^ {*} は として表され\overline{z} 、A の実行可能な解の目的関数値から ^ {*}はの下限\ 下線 {z}になります。分枝限定法は、B の実行可能領域をサブ領域に分割する方法です。徐々に減らし\overline{z}たり増やし\ 下線 {z}たりして、最終的に を見つけますから ^ {*}次の例を使用して説明します。

2. 例の説明

例 3 次の整数計画を解きます。

 \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{整列}そして整数です 

        (i) 整数制限を無視する、つまり、対応する線形計画法 B を解くと、最適解は次のようになります。

Python シンプレックス テーブルを使用すると、次の結果が得られます。

        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つを選択して分岐します。分岐用に 1 つの x を選択し、実行可能セットを 2 つのサブセットに分割します。

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

4 と 5 の間の整数は存在しないため、これら 2 つの部分集合の整数解は、元の実行可能集合の整数解と一致する必要があります。このステップは分岐と呼ばれます。これら 2 つのサブセットの計画と解決策は次のとおりです。

質問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{整列}

最適なソリューションは次のとおりです。x_{2}=4.0,x_{2}=2.1,z_{1}=349

質問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{整列}

 最適なソリューションは次のとおりです。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 341B_{12}剪定します。

        (iv B_{2})問題を分岐して問題B_{21}とを得るとB_{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}、 未満の最大の整数を表します。2 つの制約を構築する

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

これら 2 つの制約をそれぞれ問題 B に追加し、後続の 2 つのプログラミング問題B_{1}およびB_{2}整数条件を考慮せずに、これら 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