半柔性车间生产调度(遗传算法)

需求背景

生产调度问题是典型的组合优化问题(NP-hard),有别于经典的流水线车间调度,实际中的场景往往是存在并行设备,在某个流水线工序中存在两个或以上可选择的加工机器,这种情况解空间更大、复杂度更高。解决这类问题有多种方法,遗传算法、粒子群算法、差分进化等,他们原理不同但也有相似之处,我们尽量一一探讨。
柔性车间调度也叫混合流水车间调度问题(Hybrid Flow Shop Scheduling Problem, HFSSP)是经典流水车间调度的推广。它综合了经典流水车间和并行机两种调度的特点。我们这里讨论的是半柔性的调度,区别在哪呢,柔性车间调度中每个工件的加工时间是不同的,而我面对的问题是对于相同粒度的加工单位其在某台设备上的加工时间是基本一致的,也就是说这个加工时间是跟着设备变化的,但同一个工序的不同设备加工时间可能相同也可能不同和设备自身情况有关。

问题描述

本文中考虑在一条流水线上进行生产。要解决的场景是给定数量的原材料,如何规划给定粒度的材料(实际中可能是指载具)在每台机器上的分配加工路线,使得整个流水线的加工效率最高、用时最短。
假设m个工件在包含n个阶段(工序)的流水线上进行加工。每个工件都要依次通过每个阶段。每个阶段至少有一台加工机器并且至少有一个阶段包含多台并行机器如下图示。(若每阶段有且仅有一台加工机器,则称为经典流水车间调度问题Flow Shop Scheduling Problem, FSSP)。


工序的并行设备

已知各设备的加工时间,优化目标是如何确定工件的加工顺序以及每阶段工件在机器上的分配情况,使得最大完工时间极小化。

假设条件

  • 工件可以在某工序的任意一台机器上进行加工但有严格的加工顺序
  • 同一工序中所有机器功能相同,机器时间存在差别
  • 任意时刻每个工件至多在一台机器上加工
  • 每台机器某时刻只能加工一个工件
  • 工件的加工过程不允许中断
  • 默认每条调度指令都被执行成功
  • 默认设备时间包含了设备加工所需时间、上下料时间、工件运送时间。

遗传算法

基本原理

遗传算法的原理网上资料很多,也比较容易理解,这里简要说下。本文着重讨论如何用它解决实际问题。

遗传算法是一种基于自然选择和群体遗传机理的搜索算法,它模拟了自然选择和自然遗传过程中的繁殖、杂交和突变现象.再利用遗传算法求解问题时,问题的每一个可能解都被编码成一个“染色体”,即个体,若干个个体构成了群体(所有可能解).在遗传算法开始时,总是随机的产生一些个体(即初始解),根据预定的目标函数对每一个个体进行评估,给出一个适应度值,基于此适应度值,选择一些个体用来产生下一代,选择操作体现了“适者生存”的原理,“好”的个体被用来产生下一代,“坏”的个体则被淘汰,然后选择出来的个体,经过交叉和变异算子进行再组合生成新的一代,这一代的个体由于继承了上一代的一些优良性状,因而在性能上要优于上一代,这样逐步朝着最优解的方向进化.因此,遗传算法可以看成是一个由可行解组成的群体初步进化的过程。
基本流程如下:


GA基本流程

比较关键的步骤是编码和适应度函数的定义,这直接决定了选择、交叉、变异的方法以及解码的方式。尤其是编码方式需要慎重考量,这个在一定程度上决定了后续的操作方式及复杂度。

实现过程

编码

