分枝限定法は、純粋整数または混合整数計画問題を解くために使用できます。1960 年代初頭に Land Doig と Dakin らによって提案されました。この方法は柔軟性があり、コンピュータで簡単に解くことができるため、現在では整数計画法を解くための重要な方法となっています。生産スケジュール問題、巡回セールスマン問題、工場立地問題、ナップザック問題、配置問題の解決にうまく適用されています。
分枝限定法
1. 定義
分岐と境界の内容である制約付き最適化問題(実行可能解が有限)のすべての実行可能解空間に対して、系統的探索が適切に実行されます。通常、実行可能な解空間全体が、分岐と呼ばれる、より小さなサブセットに繰り返し分割され、(最小問題の) 目的の下限が、各サブセット内の解セットに対して計算されます (これは、境界と呼ばれます)。各分岐の後、境界が既知の実行可能解セットのターゲット値を超えるサブセットは、それ以上分岐されないため、多くのサブセットを無視できます。これはプルーニングと呼ばれます。これが分枝限定法の主な考え方です。
最大化整数計画問題 A があり、それに対応する線形計画法が問題 B です。問題 B を解くことから始めて、その最適解が A の整数条件を満たさない場合、B の最適目的関数は最適目的関数でなければなりません。最適な目的関数の上限 は として表され
、A の実行可能な解の目的関数値
はの下限
になります。分枝限定法は、B の実行可能領域をサブ領域に分割する方法です。徐々に減らし
たり増やし
たりして、最終的に を見つけます
。次の例を使用して説明します。
2. 例の説明
例 3 次の整数計画を解きます。
そして整数です
(i) 整数制限を無視する、つまり、対応する線形計画法 B を解くと、最適解は次のようになります。
Python シンプレックス テーブルを使用すると、次の結果が得られます。
objctive | 355.88 |
solution | 4.81 | 1.82 | 0 | 0 |
最適なソリューションは次のとおりです。
整数条件を満たしていないことがわかります。このとき、 は問題
の最適な目的関数値
の で表され
ます。そして
明らかに
は問題の整数実行可能解であり、この時点
で
はの下限であり、 で示され
ます
。
(ii)現在のものはすべて非整数であるため、整数の要件を満たしていないため、そのうちの1つを選択して分岐します。分岐用に 1 つの x を選択し、実行可能セットを 2 つのサブセットに分割します。
4 と 5 の間の整数は存在しないため、これら 2 つの部分集合の整数解は、元の実行可能集合の整数解と一致する必要があります。このステップは分岐と呼ばれます。これら 2 つのサブセットの計画と解決策は次のとおりです。
質問:
最適なソリューションは次のとおりです。
質問:
最適なソリューションは次のとおりです。
再定義:
(iii) 問題 B1 を分岐して問題 B11 と B12 を得ると、それらの最適解は
リバウンド: 、
剪定します。
(iv )問題を分岐して問題
とを得ると
、それらの最適解は
.
実行可能な解決策 はありません
剪定されます。
したがって、元の問題に対する最適な解決策は次のようになると結論付けることができます。
3. 数学的モデリング プロセス:
分枝限定法は、整数計画法 (最大化) 問題を次のように解きます。
最初に、解くべき整数計画問題を問題 A と呼び、対応する線形計画問題を問題 B と呼びます。
問題 B を解決すると、次のいずれかの状況が発生する可能性があります。
(a) B に実行可能解がない場合、A にも実行可能解がない場合、停止します。
(b) B に最適解があり、問題 A の整数条件を満たしている場合、B の最適解は A の最適解であり、停止します。
(c) B には最適解がありますが、問題 A の整数条件を満たしていません。その目的関数の値 を記録します。
観測を使用して、問題 A の実行可能な整数解を見つけます。一般に 、その目的関数の値を見つけて として記録することをお勧めします
。を使用
して、その目的関数の値を見つけようとし、
反復として記録します。
モデリングプロセス
最初のステップ: 分岐、B の最適解の整数条件を満たさない変数を選択し、その値は
[
] で表され
、 未満の最大の整数を表します。2 つの制約を構築する
と
これら 2 つの制約をそれぞれ問題 B に追加し、後続の 2 つのプログラミング問題および
。整数条件を考慮せずに、これら 2 つの後続問題を解きます。
区切り、その後の各問題を分岐として解の結果を示し、他の問題の解の結果の中で最適な目的関数の値が最大のものを新しい上限として見つけます。整数条件を満たした各ブランチから、目的関数の最大値を新しい下限として見つけます。
効果がない場合
は変更されません。
ステップ 2: 比較と枝刈り. 各枝の最適な目的関数が z 未満の場合、この枝を枝刈りします。より大きく、整数条件を満たさない場合は、最初の手順を繰り返します。最後に
=になる
まで。最適な整数解
、を取得し
ます。
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.])