github项目
该project项目的:
(1)71个测例
(2)源代码
(3)71个测例的计算结果存储文件(包括 分配方案总花费-运行时间表
和 每个测例的具体分配方案
)
都能在我的github上获取
问题描述
这是有容量限制的工厂选址问题:
(1)我们需要从待选的n个工厂中选择k个工厂来开放给用户使用
(2)我们需要将N个用户分配到k个开放的工厂
(3)要求分配到某个工厂的全部用户的需求总和不超过该工厂的容量
我们的目的是最小化总开销,总开销
包括 开放k个工厂的开销
和 将N个用户分配到k个工厂的开销
问题数据集
问题数据集我放在GitHub项目中的Instances文件夹下
数据集解释如下:
以第一个测例p1为例
(1)第一行的第一个数字表示工厂数n,第二个数表示顾客数N
(2)后续的n行表示n个工厂的容量capacity和开放费用cost
(2)后续的N个数字表示N个顾客的需求
(3)
1.再后续的N个数字表示将N个顾客分配到第1个工厂的花费
2.再后续的N个数字表示将N个顾客分配到第2个工厂的花费
3.再后续的N个数字表示将N个顾客分配到第3个工厂的花费
4.再后续的N个数字表示将N个顾客分配到第4个工厂的花费
…
n.再后续的N个数字表示将N个顾客分配到第n个工厂的花费
解题算法
本项目自己采用3种不同的算法进行求解:
(1)局部搜索(爬山法)
(2)模拟退火
(3)禁忌搜索
它们分别对应于源代码CFLP.py中的三个类LocalSearchSol、SimulatedAnnealingSol、TabuSearchSol
三各类逐个读入p1到p71,然后使用三种算法求解该问题
得到的运算结果(分配方案总花销)、求解用时分别存储到Result Table Of Local Search、Result Table Of Simulated Annealing、Result Table Of Tabu Search三个文件中
得到的分配方案分别存储到文件夹Detailed Solution Of Local Search、Detailed Solution Of Simulated Annealing、Detailed Solution Of Tabu Search中
分配方案的表示方法、分配方案对应的总花费求法
该问题的每一个解都可以使用一个N个元素的数组allocation表示,allocation[i]代表第i个用户分配的工厂标号,给定一个allocation数组,可以得出一种分配方案,求解出唯一的花费:
(1)使用该数组可得出开放了哪些工厂(没有一个用户分配到的工厂就不开放,有一个或者以上顾客分配到就开放这个工厂),从而求出开工厂的费用
(2)然后根据allocation数组求出将顾客分配到工厂的费用
(1)和(2)相加就得到总花费
局部搜索解法(爬山法)
该算法的基本步骤:
(1)随机生成一个可行解(每个工厂全部用户需求总和不超过该工厂的容量),也就是一个随机的allocation数组,具体方法是,遍历每一个用户i,随即将其分配到工厂j,若这个工厂容量足够则接受该分配,否则再生成下一个随机数j’,将顾客分配到下一个工厂j’(不用管该工厂是否开放),直到分配到一个容量足够的工厂。然后我们得到一个allocation数组,按照前面提到的分配方案对应花费求法可求得总花费
(2)从当前解出发,生成一个邻居解,如果这个邻居解优于当前解,则用它替代当前解,否则丢弃它,重新做(2)。该步骤的关键是如何生成邻居解,我使用的策略如下:随机将某个用户分配到不同于当前所在工厂的另一个工厂(不管这个工程是否开放),只要分配到的新工厂的剩余容量大于该用户需求即可;当我们找到一个满足条件的邻居,我们需要更新开放工厂的列表(原来的工厂少了一个用户,如果因此用户数变为0,需要关闭该工厂,新分配到的工厂若之前未开放,需要将其开放)、更新工厂剩余容量(原工厂容量增加,新工厂剩余容量减少);
(3)上面的(2)步骤反复执行,直到迭代100000次,结束算法,将此时的解作为算法的结果输出、存储。
模拟退火解法
该算法的基本步骤:
(0)设置初始温度T0,最低温度
T
m
i
n
T_{min}
Tmin
(1)随机生成一个可行解,求出其花费cost,对应于改状态的能量E,生成方案同生成方案同爬山法中的(1)
(2)从当前解出发,生成一个邻居解,求解出该邻居解的花费cost’,对应于改状态的能量E’,该邻居解的生成方案同爬山法中(2)生成邻居解的方案
(3)如果
Δ
E
=
E
′
−
E
\Delta E = E' - E
ΔE=E′−E小于0,则接受这个新生成的邻居解,用它替代当前解,并设置当前状态能量E=E’;否则
Δ
E
=
E
′
−
E
\Delta E = E' - E
ΔE=E′−E大于0,则以一定的概率接受新生成的邻居解,接受概率为
P
k
=
e
−
Δ
E
T
P_k = e^{\frac{-\Delta E}{T}}
Pk=eT−ΔE,T为当前温度
(4)上面的(2)-(3)步骤反复执行,直到迭代1000次,此时进入步骤(5)
(5)将当前温度更新为T=T*α,如果更新后T大于
T
m
i
n
T_{min}
Tmin重新转到步骤(2),否则转到步骤(6),我的实现中,α取0.99
(6)算法结束,将此时的将此时的解作为算法的结果输出、存储。
禁忌搜索解法
(0)初始化禁忌表,在我的实现中,禁忌表中存储的项目是某个生成邻居解的操作,该生成邻居解的操作同爬山法中(2)生成邻居解的操作,这个操作可以使用一个数对(customer,facility)表示,意思是将customer标识的顾客分配到facility标识的工厂;(后面还会提到禁忌表的优化)
(1)随机生成一个可行解,求出其花费cost,生成方案同生成方案同爬山法中的(1)
(2)生成当前解s的一个邻居解集合set,并选取其中最优邻居s’,生成一个邻居解的操作同爬山法中的(2)的操作
(3)使用该邻居解替代当前解,即s=s’
(4)如果更新后的解s’优于历史最优解,那么启用特赦准则,不管 从s生成s'的操作
是否在禁忌表中,都接受这个解,则跳过步骤(5)进入步骤(6);否则如果更新后的解s’不优于历史最优解,那么需要进入步骤(5)重新选择最佳邻居解
(5)仍然使用(2)中的set,从set中去除被禁忌的邻居,得到新的邻域set’(即从s生成该邻居时用到的操作在禁忌表中),选取新的邻域set’中的最优邻居s’’,用s’‘替换s,即s=s’'
(6)更新禁忌表,就是将从 上一次迭代的解
得到 本次迭代解
的 操作
加入到禁忌表,最简单的想法就是维护一个固定长度的队列,每次产生新解就想队列添加最近的一次操作,然后判断邻居是否被禁忌的时候就查询该队列,每次查询复杂度为O(n)。所以可以用其他方案优化:因为每一个生成邻居解的操作,这个操作可以使用一个数对(customer,facility)表示,所以全部的操作一共有n*N种,我们可以维护一个二维数组tabu_list[n][N],其中每个元素tabu_list[customer][facility]的值指示操作(customer,facility)是否被禁忌。每次产生新解我们就用产生新解的操作(customer,facility)更新禁忌表的元素tabu_list[customer][facility],将其设置为 当前的代数
(当前迭代执行到第几次)+ 禁忌代数
,然后判断邻居是否被禁忌的方法:判断生成新邻居的操作(customer,facility)是否被禁忌:tabu_list[customer][facility]是否小于当前迭代次数,如果是,则生成新邻居未被禁忌,否则该邻居被禁忌
(7)重复步骤(2)到(6),直到迭代100000次,结束算法,将此时的解作为算法的结果输出、存储。
71个测例的计算结果(包括分配方案总花费-运行时间表、每个测例的具体分配方案)
由于代码过长,所以这里先展示自己三个算法对71个测例的计算结果,在最后面再给出源代码
局部搜索解法(爬山法)
使用该解法求解71个测例:
(1)分配方案总花费-算法运行时间表(Result-table)
(2)每个测例的具体分配方案:(Detailed solution)
由于一共有71个测例,对应71个分配方案,内容过多,下面仅仅给出那么,前几个,更多的测例分配方案计算结果见github项目下的Detailed Solution Of Local Search文件夹
这里是前几个测例分配方案:
p1总花销对应开工厂方案、分配方案
p2总花销对应开工厂方案、分配方案
p3总花销对应开工厂方案、分配方案
p4总花销对应开工厂方案、分配方案
p5总花销对应开工厂方案、分配方案
模拟退火解法
使用该解法求解71个测例:
(1)分配方案总花费-算法运行时间表(Result-table)
(2)每个测例的具体分配方案:(Detailed solution)
由于一共有71个测例,对应71个分配方案,内容过多,下面仅仅给出那么,前几个,更多的测例分配方案计算结果见github项目下的Detailed Solution Of Simulated Annealing文件夹
这里是前几个测例分配方案:
p1总花销对应开工厂方案、分配方案
p2总花销对应开工厂方案、分配方案
p3总花销对应开工厂方案、分配方案
p4总花销对应开工厂方案、分配方案
p5总花销对应开工厂方案、分配方案
禁忌搜索解法
使用该解法求解71个测例:
(1)分配方案总花费-算法运行时间表(Result-table)
(2)每个测例的具体分配方案:(Detailed solution)
由于一共有71个测例,对应71个分配方案,内容过多,下面仅仅给出那么,前几个,更多的测例分配方案计算结果见github项目下的Detailed Solution Of Tabu Search文件夹
这里是前几个测例分配方案:
p1总花销对应开工厂方案、分配方案
p2总花销对应开工厂方案、分配方案
p3总花销对应开工厂方案、分配方案
p4总花销对应开工厂方案、分配方案
p5总花销对应开工厂方案、分配方案
算法比较&总结
我们首先将三种算法算出来的三个Result Table合起来看一下(result table包含每种算法计算出的分配方案总开销、算法计算用时情况):
从上面的结果我们可以看到,使用局部搜索(爬山法)求解分配方案用时很短,模拟退火用时中等(较短),而禁忌搜索则需要较长时间(有些甚至达到一分钟)
但是虽然局部搜索(爬山法)求解分配方案用时短,但是求得的解效果很差,在上述的71个测例求解分配方案种,爬山法得出的方案总花销全部远超其它两种方法,足以看出爬山法的特点:搜索过程中快速接近最优解,但是极易陷入局部极小点而停止更新解,最终解的好坏与初始解的选择有很大关系(因此只有很小概率得到比较好的解)
而模拟退火算法则在该问题中有比较好的表现,它能够在较短的时间内稳定求解出比较好的解法,在71个测例求解分配方案中,模拟退火的得出的方案有52个是三个解法中最优的。这是因为模拟退火为了防止陷入局部最优,会以一定的概率接受差解,从而跳出局部最优,接受差解的概率会随着 温度下降
、差解劣质程度上升
而降低,因此在遇到比较好的解时则跳出概率较小(遇到最优解或者较优解时不会放弃该解),从而最终搜索到较优解。模拟退火算法的特点概括为:(1)具有一定的跳过局部最优解的能力,能够以较快的速度得到问题的近似最优解(较优解),如果参数(初温、末温、降温系数)设置的很好,模拟退火能够表现出相当好的效果(2)但是正因为它会有概率接受差解,所以最终的解有可能比搜索过程中的某些解更差(3)对参数非常敏感,如果降温系数、初温、末温稍微设置不当,就会表现出较差的效果
最后就是禁忌搜索算法,它的求解用时是三种方案中最长的,并且得出的解有一定概率超过其它两种解,在上面的71个测例求解分配方案中,禁忌搜索得出的方案中有19个是三个解法中最优的,并且在反复的测试中,自己发现,反复运行禁忌搜索,它有较小概率得出远超其它两个方案的解,所以如果为禁忌搜索添加重新搜索策略(即进入局部最优之后重新初始化解和禁忌表,重新计算解),禁忌搜索总能做到比另外两种方法好,但是需要花费很长时间。禁忌搜索也能够跳出局部最优解,因为它不以局部最优作为停止准则,每次都是寻找邻居中最佳的解替换自己,哪怕邻居都比自己差,从而离开局部极小点,并且优于禁忌表的存在,由于新解是从局部极小点演变而来的,因此之前走过的路径被加入禁忌表,短时间内无法再走,所以不会又回到局部极小而产生循环。然后它将继续前往下一个局部极小点,然后记录途经的最优点,在搜索结束时将最优点作为算法求得的解。简单的说就是它通过对一些局部最优解的禁忌(也可以说是记忆)达到接纳一部分较差解,从而跳出局部搜索的目的。禁忌搜索的特点归纳为:(1)能够跳出局部最优,不断前往其它解的领域,尽可能找到全局最优(2)求解用时长,如果不给出足够的时间,禁忌搜索走过的路径只能覆盖一些局部极小点,从而无法得出较优解,但是如果给出足够的时间,就能得出很优秀的解(不像模拟退火,及时充分退火,降温系数很小,也只能达到一个瓶颈而停滞不前)(3)对于禁忌表长度要求严格,禁忌表过短会陷入局部极小循环,过长则可能找不到可行的邻居解(全部被禁忌了),并且也可能导致搜索表的用时过长,算法效率极低(4)禁忌表策略十分重要,一般不选择禁忌状态,而选择禁忌产生新解的操作。之前自己禁忌解状态,效果极差,后来改为禁忌生成邻居解的操作,则有很大程度的改善
代码框架简要说明
局部搜索解法(爬山法)、模拟退火解法、忌搜索解法三种方法分别对应LocalSearchSol、SimulatedAnnealingSol、TabuSearch三个类
每个类下的结构都相似,包括:
(1)初始化函数:
(2)每个类都使用solve函数读取测例文件,对问题求解,并将结果输出、写入文件,实例化类之后用实例调用solve函数即可完成求解:
(3)它们也都使用read_file函数读取测例文件,使用write_file将结果写入文件,这两个函数是对于三个类都是一模一样的:
但各个类各自的求解问题所需函数各不一样:
(0)比如爬山法、模拟退火法的generate_neighbor函数(名称相同,内容不同),产生一个邻居解,然后判断其优劣、求算概率(模拟退火才有),再决定是否接受邻居:
(1)爬山法中solve函数通过迭代调用该函数100000次该函数,不断求取更优的解,并将最后得到的局部最优解作为结果:
(2)模拟退火算法的solve函数则在外部固定温度,外层循环温度从100度降到0.5度,并在内存循环迭代调用generate_neighbor函数1000次,接受更优解,并以温度相关的概率接受差解,最后当外层循环的温度下降到0.5度以下时将当前解作为结果输出、存储:
(3)禁忌搜索算法则有所不同,该算法迭代调用generate_neighbor函数10000次:
generate_neighbor函数会求出满足特赦准则或者不在禁忌表中的最佳邻居替代当前解,然后不断进行搜索、遍历解集领域:
在禁忌搜索中每次产生满足条件的最佳邻居之后都会更新禁忌表、更新历史纪录最优解:
完成10000次迭代调用generate_neighbor之后,将最终的best_solution作为算法结果输出、存储。
更多的细节详见后面的源代码
源代码
github项目源代码方便观看,csdn上代码观看效果较差,没有高亮
import random
import math
import datetime
import os
import time
INSTANCE_NUMBER = 71 # 测试的时候是1,实际上一共71个测试例子
LOOP_LENGTH = 1000
MAX_TEMPERATURE = 100
MIN_TEMPERATURE = 0.5
ATTENUATION_QUOTIENT = 0.99
MAX_NUM = 100000000
class LocalSearchSol:
def __init__(self, filename):
self.filename = filename
self.facility_number = 0
self.customer_number = 0
self.facility_capacity = [] # 变量
self.facility_open_cost = []
self.customer_demand = []
self.distance_to_facility = []
self.read_file()
self.facility_open_list = [0]*self.facility_number # 变量
self.customer_allocation = [] # 变量
self.total_cost = 0 # 变量
def init_alloc_customer(self):
for i in range(0, self.customer_number):
while True:
choose_facility = random.randint(0, self.facility_number-1)
if self.facility_capacity[choose_facility] >= self.customer_demand[i]:
if self.facility_open_list[choose_facility] == 0:
self.total_cost += self.facility_open_cost[choose_facility]
self.facility_open_list[choose_facility] += 1
self.facility_capacity[choose_facility] -= self.customer_demand[i]
self.total_cost += self.distance_to_facility[i][choose_facility]
self.customer_allocation.append(choose_facility)
break
def read_file(self):
fp = open(self.filename)
problem_size = fp.readline().split()
self.facility_number = int(problem_size[0])
self.customer_number = int(problem_size[1])
for i in range(0, self.facility_number):
facility_info = fp.readline().split()
self.facility_capacity.append(int(facility_info[0]))
self.facility_open_cost.append(int(facility_info[1]))
while len(self.customer_demand) < self.customer_number:
demand_info = fp.readline().split()
for i in range(0, len(demand_info)):
self.customer_demand.append(float(demand_info[i]))
distance_infos = []
while len(distance_infos) < self.facility_number:
distance_info = []
while len(distance_info) < self.customer_number:
temp = fp.readline().split()
for i in range(0, len(temp)):
distance_info.append(float(temp[i]))
distance_infos.append(distance_info)
for i in range(0, self.customer_number):
temp_distance = []
for j in range(0, self.facility_number):
temp_distance.append(distance_infos[j][i])
self.distance_to_facility.append(temp_distance)
def write_file(self):
fp = open('Detailed Solution Of Local Search/result for ' + self.filename.split('/')[1], 'w')
fp.write(str(self.total_cost) + '\n')
for i in range(0, self.facility_number):
if self.facility_open_list[i] != 0:
fp.write('1 ')
else:
fp.write('0 ')
fp.write('\n')
for i in range(0, self.customer_number):
fp.write(str(self.customer_allocation[i]) + ' ')
def generate_neighbor_by_change_facility(self): # 产生方式为:将一个顾客从A工厂移到B工厂
while True:
choose_customer = random.randint(0, self.customer_number-1)
choose_facility = random.randint(0, self.facility_number-1)
if self.customer_allocation[choose_customer] == choose_facility:
continue # 产生的是这个点本身而不是邻居,重来
if self.facility_capacity[choose_facility] < self.customer_demand[choose_customer]:
continue # 交换被拒绝,因为目标工厂容量不足
# 交换操作可进行,计算总花费是否减少,如果减少则接受邻居解,总花费包括 到工厂距离+开工厂费用
allocated_facility_before = self.customer_allocation[choose_customer]
# print('尝试操作:将顾客', choose_customer, '从工厂', allocated_facility_before, '转到工厂', choose_facility)
cost_before = self.distance_to_facility[choose_customer][allocated_facility_before]
if self.facility_open_list[allocated_facility_before] == 1:
cost_before += self.facility_open_cost[allocated_facility_before]
cost_after = self.distance_to_facility[choose_customer][choose_facility]
if self.facility_open_list[choose_facility] == 0:
cost_after += self.facility_open_cost[choose_facility]
if cost_before <= cost_after: # 邻居解不能接受
return False
self.facility_capacity[allocated_facility_before] += self.customer_demand[choose_customer]
self.facility_capacity[choose_facility] -= self.customer_demand[choose_customer]
self.facility_open_list[allocated_facility_before] -= 1
self.facility_open_list[choose_facility] += 1
self.customer_allocation[choose_customer] = choose_facility
self.total_cost = self.total_cost - cost_before + cost_after
return True
def generate_neighbor_by_swap_customer(self): # 产生方式是交换两个不同工厂的顾客,缺点是无法开厂,应配合1使用
while True:
customer_1 = random.randint(0, self.customer_number - 1)
customer_2 = random.randint(0, self.customer_number - 1)
facility_cus_1 = self.customer_allocation[customer_1]
facility_cus_2 = self.customer_allocation[customer_2]
if customer_1 == customer_2 or facility_cus_1 == facility_cus_2:
continue # 产生的是这个点本身而不是邻居,重来
if self.facility_capacity[facility_cus_1] + self.customer_demand[customer_1] - \
self.customer_demand[customer_2] < 0 or self.facility_capacity[facility_cus_2] + \
self.customer_demand[customer_2] - self.customer_demand[customer_1] < 0:
continue # 交换被拒绝,有一个工厂的容量不足
# 交换操作可进行,计算总花费是否减少,如果减少则接受邻居解,总花费仅仅包括到工厂距离
cost_before = self.distance_to_facility[customer_1][facility_cus_1] \
+ self.distance_to_facility[customer_2][facility_cus_2]
cost_after = self.distance_to_facility[customer_1][facility_cus_2] \
+ self.distance_to_facility[customer_2][facility_cus_1]
if cost_before <= cost_after: # 邻居解不能接受
return False
self.facility_capacity[facility_cus_1] = self.facility_capacity[facility_cus_1] + \
self.customer_demand[customer_1] - self.customer_demand[customer_2]
self.facility_capacity[facility_cus_2] = self.facility_capacity[facility_cus_2] + \
self.customer_demand[customer_2] - self.customer_demand[customer_1]
self.customer_allocation[customer_1] = facility_cus_2
self.customer_allocation[customer_2] = facility_cus_1
self.total_cost = self.total_cost - cost_before + cost_after
return True
def solve(self):
self.init_alloc_customer() # 随机得到一个分配方案
print('使用局部搜索,初始分配方案花费:', self.total_cost)
for i in range(0, 100*LOOP_LENGTH):
if self.generate_neighbor_by_change_facility():
print('使用局部搜索1,第', i, '循环 ', '接受新的邻居解:', '新的花费更新为:', self.total_cost)
# elif self.generate_neighbor_by_swap_customer():
# print('使用局部搜索2,第', i, '循环 ', '接受新的邻居解:', '新的花费更新为:', self.total_cost)
self.write_file()
return self.total_cost
class SimulatedAnnealingSol:
def __init__(self, filename):
self.filename = filename
self.facility_number = 0
self.customer_number = 0
self.facility_capacity = [] # 变量
self.facility_open_cost = []
self.customer_demand = []
self.distance_to_facility = []
self.read_file()
self.facility_open_list = [0]*self.facility_number # 变量
self.customer_allocation = [] # 变量
self.total_cost = 0 # 变量
self.temperature = MAX_TEMPERATURE
def init_alloc_customer(self):
for i in range(0, self.customer_number):
while True:
choose_facility = random.randint(0, self.facility_number - 1)
if self.facility_capacity[choose_facility] >= self.customer_demand[i]:
if self.facility_open_list[choose_facility] == 0:
self.total_cost += self.facility_open_cost[choose_facility]
self.facility_open_list[choose_facility] += 1
self.facility_capacity[choose_facility] -= self.customer_demand[i]
self.total_cost += self.distance_to_facility[i][choose_facility]
self.customer_allocation.append(choose_facility)
break
def read_file(self):
fp = open(self.filename)
problem_size = fp.readline().split()
self.facility_number = int(problem_size[0])
self.customer_number = int(problem_size[1])
for i in range(0, self.facility_number):
facility_info = fp.readline().split()
self.facility_capacity.append(int(facility_info[0]))
self.facility_open_cost.append(int(facility_info[1]))
while len(self.customer_demand) < self.customer_number:
demand_info = fp.readline().split()
for i in range(0, len(demand_info)):
self.customer_demand.append(float(demand_info[i]))
distance_infos = []
while len(distance_infos) < self.facility_number:
distance_info = []
while len(distance_info) < self.customer_number:
temp = fp.readline().split()
for i in range(0, len(temp)):
distance_info.append(float(temp[i]))
distance_infos.append(distance_info)
for i in range(0, self.customer_number):
temp_distance = []
for j in range(0, self.facility_number):
temp_distance.append(distance_infos[j][i])
self.distance_to_facility.append(temp_distance)
def write_file(self):
fp = open('Detailed Solution Of Simulated Annealing/result for ' + self.filename.split('/')[1], 'w')
fp.write(str(self.total_cost) + '\n')
for i in range(0, self.facility_number):
if self.facility_open_list[i] != 0:
fp.write('1 ')
else:
fp.write('0 ')
fp.write('\n')
for i in range(0, self.customer_number):
fp.write(str(self.customer_allocation[i]) + ' ')
def generate_neighbor_by_change_facility(self): # 产生方式为:将一个顾客从A工厂移到B工厂
while True:
choose_customer = random.randint(0, self.customer_number-1)
choose_facility = random.randint(0, self.facility_number-1)
if self.customer_allocation[choose_customer] == choose_facility:
continue # 产生的是这个点本身而不是邻居,重来
if self.facility_capacity[choose_facility] < self.customer_demand[choose_customer]:
continue # 交换被拒绝,因为目标工厂容量不足
# 交换操作可进行,计算总花费是否减少,如果减少则接受邻居解,总花费包括 到工厂距离+开工厂费用
allocated_facility_before = self.customer_allocation[choose_customer]
# print('尝试操作:将顾客', choose_customer, '从工厂', allocated_facility_before, '转到工厂', choose_facility)
cost_before = self.distance_to_facility[choose_customer][allocated_facility_before]
if self.facility_open_list[allocated_facility_before] == 1:
cost_before += self.facility_open_cost[allocated_facility_before]
cost_after = self.distance_to_facility[choose_customer][choose_facility]
if self.facility_open_list[choose_facility] == 0:
cost_after += self.facility_open_cost[choose_facility]
if cost_before <= cost_after and \
random.random() > math.exp((cost_before - cost_after)/self.temperature):
return False
self.facility_capacity[allocated_facility_before] += self.customer_demand[choose_customer]
self.facility_capacity[choose_facility] -= self.customer_demand[choose_customer]
self.facility_open_list[allocated_facility_before] -= 1
self.facility_open_list[choose_facility] += 1
self.customer_allocation[choose_customer] = choose_facility
self.total_cost = self.total_cost - cost_before + cost_after
return True
def generate_neighbor_by_swap_customer(self): # 产生方式是交换两个不同工厂的顾客,缺点是无法开厂,应配合1使用
while True:
customer_1 = random.randint(0, self.customer_number - 1)
customer_2 = random.randint(0, self.customer_number - 1)
facility_cus_1 = self.customer_allocation[customer_1]
facility_cus_2 = self.customer_allocation[customer_2]
if customer_1 == customer_2 or facility_cus_1 == facility_cus_2:
continue # 产生的是这个点本身而不是邻居,重来
if self.facility_capacity[facility_cus_1] + self.customer_demand[customer_1] - \
self.customer_demand[customer_2] < 0 or self.facility_capacity[facility_cus_2] + \
self.customer_demand[customer_2] - self.customer_demand[customer_1] < 0:
continue # 交换被拒绝,有一个工厂的容量不足
# 交换操作可进行,计算总花费是否减少,如果减少则接受邻居解,总花费仅仅包括到工厂距离
cost_before = self.distance_to_facility[customer_1][facility_cus_1] \
+ self.distance_to_facility[customer_2][facility_cus_2]
cost_after = self.distance_to_facility[customer_1][facility_cus_2] \
+ self.distance_to_facility[customer_2][facility_cus_1]
if cost_before <= cost_after and \
random.random() > math.exp((cost_before - cost_after)/cost_before/self.temperature):
return False
self.facility_capacity[facility_cus_1] = self.facility_capacity[facility_cus_1] + \
self.customer_demand[customer_1] - self.customer_demand[customer_2]
self.facility_capacity[facility_cus_2] = self.facility_capacity[facility_cus_2] + \
self.customer_demand[customer_2] - self.customer_demand[customer_1]
self.customer_allocation[customer_1] = facility_cus_2
self.customer_allocation[customer_2] = facility_cus_1
self.total_cost = self.total_cost - cost_before + cost_after
return True
def solve(self):
self.init_alloc_customer()
print('使用模拟退火,初始分配方案花费:', self.total_cost)
while self.temperature > MIN_TEMPERATURE:
for i in range(0, LOOP_LENGTH):
# if i % 3 != 0:
# if self.generate_neighbor_by_swap_customer():
# print('使用模拟退火,当前温度:', self.temperature, ',第', i, '循环 ', '接受新的邻居解,花费更新为:', self.total_cost)
# continue
if self.generate_neighbor_by_change_facility():
print('使用模拟退火,当前温度:', self.temperature, ',第', i, '循环 ', '接受新的邻居解,花费更新为:', self.total_cost)
self.temperature *= ATTENUATION_QUOTIENT
self.write_file()
return self.total_cost
class TabuSearch:
def __init__(self, filename):
self.filename = filename
self.facility_number = 0
self.customer_number = 0
self.facility_capacity = [] # 变量
self.facility_open_cost = []
self.customer_demand = []
self.distance_to_facility = []
self.read_file()
# 下面三个变量通过init_alloc_customer初始化
self.facility_open_list = [0]*self.facility_number # 变量
self.customer_allocation = [] # 变量
self.total_cost = 0 # 变量
self.best_solution = [0]*self.customer_number
self.min_cost = MAX_NUM
self.tabu_list = []
for i in range(0, self.customer_number):
temp = []
for j in range(0, self.facility_number):
temp.append(0)
self.tabu_list.append(temp)
self.neighbor_index1 = 0
self.neighbor_index2 = 0
self.neighbor_index3 = 0
self._round = 0
def init_alloc_customer(self):
for i in range(0, self.customer_number):
while True:
choose_facility = random.randint(0, self.facility_number-1)
if self.facility_capacity[choose_facility] >= self.customer_demand[i]:
if self.facility_open_list[choose_facility] == 0:
self.total_cost += self.facility_open_cost[choose_facility]
self.facility_open_list[choose_facility] += 1
self.facility_capacity[choose_facility] -= self.customer_demand[i]
self.total_cost += self.distance_to_facility[i][choose_facility]
self.customer_allocation.append(choose_facility)
break
def read_file(self):
fp = open(self.filename)
problem_size = fp.readline().split()
self.facility_number = int(problem_size[0])
self.customer_number = int(problem_size[1])
for i in range(0, self.facility_number):
facility_info = fp.readline().split()
self.facility_capacity.append(int(facility_info[0]))
self.facility_open_cost.append(int(facility_info[1]))
while len(self.customer_demand) < self.customer_number:
demand_info = fp.readline().split()
for i in range(0, len(demand_info)):
self.customer_demand.append(float(demand_info[i]))
distance_infos = []
while len(distance_infos) < self.facility_number:
distance_info = []
while len(distance_info) < self.customer_number:
temp = fp.readline().split()
for i in range(0, len(temp)):
distance_info.append(float(temp[i]))
distance_infos.append(distance_info)
for i in range(0, self.customer_number):
temp_distance = []
for j in range(0, self.facility_number):
temp_distance.append(distance_infos[j][i])
self.distance_to_facility.append(temp_distance)
def update_best_solution(self):
if self.total_cost < self.min_cost:
self.min_cost = self.total_cost
for i in range(0, self.customer_number):
self.best_solution[i] = self.customer_allocation[i]
print('第', self._round, '轮,最优解更新为:', self.total_cost, '分配方案:')
print(self.customer_allocation)
# time.sleep(0.1)
def update_tabu_list(self):
self.tabu_list[self.neighbor_index1][self.neighbor_index2] = \
self._round + self.facility_number*self.customer_number*0.1
self.tabu_list[self.neighbor_index1][self.neighbor_index3] = \
self._round + self.facility_number*self.customer_number*0.1
def generate_best_neighbor(self):
best_neighbor_cost = MAX_NUM
self.neighbor_index1 = 0
self.neighbor_index2 = 0
self.neighbor_index3 = 0
for i in range(0, self.customer_number):
for j in range(0, self.facility_number):
if random.random() > 0.4:
continue # 只取一部分领域
if self.customer_allocation[i] == j:
continue # 不是邻居是本身
if self.facility_capacity[j] < self.customer_demand[i]:
continue # 容量不够拒绝操作
facility_before = self.customer_allocation[i]
cost_before = self.distance_to_facility[i][facility_before]
if self.facility_open_list[facility_before] == 1:
cost_before += self.facility_open_cost[facility_before]
cost_after = self.distance_to_facility[i][j]
if self.facility_open_list[j] == 0:
cost_after += self.facility_open_cost[j]
if cost_after - cost_before >= best_neighbor_cost:
continue # 肯定不是最优邻居,直接忽略
if self.neighbor_in_tabu_list(i, j) and self.total_cost + cost_after - cost_before >= self.min_cost:
continue # 这个邻居在禁忌表当中,不能选,除非满足特赦准则
# 满足所有条件之后,更新最佳邻居
best_neighbor_cost = cost_after - cost_before
self.neighbor_index1 = i
self.neighbor_index2 = j
self.neighbor_index3 = self.customer_allocation[self.neighbor_index1]
# 使用最佳邻居作为下一节点:
if best_neighbor_cost == MAX_NUM:
print('严重错误,禁忌表过大,找不到下一个可行邻居')
exit(0)
customer_choosed = self.neighbor_index1
facility_before = self.customer_allocation[self.neighbor_index1]
facility_after = self.neighbor_index2
self.facility_capacity[facility_before] += self.customer_demand[customer_choosed]
self.facility_capacity[facility_after] -= self.customer_demand[customer_choosed]
self.facility_open_list[facility_before] -= 1
self.facility_open_list[facility_after] += 1
self.customer_allocation[customer_choosed] = facility_after
self.total_cost = self.total_cost + best_neighbor_cost
self._round += 1
def neighbor_in_tabu_list(self, customer_choosed, facility_choosed):
return self.tabu_list[customer_choosed][facility_choosed] > self._round
def solve(self):
temp_min_cost = MAX_NUM
temp_best_solution = []
for k in range(0, 2):
self.__init__(self.filename)
# 初始化
self.init_alloc_customer()
self.update_best_solution()
# 开始搜索
for i in range(0, 10 * LOOP_LENGTH):
self.generate_best_neighbor()
# print(i)
# print('第', i, '轮,当前分配方案开销:', self.total_cost, '分配方案:')
# print(self.customer_allocation)
self.update_best_solution()
self.update_tabu_list()
print('求得的最优解花费:', self.min_cost, '分配方案:\n', self.best_solution)
if self.min_cost < temp_min_cost:
temp_min_cost = self.min_cost
temp_best_solution = self.best_solution
print('\n\n最后的最优解:', temp_min_cost, '分配方案:')
print(temp_best_solution)
temp_open_list = [0]*self.facility_number
fp = open('Detailed Solution Of Tabu Search/result for ' + self.filename.split('/')[1], 'w')
fp.write(str(temp_min_cost) + '\n')
for i in range(0, self.customer_number):
temp_open_list[self.customer_allocation[i]] = 1
for i in range(0, self.facility_number):
fp.write(str(temp_open_list[i]) + ' ')
fp.write('\n')
for i in range(0, self.customer_number):
fp.write(str(temp_best_solution[i]) + ' ')
return temp_min_cost
def main():
print('local search solution.')
print('一共提供如下几种解法:')
print('1 局部搜索')
print('2 模拟退火')
print('3 禁忌搜索')
command = input('请输入您想使用的算法:')
write_file_name = ''
if command == '1':
write_file_name = 'Result Table Of Local Search'
elif command == '2':
write_file_name = 'Result Table Of Simulated Annealing'
elif command == '3':
write_file_name = 'Result Table Of Tabu Search'
fp = open(write_file_name, 'a')
if os.stat(write_file_name).st_size == 0:
fp.write(''.ljust(10) + 'Result'.ljust(20) + 'Time(s)\n')
for i in range(1, INSTANCE_NUMBER+1):
filename = 'Instances/p' + str(i)
res = 0
start_time = datetime.datetime.now()
if command == '1':
solution1 = LocalSearchSol(filename)
res = solution1.solve()
elif command == '2':
solution2 = SimulatedAnnealingSol(filename)
res = solution2.solve()
elif command == '3':
solution3 = TabuSearch(filename)
res = solution3.solve()
end_time = datetime.datetime.now()
fp.write(('p' + str(i)).ljust(10) + str(res).ljust(20) + str((end_time - start_time).seconds) + '\n')
if __name__ == '__main__':
main()