多工序多设备的生产车间调度问题

多工序多设备的生产车间调度问题(Job Shop Scheduling Problem, JSSP)是智能制造领域的核心痛点。简单来说,就是“怎么安排多个工件在多个机器上干活,才能让总完工时间最短(MakeSpan)?”

这是一个典型的 NP-hard(非确定性多项式困难) 问题,当工序和设备稍微多一点,计算量就会呈指数级爆炸。


问题定义与数学模型

1. 核心要素

  • 工件(Jobs):待加工的零件,比如有 \(n\) 个工件 \(J_1, J_2, ..., J_n\)
  • 机器(Machines):车间里的设备,比如 \(m\) 台机器 \(M_1, M_2, ..., M_m\)
  • 工序(Operations):每个工件必须按特定顺序在指定机器上加工。
  • 目标:最小化最大完工时间(MakeSpan)

2. 数学模型(简洁版)

\(C_{ij}\) 为第 \(i\) 个工件的第 \(j\) 道工序的完工时刻,\(p_{ij}\) 为其加工时长。

  • 目标函数\(\min(MakeSpan) = \min(\max C_{ij})\)
  • 约束条件
    1. 工艺路线约束:同一工件的后一道工序必须等前一道做完才能开始。
    2. 资源独占约束:同一台机器在同一时刻只能加工一个工件。
    3. 非负约束:加工不能穿越时空提前开始。

主流求解算法流派

面对车间调度,死磕穷举法是不现实的,主流方案如下:

算法流派 代表算法 特点 适用场景
精确算法 分支定界法 (Branch and Bound) 能求出绝对最优解,但算到天荒地老 极小规模 (\(n \le 10\))
启发式算法 瓶颈移动法 (Bottleneck Shifting) 速度快,但容易陷入局部最优 对时间极其敏感的实时调度
元启发式算法 遗传算法 (GA)粒子群 (PSO)、模拟退火 (SA) 性价比之王,能在短时间内逼近最优解 绝大多数工业场景
深度强化学习 DRL (Deep Reinforcement Learning) 随数据进化,潜力巨大 柔性制造、复杂扰动环境

结论: 在实际工程中,遗传算法 (Genetic Algorithm, GA) 是处理多工序多设备调度最成熟、最通用的选择。


MATLAB 核心实现(遗传算法版)

以下是一套完整的 MATLAB 求解框架。它包含了染色体编码、交叉、变异以及甘特图绘制

Step 1:定义车间数据与参数

我们需要定义每个工件走哪条“流水线”(机器顺序)和耗时。

%% 1. 清空环境
clc; clear; close all;

%% 2. 车间数据定义 (核心输入)
% 假设有 3 个工件(Jobs),3 台机器(Machines)
% 格式:[机器编号, 加工时长]
Jobs = {
    [1, 2,  3, 3,  2, 2];  % J1: 机器1(2h) -> 机器3(3h) -> 机器2(2h)
    [2, 1,  1, 4,  3, 1];  % J2: 机器2(1h) -> 机器1(4h) -> 机器3(1h)
    [3, 3,  2, 2,  1, 3]   % J3: 机器3(3h) -> 机器2(2h) -> 机器1(3h)
};

numJobs = length(Jobs);       % 工件数量
numMachines = 3;             % 机器数量

%% 3. 遗传算法参数
popSize = 100;        % 种群大小(一次看100种排班方案)
maxGen = 100;         % 最大迭代代数
pc = 0.8;            % 交叉概率
pm = 0.2;            % 变异概率

Step 2:染色体编码与解码(决定排班优劣的核心)

采用工序编码法。比如 [1 2 3 1 2 3 1 2 3] 代表依次加工 J1的第1步,J2的第1步...

%% 4. 初始化种群 (生成随机排班表)
population = cell(popSize, 1);
for i = 1:popSize
    temp = [];
    for j = 1:numJobs
        temp = [temp, repmat(j, 1, length(Jobs{j})/2)];
    end
    population{i} = temp(randperm(length(temp))); % 打乱顺序
end

%% 5. 适应度计算 (解码器):计算MakeSpan
% 核心逻辑:维护每台机器的空闲时间表,遇到冲突就往后推
function makespan = calculateMakespan(chromosome, Jobs, numMachines)
    jobPtr = zeros(1, max(chromosome));     % 指向每个工件当前干到第几步了
    machineTime = zeros(1, numMachines);    % 每台机器的空闲时间点
    jobFinishTime = zeros(1, max(chromosome)); % 每个工件上一道工序的完工时间
    
    for gene = chromosome
        jobPtr(gene) = jobPtr(gene) + 1;
        step = jobPtr(gene);
        
        % 取出该工序的机器号和耗时
        machine = Jobs{gene}(step*2 - 1);
        duration = Jobs{gene}(step*2);
        
        % 计算当前工序的开始时间 (取决于:工件上一道何时完 + 机器何时空闲)
        startTime = max(jobFinishTime(gene), machineTime(machine));
        
        % 更新状态
        jobFinishTime(gene) = startTime + duration;
        machineTime(machine) = startTime + duration;
    end
    
    makespan = max(jobFinishTime); % 总完工时间
