算法设计与分析project(Capacitated Facility Location Problem)

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=EE小于0,则接受这个新生成的邻居解,用它替代当前解,并设置当前状态能量E=E’;否则 Δ E = E ′ − E \Delta E = E' - E ΔE=EE大于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()

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Here is a basic implementation of CVRP in Python using the Google OR-Tools library: ```python from ortools.constraint_solver import routing_enums_pb2 from ortools.constraint_solver import pywrapcp def create_data_model(): """Stores the data for the problem.""" data = {} data['distance_matrix'] = [ [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502], [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594], [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278], [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514], [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400], [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 810], [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1016], [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468], [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810], [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 650], [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878], [502, 594, 1278, 514, 400, 810, 1016, 468, 810, 650, 878, 0] ] data['num_vehicles'] = 3 data['vehicle_capacities'] = [100, 100, 100] data['depot'] = 0 return data def print_solution(data, manager, routing, solution): """Prints solution on console.""" total_distance = 0 total_load = 0 for vehicle_id in range(data['num_vehicles']): index = routing.Start(vehicle_id) plan_output = 'Route for vehicle {}:\n'.format(vehicle_id) route_distance = 0 route_load = 0 while not routing.IsEnd(index): node_index = manager.IndexToNode(index) route_load += data['demands'][node_index] plan_output += ' {} Load({}) -> '.format(node_index, route_load) previous_index = index index = solution.Value(routing.NextVar(index)) route_distance += routing.GetArcCostForVehicle( previous_index, index, vehicle_id) plan_output += ' {} Load({})\n'.format(manager.IndexToNode(index), route_load) plan_output += 'Distance of the route: {}m\n'.format(route_distance) plan_output += 'Load of the route: {}\n'.format(route_load) print(plan_output) total_distance += route_distance total_load += route_load print('Total distance of all routes: {}m'.format(total_distance)) print('Total load of all routes: {}'.format(total_load)) def main(): """Entry point of the program.""" data = create_data_model() manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']), data['num_vehicles'], data['depot']) routing = pywrapcp.RoutingModel(manager) def distance_callback(from_index, to_index): """Returns the distance between the two nodes.""" from_node = manager.IndexToNode(from_index) to_node = manager.IndexToNode(to_index) return data['distance_matrix'][from_node][to_node] transit_callback_index = routing.RegisterTransitCallback(distance_callback) routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index) dimension_name = 'Capacity' routing.AddDimension( transit_callback_index, 0, # no slack 100, # vehicle maximum capacities True, # start cumul to zero dimension_name) capacity_dimension = routing.GetDimensionOrDie(dimension_name) for i, demand in enumerate(data['demands']): index = manager.NodeToIndex(i) capacity_dimension.SetDemand(index, demand) for vehicle_id in range(data['num_vehicles']): index = routing.Start(vehicle_id) capacity_dimension.CumulVar(index).SetRange(data['vehicle_capacities'][vehicle_id], data['vehicle_capacities'][vehicle_id]) search_parameters = pywrapcp.DefaultRoutingSearchParameters() search_parameters.first_solution_strategy = ( routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION) solution = routing.SolveWithParameters(search_parameters) if solution: print_solution(data, manager, routing, solution) if __name__ == '__main__': main() ``` Note that this is just a basic implementation and can be modified to suit specific requirements and constraints of individual problem instances.

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值