import numpy as np
import pandas as pd
import matplotlib
# 设置matplotlib后端为Agg,避免PyCharm科学模式的问题
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import seaborn as sns
from typing import List, Dict, Tuple, Optional
import time
import random
import copy
from enum import Enum
import logging
from dataclasses import dataclass
from abc import ABC, abstractmethod
# 设置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# 设备类型枚举
class DeviceType(Enum):
JIA = "甲种设备"
YI = "乙种设备"
BING = "丙种设备"
# 工位枚举
class Workstation(Enum):
W1 = 1
W2 = 2
W3 = 3
W4 = 4
W5 = 5
W6 = 6
W7 = 7
W8 = 8
W9 = 9
# 轨道枚举
class Track(Enum):
TRACK1 = 1
TRACK2 = 2
@dataclass
class ProcessTime:
"""工序时间数据类"""
G01: int = 0
G02: int = 0
G03: int = 0
G04: int = 0
G05: int = 0
G06: int = 0
G07: int = 0
G08: int = 0
G09: int = 0
class Device:
"""设备类"""
def __init__(self, device_id: str, device_type: DeviceType, process_times: ProcessTime):
self.device_id = device_id
self.device_type = device_type
self.process_times = process_times
self.required_workstations = self._get_required_workstations()
def _get_required_workstations(self) -> List[Workstation]:
"""根据设备类型确定需要的工位"""
workstations = []
if self.device_type == DeviceType.JIA:
workstations = [Workstation.W7, Workstation.W8, Workstation.W9]
elif self.device_type == DeviceType.YI:
workstations = [Workstation.W4, Workstation.W5, Workstation.W6, Workstation.W7, Workstation.W8,
Workstation.W9]
elif self.device_type == DeviceType.BING:
workstations = [Workstation.W1, Workstation.W2, Workstation.W3, Workstation.W4,
Workstation.W5, Workstation.W6, Workstation.W7, Workstation.W8, Workstation.W9]
return workstations
def get_process_time(self, workstation: Workstation) -> int:
"""获取指定工位的处理时间"""
if workstation == Workstation.W1:
return self.process_times.G01
elif workstation == Workstation.W2:
return self.process_times.G02
elif workstation == Workstation.W3:
return self.process_times.G03
elif workstation == Workstation.W4:
return self.process_times.G04
elif workstation == Workstation.W5:
return self.process_times.G05
elif workstation == Workstation.W6:
return self.process_times.G06
elif workstation == Workstation.W7:
return self.process_times.G07
elif workstation == Workstation.W8:
return self.process_times.G08
elif workstation == Workstation.W9:
return self.process_times.G09
return 0
class WorkstationSchedule:
"""工位调度类"""
def __init__(self, workstation: Workstation):
self.workstation = workstation
self.schedule = [] # 存储(设备, 开始时间, 结束时间)元组
def add_device(self, device: Device, start_time: int) -> int:
"""添加设备到工位调度,返回结束时间"""
process_time = device.get_process_time(self.workstation)
end_time = start_time + process_time
# 检查是否有冲突
for _, s, e in self.schedule:
if not (end_time <= s or start_time >= e):
# 如果有冲突,调整开始时间
start_time = e
end_time = start_time + process_time
self.schedule.append((device, start_time, end_time))
self.schedule.sort(key=lambda x: x[1]) # 按开始时间排序
return end_time
def get_last_end_time(self) -> int:
"""获取最后结束时间"""
if not self.schedule:
return 0
return max(end_time for _, _, end_time in self.schedule)
class TrackSchedule:
"""轨道调度类"""
def __init__(self, track: Track, max_capacity: int = 3):
self.track = track
self.max_capacity = max_capacity
self.device_sequence = [] # 设备序列
self.workstation_schedules = {ws: WorkstationSchedule(ws) for ws in Workstation}
self.current_time = 0 # 轨道当前时间
def add_device(self, device: Device) -> int:
"""添加设备到轨道,返回完成时间"""
self.device_sequence.append(device)
# 计算设备在轨道上的开始时间
# 考虑轨道容量限制:最多同时有3台设备
if len(self.device_sequence) <= self.max_capacity:
start_time = 0
else:
# 设备必须等待前面的设备离开轨道
prev_device = self.device_sequence[-self.max_capacity - 1]
start_time = self.get_device_completion_time(prev_device)
# 按工序顺序处理设备
current_time = start_time
for workstation in device.required_workstations:
# 获取工位的最早可用时间
workstation_schedule = self.workstation_schedules[workstation]
available_time = self._get_workstation_available_time(workstation_schedule, current_time)
try:
end_time = workstation_schedule.add_device(device, available_time)
current_time = end_time
except ValueError as e:
logger.error(f"添加设备 {device.device_id} 到工位 {workstation.name} 时出错: {e}")
raise
return current_time
def _get_workstation_available_time(self, workstation_schedule: WorkstationSchedule, earliest_start: int) -> int:
"""获取工位在指定时间之后的最早可用时间"""
if not workstation_schedule.schedule:
return earliest_start
# 找到第一个可以容纳新设备的时间段
last_end = 0
for _, start, end in workstation_schedule.schedule:
if earliest_start <= start and (start - last_end) >= 0:
return max(earliest_start, last_end)
last_end = end
return max(earliest_start, last_end)
def get_device_completion_time(self, device: Device) -> int:
"""获取设备的完成时间"""
for ws in device.required_workstations:
for d, start, end in self.workstation_schedules[ws].schedule:
if d.device_id == device.device_id:
return end
return 0
def get_makespan(self) -> int:
"""获取轨道的最大完成时间"""
return max(ws.get_last_end_time() for ws in self.workstation_schedules.values())
class TestFacility:
"""测试场地类"""
def __init__(self):
self.tracks = {
Track.TRACK1: TrackSchedule(Track.TRACK1),
Track.TRACK2: TrackSchedule(Track.TRACK2)
}
def assign_device(self, device: Device, track: Track) -> int:
"""分配设备到指定轨道,返回完成时间"""
return self.tracks[track].add_device(device)
def get_makespan(self) -> int:
"""获取整个测试场地的最大完成时间"""
return max(track.get_makespan() for track in self.tracks.values())
def reset(self):
"""重置测试场地"""
self.tracks = {
Track.TRACK1: TrackSchedule(Track.TRACK1),
Track.TRACK2: TrackSchedule(Track.TRACK2)
}
class Chromosome:
"""遗传算法染色体类"""
def __init__(self, device_assignments: List[Tuple[Device, Track]]):
self.device_assignments = device_assignments # 设备分配列表
self.fitness = 0 # 适应度值
self.makespan = 0 # 最大完成时间
def evaluate(self, test_facility: TestFacility) -> int:
"""评估染色体,返回最大完成时间"""
test_facility.reset()
for device, track in self.device_assignments:
test_facility.assign_device(device, track)
self.makespan = test_facility.get_makespan()
self.fitness = 1 / self.makespan # 适应度与完成时间成反比
return self.makespan
def crossover(self, other: 'Chromosome') -> 'Chromosome':
"""交叉操作"""
# 单点交叉
crossover_point = random.randint(1, len(self.device_assignments) - 1)
new_assignments = self.device_assignments[:crossover_point] + other.device_assignments[crossover_point:]
return Chromosome(new_assignments)
def mutate(self, mutation_rate: float = 0.1):
"""变异操作"""
for i in range(len(self.device_assignments)):
if random.random() < mutation_rate:
# 随机改变设备分配轨道
device, _ = self.device_assignments[i]
new_track = random.choice([Track.TRACK1, Track.TRACK2])
self.device_assignments[i] = (device, new_track)
class GeneticAlgorithm:
"""遗传算法类"""
def __init__(self, population_size: int = 100, elitism_count: int = 10,
crossover_rate: float = 0.8, mutation_rate: float = 0.1):
self.population_size = population_size
self.elitism_count = elitism_count
self.crossover_rate = crossover_rate
self.mutation_rate = mutation_rate
self.population = []
self.best_chromosome = None
self.test_facility = TestFacility()
self.fitness_history = []
def initialize_population(self, devices: List[Device]):
"""初始化种群"""
self.population = []
for _ in range(self.population_size):
assignments = []
for device in devices:
track = random.choice([Track.TRACK1, Track.TRACK2])
assignments.append((device, track))
chromosome = Chromosome(assignments)
chromosome.evaluate(self.test_facility)
self.population.append(chromosome)
self.population.sort(key=lambda x: x.fitness, reverse=True)
self.best_chromosome = copy.deepcopy(self.population[0])
def selection(self) -> Chromosome:
"""选择操作 - 锦标赛选择"""
tournament_size = 3
tournament = random.sample(self.population, tournament_size)
tournament.sort(key=lambda x: x.fitness, reverse=True)
return tournament[0]
def evolve(self, devices: List[Device], generations: int = 100):
"""进化过程"""
self.initialize_population(devices)
for generation in range(generations):
# 评估种群
for chromosome in self.population:
chromosome.evaluate(self.test_facility)
# 排序种群
self.population.sort(key=lambda x: x.fitness, reverse=True)
# 更新最佳染色体
if self.population[0].fitness > self.best_chromosome.fitness:
self.best_chromosome = copy.deepcopy(self.population[0])
# 记录适应度历史
self.fitness_history.append(self.population[0].fitness)
# 创建新种群
new_population = []
# 保留精英
for i in range(self.elitism_count):
new_population.append(copy.deepcopy(self.population[i]))
# 生成剩余个体
while len(new_population) < self.population_size:
# 选择
parent1 = self.selection()
parent2 = self.selection()
# 交叉
if random.random() < self.crossover_rate:
child = parent1.crossover(parent2)
else:
child = copy.deepcopy(parent1)
# 变异
child.mutate(self.mutation_rate)
new_population.append(child)
self.population = new_population
if generation % 10 == 0:
logger.info(
f"Generation {generation}: Best Fitness = {self.best_chromosome.fitness}, Makespan = {self.best_chromosome.makespan}")
return self.best_chromosome
def plot_fitness_history(self):
"""绘制适应度历史"""
plt.figure(figsize=(10, 6))
plt.plot(self.fitness_history)
plt.title('Fitness History')
plt.xlabel('Generation')
plt.ylabel('Fitness')
plt.grid(True)
# 保存图像而不是显示
plt.savefig('fitness_history.png')
plt.close()
class DataLoader:
"""数据加载类"""
@staticmethod
def load_devices_from_excel(file_path: str) -> List[Device]:
"""从Excel文件加载设备数据"""
try:
# 读取Excel文件,使用第一行作为列名
df = pd.read_excel(file_path, sheet_name='Sheet1', header=0)
# 打印列名和前几行数据以进行调试
logger.info(f"数据列: {df.columns.tolist()}")
logger.info(f"前几行数据:\n{df.head()}")
# 清理数据:去除可能的空格和NaN值
df['设备编号'] = df['设备编号'].astype(str).str.strip()
# 过滤掉空设备编号的行
df = df[df['设备编号'] != 'nan']
df = df[df['设备编号'] != '']
# 过滤掉非设备编号的行(如"单位:min(分钟)")
valid_prefixes = ['J', 'Y', 'B']
df = df[df['设备编号'].str.startswith(tuple(valid_prefixes), na=False)]
# 检查是否有有效数据
if len(df) == 0:
raise ValueError("Excel文件中没有有效的设备数据")
# 填充NaN值为0
process_columns = ['G01工序', 'G02工序', 'G03工序', 'G04工序', 'G05工序', 'G06工序', 'G07工序', 'G08工序',
'G09工序']
for col in process_columns:
if col in df.columns:
df[col] = df[col].fillna(0).astype(int)
devices = []
for _, row in df.iterrows():
device_id = row['设备编号']
# 确定设备类型
if device_id.startswith('J'):
device_type = DeviceType.JIA
elif device_id.startswith('Y'):
device_type = DeviceType.YI
elif device_id.startswith('B'):
device_type = DeviceType.BING
else:
logger.warning(f"未知设备类型: {device_id}, 使用默认类型: 乙种设备")
device_type = DeviceType.YI
# 创建工序时间对象
process_times = ProcessTime(
G01=row.get('G01工序', 0),
G02=row.get('G02工序', 0),
G03=row.get('G03工序', 0),
G04=row.get('G04工序', 0),
G05=row.get('G05工序', 0),
G06=row.get('G06工序', 0),
G07=row.get('G07工序', 0),
G08=row.get('G08工序', 0),
G09=row.get('G09工序', 0)
)
devices.append(Device(device_id, device_type, process_times))
logger.info(f"成功加载 {len(devices)} 台设备")
return devices
except Exception as e:
logger.error(f"加载设备数据时出错: {e}")
logger.error(f"文件路径: {file_path}")
if 'df' in locals():
logger.error(f"数据形状: {df.shape}")
logger.error(
f"设备编号列的值: {df['设备编号'].unique() if '设备编号' in df.columns else '设备编号列不存在'}")
raise
class SolutionVisualizer:
"""解决方案可视化类"""
@staticmethod
def visualize_schedule(test_facility: TestFacility, chromosome: Chromosome):
"""可视化调度结果"""
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 10))
# 绘制轨道1的甘特图
track1_schedule = test_facility.tracks[Track.TRACK1]
SolutionVisualizer._plot_track_gantt(ax1, track1_schedule, "Track 1")
# 绘制轨道2的甘特图
track2_schedule = test_facility.tracks[Track.TRACK2]
SolutionVisualizer._plot_track_gantt(ax2, track2_schedule, "Track 2")
plt.tight_layout()
# 保存图像而不是显示
plt.savefig('schedule_visualization.png')
plt.close()
@staticmethod
def _plot_track_gantt(ax, track_schedule: TrackSchedule, title: str):
"""绘制单个轨道的甘特图"""
colors = sns.color_palette("hsv", len(Workstation))
workstation_colors = {ws: colors[i] for i, ws in enumerate(Workstation)}
for workstation in Workstation:
schedule = track_schedule.workstation_schedules[workstation].schedule
for device, start, end in schedule:
ax.barh(
y=workstation.value,
width=end - start,
left=start,
color=workstation_colors[workstation],
edgecolor='black',
alpha=0.7
)
ax.text(
x=start + (end - start) / 2,
y=workstation.value,
s=device.device_id,
ha='center',
va='center',
fontsize=8
)
ax.set_yticks([ws.value for ws in Workstation])
ax.set_yticklabels([ws.name for ws in Workstation])
ax.set_xlabel('Time (min)')
ax.set_title(title)
ax.grid(True, axis='x')
@staticmethod
def print_schedule_details(test_facility: TestFacility, chromosome: Chromosome):
"""打印调度详情"""
print("=" * 80)
print("批量设备测试流程优化结果")
print("=" * 80)
for track in [Track.TRACK1, Track.TRACK2]:
print(f"\n{track.name}:")
track_schedule = test_facility.tracks[track]
for workstation in Workstation:
schedule = track_schedule.workstation_schedules[workstation].schedule
if schedule:
print(f" {workstation.name}:")
for device, start, end in schedule:
print(f" {device.device_id}: {start}-{end} min")
print(f"\n总完成时间: {chromosome.makespan} min")
print("=" * 80)
# 使用示例
def main():
"""主函数"""
try:
# 加载数据
data_loader = DataLoader()
devices = data_loader.load_devices_from_excel('D:/地大/2025年数学竞赛/第二轮数学建模竞赛/C批量设备测试流程优化(1)/C 批量设备测试流程优化/附件1 每台设备工序测试时间表.xls')
# 创建遗传算法实例
ga = GeneticAlgorithm(
population_size=50,
elitism_count=5,
crossover_rate=0.8,
mutation_rate=0.1
)
# 运行遗传算法
start_time = time.time()
best_chromosome = ga.evolve(devices, generations=100)
end_time = time.time()
logger.info(f"优化完成,耗时: {end_time - start_time:.2f} 秒")
logger.info(f"最佳完成时间: {best_chromosome.makespan} min")
# 可视化结果
test_facility = TestFacility()
best_chromosome.evaluate(test_facility)
SolutionVisualizer.print_schedule_details(test_facility, best_chromosome)
SolutionVisualizer.visualize_schedule(test_facility, best_chromosome)
# 绘制适应度历史
ga.plot_fitness_history()
print("优化完成!结果已保存到文件中:")
print("- schedule_visualization.png: 甘特图")
print("- fitness_history.png: 适应度历史曲线")
except Exception as e:
logger.error(f"程序执行出错: {e}")
raise
if __name__ == "__main__":
main() 每一步具体是什么意思请用#解释出来