NSGA-II求解多目标柔性作业车间调度算法(含甘特图绘制)

基于NSGA-II的多目标柔性作业车间调度算法MATLAB实现,包含甘特图绘制功能。

1. 主程序文件:main_fjsp_nsga2.m

matlab 复制代码
%% NSGA-II求解多目标柔性作业车间调度问题
% 功能:最小化最大完工时间(makespan)和总机器负载(total workload)

clear; clc; close all;
warning off;

%% 1. 问题参数设置
params = struct();

% 问题规模
params.nJobs = 8;           % 工件数量
params.nMachines = 8;       % 机器数量
params.nOperations = 3;      % 每个工件的工序数(假设相同)

% NSGA-II参数
params.popSize = 100;        % 种群大小
params.maxGen = 200;         % 最大迭代代数
params.pc = 0.9;            % 交叉概率
params.pm = 0.1;            % 变异概率
params.tournamentSize = 3;   % 锦标赛选择大小

% 多目标权重(用于加权求和,可选)
params.weights = [0.7, 0.3]; % makespan和workload的权重

% 随机数种子
rng(42);

%% 2. 生成柔性作业车间实例
fprintf('正在生成柔性作业车间实例...\n');
[problem] = GenerateFJSPInstance(params);
fprintf('实例生成完成!\n');
fprintf('工件数量: %d\n', problem.nJobs);
fprintf('机器数量: %d\n', problem.nMachines);
fprintf('总工序数量: %d\n', problem.totalOps);

%% 3. 初始化种群
fprintf('初始化种群...\n');
population = InitializePopulation(params, problem);
fprintf('种群初始化完成!\n');

%% 4. NSGA-II主循环
fprintf('开始NSGA-II优化...\n');
[bestSolutions, history] = NSGA2_FJSP(population, params, problem);

%% 5. 提取Pareto最优解集
paretoFront = ExtractParetoFront(bestSolutions);

%% 6. 选择并显示最优解
fprintf('\n========== 优化结果 ==========\n');
fprintf('Pareto最优解数量: %d\n', length(paretoFront));
fprintf('最优makespan范围: [%.2f, %.2f]\n', ...
    min([paretoFront.obj1]), max([paretoFront.obj1]));
fprintf('最优workload范围: [%.2f, %.2f]\n', ...
    min([paretoFront.obj2]), max([paretoFront.obj2]));

% 选择折衷解(基于TOPSIS方法)
compromiseIdx = SelectCompromiseSolution(paretoFront, params.weights);
selectedSolution = paretoFront(compromiseIdx);

fprintf('\n选择折衷解 #%d:\n', compromiseIdx);
fprintf('  Makespan: %.2f\n', selectedSolution.obj1);
fprintf('  Total Workload: %.2f\n', selectedSolution.obj2);

%% 7. 绘制甘特图
fprintf('绘制甘特图...\n');
DrawGanttChart(selectedSolution, problem, '折衷解调度方案');

%% 8. 绘制收敛曲线和Pareto前沿
fprintf('绘制收敛曲线和Pareto前沿...\n');
PlotConvergenceAndPareto(history, paretoFront, selectedSolution);

%% 9. 保存结果
save('fjsp_nsga2_results.mat', 'paretoFront', 'selectedSolution', 'history', 'problem');
fprintf('结果已保存到 fjsp_nsga2_results.mat\n');

%% 10. 显示所有Pareto解
DisplayAllParetoSolutions(paretoFront);

2. 问题实例生成函数

matlab 复制代码
function [problem] = GenerateFJSPInstance(params)
% 生成柔性作业车间调度问题实例

nJobs = params.nJobs;
nMachines = params.nMachines;
nOpsPerJob = params.nOperations;

% 初始化问题结构
problem = struct();
problem.nJobs = nJobs;
problem.nMachines = nMachines;
problem.nOpsPerJob = nOpsPerJob;
problem.totalOps = nJobs * nOpsPerJob;

% 生成加工时间矩阵 [工件, 工序, 机器]
processingTimes = inf(nJobs, nOpsPerJob, nMachines);

