遗传算法求解经典车间调度问题(JSP)

车间调度问题

Job-Shop scheduling problem (JSP)车间调度问题(NP-hard 问题):

​ n个工件在m台机器上加工,每个工件有特定的加工工艺,每个工件加工的顺序及每道工序所花时间给定,安排工件在每台机器上工件的加工顺序,使得某种指标最优。题设为:

1)不同工件的工序之间无顺序约束;2)工序开始则不能间断,每个机器在同一时刻只能加工一个工序;3)机器不发生故障。

调度的目标就是确定每个机器上工序的加工顺序和每个工序的开工时间,使最大完工时间最小或其他指标达到最优。

 instance ft06
 
 +++++++++++++++++++++++++++++
 Fisher and Thompson 6x6 instance, alternate name (mt06)
 6 6
 2  1  0  3  1  6  3  7  5  3  4  6
 1  8  2  5  4 10  5 10  0 10  3  4
 2  5  3  4  5  8  0  9  1  1  4  7
 1  5  0  5  2  5  3  3  4  8  5  9 
 2  9  1  3  4  5  5  4  0  3  3  1
 1  3  3  3  5  9  0 10  4  4  2  1
 +++++++++++++++++++++++++++++

数据解释:

一共六台机器,六个任务

image-20240507152334582

手工排产

因为用matlab,所以把下标加1了。

process1 process2 process3 process4
MachId CycleTime MachId CycleTime MachId CycleTime MachId CycleTime
Job1 3 1 1 3 2 6 4 7
Job2 2 8 3 5 1 10 4 10
Job3 3 5 4 4 2 8 1 9
  1. 手动排工作顺序:

    执行方案:

    image-20240507155302459

    机器1 机器2 机器3 机器4
    1,2,3 2,1,3 3,1,2 3,2,1

    算一下最大完工时间:由表格可知,最大完工时间是40.

    image-20240507155318739

    JobId MachId ProcId StartTime EndTime
    1 3 1 5 6
    1 1 2 6 9
    1 2 3 9 15
    1 4 4 33 40
    2 2 1 0 8
    2 3 2 8 13
    2 1 3 13 23
    2 4 4 23 33
    3 3 1 0 5
    3 4 2 5 9
    3 2 3 15 23
    3 1 4 23 32
  2. 甘特图(横轴表示时间,纵轴表示机器)

遗传算法GA

  1. 初始化:随机生成一组候选解(染色体);
  2. 评估:计算每个染色体的适应度值,该值表示其解决问题的优劣程度;
  3. 选择:根据适应度值选染色体进行交叉和变异;
  4. 交叉:将两个染色体的一部分交换,产生新的染色体;
  5. 变异:随机改变染色体的一个部分,产生新的染色体;
  6. 重复:重复步骤2-5,直到达到终止条件。

通过Matlab实现编码,在JSP问题中,染色体的编码长度为n*m n代表有多少个job ,m表示需要进程,即job在染色体中需要出现m次。

1.GA编码步骤:

  1. 决定编码的长度和内容:长度=n*m,内容1,2,...,n出现m次,用一维数组表示即可。

    %生成编码:传两个参数【1】 jobQty,【2】 machQty
    function chrome=createChrome(jobQty,machQty)
        a=1:jobQty;
        chrome=a;
        for  i=2:machQty
            chrome=[chrome a];
        end
    end
    
  2. 将有规律的代码,进行乱序排列,使用randperm函数;

        %将编码乱序排列-生成作业的乱码
        b=randperm(jobQty*machQty);
        chrome=chrome(b);
    
  3. 根据输入的参数pop来生成多个编码,形成算法的初始种群,结果:二维数组,每行表示一个编码【可行解】;

    %根据种群数量、作业数量、设备数量生成初始种群
    function chromes=createChromes(jobQty,machQty,pop)
        chromes=zeros(pop,jobQty*machQty);
        for i=1:pop
            chromes(i,:)=createChrome(jobQty,machQty);
        end
    end
    

