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

news2024/11/19 8:29:00
from gurobipy import *
import copy
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']

'''定义了一个线性松弛问题,并用Gurobi求解'''
initial_LP = Model('initial LP') # 定义变量initial_LP,调用Gurobi的Model,选择Initial Programming(整数规划)模型
x = {} # 创建一个空字典来存储决策变量

for i in range(2): # 创建两个决策变量
    # 下界lb为0,上界ub为正无穷,变量类型vtype为连续型,变量名称name为x0和x1
    x[i] = initial_LP.addVar(lb=0,ub=GRB.INFINITY, vtype=GRB.CONTINUOUS,name = 'x_'+str(i))

initial_LP.setObjective(100*x[0]+150*x[1],GRB.MAXIMIZE) # 目标函数,设置为最大化MAXIMIZE
initial_LP.addConstr(2*x[0]+x[1]<=10) # 约束条件1
initial_LP.addConstr(3*x[0]+6*x[1]<=40) # 约束条件2

# initial_LP.optimize() # 调用求解器
# for var in initial_LP.getVars():
#     print(var.Varname,'=',var.x)

'''输出信息:
    Set parameter Username: 这是一个提示,通常在你的 Gurobi 环境中需要设置用户名
    Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (win64): 这是 Gurobi 优化器的版本信息,指出你使用的是版本 10.0.3
    CPU model: AMD Ryzen 5 6600H with Radeon Graphics, instruction set [SSE2|AVX|AVX2]: 这部分提供了计算机的 CPU 模型信息,以及支持的指令集
    Thread count: 6 physical cores, 12 logical processors, using up to 12 threads: 这部分提供了有关计算机处理器的信息,包括物理核心数量和逻辑处理器数量,以及正在使用的线程数
    Optimize a model with 2 rows, 2 columns and 4 nonzeros: 这部分提供了线性规划问题的规模信息。问题包含 2 个约束(rows),2 个变量(columns),以及 4 个非零元素
    Model fingerprint: 0x60e6e1b1: 这是问题的唯一标识,可以用于识别不同的问题实例
    Coefficient statistics: 这一部分提供了与问题的系数统计信息,包括矩阵范围、目标函数范围、边界范围以及右侧(约束右手边)范围
    Iteration Objective Primal Inf. Dual Inf. Time: 这一部分是 Gurobi 求解线性规划问题时的迭代信息。其中包括了每次迭代的目标值、主问题不可行度、对偶问题不可行度和用时
    Primal Inf(主问题不可行度):当 Primal Inf 的值为零时,表示找到了一个可行的解决方案,即问题的所有约束条件都得到满足。如果 Primal Inf 的值大于零,这意味着问题不是可行的,即无法找到满足所有约束条件的解决方案。Primal Inf 的绝对值越大,表示问题的不可行度越高
    Dual Inf(对偶问题不可行度):当 Dual Inf 的值为零时,表示对偶问题的解是可行的,这通常是好的。如果 Dual Inf 的值大于零,这意味着对偶问题是不可行的,这可能会影响原始问题的最优解'''

'''定义一个名叫Node的类,用于表示分支定界算法中的节点'''
class Node:
    '''初始化对象的属性'''
    def __init__(self):
        self.model = None
        self.x_sol = {} # 用于存储子问题的最优解
        self.x_int_sol = {} # 用于存储子问题的最优整数解
        self.local_LB = 0  # 用于存储子问题的最优下界
        self.local_UB = np.inf # 用于存储子问题的最优上界
        self.is_integer = False # 指示节点的最优解是否为整数
        self.branch_var_list = []  # 用于存储需要进行分支的变量列表

    '''定义深拷贝原始节点的函数'''
    def deepcopy(node): # node就算要深拷贝的节点
        new_node = Node() # 首先创建了一个新的'Node'对象
        new_node.local_LB = 0
        new_node.local_UB = np.inf
        # 确保了新节点的下面两个属性是独立的
        new_node.x_sol = copy.deepcopy(node.x_sol) # 用于存储子问题的最优整数解
        new_node.x_int_sol = copy.deepcopy(node.x_int_sol) # 指示节点的最优解是否为整数
        new_node.branch_var_list = [] # 用于存储需要进行分支的变量列表
        new_node.model = node.model.copy()  # 将原始节点的模型进行深拷贝,以创建新节点的模型
        new_node.is_integer = node.is_integer # 表示新节点的整数性属性与原始节点相同
        return new_node

