遗传算法在车间任务调度的应用示例

前言

本文档介绍了遗传算法用于解决车间任务调度问题的方案。
方案考虑了物料加工的先后顺序,机器对于不同工序加工的兼容性。使用者可以自定义损失函数,以满足不同的优先级标准。
算法最终目的是为了找到一种调度方案,最大化满足使用者的标准。

一、任务描述

任务要求

① 有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

job_sequence为1000条编码,表示工件加工顺序

在这里插入图片描述

process_sequence与job_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为总加工时间。
可根据自己的需求,写相应的惩罚函数,然后纳入适应度计算即可。还可添加权重参数,侧重不同的标准。

四、总结

建议多运行几次算法,因为有时候可能会陷入局部最优解。本文的算法考虑了一些更加复杂的情况,比如工序加工顺序,比如指定机器加工指定工序,并非简单理想情况。在惩罚函数上也有更大的支持度。

  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
遗传算法是一种通过模拟生物进化过程来解决优化问题的算法。车间调度问题是指在工厂车间中,如何合理安排不同任务的调度顺序和机器分配,以最大程度地提高生产效率和降低成本。 使用遗传算法解决车间调度问题的步骤如下: 1. 定义编码方式:将调度问题转化为基因编码,设计合适的编码方式来表示不同任务的调度顺序和机器分配。 2. 初始化种群:随机生成一定数量的初始解,即初代种群。 3. 适应度评价:根据任务完成时间、机器利用率等指标,对每个个体的适应度进行评估。 4. 选择操作:根据个体适应度的大小,选择一部分较优秀的个体作为父代。 5. 交叉操作:通过交叉操作,将选取的父代个体的部分基因进行交叉,生成新的个体。 6. 变异操作:对新生成的个体进行变异,引入随机性,以增加解空间的搜索能力。 7. 更新种群:将变异和交叉得到的新个体加入种群中,替换掉原来的个体。 8. 结束条件判断:如果满足结束条件(如达到预设的迭代次数或找到最优解),则终止算法,输出最优解。 9. 否则返回步骤3,进行下一次迭代。 在Java中,可以通过编写相应的遗传算法类和适应度评价函数来实现遗传算法解决车间调度问题。其中,编码方式可以使用字符串、数组或集合等数据结构表示个体,适应度评价函数可以根据具体情况设定。 通过不断迭代和搜索过程,遗传算法可以找到一组较优解,以满足车间调度问题的要求,提高生产效率和降低成本。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值