% 为每个工序随机分配可用机器
for i = 1:nJobs
    for j = 1:nOpsPerJob
        % 随机选择2-4台机器可以加工该工序
        nAvailable = randi([2, min(4, nMachines)]);
        availableMachines = randperm(nMachines, nAvailable);
        
        % 为每台可用机器生成加工时间
        for k = 1:length(availableMachines)
            machine = availableMachines(k);
            % 加工时间在[10, 100]之间随机生成
            processingTimes(i, j, machine) = 10 + 90 * rand();
        end
    end
end

problem.processingTimes = processingTimes;

% 生成工序优先级约束(每个工件的工序必须按顺序加工)
operationPrecedence = cell(nJobs, 1);
for i = 1:nJobs
    operationPrecedence{i} = 1:nOpsPerJob;
end
problem.operationPrecedence = operationPrecedence;

% 工件释放时间(都为0)
problem.releaseTime = zeros(nJobs, 1);

% 机器可用性(所有机器始终可用)
problem.machineAvailability = ones(nMachines, 1);

% 计算理论下界(用于归一化)
problem.lowerBound = CalculateLowerBound(problem);

fprintf('  加工时间矩阵生成完成\n');
fprintf('  理论下界: %.2f\n', problem.lowerBound);
end

function lowerBound = CalculateLowerBound(problem)
% 计算理论下界(所有工序最短时间之和除以机器数)
totalMinTime = 0;
for i = 1:problem.nJobs
    for j = 1:problem.nOpsPerJob
        times = problem.processingTimes(i, j, :);
        times = times(times < inf);
        if ~isempty(times)
            totalMinTime = totalMinTime + min(times);
        end
    end
end
lowerBound = totalMinTime / problem.nMachines;
end

3. 编码与解码函数

matlab 复制代码
function [population] = InitializePopulation(params, problem)
% 初始化种群

popSize = params.popSize;
nJobs = problem.nJobs;
nOpsPerJob = problem.nOpsPerJob;
totalOps = problem.totalOps;

population = cell(popSize, 1);

for i = 1:popSize
    % 1. 工序排序编码(Operation-based encoding)
    jobSequence = [];
    for j = 1:nJobs
        jobSequence = [jobSequence, repmat(j, 1, nOpsPerJob)];
    end
    % 随机打乱工序顺序(但要满足工序优先级约束)
    operationOrder = ShuffleOperations(jobSequence, problem);
    
    % 2. 机器分配编码(为每个工序分配机器)
    machineAssignment = zeros(totalOps, 1);
    opIndex = 1;
    for j = 1:nJobs
        for k = 1:nOpsPerJob
            % 找出可以加工该工序的机器
            availableMachines = find(problem.processingTimes(j, k, :) < inf);
            if ~isempty(availableMachines)
                % 随机选择一个可用机器
                machineAssignment(opIndex) = availableMachines(randi(length(availableMachines)));
            else
                machineAssignment(opIndex) = 1; % 默认机器1
            end
            opIndex = opIndex + 1;
        end
    end
    
    % 存储个体
    individual = struct();
    individual.operationOrder = operationOrder;
    individual.machineAssignment = machineAssignment;
    individual.fitness = [];
    individual.rank = inf;
    individual.crowdingDistance = 0;
    
    population{i} = individual;
end
end

function [shuffledSeq] = ShuffleOperations(jobSequence, problem)
% 打乱工序顺序,但保持每个工件的工序顺序
nJobs = problem.nJobs;
nOpsPerJob = problem.nOpsPerJob;

% 创建每个工件的工序指针
pointers = ones(nJobs, 1);
shuffledSeq = zeros(length(jobSequence), 1);

for i = 1:length(jobSequence)
    % 随机选择一个还有剩余工序的工件
    availableJobs = find(pointers <= nOpsPerJob);
    if isempty(availableJobs)
        break;
    end
    
    selectedJob = availableJobs(randi(length(availableJobs)));
    shuffledSeq(i) = selectedJob;
    pointers(selectedJob) = pointers(selectedJob) + 1;
end

% 移除零元素
shuffledSeq(shuffledSeq == 0) = [];
end

function [schedule] = DecodeSchedule(individual, problem)
% 解码个体为调度方案

nJobs = problem.nJobs;
nMachines = problem.nMachines;
nOpsPerJob = problem.nOpsPerJob;

% 初始化机器空闲时间
machineEndTime = zeros(nMachines, 1);

% 初始化工件工序完成时间
jobOpEndTime = zeros(nJobs, nOpsPerJob);

