基于基因演算法(GA)的柔性作业车间调度问题(FJSP)求解
一、问题定义与数学模型
1.1 柔性作业车间调度问题(FJSP)
FJSP是经典作业车间调度问题(JSP)的扩展,每个工序可在多台机器上加工,且加工时间不同。
问题描述:
- 有
个工件 - 每个工件
有 个工序 - 有
台机器 - 工序
可在机器子集 上加工 - 在机器
上加工时间
目标:最小化最大完工时间(Makespan)
约束:
- 工序先后约束:
- 机器容量约束:一台机器同一时间只能加工一个工序
- 工序不能中断
1.2 问题复杂度
| 问题规模 | 决策变量数 | 搜索空间大小 |
|---|---|---|
| 3工件×3工序×3机器 | 18 | |
| 5工件×5工序×5机器 | 125 | |
| 10工件×10工序×10机器 | 1000 |
二、完整MATLAB实现代码
2.1 主程序:ga_fjsp_solver.m
%% 基于基因演算法的柔性作业车间调度问题求解
% 功能:使用遗传算法求解FJSP,最小化最大完工时间
clear; clc; close all;
%% 1. 问题实例定义
fprintf('=== 柔性作业车间调度问题(FJSP)求解 ===\n');
problem = define_fjsp_instance();
fprintf('问题实例:\n');
fprintf(' 工件数量: %d\n', problem.n_jobs);
fprintf(' 机器数量: %d\n', problem.n_machines);
fprintf(' 总工序数: %d\n', problem.total_ops);
fprintf(' 搜索空间大小: %.2e\n\n', problem.search_space);
%% 2. 基因演算法参数设置
params = configure_ga_parameters(problem);
fprintf('基因演算法参数:\n');
fprintf(' 种群大小: %d\n', params.pop_size);
fprintf(' 最大代数: %d\n', params.max_gen);
fprintf(' 交叉概率: %.2f\n', params.pc);
fprintf(' 变异概率: %.2f\n', params.pm);
fprintf(' 精英保留比例: %.2f\n\n', params.elite_ratio);
%% 3. 初始化种群
fprintf('初始化种群...\n');
tic;
population = initialize_population(problem, params);
init_time = toc;
fprintf(' 种群初始化完成!耗时: %.3f秒\n', init_time);
%% 4. 基因演算法主循环
fprintf('开始基因演算法进化...\n');
tic;
[best_solution, best_fitness_history, avg_fitness_history] = genetic_algorithm(population, problem, params);
ga_time = toc;
fprintf(' 基因演算法完成!耗时: %.3f秒\n', ga_time);
%% 5. 解码最优解并生成调度方案
fprintf('解码最优解...\n');
[schedule, makespan] = decode_solution(best_solution, problem);
fprintf('\n=== 求解结果 ===\n');
fprintf('最优最大完工时间 (Makespan): %.2f\n', makespan);
fprintf('总进化代数: %d\n', length(best_fitness_history));
fprintf('最终种群平均适应度: %.2f\n', avg_fitness_history(end));
fprintf('收敛代数: %d\n', find(best_fitness_history == min(best_fitness_history), 1, 'last'));
%% 6. 可视化结果
visualize_results(schedule, makespan, best_fitness_history, avg_fitness_history, problem);
2.2 问题实例定义:define_fjsp_instance.m
function problem = define_fjsp_instance()
% 定义FJSP问题实例(示例:3工件×3工序×3机器)
% === 基本参数 ===
problem.n_jobs = 3; % 工件数量
problem.n_machines = 3; % 机器数量
% 每个工件的工序数
problem.ops_per_job = [3, 3, 3]; % 工件1、2、3各有3个工序
problem.total_ops = sum(problem.ops_per_job); % 总工序数
% === 可选机器矩阵 ===
% ops_machine{i}{j}: 工件i的第j个工序可选机器集合
% 格式: [机器索引, 加工时间]
problem.ops_machine = cell(problem.n_jobs, max(problem.ops_per_job));
% 工件1
problem.ops_machine{1,1} = [1, 2; 2, 3]; % 工序1可选机器1(2h)或机器2(3h)
problem.ops_machine{1,2} = [2, 2; 3, 4]; % 工序2可选机器2(2h)或机器3(4h)
problem.ops_machine{1,3} = [1, 3; 3, 5]; % 工序3可选机器1(3h)或机器3(5h)
% 工件2
problem.ops_machine{2,1} = [1, 3; 2, 4]; % 工序1可选机器1(3h)或机器2(4h)
problem.ops_machine{2,2} = [2, 3; 3, 5]; % 工序2可选机器2(3h)或机器3(5h)
problem.ops_machine{2,3} = [1, 4; 3, 6]; % 工序3可选机器1(4h)或机器3(6h)
% 工件3
problem.ops_machine{3,1} = [1, 4; 2, 5]; % 工序1可选机器1(4h)或机器2(5h)
problem.ops_machine{3,2} = [2, 4; 3, 6]; % 工序2可选机器2(4h)或机器3(6h)
problem.ops_machine{3,3} = [1, 5; 3, 7]; % 工序3可选机器1(5h)或机器3(7h)
% === 计算搜索空间 ===
search_space = 1;
for job = 1:problem.n_jobs
for op = 1:problem.ops_per_job(job)
n_options = size(problem.ops_machine{job, op}, 1);
search_space = search_space * n_options;
end
end
problem.search_space = search_space;
% === 工序索引映射 ===
% 将工序索引转换为全局索引
problem.op_global_idx = zeros(problem.n_jobs, max(problem.ops_per_job));
global_idx = 1;
for job = 1:problem.n_jobs
for op = 1:problem.ops_per_job(job)
problem.op_global_idx(job, op) = global_idx;
global_idx = global_idx + 1;
end
end
fprintf('FJSP实例定义完成!\n');
end
2.3 GA参数配置:configure_ga_parameters.m
function params = configure_ga_parameters(problem)
% 配置基因演算法参数
% === 种群参数 ===
params.pop_size = 100; % 种群大小
params.max_gen = 200; % 最大进化代数
params.elite_ratio = 0.1; % 精英保留比例
params.elite_count = round(params.pop_size * params.elite_ratio);
% === 遗传算子参数 ===
params.pc = 0.8; % 交叉概率
params.pm = 0.1; % 变异概率
% === 编码参数 ===
params.chromosome_length = problem.total_ops * 2; % 染色体长度:工序顺序+机器分配
% === 选择参数 ===
params.tournament_size = 3; % 锦标赛选择大小
% === 终止条件 ===
params.stagnation_limit = 50; % 停滞代数限制
params.target_fitness = 1e-6; % 目标适应度
fprintf('GA参数配置完成!\n');
end
2.4 种群初始化:initialize_population.m
function population = initialize_population(problem, params)
% 初始化种群
population = cell(params.pop_size, 1);
for i = 1:params.pop_size
% 生成随机工序顺序(基于工件优先)
op_sequence = generate_random_operation_sequence(problem);
% 生成随机机器分配
machine_assignment = generate_random_machine_assignment(op_sequence, problem);
% 组合成染色体
chromosome.op_sequence = op_sequence;
chromosome.machine_assignment = machine_assignment;
chromosome.fitness = inf; % 初始适应度为无穷大
population{i} = chromosome;
end
fprintf(' 生成 %d 个随机个体\n', params.pop_size);
end
function op_sequence = generate_random_operation_sequence(problem)
% 生成随机工序顺序
% 使用优先约束:同一工件的工序必须按顺序
op_sequence = zeros(1, problem.total_ops);
op_counter = zeros(1, problem.n_jobs);
available_jobs = 1:problem.n_jobs;
for pos = 1:problem.total_ops
% 随机选择可用的工件
valid_jobs = [];
for job = available_jobs
if op_counter(job) < problem.ops_per_job(job)
valid_jobs = [valid_jobs, job];
end
end
% 随机选择一个工件
selected_job = valid_jobs(randi(length(valid_jobs)));
op_counter(selected_job) = op_counter(selected_job) + 1;
% 记录工序
op_sequence(pos) = selected_job * 100 + op_counter(selected_job); % 编码:工件号*100+工序号
end
end
function machine_assignment = generate_random_machine_assignment(op_sequence, problem)
% 生成随机机器分配
machine_assignment = zeros(1, length(op_sequence));
for pos = 1:length(op_sequence)
% 解码工序编码
op_code = op_sequence(pos);
job_id = floor(op_code / 100);
op_id = mod(op_code, 100);
% 获取可选机器
machine_options = problem.ops_machine{job_id, op_id};
n_options = size(machine_options, 1);
% 随机选择一台机器
selected_option = randi(n_options);
machine_assignment(pos) = machine_options(selected_option, 1); % 机器编号
end
end
2.5 基因演算法主循环:genetic_algorithm.m
function [best_solution, best_fitness_history, avg_fitness_history] = genetic_algorithm(population, problem, params)
% 基因演算法主循环
best_fitness_history = zeros(params.max_gen, 1);
avg_fitness_history = zeros(params.max_gen, 1);
best_solution = population{1};
stagnation_counter = 0;
for gen = 1:params.max_gen
% 1. 评估种群适应度
population = evaluate_population(population, problem);
% 2. 记录统计信息
fitness_values = zeros(params.pop_size, 1);
for i = 1:params.pop_size
fitness_values(i) = population{i}.fitness;
end
best_fitness_history(gen) = min(fitness_values);
avg_fitness_history(gen) = mean(fitness_values);
% 3. 更新最优解
[current_best_fitness, best_idx] = min(fitness_values);
if current_best_fitness < best_solution.fitness
best_solution = population{best_idx};
stagnation_counter = 0;
else
stagnation_counter = stagnation_counter + 1;
end
% 4. 显示进化信息
if mod(gen, 10) == 0 || gen == 1
fprintf(' 第 %d 代: 最佳适应度 = %.2f, 平均适应度 = %.2f\n', ...
gen, best_fitness_history(gen), avg_fitness_history(gen));
end
% 5. 检查终止条件
if stagnation_counter >= params.stagnation_limit
fprintf(' 提前终止:连续 %d 代无改进\n', stagnation_counter);
break;
end
if best_fitness_history(gen) <= params.target_fitness
fprintf(' 提前终止:达到目标适应度\n');
break;
end
% 6. 选择操作
selected_population = selection(population, params);
% 7. 交叉操作
offspring = crossover(selected_population, params, problem);
% 8. 变异操作
offspring = mutation(offspring, params, problem);
% 9. 精英保留
population = elitism(population, offspring, params);
end
end
%% ========== 遗传算子函数 ==========
function population = evaluate_population(population, problem)
% 评估种群适应度
for i = 1:length(population)
chromosome = population{i};
% 解码并计算Makespan
[~, makespan] = decode_solution(chromosome, problem);
% 适应度 = 1 / Makespan(最小化Makespan等价于最大化适应度)
chromosome.fitness = 1 / makespan;
population{i} = chromosome;
end
end
function selected_population = selection(population, params)
% 选择操作(锦标赛选择)
selected_population = cell(params.pop_size, 1);
for i = 1:params.pop_size
% 随机选择tournament_size个个体
candidates = randperm(params.pop_size, params.tournament_size);
best_candidate = candidates(1);
best_fitness = population{best_candidate}.fitness;
% 选择适应度最好的个体
for j = 2:params.tournament_size
candidate = candidates(j);
if population{candidate}.fitness < best_fitness
best_candidate = candidate;
best_fitness = population{candidate}.fitness;
end
end
selected_population{i} = population{best_candidate};
end
end
function offspring = crossover(selected_population, params, problem)
% 交叉操作(部分映射交叉PMX)
offspring = cell(params.pop_size, 1);
for i = 1:2:params.pop_size-1
parent1 = selected_population{i};
parent2 = selected_population{i+1};
if rand() < params.pc
% 执行交叉
[child1, child2] = pmx_crossover(parent1, parent2, problem);
offspring{i} = child1;
offspring{i+1} = child2;
else
% 不交叉,直接复制
offspring{i} = parent1;
offspring{i+1} = parent2;
end
end
end
function [child1, child2] = pmx_crossover(parent1, parent2, problem)
% 部分映射交叉(PMX)
% 仅对工序顺序进行交叉,机器分配保持不变
% 复制父代
child1 = parent1;
child2 = parent2;
% 随机选择交叉点
point1 = randi([1, problem.total_ops-1]);
point2 = randi([point1+1, problem.total_ops]);
% 交换中间段
segment1 = parent1.op_sequence(point1:point2);
segment2 = parent2.op_sequence(point1:point2);
child1.op_sequence(point1:point2) = segment2;
child2.op_sequence(point1:point2) = segment1;
% 修复冲突(确保同一工件工序顺序正确)
child1.op_sequence = repair_sequence(child1.op_sequence, problem);
child2.op_sequence = repair_sequence(child2.op_sequence, problem);
end
function sequence = repair_sequence(sequence, problem)
% 修复工序顺序,确保同一工件的工序按顺序排列
% 简化版:重新生成合法序列
job_counts = zeros(1, problem.n_jobs);
new_sequence = zeros(1, length(sequence));
for pos = 1:length(sequence)
op_code = sequence(pos);
job_id = floor(op_code / 100);
op_id = mod(op_code, 100);
% 检查是否合法
if op_id == job_counts(job_id) + 1
job_counts(job_id) = job_counts(job_id) + 1;
new_sequence(pos) = op_code;
else
% 不合法,重新分配
for job = 1:problem.n_jobs
if job_counts(job) < problem.ops_per_job(job)
job_counts(job) = job_counts(job) + 1;
new_sequence(pos) = job * 100 + job_counts(job);
break;
end
end
end
end
sequence = new_sequence;
end
function offspring = mutation(offspring, params, problem)
% 变异操作
for i = 1:length(offspring)
chromosome = offspring{i};
% 工序顺序变异
if rand() < params.pm
chromosome.op_sequence = mutate_operation_sequence(chromosome.op_sequence, problem);
end
% 机器分配变异
if rand() < params.pm
chromosome.machine_assignment = mutate_machine_assignment(chromosome.op_sequence, problem);
end
offspring{i} = chromosome;
end
end
function sequence = mutate_operation_sequence(sequence, problem)
% 工序顺序变异(交换两个位置)
pos1 = randi(length(sequence));
pos2 = randi(length(sequence));
% 确保交换合法
while pos1 == pos2
pos2 = randi(length(sequence));
end
% 交换
temp = sequence(pos1);
sequence(pos1) = sequence(pos2);
sequence(pos2) = temp;
% 修复序列
sequence = repair_sequence(sequence, problem);
end
function machine_assignment = mutate_machine_assignment(op_sequence, problem)
% 机器分配变异
machine_assignment = zeros(1, length(op_sequence));
for pos = 1:length(op_sequence)
op_code = op_sequence(pos);
job_id = floor(op_code / 100);
op_id = mod(op_code, 100);
% 获取可选机器
machine_options = problem.ops_machine{job_id, op_id};
n_options = size(machine_options, 1);
% 随机选择一台机器
selected_option = randi(n_options);
machine_assignment(pos) = machine_options(selected_option, 1);
end
end
function population = elitism(population, offspring, params)
% 精英保留策略
% 合并父代和子代
combined_population = [population; offspring];
% 按适应度排序
fitness_values = zeros(length(combined_population), 1);
for i = 1:length(combined_population)
fitness_values(i) = combined_population{i}.fitness;
end
[~, sorted_indices] = sort(fitness_values);
% 选择前pop_size个
population = cell(params.pop_size, 1);
for i = 1:params.pop_size
population{i} = combined_population{sorted_indices(i)};
end
end
2.6 解码与调度:decode_solution.m
function [schedule, makespan] = decode_solution(chromosome, problem)
% 解码染色体并生成调度方案
% 初始化调度表
schedule = struct();
schedule.job_completion = zeros(1, problem.n_jobs); % 工件完成时间
schedule.machine_available = zeros(1, problem.n_machines); % 机器可用时间
schedule.operations = cell(problem.total_ops, 1); % 工序调度信息
% 按工序顺序解码
for pos = 1:length(chromosome.op_sequence)
op_code = chromosome.op_sequence(pos);
machine_id = chromosome.machine_assignment(pos);
% 解码工序编码
job_id = floor(op_code / 100);
op_id = mod(op_code, 100);
% 获取加工时间
machine_options = problem.ops_machine{job_id, op_id};
processing_time = 0;
for k = 1:size(machine_options, 1)
if machine_options(k, 1) == machine_id
processing_time = machine_options(k, 2);
break;
end
end
% 计算开始时间
% 约束1:工件前一道工序完成
earliest_start_job = schedule.job_completion(job_id);
% 约束2:机器空闲
earliest_start_machine = schedule.machine_available(machine_id);
% 实际开始时间
start_time = max(earliest_start_job, earliest_start_machine);
end_time = start_time + processing_time;
% 更新状态
schedule.job_completion(job_id) = end_time;
schedule.machine_available(machine_id) = end_time;
% 记录工序信息
schedule.operations{pos} = struct(...
'job_id', job_id, ...
'op_id', op_id, ...
'machine_id', machine_id, ...
'start_time', start_time, ...
'end_time', end_time, ...
'processing_time', processing_time);
end
% 计算最大完工时间
makespan = max(schedule.job_completion);
end
2.7 结果可视化:visualize_results.m
function visualize_results(schedule, makespan, best_fitness_history, avg_fitness_history, problem)
% 可视化结果
figure('Name', 'FJSP基因演算法求解结果', 'Color', 'white', 'Position', [100, 100, 1400, 800]);
% 1. 甘特图
subplot(2,3,1);
draw_gantt_chart(schedule, problem);
title('最优调度甘特图');
% 2. 进化曲线
subplot(2,3,2);
plot(1:length(best_fitness_history), best_fitness_history, 'b-', 'LineWidth', 2, 'DisplayName', '最佳适应度');
hold on;
plot(1:length(avg_fitness_history), avg_fitness_history, 'r--', 'LineWidth', 2, 'DisplayName', '平均适应度');
xlabel('进化代数'); ylabel('适应度 (1/Makespan)');
title('基因演算法进化曲线');
legend('Location', 'best');
grid on;
% 3. 机器利用率
subplot(2,3,3);
machine_utilization = calculate_machine_utilization(schedule, makespan, problem);
bar(1:problem.n_machines, machine_utilization, 'FaceColor', 'cyan');
xlabel('机器编号'); ylabel('利用率 (%)');
title('机器利用率');
grid on;
% 4. 工件完成时间
subplot(2,3,4);
job_completion_times = extract_job_completion_times(schedule, problem);
barh(1:problem.n_jobs, job_completion_times, 'FaceColor', 'green');
xlabel('完成时间'); ylabel('工件编号');
title('工件完成时间');
grid on;
% 5. 关键路径分析
subplot(2,3,5);
critical_path = identify_critical_path(schedule, problem);
plot_critical_path(critical_path, problem);
title('关键路径分析');
% 6. 收敛性分析
subplot(2,3,6);
convergence_analysis(best_fitness_history);
title('收敛性分析');
end
function draw_gantt_chart(schedule, problem)
% 绘制甘特图
colors = lines(problem.n_machines);
for op_idx = 1:length(schedule.operations)
op = schedule.operations{op_idx};
if ~isempty(op)
% 绘制工序条
y_pos = op.machine_id - 0.4;
x_start = op.start_time;
x_width = op.end_time - op.start_time;
rectangle('Position', [x_start, y_pos, x_width, 0.8], ...
'FaceColor', colors(op.machine_id, :), ...
'EdgeColor', 'black', 'LineWidth', 0.5);
% 添加标签
text(x_start + x_width/2, op.machine_id, ...
sprintf('J%d-O%d', op.job_id, op.op_id), ...
'HorizontalAlignment', 'center', 'VerticalAlignment', 'middle', ...
'FontSize', 8, 'Color', 'white', 'FontWeight', 'bold');
end
end
% 设置坐标轴
xlabel('时间'); ylabel('机器编号');
ylim([0.5, problem.n_machines+0.5]);
yticks(1:problem.n_machines);
xlim([0, max(schedule.machine_available) * 1.1]);
grid on;
end
function machine_utilization = calculate_machine_utilization(schedule, makespan, problem)
% 计算机器利用率
machine_utilization = zeros(1, problem.n_machines);
for machine_id = 1:problem.n_machines
busy_time = 0;
for op_idx = 1:length(schedule.operations)
op = schedule.operations{op_idx};
if ~isempty(op) && op.machine_id == machine_id
busy_time = busy_time + op.processing_time;
end
end
machine_utilization(machine_id) = (busy_time / makespan) * 100;
end
end
function job_completion_times = extract_job_completion_times(schedule, problem)
% 提取工件完成时间
job_completion_times = zeros(1, problem.n_jobs);
for job_id = 1:problem.n_jobs
last_op_end_time = 0;
for op_idx = 1:length(schedule.operations)
op = schedule.operations{op_idx};
if ~isempty(op) && op.job_id == job_id
last_op_end_time = max(last_op_end_time, op.end_time);
end
end
job_completion_times(job_id) = last_op_end_time;
end
end
function critical_path = identify_critical_path(schedule, problem)
% 识别关键路径
critical_path = [];
max_end_time = 0;
for op_idx = 1:length(schedule.operations)
op = schedule.operations{op_idx};
if ~isempty(op) && op.end_time > max_end_time
max_end_time = op.end_time;
critical_path = [critical_path, op_idx];
end
end
end
function plot_critical_path(critical_path, problem)
% 绘制关键路径
if isempty(critical_path)
plot(0, 0, 'o');
axis off;
return;
end
% 简化的关键路径可视化
path_length = length(critical_path);
x = 1:path_length;
y = rand(1, path_length) * problem.n_machines;
plot(x, y, 'r-o', 'LineWidth', 2, 'MarkerSize', 8);
xlabel('路径步骤'); ylabel('机器编号');
title('关键路径');
grid on;
end
function convergence_analysis(fitness_history)
% 收敛性分析
% 计算收敛速度
convergence_gen = find(fitness_history == min(fitness_history), 1, 'last');
improvement_rate = (fitness_history(1) - fitness_history(end)) / fitness_history(1) * 100;
% 绘制收敛曲线
plot(1:length(fitness_history), fitness_history, 'b-', 'LineWidth', 2);
hold on;
plot(convergence_gen, fitness_history(convergence_gen), 'ro', 'MarkerSize', 10, 'LineWidth', 2);
% 添加注释
text(convergence_gen, fitness_history(convergence_gen), ...
sprintf('收敛点\n第%d代', convergence_gen), ...
'VerticalAlignment', 'bottom', 'HorizontalAlignment', 'right');
xlabel('进化代数'); ylabel('适应度');
title(sprintf('收敛性分析 (改进率: %.1f%%)', improvement_rate));
grid on;
end
三、扩展功能模块
3.1 多目标FJSP
function multi_objective_fjsp()
% 多目标FJSP:最小化Makespan、总流经时间、机器负载均衡
% 目标函数
objectives = {
@(schedule) schedule.makespan, % 目标1:最小化Makespan
@(schedule) calculate_total_flow_time(schedule), % 目标2:最小化总流经时间
@(schedule) calculate_load_balance(schedule) % 目标3:最小化机器负载方差
};
% 使用NSGA-II算法求解
pareto_front = nsga2_optimization(objectives, problem, params);
end
3.2 动态FJSP
function dynamic_fjsp()
% 动态FJSP:机器故障、紧急订单插入
% 事件驱动仿真
events = {
struct('time', 50, 'type', 'machine_breakdown', 'machine_id', 2),
struct('time', 100, 'type', 'urgent_order', 'job_id', 4)
};
% 实时重调度
for event = events
if event.time <= current_time
% 更新调度方案
updated_schedule = reschedule(event, current_schedule, problem);
current_schedule = updated_schedule;
end
end
end
参考代码 使用基因演算法解柔性作业车间调度问题 www.youwenfan.com/contentcnu/60168.html
四、应用建议
4.1 参数调优指南
| 参数 | 推荐范围 | 说明 |
|---|---|---|
| 种群大小 | 50-200 | 太小易早熟,太大计算慢 |
| 交叉概率 | 0.7-0.9 | 控制探索能力 |
| 变异概率 | 0.05-0.2 | 维持多样性 |
| 精英比例 | 0.05-0.2 | 保护优良基因 |
4.2 性能优化技巧
% 1. 并行计算加速
parfor gen = 1:params.max_gen
% 并行评估种群
end
% 2. 混合启发式
% 结合局部搜索(如模拟退火)改进GA解
improved_solution = hybrid_local_search(best_solution, problem);
% 3. 自适应参数
% 根据进化状态调整pc和pm
if diversity < threshold
pm = pm * 1.5; % 增加变异率
end
4.3 工业应用注意事项
- 实际约束:考虑机器维护、工人休息、物料供应等
- 不确定性:加工时间可能有波动,需要鲁棒调度
- 实时性:快速响应生产变化,支持动态重调度
- 可视化:提供直观的甘特图和报表,方便管理人员决策