工件 | 工序 | 可选择的加工机器 | ||||
---|---|---|---|---|---|---|
M1 | M2 | M3 | M4 | M5 | ||
J1 | O11 | -- | -- | 5 | -- | -- |
O12 | 11 | -- | -- | -- | -- | |
O13 | -- | -- | -- | -- | 8 | |
J2 | O21 | -- | 6 | -- | -- | -- |
O22 | 9 | -- | -- | -- | -- | |
O23 | -- | -- | -- | 7 | -- |
有若干工件,每个工件有若干工序,有多个加工机器,但是每道工序只能在一台机器上加工。对应到上面表格中的实例就是,两个工件,工件J1有三道工序,工序Q11只能在M3上加工,加工时间是5小时。
约束是对于一个工件来说,工序的相对顺序不能变。O11->O12->O13。每时刻,每个工件只能在一台机器上加工;每个机器上只能有一个工件。
调度的任务则是安排出工序的加工顺序,加工顺序确定了,因为每道工序只有一台机器可用,加工的机器也就确定了。
调度的目的是总的完工时间最短(也可以是其他目标)。举个例子,比如确定了O21->O22->O11->O23->O12->O13的加工顺序之后,我们就可以根据加工机器的约束,计算出总的加工时间。
M2加工O21消耗6小时,工件J2当前加工时间6小时。
M1加工O22消耗9小时,工件J2当前加工时间6+9=15小时。
M3加工O11消耗5小时,工件J1当前加工时间5小时。
M4加工O23消耗7小时,工件J2加工时间15+7=22小时。
M1加工O12消耗11小时,但是要等M1加工完O22之后才开始加工O12,所以工件J1的当前加工时间为max(5,9)+11=20小时。
M5加工O13消耗8小时,工件J2加工时间20+8=28小时。
总的完工时间就是max(22,28)=28小时。
工件 | 工序 | 可选择的加工机器 | ||||
---|---|---|---|---|---|---|
M1 | M2 | M3 | M4 | M5 | ||
J1 | O11 | 2 | 6 | 5 | 3 | 4 |
O12 | -- | 8 | -- | 4 | -- | |
O13 | 4 | -- | 3 | 6 | 2 | |
J2 | O21 | 3 | -- | 6 | -- | 5 |
O22 | 4 | 6 | 5 | -- | -- | |
O23 | -- | 7 | 11 | 5 | 8 |
相比于传统作业车间调度,柔性作业车间调度放宽了对加工机器的约束,更符合现实生产情况,每个工序可选加工机器变成了多个,可以由多个加工机器中的一个加工。比如上表中的实例,J1的O12工序可以选择M2和M4加工,加工时间分别是8小时和4小时,但是并不一定选择M4加工,最后得出来的总的完工时间就更短,所以,需要调度算法求解优化。
相比于传统作业车间,柔性车间作业调度的调度任务不仅要确定工序的加工顺序,而且需要确定每道工序的机器分配。比如,确定了O21->O22->O11->O23->O12->O13的加工顺序,我们并不能相应工序的加工机器,所以还应该确定对应的[M1、M3、M5]->[M1、M2、M3]->[M1、M2、M3、M4、M5]->[M2、M3、M4、M5]->[M2、M4]->[M1、M3、M4、M5]的机器组合。调度的目的还是总的完工时间最短(也可以是其他目标,比如机器最大负荷最短、总的机器负荷最短)
引自博文“【标准算例数据源】作业车间、流水车间、柔性作业车间、其它”:https://blog.csdn.net/weixin_40775077/article/details/108762117
上面的图片是Mk数据的一个例子,第一行6 6 1表示6个工件6个机器,平均每道工序有1个可选加工机器。第2-7行为工件1-6的信息。以第2行为例,6表示工件J1有6道工序;后面的1 3 1表示工序O11有1台机器可选,可选机器为M3,加工时间为1小时;后面的1 1 3表示工序O12有1台机器可选,可选机器为M1,加工时间为3小时。依此类推。
上面的数据格式其实是不方便计算的(可行解不容易设计,调度目标如完工时长不容易计算)。所以需要读取为适合自己的格式。
下面是我的处理方式:
变量 | 类型 | 解释 |
---|---|---|
num_job | 1*1大小的整数 | 工件个数 |
num_machine | 1*1大小的整数 | 机器个数 |
num_op | 1*num_job大小的数组 | num_op[i]表示工件i的工序个数 |
operation_time | 1*num_job大小的cell,其中每个cell存储的是num_op[i]*num_machine大小的数组 | operation_time[i]存储的是工件i所有工序的可选加工机器以及加工时间,0表示不可加工 |
对应到上面图中的例子:
num_job = 6;
num_machine = 6;
num_op = [6 6 6 6 6 6];
operation_time{1} = [
0 0 1 0 0 0;
3 0 0 0 0 0;
0 6 0 0 0 0;
0 0 0 7 0 0;
0 0 0 0 0 3;
0 0 0 0 6 0;
];
operation_time{2-6}不再列出。operation_time{1}[i][j]表示工件1的第i道工序在第j台机器上的加工时间,0表示不可加工。
参考高亮老师论文改进遗传算法求解柔性作业车间调度问题——机械工程学报的编码方案。可行解分成两部分,第一部分为机器分配编码,第二部分为工序加工顺序编码。编码长度为2*工序总数。
机器分配编码按照工序的顺序来,第一个表示工序O11的机器分配,第二个数表示工序O12的机器分配,依此类推,如1.2节表格的一个可行解的机器分配可以从“[M1、M3、M5]->[M1、M2、M3]->[M1、M2、M3、M4、M5]->[M2、M3、M4、M5]->[M2、M4]->[M1、M3、M4、M5]”随机选为M3->M1->M2->M4->M2->M5,机器编码可以表示为3 1 2 4 2 5。这样编码的优势就是可以很容易设计出交叉变异操作方案,使得执行交叉变异操作之后得到的新解的机器编码是可行的。
工序加工顺序编码使用工件表示,工件i第j次出现表示工序Oij。比如1.2节表格的一个可行解的工序加工顺序可以为O21->O22->O11->O23->O12->O13,工序编码可以表示2 2 1 2 1 1。这样编码的优势是把工件对加工顺序的约束隐含到了编码里面。
对于一个可行解,要计算总的完工时间,需要计算每个工件的当前加工时间,每个机器的当前加工时间。然后根据可行解,解码出工件加工顺序和机器安排,根据2.3小节获得的数据格式计算完工时间。
主要的思路如下(细节不再给出):
参考高亮老师论文改进遗传算法求解柔性作业车间调度问题——机械工程学报的更新策略。主要实现了GA(遗传算法)的选择、交叉和变异。
采用锦标赛选择,K元锦标赛:从大小为N的种群中随机选取K个个体,将适应度最好的个体加入下一代。重复执行N次,获得N个个体。
当满足交叉概率时,执行交叉,重复N次判断。随机选择两个个体,具体交叉策略如下:
对于机器编码,采用两点交叉,随机生成两个交叉点,交换两个个体位于交叉点内的基因。
对于1.2节表格的两个可行解的机器编码,比如p1 = [3 1 2 5 4 1];p2 = [1 3 2 4 2 5];选取交叉点为2-4的话,交换p1和p2位于2-4之内的基因,得到两个子代s1 = [3 3 2 4 4 1];s2 = [1 1 2 5 2 5]。
对于工序编码,采用基于工件的POX交叉,将工件分成两个互补的集合J1和J2,P1中在J1中的工件保留,在J2中的工件使用P2中在J2中的工件按顺序对应填充。
1.2节的表格不适合解释这个交叉方案,我们提出一个新的问题:有4个工件待加工,工序数目为[2 2 2 1]。对应的两个可行解的工序编码,比如p1 = [3 1 2 4 2 1 3];p2 = [2 3 1 2 4 3 1]。J1={1,4},J2={2,3}。首先s1继承p1的J1编码,s2继承p2的J2编码,s1 = [- 1 - 4 - 1 -];s2 = [2 3 - 2 - 3 -]。用p2的J2编码填充s1的空位,同样方法填充s2。最终得到交叉后的两个子代s1 = [2 1 3 4 2 1 3];s2 = [2 3 1 2 4 3 1]。
当满足变异概率时,执行变异,重复N次判断。当执行变异时,对于机器编码:随机选择一个变异位置,然后从该位置对应工序的可选加工机器集合中选择一台其他机器。对于工序编码:随机选择两个位置,交换对应的工序。举个例子:
机器编码:p1 = [3 1 2 5 4 1];变异位置为2,位置3对应工序的可选加工机器为[1 2 3],变异后的解可能为s1 = [3 2 2 5 4 1]。
工序编码:p1 = [3 1 2 4 2 1 3];变异位置为2和4,变异后的解为s1 = [3 4 2 1 2 1 3]。
% 将fjs文件数据格式化
function [ ] = dataReady(sfilepath,tfilepath)
%DATAREAD 此处显示有关此函数的摘要
% 此函数用于将fjs文件转换为格式化的mat文件,保存num_job、num_machine、num_op、operation_time
% 加工时间为 -1 表示该工序不可在此机器上加工
% 此处显示详细说明
% sfilepath 表示源数据路径;tfilepath 表示处理后.mat存放路径
% filepath = 'TextData\Monaldo\Fjsp\Job_Data\Brandimarte_Data\Text\Mk02.fjs';
% 读取数据
fidin=fopen(sfilepath);
x = fscanf(fidin,'%f');
% 获取工件数目、机器数目、每个工件的工序数目、每个工序在各台机器上的加工时间
num_job = x(1); % 工件数目
num_machine = x(2); % 机器数目
num_op = zeros(1,num_job); % 每个工件的工序数目
operation_time = cell(1,num_job); % 每个工序在各台机器上的加工时间
k = 4; % 下次开始读数据的下标,有时需改为3
for i = 1:num_job
num_op(1,i) = x(k); % 工件 i 的工序数目
k = k + 1;
jobi_operation_time = ones(num_op(1,i),num_machine)*-1; % 工件 i 的工序加工时间
for j = 1:num_op(1,i)
num_machine_opij = x(k); % 工件 i 的工序 j 可加工机器数目
k = k + 1;
for l = 1:num_machine_opij
id_macnine_opij = x(k); % 机器编号
k = k + 1;
time_opij = x(k); % 加工时间
k = k + 1;
jobi_operation_time(j,id_macnine_opij) = time_opij; % 工件 i 的工序 j 在机器 id_macnine_opij 上的加工时间
end
end
operation_time{
i} = num2cell(jobi_operation_time);
end
% 保存文件到tfilepath
save(tfilepath,'num_job','num_machine','num_op','operation_time');
end
% 计算适应度,此时为最大完工时间
function [ cmax] = fitnessFun( x )
%FITNESSOBJ 此处显示有关此函数的摘要
% 假设x是可行解,x是一个2行to列的矩阵,to表示工序总数。x第一行表示工序加工顺序,x第二行表示机器分配情况
% 第二行按O11,O12,O21,O22,O23这样的顺序分配机器,而非与第一行对应,即第一个机器对应第一个工序,而不是第一个要加工的工序
% 此处显示详细说明
% global num_job num_machine num_op operation_time
global num_job num_machine num_op operation_time
max_num_op = max(num_op); % 工序最大数目
op_to_machine = zeros(num_job, max_num_op); % 工序映射到机器
to = 0; % 工序总数
for i = 1:num_job
for j = 1:num_op(1,i)
to = to + 1;
op_to_machine(i,j) = to;
end
end
cur_job_op = zeros(1,num_job); % 当前各工件加工到第几个工序
t_machine = zeros(1,num_machine); % 各机器加工完一个工序的完工时间
t_op = zeros(num_job,max_num_op); % 各工序完工时间
for i = 1:to
id_job = x(1,i); % x的第一行对应工件编号
cur_job_op(1,id_job) = cur_job_op(1,id_job)+1; % id_job个工件的当前加工工序加1
id_op = cur_job_op(1,id_job);
% 找到该工序对应的加工机器
index_machine = op_to_machine(id_job,id_op);
id_machine = x(2,index_machine); % x的第二行对应机器分配情况
% 更新完工时间,一个是机器,一个是工序
if id_op == 1
ftime = t_machine(1,id_machine)+cell2mat(operation_time{
id_job}(id_op,id_machine));
t_machine(1,id_machine) = ftime;
t_op(id_job,id_op) = ftime;
else
ftime = max(t_op(id_job,id_op-1),t_machine(1,id_machine))+cell2mat(operation_time{
id_job}(id_op,id_machine));
t_machine(1,id_machine) = ftime;
t_op(id_job,id_op) = ftime;
end
end
cmax = 0; % 最大完工时间
for i=1:num_job
cmax_jobi = t_op(i,num_op(1,i)); % 工件i的最大完工时间
if cmax_jobi > cmax
cmax = cmax_jobi;
end
end
end
function [bestp,bestf,Convergence_curve] = GA(N,MaxFEs,lb,ub,dim,tran_fhd)
% tran_fhd表示转换函数,将实数空间的解转换为可行解。x1也是与解的转换相关的参数,这里没有给出代码,
% 初始化的时候可以修改为直接初始化为可行解,不经过转换,删掉tran_fhd相关的部分。
%% 初始化
global num_job num_machine num_op operation_time x1
% 参数设置
N = 200;
pc = 0.8;
pm = 0.01;
k = 10; % k元锦标赛
to = sum(num_op); % 工序总数
MaxFEs = 20000;
fitness = ones(1,N)*inf;
bestf = inf;
bestp = zeros(2,dim);
Convergence_curve = [];
it = 1;
fes = 0;
% 种群初始化
X=initializationM(N,2,dim,ub,lb);
for i=1:N
X(:,:,i) = tran_fhd(X(:,:,i));
end
%% 主循环 DE Main Loop
while fes<MaxFEs
% 计算适应度
for i=1:N
fitness(i) = fitnessFun(X(:,:,i));
fes = fes+1;
if fitness(i)<bestf
bestf = fitness(i);
bestp = X(:,:,i);
end
end
% 选择 得到一群个体
X = Selection(X,N,k,fitness);
% 交叉 得到一群个体
X = Crossover(X,N,pc,to,num_job);
% 变异 得到一群个体
X = Mutation(X,N,pm,to,num_job,num_op,operation_time);
Convergence_curve(it)=bestf;
it=it+1;
end
end
function newX = Selection(X,N,k,fitness)
newX = X;
for i=1:N
% 从N个中随机选k个,取最大,加进newX
s = randperm(N,k);
sx = X(:,:,s);
sf = fitness(s);
[~,sindex] = min(sf);
newX(:,:,i) = sx(:,:,sindex);
end
end
function newX = Crossover(X,N,pc,to,num_job)
newX = X;
for i=1:N
if rand<=pc
% 选两个个体
s = randperm(N,2);
r1 = s(1);
r2 = s(2);
% 交叉
% 机器分配,两点交叉
cpoint = randperm(to,2);
cp1 = min(cpoint);
cp2 = max(cpoint);
newX(2,cp1:cp2,r1) = X(2,cp1:cp2,r2);
newX(2,cp1:cp2,r2) = X(2,cp1:cp2,r1);
% 工序顺序,pox交叉,工件分成两个集合,继承父代,继承母代
% 工件分集合
hfd = floor(num_job/2);% 划分点
js=randperm(num_job);
js1 = js(1:hfd);
js2 = js(hfd+1:end);
% 继承
c1 = zeros(1,to);
c2 = zeros(1,to);
for j = 1:to
if(ismember(X(1,j,r1),js1)==1)
c1(j) = X(1,j,r1);
end
if(ismember(X(1,j,r2),js2)==1)
c2(j) = X(1,j,r2);
end
end
% 填充
tc1 = c1;
tc2 = c2;
c1(find(tc1==0)) = tc2(find(tc2~=0));
c2(find(tc2==0)) = tc1(find(tc1~=0));
% 个体修改
newX(1,:,r1) = c1;
newX(1,:,r2) = c2;
end
end
end
function newX = Mutation(X,N,pm,to,num_job,num_op,operation_time)
newX = X;
for i=1:N
if rand<=pm
% 机器分配部分,选一个位置,从该位置可选机器选另一个机器
r1 = randperm(to,1);
top = 0;
op_id = 0;
for j = 1:num_job
op_id = r1-top;
top = top+num_op(j);
if top>=r1
job_id = j;
break
end
end
machine = cell2mat(operation_time{
job_id});
machine = find(machine(op_id,:)~=-1);
ma_id = X(2,r1,i);
if size(machine,2)>1
machine(machine==ma_id) = [];
r2 = randperm(size(machine,2),1);
ma_id = machine(r2);
end
newX(2,r1,i) = ma_id;
% 第二部分,随机选两个位置,互换
s = randperm(to,2);
r1 = s(1);
r2 = s(2);
newX(1,r1,i) = X(1,r2,i);
newX(1,r2,i) = X(1,r1,i);
end
end
end