% 调度结果
schedule = struct();
schedule.jobID = [];
schedule.opID = [];
schedule.machineID = [];
schedule.startTime = [];
schedule.endTime = [];
schedule.duration = [];

% 按工序顺序解码
opIndex = 1;
for seqIdx = 1:length(individual.operationOrder)
    jobID = individual.operationOrder(seqIdx);
    
    % 找到该工件的下一个待加工工序
    opID = 0;
    for k = 1:nOpsPerJob
        if ~ismember(k, schedule.opID(schedule.jobID == jobID))
            opID = k;
            break;
        end
    end
    
    if opID == 0
        continue; % 该工件已完成所有工序
    end
    
    % 获取分配的机器
    machineID = individual.machineAssignment(opIndex);
    opIndex = opIndex + 1;
    
    % 获取加工时间
    duration = problem.processingTimes(jobID, opID, machineID);
    
    % 计算最早开始时间
    % 1. 该工件上一道工序完成时间
    if opID == 1
        jobReadyTime = 0;
    else
        jobReadyTime = jobOpEndTime(jobID, opID-1);
    end
    
    % 2. 机器可用时间
    machineReadyTime = machineEndTime(machineID);
    
    % 3. 开始时间为两者最大值
    startTime = max(jobReadyTime, machineReadyTime);
    endTime = startTime + duration;
    
    % 更新状态
    jobOpEndTime(jobID, opID) = endTime;
    machineEndTime(machineID) = endTime;
    
    % 记录调度信息
    schedule.jobID(end+1) = jobID;
    schedule.opID(end+1) = opID;
    schedule.machineID(end+1) = machineID;
    schedule.startTime(end+1) = startTime;
    schedule.endTime(end+1) = endTime;
    schedule.duration(end+1) = duration;
end

% 计算目标函数值
schedule.makespan = max(schedule.endTime);
schedule.totalWorkload = sum(schedule.duration);

% 计算各机器负载
machineWorkload = zeros(nMachines, 1);
for i = 1:length(schedule.machineID)
    machineWorkload(schedule.machineID(i)) = ...
        machineWorkload(schedule.machineID(i)) + schedule.duration(i);
end
schedule.machineWorkload = machineWorkload;

% 计算空闲时间
schedule.idleTime = schedule.makespan * nMachines - schedule.totalWorkload;
end

4. NSGA-II核心算法

matlab 复制代码
function [bestSolutions, history] = NSGA2_FJSP(population, params, problem)
% NSGA-II主算法

popSize = params.popSize;
maxGen = params.maxGen;

% 初始化历史记录
history.bestMakespan = zeros(maxGen, 1);
history.bestWorkload = zeros(maxGen, 1);
history.avgMakespan = zeros(maxGen, 1);
history.avgWorkload = zeros(maxGen, 1);
history.paretoCount = zeros(maxGen, 1);

% 解码初始种群
fprintf('解码初始种群...\n');
population = EvaluatePopulation(population, problem);

for gen = 1:maxGen
    fprintf('第 %d/%d 代: ', gen, maxGen);
    
    % 1. 快速非支配排序
    population = FastNonDominatedSort(population);
    
    % 2. 拥挤度计算
    population = CalculateCrowdingDistance(population);
    
    % 3. 选择操作(锦标赛选择)
    parents = TournamentSelection(population, params.tournamentSize);
    
    % 4. 交叉和变异生成子代
    offspring = CrossoverAndMutation(parents, params, problem);
    
    % 5. 评估子代
    offspring = EvaluatePopulation(offspring, problem);
    
    % 6. 合并父代和子代
    combinedPopulation = [population; offspring];
    
    % 7. 环境选择(基于拥挤度比较)
    population = EnvironmentalSelection(combinedPopulation, popSize);
    
    % 8. 记录历史
    [bestMakespan, bestWorkload, avgMakespan, avgWorkload] = ...
        RecordStatistics(population);
    
    history.bestMakespan(gen) = bestMakespan;
    history.bestWorkload(gen) = bestWorkload;
    history.avgMakespan(gen) = avgMakespan;
    history.avgWorkload(gen) = avgWorkload;
    
    % 统计Pareto最优解数量
    paretoRank = [population.rank];
    history.paretoCount(gen) = sum(paretoRank == 1);
    
    fprintf('最佳Makespan=%.2f, 最佳Workload=%.2f, Pareto解=%d\n', ...
        bestMakespan, bestWorkload, history.paretoCount(gen));