2.根据编码生成具体的调度方案

  1. 初始化数据包-工时设备数据data 编码 chrome,初始数据需要将data的奇数列设备编码由原来的0开始,转换为从1开始。

    %初始化工时设备数据
    function data=initdata()
        data=[  2  1  0  3  1  6  3  7  5  3  4  6;
                1  8  2  5  4 10  5 10  0 10  3  4;
                2  5  3  4  5  8  0  9  1  1  4  7;
                1  5  0  5  2  5  3  3  4  8  5  9;
                2  9  1  3  4  5  5  4  0  3  3  1;
                1  3  3  3  5  9  0 10  4  4  2  1];
        for i=1:size(data,2)
            if mod(i,2)==1
                data(:,i)=data(:,i)+1;
            end
        end
    end      
    
  2. 将编码方案转换为具体调度方案

    1. 定义一个jobQty*machQty行,5列的二维数组【schedule】,用于存储编码对应的调度方案;

      定义过程涉及到函数size,能够返回特定数组变量的行数和列数;

      1. size(数组变量名):返回行数,列数;
      2. size(数组变量名,1):返回行数;
      3. size(数组变量名,2):返回列数;
    2. 根据chrome和data,将schedule数组填充起来

      定义长度为jobQty一维数组,用于存储各个作业当前工序最早可开工事件,或者当前作业前一工序的完工时间,jobCanStartTime;

      定义长度为jobQty的一维数组,用于存储各个作业当前拟排产的工序号,jobprocessId;

      循环对chrome中的每个字码【对应特定作业】,完成工序号、设备号、开工时间、完工时间的生成;

    3. 向schedule数组中安排chrome对应的具体开工时间和完工时间,与前三列同步生成的。

      ​ 当获取了当前jobId、machId、procId,从schedule中提取第二列数值为machSch的全部行

      ​ 【1】如果没找到对应的行,表示这个machId值的设备还没有工作-作业;

      ​ 直接向schedule中添加一条数据,第四列startTime就是jobId的可开工时间,第5列endTime为第四列数据+data的工时数据;

      ​ 【2】如果找到了至少一行数据,则表示这个machId值对应设备已经安排了工作;

      ​ 就需要判断具体将开工时间和完工时间安排在什么时段,首先将新的数组按照开工时间升序排列;考虑三种情况进行作业安排;

      ​ (1)是否能排到该设备的第一个作业之前;

      ​ (2)是否能排到其他作业之前;

      ​ (3)是否需要排到最后一个作业之后;

%将编码转换成具体的调度方案
function schedule=createSchedule(data,chrome)
    jobQty=size(data,1);
    machQty=size(data,2)/2;
    schedule=zeros(jobQty*machQty,5);
    %定义中间数组
    %用来存储对应工作的开始时间,所以初始化为0;
    jobCanStartTime=zeros(1,jobQty);
    %用来记忆当前工作进行到哪一个工序,初始就是第一个工序,所以初始化为1;
    jobProcessId=ones(1,jobQty);
    for i=1:jobQty*machQty
        %获取编码中的当前作业号,i位置的数字
        %从编码中取出一个job
        nowJobId=chrome(i);
        %找到这个job的执行到哪一个工序
        nowProcId=jobProcessId(nowJobId);
        %找到当前工作的Id
        nowMachId=data(nowJobId,2*nowProcId-1);
        nowProcTime=data(nowJobId,2*nowProcId);
        %判断是否该台机器已经运行,若未运行,则machSch为空数组
        machSch=schedule(schedule(:,2)==nowMachId,:);
        %jobCanST指当前工作可以开始的时间。
        jobCanST=jobCanStartTime(nowJobId);
        if size(machSch,1)==0  %即机器还未安排工作
            startTime=jobCanStartTime(nowJobId);
            endTime=startTime+nowProcTime;
        else  %如果设备已经安排了工作
            %按照开工时间进行升序排列
            machSch=sortrows(machSch,4);
            rows=size(machSch,1);
            %处理第一行已排作业,检查是否能将当前作业排到该作业之前
            done=0;
            if jobCanST<machSch(1,4)   %当前工作开始时间是否小于设备最小的运行时间。
                if machSch(1,4)-jobCanST>=nowProcTime
                    startTime=jobCanST;
                    endTime=startTime+nowProcTime;
                    done=1;
                end
            end
            if done==0
                for j=2:rows
                    if jobCanST<machSch(j,4)  %判断是否能够在中间,时间完成
                        if machSch(j,4)-max(jobCanST,machSch(j-1,5))>=nowProcTime
                            startTime=max(jobCanST,machSch(j-1,5));
                            endTime=startTime+nowProcTime;
                            done=1;
                            break;
                        end
                    end
                end
            end
            if done==0 %只能排到最后面
                startTime=max(jobCanST,machSch(rows,5));
                endTime=startTime+nowProcTime;
            end          
        end
        schedule(i,1)=nowJobId;
        schedule(i,2)=nowMachId;
        schedule(i,3)=nowProcId;
        schedule(i,4)=startTime;
        schedule(i,5)=endTime;
        jobProcessId(nowJobId)=jobProcessId(nowJobId)+1;
        jobCanStartTime(nowJobId)=endTime;
    end
