活动i: a i a_i ai;
活动i持续时间: d i d_i di;
活动i先于活动j: x i j = 1 x_{ij}=1 xij=1,else: x i j = 0 x_{ij}=0 xij=0;
同一时间资源m可用上限: r m r_m rm;
增加虚拟开始活动时间 s 0 s_0 s0,不晚于任何活动开始时间;
增加虚拟结束活动时间 s n + 1 s_{n+1} sn+1,不早于任何活动开始时间.
目标:
m i n min min s n + 1 s_n+1 sn+1
约束:
(1)对于任一资源m,同一时间内资源占用之和 < = r m <=r_m <=rm;
(2) s j − s i > = d i + ( X i j − 1 ) M s_j-s_i>=d_i+(X_{ij}-1)M sj−si>=di+(Xij−1)M, M M M为极大值。
对于约束(1)通过扫描每一代结果发现违法约束的活动加入禁止集F(不可同时工作的活动序列集合),并在下一代中对禁止集中的每组活动新增一条约 ∑ i = 1 N ( x j i + x i j ) > = 1 \sum_{i=1}^N (x_{ji}+x_{ij})>=1 ∑i=1N(xji+xij)>=1。
例如{ a 2 , a s 4 , a 7 a_2,as_4,a_7 a2,as4,a7}为禁止集中的一条序列,则在下一代中新增约束
x 2 , 4 + x 2 , 7 + x 4 , 2 + x 7 , 4 + x 4 , 7 + x 7 , 2 > = 1 x_{2,4}+x_{2,7}+x_{4,2}+x_{7,4}+x_{4,7}+x_{7,2}>=1 x2,4+x2,7+x4,2+x7,4+x4,7+x7,2>=1
直至禁止集不在新增。
此方法有两条提速手段:
1、为最后一代和其他代设置不同的gap参数和求解时间约束,既保证了求解效果又提高了求解效率。
时间和gap值都是超参。时间越长,gap越小,结果越准确但耗时更长
m.Params.MIPGap = 10**(-1) # 设置求解混合整数规划的 Gap 为10^(-
m.Params.TimeLimit = 300 # 设置最长求解时间为 300 秒
if len_f==0:
m.Params.MIPGap = 10**(-2) # 设置求解混合整数规划的 Gap 为10^(-
m.Params.TimeLimit = 60*30 # 设置最长求解时间为 300 秒
2、设置每一代查找禁止集序列的个数,该方法不必然提升效率,且效果有限,如需使用可将if的注释取消,count==2,2是个超参,限制每一代搜索违反资源约束的序列个数上限。
def find_F(splice_nodes,rc,R):#splice_nodes区间结点集合
count=0
f_index=[]
for i in range(0,len(splice_nodes)):
count=count+1
f_index.append(i)
#if count == 2:break
3、代码算例
读入目录:
routh='D:\liuyuyang\code\整理\三级原表.xlsx'
输出结果
end_list=list(map(lambda x: x[0]+x[1], zip(s_list, D)))
#name=list(np.array((list(map(lambda x:'活动'+str(x[0]),zip(range(len(D))))))))
name=['虚拟开始节点']+list(pd.read_excel(routh)['任务名称'].values)+['虚拟结束节点']
gante=pd.DataFrame(np.array((name,s_list,end_list)).T)
gante.columns=['活动名称','开始时间','结束时间']
gante2=gante.copy()
gante2.to_csv('sanji_不限制每一轮个数.csv')
绘制甘特图
base_day=datetime.date(2022,10,31)#基础日期
gante2=gante.copy()
for i in range(len(gante2)):
for j in range(1,3):
#print(i,j)
#if type(float(gante.iloc[i,j])) == str:
gante2.iloc[i,j]=base_day+datetime.timedelta(days=int(float(gante2.iloc[i,j])))
# 重命名索引
df = gante2.rename(columns = {'活动名称':'Task',
'开始时间':'Start',
'结束时间':'Finish'})
# 使用 plotly 绘图
fig = ff.create_gantt(df,height=20*len(df),width=20*len(df))
fig.show()
py.offline.plot(fig,filename='result.html')
完整代码:
#使用Gurobi求解.py
import pandas as pd
import numpy as np
import gurobipy as gp
from gurobipy import GRB
import pandas as pd
import plotly.figure_factory as ff
import plotly.io as pio
import plotly as py
import datetime
pio.renderers.default = "notebook"
def caculation(F,E_i,E_j,D,nodes,len_f):
m = gp . Model (" new model ") # Create variables
x = m.addVars(nodes,nodes,vtype=GRB.BINARY, name ="x")
s = m.addVars(nodes,vtype=GRB.INTEGER,name='s')
F=F
#定义目标函数
m.setObjective(s[nodes-1],GRB.MINIMIZE)
m.Params.MIPGap = 10**(-1) # 设置求解混合整数规划的 Gap 为10^(-
m.Params.TimeLimit = 300 # 设置最长求解时间为 300 秒
if len_f==0:
m.Params.MIPGap = 10**(-2) # 设置求解混合整数规划的 Gap 为10^(-
m.Params.TimeLimit = 60*30 # 设置最长求解时间为 300 秒
# 设置约束条件
def con_1(E_i,E_j):#(7)
for e in range(0,len(np.array(E_i))):
i=E_i[e]
j=E_j[e]
m . addConstr (x[i,j]==1)
def con_2(F):#(9)
for f in F:
ff=[]
for i in f:
if i!=0:ff.append(i)
m . addConstr (sum(x[i,j] for i in ff
for j in ff
if i!=j)>=1 )
def con_3(nodes):#(8)
m.addConstrs((x[i,j]+x[j,i]<=1 for i in range(0,nodes)\
for j in range(0,nodes)\
if i!=j),name='(8)')
def con_4(D):
m.addConstrs((s[j]-s[i]>=D[i] +1000*(x[i,j]-1)
for i in range(0,nodes)
for j in range(0,nodes)
if i!=j),name='(10)')
#约束1
con_1(E_i,E_j)
#约束2禁止集
con_2(F)
#约束3
con_3(nodes)
#约束4
con_4(D)
m.update()
m.optimize() #求解
#顺序矩阵
Relation_matrix=pd.DataFrame(np.zeros([nodes,nodes]))
for i in range(0,nodes):
for j in range(0,nodes):
Relation_matrix.iloc[i,j]=x[i,j].x
#开始时间
s_list=[]
for i in range(0,nodes):
s_list.append(s[i].x)
return s_list,Relation_matrix
#找出禁止集
def sou_need(R,node):
sum=0
for i in node:
d=R[int(i)]
sum=sum+d
return sum
def find_F(splice_nodes,rc,R):#splice_nodes区间结点集合
count=0
f_index=[]
for i in range(0,len(splice_nodes)):
count=count+1
f_index.append(i)
#if count == 2:break
if len(np.array(f_index).reshape(-1,1))==0:
F=[]
elif len(np.array(f_index).reshape(-1,1))==1:
F=splice_nodes[f_index[0]].reshape(1,-1)
else:
F=splice_nodes[f_index[0]].reshape(1,-1)
for i in range(1,len(f_index)):
f=splice_nodes[f_index[i]].reshape(1,-1)
F=np.row_stack([F,f])
return F
#所有并行的节点
def SP_nodes(s_list,nodes):
end_list = list(map(lambda x: x[0]+x[1], zip(s_list, D)))
splice_time=list(set(s_list) | set(end_list))
splice_nodes=np.zeros([len(splice_time)-1,nodes])
for i in range(0,len(splice_time)-1):
c_nodes=[]
for j in range(0,nodes):
if s_list[j]<=splice_time[i] and end_list[j]>=splice_time[i+1]:
c_nodes.append(j)
for n in range(0,len(c_nodes)):
splice_nodes[i][n]= c_nodes[n]
return splice_nodes
def act(routh):
data=pd.read_excel(routh)
a=pd.read_excel(routh)['后续'].values
X_NODE=np.zeros((len(a)+2,len(a)+2))
X_NODE[:-1,-1]=1
X_NODE[0,1:]=1
for i in range(len(a)):
#ss2=str(a[i]).translate(str.maketrans(",", ",") )
temp=str(a[i]).translate(str.maketrans(",", ",") ).split(',')
temp_l=[int(j) for j in temp]
for k in temp_l:
X_NODE[i+1,k]=1
pd.DataFrame(X_NODE) .to_csv('二级.csv')
X_NODE=np.array(pd.read_csv('二级.csv').iloc[:,1:])
numberOfNode=X_NODE.shape[1]
#E_i=[0,0,0,1,4,4,2,3,6,7,5,8]#a selected edge in the graph
E_i=list(np.argwhere(X_NODE==1)[:,0])
E_j=list(np.argwhere(X_NODE==1)[:,1])
R=[0]+list(pd.read_excel(routh)['资源1需求'].values)+[0]#资源需求
#D=[0,2,7,3,4,8,6,4,2,0]
global D
D=[0]+list(pd.read_excel(routh)['工期'].values)+[0]#持续时间
numberOfNode=X_NODE.shape[1]
#F=np.array([[1,4,2,0,0,0],[3,4,2]])
FF=[]
count=0
len_f=1
while 1:
s_list,Relation=caculation(FF,E_i,E_j,D,numberOfNode,len_f)
#所有并行的节点
splice_nodes=SP_nodes(s_list,numberOfNode)
f=find_F(splice_nodes,rc,R)
#print(f)
#global len_f
len_f=len(f)
if len(f)==0:
s_list,Relation=caculation(FF,E_i,E_j,D,numberOfNode,len_f)
break
if count==0:FF=f
else: FF=np.row_stack([FF,f])
print('FF长度:',len(FF))
count = count+1
end_list=list(map(lambda x: x[0]+x[1], zip(s_list, D)))
#name=list(np.array((list(map(lambda x:'活动'+str(x[0]),zip(range(len(D))))))))
name=['虚拟开始节点']+list(pd.read_excel(routh)['任务名称'].values)+['虚拟结束节点']
gante=pd.DataFrame(np.array((name,s_list,end_list)).T)
gante.columns=['活动名称','开始时间','结束时间']
gante2=gante.copy()
gante2.to_csv('sanji_不限制每一轮个数.csv')
base_day=datetime.date(2022,10,31)#基础日期
gante2=gante.copy()
for i in range(len(gante2)):
for j in range(1,3):
#print(i,j)
#if type(float(gante.iloc[i,j])) == str:
gante2.iloc[i,j]=base_day+datetime.timedelta(days=int(float(gante2.iloc[i,j])))
# 重命名索引
df = gante2.rename(columns = {'活动名称':'Task',
'开始时间':'Start',
'结束时间':'Finish'})
# 使用 plotly 绘图
fig = ff.create_gantt(df,height=20*len(df),width=20*len(df))
fig.show()
py.offline.plot(fig,filename='result.html')
return print('Finish')
def main():
try:
act(routh)
except gp.GurobiError as e:
print('Error code ' + str(e.errno) + ': ' + str(e))
except AttributeError:
print('Encountered an attribute error')
if __name__== "__main__" :
rc=8
routh='D:\liuyuyang\code\整理\三级原表.xlsx'
main()
遗传算法模拟一个人工种群的进化过程,通过选择(Selection)、交叉(Crossover)以及变异(Mutation)等机制,在每次迭代中都保留一组候选个体,重复此过程,种群经过若干代进化后,理想情况下其适应度达到近似最优的状态。
适应度函数:如果满足两条约束则目标函数为适应度函数值,如果违反约束则设置极大值为适应度函数值。
选择:
def selection(self, tourn_size=3):
aspirants_idx = np.random.randint(self.size_pop, size=(self.size_pop, tourn_size))
aspirants_values = self.FitV[aspirants_idx]
winner = aspirants_values.argmax(axis=1) # winner index in every team
sel_index = [aspirants_idx[i, j] for i, j in enumerate(winner)]
self.Chrom = self.Chrom[sel_index, :]
return self.Chrom
交叉:
def crossover(self):
#Chrom, size_pop, len_chrom = self.Chrom, self.size_pop, self.len_chrom
for i in range(0, self.size_pop, 2):
Chrom1, Chrom2 = self.Chrom[i], self.Chrom[i + 1]
cxpoint1, cxpoint2 = np.random.randint(0, self.len_chrom - 1, 2)
if cxpoint1 >= cxpoint2:
cxpoint1, cxpoint2 = cxpoint2, cxpoint1 + 1
# crossover at the point cxpoint1 to cxpoint2
pos1_recorder = {value: idx for idx, value in enumerate(Chrom1)}
pos2_recorder = {value: idx for idx, value in enumerate(Chrom2)}
for j in range(cxpoint1, cxpoint2):
value1, value2 = Chrom1[j], Chrom2[j]
pos1, pos2 = pos1_recorder[value2], pos2_recorder[value1]
Chrom1[j], Chrom1[pos1] = Chrom1[pos1], Chrom1[j]
Chrom2[j], Chrom2[pos2] = Chrom2[pos2], Chrom2[j]
pos1_recorder[value1], pos1_recorder[value2] = pos1, j
pos2_recorder[value1], pos2_recorder[value2] = j, pos2
self.Chrom[i], self.Chrom[i + 1] = Chrom1, Chrom2
return self.Chrom
变异:
def reverse(individual):
n1, n2 = np.random.randint(0, individual.shape[0] - 1, 2)
if n1 >= n2:
n1, n2 = n2, n1 + 1
individual[n1:n2] = individual[n1:n2][::-1]
return individual
def mutation(self):
for i in range(self.size_pop):
if np.random.rand() < self.prob_mut:
self.Chrom[i] = reverse(self.Chrom[i])
return self.Chrom
完整代码
from sko.GA import GeneticAlgorithmBase
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sko.operators import crossover, mutation, ranking, selection
import plotly.figure_factory as ff
import plotly.io as pio
import plotly as py
import datetime
pio.renderers.default = "notebook"
class GA_AVIC(GeneticAlgorithmBase):
def __init__(self, func, n_dim,
size_pop=50, max_iter=200, prob_mut=0.001,
constraint_eq=tuple(), constraint_ueq=tuple(), early_stop=None):
super().__init__(func, n_dim, size_pop, max_iter, prob_mut, constraint_eq, constraint_ueq, early_stop)
self.has_constraint = True
self.len_chrom = self.n_dim
self.crtbp()
def crtbp(self):
# create the population
tmp = np.random.rand(self.size_pop, self.len_chrom)
self.Chrom = tmp.argsort(axis=1)
return self.Chrom
def chrom2x(self, Chrom):
return Chrom
def ranking(self):
# GA select the biggest one, but we want to minimize func, so we put a negative here
self.FitV = -self.Y
def selection(self, tourn_size=3):
aspirants_idx = np.random.randint(self.size_pop, size=(self.size_pop, tourn_size))
aspirants_values = self.FitV[aspirants_idx]
winner = aspirants_values.argmax(axis=1) # winner index in every team
sel_index = [aspirants_idx[i, j] for i, j in enumerate(winner)]
self.Chrom = self.Chrom[sel_index, :]
return self.Chrom
def crossover(self):
#Chrom, size_pop, len_chrom = self.Chrom, self.size_pop, self.len_chrom
for i in range(0, self.size_pop, 2):
Chrom1, Chrom2 = self.Chrom[i], self.Chrom[i + 1]
cxpoint1, cxpoint2 = np.random.randint(0, self.len_chrom - 1, 2)
if cxpoint1 >= cxpoint2:
cxpoint1, cxpoint2 = cxpoint2, cxpoint1 + 1
# crossover at the point cxpoint1 to cxpoint2
pos1_recorder = {value: idx for idx, value in enumerate(Chrom1)}
pos2_recorder = {value: idx for idx, value in enumerate(Chrom2)}
for j in range(cxpoint1, cxpoint2):
value1, value2 = Chrom1[j], Chrom2[j]
pos1, pos2 = pos1_recorder[value2], pos2_recorder[value1]
Chrom1[j], Chrom1[pos1] = Chrom1[pos1], Chrom1[j]
Chrom2[j], Chrom2[pos2] = Chrom2[pos2], Chrom2[j]
pos1_recorder[value1], pos1_recorder[value2] = pos1, j
pos2_recorder[value1], pos2_recorder[value2] = j, pos2
self.Chrom[i], self.Chrom[i + 1] = Chrom1, Chrom2
return self.Chrom
def reverse(individual):
n1, n2 = np.random.randint(0, individual.shape[0] - 1, 2)
if n1 >= n2:
n1, n2 = n2, n1 + 1
individual[n1:n2] = individual[n1:n2][::-1]
return individual
def mutation(self):
for i in range(self.size_pop):
if np.random.rand() < self.prob_mut:
self.Chrom[i] = reverse(self.Chrom[i])
return self.Chrom
#ranking = ranking.ranking
#selection = selection.selection_tournament_faster
#crossover = crossover.crossover_pmx
#mutation = mutation.mutation_reverse
def run(self):
self.max_iter = self.max_iter
for i in range(self.max_iter):
Chrom_old = self.Chrom.copy()
print('当前第{}代,共{}代,完成度{}%'.format(i + 1, self.max_iter, float(i / self.max_iter * 100)))
self.X = self.chrom2x(self.Chrom)
self.Y = self.x2y()
self.ranking()
self.selection()
self.crossover()
self.mutation()
# put parent and offspring together and select the best size_pop number of population
self.Chrom = np.concatenate([Chrom_old, self.Chrom], axis=0)
self.X = self.chrom2x(self.Chrom)
self.Y = self.x2y()
self.ranking()
selected_idx = np.argsort(self.Y)[:self.size_pop]
self.Chrom = self.Chrom[selected_idx, :]
# record the best ones
generation_best_index = self.FitV.argmax()
self.generation_best_X.append(self.X[generation_best_index, :].copy())
self.generation_best_Y.append(self.Y[generation_best_index])
self.all_history_Y.append(self.Y.copy())
self.all_history_FitV.append(self.FitV.copy())
global_best_index = np.array(self.generation_best_Y).argmin()
self.best_x = self.generation_best_X[global_best_index]
self.best_y = self.func(np.array([self.best_x]))
return self.best_x, self.best_y
class caculation():
def __init__(self, path, resource_ceiling, max_iter=100, prob_mut=0.001):
self.path = path
self.resource_ceiling = resource_ceiling
self.max_iter = max_iter
self.prob_mut = prob_mut
self.a = pd.read_excel(self.path)['后续'].values # 后续节点
self.R = pd.read_excel(self.path)['资源1需求'].values # 资源需求
self.D = pd.read_excel(self.path)['工期'].values # 持续时间
self.n_dim = len(self.a)
self.consq = []
self.basedata=datetime.date(2022, 10, 31)
for i in range(len(self.a)):
temp = str(self.a[i]).translate(str.maketrans(",", ",")).split(',')
if temp[0] == 'nan': continue
temp_l = [int(j) for j in temp]
for k in temp_l:
self.consq.append(lambda x: eval('x[{}]-x[{}]'.format(i, k - 1)))
def result(self):
ga = GA_AVIC(func=self.fun2, n_dim=self.n_dim, size_pop=50, max_iter=self.max_iter, prob_mut=self.prob_mut,
constraint_ueq=self.consq)
self.best_x, self.best_y = ga.run()
self.start_time, self.finish_time = self.ssgs(self.best_x)
self.Y_history = pd.DataFrame(ga.all_history_Y)
name = list(pd.read_excel(self.path)['任务名称'].values)
gante = pd.DataFrame(np.array((name, self.start_time, self.finish_time)).T)
gante.columns = ['活动名称', '开始时间', '结束时间']
gante2 = gante.copy()
gante2.to_csv('活动时间(数字).csv')
base_day = self.basedata # 基础日期
for i in range(len(gante2)):
for j in range(1, 3):
# print(i,j)
# if type(float(gante.iloc[i,j])) == str:
gante2.iloc[i, j] = base_day + datetime.timedelta(days=int(float(gante2.iloc[i, j])))
# 重命名索引
gante2.to_csv('活动时间(日期).csv')
self.gt=gante2
return self.start_time, self.finish_time, self.best_x, self.best_y
def figure(self):
fig, ax = plt.subplots(2, 1)
ax[0].plot(self.Y_history.index, self.Y_history.values, '.', color='red')
self.Y_history.min(axis=1).cummin().plot(kind='line')
plt.show()
def gantt(self):
gt=self.gt
df =gt.rename(columns={'活动名称': 'Task',
'开始时间': 'Start',
'结束时间': 'Finish'})
# 使用 plotly 绘图
fig = ff.create_gantt(df, height=20 * len(df), width=20 * len(df))
fig.show()
py.offline.plot(fig, filename='result.html')
return print('Finish')
def fun2(self, p):
self.start_time, self.finish_time = self.ssgs(p)
return self.finish_time[-1]
def ssgs(self, x):
t = 0
# print("x:",x)
start_time = np.array([t])
finish_time = np.array([t + self.D[x[0]]])
resource_usage = self.R[x[0]]
check_points = {t + self.D[x[0]]}
i = 1
while 1:
if resource_usage + self.R[x[i]] <= self.resource_ceiling:
resource_usage = resource_usage + self.R[x[i]]
start_time = np.append(start_time, t)
finish_time = np.append(finish_time, start_time[i] + self.D[x[i]])
check_points.add(start_time[i] + self.D[x[i]])
if i == len(x) - 1: break
i = i + 1
else:
# print((self.start_time))
# print(i)
# print("check_points:",check_points)
# print("resource_usags:",resource_usage)
t = list(check_points)[0]
check_points.remove(t)
ss = np.argwhere(finish_time == t)[:, 0]
for j in ss:
resource_usage = resource_usage - self.R[x[j]]
# print(('资源释放:',j))
# fin=list(check_points)[-1]
return start_time, finish_time
def main():
try:
cacu=caculation(routh,max_iter=2,resource_ceiling=rc)
s,f,x,y=cacu.result()
cacu.figure()
except AttributeError:
print('Encountered an attribute error')
if __name__== "__main__" :
rc=15
routh='D:\liuyuyang\code\整理\三级原表.xlsx'#文件路径
main()