python调用求解器SCIP求解设施选址覆盖问题

1. 设施选址集合覆盖问题

  • 选址问题(Facility location problem, FLP) 分为离散型连续型两类。其中离散选址问题即设施点被选址的地点是离散的。此类问题往往设定设施点与需求点都位于在网络节点上,需求点区位确定,需求点与其 他一些节点作为设施被选点,需求点与设施被选点之间有连线相连。 离散选址问题纷繁复杂,在国外学者的研究基础上,我们认为主要的离散选址问题有:中值问题、覆盖问题、中心问题、多产品问题、动态选址问题、路径选址、多目标选址、网络中心选址问题

  • 集合覆盖问题(Set Covering Problem, SCP) 是经典组合优化问题之一,被广泛应用到航空的人员行程安排、电路设计、运输的车辆路线安排等领域。该问题已被证明是一个NP-C问题。

  • 设施选址集合覆盖问题被分为集覆盖问题(完全覆盖问题)、最大覆盖问题

    1. 集覆盖问题(Location Set Covering Problem,LSCP研究满足覆盖所有需求点顾客需求点的前提下,设施的建站个数或建设费用最小的问题;
    • 数学模型
      在这里插入图片描述
    1. 最大覆盖问题(Maximum Covering Location Problem,MCLP) 研究在备选物流中心里,如何选择p个设施,使得服务的需求点数最多或需求量最大;
    • 数学模型
      在这里插入图片描述

大白话:
1.集覆盖即要求所有的需求点都要被满足,就算为了某个需求点被覆盖满足,建立了成本很大的设施也没关系(前提假设是设施是肯定能满足所有的需求点的),所以目标是最小化成本。
2.最大覆盖问题在建造的设施数量是固定的约束下,尽可能满足足够多的需求,当然每个需求点可能有一定的权重(这个可以由需求点的人数、距离决定),所以目标是最大化满足的需求

2. 算法实现

2.1 测试数据集 OR-Library

OR-Library is a collection of test data sets for a variety of Operations Research (OR) problems.

  • 测试算例直接使用 Set covering的测试算例,代码中给出了OR-Library数据集txt文件的解析的方式。
    在这里插入图片描述

2.2 python调用SCIP求解设施选址覆盖问题完整代码:

import pandas as pd
import numpy as np
import pyscipopt as opt


class SCP_FLP(object):
    def __init__(self):
        self.num_nodes = None  # 需求点的数量
        self.num_facilities = None  # 设施数量
        self.setup_cost = None  # 每个设施的建造成本
        self.cover_matrix = 0  # cover_matrix[[i, j]], 需求点i能否被设施j是否能覆盖

    def prepare_data(self, filepath):
        # 获取数据集 OR-Library数据集数据,http://people.brunel.ac.uk/~mastjjb/jeb/orlib/scpinfo.html
        # 获取所有的行
        lines = []
        with open(filepath, 'r') as file:
            for line in file:
                lines.append(line)

        # 第一行获取服务点的数量num_nodes和仓库的数量num_facilities
        line_0 = lines[0].strip().split()
        self.num_nodes, self.num_facilities = list(map(int, line_0))
        
        iter_lines = iter(lines)
        next(iter_lines)
        # 从第二行开始获取num_facilities个设施的建造成本
        self.setup_cost = np.array([])
        for line in iter_lines:
            self.setup_cost = np.append(self.setup_cost, list(map(int, line.strip('\n').split())))
            if len(self.setup_cost) == self.num_facilities:
                break
            
        # 获取需求点是否能被设施覆盖矩阵。这段逻辑比较复杂,为了不同算例的通用性。
        idx_point = -1
        count_aux = 0
        mark_value = 0

        line_of_size = True
        self.cover_matrix = np.full((self.num_nodes, self.num_facilities), 0)
        for line in iter_lines:
            if line_of_size:
                # 一行只有一个数据的数据值mark_value,然后取下一行开始的长度为mark_value的数据集
                mark_value = int(line.strip('\n').split()[0])
                line_of_size = False
                idx_point += 1
                count_aux = 0
            else:
                line_search = line.strip('\n').split()
                count_aux += len(line_search)
                # 生成覆盖关系矩阵
                for j in line_search:
                    self.cover_matrix[idx_point][int(j) - 1] = 1

                if count_aux == mark_value:
                    line_of_size = True

    def cover_model(self):
        # 创建模型
        model = opt.Model('LSCP')

        # 添加变量:是否在j处建造设施,1则建造,0则不建造
        select = {}
        for n in range(self.num_facilities):
            select[n] = model.addVar(vtype='B', name='select_' + str(n))

        # 添加约束: 每个需求点至少被一个设施服务范围所覆盖
        for i in range(self.num_nodes):
            model.addCons(opt.quicksum(self.cover_matrix[i][j] * select[j] for j in range(self.num_facilities)) >= 1)
            
        # 设置目标
        model.setObjective(opt.quicksum(self.setup_cost[j] * select[j] for j in range(self.num_facilities)))
        model.setMinimize()
        # model.hideOutput()
        # 优化求解
        model.optimize()

        # 输出解
        print('model_obj =', model.getObjVal())
        select_facility = []
        for i in range(self.num_facilities):
            if model.getVal(select[i]) > 0:
                select_facility.append(i)
        print('select facility =', select_facility)

    def max_cover_model(self):

        np.random.seed(0)
        # 需求点权重(可以是和距离的正比的权重、需求点的需求量等等)
        weight_points = np.random.randint(10, size=self.num_nodes)
        fixed_facilities_num = int(self.num_facilities / 2)
 
        # 创建模型
        model = opt.Model('MCLP')

        # 添加变量1:设施j是否建造,0-1变量, 1则建造,0则不建造
        select = {}
        for n in range(self.num_facilities):
            select[n] = model.addVar(vtype='B', name='select_' + str(n))

        # 添加变量2:需求点i是否被覆盖,1则被覆盖,0则不被覆盖
        served = {}
        for n in range(self.num_nodes):
            served[n] = model.addVar(vtype='B', name='served_' + str(n))

        # 添加约束1: 从备选中选择的设施 = 决策设施数量
        model.addCons(opt.quicksum(select[j] for j in range(self.num_facilities)) == fixed_facilities_num)

        # 添加约束2: 若有设施j建造(select=1), 且覆盖需求点i,则由于目标最大化, served=1; 反之亦然
        for i in range(self.num_nodes):
            model.addCons(opt.quicksum(self.cover_matrix[i][j] * select[j] for j in range(self.num_facilities)) >= served[i])

        # 设置目标最大化: 在设施总数量确定的情况下,选择设施及服务点,使得需求量最大
        model.setObjective(opt.quicksum(weight_points[i] * served[i] for i in range(self.num_nodes)))
        model.setMaximize()
        # model.hideOutput()
        # 优化求解
        model.optimize()
        
if __name__ == '__main__':

    flp = SCP_FLP()
    # 算例Scp41
    flp.prepare_data('./scp41.txt')
    # 集覆盖问题
    flp.cover_model()
    # 最大覆盖问题
    flp.max_cover_model()

2.3 数据结果

  • 集覆盖问题结果
    在这里插入图片描述
  • 最大覆盖问题结果
    在这里插入图片描述

参考文献

[1] Owen S H , Daskin M S . Strategic facility location: A review[J]. Euro.j.oper.res, 1998, 111(3):423-447.

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值