'''分支定界算法函数'''
def branch_and_bound(initial_LP):
    '''初始化上下界列表'''
    trend_UB = []
    trend_LB =[]
    initial_LP.optimize() # 调用求解器进行求解
    global_LB = 0
    global_UB = initial_LP.ObjVal # 将最优上界存储在global_UB中
    eps = 1e-3 # 阈值,可用来判断是否为整数解。比如2.38取整之后为2,与2.38相差超过eps,则认为不是整数解
    incumbent_node = None # 存储当前最优解的节点
    Gap = np.inf # 当前最优解和全局上界的差距

    '''分支定界的开始'''
    Queue = [] # 用队列实现深度优先搜索

    node = Node() # 创建根节点
    node.local_LB = 0 # 局部下界初始化为0

    node.local_UB = global_UB # 根节点的局部上界初始化为全局上界

    node.model = initial_LP.copy() # 由于是子问题,因此需要拷贝出一个独立的问题
    node.model.setParam('OutputFlag',0) # 子问题求解过程不需要输出详细信息
    Queue.append(node) # 将根节点输入队列

    '''分支定界算法的主循环'''
    cnt = 0 # 计数器
    while(len(Queue)>0 and global_UB - global_LB >eps) : # 当队列为空或者全局上下界之差小于阈值时退出循环
        cnt += 1 # 记录迭代次数
        # 使用深度优先搜索,后进先出
        # pop: 从列表中删除最后一个元素,并返回该元素的值
        current_node = Queue.pop() # 当前节点的线性模型
        current_node.model.optimize()
        Solution_status = current_node.model.status # 获取求解状态

        # 跟踪当前解的性质
        Is_Integer = True # 初始化为整数
        Is_pruned = False # 初始化为不剪枝

        '''若子问题的求解有效'''
        if(Solution_status == 2): # 当求解状态为2时,当前模型成功收敛到最优解
            '''检查解是否为整数'''
            for var in current_node.model.getVars(): # 循环遍历当前节点的所有变量
                current_node.x_sol[var.VarName] = var.x # 提取决策变量
                print(var.VarName,'=',var.x) # 例如输出 x_0 = 2.2222222222222223
                # 把当前解化为整数解
                current_node.x_int_sol[var.VarName] = (int)(var.x) # 取整后储存起来

                if (abs( (int)(var.x)-var.x)>= eps): # 如果取整后和原始解相差超过eps
                    Is_Integer = False # 则认为不是整数解
                    current_node.branch_var_list.append(var.VarName) # 添加到需要分支的列表中

            '''更新局部上界和局部下界'''
            if(Is_Integer == True): # 如果当前解是整数解
                '''当当前节点包含一个整数解时,这是一个非常好的情况,因为找到了一个可行的整数解,它是问题的一个潜在最优解'''
                current_node.local_LB = current_node.model.ObjVal # 将当前节点的局部下界更新为当前节点模型的目标函数值
                current_node.local_UB = current_node.model.ObjVal # 将当前节点的局部下界更新为当前节点模型的目标函数值
                current_node.is_integer = True # 表示当前节点包含整数解
                if(current_node.local_LB > global_LB): # 如果当前节点的局部下界大于全局下界
                    global_LB = current_node.local_LB # 更新全局下界的值
                    incumbent_node = Node.deepcopy(current_node) # 深拷贝以保存当前节点

            else: # 如果不是整数解
                '''当当前节点的解不是整数解时,不能将解视为潜在的整数最优解,因为目标是寻找整数解'''
                Is_Integer = False
                current_node.local_UB = current_node.model.ObjVal # 将当前节点的局部上界更新为当前节点模型的目标函数值
                if current_node.local_UB < global_LB: # 如果局部上界小于全局下界
                    Is_pruned = True # 则剪枝
                    current_node.is_integer = False # 设置为非整数解
                else:
                    Is_pruned = False # 不剪枝
                    current_node.is_integer = False
                    for var_name in current_node.x_int_sol.keys(): # 遍历每个整数解
                        var = current_node.model.getVarByName(var_name) # 获取当前节点的变量名
                        current_node.local_LB += current_node.x_int_sol[var_name]*var.Obj # 一种启发式算法去更新局部下界
                        # 对父节点的解执行向下取整操作,然后计算该整数解对于局部下界的目标函数值贡献
                        # 通过向下取整解得到的解可能仍然是问题的可行解

                    '''更新全局下界'''
                    if (current_node.local_LB > global_LB): # 如果局部下界大于全局下界,那么该节点可以继续分支
                        global_LB = current_node.local_LB
                        incumbent_node = Node.deepcopy(current_node)

                    '''分支'''
                    branch_var_name = current_node.branch_var_list[0] # 获取需要分支节点名称的第一个
                    # 分支的两个节点边界
                    left_var_bound = (int)(current_node.x_sol[branch_var_name])
                    right_var_bound = (int)(current_node.x_sol[branch_var_name])+1

                    '''创建左右节点'''
                    left_node = Node.deepcopy(current_node)
                    right_node = Node.deepcopy(current_node)

                    '''给左节点添加约束'''
                    temp_var = left_node.model.getVarByName(branch_var_name) # 获取要分支的对象
                    left_node.model.addConstr(temp_var <= left_var_bound,name = 'branch_left_'+str(cnt)) # 小于等于的约束
                    left_node.model.update() # 添加条件后更新模型

                    temp_var = right_node.model.getVarByName(branch_var_name)
                    right_node.model.addConstr(temp_var >= right_var_bound,name = 'branch_right_'+str(cnt)) # 大于等于的约束
                    left_node.model.update()

                    '''节点入队'''
                    Queue.append(left_node)
                    Queue.append(right_node)

        elif(Solution_status !=2) : # 如果线性模型求解不成功
            Is_Integer = False
            Is_pruned = True

        '''更新上界'''
        temp_global_UB = 0
        for node in Queue: # 遍历队列的每个节点并进行求解
            node.model.optimize()
            if(node.model.status == 2):
                if(node.model.ObjVal >=temp_global_UB):
                    temp_global_UB = node.model.ObjVal # 更新全局上界
        global_UB = temp_global_UB
        Gap = 100*(global_UB - global_LB)/global_LB
        print('Gap:',Gap,' %')
        trend_UB.append(global_UB)
        trend_LB.append(global_LB) # 下界在前面已经更新了

    print(' ---------------------------------- ')
    print(' 整数规划模型求解成功 ')
    print(' ---------------------------------- ')
    print('最优解:', incumbent_node.x_int_sol)
    print('最优目标函数:', global_LB)
    plt.figure()
    plt.plot( trend_LB , label="下界",marker='o')
    plt.plot( trend_UB, label="上界",marker='o')
    plt.xlabel('迭代次数',fontsize=14)
    plt.ylabel('边界更新',fontsize=14)
    plt.title("分支定界算法求解整数规划",fontsize=18)
    plt.legend()
    plt.show()
    return incumbent_node, Gap

