前言
本文档介绍了遗传算法用于解决车间任务调度问题的方案。
方案考虑了物料加工的先后顺序,机器对于不同工序加工的兼容性。使用者可以自定义损失函数,以满足不同的优先级标准。
算法最终目的是为了找到一种调度方案,最大化满足使用者的标准。
一、任务描述
任务要求
① 有N个工件(J1, J2, …, JN),每个工件包含若干个工序(P1, P2, …, PM)。
② 有M台机器(M1, M2, …, MM),每台机器可以加工一部分工序。
③ 每个工序有一组优先加工机器列表,例如,工件J1的工序P1优先在M1和M4 上加工,工序P2优先在M2上加工,工序P3没有优先加工机器。
④ 同一工件的工序必须按照指定的优先级顺序进行加工,不能交叉进行。
⑤ 同一台机器同一时刻只能加工一个工序。
⑥ 目标是找到一个调度方案,使得所有工件的工序在满足优先级的前提下,尽量减少总加工时间。
约束条件
在解决工件加工优先级问题时,需要考虑以下约束条件:
① 每个工件的工序必须按照其优先级顺序进行加工,不能改变工序的顺序。
② 每个工件的工序必须在兼容的机器上进行加工,不能随意分配机器。
③ 每台机器同一时刻只能加工一个工序,不能同时进行多个工序的加工。
二、解决方案
为了解决这个问题,我们将使用遗传算法进行优化。遗传算法是一种模拟自然选择和遗传机制的优化算法,适用于组合优化问题,如调度问题。
遗传算法的优化过程涉及以下几个主要步骤:
a. 初始化种群(Initialize Population):
首先,我们创建一个种群,其中包含多个随机生成的调度方案(个体)。每个调度方案表示了一种工件和工序在机器上的安排方式。为了表示工件的顺序,我们采用整数编码,其中每个整数代表一个工件,并且整数的顺序对应于工件在调度中的顺序。由于工件的加工顺序是固定的,因此整数编码也能唯一确定工件的排列顺序。基因由工件所对应的一组具体工序的加工顺序组成,该顺序是根据工序加工前后依赖顺序确定的。
# 遗传算法参数
population_size = 1000
mutation_rate = 0.1
tournament_size = int(population_size * 0.05)
elite_size = int(population_size * 0.01)
max_generations = 100
# 遗传算法参数
# 工件集合
jobs = []
# 机器集合
machines = []
# 工件加工优先级的字典
priority_machine = {
'J1': {'P1': ['M1', 'M4'], 'P2': ['M2'], 'P3': []},
'J2': {'P1': ['M1'], 'P4': ['M5', 'M6'], 'P3': ['M3']},
'J3': {'P4': ['M5'], 'P2': ['M2'], 'P3': ['M3']},
'J4': {'P1': ['M1'], 'P2': ['M2'], 'P3': ['M3']},
'J5': {'P2': ['M2'], 'P4': ['M6'], 'P3': ['M3']},
'J6': {'P4': ['M5'], 'P1': ['M1'], 'P3': ['M3']},
'J7': {'P3': [], 'P2': ['M2'], 'P1': ['M1']},
'J8': {'P1': ['M1'], 'P4': ['M6'], 'P2': ['M2']},
'J9': {'P4': ['M5', 'M6'], 'P3': ['M6'], 'P2': ['M2']},
'J10': {'P5': ['M7']},
'J11': {'P1': ['M1'], 'P2': ['M2'], 'P3': ['M3']},
'J12': {'P1': ['M1'], 'P4': ['M6'], 'P3': []},
'J13': {'P4': ['M6'], 'P2': [], 'P3': []},
'J14': {'P1': ['M1'], 'P2': [], 'P3': []},
'J15': {'P6': ['M8']},
'J16': {'P4': [], 'P1': ['M1'], 'P3': []},
'J17': {'P3': [], 'P2': ['M2'], 'P1': []},
'J18': {'P1': [], 'P4': [], 'P2': ['M2']},
'J19': {'P4': ['M6'], 'P3': ['M3'], 'P2': []},
'J20': {'P2': ['M2'], 'P1': [], 'P3': []},
}
# list中index为0的dict表示工件对应的工序加工时间
# list中index为1的list表示当前工件加工所需要的前置工件,若为None则无前置工件要求
job_data = {
'J1': [{'P1': 5, 'P2': 3, 'P3': 4}, None], # 工件J1的工序时间字典
'J2': [{'P1': 2, 'P4': 4, 'P3': 6}, None], # 工件J2的工序时间字典
'J3': [{'P4': 3, 'P2': 2, 'P3': 3}, None], # 工件J3的工序时间字典
'J4': [{'P1': 4, 'P2': 5, 'P3': 6}, None], # 工件J4的工序时间字典
'J5': [{'P2': 3, 'P4': 5, 'P3': 4}, None], # 工件J5的工序时间字典
'J6': [{'P4': 6, 'P1': 3, 'P3': 7}, None], # 工件J6的工序时间字典
'J7': [{'P3': 5, 'P2': 4, 'P1': 4}, None], # 工件J7的工序时间字典
'J8': [{'P1': 3, 'P4': 5, 'P2': 3}, None], # 工件J8的工序时间字典
'J9': [{'P4': 4, 'P3': 4, 'P2': 5}, None], # 工件J9的工序时间字典
'J10': [{'P5': 12}, ['J16', 'J2']], # 工件J10的工序时间字典
'J11': [{'P1': 3, 'P2': 4, 'P3': 5}, None], # 工件J11的工序时间字典
'J12': [{'P1': 2, 'P4': 4, 'P3': 3}, None], # 工件J12的工序时间字典下
'J13': [{'P4': 3, 'P2': 5, 'P3': 6}, None], # 工件J13的工序时间字典
'J14': [{'P1': 4, 'P2': 3, 'P3': 5}, None], # 工件J14的工序时间字典
'J15': [{'P6': 9}, ['J13', 'J5', 'J10']], # 工件J15的工序时间字典
'J16': [{'P4': 4, 'P1': 6, 'P3': 3}, None], # 工件J16的工序时间字典
'J17': [{'P3': 5, 'P2': 4, 'P1': 7}, None], # 工件J17的工序时间字典
'J18': [{'P1': 3, 'P4': 4, 'P2': 3}, None], # 工件J18的工序时间字典
'J19': [{'P4': 6, 'P3': 5, 'P2': 4}, None], # 工件J19的工序时间字典
'J20': [{'P2': 3, 'P1': 5, 'P3': 7}, None], # 工件J20的工序时间字典
}
machine_data = {
'M1': ['P1'], # 机器M1能够加工的工序为P1
'M2': ['P2'], # 机器M2能够加工的工序为P2
'M3': ['P3'], # 机器M3能够加工的工序为P3
'M4': ['P1', 'P2'], # 机器M4能够加工的工序为P1和P2
'M5': ['P2', 'P4'], # 机器M5能够加工的工序为P2和P3
'M6': ['P4', 'P3'], # 机器M6能够加工的工序为P1和P3
'M7': ['P5'],
'M8': ['P6']
}
# 希望J在n时刻前完成
order_completion_time = {
'J1': 45,
'J2': 55,
'J3': 50,
'J4': 60,
'J5': 40,
'J6': 58,
'J7': 42,
'J8': 48,
'J9': 55,
'J10': 52,
'J11': 47,
'J12': 56,
'J13': 39,
'J14': 43,
'J15': 59,
'J16': 38,
'J17': 57,
'J18': 49,
'J19': 41,
'J20': 53,
}
# 根据machine_data反转生成工序P可以在哪些机器M上面加工
# process_to_machines_dict:{'P1': ['M1', 'M4'], 'P2': ['M2', 'M4', 'M5'], 'P3': ['M3', 'M6'], 'P4': ['M5', 'M6'], 'P5': ['M7'], 'P6': ['M8']}
def process_to_machines():
process_to_machines_dict = {}
for machine, processes in machine_data.items():
for process in processes:
if process in process_to_machines_dict:
process_to_machines_dict[process].append(machine)
else:
process_to_machines_dict[process] = [machine]
return process_to_machines_dict
# 工件类
class Job:
def __init__(self, job_id, operations, compose):
self.job_id = job_id
self.operations = operations
self.compose = compose
# 机器类
class Machine:
def __init__(self, machine_id):
self.machine_id = machine_id
self.capabilities = [] # 机器能够加工的工序列表
self.current_time = 0
# 初始化工件集和机器集
def initialize_jobs_machines(job_data, machine_data):
"""
初始化工件集和机器集
Args:
job_data (dict): 工件数据,键为工件ID,值为工件的工序字典
machine_data (dict): 机器数据,键为机器ID,值为机器能够加工的工序列表
"""
for job_id, information in job_data.items():
operations, compose = information
job = Job(job_id, operations, compose)
jobs.append(job)
for machine_id, capabilities in machine_data.items():
machine = Machine(machine_id)
machine.capabilities = capabilities
machines.append(machine)
# 生成初始种群
def generate_population(population_size):
"""
生成初始种群
Args:
population_size (int): 种群大小
Returns:
list: 初始种群,包含多个调度方案(染色体)
"""
job_sequence = []
process_sequence = []
for _ in range(population_size):
chromosome = list(range(len(jobs))) # 使用工件的ID作为基因编码
random.shuffle(chromosome) # 随机打乱基因顺序
job_sequence.append(chromosome) # 将染色体添加到种群中
for job_gene in job_sequence:
chromosome = [] # 存储染色体的列表
for job_index in job_gene:
job = jobs[job_index]
gene = [] # 存储基因的列表
for operation_name in job.operations:
capable_machines = [] # 存储能够加工当前工序的机器的列表
for machine in machines:
if operation_name in machine.capabilities:
capable_machines.append(machine.machine_id)
if capable_machines:
# 从可行机器列表中随机选择一个机器作为该工序的调度机器
machine_id = random.choice(capable_machines)
gene.append((job.job_id, operation_name, machine_id))
else:
# 当前工序没有可行的机器
raise ValueError(f"工件{job.job_id} 的工序 {operation_name} 没有可行的机器进行加工")
chromosome.extend([gene]) # 将基因添加到染色体中
process_sequence.append(chromosome) # 将染色体添加到种群中
return job_sequence, process_sequence
b. 计算适应度(Fitness Calculation):
对于种群中的每个调度方案,我们需要计算其适应度值,也就是该调度方案的总加工时间,它用于评估每个个体(调度方案)在解决问题中的优劣程度。总加工时间越短,适应度值越高。适应度计算时按照调度方案的顺序依次安排每个工件的工序,并根据每个机器的可用时间来计算每个工序的开始和结束时间,以此来得到该个体的总加工时间。在遗传算法中,通常希望最小化问题,因此取总加工时间的负值。此外,我们根据给出的优先级匹配字典(priority_machine),计算出优先级奖励。接着,我们计算优先级奖励与总加工时间的差值,将其作为适应度的第二个值。这样做的目的是,当总加工时间相同时,优先选择具有更高优先级的工序,以提高解决方案的质量。最后,适应度函数返回这两个值的元组,作为该调度方案的适应度。遗传算法会根据这些适应度值来选择优秀的个体,并通过交叉和变异操作产生下一代种群,逐步寻找更优的解决方案。
# 进化过程
def evolution(job_population, process_population):
"""
进化过程,生成下一代种群
Args:
job_population (list): 当前种群的工件编码序列
process_population (list): 当前种群的过程编码序列
Returns:
list, list: 两个列表,分别为进化后种群的工件编码序列和过程编码序列
"""
new_job_population = []
new_process_population = []
for _ in range(elite_size):
best_index = max(range(len(job_population)), key=lambda i: fitness_function(process_population[i])[1])
new_job_population.append(job_population[best_index])
new_process_population.append(process_population[best_index])
while len(new_job_population) < population_size:
parents_index = selection(job_population, process_population)
parent1_index, parent2_index = parents_index
offspring_job, offspring_process = partially_mapped_crossover(job_population[parent1_index], process_population[parent1_index],
job_population[parent2_index], process_population[parent2_index])
offspring_job, offspring_process = mutate(offspring_job, offspring_process)
new_job_population.append(offspring_job)
new_process_population.append(offspring_process)
return new_job_population, new_process_population
# 适应度函数(此处使用总加工时间作为适应度)
def fitness_function(process_sequence):
"""
计算染色体的适应度(总加工时间和优先级奖励的差值)
Args:
process_sequence (list): 调度方案(染色体)
Returns:
tuple: 适应度元组,包含总加工时间的负值和优先级奖励与总加工时间差值
"""
schedule = calculate_schedule(process_sequence)
if not schedule[-1]:
return float('-inf')
# 比较所有工件的最后一项工序的结束时间
total_processing_time = max([sub_schedule[-1][1] for sub_schedule in schedule])
# 计算优先加工机器满足率
priority_matching = calculate_priority_reward(process_sequence) # 计算满足条件
total_processes = count_processes() # 计算总数量
priority_reward = priority_matching / total_processes
# 计算订单完成时间满足率
order_completion_matching = calculate_order_completion_reward(schedule)
order_completion_reward = order_completion_matching / len(job_data)
total_reward = priority_reward + (1 * order_completion_reward) - total_processing_time
return -total_processing_time, total_reward
# 计算工件在每个机器上的加工时间表
def calculate_schedule(process_chromosome):
"""
计算工件在每个机器上的加工时间表
Args:
process_chromosome (list): 调度方案(过程编码染色体)
Returns:
list: 工件在每个机器上的加工时间表,二维列表
"""
# 重置机器的 current_time
for machine in machines:
machine.current_time = 0
# 根据 job_data 创建一个新的词典,将所有值初始化为0
job_count = {key: 0 for key in job_data}
schedule = [[] for _ in range(len(jobs))]
# 存储暂时无法加工的组合工件
leftover_composer = []
for process_genes in process_chromosome:
job_name = process_genes[0][0] # 获取工件名字
job_index = None
for index, job in enumerate(jobs):
if job.job_id == job_name:
job_index = index
break
if job_index is None:
raise ValueError(f"找不到工件名字 '{job_name}' 对应的工件编码。")
# 如果是组合工件,判断能否加工
# 如果能则正常计算加工时间
# 如果不能则加入leftover_composer
if job_data[job_name][1] is not None and not all(job_count[job_child_name] > 0 for job_child_name in job_data[job_name][1]):
leftover_composer.append(process_genes)
continue
for gene in process_genes:
_, operation_name, machine_id = gene
machine_index = None
for index, machine in enumerate(machines):
if machine.machine_id == machine_id:
machine_index = index
break
if machine_index is None:
raise ValueError(f"找不到机器编号 '{machine_id}' 对应的机器索引。")
operation_time = jobs[job_index].operations[operation_name]
if job_data[job_name][1] is not None:
sub_composer_max_time = calculate_start_time(job_data[process_genes[0][0]][1], schedule)
start_time = max(machines[machine_index].current_time, sub_composer_max_time)
else:
start_time = max(machines[machine_index].current_time,
schedule[job_index][-1][1] if schedule[job_index] else 0)
end_time = start_time + operation_time
machines[machine_index].current_time = end_time
schedule[job_index].append((start_time, end_time, machine_id, operation_name))
# 判断是组合工件还是独立工件
# 如果是组合工件则需要消减子工件数量,增加组合工件数量
if job_data[job_name][1] is not None:
for job_child_name in job_data[job_name][1]:
job_count[job_child_name] -= 1
job_count[job_name] += 1
else:
job_count[job_name] += 1
# 检查当前零件加工完之后,leftover_composer中零件是否能够加工
# 如果能则加工
# 如果不能则继续进入leftover_composer
for index_leftover_composer, compose in enumerate(leftover_composer):
if all(job_count[job_child_name] > 0 for job_child_name in job_data[compose[0][0]][1]):
job_name = compose[0][0] # 获取工件名字
job_index = None
for index, job in enumerate(jobs):
if job.job_id == job_name:
job_index = index
break
if job_index is None:
raise ValueError(f"找不到工件名字 '{job_name}' 对应的工件编码。")
for gene in compose:
_, operation_name, machine_id = gene
machine_index = None
for index, machine in enumerate(machines):
if machine.machine_id == machine_id:
machine_index = index
break
if machine_index is None:
raise ValueError(f"找不到机器编号 '{machine_id}' 对应的机器索引。")
operation_time = jobs[job_index].operations[operation_name]
# 组合工件的起始时间应该取决于max(子工件完成的最晚时间,加工机器的结束时间)
sub_composer_max_time = calculate_start_time(job_data[compose[0][0]][1], schedule)
start_time = max(machines[machine_index].current_time, sub_composer_max_time)
end_time = start_time + operation_time
machines[machine_index].current_time = end_time
schedule[job_index].append((start_time, end_time, machine_id, operation_name))
job_count[job_name] += 1
for child_composer in job_data[compose[0][0]][1]:
job_count[child_composer] -= 1
del leftover_composer[index_leftover_composer]
while_num = 0
max_num = min(math.factorial(len(leftover_composer)), 100000)
while leftover_composer:
if while_num > max_num:
return None
for index_leftover_composer, compose in enumerate(leftover_composer):
if all(job_count[job_child_name] > 0 for job_child_name in job_data[compose[0][0]][1]):
job_name = compose[0][0] # 获取工件名字
job_index = None
for index, job in enumerate(jobs):
if job.job_id == job_name:
job_index = index
break
if job_index is None:
raise ValueError(f"找不到工件名字 '{job_name}' 对应的工件编码。")
for gene in compose:
_, operation_name, machine_id = gene
machine_index = None
for index, machine in enumerate(machines):
if machine.machine_id == machine_id:
machine_index = index
break
if machine_index is None:
raise ValueError(f"找不到机器编号 '{machine_id}' 对应的机器索引。")
operation_time = jobs[job_index].operations[operation_name]
# 组合工件的起始时间应该取决于max(子工件完成的最晚时间,加工机器的结束时间)
sub_composer_max_time = calculate_start_time(job_data[compose[0][0]][1], schedule)
start_time = max(machines[machine_index].current_time, sub_composer_max_time)
end_time = start_time + operation_time
machines[machine_index].current_time = end_time
schedule[job_index].append((start_time, end_time, machine_id, operation_name))
job_count[job_name] += 1
for child_composer in job_data[compose[0][0]][1]:
job_count[child_composer] -= 1
del leftover_composer[index_leftover_composer]
while_num += 1
for sub_schedule in schedule:
if not sub_schedule:
raise ValueError("存在没有被安排任何工序的工件")
return schedule
c. 选择(Selection):
使用一种选择策略(如锦标赛算法)从当前种群中随机选择若干个调度方案作为备选,再从备选中计算最好的两个个体,用于交叉产生子代。
# 选择操作(此处使用锦标赛选择)
def selection(job_population, process_population):
"""
执行选择操作,选择优秀的个体作为下一代的父代
Args:
job_population (list): 当前种群的工件编码序列
process_population (list): 当前种群的过程编码序列
Returns:
list: 父代染色体索引集合,包含两个最好的个体索引
"""
selected_parents = []
for _ in range(2): # 返回两个最好的个体作为父代
tournament = random.sample(range(len(job_population)), tournament_size)
best_index = max(tournament, key=lambda i: fitness_function(process_population[i])[1])
selected_parents.append(best_index)
return selected_parents
d. 交叉(Crossover):
将选择的父代进行交叉操作,产生新的调度方案作为子代。交叉操作旨在将两个父代的优良特征结合起来,以期望得到更好的解。在本项目中采用部分映射交叉(Partially-Mapped Crossover,PMX)来完成交叉遗传。PMX交叉是基于排列编码(permutation encoding)的遗传算法中常用的交叉算子,主要用于解决排列问题。
下面详细解释 partially_mapped_crossover 方法的实现:
① 首先,定义了两个父代个体,分别是 parent1_job 和 parent2_job,代表工件编码序列,以及 parent1_process 和 parent2_process,代表过程编码序列。
② 初始化子代个体,child_job 和 child_process,初始值为 None。
③ 随机选择两个不相同的位置 start 和 end,并保证 start 小于 end,以便后面进行部分映射。
④ 将 parent1_job 中 start 到 end 范围内的元素复制到子代 child_job 对应的位置, 同样将 parent1_process 中的相应元素复制到 child_process 中。
⑤ 接下来,根据 parent2_job 的信息,填充子代 child_job 中的剩余位置。从 parent2_job 中取出一个未处理的元素,如果这个元素不在子代 child_job 中,就找到对应的 parent1_job 中的位置,并检查该位置在子代 child_job 中是否已经被填充,如果没有填充,则将对应的 parent2_job 和 parent2_process 中的元素填充到子代相应位置, 直到子代 child_job 中的所有位置都被填充。
⑥ 最后,如果子代 child_job 中还有空位,说明 parent2_job 中对应位置的元素在 parent1_job 中没有对应关系,因此直接将 parent2_job 和 parent2_process 中的元素填充到子代相应位置。
⑦ 返回生成的子代工件编码序列 child_job 和子代过程编码序列 child_process。
PMX交叉通过随机选择片段并进行部分映射,保留了两个父代个体的信息,同时确保了子代个体的有效性和多样性。这有助于遗传算法在搜索过程中逐步优化,最终找到更优的解决方案。
# 交叉操作(此处使用顺序交叉)
def partially_mapped_crossover(parent1_job, parent1_process, parent2_job, parent2_process):
"""
执行部分映射交叉(PMX),生成子代染色体
Args:
parent1_job (list): 父代工件编码序列
parent1_process (list): 父代过程编码序列
parent2_job (list): 另一个父代工件编码序列
parent2_process (list): 另一个父代过程编码序列
Returns:
list, list: 子代工件编码序列和过程编码序列
"""
size = len(parent1_job)
child_job = [None] * size
child_process = [None] * size
while True:
start, end = sorted(random.sample(range(size), 2))
if start != end and start < end:
break
child_job[start:end] = parent1_job[start:end]
child_process[start:end] = parent1_process[start:end]
for i in range(size):
if parent2_job[i] not in child_job:
index = i
while child_job[index] is not None:
index = parent2_job.index(parent1_job[index])
if child_job[index] is None:
child_job[index] = parent2_job[i]
child_process[index] = parent2_process[i]
for i in range(size):
if child_job[i] is None:
child_job[i] = parent2_job[i]
child_process[i] = parent2_process[i]
return child_job, child_process
e. 变异(Mutation):
对种群中的一些个体(调度方案)执行变异操作。变异是为了引入新的解决方案,增加种群的多样性,避免陷入局部最优解。我们随机交换两个工件的位置及其对应加工工序的位置,来完成变异操作。
# 变异操作(此处使用基于概率的变异)
def mutate(offspring_job, offspring_process):
"""
执行变异操作,对染色体进行突变
Args:
offspring_job (list): 工件编码染色体
offspring_process (list): 过程编码染色体
Returns:
list, list: 突变后的工件编码染色体和过程编码染色体
"""
if random.random() < mutation_rate:
point1, point2 = random.sample(range(len(offspring_job)), 2)
temp1 = offspring_job[point1]
offspring_job[point1] = offspring_job[point2]
offspring_job[point2] = temp1
temp2 = offspring_process[point1]
offspring_process[point1] = offspring_process[point2]
offspring_process[point2] = temp2
return offspring_job, offspring_process
f. 迭代(Iteration):
重复进行选择、交叉和变异操作,逐代演化种群,直到满足终止条件(如达到最大迭代次数)。
g. 收敛与输出(Convergence and Output):
在遗传算法的迭代过程中,记录每一代种群中适应度最优的调度方案。最终,输出找到的最优调度方案作为工件加工的解决方案。
通过遗传算法的进化过程,我们希望能够找到最优的工件加工顺序,使得总加工时间最小化,并且尽量满足每个工序的优先级要求。最终,算法将输出最优调度方案,并绘制甘特图和适应度曲线,以便进一步分析和优化。
# 绘制甘特图
def draw_gantt_chart(machine_data, best_job_chromosome, best_schedule):
"""
绘制甘特图,展示最优调度方案的工序在机器上的加工情况。
Args:
machine_data (dict): 机器能够加工的工序字典
best_job_chromosome (list): 最优调度方案的染色体,包含工件和工序编码序列
best_schedule (list): 最优调度方案的工序加工顺序列表,包含工序的开始时间、结束时间、机器编号等信息
Returns:
None
"""
max_end_time = max([schedule[-1][1] for schedule in best_schedule])
# 创建图表对象和坐标轴对象
fig, ax = plt.subplots(figsize=(max_end_time+1, len(machine_data)))
# 定义机器编号和对应的纵坐标位置
machine_ids = list(machine_data.keys())
machine_positions = list(range(len(machine_data)))
# 获取tab20颜色映射
tab20 = cm.get_cmap('tab20', len(best_job_chromosome))
# 绘制每个工件的甘特图
for i, schedule in enumerate(best_schedule):
color_index = i % 20 # 循环使用tab20c的20种颜色
color = tab20(color_index)
for start_time, end_time, machine_id, operation_name in schedule:
machine_index = machine_ids.index(machine_id)
duration = end_time - start_time
ax.barh(machine_index, duration, left=start_time, height=0.6, color=color,
edgecolor="black")
label = f"{jobs[i].job_id}.{operation_name}"
ax.text((start_time + end_time) / 2, machine_index, label, ha='center', va='center')
# 设置纵坐标刻度和标签
ax.set_yticks(machine_positions)
ax.set_yticklabels(machine_ids)
# 设置横坐标刻度和标签
ax.set_xticks(range(0, max_end_time + 1, 1))
ax.set_xlabel("时间")
ax.set_ylabel("机器")
# 设置标题和标签
ax.set_title("甘特图")
# 保存图表到当前路径
plt.tight_layout()
plt.savefig("gantt_chart.png", dpi=300)
plt.close() # 关闭当前图表,以便绘制新的图表
三、惩罚函数
在计算适应度函数fitness_function的时候,有三项指标加起来形成最终的适应度评分。
total_reward = priority_reward + order_completion_reward - total_processing_time
其中priority_reward表示计算有多少工序加工的时候在推荐的机器上加工。order_completion_reward表示有多少工件按时完成加工任务。- total_processing_time为总加工时间。
可根据自己的需求,写相应的惩罚函数,然后纳入适应度计算即可。还可添加权重参数,侧重不同的标准。
四、总结
建议多运行几次算法,因为有时候可能会陷入局部最优解。本文的算法考虑了一些更加复杂的情况,比如工序加工顺序,比如指定机器加工指定工序,并非简单理想情况。在惩罚函数上也有更大的支持度。