end

Step 3:主循环(选择、交叉、变异)

%% 6. 开始进化
bestScoreHistory = zeros(maxGen, 1);

for gen = 1:maxGen
    % (a) 计算适应度 (MakeSpan越小越好)
    fitness = inf(popSize, 1);
    for i = 1:popSize
        fitness(i) = calculateMakespan(population{i}, Jobs, numMachines);
    end
    
    % (b) 选择优秀个体 (轮盘赌选择)
    prob = 1 ./ fitness;
    prob = prob / sum(prob);
    idx = randsample(popSize, popSize, true, prob);
    parents = population(idx);
    
    % (c) 交叉 (POX交叉算子)
    children = cell(popSize, 1);
    for i = 1:2:popSize
        p1 = parents{i}; p2 = parents{i+1};
        cutPoint = randi(length(p1)-1);
        
        child1 = [p1(1:cutPoint), p2(cutPoint+1:end)];
        child2 = [p2(1:cutPoint), p1(cutPoint+1:end)];
        
        children{i} = child1;
        children{i+1} = child2;
    end
    
    % (d) 变异 (交换两个基因的位置)
    for i = 1:popSize
        if rand < pm
            swapIdx = randperm(length(children{i}), 2);
            temp = children{i}(swapIdx(1));
            children{i}(swapIdx(1)) = children{i}(swapIdx(2));
            children{i}(swapIdx(2)) = temp;
        end
    end
    
    population = children;
    bestScoreHistory(gen) = min(fitness);
    
    if mod(gen, 10) == 0
        fprintf('代数: %d, 当前最优完工时间: %.2f\n', gen, bestScoreHistory(gen));
    end
end

Step 4:结果可视化(画出甘特图)

老板最爱看的就是这张图,直观展示每台机器的负荷。

%% 7. 找出最优解并画图
[bestMakespan, bestIdx] = min(fitness);
bestChromosome = population{bestIdx};

figure('Name', '车间调度甘特图', 'Color', 'w', 'Position', [100 100 900 500]);
colors = jet(numJobs); % 给不同工件染不同颜色

jobPtr = zeros(1, max(bestChromosome));
machineTime = zeros(1, numMachines);
jobFinishTime = zeros(1, max(bestChromosome));

for gene = bestChromosome
    jobPtr(gene) = jobPtr(gene) + 1;
    step = jobPtr(gene);
    
    machine = Jobs{gene}(step*2 - 1);
    duration = Jobs{gene}(step*2);
    
    startTime = max(jobFinishTime(gene), machineTime(machine));
    endTime = startTime + duration;
    
    % 绘制矩形块
    rectangle('Position', [startTime, machine-0.4, duration, 0.8], ...
              'FaceColor', colors(gene, :), 'EdgeColor', 'k');
    text(startTime+duration/2, machine, sprintf('J%d', gene), ...
         'HorizontalAlignment', 'center', 'Color', 'white', 'FontWeight', 'bold');
    
    % 更新状态
    jobFinishTime(gene) = endTime;
    machineTime(machine) = endTime;
end

xlabel('时间 (小时)');
ylabel('机器编号');
title(sprintf('最优调度方案 (总完工时间 = %.2f)', bestMakespan), 'FontSize', 14);
set(gca, 'YTick', 1:numMachines, 'YDir', 'reverse', 'Box', 'on', 'FontSize', 10);
grid on;

参考代码 多工序多设备的生产车间调度问题 www.youwenfan.com/contentcnu/77684.html

工程落地建议

  1. 数据输入别硬编码:实际生产中,切勿像上面代码那样手动写 Jobs。建议读取 Excel 文件:

    data = readtable('production_order.xlsx'); 
    % 自动解析工件、工序、机器、工时
    
  2. 引入柔性约束:如果一台机器坏了怎么办?建议在解码器中加入设备可用性校验。如果某台机器不可用,触发“工序重路由”逻辑。

  3. 多目标优化:车间不仅追求时间最短(MakeSpan),还追求机器均衡负载(Balance)和最小化延期(Tardiness)。可以将适应度函数改为加权求和:

    \[Fitness = w_1 \times MakeSpan + w_2 \times LoadUnbalance \]

posted @ 2026-04-27 15:56  u95900090  阅读(71)  评论(0)    收藏  举报