'''调用分支定界算法'''
result, gap = branch_and_bound(initial_LP)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1112562.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

运机转债上市价格预测

运机转债-127092 基本信息 转债名称&#xff1a;运机转债&#xff0c;评级&#xff1a;AA-&#xff0c;发行规模&#xff1a;7.3亿元。 正股名称&#xff1a;运机集团&#xff0c;今日收盘价&#xff1a;16.2元&#xff0c;转股价格&#xff1a;17.67元。 当前转股价值 转债面…

【AIGC核心技术剖析】基于大规模弱监督的鲁棒语音识别【附源码】

论文研究了语音处理系统的能力&#xff0c;该系统只是为了预测互联网上的大量音频成绩单而训练的。当扩展到 680,000 小时的多语言和多任务监督时&#xff0c;生成的模型可以很好地推广到标准基准&#xff0c;并且通常与先前的完全监督结果竞争&#xff0c;但在零镜头传输设置中…

【ArcGIS绘图系列1】在ArcGIS中制作柱状图与饼状图

成图展示 图形出处&#xff1a;J2023-Assessment of agricultural drought based on multi-source remote sensing data in a major grain producing area of Northwest China 实现步骤 第一步 查看数据信息 数据输入到ArcGIS中&#xff1a;包含数据表和shp文件 1、shp文件…

ZXing.Net 的Core平台生成二维码

一、引用 二、代码 帮助类 /// <summary>/// ZXing.NET 二维码帮助类/// </summary>public class ZXingHelper{/// <summary>/// 站点二维码的目录/// </summary>private static string QRCodeDirectory "QRCode";/// <summary>/// 使…

KingBase服务器参数配置(Kylin)

主配置文件/KingbaseES/V8/data/kingbase.conf # 可通过find查找 [默认存储在database cluseter目录中] find / -name kingbase.conf辅助参数文件kingbase.auto.conf # 可通过find查找 [默认存储在database cluseter目录中] find / -name kingbase.auto.conf查看当前会话的参…

互联网Java工程师面试题·Java 总结篇·第十弹

目录 82、JDBC 能否处理 Blob 和 Clob&#xff1f; 83、简述正则表达式及其用途。 84、Java 中是如何支持正则表达式操作的&#xff1f; 85、获得一个类的类对象有哪些方式&#xff1f; 86、如何通过反射创建对象&#xff1f; 87、如何通过反射获取和设置对象私有字段的值…

python -pandas -处理excel合并单元格问题

对于合并的单元格&#xff0c;不进行处理情况下&#xff0c;会默认输出nan问题 解决方法&#xff1a; class A(object):def __init__(self, xlsx_file_path, sheet_index):self.xlsx_file FileDataProcesser.read_excel(xlsx_file_path, sheet_index)self.sheet_data self.…

优雅而高效的JavaScript——?? 运算符、?. 运算符和 ?. 运算符

