遗传算法求解车间调度问题(附python代码)

背景介绍

车间调度问题(Job Shop Scheduling Problem, JSSP)是一类经典的组合优化问题,它在制造业和生产管理中有着广泛的应用。JSSP 的目标是对车间中的一系列作业进行排程,以使得作业在不同机器上的加工顺序是最优的,达到某种特定的优化目标,比如最小化总的作业完成时间(即使得总完工时间尽可能短),或者最小化延迟时间、最大化产量等。车间调度问题通常被描述为一系列作业集合 J 和一系列机器集合 M,每个作业由一系列操作组成,每个操作需要在特定的机器上加工一定时间。作业调度的规则如下:
(1)每个作业的操作需要按照特定的顺序进行。
(2)同一时刻,一台机器只能加工一个操作。
(3)操作一旦开始,在完成之前不能中断。
(4)所有作业的开始加工时间不得早于零。
(5)同一时刻,一个工件只能在一台机器上进行加工

车间调度问题可以被更进一步分类,如:
(a)开放式车间调度(Open Shop Scheduling):作业的操作可以在任意顺序进行。
(b)流水式车间调度(Flow Shop Scheduling):所有作业的操作必须按照相同的顺序进行。
(c)柔性车间调度(Flexible Job Shop Scheduling):每个操作可以在多台不同但功能相似的机器上加工。

车间调度问题是 NP-难问题,这意味着没有已知的多项式时间的算法可以保证找到每个实例的最优解。因此,对于实际应用中的车间调度问题,通常会运用启发式算法和元启发式算法来寻求近似的最优解,比如遗传算法、粒子群优化、蚁群算法和禁忌搜索等。

车间调度问题的研究对于提高生产效率、降低成本、提高企业的市场竞争力等都具有重要意义。在实际应用中,调度方案通常还需要考虑各种现实限制,例如机器故障、交货期限、工人的工作时间等。因此,车间调度问题在研究和工业领域都是一个非常活跃和挑战性的领域。
在这里插入图片描述

问题简介

如下表所示为某车间调度问题数据,该问题工包含5个工件,每个工件4道工序,共有6台机器,其中工序1只能由机器1完成,工序2可由机器2或机器4完成,工序3可由机器3或者机器5完成,工序4只能由机器6完成,表中数据为各工件的各项工序在机器上加工所需的时间。
在这里插入图片描述
上述问题可以描述成一个简单的柔性车间调度问题,问题包含以下基本假设:
(1) 同一时刻同一台机器只能加工一个工件。
(2) 每个工件在某一时刻只能在一台机器上加工,不能中断操作。
(3) 同一工件有先后顺序之分,不同工件则没有。
(4) 不同的工件具有相同的优先级。
(5)T=0时刻是开始时刻,此时所有工件尚未开始加工。
(6)所有工件的所有工序都要完成。

问题的目标函数是最小化完工时间,我们需要通过算法手段决策工件的工序在哪台机器上执行以及机器的具体执行顺序,下图为上述问题工序的加工顺序图。
在这里插入图片描述

算法介绍

本文拟采用遗传算法对该问题进行求解,实际上只要定义了编码、解码以及适应度函数,基本所有启发算法都可以代入使用,方便起见,本文选择相对简单的遗传算法来对该问题进行求解,着重介绍下编码、解码、适应度的计算以及交叉和变异。方便起见,下文提到的所有索引都从0开始,机器0代表机器1,机器1代表机器2,以此类推。
编码方式
通常来说,遗传算法中每个个体代表一个完整的解决方案,对于车间调度问题而言,若要表达完整的解决方案,编码中最少应该包含每个机器的加工工件以及加工顺序两部分信息。简单起见,本文的编码方式如下,工包含两部分信息,其中order部分为各机器的加工顺序信息,machine为各工件每道工序选择的加工机器信息。

								{'order': [[2, 1, 4, 0, 3],
								  [4, 3, 1, 0, 2],
								  [0, 1, 4, 2, 3],
								  [1, 2, 0, 4, 3],
								  [2, 1, 4, 0, 3],
								  [1, 2, 4, 0, 3]],
								 'machine': [[0, 1, 4, 5],
								  [0, 1, 4, 5],
								  [0, 3, 4, 5],
								  [0, 3, 4, 5],
								  [0, 3, 2, 5]]}

