数字化车间智能排产调度挑战赛(二)—— 启发式规则

科大讯飞–数字化车间智能排产调度挑战赛

本系列文章用于记录比赛中模型构建,算法设计,仅用于记录与学习。
系列文章将分为一下几个部分

  1. 分析问题,建立数学模型构建,并基于求解器验证
  2. 设计启发式规则求解车间调度问题
  3. 关键路径+VNS的混合算法求解车间调度问题

这三个部分也是我在解决这个问题过程中,求解方法的一个进化过程。第一次接触车间调度这类问题,涉及的内容也不会很深。下面就开始我们的第一部分内容:分析问题,构建数学模型,小规模样例验证。

启发式求解

  由于求解器无法求解大规模的车间调度问题,本题中共40个产品,平均每种产品15个工序,36台机器,求解器无法在有效时间内得到可行解。在设计智能算法之前,想着先设计一种基于优先规则的启发式算法看看求解效果,后续也可以将该算法的求解结果作为初始解进一步求解。
这里我们使用的优先规则是机器最早开始时间+产品优先级。

  1. 机器最早开始时间,是指以机器角度出发寻找可加工的产品工序。只要有机器空闲就去寻找当前最早可加工的产品及其对应的工序,这样做的目的是使得机器尽可能少的空闲;
  2. 产品优先级,给不同产品设置优先级是因为同一台机器可对应多种产品的不同工序,方便机器在选择产品时有一定的优先顺序。
    为什么要引入产品优先级。对数据进行分析以及多次实验后发现,产品生产的先后顺序对最早完工时间有一定的影响。后续实验中将通过实验证明。

1. 说明

这里需要说明几点:

  1. 在赛题中要求考虑工序B的准备时间,但在我们的启发式规则中先不考虑这一点(在这里影响不大);
  2. 工序B结束后必须立即开始工序C。由于我们是以机器的最早开工时间规则出发,可能存在工序B结束后工序C所对应的机器不处于空闲,而违反了这条约束。因此,在确定工序B的实际开工时间时,要同时考虑上一工序的结束时间、机器的最早开工时间、工序C对应机器的最早开工时间。(工序C只有一台机器加工)

t i = m a x ( 前 序 工 序 完 工 时 间 , 机 器 最 早 开 工 时 间 , 工 序 C 最 早 开 工 时 间 ) t_i = max(前序工序完工时间,机器最早开工时间,工序C最早开工时间) ti=max(C)

  1. 与上一篇一样,将每道工序都看作一个任务,因为工序D是按节生产所有一个工序D又可分为多个任务。如下表所示
  2. 由于工序A只有两台机器,又每个产品只有一个工序A且都是第一工序,因此可以首先将工序A对应的任务首先进行加工
indexroute_idroute_Nonameequ_typeproduct_idwork_time
0A11工序AT-01P202201101240
1A12工序BT-03P202201101480
2A13工序CT-02P20220110196
3A14工序DT-05P202201101480
4A14工序DT-05P202201101480
5A14工序DT-05P202201101480
6A14工序DT-05P202201101480
7A14工序DT-05P202201101480
8A14工序DT-05P202201101480
9A14工序DT-05P202201101480
10A14工序DT-05P202201101480
11A15工序BT-03P202201101300
24A113工序DT-05P202201101480
25A113工序DT-05P202201101480
26A113工序DT-05P202201101480
27B11工序AT-01P202201102240
28B12工序BT-03P202201102200
29B13工序CT-02P20220110248

2. 求解步骤

在求解之前,要准备一些属性记录任务与机器的状态。

  1. 机器的空闲开始时间。记录机器的最近一次的完工时间
  2. 任务的最早可开工时间。即前序工序的完工时间
  3. 任务状态。分为已完工、可开工、不可开工三种,主要用于机器寻找可加工任务
  4. 产品优先级。不同产品之间设计先后顺序,用于当机器对应多个可开工的任务时,选择优先级高的优先进行加工

求解步骤

