论文复现丨物流中心选址问题:蜘蛛猴算法求解


路径优化系列文章:

物流中心选址问题

一般物流中心选址问题是指:在有限的用户(即需求点)中找出一定数量的地点建立配送中心,实现从物流中心到用户之间的配送,使得各配送中心到由它配送用户的需求量与距离的乘积之和最小。

本文假设:

(1)不考虑物流中心的规模及经济问题;

(2)配送中心必须满足所有位置的需求;

(3)不考虑其他费用。

模型

推文的测试模型包括基准函数和中心选择问题,以下是5个基准函数,可以任意增加:

在文件HS:

基准函数纬度n目前设置为20,可修改。

物流中心选址问题的目标函数和约束见参考文献:

数据

论文数据,31个城市选6个中心的数据(复制pdf数据到txt):

1 (1304,2312) 20 17 (3918,2179) 90 
2 (3639,1315) 90 18 (4061,2370) 70 
3 (4177,2244) 90 19 (3780,2212) 100 
4 (3712,1399) 60 20 (3676,2578) 50 
5 (3757,1187) 70 21 (4029,2838) 50 
6 (3326,1556) 70 22 (4263,2931) 50
7 (3238,1229) 40 23 (3429,1908) 80 
8 (4196,1044) 90 24 (3507,2376) 70 
9 (4312,790) 90 25 (3394,2643) 80 
10 (4386,570) 70 26 (3439,3201) 40 
11 (3007,1970) 60 27 (2935,3240) 40 
12 (2562,1756) 40 28 (3140,3550) 60 
13 (2788,1491) 40 29 (2545,2357) 70 
14 (2381,1676) 40 30 (2778,2826) 50 
15 (1332,695) 20 31 (2370,2975) 30 
16 (3715,1678) 80

按行和空格拆分数据,具体读取在文件read_decode_paint。

编码解码

编码

基准函数和选址问题都需要生成指定纬度下的随机初始解,2-16生成方式:

基准的最大最小值见函数定义域,对于选址问题,设置最大值为1,最小值为0。

选址问题的初始解还需转换编码:

这其中会出现问题,候选中心数大于或小于设定值。文献是选择6个点,当编码1的计数大于或者小于6,是非可行解,需要修正解。

修正方法:大于6个点时,排序编码x的所有基因值,更正最大6个以外的1为0;小于6个点时,排序编码x的所有基因值,更正不足6个外的0为1.。

如3个点选择2个中心

x=[0.6,0.55,0.8],转换编码 y=[1,1,1],修正编码为y= [1,0,1],保留最大2个,0.55对应的1修正为0

x=[0.35,0.4,0.8],转换编码 y=[0,0,1],修正编码为y= [0,1,1],补充次大的1个,0.4对应的0修正为1

核心代码:

def f_xuan(n,num,x):
    if x==[]:                             # 为空时生成初始解
        x = []
        for i in range(n):
            xx = np.random.random()       # 0到1生成初始解
            x.append(xx)
    y = []
    for xx in x:                          # 编码转化为0,1
        if xx < .5:
            y.append(0)
        else:
            y.append(1)
    idx = np.array(x).argsort()           # 排序x
    y1 = np.array(y)
    if sum(y)<num:                        # 如果1的计数小于候选数,补充1
        y1[idx[-num:]] = 1
    else:                                 # 否则删除1
        y1[idx[:-num]] = 0
    return x, y1.tolist()

解码

基准函数解码较为简单,新解传入对应函数就能返回目标。选址问题设计1种贪婪选择策略:对于每个点,计算点到所有候选中心的距离与需求量的乘积,选择乘积最小对应的中心:

代码如下:

def decode(y,inf):
    center_idx = [i for i in range(len(y)) if y[i] == 1]  # 候选位置读取
    center_road = [[] for i in range(len(center_idx))]
    f = 0
    for w in range(len(y)):
        if y[w] == 0:
            disdemand = [np.sqrt((inf[w][0]-inf[c][0])**2 + (inf[w][1]-inf[c][1])**2)*inf[w][2] for c in center_idx]
            f += min(disdemand)                          # 目标更新
            idx = disdemand.index(min(disdemand))        # 最小目标对应的候选中心
            center_road[idx].append(w)                   # 对应候选中心加入点

改进算法

算法步骤