end

3.根据调度方案绘制甘特图

绘图命令:

line([1 2], [4 4],'lineWidth',2)
ylim([3 6])

完整参考程序

%绘制甘特图
function drawGant(schedule)
    rows=size(schedule,1);
    maxMachId=max(schedule(:,2));
    jobQty=max(schedule(:,1));
    mycolor=rand(jobQty,3);
    figure;
    xlabel("Time");
    ylabel("Machine");
    ylim([0 maxMachId+1]);
    for i=1:rows
        x=[schedule(i,4),schedule(i,5)];
        y=[schedule(i,2),schedule(i,2)];
        line(x,y,'lineWidth',10,'color',mycolor(schedule(i,1),:));
        procId=schedule(i,3);
        jobId=schedule(i,1);
        txt=[int2str(jobId) ' ' int2str(procId)];
        text(x(1),y(1),txt);
    end       
end

4.获取目标函数的适应度--目标函数的值

本案例:

  • 目标函数:最大完工时间
  • 目标:最小化最大完工时间
%获取调度方案的最大完工时间
function finishTime=fitness(schedule)
    finishTime=max(schedule(:,5));
end

5.遗传算法运算

Step1:初始化-算法控制参数,初始种群-【进行决策变量的编码】;

Step2:对种群进行遗传操作

​ Step2.1:复制

​ Step2.2:交叉

​ Step2.3:变异

Step3:判断是否终止-算法终止条件是否满足。若是,则执行step4;否则执行step2;

Step4:将算法获取到的最优解输出。

程序写法

  1. 将遗传算法主程序的循环结构写出来

  2. 复制程序设计

    1. 计算种群每条染色体的适应度值;fitnesses

    2. 将适应度数组升序排序,同时保留对应适应度值的编码序号;

      排序函数【数值,序号】=sortrows(a,1);

    3. 获得每个染色体的遗传参考值、归一化、累计概率值;

    4. 从现有的pop【20】个染色体中根据轮盘赌选择特定pop【20】个染色体,形成新的种群。

5.1 复制操作

  1. 比例选择(轮盘赌):按照目标函数值,从函数输出一个参考值,对这个参考值进行归一化处理,生成一个轮盘。然后生成随机数去命中这个轮盘中的个体。

    image-20240508193523832

  2. 基于排名的选择:....

  3. 锦标赛选择:....

%复制操作
function outchromes=copyChromes(data,inchromes)
    outchromes=zeros(size(inchromes));
    %步骤1:计算每条染色体的适应度值
    pop=size(inchromes,1);
    fitnesses=zeros(pop,1);
    for i=1:pop
        sch=createSchedule(data,inchromes(i,:));
        fit=fitness(sch);
        fitnesses(i)=fit;
    end
    %步骤2:排序并获取序号
    [fit2,chromeId]=sortrows(fitnesses,1);
    %步骤3:获取遗传参考值
    maxVal=max(fit2)*1.2;
    fit3=maxVal-fit2;
    %归一化处理
    sumFit=sum(fit3);
    oneFit=fit3/sumFit;
    finalFit=zeros(pop,1);
    for i=1:pop
        finalFit(i)=sum(oneFit(1:i));
    end
    %步骤4:选择形成对应的染色体
    for i=1:pop
        midVal=rand();
        %选择复制染色体;
        if midVal<finalFit(1)
            outchromes(i,:)=inchromes(chromeId(1),:);
        else
            for j=1:pop-1
                if midVal>=finalFit(j)&& midVal<finalFit(j+1)
                    outchromes(i,:)=inchromes(chromeId(j+1),:);
                    break;
                end
            end
        end
        
    end
