禁忌搜索算法实现订单接收与调度
在按订单生产的制造系统中,有限的生产能力与严格的订单交货期要求决策者从若干候选订单中选择接受某些订单并
编制生产计划。决策者需在订单收益与拖期惩罚之间进行权衡,以所有接受订单的实际收益之和最大为目标进行优化。提出了一种禁忌搜索算法用于求解考虑发布日期和队列准备时间的单机环境下的订单接受与调度问题。
数据文件如下:
订单序号 | 惩罚系数Wj | 库存费用Kt | 收益Qj | 交货期DJ | 处理时间Pj |
---|---|---|---|---|---|
1 | 0.142 | 0.0025 | 50 | 22 | 8 |
2 | 0.132 | 0.0022 | 45 | 20 | 5 |
3 | 0.321 | 0.0023 | 46 | 19 | 7 |
4 | 0.345 | 0.0022 | 45 | 15 | 9 |
5 | 0.432 | 0.0017 | 35 | 33 | 11 |
6 | 0.123 | 0.0013 | 26 | 20 | 13 |
7 | 0.122 | 0.0023 | 46 | 14 | 14 |
8 | 0.133 | 0.0014 | 28 | 13 | 9 |
9 | 0.154 | 0.0029 | 58 | 20 | 6 |
10 | 0.143 | 0.0022 | 45 | 21 | 9 |
代码如下:
import pandas as pd
import numpy as np
import copy
import random
def Inc(candidate,rate):
global current_scheme
#增加的订单的次序
incOrder = random.sample(range(1, max(current_scheme)+2), 1)[0]
#选择增加的位置,不能和已经有的重合
empty_index = [n for m,n in zip(current_scheme,np.arange(len(current_scheme))) if m == 0]
opsition = random.sample(empty_index, 1)[0]
#得到增加一个order的可行解
inc_candidate = copy.copy(current_scheme)
inc_candidate[opsition] = incOrder
#对incOrder后编号的订单号加1
for i in range(incOrder,max(current_scheme)+1):
inc_candidate[current_scheme.index(i)] = i+1
candidate.append(inc_candidate)
#取inc_candidate的邻域,取得数量由rate控制,对inc_candidate进行交换就可以
#存储两个交换的位置,避免重复交换
exchange_position = []
#由订单的数量max(inc_candidate)与rate共同决定
orderNum = max(inc_candidate)
temp = int((orderNum*(orderNum-1)/2+orderNum*(len(inc_candidate)-orderNum))*rate)
#随机选取邻域
while True:
#获取交换的位置
current = random.sample(range(0,len(inc_candidate)), 2)
current.sort()
#若果这两个位置都是0不必交换
if inc_candidate[current[0]] == inc_candidate[current[1]]:
exchange_position.append(current)
if current not in exchange_position:
exchange_position.append(current)
candidate.append(exchange(current,inc_candidate))
temp -= 1
if temp == 0:
break
return candidate
def Dec(candidate,rate):
global current_scheme
#选择减的位置,和已经有的重合
occupy_index = [n for m,n in zip(current_scheme,np.arange(len(current_scheme))) if m != 0]
opsition = random.sample(occupy_index, 1)[0]
#确定减少的订单的序列值
decOrder = current_scheme[opsition]
#得到减少一个order的可行解
dec_candidate = copy.copy(current_scheme)
dec_candidate[opsition] = 0
#对incOrder后编号的订单号减1
for i in range(decOrder+1,max(current_scheme)+1):
dec_candidate[current_scheme.index(i)] = i-1
candidate.append(dec_candidate)
#取inc_candidate的邻域,取得数量由rate控制,对inc_candidate进行交换就可以
#存储两个交换的位置,避免重复交换
exchange_position = []
#由订单的数量max(inc_candidate)与rate共同决定
orderNum = max(dec_candidate)
temp = int((orderNum*(orderNum-1)/2+orderNum*(len(dec_candidate)-orderNum))*rate)
#获取邻域
while True:
#获取交换的位置
current = random.sample(range(0,len(dec_candidate)), 2)
current.sort()
#若果这两个位置都是0不必交换
if dec_candidate[current[0]] == dec_candidate[current[1]]:
exchange_position.append(current)
if current not in exchange_position:
exchange_position.append(current)
candidate.append(exchange(current,dec_candidate))
temp -= 1
if temp == 0:
break
return candidate
def Fix(candidate,rate):
global current_scheme
candidate.append(current_scheme)
#取inc_candidate的邻域,取得数量由rate控制,对inc_candidate进行交换就可以
#存储两个交换的位置,避免重复交换
exchange_position = []
#由订单的数量max(inc_candidate)与rate共同决定
orderNum = max(current_scheme)
temp = int((orderNum*(orderNum-1)/2+orderNum*(len(current_scheme)-orderNum))*rate)
#获取邻域
while True:
#获取交换的位置
current = random.sample(range(0,len(current_scheme)), 2)
current.sort()
#若果这两个位置都是0不必交换
if current_scheme[current[0]] == current_scheme[current[1]]:
exchange_position.append(current)
if current not in exchange_position:
exchange_position.append(current)
candidate.append(exchange(current,current_scheme))
temp -= 1
if temp == 0:
break
return candidate
#初始化可行解
def initialize_scheme(order_num):
global best_scheme
global best_q
global current_scheme
global current_q
global current_best
#选择订单的个数,初始为所有的一半
selectNumOrder = int(order_num/2)
#得到选择的订单,这是0-9选 5 个
selectOrder = random.sample(range(0,len(data)-1),selectNumOrder)
#给选择的订单排序从1开始
orderOrder = random.sample(range(1,selectNumOrder+1),selectNumOrder)
#生成可行解序列
for i in range(selectNumOrder):
current_scheme[selectOrder[i]] = orderOrder[i]
#获得初始可行解获得的受益
current_q = profit(current_scheme)
current_best.append(current_q)
#初始化最好解和受益
best_scheme.append(current_scheme)
best_q.append(current_q)
def profit(scheme):
global alpha
global beta
global data
#y1是接收订单的数量
y1 = max(scheme)
#y2是受益,S是上一个订单的开始时间,+P为这个订单结束时间
y2 = 0.0
S = 0.0
#依次计算每个订单的受益,scheme.index(i)是排在第i位执行的订单
for i in range(1,y1+1):
#获得第i位执行的订单的信息
order = scheme.index(i)
W = data.iloc[order]['惩罚系数Wj']
K = data.iloc[order]['库存费用Kt']
Q = data.iloc[order]['收益Qj']
D = data.iloc[order]['交货期DJ']
P = data.iloc[order]['处理时间Pj']
#该订单结束处理的时间
S += P
#延期的时间,
delay_time = max(S - D,0)
y2 += Q - W*delay_time - K*delay_time
total_profit = y1*alpha+y2*beta
return total_profit
def getCandidate(numCandidates):
global best_q
global best_scheme
global current_tabu_num
global current_q
global current_scheme
global tabu_list
global current_best
#记录邻域候选解
candidate = []
#录邻域候选解的收益
candidate_q = []
#找到三个方向的candidate,并选取这里面的最优解最为current_scheme
#判断是否能增加或者减少一个order
orderNum = max(current_scheme)
if 1 < orderNum < len(current_scheme):
candidate = Inc(candidate,numCandidates)
candidate = Dec(candidate,numCandidates)
candidate = Fix(candidate,numCandidates)
elif orderNum == 1:
candidate = Inc(candidate,numCandidates)
candidate = Fix(candidate,numCandidates)
elif orderNum == len(current_scheme):
candidate = Dec(candidate,numCandidates)
candidate = Fix(candidate,numCandidates)
#计算邻域候选解的收益
for scheme in candidate:
if scheme not in tabu_list:
candidate_q.append(profit(scheme))
else:
candidate_q.append(0)
#得到候选解中的最优解
candidate_best = max(candidate_q)
best_index = candidate_q.index(candidate_best)
#更新现在的最优解
current_scheme = copy.copy(candidate[best_index])
current_q = candidate_best
#与当前最优解进行比较
now_best_q = best_q[-1]
now_best_scheme = best_scheme[-1]
current_best.append(now_best_q)
if current_q > now_best_q:
best_q.append(current_q)
best_scheme.append(current_scheme)
else:
best_q.append(now_best_q)
best_scheme.append(now_best_scheme)
#加入禁忌表
tabu_list.append(current_scheme)
def exchange(current,inc_candidate):
index1 = current[0]
index2 = current[1]
current_list = copy.copy(inc_candidate)
mid = current_list[index1]
current_list[index1] = current_list[index2]
current_list[index2] = mid
return current_list
#更新禁忌表
def update_tabu(tabu_limit):
global tabu_list
tabu_list_len = len(tabu_list)
if tabu_list_len > tabu_limit:
del(tabu_list[0])
if __name__ == '__main__':
data = pd.read_excel(r'data.xlsx')
#订单数量
order_num = len(data)
#订单序号 惩罚系数Wj 库存费用Kt 收益Qj 交货期DJ 处理时间Pj
#先随机初始化,得到一个可行解队列
#记录迭代次数
iteration = 20 ###########################################################可以调整的参数
alpha = 0.01 ###########################################
beta = 0.99 ###############################################
#记录每次迭代最好的方案
best_scheme = []
best_q = []
#记录邻域中最好的方案
current_scheme = [0]*order_num
current_q = 0.0
current_best = []
#禁忌表
tabu_list = []
#禁忌长度,即禁忌期限
tabu_limit = 30 ############################################################
#当前禁忌对象数量
current_tabu_num = 0
#定义获得邻域的候选解数量,为理论邻域数量的多少。5*4/2 + 5*5
numCandidates = 0.5 ############################################################3
initialize_scheme(order_num)
for it in range(iteration):
print('第',it,'次迭代')
print('最佳收益:',best_q[-1])
#获得候选解,邻域,这里有三种方式,增加1个订单,减少1个订单,不改变订单数量->交换订单的执行次序
getCandidate(numCandidates)
update_tabu(tabu_limit)
print("最好的方案是:",best_scheme[-1])
运行结果作图:
#下面进行可视化,甘特图,迭代曲线
import matplotlib.pyplot as plt
from pylab import mpl
from matplotlib import rcParams
plt.figure(dpi = 160,figsize=(5, 4))
config = {
"font.family":"serif", #serif
"font.size": 10,
"mathtext.fontset":'stix',
}
rcParams.update(config)
plt.plot(np.arange(len(best_q)),best_q,'-ok')
plt.rcParams['xtick.direction'] = 'in'#将x周的刻度线方向设置向内
plt.rcParams['ytick.direction'] = 'in'#将y轴的刻度方向设置向内
plt.xlabel("Iteration",{"size":10})
plt.ylabel("Total revenue",{"size":10})
plt.show()
甘特图,代码如下:
def gantt(best_scheme,data):
#对应加工的订单号
orders = []
#对应的加工开始时间
ST = []
#对应的加工结束时间
ET = []
#对应的DDL
DL = []
s = 0
for i in range(1,max(best_scheme[-1])+1):
order = best_scheme[-1].index(i)
orders.append(order+1)
ST.append(s)
s += data.iloc[order]['处理时间Pj']
ET.append(s)
DL.append(data.iloc[order]['交货期DJ'])
plt.figure(dpi = 160,figsize=(5, 4))
config = {
"font.family":"serif", #serif
"font.size": 10,
"mathtext.fontset":'stix',
}
rcParams.update(config)
for i in range(len(orders)):
plt.barh(orders[i], ET[i]-ST[i],0.3, left=ST[i],color = 'k')
plt.barh(orders[i], 0.2,0.5, left=DL[i],color = 'r')
plt.xlabel("Time",{"size":10})
plt.ylabel("Order Number ",{"size":10})
plt.yticks(np.arange(1,len(best_scheme[-1])+1))
plt.legend(['Process','Deadline'],loc='upper right',frameon=False,fontsize = "small")
gantt(best_scheme,data)