作者简介:本人擅长运筹优化建模及算法设计,包括各类车辆路径问题、生产车间调度、二三维装箱问题,熟悉CPLEX和gurobi求解器
微信公众号:运筹优化与学习
如有运筹优化相关建模或代码定制需求,可通过微信公众号联系我们
前言
之前的推文都是介绍车辆路径问题以及装箱问题,我们以这篇为引子,开始作业车间调度问题及其求解算法学习之路。本推文将展示遗传算法求解作业车间调度问题的求解思路以及python代码。
作业车间调度问题简介
排产调度(scheduling)是生产制造业和服务业的重要决策对象,旨在目标明确的前提下,合理地将资源分配给任务。作业车间调度问题(Job-Shop Scheduling Problem,简称 JSSP)就是典型的排产调度优化问题,也正是这篇推文的主题。
作业车间调度问题可以描述为:一个车间中有mmm台功能不同的机器(machine),机器集合用I={1,2,⋯ ,m}I=\left\{1,2,\cdots,m\right\}I={1,2,⋯,m}表示。有nnn个工件(job)需要加工,工件集合用J={1,2,⋯ ,n}J=\left\{1,2,\cdots,n\right\}J={1,2,⋯,n}表示。每台机器不能同时处理两个及以上工件。每个工件需要以特定的顺序在特定的机器上进行加工,若工件jjj需要在机器mmm上加工,则称oijo_{ij}oij为一个作业(operation),作业集合用OOO表示,每个作业oijo_{ij}oij的加工时长为tijt_{ij}tij。特定的加工顺序意味着作业之间存在前后依赖关系,若一个工件的前继作业没有完成,该工件的后续作业就不能开始,且作业一旦开始就不能中断。作业车间调度问题的目标就是制定出一个满足所有约束的生产方案,使得所需的完工时间(makespan, 即所有工件加工完成所需要的时间)最小。
数学模型
参数及集合
I={1,2,⋯ ,m}I=\left\{1,2,\cdots,m\right\}I={1,2,⋯,m}:机器集合
J={1,2,⋯ ,n}J=\left\{1,2,\cdots,n\right\}J={1,2,⋯,n}:工件集合
OOO:作业集合
δj\delta_jδj:工件jjj的机器加工顺序
tijt_{ij}tij:作业oijo_{ij}oij的加工时间
变量
CmaxC_{max}Cmax:最大完工时间
sijs_{ij}sij:作业oijo_{ij}oij的开始时间
约束条件
- 每个工序当前仅当被加工一次;
- 工序的开始时间迟于前一道工序的完成时间;
- 机器每次当且仅当能加工一个工件;
- 每道工序的加工过程不可中断;
遗传算法求解作业车间调度问题
整体思路
- 步骤1 初始化随机产生nPop个染色体个体,nPop为种群规模。
- 步骤2 计算个体适应度,评价个体适应度值。
- 步骤3 按赌轮选择策略选取下一代种群。
- 步骤4a 按交叉概率Pc,对两父代个体交叉n次,从最优父代和所有后代中选择最优两染色体作为下一代。
- 步骤4b 按变异概率Pm选择个体,进行变异操作生成新个体。
- 步骤5 合并交叉、变异产生的新个体生成的新一代的种群
- 步骤6 判断是否达到终止条件,若满足则输出最优解,结束算法;否则转步骤3。
编码与解码
采用基于工序编码全排列的编码方式进行编码,染色体的长度等于所有工件的工序之和。先将工序按照1,2,⋯ ,O1,2,\cdots,O1,2,⋯,O的顺序编码,其中O代表总的工序数。编码顺序代表了工序被加工的优先级。
初始种群生成方式采用将工序编码随机全排列的方式随机生成,代码如下:
def generateRandomIndividual(population, job_number, machine_number):
ind = list(range(job_number*machine_number))
random.shuffle(ind)
population.append([ind, None])
交叉
应用于作业车间调度问题方面,常见的遗传算法交叉算子包括单点交叉,多点交叉,均匀交叉,基于工件顺序的交叉和基于工件优先顺序的交叉等。
本文采用多点交叉方式,即随机产生两个交叉点位,交换两个点位之间的基因,代码如下:
def crossover(father, mother):
indexes = range(len(father[0]))
# 随机产生交叉点位
start_index = random.choice(indexes)
end_index = random.choice(indexes[start_index:])
father_gen = father[0][start_index:end_index]
fetus = removeFromList(mother[0], father_gen)
result = []
result.extend(fetus[:start_index])
result.extend(father_gen)
result.extend(fetus[start_index:])
return [result, None]
变异
作业车间调度问题的遗传算法变异算子包括交换变异、插入变异和逆转变异等。
本文采用了交换变异的方式,即随机产生两个变异点,将两个变异点位的基因编码交换,以产生新的个体。
def mutation(population, mutation_rate):
if(random.random() > mutation_rate):
candidate = random.choice(population[1:])
swap_rnd(candidate[0])
candidate[1] = None
def swap_rnd(candidate):
id1 = random.choice(range(len(candidate)))
id2 = random.choice(range(len(candidate)))
tmp = candidate[id1]
candidate[id1] = candidate[id2]
candidate[id2] = tmp
return candidate
代码和结果展示
遗传算法主循环代码
def genetic(times, machines, n, population_number, iterations, rate):
machine_number = len(machines[0])
start_time = time.time()
population = generate_population(population_number, n, machine_number)
global_best_ind, global_best = sortAndGetBestIndividual(population, times, machines, n)
for i in range(iterations):
population = evolve(population, rate)
best_ind, best_result = sortAndGetBestIndividual(population, times, machines, n)
total_fitness, diffPercentage = getFitness(population)
if(not global_best or best_result < global_best):
global_best = best_result
global_best_ind = copy.deepcopy(best_ind)
printProgress(best_result, i, time.time() - start_time)
checkDiversity(population, diffPercentage, n, machine_number)
best_result, best_table = calculateMakespan(times, machines, global_best_ind[0], n)
print("\nOVERALL RESULT")
print("RESULT: %s" %best_result) # 目标函数值
print('the elapsed time:%ss'% (int(time.time() - start_time))) # 求解时间
print("Permutation: ")
print(fromPermutation(global_best_ind[0], n)) # 输出最终解基因编码
printTable(best_table) # 输出结果
plotResult(best_table, best_result) # 绘制甘特图
测试算例说明

第一行两个数分别代表工件数(n=10)和机器数(m=5)
每一行代表每个工件不同工序的加工时间,每个工序都用一对数字(x, y)来表示,其中x是处理该工序的机器编号,y是该工序的加工时间。
结果展示
甘特图



本文介绍了作业车间调度问题的背景和数学模型,重点阐述了如何使用遗传算法解决此问题,包括初始化种群、计算适应度、选择策略、交叉和变异操作,以及提供了一个Python实现示例。
6399

被折叠的 条评论
为什么被折叠?