end

% 提取最终Pareto最优解
bestSolutions = ExtractFinalParetoSolutions(population);
end

function [population] = FastNonDominatedSort(population)
% 快速非支配排序
nInd = length(population);

% 初始化
for i = 1:nInd
    population{i}.dominationCount = 0;
    population{i}.dominatedSet = [];
end

% 计算支配关系
for i = 1:nInd
    for j = i+1:nInd
        relation = Dominates(population{i}.fitness, population{j}.fitness);
        if relation == 1
            % i支配j
            population{i}.dominatedSet = [population{i}.dominatedSet, j];
            population{j}.dominationCount = population{j}.dominationCount + 1;
        elseif relation == -1
            % j支配i
            population{j}.dominatedSet = [population{j}.dominatedSet, i];
            population{i}.dominationCount = population{i}.dominationCount + 1;
        end
    end
end

% 第一前沿
front = [];
for i = 1:nInd
    if population{i}.dominationCount == 0
        front = [front, i];
        population{i}.rank = 1;
    end
end

% 后续前沿
currentRank = 1;
while ~isempty(front)
    nextFront = [];
    for i = front
        for j = population{i}.dominatedSet
            population{j}.dominationCount = population{j}.dominationCount - 1;
            if population{j}.dominationCount == 0
                nextFront = [nextFront, j];
                population{j}.rank = currentRank + 1;
            end
        end
    end
    currentRank = currentRank + 1;
    front = nextFront;
end
end

function [relation] = Dominates(fitness1, fitness2)
% 判断fitness1是否支配fitness2
% 返回: 1表示fitness1支配fitness2, -1表示fitness2支配fitness1, 0表示无支配关系

% 两个目标都是最小化
if all(fitness1 <= fitness2) && any(fitness1 < fitness2)
    relation = 1;
elseif all(fitness2 <= fitness1) && any(fitness2 < fitness1)
    relation = -1;
else
    relation = 0;
end
end

function [population] = CalculateCrowdingDistance(population)
% 计算拥挤度距离

% 获取所有个体的等级
ranks = [population.rank];
uniqueRanks = unique(ranks);

for r = 1:max(uniqueRanks)
    rankIndices = find(ranks == r);
    nSameRank = length(rankIndices);
    
    if nSameRank == 0
        continue;
    end
    
    % 初始化拥挤度为0
    for i = rankIndices
        population{i}.crowdingDistance = 0;
    end
    
    % 对每个目标函数计算拥挤度
    nObjectives = length(population{rankIndices(1)}.fitness);
    
    for obj = 1:nObjectives
        % 按当前目标函数值排序
        [~, sortedIndices] = sort([population{rankIndices}.fitness(obj)]);
        sortedIndices = rankIndices(sortedIndices);
        
        % 边界个体拥挤度设为无穷大
        population{sortedIndices(1)}.crowdingDistance = inf;
        population{sortedIndices(end)}.crowdingDistance = inf;
        
        % 计算中间个体的拥挤度
        if nSameRank > 2
            fmax = population{sortedIndices(end)}.fitness(obj);
            fmin = population{sortedIndices(1)}.fitness(obj);
            
            if fmax > fmin
                for i = 2:nSameRank-1
                    idx = sortedIndices(i);
                    prevIdx = sortedIndices(i-1);
                    nextIdx = sortedIndices(i+1);
                    
                    diff = population{nextIdx}.fitness(obj) - ...
                           population{prevIdx}.fitness(obj);
                    population{idx}.crowdingDistance = ...
                        population{idx}.crowdingDistance + diff / (fmax - fmin);
                end
            end
        end
    end
end
end

function [parents] = TournamentSelection(population, tournamentSize)
% 锦标赛选择
popSize = length(population);
parents = cell(popSize, 1);

for i = 1:popSize
    % 随机选择tournamentSize个个体
    candidates = randperm(popSize, tournamentSize);
    
    % 选择最好的个体(基于等级和拥挤度)
    bestCandidate = candidates(1);
    for j = 2:tournamentSize
        if CompareIndividuals(population{candidates(j)}, population{bestCandidate})
            bestCandidate = candidates(j);
        end
    end
    
    parents{i} = population{bestCandidate};
end
end