编码这块网上博客、论文都有很多实现,不过没找到易于理解的高效的编码。根据自己的实际情况,我的编码方案如下(下文中的追溯单位等同于工件等同于最小调度单位):
[工序1-各个工件的设备号,工序2-各个工件的设备号,工序3-各个工件的设备号,...... ,工序n-各个工件的设备号]
上述编码中是由等长的n段组成,每段是一个工序,按实际加工顺序排列;每台设备有唯一的整数编号,每个工件对应一个设备编号,工件数m就是每段工序的编码长度,因此一条染色体的编码长度就是工序数p*工件数m。编码内容为设备编号。
假设我们有:

  • 4个工件{0:'#W-1',1:'#W-2',2:'#W-3',3:'#W-4'}
  • 5道工序{0:'#P-1',1:'#P-2',2:'#P-3',3:'#P-4',4:'#P-5'}
  • 共计12台设备{0:'#M-1-1a',1:'#M-2-2a',2:'#M-2-2b',3:'#M-3-3a',4:'#M-3-3b',5:'#M-3-3c',6:'#M-4-4a',7:'#M-4-4b',8:'#M-4-4c',9:'#M-4-4d',10:'#M-5-5a',11:'#M-5-5b'},其中各个工序的机器数为1,2,3,4,2。
    一条完整的染色体编码形如:
    [0.0, 0.0, 0.0, 0.0, 2.0, 2.0, 1.0, 2.0, 4.0, 3.0, 4.0, 5.0, 7.0, 7.0, 8.0, 9.0, 10.0, 11.0, 10.0, 10.0]

初始化

有了编码方案,后面就逐步实现遗传算法的各个环节即可,首先是初始化,这里采用随机初始化。基于python的实现如下(文末会有完整代码,如下代码段假设各个变量都已定义):

def init_population(self):
        print(self.population_number)
        for _ in range(self.population_number):#self.population_number为初始化种群数量
            g = Gene()
            chromosome = []
            for j in range(self.process_number):#工序数
                p_choice = copy.deepcopy(self.process_matrix[j])
                for k in range(self.workpiece_number):#工件数
                    p = random.choice(p_choice)
                    chromosome.append(p)
#                    if len(p_choice)>1: #如果希望避免有的设备没被选到加上如下代码,种群数比较大的话不需要
#                        p_choice.remove(p)
#                    else:
#                        p_choice = copy.deepcopy(self.process_matrix[j])

            g.chromosome = chromosome
#            print("chromosome:", g.chromosome)
            g.fitness = self.calculate_fitness(g)
            if g in self.genes:
                print(g)#重复的编码
                print('---------------')
            self.genes.add(g)

选择个体

从种群中随机选择一定数量的个体,然后选择适应度最高的,代码如下:

def select_gene(self, n: int = 100):
        if len(self.genes) <= n:
            best_gene = Gene(0)
            for g in self.genes:
                if g.fitness > best_gene.fitness:
                    best_gene = g
            return best_gene
        index_list = list(range(len(self.genes)))
        index_set = {index_list.pop(randint(0, len(index_list) - 1)) for _ in range(n)}
        best_gene = Gene(0)
        i = 0
        for gene in self.genes:
            if i in index_set:
                if best_gene.fitness < gene.fitness:
                    best_gene = gene
            i += 1
        return best_gene

交叉

得益于编码方式变异操作简单明了,因为编码中相同位置也就是相同工序,交叉的策略是随机选取一段编码进行互换,互换的内容也就是设备编号。

def gene_cross(self, g1: Gene, g2: Gene) -> tuple:
        chromosome_size = self.chromosome_size
 
        def gene_generate(father: Gene, mother: Gene) -> Gene:
            index_list = list(range(chromosome_size))
            p1 = randint(0, len(index_list) - 1)
            p2 = randint(0, len(index_list) - 1)
            start = min(p1, p2)
            end = max(p1, p2)
            prototypef = father.chromosome[start: end + 1]
            prototypem = mother.chromosome[start: end + 1]
            father.chromosome[start: end + 1] = prototypem
            mother.chromosome[start: end + 1] = prototypef
            child1 = Gene()
            child2 = Gene()
            child1.chromosome = father.chromosome
            child1.fitness = self.calculate_fitness(child1)#计算适应度,下文实现
            child2.chromosome = mother.chromosome
            child2.fitness = self.calculate_fitness(child2)
            return child1,child2
 
        return gene_generate(g1, g2)

变异

随机选取一个位置,随机选取该工序可选的设备号替代原设备号即可。实现如下:

def gene_mutation(self, g: Gene, n = 2) -> None:
        chromosomes = [g.chromosome[i:i+self.workpiece_number] for i in range(0,len(g.chromosome),self.workpiece_number)]
        for index,item in enumerate(chromosomes):
            position = randint(0, len(item) - 1)
            item[position] = random.choice(self.process_matrix[index])
        g.chromosome = list(itertools.chain.from_iterable(chromosomes))
        g.fitness = self.calculate_fitness(g)

适应度计算