Step 1. 设置参数。种群规模 P ,最大迭代次数 N ,干扰率 pr ,最大组数 MG ,全局 领导限制 gll,局部领导限制lll ,指引因子最大值cmax 及最小值cmin ,变异概率β。

Step 2. 用公式(2-16)生成群体中所有蜘蛛猴的初始位置。然后对每个蜘蛛猴的适应度值 进行计算并排序,选出局部和全局领导者。

Step 3. 当不满足终止条件时,执行以下步骤:

Step 3.1. 为了寻找最优解,利用(3-2)式产生新的位置;结合每只猴子适应性的大小择 优选出适宜的位置。

Step 3.2. 根据(2-19)式,得出全部组中个体被筛选的可能性;然后利用(3-4)式将选中的 蜘蛛猴进行位置更新。

Step 3.3. 对全部组的猴子筛选,选出局部及全局指引者的位置。

Step 3.4. 如果任何一个局部指引者在指定次数(即局部指引限制lll )没有更新其位置,那 么局部指引者会再次带领该组全部猴子寻找食物,根据扰动率 pr 的大小采取式(2-21)来更 新它们的位置。

Step 3.5. 执行 Cauchy 变异操作,根据式(3-5)更新蜘蛛猴的位置,选出较优的位置。

Step 3.6. 如果全局指引者在指定次数(即全局领导限制 gll )没有更新其位置,且此时没有达到最大组数 MG ,最优指引者会将群体进行分组;否则,最优指引者会把全部小组并 为一组。

3-2更新方式:

代码没有加λ,可以自行调整。

3-4更新方式:

3-5更新方式:

核心代码:

for g in range(self.N):
    if g == 0:
        Total_x, answer = self.creat(wd, hs)                      # 2-16生成初始位置            
        best_idx = answer.index(min(answer))
        gbest, pbest = Total_x[best_idx], Total_x[best_idx]       # 初始全局和局部领导者
        pbidx = [best_idx]
        pbcount = [0]
        result[0].append(g)
        result[1].append(min(answer))
    for w in range(self.P):
        r = np.random.randint(0, self.P)                          # 随机选择索引
        xr = Total_x[r]
        x = Total_x[w]
        mu = 1 - g/self.N
        xnew = x + mu*(pbest - x) + mu*(xr - x)                  # 3-2更新方式
        prob = .9*(answer[w]/max(answer)) + .1              
        if np.random.random() < prob:                            # 2-19方法概率计算,
            x = xnew
            f = answer[w]
            fmin = min(answer)
            fave = sum(answer)/len(answer)
            if f<= fave:
                c = self.cmin + (self.cmax-self.cmin)/(fave - fmin)*(f - fmin)  # c值计算方式
            else:
                c = self.cmax
            xnew = x + c*np.random.random()*(gbest - x) + c*np.random.uniform(-1,1)*(xr - x)
                                                            # 3-4计算方式
        if hs == 'f_xuan':
            x, y = rdp.f_xuan(31,6,xnew)
            center_road,center_idx,f = rdp.decode(y,inf)
        else:
            x1, f = eval(f'HS.{hs}({wd},{xnew.tolist()})')
        if f < answer[w]:                                   # 新解优于原解时更新
            Total_x[w] = xnew
            answer[w] = f
    best_idx1 = answer.index(min(answer))
    if best_idx1 == best_idx:                               # 全局最优位置不变时,gl加1
        gl += 1
    else:                                                   # 否则归0
        gl = 0
    lenth = int(self.P/len(pbidx))                          # 目前的组数
    
    for w in range(len(pbidx)):
        pbans = answer[lenth*w:lenth*(w+1)]                 # 截取每个组的目标
        pbid = pbans.index(min(pbans))                      # 每个组的最优解(局部领导)
        if pbid == pbidx[w]:                                # 局部领导位置不变时,该位置计数加1
            pbcount[w] += 1
        else:                                               # 否则归零
            pbcount[w] = 0
        if pbcount[w] == self.lll:                          # 如果达到局部领导限制
            for k in range(lenth):
                if np.random.random() < pr:                 # 一定概率下随机生成初始解
                    xnew, f = eval(f'HS.{hs}({wd},[])')
                else:
                    r = np.random.randint(0, P)                         # 否则随机选择索引
                    xr = Total_x[r]          
                    x = Total_x[w]
                    xnew = x + np.random.random()*(pbest - x) + np.random.random()*(xr - x)  # 2-21更新方式
                    f = HS.f1c(xnew)
                Total_x[lenth*w + k] = xnew
                answer[lenth*w + k] = f
    
    for w in range(self.P):                                 #  Cauchy 变异操作
        x = Total_x[w]
        if np.random.random() < self.β:
            a = 2*(1- g/self.N)
            xnew = x + a*math.tan(np.pi*(np.random.random() - .5))  # 3-5更新方式
        else:
            xnew = x
        if hs == 'f_xuan':
            x, y = rdp.f_xuan(31,6,xnew)
            center_road,center_idx,f = rdp.decode(y,inf)
        else:
            x1, f = eval(f'HS.{hs}({wd},{xnew.tolist()})')
        if f < answer[w]:                                   # 新解优于原解时更新
            Total_x[w] = xnew
            answer[w] = f

    if gl == self.gll:                                   # 如果到达全局领导限制,组数加1            
        mg += 1
    if mg == self.MG:                                    # 如果组数到达限制,归1,全局领导和局部领导相同
        mg = 1
        best_idx = answer.index(min(answer))
        pbidx = [best_idx]
    else:                                               # 没到达组数限制时
        pbidx = []
        lenth = int(self.P/mg)                          # 新的组数(+1)对应的组长度
        pbcount = []
        for mgg in range(mg):               
            pbans = answer[lenth*mgg:lenth*(mgg+1)]     # 组长度对应的目标
            bid = pbans.index(min(pbans))               # 局部领导位置
            pbidx.append(bid)
            pbcount.append(0)                           # 局部领导位置计数