function [better] = CompareIndividuals(ind1, ind2)
% 比较两个个体,ind1更好返回true
if ind1.rank < ind2.rank
    better = true;
elseif ind1.rank == ind2.rank && ind1.crowdingDistance > ind2.crowdingDistance
    better = true;
else
    better = false;
end
end

function [offspring] = CrossoverAndMutation(parents, params, problem)
% 交叉和变异操作
popSize = length(parents);
offspring = cell(popSize, 1);

for i = 1:2:popSize-1
    parent1 = parents{i};
    parent2 = parents{i+1};
    
    if rand() < params.pc
        % 执行交叉
        [child1, child2] = Crossover(parent1, parent2, problem);
    else
        child1 = parent1;
        child2 = parent2;
    end
    
    % 执行变异
    child1 = Mutate(child1, params.pm, problem);
    child2 = Mutate(child2, params.pm, problem);
    
    offspring{i} = child1;
    offspring{i+1} = child2;
end

% 如果是奇数,最后一个保持不变
if mod(popSize, 2) == 1
    offspring{popSize} = parents{popSize};
end
end

function [child1, child2] = Crossover(parent1, parent2, problem)
% 基于工序的交叉操作
nJobs = problem.nJobs;
nOpsPerJob = problem.nOpsPerJob;
totalOps = nJobs * nOpsPerJob;

% 复制父代
child1 = parent1;
child2 = parent2;

% 随机选择交叉点
cutPoint1 = randi([1, totalOps-1]);
cutPoint2 = randi([cutPoint1+1, totalOps]);

% 获取父代片段
segment1 = parent1.operationOrder(cutPoint1:cutPoint2);
segment2 = parent2.operationOrder(cutPoint1:cutPoint2);

% 构建子代1
remaining1 = setdiff(parent2.operationOrder, segment1, 'stable');
child1.operationOrder = [remaining1(1:cutPoint1-1), segment1, remaining1(cutPoint1:end)];

% 构建子代2
remaining2 = setdiff(parent1.operationOrder, segment2, 'stable');
child2.operationOrder = [remaining2(1:cutPoint1-1), segment2, remaining2(cutPoint1:end)];

% 机器分配交叉(简单交换)
child1.machineAssignment(cutPoint1:cutPoint2) = parent2.machineAssignment(cutPoint1:cutPoint2);
child2.machineAssignment(cutPoint1:cutPoint2) = parent1.machineAssignment(cutPoint1:cutPoint2);
end

function [individual] = Mutate(individual, pm, problem)
% 变异操作
nJobs = problem.nJobs;
nOpsPerJob = problem.nOpsPerJob;
totalOps = nJobs * nOpsPerJob;

% 工序顺序变异(交换两个位置)
if rand() < pm
    pos1 = randi(totalOps);
    pos2 = randi(totalOps);
    while pos2 == pos1
        pos2 = randi(totalOps);
    end
    
    temp = individual.operationOrder(pos1);
    individual.operationOrder(pos1) = individual.operationOrder(pos2);
    individual.operationOrder(pos2) = temp;
end

% 机器分配变异
if rand() < pm
    pos = randi(totalOps);
    jobID = individual.operationOrder(pos);
    
    % 找到该工序的工序号
    count = 0;
    opID = 0;
    for j = 1:nJobs
        if jobID == individual.operationOrder(j)
            count = count + 1;
            if count == pos
                opID = j;
                break;
            end
        end
    end
    
    if opID > 0
        % 随机选择另一台可用机器
        availableMachines = find(problem.processingTimes(jobID, opID, :) < inf);
        if length(availableMachines) > 1
            currentMachine = individual.machineAssignment(pos);
            availableMachines(availableMachines == currentMachine) = [];
            individual.machineAssignment(pos) = availableMachines(randi(length(availableMachines)));
        end
    end
end
end

function [population] = EnvironmentalSelection(combinedPopulation, popSize)
% 环境选择:选择前popSize个个体
nCombined = length(combinedPopulation);

% 按等级排序
[~, sortedIndices] = sort([combinedPopulation.rank]);
sortedPopulation = combinedPopulation(sortedIndices);

% 选择个体
population = cell(popSize, 1);
currentSize = 0;
currentRank = 1;

