柔性作业车间调度

柔性作业车间调度


1 问题描述

1.1 传统作业车间调度

传统作业车间带调度实例
工件 工序 可选择的加工机器
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小时。

1.2 柔性作业车间调度

柔性作业车间带调度实例(参考自高亮老师论文
《改进遗传算法求解柔性作业车间调度问题》——机械工程学报)
工件 工序 可选择的加工机器
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]的机器组合。调度的目的还是总的完工时间最短(也可以是其他目标,比如机器最大负荷最短、总的机器负荷最短)


2 数据处理

2.1 数据集

引自博文“【标准算例数据源】作业车间、流水车间、柔性作业车间、其它”:https://blog.csdn.net/weixin_40775077/article/details/108762117

2.2 数据解释

柔性作业车间调度_第1张图片
上面的图片是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小时。依此类推。

2.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表示不可加工。


3 优化算法求解

3.1 编码方案

参考高亮老师论文改进遗传算法求解柔性作业车间调度问题——机械工程学报的编码方案。可行解分成两部分,第一部分为机器分配编码,第二部分为工序加工顺序编码。编码长度为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。这样编码的优势是把工件对加工顺序的约束隐含到了编码里面。

3.2 适应度计算(目标函数)

对于一个可行解,要计算总的完工时间,需要计算每个工件的当前加工时间,每个机器的当前加工时间。然后根据可行解,解码出工件加工顺序和机器安排,根据2.3小节获得的数据格式计算完工时间。

主要的思路如下(细节不再给出):

  1. 初始化,每个工件的当前加工时间 = 0;每个机器的当前加工时间 = 0;
  2. 工件的当前工序完工时间 = max(工件上一道工序完工时间,当前工序对应加工机器的上一个任务完工时间)+对应机器对当前工序的加工时间;
  3. 机器的当前加工时间 = 工件的当前工序完工时间;

3.3 可行解更新策略

参考高亮老师论文改进遗传算法求解柔性作业车间调度问题——机械工程学报的更新策略。主要实现了GA(遗传算法)的选择、交叉和变异。

3.3.1 选择

采用锦标赛选择,K元锦标赛:从大小为N的种群中随机选取K个个体,将适应度最好的个体加入下一代。重复执行N次,获得N个个体。

3.3.2 交叉

当满足交叉概率时,执行交叉,重复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]。

3.3.3 变异

当满足变异概率时,执行变异,重复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]。


4 代码

4.1 数据处理

% 将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

4.2 适应度计算

% 计算适应度,此时为最大完工时间
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

4.3 GA相关的代码

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

你可能感兴趣的:(优化算法,算法)