问题描述
柔性作业车间问题可描述为:每一待加工工件包含一道或一道以上工序,每个工件的工序 顺序都是已知的,每道工序可以在一台及以上的机器上完成加工, 加工的时间会因为所选择的加工机器不同而变得不同。 调度方案需要确定工序的加工顺序和机器的选择,从而使得整个调度系统的各指标达到最优。
加工过程需满足以下条件:
(1)工件工序只能在可加工设备上进行加工
(2)一台设备同一时间段只能对一个工件工序进行加工
(3)工件工序开始加工后无法停止
(4)工件下一工序的开始时间大于等于上一工序的结束时间,即工件工序具备加工次序
工件数据
仿真数据来源于文献:[1]高尚,李荣平.改进NSGA-Ⅱ求解多目标柔性作业车间调度问题[J].河北企业,2022(04):70-72.DOI:10.19885/j.cnki.hbqy.2022.04.021.
最早开始时间启发策略
最早开始时间启发策略是指在工件工序加工过程中,依据工件工序在各个设备上的最早的开始加工时间为依据将工件工序分配到设备进行加工。
实现代码:
/**
* 启发策略-最早开始时间
*/
private void EST(){
while (true) {
//遍历工件
for (int i = 0; i < data.workId.length; i++) {
//选取工序i的剩余第一个工序
if (workProcess.get(i).size() != 0) {
//System.out.println("=======工件"+(i+1)+"|工序"+workProcess.get(i).get(0)+"=======");
//随机生成设备遍历顺序
int[] orderArr = new int[data.deviceId.length];
for (int j = 0; j < data.deviceId.length; j++) orderArr[j] = j;
shuffle(orderArr);
//找到开始时间最短的机器设备
int deviceIndex = data.deviceId.length;
double deviceTime = Double.MAX_VALUE;
//随机遍历机器
for (int j = 0; j < data.deviceId.length; j++) {
//工序只能在可加工设备上进行加工
if (data.ProcessTime[i][workProcess.get(i).get(0) - 1][orderArr[j]] != Double.MAX_VALUE) {
if (workResult.get(orderArr[j]).size() == 0) {
//下一工序的开始时间大于等于上一工序的结束时间
if (workProcess.get(i).get(0) == 1) {
//第一个工序
deviceTime = 0;
deviceIndex = orderArr[j];
//System.out.println("遍历设备"+(orderArr[j]+1)+"\t"+"目标设备"+(deviceIndex+1)+"\t"+deviceTime);
break;
}
} else {
if (workProcess.get(i).get(0) == 1) {
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime < deviceTime) {
deviceTime = workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime+1;
deviceIndex = orderArr[j];
}
} else {
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime < deviceTime) {
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime >= endTime[i][workProcess.get(i).get(0) - 2]) {
deviceTime = workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime+1;
deviceIndex = orderArr[j];
}
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime < endTime[i][workProcess.get(i).get(0) - 2]) {
deviceTime = endTime[i][workProcess.get(i).get(0) - 2]+1;
deviceIndex = orderArr[j];
}
}
}
}
}
//System.out.println("遍历设备"+(orderArr[j]+1)+"\t"+"目标设备"+(deviceIndex+1)+"\t"+deviceTime);
}
//更新工件信息
if (deviceIndex != data.deviceId.length) {
shop.workId = i + 1;
shop.workProcess = workProcess.get(i).get(0);
shop.deviceId = deviceIndex + 1;
shop.startTime = deviceTime;
startTime[i][workProcess.get(i).get(0) - 1] = shop.startTime;
//System.out.println(i + "\t" + (workProcessTemp.get(i).get(0) - 1) + "\t" + deviceIndex);
shop.endTime = shop.startTime + data.ProcessTime[i][workProcess.get(i).get(0) - 1][deviceIndex];
endTime[i][workProcess.get(i).get(0) - 1] = shop.endTime;
//工件信息加入结果列表
workResult.get(deviceIndex).add(shop.clone());
//删除该工序
workProcess.get(i).remove(0);
}
}
}
//所有工序分配完毕时,结束
boolean stopCondition = true;
for (int i = 0; i < data.workId.length; i++) {
if (workProcess.get(i).size() != 0) {
stopCondition = false;
break;
}
}
if (stopCondition) break;
}
printResult(workResult,endTime);
}
启发求解结果:
最早开始时间启发策略改进首工序分配
在使用最早开始时间启发策略过程中,采用随机分配工序的方式到设备,并且一旦设备满足加工条件,就将工件分配到该设备进行加工。其结果也可以发现,总会存在个别设备未被分配加工任务,导致最大的工件完工时间较长。
在这里,采用匈牙利算法对首工序进行设备分配,其余工序同样采用随机分配的方式。当然,若是有兴趣的研究者可以逐次将下一工序进行分配。
实现代码:
/**
* 贪婪启发策略-最早开始时间
* 匈牙利算法分配第一工序和设备
*/
private void GEST(){
//第一个工序分配
int[] assignResult=shopAssign(data.ProcessTime);
//分配第一个工序
for(int i=0;i<assignResult.length;i++){
int deviceIndex = data.deviceId.length;
double deviceTime = Double.MAX_VALUE;
if (workProcess.get(i).size() != 0){
if (workResult.get(assignResult[i]-1).size() == 0){
deviceTime = 0;
deviceIndex = assignResult[i]-1;
}
else{
deviceTime = workResult.get(assignResult[i]-1).get(workResult.get(assignResult[i]-1).size() - 1).endTime+1;
deviceIndex = assignResult[i]-1;
}
}
if (deviceIndex != data.deviceId.length) {
shop.workId = i + 1;
shop.workProcess = workProcess.get(i).get(0);
shop.deviceId = deviceIndex + 1;
shop.startTime = deviceTime;
startTime[i][workProcess.get(i).get(0) - 1] = shop.startTime;
shop.endTime = shop.startTime + data.ProcessTime[i][workProcess.get(i).get(0) - 1][deviceIndex];
endTime[i][workProcess.get(i).get(0) - 1] = shop.endTime;
workResult.get(deviceIndex).add(shop.clone());
workProcess.get(i).remove(0);
}
}
//分配其余工序
while (true) {
//遍历工件
for (int i = 0; i < data.workId.length; i++) {
//选取工序i的剩余第一个工序
if (workProcess.get(i).size() != 0) {
//System.out.println("=======工件"+(i+1)+"|工序"+workProcess.get(i).get(0)+"=======");
//随机生成设备遍历顺序
int[] orderArr = new int[data.deviceId.length];
for (int j = 0; j < data.deviceId.length; j++) orderArr[j] = j;
shuffle(orderArr);
//找到开始时间最短的机器设备
int deviceIndex = data.deviceId.length;
double deviceTime = Double.MAX_VALUE;
//随机遍历机器
for (int j = 0; j < data.deviceId.length; j++) {
//工序只能在可加工设备上进行加工
if (data.ProcessTime[i][workProcess.get(i).get(0) - 1][orderArr[j]] != Double.MAX_VALUE) {
if (workResult.get(orderArr[j]).size() == 0) {
//下一工序的开始时间大于等于上一工序的结束时间
if (workProcess.get(i).get(0) == 1) {
//第一个工序
deviceTime = 0;
deviceIndex = orderArr[j];
//System.out.println("遍历设备"+(orderArr[j]+1)+"\t"+"目标设备"+(deviceIndex+1)+"\t"+deviceTime);
break;
}
} else {
if (workProcess.get(i).get(0) == 1) {
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime < deviceTime) {
deviceTime = workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime+1;
deviceIndex = orderArr[j];
}
} else {
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime < deviceTime) {
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime >= endTime[i][workProcess.get(i).get(0) - 2]) {
deviceTime = workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime+1;
deviceIndex = orderArr[j];
}
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime < endTime[i][workProcess.get(i).get(0) - 2]) {
deviceTime = endTime[i][workProcess.get(i).get(0) - 2]+1;
deviceIndex = orderArr[j];
}
}
}
}
}
//System.out.println("遍历设备"+(orderArr[j]+1)+"\t"+"目标设备"+(deviceIndex+1)+"\t"+deviceTime);
}
//更新工件信息
if (deviceIndex != data.deviceId.length) {
shop.workId = i + 1;
shop.workProcess = workProcess.get(i).get(0);
shop.deviceId = deviceIndex + 1;
shop.startTime = deviceTime;
startTime[i][workProcess.get(i).get(0) - 1] = shop.startTime;
//System.out.println(i + "\t" + (workProcessTemp.get(i).get(0) - 1) + "\t" + deviceIndex);
shop.endTime = shop.startTime + data.ProcessTime[i][workProcess.get(i).get(0) - 1][deviceIndex];
endTime[i][workProcess.get(i).get(0) - 1] = shop.endTime;
//工件信息加入结果列表
workResult.get(deviceIndex).add(shop.clone());
//删除该工序
workProcess.get(i).remove(0);
}
}
}
//所有工序分配完毕时,结束
boolean stopCondition = true;
for (int i = 0; i < data.workId.length; i++) {
if (workProcess.get(i).size() != 0) {
stopCondition = false;
break;
}
}
if (stopCondition) break;
}
printResult(workResult,endTime);
}
运行结果发现,工件在设备上的分配更为有序。
最早结束时间启发策略
与最早开始时间相同,最早结束时间启发策略是指在工件工序加工过程中,依据工件工序在各个设备上的最早的结束加工时间为依据将工件工序分配到设备进行加工。
实现代码:
/**
* 启发策略-最早结束时间
* 随机分配工序
*/
private void EET(){
while (true) {
//遍历工件
for (int i = 0; i < data.workId.length; i++) {
//选取工序i的剩余第一个工序
if (workProcess.get(i).size() != 0) {
//System.out.println("=======工件"+(i+1)+"|工序"+workProcess.get(i).get(0)+"=======");
//随机生成设备遍历顺序
int[] orderArr = new int[data.deviceId.length];
for (int j = 0; j < data.deviceId.length; j++) orderArr[j] = j;
shuffle(orderArr);
//找到开始时间最短的机器设备
int deviceIndex = data.deviceId.length;
double deviceTime = Double.MAX_VALUE;
//随机遍历机器
for (int j = 0; j < data.deviceId.length; j++) {
//工序只能在可加工设备上进行加工
if (data.ProcessTime[i][workProcess.get(i).get(0) - 1][orderArr[j]] != Double.MAX_VALUE) {
if (workResult.get(orderArr[j]).size() == 0) {
//下一工序的开始时间大于等于上一工序的结束时间
if (workProcess.get(i).get(0) == 1) {
//第一个工序
deviceTime = data.ProcessTime[i][workProcess.get(i).get(0)-1][orderArr[j]];
deviceIndex = orderArr[j];
break;
}
} else {
if (workProcess.get(i).get(0) == 1) {
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime+1+data.ProcessTime[i][workProcess.get(i).get(0)-1][orderArr[j]] < deviceTime) {
deviceTime = workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime+1+data.ProcessTime[i][workProcess.get(i).get(0)-1][orderArr[j]];
deviceIndex = orderArr[j];
}
} else {
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime+1+data.ProcessTime[i][workProcess.get(i).get(0)-1][orderArr[j]]< deviceTime) {
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime >= endTime[i][workProcess.get(i).get(0) - 2]) {
deviceTime = workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime+1+data.ProcessTime[i][workProcess.get(i).get(0)-1][orderArr[j]];
deviceIndex = orderArr[j];
}
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime < endTime[i][workProcess.get(i).get(0) - 2]) {
deviceTime = endTime[i][workProcess.get(i).get(0) - 2]+1+data.ProcessTime[i][workProcess.get(i).get(0)-1][orderArr[j]];
deviceIndex = orderArr[j];
}
}
}
}
}
//System.out.println("遍历设备"+(orderArr[j]+1)+"\t"+"目标设备"+(deviceIndex+1)+"\t"+deviceTime);
}
//更新工件信息
if (deviceIndex != data.deviceId.length) {
shop.workId = i + 1;
shop.workProcess = workProcess.get(i).get(0);
shop.deviceId = deviceIndex + 1;
shop.endTime = deviceTime;
endTime[i][workProcess.get(i).get(0) - 1] = shop.endTime;
shop.startTime = shop.endTime - data.ProcessTime[i][workProcess.get(i).get(0) - 1][deviceIndex];;
startTime[i][workProcess.get(i).get(0) - 1] = shop.startTime;
//工件信息加入结果列表
workResult.get(deviceIndex).add(shop.clone());
//删除该工序
workProcess.get(i).remove(0);
}
}
}
//所有工序分配完毕时,结束
boolean stopCondition = true;
for (int i = 0; i < data.workId.length; i++) {
if (workProcess.get(i).size() != 0) {
stopCondition = false;
break;
}
}
if (stopCondition) break;
}
printResult(workResult,endTime);
}
启发求解结果:
最早结束时间启发策略改进首工序分配
/**
* 贪婪启发策略-最早结束时间
* 匈牙利算法分配第一工序和设备
*/
private void GEET(){
//第一个工序分配
int[] assignResult=shopAssign(data.ProcessTime);
//分配第一个工序
for(int i=0;i<assignResult.length;i++){
int deviceIndex = data.deviceId.length;
double deviceTime = Double.MAX_VALUE;
if (workProcess.get(i).size() != 0){
if (workResult.get(assignResult[i]-1).size() == 0){
deviceTime = data.ProcessTime[i][workProcess.get(i).get(0)-1][assignResult[i]-1];
deviceIndex = assignResult[i]-1;
}
else{
deviceTime = workResult.get(assignResult[i]-1).get(workResult.get(assignResult[i]-1).size() - 1).endTime+1+data.ProcessTime[i][workProcess.get(i).get(0)-1][assignResult[i]-1];
deviceIndex = assignResult[i]-1;
}
}
if (deviceIndex != data.deviceId.length) {
shop.workId = i + 1;
shop.workProcess = workProcess.get(i).get(0);
shop.deviceId = deviceIndex + 1;
shop.endTime = deviceTime;
endTime[i][workProcess.get(i).get(0) - 1] = shop.endTime;
shop.startTime = shop.endTime - data.ProcessTime[i][workProcess.get(i).get(0) - 1][deviceIndex];;
startTime[i][workProcess.get(i).get(0) - 1] = shop.startTime;
workResult.get(deviceIndex).add(shop.clone());
workProcess.get(i).remove(0);
}
}
//分配其余工序
while (true) {
//遍历工件
for (int i = 0; i < data.workId.length; i++) {
//选取工序i的剩余第一个工序
if (workProcess.get(i).size() != 0) {
//System.out.println("=======工件"+(i+1)+"|工序"+workProcess.get(i).get(0)+"=======");
//随机生成设备遍历顺序
int[] orderArr = new int[data.deviceId.length];
for (int j = 0; j < data.deviceId.length; j++) orderArr[j] = j;
shuffle(orderArr);
//找到开始时间最短的机器设备
int deviceIndex = data.deviceId.length;
double deviceTime = Double.MAX_VALUE;
//随机遍历机器
for (int j = 0; j < data.deviceId.length; j++) {
//工序只能在可加工设备上进行加工
if (data.ProcessTime[i][workProcess.get(i).get(0) - 1][orderArr[j]] != Double.MAX_VALUE) {
if (workResult.get(orderArr[j]).size() == 0) {
//下一工序的开始时间大于等于上一工序的结束时间
if (workProcess.get(i).get(0) == 1) {
//第一个工序
deviceTime = data.ProcessTime[i][workProcess.get(i).get(0)-1][orderArr[j]];
deviceIndex = orderArr[j];
break;
}
} else {
if (workProcess.get(i).get(0) == 1) {
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime+1+data.ProcessTime[i][workProcess.get(i).get(0)-1][orderArr[j]] < deviceTime) {
deviceTime = workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime+1+data.ProcessTime[i][workProcess.get(i).get(0)-1][orderArr[j]];
deviceIndex = orderArr[j];
}
} else {
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime+1+data.ProcessTime[i][workProcess.get(i).get(0)-1][orderArr[j]]< deviceTime) {
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime >= endTime[i][workProcess.get(i).get(0) - 2]) {
deviceTime = workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime+1+data.ProcessTime[i][workProcess.get(i).get(0)-1][orderArr[j]];
deviceIndex = orderArr[j];
}
if (workResult.get(orderArr[j]).get(workResult.get(orderArr[j]).size() - 1).endTime < endTime[i][workProcess.get(i).get(0) - 2]) {
deviceTime = endTime[i][workProcess.get(i).get(0) - 2]+1+data.ProcessTime[i][workProcess.get(i).get(0)-1][orderArr[j]];
deviceIndex = orderArr[j];
}
}
}
}
}
//System.out.println("遍历设备"+(orderArr[j]+1)+"\t"+"目标设备"+(deviceIndex+1)+"\t"+deviceTime);
}
//更新工件信息
if (deviceIndex != data.deviceId.length) {
shop.workId = i + 1;
shop.workProcess = workProcess.get(i).get(0);
shop.deviceId = deviceIndex + 1;
shop.endTime = deviceTime;
endTime[i][workProcess.get(i).get(0) - 1] = shop.endTime;
shop.startTime = shop.endTime - data.ProcessTime[i][workProcess.get(i).get(0) - 1][deviceIndex];;
startTime[i][workProcess.get(i).get(0) - 1] = shop.startTime;
//工件信息加入结果列表
workResult.get(deviceIndex).add(shop.clone());
//删除该工序
workProcess.get(i).remove(0);
}
}
}
//所有工序分配完毕时,结束
boolean stopCondition = true;
for (int i = 0; i < data.workId.length; i++) {
if (workProcess.get(i).size() != 0) {
stopCondition = false;
break;
}
}
if (stopCondition) break;
}
printResult(workResult,endTime);
}
无论是最早开始时间还是最早结束时间策略,启发策略均能够得到可行的解方案。但是至于解方案的质量高低,还需要设计更好的启发策略。此次笔记仅实现了简单的两个启发策略,未来还有待进一步改善方法技术。
========================================
今天到此为止,后续记录其他应用问题的学习过程。