end

5.2 交叉操作

对于二进制编码,通常采用单点交叉(只有一个分隔点)和多点交叉(有多个分隔点)。

组合优化时,GA通常采用部分映射交叉、次序交叉、循环交叉、基于位置的交叉、Non-ABEL群交叉等方式。

image-20240509151037766

采用部分映射交叉,两点交叉。

  1. 对染色体群体前后相邻的两条染色体,根据交叉概率决定这两个染色体是否进行交叉操作;如果进行交叉操作,则执行step2,否则继续查找后面的两条染色体进行判断,直到全部染色体判断结束。
  2. 随机生成1:n*m之间的两个点,p1和p2;
  3. 将父代染色体,C1和C2在p1和p2两点之间的基因片段交换,形成新的子代D1和D2;
  4. 对子代D1和D2进行有效性处理-编码不能有缺,同时不能存在重复。(比较麻烦)
    1. 将两个交换片段对比,将不能抵消的代码保留下来;
    2. 将不能抵消的代码,逐次对子串进行替换;
%交叉操作
function outchromes=crossChromes(chromes,crossRate)
    outchromes=chromes;
    pop=size(chromes,1);
    cols=size(chromes,2);
    for i=1:2:pop
        if rand()<crossRate   %进行交叉操作
            parent1=chromes(i,:);
            parent2=chromes(i+1,:);
            p=sortrows(randperm(cols,2)');
            %得到两个交换的片段
            crossP1=parent1(p(1):1:p(2));
            crossP2=parent2(p(1):1:p(2));
            son1=parent1;
            son1(p(1):1:p(2))=crossP2;
            son2=parent2;
            son2(p(1):1:p(2))=crossP1;
            %子片段对比
            crossLen=size(crossP1,2);
            for j=crossLen:-1:1
                midCode=crossP1(j);
                for k=1:size(crossP2,2)
                    if crossP2(k)== midCode
                        crossP1(j)=[];
                        crossP2(k)=[];
                        break;
                    end     
                end     
            end 
            %染色体编码置换
            repeatNum=size(crossP1,2);
            if repeatNum>0
                %对子代1的有效性置换
                for j=1:repeatNum
                    midCode=crossP2(j);
                    if p(1)==1
                        for k=p(2)+1:cols
                            if son1(k)==midCode
                                son1(k)=crossP1(j);
                                break;
                            end
                        end
                    else
                        if p(2)==cols
                            for k=1:p(1)-1
                                if son1(k)==midCode
                                    son1(k)=crossP1(j);
                                    break;
                                end
                            end
                        else
                            getIt=0;
                            for k=1:p(1)-1
                                if son1(k)==midCode
                                    son1(k)=crossP1(j);
                                    getIt=1;
                                    break;
                                end
                            end
                            if getIt==0
                               for k=p(2)+1:cols
                                    if son1(k)==midCode
                                        son1(k)=crossP1(j);
                                        break;
                                    end
                                end
                            end 
                        end
                    end
                end
                %对子代2的有效性置换
                for j=1:repeatNum
                    midCode=crossP1(j);
                    if p(1)==1
                        for k=p(2)+1:cols
                            if son2(k)==midCode
                                son2(k)=crossP2(j);
                                break;
                            end
                        end
                    else
                        if p(2)==cols
                            for k=1:p(1)-1
                                if son2(k)==midCode
                                    son2(k)=crossP2(j);
                                    break;
                                end
                            end
                        else
                            getIt=0;
                            for k=1:p(1)-1
                                if son2(k)==midCode
                                    son2(k)=crossP2(j);
                                    getIt=1;
                                    break;
                                end
                            end
                            if getIt==0
                               for k=p(2)+1:cols
                                    if son2(k)==midCode
                                        son2(k)=crossP2(j);
                                        break;
                                    end
                                end
                            end 
                        end
                    end
                end
            end
        outchromes(i,:)=son1;
        outchromes(i+1,:)=son2;
        end
    end
    
end

5.3 变异操作

变异操作主要采用互换、逆序、插入变异。

  1. 互换操作(SWAP),即随机交换染色体中两个不同基因的位置。

  2. 逆序操作(INV),即将染色体中两不同随机位置间的基因串逆序。(fliplr函数)

    %变异操作--逆序方式
    function chromes=muteChromesInvert(chromes,muteRate)
        [pop,len]=size(chromes);
        for i=1:pop
            nowChrome=chromes(i,:);
            if rand()<muteRate
                points=sortrows(randperm(len,2));
                invertChrome=nowChrome(points(1):points(2));
                newChrome=fliplr(invertChrome);
                chromes(i,points(1):points(2))=newChrome;
            end   
        end
    end
    
  3. 插入操作(INS),随机选择某个点插入到串中的某个位置。 (circshift函数)

    %变异操作--插入方式
    function chromes=muteChromesInsert(chromes,muteRate)
        [pop,len]=size(chromes);
        for i=1:pop
            nowChrome=chromes(i,:);
            if rand()<muteRate
    %             outPoint=randperm(len,1);
    %             inPoint=randperm(len,1);
                points=randperm(len,2);
                %用circshift函数实现数组变化。只改变当前片段的次序就可以
                if points(1)<points(2)   %插入点在后面  将一个点(1)插入到另一个点(2)之前;
                    insertChrome=nowChrome(points(1):points(2)-1);
                    newChrome=circshift(insertChrome,-1);
                    chromes(i,points(1):points(2)-1)=newChrome;
                else
                    insertChrome=nowChrome(points(2):points(1));
                    newChrome=circshift(insertChrome,1);
                    chromes(i,points(2):points(1))=newChrome;
                end
            end   
        end
    end
    

6.最优化集成

% 算法主程序
function GA4JSP()
    clc;clear
    dt=initData();
    [rows,cols]=size(dt);
    pop=40;
    chromes=createChromes(rows,cols/2,pop);
%     chrome1=chromes(1,:);
%     sch=createSchedule(dt,chrome1);
%     %drawGant(sch);
%     finishT=fitness(sch);
    
    %初始化算法参数
    crossRate=0.6;
    muteRate=0.3;
    maxGeneration=100;
    nowGeneration=0;
    [optFitness,optChrome]=findOptSolution(chromes,dt);

    %迭代循环程序-同时包含终止条件
    while nowGeneration<maxGeneration
        %复制操作
        disp(['generation:' int2str(nowGeneration)]);
        chromes=copyChromes(dt,chromes);
        %交叉操作
        chromes=crossChromes(chromes,crossRate);

        %变异操作
        chromes=muteChromesInsert(chromes,muteRate);
        chromes=muteChromesInvert(chromes,muteRate);
        
        %判断是否替换最优解
        [nowOptFit,nowOptChrome]=findOptSolution(chromes,dt);
        if nowOptFit < optFitness
            optFitness=nowOptFit;
            optChrome=nowOptChrome;
        end        
        nowGeneration=nowGeneration+1;
    end
    %显示最优结果
    disp(['耗时:' int2str(optFitness)]);
    sch=createSchedule(dt,optChrome);
    drawGant(sch);
end

%找出当前种群中最优解的染色体编码以及最优解的值
function [nowOptFit,nowOptChrome]=findOptSolution(inchromes,data)
    %计算每条染色体的适应度值,放到数组中
    pop=size(inchromes,1);
    fitnesses=zeros(pop,1);
    for i=1:pop
        sch=createSchedule(data,inchromes(i,:));
        fit=fitness(sch);
        fitnesses(i)=fit;
    end
    %对适应度值,排序并获取序号,顺序是升序的,所以第一个是最好的。
    [fit2,chromeId]=sortrows(fitnesses,1);
    nowOptFit=fit2(1);
    nowOptChrome=inchromes(chromeId(1),:);
end

完整程序

% 算法主程序
function GA4JSP()
    clc;clear
    dt=initData();
    [rows,cols]=size(dt);
    pop=40;
    chromes=createChromes(rows,cols/2,pop);
%     chrome1=chromes(1,:);
%     sch=createSchedule(dt,chrome1);
%     %drawGant(sch);
%     finishT=fitness(sch);
    
    %初始化算法参数
    crossRate=0.6;
    muteRate=0.3;
    maxGeneration=100;
    nowGeneration=0;
    [optFitness,optChrome]=findOptSolution(chromes,dt);

    %迭代循环程序-同时包含终止条件
    while nowGeneration<maxGeneration
        %复制操作
        disp(['generation:' int2str(nowGeneration)]);
        chromes=copyChromes(dt,chromes);
        %交叉操作
        chromes=crossChromes(chromes,crossRate);

        %变异操作
        chromes=muteChromesInsert(chromes,muteRate);
        chromes=muteChromesInvert(chromes,muteRate);
        
        %判断是否替换最优解
        [nowOptFit,nowOptChrome]=findOptSolution(chromes,dt);
        if nowOptFit < optFitness
            optFitness=nowOptFit;
            optChrome=nowOptChrome;
        end        
        nowGeneration=nowGeneration+1;
    end
    %显示最优结果
    disp(['耗时:' int2str(optFitness)]);
    sch=createSchedule(dt,optChrome);
    drawGant(sch);
end

%找出当前种群中最优解的染色体编码以及最优解的值
function [nowOptFit,nowOptChrome]=findOptSolution(inchromes,data)
    %计算每条染色体的适应度值,放到数组中
    pop=size(inchromes,1);
    fitnesses=zeros(pop,1);
    for i=1:pop
        sch=createSchedule(data,inchromes(i,:));
        fit=fitness(sch);
        fitnesses(i)=fit;
    end
    %对适应度值,排序并获取序号,顺序是升序的,所以第一个是最好的。
    [fit2,chromeId]=sortrows(fitnesses,1);
    nowOptFit=fit2(1);
    nowOptChrome=inchromes(chromeId(1),:);
end


%变异操作--逆序方式
function chromes=muteChromesInvert(chromes,muteRate)
    [pop,len]=size(chromes);
    for i=1:pop
        nowChrome=chromes(i,:);
        if rand()<muteRate
            points=sortrows(randperm(len,2));
            invertChrome=nowChrome(points(1):points(2));
            newChrome=fliplr(invertChrome);
            chromes(i,points(1):points(2))=newChrome;
        end   
    end
end


%变异操作--插入方式
function chromes=muteChromesInsert(chromes,muteRate)
    [pop,len]=size(chromes);
    for i=1:pop
        nowChrome=chromes(i,:);
        if rand()<muteRate
%             outPoint=randperm(len,1);
%             inPoint=randperm(len,1);
            points=randperm(len,2);
            %用circshift函数实现数组变化。只改变当前片段的次序就可以
            if points(1)<points(2)   %插入点在后面  将一个点(1)插入到另一个点(2)之前;
                insertChrome=nowChrome(points(1):points(2)-1);
                newChrome=circshift(insertChrome,-1);
                chromes(i,points(1):points(2)-1)=newChrome;
            else
                insertChrome=nowChrome(points(2):points(1));
                newChrome=circshift(insertChrome,1);
                chromes(i,points(2):points(1))=newChrome;
            end
        end   
    end
end



%交叉操作
function outchromes=crossChromes(chromes,crossRate)
    outchromes=chromes;
    pop=size(chromes,1);
    cols=size(chromes,2);
    for i=1:2:pop
        if rand()<crossRate   %进行交叉操作
            parent1=chromes(i,:);
            parent2=chromes(i+1,:);
            p=sortrows(randperm(cols,2)');
            %得到两个交换的片段
            crossP1=parent1(p(1):1:p(2));
            crossP2=parent2(p(1):1:p(2));
            son1=parent1;
            son1(p(1):1:p(2))=crossP2;
            son2=parent2;
            son2(p(1):1:p(2))=crossP1;
            %子片段对比
            crossLen=size(crossP1,2);
            for j=crossLen:-1:1
                midCode=crossP1(j);
                for k=1:size(crossP2,2)
                    if crossP2(k)== midCode
                        crossP1(j)=[];
                        crossP2(k)=[];
                        break;
                    end     
                end     
            end 
            %染色体编码置换
            repeatNum=size(crossP1,2);
            if repeatNum>0
                %对子代1的有效性置换
                for j=1:repeatNum
                    midCode=crossP2(j);
                    if p(1)==1
                        for k=p(2)+1:cols
                            if son1(k)==midCode
                                son1(k)=crossP1(j);
                                break;
                            end
                        end
                    else
                        if p(2)==cols
                            for k=1:p(1)-1
                                if son1(k)==midCode
                                    son1(k)=crossP1(j);
                                    break;
                                end
                            end
                        else
                            getIt=0;
                            for k=1:p(1)-1
                                if son1(k)==midCode
                                    son1(k)=crossP1(j);
                                    getIt=1;
                                    break;
                                end
                            end
                            if getIt==0
                               for k=p(2)+1:cols
                                    if son1(k)==midCode
                                        son1(k)=crossP1(j);
                                        break;
                                    end
                                end
                            end 
                        end
                    end
                end
                %对子代2的有效性置换
                for j=1:repeatNum
                    midCode=crossP1(j);
                    if p(1)==1
                        for k=p(2)+1:cols
                            if son2(k)==midCode
                                son2(k)=crossP2(j);
                                break;
                            end
                        end
                    else
                        if p(2)==cols
                            for k=1:p(1)-1
                                if son2(k)==midCode
                                    son2(k)=crossP2(j);
                                    break;
                                end
                            end
                        else
                            getIt=0;
                            for k=1:p(1)-1
                                if son2(k)==midCode
                                    son2(k)=crossP2(j);
                                    getIt=1;
                                    break;
                                end
                            end
                            if getIt==0
                               for k=p(2)+1:cols
                                    if son2(k)==midCode
                                        son2(k)=crossP2(j);
                                        break;
                                    end
                                end
                            end 
                        end
                    end
                end
            end
        outchromes(i,:)=son1;
        outchromes(i+1,:)=son2;
        end
    end
    
end

%复制操作
function outchromes=copyChromes(data,inchromes)
    outchromes=zeros(size(inchromes));
    %步骤1:计算每条染色体的适应度值
    pop=size(inchromes,1);
    fitnesses=zeros(pop,1);
    for i=1:pop
        sch=createSchedule(data,inchromes(i,:));
        fit=fitness(sch);
        fitnesses(i)=fit;
    end
    %步骤2:排序并获取序号
    [fit2,chromeId]=sortrows(fitnesses,1);
    %步骤3:获取遗传参考值
    maxVal=max(fit2)*1.2;
    fit3=maxVal-fit2;
    %归一化处理
    sumFit=sum(fit3);
    oneFit=fit3/sumFit;
    finalFit=zeros(pop,1);
    for i=1:pop
        finalFit(i)=sum(oneFit(1:i));
    end
    %步骤4:选择形成对应的染色体
    for i=1:pop
        midVal=rand();
        %选择复制染色体;
        if midVal<finalFit(1)
            outchromes(i,:)=inchromes(chromeId(1),:);
        else
            for j=1:pop-1
                if midVal>=finalFit(j)&& midVal<finalFit(j+1)
                    outchromes(i,:)=inchromes(chromeId(j+1),:);
                    break;
                end
            end
        end
        
    end
end


%获取调度方案的最大完工时间
function finishTime=fitness(schedule)
    finishTime=max(schedule(:,5));
end



%绘制甘特图
function drawGant(schedule)
    rows=size(schedule,1);
    maxMachId=max(schedule(:,2));
    jobQty=max(schedule(:,1));
    mycolor=rand(jobQty,3);
    figure;
    xlabel("Time");
    ylabel("Machine");
    ylim([0 maxMachId+1]);
    for i=1:rows
        x=[schedule(i,4),schedule(i,5)];
        y=[schedule(i,2),schedule(i,2)];
        line(x,y,'lineWidth',10,'color',mycolor(schedule(i,1),:));
        procId=schedule(i,3);
        jobId=schedule(i,1);
        txt=[int2str(jobId) ' ' int2str(procId)];
        text(x(1),y(1),txt);
    end       
end



%将编码转换成具体的调度方案
function schedule=createSchedule(data,chrome)
    jobQty=size(data,1);
    machQty=size(data,2)/2;
    schedule=zeros(jobQty*machQty,5);
    %定义中间数组
    %用来存储对应工作的开始时间,所以初始化为0;
    jobCanStartTime=zeros(1,jobQty);
    %用来记忆当前工作进行到哪一个工序,初始就是第一个工序,所以初始化为1;
    jobProcessId=ones(1,jobQty);
    for i=1:jobQty*machQty
        %获取编码中的当前作业号,i位置的数字
        %从编码中取出一个job
        nowJobId=chrome(i);
        %找到这个job的执行到哪一个工序
        nowProcId=jobProcessId(nowJobId);
        %找到当前工作的Id
        nowMachId=data(nowJobId,2*nowProcId-1);
        nowProcTime=data(nowJobId,2*nowProcId);
        %判断是否该台机器已经运行,若未运行,则machSch为空数组
        machSch=schedule(schedule(:,2)==nowMachId,:);
        %jobCanST指当前工作可以开始的时间。
        jobCanST=jobCanStartTime(nowJobId);
        if size(machSch,1)==0  %即机器还未安排工作
            startTime=jobCanStartTime(nowJobId);
            endTime=startTime+nowProcTime;
        else  %如果设备已经安排了工作
            %按照开工时间进行升序排列
            machSch=sortrows(machSch,4);
            rows=size(machSch,1);
            %处理第一行已排作业,检查是否能将当前作业排到该作业之前
            done=0;
            if jobCanST<machSch(1,4)   %当前工作开始时间是否小于设备最小的运行时间。
                if machSch(1,4)-jobCanST>=nowProcTime
                    startTime=jobCanST;
                    endTime=startTime+nowProcTime;
                    done=1;
                end
            end
            if done==0
                for j=2:rows
                    if jobCanST<machSch(j,4)  %判断是否能够在中间,时间完成
                        if machSch(j,4)-max(jobCanST,machSch(j-1,5))>=nowProcTime
                            startTime=max(jobCanST,machSch(j-1,5));
                            endTime=startTime+nowProcTime;
                            done=1;
                            break;
                        end
                    end
                end
            end
            if done==0 %只能排到最后面
                startTime=max(jobCanST,machSch(rows,5));
                endTime=startTime+nowProcTime;
            end          
        end
        schedule(i,1)=nowJobId;
        schedule(i,2)=nowMachId;
        schedule(i,3)=nowProcId;
        schedule(i,4)=startTime;
        schedule(i,5)=endTime;
        jobProcessId(nowJobId)=jobProcessId(nowJobId)+1;
        jobCanStartTime(nowJobId)=endTime;
    end
end

%初始化工时设备数据
function data=initData()
    data=[  2  1  0  3  1  6  3  7  5  3  4  6;
            1  8  2  5  4 10  5 10  0 10  3  4;
            2  5  3  4  5  8  0  9  1  1  4  7;
            1  5  0  5  2  5  3  3  4  8  5  9;
            2  9  1  3  4  5  5  4  0  3  3  1;
            1  3  3  3  5  9  0 10  4  4  2  1];
    for i=1:size(data,2)
        if mod(i,2)==1
            data(:,i)=data(:,i)+1;
        end
    end
end      

%根据种群数量、作业数量、设备数量生成初始种群
function chromes=createChromes(jobQty,machQty,pop)
    chromes=zeros(pop,jobQty*machQty);
    for i=1:pop
        chromes(i,:)=createChrome(jobQty,machQty);
    end
end



%生成编码:传两个参数【1】 jobQty,【2】 machQty
function chrome=createChrome(jobQty,machQty)
    a=1:jobQty;
    chrome=a;
    for  i=2:machQty
        chrome=[chrome a];
    end
    %将编码乱序排列-生成作业的乱码
    b=randperm(jobQty*machQty);
    chrome=chrome(b);
end

posted @ 2024-05-10 10:16  林每天都要努力  阅读(309)  评论(0编辑  收藏  举报