• 运筹优化 | 分支定界算法(Branch and Bound)Python求解整数规划


    1. from gurobipy import *
    2. import copy
    3. import numpy as np
    4. import matplotlib.pyplot as plt
    5. plt.rcParams['font.sans-serif']=['SimHei']
    6. '''定义了一个线性松弛问题,并用Gurobi求解'''
    7. initial_LP = Model('initial LP') # 定义变量initial_LP,调用Gurobi的Model,选择Initial Programming(整数规划)模型
    8. x = {} # 创建一个空字典来存储决策变量
    9. for i in range(2): # 创建两个决策变量
    10. # 下界lb为0,上界ub为正无穷,变量类型vtype为连续型,变量名称name为x0和x1
    11. x[i] = initial_LP.addVar(lb=0,ub=GRB.INFINITY, vtype=GRB.CONTINUOUS,name = 'x_'+str(i))
    12. initial_LP.setObjective(100*x[0]+150*x[1],GRB.MAXIMIZE) # 目标函数,设置为最大化MAXIMIZE
    13. initial_LP.addConstr(2*x[0]+x[1]<=10) # 约束条件1
    14. initial_LP.addConstr(3*x[0]+6*x[1]<=40) # 约束条件2
    15. # initial_LP.optimize() # 调用求解器
    16. # for var in initial_LP.getVars():
    17. # print(var.Varname,'=',var.x)
    18. '''输出信息:
    19. Set parameter Username: 这是一个提示,通常在你的 Gurobi 环境中需要设置用户名
    20. Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (win64): 这是 Gurobi 优化器的版本信息,指出你使用的是版本 10.0.3
    21. CPU model: AMD Ryzen 5 6600H with Radeon Graphics, instruction set [SSE2|AVX|AVX2]: 这部分提供了计算机的 CPU 模型信息,以及支持的指令集
    22. Thread count: 6 physical cores, 12 logical processors, using up to 12 threads: 这部分提供了有关计算机处理器的信息,包括物理核心数量和逻辑处理器数量,以及正在使用的线程数
    23. Optimize a model with 2 rows, 2 columns and 4 nonzeros: 这部分提供了线性规划问题的规模信息。问题包含 2 个约束(rows),2 个变量(columns),以及 4 个非零元素
    24. Model fingerprint: 0x60e6e1b1: 这是问题的唯一标识,可以用于识别不同的问题实例
    25. Coefficient statistics: 这一部分提供了与问题的系数统计信息,包括矩阵范围、目标函数范围、边界范围以及右侧(约束右手边)范围
    26. Iteration Objective Primal Inf. Dual Inf. Time: 这一部分是 Gurobi 求解线性规划问题时的迭代信息。其中包括了每次迭代的目标值、主问题不可行度、对偶问题不可行度和用时
    27. Primal Inf(主问题不可行度):当 Primal Inf 的值为零时,表示找到了一个可行的解决方案,即问题的所有约束条件都得到满足。如果 Primal Inf 的值大于零,这意味着问题不是可行的,即无法找到满足所有约束条件的解决方案。Primal Inf 的绝对值越大,表示问题的不可行度越高
    28. Dual Inf(对偶问题不可行度):当 Dual Inf 的值为零时,表示对偶问题的解是可行的,这通常是好的。如果 Dual Inf 的值大于零,这意味着对偶问题是不可行的,这可能会影响原始问题的最优解'''
    29. '''定义一个名叫Node的类,用于表示分支定界算法中的节点'''
    30. class Node:
    31. '''初始化对象的属性'''
    32. def __init__(self):
    33. self.model = None
    34. self.x_sol = {} # 用于存储子问题的最优解
    35. self.x_int_sol = {} # 用于存储子问题的最优整数解
    36. self.local_LB = 0 # 用于存储子问题的最优下界
    37. self.local_UB = np.inf # 用于存储子问题的最优上界
    38. self.is_integer = False # 指示节点的最优解是否为整数
    39. self.branch_var_list = [] # 用于存储需要进行分支的变量列表
    40. '''定义深拷贝原始节点的函数'''
    41. def deepcopy(node): # node就算要深拷贝的节点
    42. new_node = Node() # 首先创建了一个新的'Node'对象
    43. new_node.local_LB = 0
    44. new_node.local_UB = np.inf
    45. # 确保了新节点的下面两个属性是独立的
    46. new_node.x_sol = copy.deepcopy(node.x_sol) # 用于存储子问题的最优整数解
    47. new_node.x_int_sol = copy.deepcopy(node.x_int_sol) # 指示节点的最优解是否为整数
    48. new_node.branch_var_list = [] # 用于存储需要进行分支的变量列表
    49. new_node.model = node.model.copy() # 将原始节点的模型进行深拷贝,以创建新节点的模型
    50. new_node.is_integer = node.is_integer # 表示新节点的整数性属性与原始节点相同
    51. return new_node
    52. '''分支定界算法函数'''
    53. def branch_and_bound(initial_LP):
    54. '''初始化上下界列表'''
    55. trend_UB = []
    56. trend_LB =[]
    57. initial_LP.optimize() # 调用求解器进行求解
    58. global_LB = 0
    59. global_UB = initial_LP.ObjVal # 将最优上界存储在global_UB中
    60. eps = 1e-3 # 阈值,可用来判断是否为整数解。比如2.38取整之后为2,与2.38相差超过eps,则认为不是整数解
    61. incumbent_node = None # 存储当前最优解的节点
    62. Gap = np.inf # 当前最优解和全局上界的差距
    63. '''分支定界的开始'''
    64. Queue = [] # 用队列实现深度优先搜索
    65. node = Node() # 创建根节点
    66. node.local_LB = 0 # 局部下界初始化为0
    67. node.local_UB = global_UB # 根节点的局部上界初始化为全局上界
    68. node.model = initial_LP.copy() # 由于是子问题,因此需要拷贝出一个独立的问题
    69. node.model.setParam('OutputFlag',0) # 子问题求解过程不需要输出详细信息
    70. Queue.append(node) # 将根节点输入队列
    71. '''分支定界算法的主循环'''
    72. cnt = 0 # 计数器
    73. while(len(Queue)>0 and global_UB - global_LB >eps) : # 当队列为空或者全局上下界之差小于阈值时退出循环
    74. cnt += 1 # 记录迭代次数
    75. # 使用深度优先搜索,后进先出
    76. # pop: 从列表中删除最后一个元素,并返回该元素的值
    77. current_node = Queue.pop() # 当前节点的线性模型
    78. current_node.model.optimize()
    79. Solution_status = current_node.model.status # 获取求解状态
    80. # 跟踪当前解的性质
    81. Is_Integer = True # 初始化为整数
    82. Is_pruned = False # 初始化为不剪枝
    83. '''若子问题的求解有效'''
    84. if(Solution_status == 2): # 当求解状态为2时,当前模型成功收敛到最优解
    85. '''检查解是否为整数'''
    86. for var in current_node.model.getVars(): # 循环遍历当前节点的所有变量
    87. current_node.x_sol[var.VarName] = var.x # 提取决策变量
    88. print(var.VarName,'=',var.x) # 例如输出 x_0 = 2.2222222222222223
    89. # 把当前解化为整数解
    90. current_node.x_int_sol[var.VarName] = (int)(var.x) # 取整后储存起来
    91. if (abs( (int)(var.x)-var.x)>= eps): # 如果取整后和原始解相差超过eps
    92. Is_Integer = False # 则认为不是整数解
    93. current_node.branch_var_list.append(var.VarName) # 添加到需要分支的列表中
    94. '''更新局部上界和局部下界'''
    95. if(Is_Integer == True): # 如果当前解是整数解
    96. '''当当前节点包含一个整数解时,这是一个非常好的情况,因为找到了一个可行的整数解,它是问题的一个潜在最优解'''
    97. current_node.local_LB = current_node.model.ObjVal # 将当前节点的局部下界更新为当前节点模型的目标函数值
    98. current_node.local_UB = current_node.model.ObjVal # 将当前节点的局部下界更新为当前节点模型的目标函数值
    99. current_node.is_integer = True # 表示当前节点包含整数解
    100. if(current_node.local_LB > global_LB): # 如果当前节点的局部下界大于全局下界
    101. global_LB = current_node.local_LB # 更新全局下界的值
    102. incumbent_node = Node.deepcopy(current_node) # 深拷贝以保存当前节点
    103. else: # 如果不是整数解
    104. '''当当前节点的解不是整数解时,不能将解视为潜在的整数最优解,因为目标是寻找整数解'''
    105. Is_Integer = False
    106. current_node.local_UB = current_node.model.ObjVal # 将当前节点的局部上界更新为当前节点模型的目标函数值
    107. if current_node.local_UB < global_LB: # 如果局部上界小于全局下界
    108. Is_pruned = True # 则剪枝
    109. current_node.is_integer = False # 设置为非整数解
    110. else:
    111. Is_pruned = False # 不剪枝
    112. current_node.is_integer = False
    113. for var_name in current_node.x_int_sol.keys(): # 遍历每个整数解
    114. var = current_node.model.getVarByName(var_name) # 获取当前节点的变量名
    115. current_node.local_LB += current_node.x_int_sol[var_name]*var.Obj # 一种启发式算法去更新局部下界
    116. # 对父节点的解执行向下取整操作,然后计算该整数解对于局部下界的目标函数值贡献
    117. # 通过向下取整解得到的解可能仍然是问题的可行解
    118. '''更新全局下界'''
    119. if (current_node.local_LB > global_LB): # 如果局部下界大于全局下界,那么该节点可以继续分支
    120. global_LB = current_node.local_LB
    121. incumbent_node = Node.deepcopy(current_node)
    122. '''分支'''
    123. branch_var_name = current_node.branch_var_list[0] # 获取需要分支节点名称的第一个
    124. # 分支的两个节点边界
    125. left_var_bound = (int)(current_node.x_sol[branch_var_name])
    126. right_var_bound = (int)(current_node.x_sol[branch_var_name])+1
    127. '''创建左右节点'''
    128. left_node = Node.deepcopy(current_node)
    129. right_node = Node.deepcopy(current_node)
    130. '''给左节点添加约束'''
    131. temp_var = left_node.model.getVarByName(branch_var_name) # 获取要分支的对象
    132. left_node.model.addConstr(temp_var <= left_var_bound,name = 'branch_left_'+str(cnt)) # 小于等于的约束
    133. left_node.model.update() # 添加条件后更新模型
    134. temp_var = right_node.model.getVarByName(branch_var_name)
    135. right_node.model.addConstr(temp_var >= right_var_bound,name = 'branch_right_'+str(cnt)) # 大于等于的约束
    136. left_node.model.update()
    137. '''节点入队'''
    138. Queue.append(left_node)
    139. Queue.append(right_node)
    140. elif(Solution_status !=2) : # 如果线性模型求解不成功
    141. Is_Integer = False
    142. Is_pruned = True
    143. '''更新上界'''
    144. temp_global_UB = 0
    145. for node in Queue: # 遍历队列的每个节点并进行求解
    146. node.model.optimize()
    147. if(node.model.status == 2):
    148. if(node.model.ObjVal >=temp_global_UB):
    149. temp_global_UB = node.model.ObjVal # 更新全局上界
    150. global_UB = temp_global_UB
    151. Gap = 100*(global_UB - global_LB)/global_LB
    152. print('Gap:',Gap,' %')
    153. trend_UB.append(global_UB)
    154. trend_LB.append(global_LB) # 下界在前面已经更新了
    155. print(' ---------------------------------- ')
    156. print(' 整数规划模型求解成功 ')
    157. print(' ---------------------------------- ')
    158. print('最优解:', incumbent_node.x_int_sol)
    159. print('最优目标函数:', global_LB)
    160. plt.figure()
    161. plt.plot( trend_LB , label="下界",marker='o')
    162. plt.plot( trend_UB, label="上界",marker='o')
    163. plt.xlabel('迭代次数',fontsize=14)
    164. plt.ylabel('边界更新',fontsize=14)
    165. plt.title("分支定界算法求解整数规划",fontsize=18)
    166. plt.legend()
    167. plt.show()
    168. return incumbent_node, Gap
    169. '''调用分支定界算法'''
    170. result, gap = branch_and_bound(initial_LP)

    63ccba7779bf4ae2acb214ecfe71f2f5.png5d719e07eae54ca49e07249934b6554b.png0ac545239e8b4f448b65d8961ffae0ad.png069e930828ec48cd9d1e0f90ffba8c42.png

  • 相关阅读:
    用VS Code运行C语言(安装VS Code,mingw的下载和安装)
    K线形态识别_镊子线
    Ceph对象存储
    IronPDF for Java 2023.9.2 Crack
    详细解读 React useCallback &useMemo
    离散优化算法和连续优化算法
    面试官:设计模式中的适配器模式是什么?
    上位机通过Modbus转Profinet网关与CGV300变频器通讯配置案例
    在线Excel转JSON工具
    String长度限制?
  • 原文地址:https://blog.csdn.net/L040514/article/details/133937154