例如其中[2, 1, 4, 0, 3]表示第一台机器的加工顺序为2->1->4->0->3,以此类推,后面的5个list分别为其余5台机器的加工顺序。看到这里可能会有疑问,前面不是提到了某些工序只需要在多台机器中的任意一台进行加工即可吗?order中的信息看起来所有工件都会在所有机器上进行加工?machine中的信息就是用于解决这个问题的,machine的长度为5,代表5个工件的使用机器情况,例如[0,1,4,5]表示工件0的四道工序分别在机器0、1、4、5上进行加工。

解码方式
以编码方式中提到的个体为例,由于工序0只有一台机器,因此机器0的加工顺序为[[2, 1, 4, 0, 3],工序2的可加工机器为1和3,从machine信息中可以看出来,使用机器1加工的是[0,1],使用机器3加工的是[2,3,4],再结合order中的顺序,可以得到每台机器上加工的工件及其加工顺序为:

			[[2, 1, 4, 0, 3], [1, 0], [4], [2, 4, 3], [2, 1, 0, 3], [1, 2, 4, 0, 3]]

以第一台机器为例,可以计算出每个工件的开始及结束加工时间如下,其中t1为开始加工时间,t2为结束加工时间。

							{2: {'t1': 0, 't2': 5},
							   1: {'t1': 5, 't2': 14},
							   4: {'t1': 14, 't2': 20},
							   0: {'t1': 20, 't2': 27},
							   3: {'t1': 27, 't2': 38}}

同理,可以获得其余每台机器上的加工时间,用如下所示的字典表示,其中第一个key为工序编号,第2个key为机器编号,第3个key为工件编号,例如time_table[0][0][2]={‘t1’: 0, ‘t2’: 5}表示第一道工序,工件2在机器0上进行加工,开始时间是0,结束时间是5。

								{0: {0: {2: {'t1': 0, 't2': 5},
								   1: {'t1': 5, 't2': 14},
								   4: {'t1': 14, 't2': 20},
								   0: {'t1': 20, 't2': 27},
								   3: {'t1': 27, 't2': 38}}},
								 1: {1: {1: {'t1': 14, 't2': 20}, 0: {'t1': 27, 't2': 32}},
								  3: {2: {'t1': 5, 't2': 11},
								   4: {'t1': 20, 't2': 33},
								   3: {'t1': 38, 't2': 48}}},
								 2: {2: {4: {'t1': 33, 't2': 44}},
								  4: {2: {'t1': 11, 't2': 20},
								   1: {'t1': 20, 't2': 31},
								   0: {'t1': 32, 't2': 39},
								   3: {'t1': 48, 't2': 56}}},
								 3: {5: {1: {'t1': 31, 't2': 37},
								   2: {'t1': 37, 't2': 45},
								   4: {'t1': 45, 't2': 54},
								   0: {'t1': 54, 't2': 61},
								   3: {'t1': 61, 't2': 71}}}}

适应度计算
在上一节解码过程中,我们首先根据order和machine的信息获得了每台机器上加工的工件以及具体的加工顺序,然后按照离散时间仿真的思路(略复杂)能够获得每个工件的在每道工序的具体加工时间以及结束时间及其对应的机器,那么最大完工时间只需要在上述time table取所有工件的最大t2即可,我们定义适应度为最大完工时间的倒数,这样适应度永远大于0,且适应度越大,最大完工时间越短。

种群交叉
交叉方式分为两部分,第一部分是order部分的交叉,第二部分是machine部分的交叉将父代和母代的加工顺序组合分配给子代,例如假设父代和母代的order信息如下:

									父代:[[2, 1, 4, 0, 3],
									  [4, 3, 1, 0, 2],
									  [0, 1, 4, 2, 3],
									  [1, 2, 0, 4, 3],
									  [2, 1, 4, 0, 3],
									  [1, 2, 4, 0, 3]]
									
									母代:[[1, 2, 3, 0, 4],
									  [3, 4, 2, 0, 1],
									  [1, 0, 2, 4, 3],
									  [2, 1, 3, 0, 4],
									  [2, 1, 4, 3, 0],
									  [3, 1, 4, 0, 2]]

相当于父代和母代总共有12个排列顺序,随机选择6个分配给孩子1,随机选择6个分配给孩子2,可以获得如下两个子代,第二部分是machine的交叉,过程与第一部分类似。

									子代1:[[2, 1, 4, 0, 3],
									  [4, 3, 1, 0, 2],
									  [0, 1, 4, 2, 3],
									  [2, 1, 3, 0, 4],
									  [2, 1, 4, 3, 0],
									  [3, 1, 4, 0, 2]]
									
									子代2:[[1, 2, 3, 0, 4],
									  [3, 4, 2, 0, 1],
									  [1, 0, 2, 4, 3],
									  [1, 2, 0, 4, 3],
									  [2, 1, 4, 0, 3],
									  [1, 2, 4, 0, 3]]

个体变异
变异也有两种情况,第一种是order部分的变异,一部分是machine部分的变异。首先并非所有个体都要参与变异,一般有一个变异参数,对于交叉生成的个体,如果生成的随机数小于这个参数则进行变异。order部分的变异首先生成一个随机数n,该随机数指定order要变异的次数,其次生成3n个随机数,每3个为一组,例如生成的随机数是1,2,3表示对机器1的加工顺序2和加工顺序3进行调换,例如n=1,且生成的随机数为0、0、1,那么子代1将变为:

									子代1:[[1, 2, 4, 0, 3],
									  [4, 3, 1, 0, 2],
									  [0, 1, 4, 2, 3],
									  [2, 1, 3, 0, 4],
									  [2, 1, 4, 3, 0],
									  [3, 1, 4, 0, 2]]

接下来是machine的变异,machine的变异首先生成一个随机数m,表示machine部分进行变异的次数,其次生成2m个随机数,每2为一组,例如生成的随机数2,1,子代的machine从子代a变成子代b,工件2的工序1使用的机器由3变为1。

							子代a: [[0, 1, 4, 5],
										  [0, 1, 4, 5],
										  [0, 3, 4, 5],
										  [0, 3, 4, 5],
										  [0, 3, 2, 5]]
						
							子代b:[[0, 1, 4, 5],
										  [0, 1, 4, 5],
										  [0, 1, 4, 5],
										  [0, 3, 4, 5],
										  [0, 3, 2, 5]]

代码实现

数据处理

# 工件数量
workpiece_num=5

# 工序数量
process_num=4

# 机器数量
machine_num=6

# 加工时间
process_time_dict={}
process_time_dict[0]=[7,5,9,9,7,7]
process_time_dict[1]=[9,6,8,8,11,6]
process_time_dict[2]=[5,8,6,6,9,8]
process_time_dict[3]=[11,12,12,10,8,10]
process_time_dict[4]=[6,9,11,13,9,9]

# 工序对应的机器
process_machin_dict={0:[0],1:[1,3],2:[2,4],3:[5]}

种群交叉

'''
种群交叉
'''
def cross(father,mother):
    child1={}
    child2={}
    
    order1=[]
    order2=[]
    
    for i in range(machine_num):
        if randint(0,1):
            order1.append(father['order'][i])
            order2.append(mother['order'][i])
        else:
            order1.append(mother['order'][i])
            order2.append(father['order'][i])
    child1['order']=order1
    child2['order']=order2
    
            
    machine1=[]
    machine2=[]
    
    for j in range(workpiece_num):
        if randint(0,1):
            machine1.append(father['machine'][j])
            machine2.append(mother['machine'][j])
        else:
            machine1.append(mother['machine'][j])
            machine2.append(father['machine'][j])
    child1['machine']=machine1
    child2['machine']=machine2
    
    return child1,child2

个体变异

'''
个体变异
'''
def mutate(solution):
    solution_copy=deepcopy(solution)
    
    # order调整
    swap_num=randint(0,machine_num)
    for i in range(swap_num):
        idx=randint(0,machine_num-1)
        s1,s2=randint(0,workpiece_num-1),randint(0,workpiece_num-1)
        temp=solution_copy['order'][idx][s1]
        solution_copy['order'][idx][s1]=solution_copy['order'][idx][s2]
        solution_copy['order'][idx][s2]=temp
        
    # machine调整
    change_num=randint(0,workpiece_num)
    for j in range(change_num):
        idx=randint(0,process_num-1)
        solution_copy['machine'][j][idx]=random.choice(process_machin_dict[idx])
        
    if get_fitness(solution_copy)>get_fitness(solution):
        return solution_copy
    return solution

可视化

'''
绘制甘特图
'''
def plot_gantt(solution):
    # 给定的数据
    time_table,machine_order=resolve_solution(deepcopy(solution))
    data={}
    for k in time_table:
        for i in time_table[k]:
            data[i]=time_table[k][i]

    # 创建颜色绘图
    colors = list(mcolors.TABLEAU_COLORS.keys())  # 预定义的颜色
    color_map = {}

    fig, ax = plt.subplots(figsize=[20,10])
    for machine, jobs in data.items():
        for job, times in jobs.items():
            start_time = times['t1']
            end_time = times['t2']
            duration = end_time - start_time

            # 检查工件是否已经分配了颜色
            if job not in color_map:
                color_map[job] = colors.pop(0)

            # 绘制甘特图的条形块
            ax.broken_barh([(start_time, duration)], (machine - 0.4, 0.8), facecolors=color_map[job])

    # 设置y轴
    ax.set_yticks(range(len(data)))
    ax.set_yticklabels(['Machine ' + str(i) for i in range(machine_num)])

    # 设置x轴
    ax.set_xlabel('Time')
    ax.set_xlim(0, max(time['t2'] for machine in data.values() for time in machine.values()))

    # 绘制图例
    patches = [plt.Rectangle((0,0),1,1, color=mcolors.TABLEAU_COLORS[color]) for color in color_map.values()]
    labels = [f'Workpiece {i}' for i in color_map.keys()]
    ax.legend(patches, labels, loc='upper right')

    # 在条形上添加任务编号
    for machine, jobs in data.items():
        for job, times in jobs.items():
            start_time = times['t1']
            duration = times['t2'] - times['t1']
            ax.text(x=start_time + duration/2, y=machine, s=f'{job}', color='white', va='center', ha='center',
                    bbox=dict(boxstyle='round,pad=0.1', facecolor=color_map[job], edgecolor='none'))

    # 显示网格
    ax.grid(False)

    # 展示图表
    plt.show()

结果展示

收敛过程
在这里插入图片描述

甘特图
在这里插入图片描述

本文小结

本文对车间调度问题进行了简单介绍,并以一个简单柔性车间调度问题为例,使用遗传算法对该问题进行了求解,并且详细描述了使用遗传算法求解过程中的编码、解码、交叉以及变异等操作。另外,本文给出了遗传算法求解车间调度问题的主体python代码,最后展示了算法求解的结果,包括结果收敛过程以及甘特图,验证了算法的有效性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/708412.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

重生之 SpringBoot3 入门保姆级学习(21、场景整合 Redis 定制对象序列化存储)

重生之 SpringBoot3 入门保姆级学习(21、场景整合 Redis 定制对象序列化存储) 6.4 定制化 6.4 定制化 需求:保存一个 Person 对象到 redis 创建 Person 类 package com.zhong.redis.entity;import lombok.AllArgsConstructor; import lombok…

为什么Mamba模型被拒?

Mamba模型问世 最近,国际学习表征会议(ICLR)公布了2024年会议的最终决定,其中引起广泛关注的是一个名为Mamba的模型。这个模型最初被认为是对抗著名的Transformer架构进行语言建模任务的主要竞争者,但最终被拒绝&…

HTML制作一个超迷人的科技之眼

大家好,今天制作一个科技之眼! 先看具体效果: 要制作一个超迷人的“科技之眼”网页效果,你可以结合HTML、CSS和JavaScript来实现。下面是一个简单的步骤指南和示例代码,帮助你开始这个项目。 1. 设计概念 首先&…

Nginx 搭建 lnmp

一.编译安装Nginx 1.新建用户前期准备 官网下载nginx安装包 https://nginx.org/en/download.html yum -y install gcc pcre-devel openssl-devel zlib-devel openssl openssl-devel #安装依赖包 useradd -M -s /sbin/nologin nginx #新建nginx用户便于管理 2.切换到/opt…

大屏幕互动系统PHP源码 附动态背景图和配乐素材 含搭建教程

最新大屏幕互动系统PHP源码 附动态背景图和配乐素材 含搭建教程 测试环境:apachePHP7.3MySQL5.7 源码免费下载地址抄笔记 (chaobiji.cn)

家里满是“飞尘、毛絮”怎么办?用空气净化器,干净又卫生!

随着气温的升高,家中的毛絮和飞尘问题愈发严重,这些细小的颗粒常常聚集在房间的角落,即使每日清洁,似乎也难以彻底清除,反而可能使情况恶化。特别是对于养宠物的家庭来说,毛絮问题尤为突出,即使…

一键安全体检!亚信安全携手鼎捷软件推出企业安全体检活动 正式上线

亚信安全联合鼎捷软件股份有限公司(以下简称“鼎捷软件”)正式推出“一键安全体检”服务。亚信安全网络安全专家将携手鼎捷软件数据安全专家,围绕企业的数智安全状况,进行问题探索与治愈、新问题预测与预警,在全面筛查…

MPT(merkle Patricia trie )及理解solidity里的storage

what? MPT树是一种数据结构,用于在以太坊区块链中高效地存储和检索账户状态、交易历史和其他重要数据。MPT树的设计旨在结合Merkle树和Patricia树的优点,以提供高效的数据存储和验证 MPT树由四种类型的节点组成: **扩展节点&…

Redis的缓存击穿、缓存穿透和缓存雪崩是什么?怎么预防?

Redis的缓存击穿、缓存穿透和缓存雪崩是什么?怎么预防? 前言缓存击穿定义解决思路实现加锁设置过期时间Lua脚本刷新锁 缓存穿透定义实现 缓存雪崩定义解决思路 总结 前言 最近在CSDN上看到了一篇博客,Redis缓存击穿、雪崩、穿透!…

04 DNS域名解析服务

1、DNS系统的作用及类型 在整个互联网大家庭中,大部分的网站、邮件等服务器都使用了域名形式的地址,如www.baidu.com、mail.163.com等。很显然这种地址形式要比使用61.233.189.147、202.108.33.74的IP地址形式更加直观,且更容易被用户记住。…

UE4中性能优化工具合集

UE4中性能优化工具合集 简述CPUUnreal InsightUnreal ProfilerSimpleperfAndroid StudioPerfettoXCode TimeprofilerBest Practice GPUAdreno GPUMali GPUAndroid GPU Inspector (AGI) 内存堆内存分析Android StudioLoliProfilerUE5 Memory InsightsUnity Mono 内存MemreportRH…

父亲节献礼,让爱从脚下升起!一双舒适劳保鞋,守护他的每一步

时光荏苒,转眼间我们又迎来了一个温馨的节日——父亲节。在这个特别的日子里,你是否已经为父亲精心挑选了一份特别的礼物呢?如果没有,那么今天就来给大家推荐一款既实用又贴心的父亲节礼物——一双舒适耐用的劳保鞋。它不仅能守护…

长亭Nginx入门

在学习Nginx时我们先学习下防火墙原理】 将流量代理给防火墙 这样WAF 会分析流量 防火墙安装网络拓扑图 流量给防火墙 再给负载均衡 反向代理这个网络拓扑图是 防火墙充当了反向代理角色 所以我们就知道了我们为了要学习Nginx 因为这个服务器支持很多功能模块 自己本身就能…

IO高级 -- 文件操作(Path、Paths、Files)

一、基础:File 1.1 构造方法: 1、 public File(String pathname) :通过给定的路径来创建新的 File实例。2、 public File(String parent, String child) :从父路径(字符串)和子路径创建新的 File实例。3、 public File(File pare…

【Windows10】查看WIFI密码

操作步骤 电脑上查看已连接Wi-Fi的密码的步骤如下: 连接需要查看密码的Wi-Fi。右键点击任务栏上的 [网络] 图标,选择 [开启"网络和Internet"设置]。在 高级网络设置 项目中,点选 [网络和共享中心]。开启网络和共享中心的窗口后,点…

vue+showdown展示Markdown 文本

前言&#xff1a; vueshowdown展示Markdown 文本&#xff0c;资料整理 使用教程-vditor&#xff1a; 1、安装 npm install vditor --save 2、使用 <template><div id"vditor" name"description" ></div> </template> <scri…

探索高效存储与快速查找: 深入了解B树数据结构

探索高效存储与快速查找: 深入了解B树数据结构 一、什么是B树二、B树的实现2.1 节点的定义2.2 插入关键字2.3 删除关键字2.4 查找关键字2.5 遍历B树 一、什么是B树 B树&#xff0c;也称为B-tree&#xff0c;是一种多路平衡查找树。它被广泛用于文件系统和数据库之中&#xff0c…

2024年6月-Docker配置镜像代理

步骤1&#xff1a;编辑 daemon.json 文件 vim /etc/docker/daemon.json步骤2&#xff1a;添加配置 将以下内容粘贴到文件中&#xff1a; {"insecure-registries": ["192.168.0.99:8800"],"data-root": "/mnt/docker","registr…

区间分割求解方程

本文实现了基于mpi4py的多进程算法 mpi不过多介绍&#xff0c;某些函数的用法也不是介绍范围&#xff0c;这里只给出怎么实现多进程的方程求根算法。区间划分求解方程&#xff0c;在串行程序里&#xff0c;二分法是非常经典的算法&#xff0c;现在对其进行拓展&#xff0c;实现…

YUV格式与RGB格式详解

图像处理 文章目录 图像处理前言YUV 格式YUV 采样 前言 像素格式描述了像素数据存储所用的格式&#xff0c;定义了像素在内存中的编码方式。RGB 和 YUV 为两种经常使用的像素格式。/ 1024 / 1024 2.63 MB 存储空间。 RGB 和 RGBA 格式 RGB 图像具有三个通道 R、G、B&#xff…