步骤一:根据一定的规则(产品总工作时长、工序B的最早开工时间等)获得产品的优先级;
步骤二:初始化任务的初始状态,除了每个产品的工序A为可开工状态其余皆为不可开工状态;
步骤三:根据优先级对工序A对应的任务进行加工,并更新任务的状态及紧后工序的状态;
步骤四:对机器的空闲时间进行排序,取最早可开工机器k;
步骤五:根据机器k的空闲开始时间以及任务状态检索任务,存储为任务列表R;
步骤六:判断任务列表R是否为空,是则k=k+1,返回步骤五,否则进行下一步;
步骤七:根据任务的最早可加工时间进行排序,选择最早开始的任务进行加工,更新机器状态、任务状态及后续的工序状态;
    1. 确定任务的开工时间及结束时间
    2. 更新机器的释放时间
    3. 更新当前任务的状态、开工时间、完工时间
    4. 更新当前任务后续节点的最早开工时间,若当前任务为产品的最后一个工序则无须更新
步骤八:判断所有任务是否均已完工,是则结束,否则返回步骤四。

3. 结果对比
对于产品的优先级计算通过对数据进行观察,最终设计了两种规则,一个是产品总工时越长优先级越高;另一个是产品第二道工序越早开工优先级越高。
① 产品总工时越长优先级越高
② 产品第二道工序越早开工优先级越高
③ 随机赋予产品优先级(10次中取最好)

策略①+②②+①
总时长1543415272152041493615484
评分74.1774.9675.3076.6573.93

结果表明,先对产品进行优先排序对最终的总时长有一定的影响。

4. 代码
数据读取代码:此部分代码于上一篇的一样

import itertools

import pandas as pd
import numpy as np
import math


def result_process(product_info, route_info):
    # 将产品信息划分为多个子任务
    result = []
    for i in range(len(product_info)):
        route_id = product_info.iloc[i, 2]
        route_temp = route_info[route_info['route_id'] == route_id].reset_index(drop=True)
        route_temp['product_id'] = product_info.iloc[i, 0]
        route_temp['work_time'] = 0
        for j in range(len(route_temp)):
            if route_temp.iloc[j, 4][-1] == 'h':
                if route_temp.iloc[j, 2] == "工序C":
                    route_temp.loc[j, 'work_time'] = float(route_temp.iloc[j, 4][:-1]) * product_info.iloc[i, 1] * 60
                else:
                    route_temp.loc[j, 'work_time'] = float(route_temp.iloc[j, 4][:-1]) * 60
            else:
                route_temp.loc[j, 'work_time'] = float(route_temp.iloc[j, 4][:3])
                route_temp.loc[j, 'ready_time'] = float(route_temp.iloc[j, 4][4:7])
        route_temp = route_temp.fillna(0)

        route_D = route_temp[route_temp['name'] == '工序D']
        for j in range(product_info.iloc[i, 1] - 1):
            route_temp = pd.concat([route_temp, route_D], axis=0)
        if i == 0:
            result = route_temp
        else:
            result = pd.concat([result, route_temp], axis=0)
    result = result.sort_values(['product_id', 'route_No']).reset_index(drop=True)

    result = result.sort_values(['product_id', 'route_No']).reset_index(drop=True).reset_index()
    return result