适应度计算稍微有点复杂,需要计算每道工序每个工件的开始和结束时间,以最后一个工件最后一道工序的结束时间作为总时间。定义machine_td用于记录每台设备的加工结束时间。这部分不太好理解需要多想想。

def evaluate_gene(self, g: Gene) -> GeneEvaluation:
        evaluation = GeneEvaluation()
        machine_td = {};w_time_list = []
        chro_list = [g.chromosome[i:i+(self.workpiece_number)] for i in range(0,len(g.chromosome),self.workpiece_number)]
        ch_mat = np.array(chro_list).T
        for w in range(ch_mat.shape[0]):#追溯单位数目
            for p in range(ch_mat.shape[1]):#工序数
                if ch_mat[w][p] in machine_td:
                    evaluation.start_time[w][p] = machine_td[ch_mat[w][p]] if p==0 \
                    else max(machine_td[ch_mat[w][p-1]],machine_td[ch_mat[w][p]])
                    
                    evaluation.end_time[w][p] = machine_td[ch_mat[w][p]]+self.time_dict[ch_mat[w][p]] if p==0 \
                    else max(machine_td[ch_mat[w][p-1]],machine_td[ch_mat[w][p]]) +self.time_dict[ch_mat[w][p]]
                    
                    machine_td[ch_mat[w][p]] = machine_td[ch_mat[w][p]] + self.time_dict[ch_mat[w][p]] if p==0 \
                    else max(machine_td[ch_mat[w][p-1]],machine_td[ch_mat[w][p]]) +self.time_dict[ch_mat[w][p]]
               
                    
                if not ch_mat[w][p] in machine_td:
                    evaluation.start_time[w][p] = 0 if p==0 else machine_td[ch_mat[w][p-1]]
                    evaluation.end_time[w][p] = self.time_dict[ch_mat[w][p]] if p==0 \
                    else machine_td[ch_mat[w][p-1]]+self.time_dict[ch_mat[w][p]]
                    
                    machine_td[ch_mat[w][p]] = self.time_dict[ch_mat[w][p]] if p==0 \
                    else machine_td[ch_mat[w][p-1]]+self.time_dict[ch_mat[w][p]]
                    
                evaluation.machine[w][p] = ch_mat[w][p]
                    
            w_time_list.append(machine_td[ch_mat[w][p]])
        evaluation.fulfill_time = max(w_time_list)
# 计算适应度
    def calculate_fitness(self, g: Gene) -> float:
        return 1 / self.evaluate_gene(g).fulfill_time

完整过程:

single_ga_new.py

# -*- coding: utf-8 -*-
"""
Created on 2019/9/18 4:00 PM 
# 918 勿忘国耻
@author: xuanlei
"""

from random import (randint)
from typing import (List, Tuple, Set, Dict, Any)
from collections import namedtuple
import random
import numpy as np
import itertools
import copy
import pprint
from tqdm import tqdm
MATRIX_SIZE = 5000
 
 
# 个体对象,染色体和适应度
class Gene(object):
    def __init__(self, fitness: float = 0, chromosome = None):
        self.fitness = fitness
        self.chromosome: list = chromosome
 
    def __eq__(self, other):
        if isinstance(other, Gene):
            return other.fitness == self.fitness and other.chromosome == self.chromosome
        return False
 
    def __hash__(self):
        return hash("".join(map(lambda x: str(x), self.chromosome)))
 
    def __str__(self):
        return "{} => {}".format(self.chromosome, self.fitness)
 
 
# 存储解码结果
class GeneEvaluation:
    def __init__(self):
        self.fulfill_time = 0
        self.machine = np.empty([MATRIX_SIZE,MATRIX_SIZE],dtype=float)#[[0 for _ in range(MATRIX_SIZE)] for _ in range(MATRIX_SIZE)]
        self.end_time = np.empty([MATRIX_SIZE,MATRIX_SIZE],dtype=float)#[[0 for _ in range(MATRIX_SIZE)] for _ in range(MATRIX_SIZE)]
        self.start_time = np.empty([MATRIX_SIZE,MATRIX_SIZE],dtype=float)#
 
 
# 遗传算法实现
class GA:
    def __init__(self, population_number = 1000000, times = 1000, cross_probability = 0.85,
                 mutation_probability = 0.05, workpiece_number = 0):
        self.population_number = population_number  # 种群数量
        self.times = times  # 遗传代数
        self.cross_probability = cross_probability  # 交叉概率
        self.mutation_probability = mutation_probability  # 突变概率
 
        self.workpiece_number = workpiece_number  # 工件数量
        self.process_number: int = 0  # 工序数量
        self.chromosome_size: int = 0  # 染色体长度
 
        self.machine_matrix = np.empty([MATRIX_SIZE,MATRIX_SIZE],dtype=float)#[[-1 for _ in range(MATRIX_SIZE)] for _ in range(MATRIX_SIZE)]
        self.time_dict = {}
        self.process_matrix = {}#dict
 
        self.genes: Set[Gene] = set()
 
    # 评估染色体
    def evaluate_gene(self, g: Gene) -> GeneEvaluation:
        evaluation = GeneEvaluation()
        machine_td = {};w_time_list = []
        chro_list = [g.chromosome[i:i+(self.workpiece_number)] for i in range(0,len(g.chromosome),self.workpiece_number)]
        ch_mat = np.array(chro_list).T
        for w in range(ch_mat.shape[0]):#追溯单位数目
            for p in range(ch_mat.shape[1]):#工序数
                
                if ch_mat[w][p] in machine_td:
                    evaluation.start_time[w][p] = machine_td[ch_mat[w][p]] if p==0 \
                    else max(machine_td[ch_mat[w][p-1]],machine_td[ch_mat[w][p]])
                    
                    evaluation.end_time[w][p] = machine_td[ch_mat[w][p]]+self.time_dict[ch_mat[w][p]] if p==0 \
                    else max(machine_td[ch_mat[w][p-1]],machine_td[ch_mat[w][p]]) +self.time_dict[ch_mat[w][p]]
                    
                    machine_td[ch_mat[w][p]] = machine_td[ch_mat[w][p]] + self.time_dict[ch_mat[w][p]] if p==0 \
                    else max(machine_td[ch_mat[w][p-1]],machine_td[ch_mat[w][p]]) +self.time_dict[ch_mat[w][p]]
               
                    
                if not ch_mat[w][p] in machine_td:
                    evaluation.start_time[w][p] = 0 if p==0 else machine_td[ch_mat[w][p-1]]
                    evaluation.end_time[w][p] = self.time_dict[ch_mat[w][p]] if p==0 \
                    else machine_td[ch_mat[w][p-1]]+self.time_dict[ch_mat[w][p]]
                    
                    machine_td[ch_mat[w][p]] = self.time_dict[ch_mat[w][p]] if p==0 \
                    else machine_td[ch_mat[w][p-1]]+self.time_dict[ch_mat[w][p]]
                    
                evaluation.machine[w][p] = ch_mat[w][p]
                    
            w_time_list.append(machine_td[ch_mat[w][p]])
        evaluation.fulfill_time = max(w_time_list)
        return evaluation
 
    # 计算适应度
    def calculate_fitness(self, g: Gene) -> float:
        return 1 / self.evaluate_gene(g).fulfill_time
 
    # 个体交叉
    def gene_cross(self, g1: Gene, g2: Gene) -> tuple:
        chromosome_size = self.chromosome_size
 
        def gene_generate(father: Gene, mother: Gene) -> Gene:
            index_list = list(range(chromosome_size))
            p1 = randint(0, len(index_list) - 1)
            p2 = randint(0, len(index_list) - 1)
            start = min(p1, p2)
            end = max(p1, p2)
            prototypef = father.chromosome[start: end + 1]
            prototypem = mother.chromosome[start: end + 1]
            father.chromosome[start: end + 1] = prototypem
            mother.chromosome[start: end + 1] = prototypef
            child1 = Gene()
            child2 = Gene()
            child1.chromosome = father.chromosome
            child1.fitness = self.calculate_fitness(child1)
            child2.chromosome = mother.chromosome
            child2.fitness = self.calculate_fitness(child2)
            return child1,child2
 
        return gene_generate(g1, g2)
 
    # 突变
    def gene_mutation(self, g: Gene, n = 2) -> None:
        chromosomes = [g.chromosome[i:i+self.workpiece_number] for i in range(0,len(g.chromosome),self.workpiece_number)]
        for index,item in enumerate(chromosomes):
            position = randint(0, len(item) - 1)
            item[position] = random.choice(self.process_matrix[index])
        g.chromosome = list(itertools.chain.from_iterable(chromosomes))
#        print(g.chromosome)
        g.fitness = self.calculate_fitness(g)
 

    
    def init_population(self):
        print(self.population_number)
        for _ in range(self.population_number):
            g = Gene()
            chromosome = []
            for j in range(self.process_number):#工序
                p_choice = copy.deepcopy(self.process_matrix[j])
                for k in range(self.workpiece_number):
                    p = random.choice(p_choice)
                    chromosome.append(p)
#                    if len(p_choice)>1:
#                        p_choice.remove(p)
#                    else:
#                        p_choice = copy.deepcopy(self.process_matrix[j])

            g.chromosome = chromosome
#            print("chromosome:", g.chromosome)
            g.fitness = self.calculate_fitness(g)
            if g in self.genes:
                print(g)
                print('---------------')
            self.genes.add(g)
#            print(g)
#        print(i)
        print(len(self.genes))
    
    # 选择个体
    def select_gene(self, n: int = 100):
        if len(self.genes) <= n:
            best_gene = Gene(0)
            for g in self.genes:
                if g.fitness > best_gene.fitness:
                    best_gene = g
            return best_gene
        index_list = list(range(len(self.genes)))
        index_set = {index_list.pop(randint(0, len(index_list) - 1)) for _ in range(n)}
        best_gene = Gene(0)
        i = 0
        for gene in self.genes:
            if i in index_set:
                if best_gene.fitness < gene.fitness:
                    best_gene = gene
            i += 1
        return best_gene
 
    # 遗传算法
    def exec(self, parameter: List[List[Tuple]]) -> GeneEvaluation:
        workpiece_size = len(parameter)
        self.process_number = len(parameter[0])
        for i in range(len(parameter)):
            self.time_dict.update(dict(parameter[i]))
        for i in range(workpiece_size):
            self.chromosome_size += len(parameter[i])#染色体长度=工件数*工序数
            for j in range(self.process_number):
                self.machine_matrix[i][j] = parameter[i][j][0]
#                self.time_matrix[i][j] = parameter[i][j][1]
                '''
                4行5列 machine_matrix记录每个追溯单位(行)每道工序(列)的机器编号
                       time_matrix记录每个追溯单位(行)每道工序(列)对应机器完成该工序所需时间
                '''
 
        for i in range(workpiece_size):#工序和机器号的key value
            for j in range(self.process_number):
                if self.machine_matrix[i][j] != -1:
                    self.process_matrix.setdefault(j,[]).append(self.machine_matrix[i][j])
                    self.process_matrix[j] = list(set(self.process_matrix[j]))
 
        self.init_population()
        print(len(self.genes))
        for _ in tqdm(range(self.times)):
            probability = randint(1, 100) / 100
            if probability < self.mutation_probability:
                index = randint(0, len(self.genes))#22
                i = 0
                for gene in self.genes:
                    if i == index:
                        self.gene_mutation(gene)
                        break
                    i += 1
            else:
                g1, g2 = self.select_gene(), self.select_gene()
                children = self.gene_cross(g1, g2)
                self.genes.update({*children})
                
        best_gene = Gene(0)
        print('=====================...>>>')
        print(len(self.genes))
        print('=====================...<<<')
        for gene in self.genes:
            if best_gene.fitness < gene.fitness:
                best_gene = gene
        return self.evaluate_gene(best_gene)
 
 
ResultData = namedtuple("ResultData", ["fulfill_time", "row_data"])
 
 
# 输出结果
def schedule() -> ResultData:
#    print(data)
#    reshape = reshape_data(data)#ReshapeData(ans, workpieces, machines, process, reverse_machines, reverse_workpieces)
    parameter =  [[(0, 10), (1, 15), (3, 9), (6, 35), (10, 8)],
                  [(0, 10), (2, 18), (4, 4), (7, 47), (11, 5)],
                  [(0, 10), (2, 18), (5, 8), (8, 49), (10, 8)],
                  [(0, 10), (1, 15), (5, 8), (9, 40), (11, 5)]]#4个工件
    '''
    [[(2, 21), (4, 21)],
     [(4, 10), (0, 21), (3, 12)],
     [(1, 5)],
     [(4, 10), (1, 11), (0, 5)]]
    每个工件对应的加工设备编号和所需时间,并按照工序优先级排序
    '''
