今天做作業,要實現整數線性規劃的分枝定界法算法。找了一些網上的博客,發現都很屎,感覺自己寫的這個比較清楚、規範,所以在此記錄。如有錯誤,請指正。
from scipy.optimize import linprog
import numpy as np
import math
import sys
from queue import Queue
class ILP():
def __init__(self, c, A_ub, b_ub, A_eq, b_eq, bounds):
# 全局參數
self.LOWER_BOUND = -sys.maxsize
self.UPPER_BOUND = sys.maxsize
self.opt_val = None
self.opt_x = None
self.Q = Queue()
# 這些參數在每輪計算中都不會改變
self.c = -c
self.A_eq = A_eq
self.b_eq = b_eq
self.bounds = bounds
# 首先計算一下初始問題
r = linprog(-c, A_ub, b_ub, A_eq, b_eq, bounds)
# 若最初問題線性不可解
if not r.success:
raise ValueError('Not a feasible problem!')
# 將解和約束參數放入隊列
self.Q.put((r, A_ub, b_ub))
def solve(self):
while not self.Q.empty():
# 取出當前問題
res, A_ub, b_ub = self.Q.get(block=False)
# 當前最優值小於總下界,則排除此區域
if -res.fun < self.LOWER_BOUND:
continue
# 若結果 x 中全爲整數,則嘗試更新全局下界、全局最優值和最優解
if all(list(map(lambda f: f.is_integer(), res.x))):
if self.LOWER_BOUND < -res.fun:
self.LOWER_BOUND = -res.fun
if self.opt_val is None or self.opt_val < -res.fun:
self.opt_val = -res.fun
self.opt_x = res.x
continue
# 進行分枝
else:
# 尋找 x 中第一個不是整數的,取其下標 idx
idx = 0
for i, x in enumerate(res.x):
if not x.is_integer():
break
idx += 1
# 構建新的約束條件(分割
new_con1 = np.zeros(A_ub.shape[1])
new_con1[idx] = -1
new_con2 = np.zeros(A_ub.shape[1])
new_con2[idx] = 1
new_A_ub1 = np.insert(A_ub, A_ub.shape[0], new_con1, axis=0)
new_A_ub2 = np.insert(A_ub, A_ub.shape[0], new_con2, axis=0)
new_b_ub1 = np.insert(
b_ub, b_ub.shape[0], -math.ceil(res.x[idx]), axis=0)
new_b_ub2 = np.insert(
b_ub, b_ub.shape[0], math.floor(res.x[idx]), axis=0)
# 將新約束條件加入隊列,先加最優值大的那一支
r1 = linprog(self.c, new_A_ub1, new_b_ub1, self.A_eq,
self.b_eq, self.bounds)
r2 = linprog(self.c, new_A_ub2, new_b_ub2, self.A_eq,
self.b_eq, self.bounds)
if not r1.success and r2.success:
self.Q.put((r2, new_A_ub2, new_b_ub2))
elif not r2.success and r1.success:
self.Q.put((r1, new_A_ub1, new_b_ub1))
elif r1.success and r2.success:
if -r1.fun > -r2.fun:
self.Q.put((r1, new_A_ub1, new_b_ub1))
self.Q.put((r2, new_A_ub2, new_b_ub2))
else:
self.Q.put((r2, new_A_ub2, new_b_ub2))
self.Q.put((r1, new_A_ub1, new_b_ub1))
def test1():
""" 此測試的真實最優解爲 [4, 2] """
c = np.array([40, 90])
A = np.array([[9, 7], [7, 20]])
b = np.array([56, 70])
Aeq = None
beq = None
bounds = [(0, None), (0, None)]
solver = ILP(c, A, b, Aeq, beq, bounds)
solver.solve()
print("Test 1's result:", solver.opt_val, solver.opt_x)
print("Test 1's true optimal x: [4, 2]\n")
def test2():
""" 此測試的真實最優解爲 [2, 4] """
c = np.array([3, 13])
A = np.array([[2, 9], [11, -8]])
b = np.array([40, 82])
Aeq = None
beq = None
bounds = [(0, None), (0, None)]
solver = ILP(c, A, b, Aeq, beq, bounds)
solver.solve()
print("Test 2's result:", solver.opt_val, solver.opt_x)
print("Test 2's true optimal x: [2, 4]\n")
if __name__ == '__main__':
test1()
test2()
運行結果截圖: