禁忌搜索在离散物流选址模型中的应用

本文同样研究[上一篇]中离散的物流选址问题,考虑展示禁忌搜索算法求解该问题,问题介绍描述同样参考上一篇。
1 背景介绍
禁忌搜索算法(Tabu Search,TS)是局部搜索算法的推广,是人工智能在组合优化算法中的一个成功应用。Glover在1986年首次提出这一概念,进而形成一套完整的算法。禁忌搜索算法的特点就是采用了禁忌技术,所谓的禁忌就是禁止重复前面的工作。为了回避局部邻域搜索陷入混沌,禁忌搜索算法采用一个禁忌表记录下已经到达的可行解,在下一次搜索中,利用禁忌表中的信息不再或有选择地搜索这些点,以此来跳出局部最优点。禁忌搜索算法思路简单,应用性强,不受函数性质控制,是一种人工智能算法。
2 代码设计
代码设计同样分为两大模块,一是根据选址方案计算最小总费用,二是使用禁忌搜索寻找最优选址方案。
(1)由于考虑的问题相同,前面的参数设置部分就参考上一篇直接给出。

import pandas as pd
import numpy as np
import pulp

data = pd.read_csv(r'transcost.csv')
demand = [729, 630, 321, 293, 251, 573, 207, 732, 481, 783]
capacity = [4500, 3500, 5000, 3000, 2500]
fix_cost_lst = [304, 281, 459, 292, 241]
dic = {0:'A',1:'B',2:'C',3:'D',4:'E'}

(2)第一大模块同样直接给出代码,直接来的童鞋可以参考上一篇。

def Get_Translist(pop,data,dic):
    transcost = [list(data[dic[i]]) for i in range(len(pop)) if pop[i] == 1]
    return transcost

def Cal_total_capacity(location,capality):
    total_capacity_lst = [capality[i] for i in range(len(location)) if location[i] == 1]
    return sum(total_capacity_lst)

def fixpop(location,capacity,demand):
    while sum(demand) > Cal_total_capacity(location, capacity):
        ind = np.random.randint(0,len(location))     
        location[ind] = 1 if location[ind] == 0 else 1
    return location

def transportation_problem(costs, x_max, y_max):
    row = len(costs)
    col = len(costs[0])
    prob = pulp.LpProblem('Transportation Problem', sense=pulp.LpMinimize)
    var = [[pulp.LpVariable(f'x{i}{j}', lowBound=0, cat=pulp.LpInteger) for j in range(col)] for i in range(row)]
    flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x]
    prob += pulp.lpDot(flatten(var), costs.flatten())
    for i in range(row):
        prob += (pulp.lpSum(var[i]) <= x_max[i])
    for j in range(col):
        prob += (pulp.lpSum([var[i][j] for i in range(row)]) == y_max[j])
    prob.solve()
    return {'objective':pulp.value(prob.objective), 'var': [[pulp.value(var[i][j]) for j in range(col)] for i in range(row)]}

def Cal_cost(location):
    pop = fixpop(location,capacity,demand)
    fix_cost = 0
    capacity_new = []
    for i in range(len(pop)):
        if pop[i] == 1:
            fix_cost += fix_cost_lst[i]
            capacity_new.append(capacity[i])
    translist = Get_Translist(pop, data, dic)
    costs = np.array(translist)
    
    max_plant = capacity_new
    max_cultivation = demand
    res = transportation_problem(costs, max_plant, max_cultivation)
    return res['var'],res["objective"] + fix_cost

(3)第二部分是采用禁忌搜索寻找最优方案。首先依据当前的选址方案产生邻域,邻域的产生方法是对每个工厂依次进行抛弃/使用(0/1)的变换。由于len(location)== 5,故邻域解的个数是5,即对 location 中的每一个值进行变换后加入到邻域集合location_new_lst 中。
随后用 Cal_cost(location) 计算每一个邻域方案的值 ,并找出最小的3个值及其对应的选址方案组合成禁忌参考列表。

def Neibor(location):
    location_new_lst = []              #存储当前location的所有邻域
    for i in range(len(location)): 
        location_copy = location.copy()
        location_copy[i] = 0 if location_copy[i] == 1 else 1    #0-1变换
        location_copy = fixpop(location_copy, capacity, demand) #检验修正location
        location_new_lst.append(location_copy)                  #添加到location_new_lst
  
    cost_value = [Cal_cost(location)[1] for location in location_new_lst]  #计算每一个领域的最小费用,添加到列表
    ind_lst = find_min(3,cost_value)                            #寻找cost_value中最小的3个值所对应的索引,函数在下面给出
    best_value_lst = [cost_value[i] for i in ind_lst]	 		#添加最小值的3个值到best_value_lst
    pop_new_lst_select = [location_new_lst[i] for i in ind_lst] #添加最小值的3个值所对应的选址方案到pop_new_lst_select
    ref_lst = list(zip(best_value_lst,pop_new_lst_select))      #组合成禁忌参考列表
    return ref_lst
   
def find_min(n,lst):
    lst_copy = lst.copy()
    ind = []
    while len(ind) < n:
        for i in range(len(lst_copy)):
            if lst_copy[i] == min(lst_copy):
                ind.append(i)
                lst_copy[i] = float('inf')
    return ind[:n]

ref_lst 的 含义是“location 的所有邻域中对应的最优值及其选址方案”,它是下一步更新禁忌表及寻优选择的关键。
(4)更新禁忌表。首先选择邻域中最优的选址方案(即ref_lst[0][1]),如果它不在禁忌表中,那么将它添加到禁忌表;如果在,考虑特赦准则,即比较 ref_lst[0][0] 和当前最优值(Valueold),对禁忌表进行恰当更新。详细的更新规则请参考大佬的禁忌搜索算法过程。

def update(ref_lst,Tabu_lst,Valueold):
    mark = True
    while mark:
        if (ref_lst[0][1] not in Tabu_lst):      #如果ref_lst中的最优方案不在禁忌表中
            Tabu_lst.insert(0, ref_lst[0][1])    #在禁忌表的第0位插入之
            del Tabu_lst[-1]					 #同时删除禁忌表的最后一个禁忌元素
            mark = False
        else:
            if ref_lst[0][0] < Valueold:         #考虑特赦准则,如果ref_lst中的最优方案在禁忌表中,但是该方案对应的最优值优于目前搜索的最优值,
            									 #则删除Tabu_lst中的该方案并在第0位添加该方案
                Tabu_lst.remove(ref_lst[0][1])
                Tabu_lst.insert(0, ref_lst[0][1])
                mark = False
            else:								 #如果如果ref_lst中的最优方案在禁忌表中,同时该方案对应的最优值劣于目前搜索的最优值	
                print('Tabu works')              #则禁忌表生效,删除第一个元素,继续考虑ref_lst后面的元素				 
                del ref_lst[0] 					 
                mark = True               
    return ref_lst[0][0],ref_lst[0][1],Tabu_lst  #返回邻域中的最优值、对应选址和更新后的禁忌表

(5)下面是主函数部分。

location = [0,0,1,0,0]               #设置初始选址方案
Valueold = Cal_cost(location)[1]	 #计算初始方案的最优值
Tabu_lst = [None,None,None]			 #设置初始禁忌表
print(0,'   minvalue:',Valueold,',location:', location,'\n','    Tabu List:', Tabu_lst)
print('='*80)
count = 0
Value_lst = []						 #记录每一次寻找过程中的最优值
location_lst = []					 #记录每一次寻找过程中的最优选址方案
while count < 10:					 #迭代10次
    count += 1
    ref_lst = Neibor(location)
    Valuenew, location_new, Tabu_lst = update(ref_lst,Tabu_lst,Valueold)   #更新禁忌表,返回邻域的最优值和对应的选址方案
    print(count,'   minvalue:',Valuenew,',location:', location_new,'\n','    Tabu List:', Tabu_lst)
    print('='*80)
    Value_lst.append(Valuenew)
    location_lst.append(location_new)
    location = location_new
    if Valuenew < Valueold:          #更新当前搜索的最优值
        Valueold = Valuenew
#打印输出结果
opt = [(i,j) for i,j in zip(Value_lst,location_lst) if i == min(Value_lst)][0]
trans_schedule = Cal_cost(opt[1])[0]
print(' The best value of Tabu search is: {}'.format(opt[0]),'\n',
      'The optimal location schedule is: {}'.format(opt[1]),'\n',
      'The optimal transport schedule is: {}'.format(trans_schedule)
      )

3 运行结果

0    minvalue: 31424.0 ,location: [0, 0, 1, 0, 0] 
     Tabu List: [None, None, None]
================================================================================
1    minvalue: 19328.0 ,location: [0, 1, 1, 0, 0] 
     Tabu List: [[0, 1, 1, 0, 0], None, None]
================================================================================
2    minvalue: 15513.0 ,location: [0, 1, 1, 1, 0] 
     Tabu List: [[0, 1, 1, 1, 0], [0, 1, 1, 0, 0], None]
================================================================================
3    minvalue: 13770.0 ,location: [1, 1, 1, 1, 0] 
     Tabu List: [[1, 1, 1, 1, 0], [0, 1, 1, 1, 0], [0, 1, 1, 0, 0]]
================================================================================
4    minvalue: 13562.0 ,location: [1, 1, 0, 1, 0] 
     Tabu List: [[1, 1, 0, 1, 0], [1, 1, 1, 1, 0], [0, 1, 1, 1, 0]]
================================================================================
Tabu works
5    minvalue: 13803.0 ,location: [1, 1, 0, 1, 1] 
     Tabu List: [[1, 1, 0, 1, 1], [1, 1, 0, 1, 0], [1, 1, 1, 1, 0]]
================================================================================
Tabu works
6    minvalue: 14011.0 ,location: [1, 1, 1, 1, 1] 
     Tabu List: [[1, 1, 1, 1, 1], [1, 1, 0, 1, 1], [1, 1, 0, 1, 0]]
================================================================================
7    minvalue: 13770.0 ,location: [1, 1, 1, 1, 0] 
     Tabu List: [[1, 1, 1, 1, 0], [1, 1, 1, 1, 1], [1, 1, 0, 1, 1]]
================================================================================
8    minvalue: 13562.0 ,location: [1, 1, 0, 1, 0] 
     Tabu List: [[1, 1, 0, 1, 0], [1, 1, 1, 1, 0], [1, 1, 1, 1, 1]]
================================================================================
Tabu works
9    minvalue: 13803.0 ,location: [1, 1, 0, 1, 1] 
     Tabu List: [[1, 1, 0, 1, 1], [1, 1, 0, 1, 0], [1, 1, 1, 1, 0]]
================================================================================
Tabu works
10    minvalue: 14011.0 ,location: [1, 1, 1, 1, 1] 
     Tabu List: [[1, 1, 1, 1, 1], [1, 1, 0, 1, 1], [1, 1, 0, 1, 0]]
================================================================================
 The best value of Tabu search is: 13562.0 
 The optimal location schedule is: [1, 1, 0, 1, 0] 
 The optimal transport schedule is: 
 [[0.0, 0.0, 0.0, 0.0, 0.0, 573.0, 0.0, 0.0, 481.0, 783.0], 
  [729.0, 630.0, 0.0, 0.0, 0.0, 0.0, 207.0, 0.0, 0.0, 0.0],
  [0.0, 0.0, 321.0, 293.0, 251.0, 0.0, 0.0, 732.0, 0.0, 0.0]]

从结果可以发现禁忌搜索从第三步开始就进行循环迭代了,迭代周期是【3-4-5-6】,故算法寻找到的最优选址是[1, 1, 0, 1, 0] ,最优值和运输方案如结果所示。
4 一些想法
目前该问题的退火算法和禁忌搜索都写完了,可以看出禁忌搜索的原理还是相对简单,实现起来较容易,但求解大规模的规划问题可能就不那么理想,用遗传算法求解也写过,但确实是效果不好。个人认为遗传算法比较适合基因长度较长的大规模组合优化问题,对于本例中的小规模(选址范围只有5个工厂),每一代的最优个体比较容易在交叉变异中被破坏,因此适宜的基因片段不容易遗传到子代,自然搜索的目的就不明确,收敛效果很差。目前还在学习蚁群算法,等弄懂原理之后再来这里胡乱写点东西吧。虽然学习时间比学校少很多,但还是会抓住时间来学习的,任何幸福都来自平凡的努力和坚持,大家加油!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值