结果

代码运行环境
windows系统,python3.6.0,第三方库及版本号如下:

matplotlib (3.3.4)
numpy (1.19.5)
pandas (1.1.5)
xlrd (1.2.0)
XlsxWriter (3.0.3)

第三方库需要在安装完python之后,额外安装,以前文章有讲述过安装第三方库的解决办法。

主函数

设计主函数如下:

from cmasmo import CMASMO
import paint
import HS
import read_decode_paint as rdp
import numpy as np


P = 50       # 种群规模       
N = 1000     # 迭代次数
pr = .5      # 扰动率
MG = 5       # 最大组数
gll = 50     # 全局领导限制
lll = 1500   # 局部领导限制
cmax = .8    # 最高学习因子
cmin = .3    # 最低学习因子
β = .8       # 变异概率

wd = 20      # 函数纬度,目前是20
hs = 'f1'    # 函数:f1到f5 和 f_xuan

if hs == 'f_xuan':  # 选址问题视数据而定,目前是31
    wd = 31

cm = CMASMO(P, N, pr, MG, gll, lll, cmax, cmin, β)
x,result = cm.total(wd, hs)

print('*'*20)
if hs == 'f_xuan':
    inf = rdp.get()
    x, y = rdp.f_xuan(31,6,x)              # 目前是31点选择6个中心,修正时cmasmo对应行也修正
    center_road,center_idx,f = rdp.decode(y,inf)
    print('选择中心编码:',y)
    print('对应点:',np.array(center_idx)+1)
    print('目标:',f)
    
    rdp.draw(center_road,center_idx,f,inf)
else:
    x1, f = eval(f'HS.{hs}({wd},{x})')
    print('函数解:',x)
    print('目标:',f)
rdp.draw_change(result)

运行结果

f1结果如下:

f1目标随迭代次数的变化图如下:

f2目标随迭代次数的变化图如下:

f3目标随迭代次数的变化图如下:

f4目标随迭代次数的变化图如下:

f5目标随迭代次数的变化图如下:

f_xuan结果如下:

选址图

f_xuan目标随迭代次数的变化图如下:

结论

  • 本推文主要是对参考论文进行复现,为了解可行设计了选址中心的编码修正方式,为了求解效率设计了贪婪的中心选择方式。其中基准函数容易陷入局部最优,可从算法参数、算法流程、初始解或者函数定义域等进行优化,基准函数目前是5个,其余的可自行在HS添加。

  • 数据和算法皆可修改(修改选址数据时,其一行包含1个或者2个点的相关数据,否则报错,中心点和选择个数在main和cmasmo都需修改)。

  • 参考文献:蜘蛛猴算法的改进及其在物流中心选址问题中的应用_杨转(第3章)

代码

为了方便和便于修改和测试,把代码在4个py文件里。

演示视频:

论文复现丨物流中心选址问题:蜘蛛猴算法求解

  • 22
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值