资源约束下调度算法(gurobi和遗传算法)

1、基本符号

活动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,不早于任何活动开始时间.

2、基本模型

目标:
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 sjsi>=di+(Xij1)M, M M M为极大值。

3、求解方法:

方法1:调用gurobi求解器。

对于约束(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()

方法2:使用遗传算法

遗传算法模拟一个人工种群的进化过程,通过选择(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()

你可能感兴趣的:(gurobi,算法,python)