基于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. 算法特点说明
- 多目标优化:同时优化最大完工时间和总机器负载
- Pareto最优解:提供一组非支配解供决策者选择
- 可视化:包含甘特图、收敛曲线和Pareto前沿图
- 灵活性:可轻松扩展更多目标函数和约束条件
- 可解释性:每个解都有清晰的调度方案
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