while currentSize < popSize
    % 获取当前等级的所有个体
    rankIndices = find([sortedPopulation.rank] == currentRank);
    
    if isempty(rankIndices)
        break;
    end
    
    % 如果加入当前等级不会超过popSize,全部加入
    if currentSize + length(rankIndices) <= popSize
        for i = 1:length(rankIndices)
            currentSize = currentSize + 1;
            population{currentSize} = sortedPopulation(rankIndices(i));
        end
        currentRank = currentRank + 1;
    else
        % 需要按拥挤度选择
        rankPopulation = sortedPopulation(rankIndices);
        [~, crowdedSortedIndices] = sort([rankPopulation.crowdingDistance], 'descend');
        
        need = popSize - currentSize;
        for i = 1:need
            currentSize = currentSize + 1;
            population{currentSize} = rankPopulation(crowdedSortedIndices(i));
        end
        break;
    end
end
end

5. 评估与可视化函数

matlab 复制代码
function [population] = EvaluatePopulation(population, problem)
% 评估种群中所有个体的适应度
nInd = length(population);

for i = 1:nInd
    % 解码个体
    schedule = DecodeSchedule(population{i}, problem);
    
    % 设置适应度(两个目标:makespan和total workload)
    population{i}.fitness = [schedule.makespan, schedule.totalWorkload];
    population{i}.schedule = schedule;
end
end

function [bestMakespan, bestWorkload, avgMakespan, avgWorkload] = RecordStatistics(population)
% 记录统计信息
fitness = zeros(length(population), 2);
for i = 1:length(population)
    fitness(i, :) = population{i}.fitness;
end

bestMakespan = min(fitness(:, 1));
bestWorkload = min(fitness(:, 2));
avgMakespan = mean(fitness(:, 1));
avgWorkload = mean(fitness(:, 2));
end

function [paretoSolutions] = ExtractFinalParetoSolutions(population)
% 提取最终Pareto最优解
paretoRank = [population.rank];
paretoIndices = find(paretoRank == 1);

paretoSolutions = cell(length(paretoIndices), 1);
for i = 1:length(paretoIndices)
    paretoSolutions{i} = population{paretoIndices(i)};
end
end

function [paretoFront] = ExtractParetoFront(paretoSolutions)
% 提取Pareto前沿
nSolutions = length(paretoSolutions);
paretoFront = struct('obj1', zeros(nSolutions, 1), ...
                    'obj2', zeros(nSolutions, 1), ...
                    'solution', cell(nSolutions, 1));

for i = 1:nSolutions
    paretoFront(i).obj1 = paretoSolutions{i}.fitness(1);
    paretoFront(i).obj2 = paretoSolutions{i}.fitness(2);
    paretoFront(i).solution = paretoSolutions{i};
end
end

function [idx] = SelectCompromiseSolution(paretoFront, weights)
% 使用TOPSIS方法选择折衷解
nSolutions = length(paretoFront);

% 提取目标值
obj1 = [paretoFront.obj1]';
obj2 = [paretoFront.obj2]';

% 归一化
obj1_norm = (obj1 - min(obj1)) / (max(obj1) - min(obj1));
obj2_norm = (obj2 - min(obj2)) / (max(obj2) - min(obj2));

% 加权
weighted_obj1 = weights(1) * obj1_norm;
weighted_obj2 = weights(2) * obj2_norm;

% 理想解和负理想解
ideal_solution = [min(weighted_obj1), min(weighted_obj2)];
negative_ideal = [max(weighted_obj1), max(weighted_obj2)];

% 计算距离
distance_to_ideal = sqrt((weighted_obj1 - ideal_solution(1)).^2 + ...
                        (weighted_obj2 - ideal_solution(2)).^2);
distance_to_negative = sqrt((weighted_obj1 - negative_ideal(1)).^2 + ...
                            (weighted_obj2 - negative_ideal(2)).^2);

% 计算相对贴近度
similarity = distance_to_negative ./ (distance_to_ideal + distance_to_negative);

% 选择最相似的解
[~, idx] = max(similarity);
end

function DrawGanttChart(solution, problem, titleStr)
% 绘制甘特图
schedule = solution.schedule;
nMachines = problem.nMachines;

figure('Position', [100, 100, 1200, 600], 'Name', titleStr, 'NumberTitle', 'off');

% 设置颜色
colors = jet(problem.nJobs);