class Data:
    def __init__(self):
        self.order = []
        self.equ_info = []
        self.order_num = 0
        self.pro_route = []
        self.BC_order = []
        self.equ_order = []
        self.order_equ = []
        self.conf_ = []
        self.BB_order = []
        self.equ_num = 0
        self.order_order = []
        self.ready_B = []

    def pro_order(self,result):
        # 获取每个任务的紧前任务
        pro_route = {1: [0]}
        BC = []
        for i in range(2, len(result)):
            if result.loc[i, 'product_id'] == result.loc[i - 1, 'product_id']:
                if result.loc[i, 'route_No'] == result.loc[i - 1, 'route_No'] + 1:
                    temp_pro = []
                    count = 1
                    for j in range(i, 0, -1):
                        if result.loc[i, 'route_No'] == result.loc[i - count, 'route_No'] + 1:
                            temp_pro.append(i - count)
                            count += 1
                        else:
                            break
                    pro_route[i] = temp_pro
                else:
                    pro_route[i] = pro_route[i - 1]
            # 获取B、C的组合
            if result.loc[i, 'name'] == '工序C' and result.loc[i - 1, 'name'] == '工序B':
                BC.append([i - 1, i])
        self.pro_route = pro_route
        self.BC_order = BC

    def conf_equ_order(self, result, equ_info):
        # 构建任务与设备的资质矩阵、每个设备的子任务集合、每个子任务的设备集合
        conf_ = np.zeros((len(result), len(equ_info)))
        equ_order = {}
        for j in range(len(equ_info)):
            order = []
            for i in range(len(result)):
                if result.loc[i, 'equ_type'] == equ_info.loc[j, 'equ_type']:
                    conf_[i][j] = 1
                    order.append(i)
            equ_order[j] = order
        var_start = np.zeros(len(equ_info))
        var_start[0] = 1
        var_start[1] = 1
        conf_ = np.insert(conf_, len(conf_), np.ones(len(equ_info)), axis=0)
        self.conf_ = conf_
        self.equ_order = equ_order
        order_equ = {}
        order_order = {}
        for i in range(len(result)):
            order_equ[i] = np.where(conf_[i][:] == 1)[0]
            order_order[i] = equ_order[order_equ[i][0]]
        self.order_equ = order_equ
        self.order_order = order_order

    def BB_index(self, result, product_info):
        BB_order = {}
        for i in range(len(product_info)):
            p_id = product_info.loc[i, 'product_id']
            df = result[(result['product_id'] == p_id) & (result['name'] == '工序B')]
            df = df.reset_index(drop=True)
            self.ready_B.append(df.loc[0, 'index'])
            for j in range(1, len(df)):
                ind = df.loc[j, 'index']
                equ_type = df.loc[j, 'equ_type']
                df1 = df[df['equ_type'] == equ_type]
                arr = np.array(df1['index'])
                arr = arr[arr < ind]
                if len(arr) > 0:
                    BB_order[ind] = arr
                else:
                    self.ready_B.append(df.loc[j, 'index'])
        self.BB_order = BB_order

    def readData(self, product_info):
        route_info = pd.read_csv('工艺路线.csv', encoding="gbk")
        equ_info = pd.read_csv('设备信息.csv', encoding="gbk")
        self.equ_info = equ_info

        # 0表示D分批,1表示D不分批
        result = result_process(product_info, route_info)
        # 获取每个任务的紧前任务
        self.pro_order(result)
        # # 构建任务与设备的资质矩阵、每个设备的子任务集合
        self.conf_equ_order(result, equ_info)
        # 同一个产品的BB工序序号
        self.BB_index(result, product_info)
        self.order_num = len(result)
        self.order = result
        self.equ_num = len(equ_info)

启发式代码

import pandas as pd
from Data import Data
import numpy as np
import time
from sklearn.utils import shuffle


"""初始化任务的状态"""
def init_(data):
    flag = [1]
    early_start = [0]
    for i in range(1, len(data)):
        # 每个产品的第一道工序状态设置为“可开工”,其余设置为“不可开工”
        if data.loc[i, 'product_id'] == data.loc[i - 1, 'product_id']:
            flag.append(0)
            early_start.append(-1)
        else:
            flag.append(1)
            early_start.append(0)
    data['flag'] = np.array(flag)
    data['early_start'] = np.array(early_start)
    data['end'] = -1
    data['equ_name'] = ""
    return data


"""产品优先级"""
def priority_(res, flag):
    product_id = np.unique(np.array(res['product_id']))
    total_time = {}
    gx2_wt = []
    index = []
    # 记录产品的总加工时长,及第二道工序的最早开工时间
    for p in product_id:
        df = res[res['product_id'] == p]
        total_time[p] = sum(df['work_time'])
        index.append(df.iloc[0, 0])
        gx2_wt.append(df.iloc[1, 9])
    temp = pd.DataFrame.from_dict(total_time, orient='index')
    temp['id'] = index
    temp['gx2_wt'] = gx2_wt
    """
    flag = 1,只对总时长进行排序
    flag = 2,只对工序B进行排序
    flag = 3,先对总时长进行排序,后对工序B进行排序
    flag = 4,先对工序B进行排序,再对总时长进行排序
    flag = 5,随机赋予优先级
    """
    if flag == 1:
        temp = temp.sort_values(by=[0], ascending=False).reset_index()
    elif flag == 2:
        temp = temp.sort_values(by='gx2_wt').reset_index()
    elif flag == 3:
        temp = temp.sort_values(by='gx2_wt').sort_values(by=[0], ascending=False).reset_index()
    elif flag == 4:
        temp = temp.sort_values(by=[0], ascending=False).sort_values(by='gx2_wt').reset_index()
    else:
        temp = shuffle(temp).reset_index()
    return temp


"""根据优先级对工序A对应的任务进行加工,并更新任务的状态及紧后工序的状态"""
def gx_A(res, pri, equ_index):
    m0 = m1 = 0
    m0_name = 'Z-1001'
    m1_name = 'Z-1002'
    # 根据工序2的优先级给产品中的工序A排序
    for p in range(len(pri)):
        ind = pri.loc[p, 'id']
        if m0 <= m1:
            m0 = m0 + res.loc[ind, 'work_time']
            # 更新当前任务的状态信息
            res.loc[ind, 'flag'] = 2
            res.loc[ind, 'end'] = m0
            res.loc[ind, 'equ_name'] = m0_name
            # 更新紧后工序的状态信息
            res.loc[ind + 1, 'flag'] = 1
            res.loc[ind + 1, 'early_start'] = m0
            equ_index[m0_name].append(ind)
        else:
            m1 = m1 + res.loc[ind, 'work_time']
            res.loc[ind, 'flag'] = 2
            res.loc[ind, 'end'] = m1
            res.loc[ind, 'equ_name'] = m1_name
            res.loc[ind + 1, 'flag'] = 1
            res.loc[ind + 1, 'early_start'] = m1
            equ_index[m1_name].append(ind)
    return res, equ_index


"""启发式规则:机器最早开工时间"""
def machine_early_start_time(res, equ_release_time, equ_index):
    equ_num = len(equ_release_time)
    res_num = len(res)
    # 根据设备的最早开始时间进行车间调度
    while True:
        # 遍历设备,选择最早开始加工的设备
        equ_release_time = equ_release_time.sort_values(by=['release_time'])
        for e in range(equ_num):
            equ_name = equ_release_time.index[e]
            if equ_name == 'Y-2045':
                continue
            equ_type = equ_release_time.iloc[e, 0]
            es = equ_release_time.iloc[e, 1]
            # 筛选出可加工工序
            df_pro = res[(res['equ_type'] == equ_type) & (res['flag'] == 1)]
            # 选择优先级最高的工序进行加工
            if len(df_pro) > 0:
                # 对任务的最早开加工时间进行排序,选择最早开始的任务
                df = df_pro.sort_values(by=['early_start'])
                ind = df.index[0]
                equ_index[equ_name].append(ind)
                # 计算释放时间
                release_t = es + df.iloc[0, 9] if es >= df.iloc[0, 11] else df.iloc[0, 9] + df.iloc[0, 11]
                # 判断是否在最后一个工序
                if ind != res_num - 1:
                    # 判断前后两工序是否是同一个产品的工序
                    if res.loc[ind, 'product_id'] == res.loc[ind + 1, 'product_id']:
                        # 判断当前工序是否为工序B
                        if df.iloc[0, 3] == '工序B':
                            # 如果是工序B,释放时间要与工序C的设备有关
                            es_y = equ_release_time.loc['Y-2045', 'release_time']
                            if es_y > release_t:
                                release_t = es_y
                            equ_release_time.loc[equ_name, 'release_time'] = release_t
                            # 更新工序信息
                            res.loc[ind, 'flag'] = 2
                            res.loc[ind, 'end'] = release_t
                            res.loc[ind, 'equ_name'] = equ_name
                            # 更新工序C的信息
                            equ_index['Y-2045'].append(ind + 1)
                            res.loc[ind + 1, 'early_start'] = release_t
                            release_t = release_t + res.loc[ind + 1, 'work_time']
                            res.loc[ind + 1, 'flag'] = 2
                            res.loc[ind + 1, 'end'] = release_t
                            res.loc[ind + 1, 'equ_name'] = 'Y-2045'
                            equ_release_time.loc['Y-2045', 'release_time'] = release_t
                            # 更新后续的工序
                            count = 2
                            while True:
                                res.loc[ind + count, 'flag'] = 1
                                res.loc[ind + count, 'early_start'] = release_t
                                count += 1
                                if ind + count >= res_num:
                                    break
                                if res.loc[ind + count, 'name'] != "工序D":
                                    break
                        else:
                            # 不是工序B则是工序D
                            # 更新设备的空闲开始时间
                            equ_release_time.loc[equ_name, 'release_time'] = release_t
                            # 更新工序信息
                            res.loc[ind, 'flag'] = 2
                            res.loc[ind, 'end'] = release_t
                            res.loc[ind, 'equ_name'] = equ_name
                            p_id = res.loc[ind, 'product_id']
                            next_route_no = res.loc[ind, 'route_No']
                            temp_df = res[(res['product_id'] == p_id) & (res['route_No'] == next_route_no)]
                            # 工序D比较特殊,只有当当前工序D对应的所有任务都完工,才更新紧后工序
                            if sum(np.array(temp_df['flag'])) >= len(temp_df) * 2:
                                n_df = res[(res['product_id'] == p_id) & (res['route_No'] == next_route_no + 1)]
                                if len(n_df) > 0:
                                    n_ind = n_df.index[0]
                                    res.loc[n_ind, 'flag'] = 1
                                    res.loc[n_ind, 'early_start'] = max(np.array(temp_df['end']))
                            break
                    else:
                        # 更新设备的空闲开始时间
                        equ_release_time.loc[equ_name, 'release_time'] = release_t
                        # 更新工序信息
                        res.loc[ind, 'flag'] = 2
                        res.loc[ind, 'end'] = release_t
                        res.loc[ind, 'equ_name'] = equ_name
                else:
                    # 更新设备的空闲开始时间
                    equ_release_time.loc[equ_name, 'release_time'] = release_t
                    # 更新工序信息
                    res.loc[ind, 'flag'] = 2
                    res.loc[ind, 'end'] = release_t
                    res.loc[ind, 'equ_name'] = equ_name
        if sum(np.array(res['flag'])) >= len(res) * 2:
            break
    return res, equ_release_time, equ_index