&#x1f974;博主&#xff1a;小猫娃来啦 &#x1f974;文章核心&#xff1a;优雅而高效的JavaScript——?? 运算符、?. 运算符和 ?. 运算符 文章目录 引言空值处理的挑战解决方案1&#xff1a;?? 运算符基本用法与 || 运算符的区别实际应用场景举例 解决方案2&#xff…

leetCode 392. 判断子序列 动态规划 + 优化空间 / 双指针 等多种解法

392. 判断子序列 - 力扣&#xff08;LeetCode&#xff09; 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c…

【2023】redis-stream配合spring的data-redis详细使用

目录 一、简介1、介绍2、对比二、整合spring的data-redis实现1、使用依赖2、配置类2.1、配置RedisTemplate bean2.2、异常类3、实体类3.1、User3.2、Book4、发送消息4.1、RedisStreamUtil工具类4.2、通过延时队列线程池模拟发送消息4.3、通过http主动发送消息5、&#x1f31f;消…

003数据安全传输-多端协议传输平台:Protobuf - 部署

文章目录 一、Windows环境二、Linux Centos环境三、protobuf测试3.1 新建.proto文件生成相应的类3.2 .proto生成相应的类的使用3.3 配置VS3.4 test代码 一、Windows环境 在windows下配置&#xff0c;无论protobuf是什么版本&#xff0c;IDE和编译器的版本都要保持一致。 比如…

Linux - 大括号的妙用

示例1 touch demo_{1..10}.txt示例2 touch case_{a,b,c,d}.txt示例3 touch {a,b}{1..4}.txt

第三章 内存管理 十一、虚拟内存的基本概念

目录 一、传统存储管理 1、缺点 二、局部性原理 1、时间局部性&#xff1a; 2、空间局部性&#xff1a; 三、虚拟内存的定义和特征 1、结构 ​编辑 2、定义 3、特征 &#xff08;1&#xff09;多次性: &#xff08;2&#xff09;对换性: &#xff08;3&#xff09;…

【来点小剧场--项目测试报告】个人博客系统测试报告

一、项目背景 个人博客系统采用前后端分离的方法来实现&#xff0c;使用了MySQL数据库来存储相关的数据&#xff0c;同时对Redis进行配置&#xff0c;将session会话存储在redis中以方便分布式运转&#xff0c;最后通过云服务器将项目部署到网络上。前端主要有六个页面构成&…

Vue3 + Nodejs 实战 ,文件上传项目--大文件分片上传+断点续传

目录 1.大文件上传的场景 2.前端实现 2.1 对文件进行分片 2.2 生成hash值&#xff08;唯一标识&#xff09; 2.3 发送上传文件请求 3.后端实现 3.1 接收分片数据临时存储 3.2 合并分片 4.完成段点续传 4.1修改后端 4.2 修改前端 5.测试 博客主页&#xff1a;専心_前端…

[牛客]计算机网络习题笔记_1019

1、物理层&#xff1a;以太网 调制解调器 电力线通信(PLC) SONET/SDH G.709 光导纤维 同轴电缆 双绞线等。 2、数据链路层&#xff08;网络接口层包括物理层和数据链路层&#xff09;&#xff1a;Wi-Fi(IEEE 802.11) WiMAX(IEEE 802.16) ATM DTM 令牌环 以太网 FDD…

高校教务系统登录页面JS分析——华东交通大学

高校教务系统密码加密逻辑及JS逆向 本文将介绍高校教务系统的密码加密逻辑以及使用JavaScript进行逆向分析的过程。通过本文&#xff0c;你将了解到密码加密的基本概念、常用加密算法以及如何通过逆向分析来破解密码。 本文仅供交流学习&#xff0c;勿用于非法用途。 一、密码加…

android studio打开flutter项目报红

一、android studio打开flutter项目报红&#xff0c;如下图&#xff1a; 二、解决方法&#xff1a; 2.1 在这个build.gradle添加以下代码&#xff0c;如图&#xff1a; 2.2 在build.gradle最顶部添加如下代码&#xff1a; def localProperties new Properties() def localPr…

水经注地图服务 5.0.1-rc 版发布

《水经注地图服务》&#xff08;WeServer&#xff09;是一款可快速发布全国乃至全球海量卫星影像的地图发布服务产品。 它可以轻松发布260TB级海量卫星影像&#xff0c;从而使“在内网建立一个离线版的地球”不只是一个梦想&#xff01; ​01 新版发布 水经注地图服务 5.0…

NodeMCU ESP8266 读取按键外部输入信号详解(图文并茂)

NodeMCU ESP8266 读取按键外部输入信号教程&#xff08;图文并茂&#xff09; 文章目录 NodeMCU ESP8266 读取按键外部输入信号教程&#xff08;图文并茂&#xff09;前言按键输入常用接口pinModedigitalRead 示例代码结论 前言 ESP8266如何检测外部信号的输入&#xff0c;通常…