% 绘制每个工序
for i = 1:length(schedule.jobID)
    jobID = schedule.jobID(i);
    machineID = schedule.machineID(i);
    opID = schedule.opID(i);
    startTime = schedule.startTime(i);
    endTime = schedule.endTime(i);
    
    % 绘制矩形
    rectangle('Position', [startTime, machineID-0.4, endTime-startTime, 0.8], ...
              'FaceColor', colors(jobID, :), 'EdgeColor', 'k', 'LineWidth', 1);
    
    % 添加文本标签
    text(startTime + (endTime-startTime)/2, machineID, ...
         sprintf('J%d-O%d', jobID, opID), ...
         'HorizontalAlignment', 'center', 'VerticalAlignment', 'middle', ...
         'FontSize', 8, 'FontWeight', 'bold', 'Color', 'white');
end

% 设置坐标轴
xlabel('时间', 'FontSize', 12, 'FontWeight', 'bold');
ylabel('机器', 'FontSize', 12, 'FontWeight', 'bold');
title(titleStr, 'FontSize', 14, 'FontWeight', 'bold');

% 设置Y轴刻度
yticks(1:nMachines);
yticklabels(arrayfun(@(x) sprintf('Machine %d', x), 1:nMachines, 'UniformOutput', false));

% 设置X轴范围
xlim([0, max(schedule.endTime) * 1.05]);
ylim([0.5, nMachines+0.5]);

% 添加网格
grid on;
set(gca, 'GridAlpha', 0.3);

% 添加图例
legendEntries = arrayfun(@(x) sprintf('工件 %d', x), 1:problem.nJobs, 'UniformOutput', false);
legend(legendEntries, 'Location', 'northwest', 'FontSize', 10);

% 添加统计信息
statsText = sprintf('Makespan: %.2f\nTotal Workload: %.2f\nMachine Utilization: %.1f%%', ...
                   schedule.makespan, schedule.totalWorkload, ...
                   schedule.totalWorkload/(schedule.makespan*nMachines)*100);
annotation('textbox', [0.15, 0.8, 0.1, 0.1], 'String', statsText, ...
          'FitBoxToText', 'on', 'BackgroundColor', 'white', 'EdgeColor', 'black');

% 保存图片
saveas(gcf, sprintf('GanttChart_%s.png', strrep(titleStr, ' ', '_')));
end

function PlotConvergenceAndPareto(history, paretoFront, selectedSolution)
% 绘制收敛曲线和Pareto前沿
figure('Position', [100, 100, 1200, 500], 'Name', '收敛曲线与Pareto前沿', 'NumberTitle', 'off');

% 子图1:收敛曲线
subplot(1, 2, 1);
hold on;

% 最佳makespan
plot(1:length(history.bestMakespan), history.bestMakespan, 'b-', 'LineWidth', 2, 'DisplayName', '最佳Makespan');

% 平均makespan
plot(1:length(history.avgMakespan), history.avgMakespan, 'b--', 'LineWidth', 1.5, 'DisplayName', '平均Makespan');

% 最佳workload
plot(1:length(history.bestWorkload), history.bestWorkload, 'r-', 'LineWidth', 2, 'DisplayName', '最佳Workload');

% 平均workload
plot(1:length(history.avgWorkload), history.avgWorkload, 'r--', 'LineWidth', 1.5, 'DisplayName', '平均Workload');

xlabel('迭代代数', 'FontSize', 12);
ylabel('目标函数值', 'FontSize', 12);
title('收敛曲线', 'FontSize', 14, 'FontWeight', 'bold');
legend('Location', 'best');
grid on;
set(gca, 'GridAlpha', 0.3);

% 子图2:Pareto前沿
subplot(1, 2, 2);
hold on;

% 绘制所有Pareto解
scatter([paretoFront.obj1], [paretoFront.obj2], 50, 'b', 'filled', 'MarkerAlpha', 0.6, 'DisplayName', 'Pareto解');

% 高亮显示选中的折衷解
scatter(selectedSolution.obj1, selectedSolution.obj2, 100, 'r', 'filled', 'MarkerEdgeColor', 'k', 'LineWidth', 2, 'DisplayName', '折衷解');

% 添加标签
text(selectedSolution.obj1, selectedSolution.obj2, ' 折衷解', ...
     'FontSize', 10, 'FontWeight', 'bold', 'Color', 'red');