#    print(parameter)
    n = 4#工件数目
    ga = GA(workpiece_number = n)
    result = ga.exec(parameter)
    p = ga.process_number
#    print(n,p)
    row_data = []
    res_w = {0:'#W-1',1:'#W-2',2:'#W-3',3:'#W-4'}
    res_m = {0:'#M-1-1a',1:'#M-2-2a',2:'#M-2-2b',3:'#M-3-3a',4:'#M-3-3b',5:'#M-3-3c',
             6:'#M-4-4a',7:'#M-4-4b',8:'#M-4-4c',9:'#M-4-4d',10:'#M-5-5a',11:'#M-5-5b'}
    res_p = {0:'#P-1',1:'#P-2',2:'#P-3',3:'#P-4',4:'#P-5'}
    for i in range(n):
        for j in range(p):
            temp = {
                "workpiece": res_w[i],
                "process": res_p[j],
                "machine": res_m[result.machine[i][j]],
                "startTime": result.start_time[i][j],
                "endTime": result.end_time[i][j]
            }
            # print(i, j, machine_matrix[i][j], result.start_time[i][j], result.end_time[i][j])
            row_data.append(temp)
 

    return ResultData(result.fulfill_time, row_data)
 
 
if __name__ == "__main__":
    pprint.pprint(schedule().row_data)

运行结果如下,因为这里测试的参数比较小只有4个工件12台设备,所以设置的初始种群和迭代次数非常大,目的是找出全局最优解(运行多次结果基本一致,确实是全局最优了),实际中的解空间往往非常大,需要折中选择初始种群规模和迭代次数。

[{'endTime': 10.0,
  'machine': '#M-1-1a',
  'process': '#P-1',
  'startTime': 0.0,
  'workpiece': '#W-1'},
 {'endTime': 28.0,
  'machine': '#M-2-2b',
  'process': '#P-2',
  'startTime': 10.0,
  'workpiece': '#W-1'},
 {'endTime': 32.0,
  'machine': '#M-3-3b',
  'process': '#P-3',
  'startTime': 28.0,
  'workpiece': '#W-1'},
 {'endTime': 81.0,
  'machine': '#M-4-4c',
  'process': '#P-4',
  'startTime': 32.0,
  'workpiece': '#W-1'},
 {'endTime': 89.0,
  'machine': '#M-5-5a',
  'process': '#P-5',
  'startTime': 81.0,
  'workpiece': '#W-1'},
 {'endTime': 20.0,
  'machine': '#M-1-1a',
  'process': '#P-1',
  'startTime': 10.0,
  'workpiece': '#W-2'},
 {'endTime': 35.0,
  'machine': '#M-2-2a',
  'process': '#P-2',
  'startTime': 20.0,
  'workpiece': '#W-2'},
 {'endTime': 43.0,
  'machine': '#M-3-3c',
  'process': '#P-3',
  'startTime': 35.0,
  'workpiece': '#W-2'},
 {'endTime': 90.0,
  'machine': '#M-4-4b',
  'process': '#P-4',
  'startTime': 43.0,
  'workpiece': '#W-2'},
 {'endTime': 95.0,
  'machine': '#M-5-5b',
  'process': '#P-5',
  'startTime': 90.0,
  'workpiece': '#W-2'},
 {'endTime': 30.0,
  'machine': '#M-1-1a',
  'process': '#P-1',
  'startTime': 20.0,
  'workpiece': '#W-3'},
 {'endTime': 48.0,
  'machine': '#M-2-2b',
  'process': '#P-2',
  'startTime': 30.0,
  'workpiece': '#W-3'},
 {'endTime': 52.0,
  'machine': '#M-3-3b',
  'process': '#P-3',
  'startTime': 48.0,
  'workpiece': '#W-3'},
 {'endTime': 92.0,
  'machine': '#M-4-4d',
  'process': '#P-4',
  'startTime': 52.0,
  'workpiece': '#W-3'},
 {'endTime': 100.0,
  'machine': '#M-5-5a',
  'process': '#P-5',
  'startTime': 92.0,
  'workpiece': '#W-3'},
 {'endTime': 40.0,
  'machine': '#M-1-1a',
  'process': '#P-1',
  'startTime': 30.0,
  'workpiece': '#W-4'},
 {'endTime': 55.0,
  'machine': '#M-2-2a',
  'process': '#P-2',
  'startTime': 40.0,
  'workpiece': '#W-4'},
 {'endTime': 59.0,
  'machine': '#M-3-3b',
  'process': '#P-3',
  'startTime': 55.0,
  'workpiece': '#W-4'},
 {'endTime': 94.0,
  'machine': '#M-4-4a',
  'process': '#P-4',
  'startTime': 59.0,
  'workpiece': '#W-4'},
 {'endTime': 100.0,
  'machine': '#M-5-5b',
  'process': '#P-5',
  'startTime': 95.0,
  'workpiece': '#W-4'}]