def heuri_():
    product = pd.read_csv('产品信息.csv')
    ori_data = Data()
    ori_data.readData(product)
    data = ori_data.order
    equ = pd.read_csv('设备信息.csv', encoding="gbk")
    equ_index = {}
    equ_release_time = {}
    equ_num = len(equ)
    for e in range(equ_num):
        equ_index[equ.iloc[e, 0]] = []
        equ_release_time[equ.iloc[e, 0]] = [equ.iloc[e, 1], 0]
    equ_release_time = pd.DataFrame.from_dict(equ_release_time, orient='index')
    equ_release_time.columns = ['type', 'release_time']
    # 任务数据初始化
    res = init_(data)
    res_num = len(res)
    # 产品优先级
    # 1. 总工时越长优先级越高
    # pri = priority_(res, 1)
    # 2. 工序B越早开始优先级越高
    # pri = priority_(res, 2)
    # pri = priority_(res, 3)
    # pri = priority_(res, 4)
    # 3. 随机赋予优先级
    pri = priority_(res, 5)

    # 根据优先级对工序A对应的任务进行加工,并更新任务的状态及紧后工序的状态;
    res, equ_index = gx_A(res, pri, equ_index)

    # 启发式:从机器角度出发,以最早可加工的机器选择可加工的工序
    res, equ_release_time, equ_index = machine_early_start_time(res, equ_release_time, equ_index)
    print(max(res['end']))
    return equ_index, res


if __name__ == '__main__':
    st = time.time()
    equ_, res_ = heuri_()
    print(time.time() - st)

写到这里,启发式规则求解基本已经完成,最终的结果应该是七十多分,但离最优解还有一定的距离,还有优化空闲。因此,后续以启发式规则为初始解,设计关键路径+VNS的智能算法进行求解。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值