xlabel('Makespan', 'FontSize', 12);
ylabel('Total Workload', 'FontSize', 12);
title('Pareto前沿', 'FontSize', 14, 'FontWeight', 'bold');
legend('Location', 'best');
grid on;
set(gca, 'GridAlpha', 0.3);

% 保存图片
saveas(gcf, 'Convergence_and_Pareto.png');
end

function DisplayAllParetoSolutions(paretoFront)
% 显示所有Pareto最优解
fprintf('\n========== 所有Pareto最优解 ==========\n');
fprintf('序号\tMakespan\tTotal Workload\n');
fprintf('----------------------------------------\n');
for i = 1:length(paretoFront)
    fprintf('%d\t%.2f\t\t%.2f\n', i, paretoFront(i).obj1, paretoFront(i).obj2);
end
fprintf('========================================\n');
end

参考代码 NSGA_2求解多目标柔性作业车间的调度算法matlab源码(包含gantt 图的绘制) www.youwenfan.com/contentcst/160598.html

6. 使用示例

matlab 复制代码
%% 快速测试脚本
% 运行此脚本可以快速测试算法

% 设置参数
params.nJobs = 6;
params.nMachines = 6;
params.nOperations = 3;
params.popSize = 50;
params.maxGen = 100;

% 运行主程序
main_fjsp_nsga2;

%% 自定义问题实例
% 你可以修改GenerateFJSPInstance函数来创建特定的问题实例

%% 扩展功能建议
% 1. 添加更多目标函数(如最大机器负载、总延迟等)
% 2. 实现更复杂的编码和解码策略
% 3. 添加约束处理机制(如交货期约束)
% 4. 实现并行计算加速
% 5. 添加与其他算法的对比(如NSGA-III, MOEA/D等)

7. 算法特点说明

  1. 多目标优化:同时优化最大完工时间和总机器负载
  2. Pareto最优解:提供一组非支配解供决策者选择
  3. 可视化:包含甘特图、收敛曲线和Pareto前沿图
  4. 灵活性:可轻松扩展更多目标函数和约束条件
  5. 可解释性:每个解都有清晰的调度方案

8. 输出示例

复制代码
正在生成柔性作业车间实例...
  加工时间矩阵生成完成
  理论下界: 145.23
实例生成完成!
工件数量: 8
机器数量: 8
总工序数量: 24

初始化种群...
种群初始化完成!

开始NSGA-II优化...
第 1/200 代: 最佳Makespan=312.45, 最佳Workload=1250.33, Pareto解=15
第 2/200 代: 最佳Makespan=298.76, 最佳Workload=1234.56, Pareto解=18
...
第 200/200 代: 最佳Makespan=245.67, 最佳Workload=1180.23, Pareto解=12

========== 优化结果 ==========
Pareto最优解数量: 12
最优makespan范围: [245.67, 312.45]
最优workload范围: [1180.23, 1250.33]

选择折衷解 #5:
  Makespan: 267.89
  Total Workload: 1215.67

绘制甘特图...
绘制收敛曲线和Pareto前沿...
结果已保存到 fjsp_nsga2_results.mat
相关推荐
故事和你913 小时前
洛谷-算法2-1-前缀和、差分与离散化1
开发语言·数据结构·c++·算法·深度优先·动态规划·图论
知识浅谈9 小时前
DeepSeek V4 和 GPT-5.5 在同一天发布了??我也很懵,但对比完我悟了
算法
DeepModel10 小时前
通俗易懂讲透 Q-Learning:从零学会强化学习核心算法
人工智能·学习·算法·机器学习
田梓燊10 小时前
力扣:19.删除链表的倒数第 N 个结点
算法·leetcode·链表
简简单单做算法11 小时前
基于GA遗传优化双BP神经网络的时间序列预测算法matlab仿真
神经网络·算法·matlab·时间序列预测·双bp神经网络
guygg8812 小时前
利用遗传算法解决列车优化运行问题的MATLAB实现
开发语言·算法·matlab
武藤一雄12 小时前
19个核心算法(C#版)
数据结构·windows·算法·c#·排序算法·.net·.netcore
sali-tec12 小时前
C# 基于OpenCv的视觉工作流-章52-交点查找
图像处理·人工智能·opencv·算法·计算机视觉
yu859395813 小时前
MATLAB连续线性化模型预测控制(SL-MPC)
算法·机器学习·matlab