绘制甘特图

上述结果不够简洁明了,可以绘制甘特图更加形象地展示调度结果。代码如下:

# -*- coding: utf-8 -*-
"""
Created on 2019/9/18 5:10 PM 
# 918 勿忘国耻
@author: xuanlei
"""   
import matplotlib.pyplot as plt
import numpy as np
from single_ga_new import schedule

plt.figure(figsize=(15,8))
ax=plt.gca()
[ax.spines[i].set_visible(False) for i in ["top","right"]]
from pylab import mpl
mpl.rcParams['font.sans-serif'] = ['FangSong']
mpl.rcParams['axes.unicode_minus'] = False



def gatt(m,t):
    """甘特图
    m机器集
    t时间集
    """
    tt = [x[1]-x[0] for x in t]
    for j in range(len(m)):#工序j
        i=num_machine[m[j][0]]#机器编号i
        sn = '追溯单位-'+str(m[j][1][1:])
#        if j==0:
#            plt.barh(i,tt[j],height=1.2,align='center')
#            plt.text(tt[j]/2,i,'%s\nT:%s'%((sn),tt[j]),color="white",verticalalignment="center",horizontalalignment="center",size=8)
#        else:
        plt.barh(i,tt[j],left=(t[j][0]),height=0.8,align='center')
        plt.text(t[j][0]+tt[j]/2,i,'%s\nT-%s'%((sn),str(tt[j])+'('+str(t[j])+')'),color="black",verticalalignment="center",horizontalalignment="center",size=8)


def make_reverse_index(arr: list) -> dict:
    result = {}
    for i in range(len(arr)):
        result[arr[i]] = i
    return result

if __name__=="__main__":
    """测试代码"""
#    m=np.random.randint(1,7,35)#reshape.machine#
#    t=np.random.randint(15,45,35)
    
    tt = schedule().row_data
    mms = ['#M-1-1a','#M-2-2a','#M-2-2b','#M-3-3a','#M-3-3b','#M-3-3c',
             '#M-4-4a','#M-4-4b','#M-4-4c','#M-4-4d','#M-5-5a','#M-5-5b']
    mm = dict(zip(range(len(mms)),mms))
    plot_result = [[item['machine'],item['workpiece'],item['process'],item['startTime'],item['endTime']] for item in tt]
    plot_result2 = [value for index, value in sorted(enumerate(plot_result), key=lambda plot_result:plot_result[1])]
#    mm.sort(reverse=True)
    num_machine = make_reverse_index(mm)
    m = [[x[0],x[1]] for x in plot_result2]
    t = [[x[-2],x[-1]] for x in plot_result2]
    
    gatt(m,t)
#    time =t
#    plt.xticks(np.arange(len(time)), time)
    plt.yticks(np.arange(len(num_machine)),list(num_machine))
    #plt.grid()
    plt.savefig("gant.png",dpi=250)
    

如下图:
这个图并非是上面结果的绘制,是上述参数下多次运行的结果之一,基本近似。


调度结果

由上图可以看出,有个设备没用上却达到了最优状态,说明该工序下的设备是冗余的,可以考虑减除;另外工序1只有一台设备显然成为产能的瓶颈应该增加设备。

小结

上面就是用遗传算法解决针对批量加工标准工件、设备时间相对固定且存在并行设备的半柔性生产调度问题的过程。通过调度算法运行模拟不仅对工件的生产加工进行合理规划最大限度地接近理论产能,同时也可以推算出产能瓶颈从而优化各个工序间的设备配比。对实际上产来说有很大意义。

参考

https://blog.csdn.net/daydream13580130043/article/details/95927841

https://zhuanlan.zhihu.com/p/47921580

你可能感兴趣的:(半柔性车间生产调度(遗传算法))