

一句话介绍: 这套程序用遗传算法同时优化“工序先后顺序”和“每道工序选择哪台机器”,在满足工件工艺顺序和机器不能冲突的条件下,尽量缩短所有工件全部完成所需要的总时间,并输出最优调度方案。
车间里往往不是只有一个工件、一台机器。
当多个工件需要经过多道工序,而且同一道工序还可能有多台机器可选时,就会同时遇到两个问题:
先加工谁?
这道工序分配到哪台机器?
如果安排不好,某些机器会长时间等待,某些工件又会排队,最终导致整个生产周期变长。
这套程序的处理流程可以概括为:
工件工艺 + 可选机器 + 加工时间 → 生成大量调度方案 → 遗传选择 / 交叉 / 变异 → 计算每个方案完工时间 → 保留更优方案 → 输出较短的总加工时间和调度方案
它优化的核心指标就是:
让最后一个工序尽可能早完成。
1. 这个程序真正求解的是什么问题?
工程名称写的是“GA 求解 JOB SHOP 调度问题”,但从源码来看,它比传统固定机器的 Job Shop 更灵活。
当前程序共有:4 个工件;每个工件 3 道工序;共 12 道工序;6 台机器
而且每一道工序并不是只能在一台固定机器上完成,而是给出了 2~3 台候选机器,不同机器的加工时间也不同。
因此从实际建模方式看,它更接近:uli柔性作业车间调度问题 FJSP(Flexible Job Shop Sched uling Problem)
程序需要同时决定:
每个工件的工序怎样穿插安排;
每道工序从候选机器中选哪一台;
最终怎样让所有工件尽快完成。
2. 当前实例的加工数据
每个单元格中的“机器:时间”表示该道工序可选择的机器及对应加工时间。
工件 | 工序 1 | 工序 2 | 工序 3 |
|---|---|---|---|
工件 1 | M1:2,M2:3,M3:4 | M2:3,M4:2,M5:4 | M1:1,M2:4,M3:5 |
工件 2 | M1:3,M3:5,M5:2 | M1:4,M2:3,M5:6 | M3:4,M5:7,M6:11 |
工件 3 | M1:5,M2:6 | M2:4,M4:3,M5:5 | M3:13,M5:9,M6:12 |
工件 4 | M1:9,M3:7,M4:9 | M2:6,M4:4,M6:5 | M1:1,M3:3,M6:3 |
这个数据结构很关键,因为程序不是简单寻找“工序排列”,而是在排列的同时还要考虑机器选择。
3. 调度必须满足哪些基本约束?
程序附带的说明文件明确给出了 Job Shop 的主要约束。
任意时刻,一台机器最多只能处理一道工序。
一个工件当前工序没有完成时,下一道工序不能提前开始。
某道工序一旦开始,就连续加工到结束。
不存在额外的到达时间限制。
例如工件 1 必须:
工序 1 → 工序 2 → 工序 3
不能为了缩短时间把工序 3 提前到工序 1 前面。
4. 程序真正优化的目标是什么?
对于每一种候选调度方案,程序都会计算 12 道工序的开始时间和结束时间。
如果最后完成的工序结束时间为 Cmax,那么程序的目标就是:min Cmax
这里的 Cmax 叫做 最大完工时间(Makespan)。
简单理解:
所有工件都做完的那个时刻越早,调度方案越好。
因此程序不是最小化某一道工序的加工时间,而是在优化整个车间的最终完成时间。
5. 一道工序什么时候能够开始?
程序中的 caltime.m 很直观地体现了调度约束。
对于工件 j 的第 k 道工序,如果它被分配到机器 m,那么开始时间由两个条件共同决定:
S(j,k) = max[C(j,k-1), A(m)]
其中:
S(j,k):当前工序开始时间;
C(j,k-1):这个工件上一道工序的完成时间;
A(m):机器 m 当前最早空闲时间。
完成时间则为:
C(j,k) = S(j,k) + p(j,k,m)
其中 p(j,k,m) 是当前工序在机器 m 上的加工时间。
这个公式说明了程序为什么能够自动避免冲突:
只有工件自己准备好了,并且机器也空闲了,当前工序才能开始。
6. 遗传算法中的一条“染色体”代表什么?
这是整个程序最有特点的地方。
每个个体一共有 24 个基因,但不是 24 个完全相同意义的数字,而是分成两层:
前 12 个基因:工序排序层
后 12 个基因:机器选择层
前半段不是直接写“工件 1 的工序 2”,而是重复写工件编号。
因为每个工件有 3 道工序,所以每个工件编号必须恰好出现 3 次。
程序按照某个工件在染色体中出现的次数自动判断工序号:
工件 1 第一次出现 → 工件 1 的工序 1
工件 1 第二次出现 → 工件 1 的工序 2
工件 1 第三次出现 → 工件 1 的工序 3
这样就天然保证了:
同一工件内部的工序顺序不会被打乱。
后 12 个基因不是直接保存机器编号,而是保存:
当前工序候选机器列表中的第几个机器。
例如某道工序可选 M2、M4、M5,如果机器基因为 2,就表示选择第二个候选机器 M4。
所以一条完整染色体同时回答了:
工序怎样排序 + 每道工序用哪台机器
这也是当前程序能够求解柔性调度问题的核心。
7. 初始种群是怎么生成的?
程序设置:
参数 | 默认值 |
种群个体数 | 40 |
最大遗传代数 | 50 |
代沟 | 0.9 |
交叉率 | 0.8 |
变异率 | 0.6 |
初始化时,每个个体都会随机生成一个合法的工件序列,同时为每道工序随机选择一台候选机器。
因此一开始并不是只有一个调度方案,而是:
40 个不同的可行调度方案同时开始搜索。
群体搜索是遗传算法相对于“只改一个方案”的重要特点。
8. 程序怎样判断哪些个体更值得保留?
cal.m 会对种群中每个个体完成完整调度解码,得到最大完工时间 Cmax。
因为这个问题是:
Cmax 越小越好
程序再使用排序适应度 ranking 将目标函数值转换成适应度。
之后用轮盘赌选择法从当前种群中选择下一步参与繁殖的个体。
经典轮盘赌思想可以表示为:
Pᵢ = Fᵢ / ΣⱼFⱼ
其中:
Fᵢ:第 i 个个体的适应度;
Pᵢ:它被选中的概率。
适应度高的个体更容易被选中,但不是绝对只保留第一名。
这样可以同时做到:
偏向好方案,又保留一定随机性。
9. 交叉操作为什么不能直接随便切?
普通二进制遗传算法可以直接交换染色体片段,但这个程序不行。
因为每个工件编号必须恰好出现 3 次。
如果直接交换一段,有可能得到:
工件 1 出现 4 次;
工件 2 只出现 2 次;
这样的非法染色体。
所以 across.m 在交叉以后还会检查:
哪些工件编号多了;
哪些工件编号少了;
然后用缺失基因替换多余基因。
同时,机器选择基因也会按照新的工件序列重新对应。
因此这个交叉算子不是简单“剪开再拼接”,而是:
在产生新组合的同时修复调度编码合法性。
10. 变异操作为什么主要改“机器选择”?
aberranceJm.m 的变异重点放在染色体第二层,也就是机器分配。
这意味着程序会尝试:
同一道工序如果换到另一台候选机器,整个车间完成时间会不会更短?
更加有意思的是,机器变异不是完全等概率随机。
selectJm.m 会根据候选机器加工时间计算选择权重。
当前代码的机器选择权重可以概括为:
w(m) = 2Tmax - T(m)
再归一化得到机器选择概率:
P(m) = w(m) / Σw
其中:
T(m):当前候选机器的加工时间;
Tmax:候选机器中的最大加工时间。
因为加工时间越短,2Tmax - T(m) 越大,所以:
速度更快的机器获得更高的变异选择概率,但较慢机器仍然保留被选择的可能。
这比永远直接选择最短加工时间更有搜索多样性。
11. 为什么不能每道工序都直接选“最快机器”?
看起来最简单的方法是:
每道工序都选择加工时间最短的机器。
但调度问题不能只看单道工序。
如果很多工序都认为同一台机器最快,那么所有工序会一起挤到那台机器上排队,最终反而可能拖长总完工时间。
所以真正需要优化的是:
单道工序加工时间 + 机器之间的负载平衡 + 工件内部工序先后关系
遗传算法可以同时调整:工序穿插顺序;机器分配;
因此能够寻找整体上更好的组合,而不是只做局部贪心选择。
12. 为什么遗传算法适合这种调度问题?
12 道工序的排序本身就有大量组合,再叠加每道工序的机器选择,方案数量会迅速增加。
这种问题很难靠人工逐个尝试。
遗传算法不需要把全部组合完全枚举,而是通过种群迭代逐步搜索更优区域。
当前程序把:
工序排序 + 机器选择
放在同一个个体中。
这使遗传操作可以直接对完整调度方案进行优化,而不是先单独选机器、再单独排工序。
选择负责让较好的调度方案更容易保留下来;
交叉负责把不同父代中的结构重新组合;
变异负责尝试新的机器分配,增加局部搜索和种群多样性。
三种机制共同作用,使搜索既不是纯随机,也不容易完全停在一个固定方案上。
主程序使用代沟 GGAP = 0.9,40 个父代中每轮选择约 36 个子代。
重新插入时采用基于目标值的替换方式,较差父代优先被子代替换,因此会保留一部分当前较优个体。
这相当于加入了一定的精英保留思想,有助于避免已经找到的好解在下一代完全丢失。
13. 当前实例的理论最优完工时间可以达到 17
根据当前 4 个工件的加工时间,可以先得到一个很直接的下界。
工件 3 的三道工序即使每道都选择最快机器,也至少需要:
5 + 3 + 9 = 17
因此任何调度方案都不可能满足:
Cmax < 17
另一方面,按照当前数据可以构造一个没有机器冲突、且全部满足工艺顺序的可行调度,并在时间 17 前完成所有工件。
工件 | 一组可行安排 |
工件 1 | O1 5~8 → O2 8~10 → O3 10~11 |
工件 2 | O1 0~2 → O2 2~5 → O3 7~11 |
工件 3 | O1 0~5 → O2 5~8 → O3 8~17 |
工件 4 | O1 0~7 → O2 7~12 → O3 12~13 |
因此这个实例可以确认:
理论最优 Makespan = 17
遗传算法的任务,就是通过随机种群和遗传操作尽量找到这个最优值或与它非常接近的方案。
由于遗传算法具有随机性,50 代内是否每次都找到 17,仍然可能受到初始种群和随机遗传过程影响。
14. 程序最终会得到什么?
运行结束后,程序主要给出两类结果。
第一类是:
随着遗传代数增加,当前最优加工时间和种群平均加工时间如何变化。
这可以用来判断算法是否逐步收敛。
第二类是最终调度方案:
每道工序在哪台机器上、什么时候开始、什么时候结束。
程序会用甘特图形式把不同工序安排到 6 台机器的时间轴上,因此可以直观看到:
机器有没有加工冲突;哪些机器比较忙;
哪些机器存在空闲;不同工件的工序怎样穿插;
最后一个工序什么时候完成。
15. 它适合用在哪里?
当多个订单需要经过多道加工工序,而且机器资源有限时,可以利用类似方法寻找更紧凑的排产方案。
如果某道工序可以由多台不同设备完成,不同设备加工速度又不同,就会同时出现“排序 + 机器分配”问题,这与当前程序的模型非常接近。
这份工程把初始化、适应度、轮盘赌、交叉、变异和重新插入分别写成独立函数,非常适合理解遗传算法的完整流程。
可以进一步将 GA 与:贪心规则;粒子群;模拟退火;禁忌搜索;
等方法比较,研究不同优化算法对 Makespan 的影响。
16. 这个程序本身有哪些优势?
编码有针对性: 不是普通二进制编码,而是专门针对工件顺序和机器选择设计双层染色体。
自动保证工艺顺序: 同一工件编号的第 1、2、3 次出现自动对应三道工序。
机器分配也参与优化: 不只是排顺序,还能同时选择候选机器。
变异有启发信息: 加工时间较短的机器具有更高选择概率,而不是完全盲目随机。
调度解码完整: 自动检查机器空闲时间和前一道工序完成时间。
结果直观: 最终可以直接得到最短完工时间和对应甘特调度方案。
17. 怎么运行?
使用方式比较简单:
将 GA程序 目录中的 .m 文件保持在同一 MATLAB 工作目录;
打开并运行 Main.m;
程序自动生成 40 个初始调度个体;
自动进行 50 代遗传搜索;
输出最优调度时间;
查看最终工序安排和甘特调度结果。
如果修改实际车间数据,主要需要调整:
Jm:每道工序可以使用哪些机器;
T:不同候选机器上的加工时间;
JmNumber:机器总数。
18. 哪些遗传参数最值得实验?
参数 | 当前值 | 主要作用 |
| 40 | 种群大小 |
| 50 | 最大遗传代数 |
| 0.9 | 每代生成子代的比例 |
| 0.8 | 交叉概率 |
| 0.6 | 机器基因变异概率 |
一般来说:
增大种群可以增加搜索多样性,但计算量也会增加;
增加代数可以提供更多搜索机会;
交叉率决定方案重组频率;
变异率决定机器分配发生随机改变的频率。
当前 MUTR = 0.6 相对较高,说明程序非常积极地尝试改变机器分配。
19. 源码中几个值得注意的实现细节
aberranceJm.m 只修改后半段机器基因。
因此工序顺序的变化主要依赖:
初始种群随机性;交叉操作。
如果希望进一步增强局部搜索,可以考虑增加针对工序排序层的交换、插入或逆序变异。
当前 GGAP = 0.9 时,40 个个体大约会选择 36 个进入 across.m。
但 across.m 中先计算 Num = floor(NIND/2),然后再按两个一组循环到 Num,因此实际只有前半部分被选个体进入交叉配对检查。
如果原设计目标是让所有 36 个选中个体都两两尝试交叉,这里的循环上界值得复核。
20. 一句话看懂这个项目
这是一个 MATLAB 遗传算法柔性作业车间调度程序:它用双层染色体同时编码工件工序顺序和机器选择,通过排序适应度、轮盘赌选择、合法性 交叉、机器分配变异和精英式重新插入不断改进调度方案,最终最小化所有工件全部完成的 Makespan;当前 4 工件、3 工序、6 机器实例的理论最优完工时间可以达到 17。