import copy import math import random import time # import matplotlib.pyplot as plt import numpy as np import openpyxl from base_optimizer.optimizer_common import * def dynamic_programming_cycle_path(pcb_data, cycle_placement, assigned_feeder): head_sequence = [] num_pos = sum([placement != -1 for placement in cycle_placement]) + 1 pos, head_set = [], [] feeder_set = set() for head, feeder in enumerate(assigned_feeder): if feeder == -1: continue head_set.append(head) placement = cycle_placement[head] pos.append([pcb_data.iloc[placement]['x'] - head * head_interval + stopper_pos[0], pcb_data.iloc[placement]['y'] + stopper_pos[1], pcb_data.iloc[placement]['r'], head]) feeder_set.add(feeder - head * interval_ratio) pos.insert(0, [slotf1_pos[0] + ((min(list(feeder_set)) + max(list(feeder_set))) / 2 - 1) * slot_interval, slotf1_pos[1], None, 0]) def get_distance(pos_1, pos_2): # 拾取起始与终止位置 或 非同轴 if pos_1[2] is None or pos_2[2] is None or pos_1[3] + (1 if pos_1[3] % 2 == 0 else -1) != pos_2[3]: return max(axis_moving_time(pos_1[0] - pos_2[0], 0), axis_moving_time(pos_1[1] - pos_2[1], 1)) else: return max(axis_moving_time(pos_1[0] - pos_2[0], 0), axis_moving_time(pos_1[1] - pos_2[1], 1), head_rotary_time(pos_1[2] - pos_2[2])) # 各节点之间的距离 dist = [[get_distance(pos_1, pos_2) for pos_2 in pos] for pos_1 in pos] min_dist = [[np.inf for _ in range(num_pos)] for s in range(1 << num_pos)] min_path = [[[] for _ in range(num_pos)] for s in range(1 << num_pos)] # 状压dp搜索 for s in range(1, 1 << num_pos, 2): # 考虑节点集合s必须包括节点0 if not (s & 1): continue for j in range(1, num_pos): # 终点j需在当前考虑节点集合s内 if not (s & (1 << j)): continue if s == int((1 << j) | 1): # 若考虑节点集合s仅含节点0和节点j,dp边界,赋予初值 # print('j:', j) min_path[s][j] = [j] min_dist[s][j] = dist[0][j] # 枚举下一个节点i,更新 for i in range(1, num_pos): # 下一个节点i需在考虑节点集合s外 if s & (1 << i): continue if min_dist[s][j] + dist[j][i] < min_dist[s | (1 << i)][i]: min_path[s | (1 << i)][i] = min_path[s][j] + [i] min_dist[s | (1 << i)][i] = min_dist[s][j] + dist[j][i] ans_dist = float('inf') ans_path = [] # 求最终最短哈密顿回路 for i in range(1, num_pos): if min_dist[(1 << num_pos) - 1][i] + dist[i][0] < ans_dist: # 更新,回路化 ans_path = min_path[s][i] ans_dist = min_dist[(1 << num_pos) - 1][i] + dist[i][0] for parent in ans_path: head_sequence.append(head_set[parent - 1]) start_head, end_head = head_sequence[0], head_sequence[-1] if pcb_data.iloc[cycle_placement[start_head]]['x'] - start_head * head_interval > \ pcb_data.iloc[cycle_placement[end_head]]['x'] - end_head * head_interval: head_sequence = list(reversed(head_sequence)) return ans_dist, head_sequence def dynamic_programming_cycle_route(place_pos: list[Point], pick_pos: Point, rtn_seq=True): head_sequence = [] num_pos = len(place_pos) + 1 pos = [pick_pos] + place_pos def get_distance(pos_1: Point, pos_2: Point): # 拾取起始与终止位置 或 非同轴 if pos_1.h is None or pos_2.h is None or pos_1.h + (1 if pos_1.h % 2 == 0 else -1) != pos_2.h: return max(axis_moving_time(pos_1.x - pos_2.x, 0), axis_moving_time(pos_1.y - pos_2.y, 1)) else: return max(axis_moving_time(pos_1.x - pos_2.x, 0), axis_moving_time(pos_1.y - pos_2.y, 1), head_rotary_time(pos_1.r - pos_2.r)) # 各节点之间的距离 dist = [[get_distance(pos_1, pos_2) for pos_2 in pos] for pos_1 in pos] min_dist = [[np.inf for _ in range(num_pos)] for s in range(1 << num_pos)] min_path = [[[] for _ in range(num_pos)] for s in range(1 << num_pos)] if rtn_seq else None # 状压dp搜索 for s in range(1, 1 << num_pos, 2): # 考虑节点集合s必须包括节点0 if not (s & 1): continue for j in range(1, num_pos): # 终点j需在当前考虑节点集合s内 if not (s & (1 << j)): continue if s == int((1 << j) | 1): # 若考虑节点集合s仅含节点0和节点j,dp边界,赋予初值 # print('j:', j) if min_path: min_path[s][j] = [j] min_dist[s][j] = dist[0][j] # 枚举下一个节点i,更新 for i in range(1, num_pos): # 下一个节点i需在考虑节点集合s外 if s & (1 << i): continue if min_dist[s][j] + dist[j][i] < min_dist[s | (1 << i)][i]: if min_path: min_path[s | (1 << i)][i] = min_path[s][j] + [i] min_dist[s | (1 << i)][i] = min_dist[s][j] + dist[j][i] ans_dist = float('inf') ans_path = [] # 求最终最短哈密顿回路 for i in range(1, num_pos): if min_dist[(1 << num_pos) - 1][i] + dist[i][0] < ans_dist: # 更新,回路化 if min_path: ans_path = min_path[s][i] ans_dist = min_dist[(1 << num_pos) - 1][i] + dist[i][0] if len(ans_path): for parent in ans_path: head_sequence.append(place_pos[parent - 1].h) if place_pos[ans_path[0] - 1].x > place_pos[ans_path[-1] - 1].x: head_sequence = list(reversed(head_sequence)) return ans_dist, head_sequence def quick_sort_cycle_route(place_pos: list[Point], pick_pos: Point, rtn_seq=True): cycle_place_pos = sorted(place_pos, key=lambda pt: pt.x) move_time = 0 for mount_seq in range(len(cycle_place_pos) - 1): if cycle_place_pos[mount_seq].h + (1 if cycle_place_pos[mount_seq].h % 2 == 0 else -1) \ != cycle_place_pos[mount_seq + 1].h: move_time += max(axis_moving_time(cycle_place_pos[mount_seq].x - cycle_place_pos[mount_seq + 1].x, 0), axis_moving_time(cycle_place_pos[mount_seq].y - cycle_place_pos[mount_seq + 1].y, 1)) else: move_time += max(axis_moving_time(cycle_place_pos[mount_seq].x - cycle_place_pos[mount_seq + 1].x, 0), axis_moving_time(cycle_place_pos[mount_seq].y - cycle_place_pos[mount_seq + 1].y, 1), head_rotary_time(cycle_place_pos[mount_seq].r - cycle_place_pos[mount_seq + 1].r)) delta_x = axis_moving_time(cycle_place_pos[0].x - pick_pos.x, 0) delta_y = axis_moving_time(cycle_place_pos[0].y - pick_pos.y, 1) move_time += max(delta_x, delta_y) + 0.01 * (delta_x ** 2 + delta_y ** 2) delta_x = axis_moving_time(cycle_place_pos[-1].x - pick_pos.x, 0) delta_y = axis_moving_time(cycle_place_pos[-1].y - pick_pos.y, 1) move_time += max(delta_x, delta_y) + 0.01 * (delta_x ** 2 + delta_y ** 2) head_seq = [place_pos.h for place_pos in cycle_place_pos] return move_time, head_seq @timer_wrapper def place_allocate_sequence_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result, hinter=True): placement_result, head_sequence_result = [], [] if len(pcb_data) == 0: return placement_result, head_sequence_result mount_point_index = [[] for _ in range(len(component_data))] mount_point_pos = [[] for _ in range(len(component_data))] for i in range(len(pcb_data)): part = pcb_data.iloc[i]['part'] component_index = component_data[component_data['part'] == part].index.tolist()[0] # 记录贴装点序号索引和对应的位置坐标 mount_point_index[component_index].append(i) mount_point_pos[component_index].append([pcb_data.iloc[i]['x'], pcb_data.iloc[i]['y']]) search_dir = 0 # 0:自左向右搜索 1:自右向左搜索 for cycle_set in range(len(component_result)): floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)]) for cycle in range(floor_cycle, ceil_cycle): # search_dir = 1 - search_dir assigned_placement = [-1] * max_head_index max_pos = [max(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0] min_pos = [min(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0] point2head_range = min(math.floor((max_pos - min_pos) / head_interval) + 1, max_head_index) # 最近邻确定 way_point = None head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index) for head_counter, head in enumerate(head_range): if component_result[cycle_set][head] == -1: continue component_index = component_result[cycle_set][head] if way_point is None: index = 0 if way_point is None: if search_dir: index = np.argmax(mount_point_pos[component_index], axis=0)[0] else: index = np.argmin(mount_point_pos[component_index], axis=0)[0] else: for next_head in head_range: component_index = component_result[cycle_set][next_head] if assigned_placement[next_head] == -1 and component_index != -1: num_points = len(mount_point_pos[component_index]) index = np.argmin( [abs(mount_point_pos[component_index][i][0] - way_point[0]) * .1 + abs( mount_point_pos[component_index][i][1] - way_point[1]) for i in range(num_points)]) # index = random.randint(0, num_points - 1) head = next_head break # index = np.argmax(mount_point_pos[component_index], axis=0)[0] assigned_placement[head] = mount_point_index[component_index][index] # 记录路标点 way_point = mount_point_pos[component_index][index] way_point[0] += (max_head_index - head - 1) * head_interval if search_dir else -head * head_interval mount_point_index[component_index].pop(index) mount_point_pos[component_index].pop(index) else: head_index, point_index = -1, -1 min_cheby_distance, min_euler_distance = float('inf'), float('inf') for next_head in range(max_head_index): if assigned_placement[next_head] != -1 or component_result[cycle_set][next_head] == -1: continue next_comp_index = component_result[cycle_set][next_head] for counter in range(len(mount_point_pos[next_comp_index])): if search_dir: delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0] + (max_head_index - next_head - 1) * head_interval) else: delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0] - next_head * head_interval) delta_y = abs(mount_point_pos[next_comp_index][counter][1] - way_point[1]) euler_distance = pow(axis_moving_time(delta_x, 0), 2) + pow(axis_moving_time(delta_y, 1), 2) cheby_distance = max(axis_moving_time(delta_x, 0), axis_moving_time(delta_y, 1)) + 5e-2 * euler_distance if cheby_distance < min_cheby_distance or (abs(cheby_distance - min_cheby_distance) < 1e-9 and euler_distance < min_euler_distance): # if euler_distance < min_euler_distance: min_cheby_distance, min_euler_distance = cheby_distance, euler_distance head_index, point_index = next_head, counter component_index = component_result[cycle_set][head_index] assert 0 <= head_index < max_head_index # point_index = random.randint(0, len(mount_point_index[component_index]) - 1) # 贴装点随机分配 assigned_placement[head_index] = mount_point_index[component_index][point_index] way_point = mount_point_pos[component_index][point_index] way_point[0] += (max_head_index - head_index - 1) * head_interval if search_dir \ else -head_index * head_interval mount_point_index[component_index].pop(point_index) mount_point_pos[component_index].pop(point_index) placement_result.append(assigned_placement) # 各个头上贴装的元件类型 head_sequence_result.append( dynamic_programming_cycle_path(pcb_data, assigned_placement, feeder_slot_result[cycle_set])[1]) return placement_result, head_sequence_result @timer_wrapper def beam_search_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result): beam_width = 4 # 集束宽度 base_points = [float('inf'), float('inf')] mount_point_index = [[] for _ in range(len(component_data))] mount_point_pos = [[] for _ in range(len(component_data))] for idx, data in pcb_data.iterrows(): component_index = component_data[component_data['part'] == data.part].index.tolist()[0] # 记录贴装点序号索引和对应的位置坐标 mount_point_index[component_index].append(idx) mount_point_pos[component_index].append([data.x, data.y]) # 记录最左下角坐标 base_points[0] = min(base_points[0], mount_point_pos[component_index][-1][0]) base_points[1] = min(base_points[1], mount_point_pos[component_index][-1][1]) beam_placement_sequence, beam_head_sequence = [], [] beam_mount_point_index, beam_mount_point_pos = [], [] for beam_counter in range(beam_width): beam_mount_point_index.append(copy.deepcopy(mount_point_index)) beam_mount_point_pos.append(copy.deepcopy(mount_point_pos)) beam_placement_sequence.append([]) beam_head_sequence.append([]) beam_distance = [0 for _ in range(beam_width)] # 记录当前集束搜索点的点数 def argpartition(list, kth): if kth < len(list): return np.argpartition(list, kth) else: index, indexes = 0, [] while len(indexes) < kth: indexes.append(index) index += 1 if index >= len(list): index = 0 return np.array(indexes) with tqdm(total=100) as pbar: search_dir = 0 pbar.set_description('beam search route schedule') for cycle_set in range(len(component_result)): floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)]) for cycle in range(floor_cycle, ceil_cycle): search_dir = 1 - search_dir beam_way_point = None for beam_counter in range(beam_width): beam_placement_sequence[beam_counter].append([-1 for _ in range(max_head_index)]) head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index) for head in head_range: component_index = component_result[cycle_set][head] if component_index == -1: continue if beam_way_point is None: # 首个贴装点的选取,距离基准点最近的beam_width个点 beam_way_point = [[0, 0]] * beam_width for beam_counter in range(beam_width): if search_dir: index = np.argmax(beam_mount_point_pos[beam_counter][component_index], axis=0)[0] else: index = np.argmin(beam_mount_point_pos[beam_counter][component_index], axis=0)[0] beam_placement_sequence[beam_counter][-1][head] = \ beam_mount_point_index[beam_counter][component_index][index] beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][index] beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \ search_dir else -head * head_interval beam_mount_point_index[beam_counter][component_index].pop(index) beam_mount_point_pos[beam_counter][component_index].pop(index) else: # 后续贴装点 search_beam_distance = [] search_beam_component_index = [0] * (beam_width ** 2) for beam_counter in range(beam_width ** 2): search_beam_distance.append(beam_distance[beam_counter // beam_width]) for beam_counter in range(beam_width): # 对于集束beam_counter + 1最近的beam_width个点 num_points = len(beam_mount_point_pos[beam_counter][component_index]) dist = [] for i in range(num_points): if search_dir: delta_x = axis_moving_time( beam_mount_point_pos[beam_counter][component_index][i][0] - beam_way_point[beam_counter][0] + (max_head_index - head - 1) * head_interval, 0) else: delta_x = axis_moving_time( beam_mount_point_pos[beam_counter][component_index][i][0] - beam_way_point[beam_counter][0] - head * head_interval, 0) delta_y = axis_moving_time(beam_mount_point_pos[beam_counter][component_index][i][1] - beam_way_point[beam_counter][1], 1) dist.append(max(delta_x, delta_y)) indexes = argpartition(dist, kth=beam_width)[:beam_width] # 记录中间信息 for i, index in enumerate(indexes): search_beam_distance[i + beam_counter * beam_width] += dist[index] search_beam_component_index[i + beam_counter * beam_width] = index indexes = np.argsort(search_beam_distance) beam_mount_point_pos_cpy = copy.deepcopy(beam_mount_point_pos) beam_mount_point_index_cpy = copy.deepcopy(beam_mount_point_index) beam_placement_sequence_cpy = copy.deepcopy(beam_placement_sequence) beam_head_sequence_cpy = copy.deepcopy(beam_head_sequence) beam_counter = 0 assigned_placement = [] for i, index in enumerate(indexes): # 拷贝原始集束数据 beam_mount_point_pos[beam_counter] = copy.deepcopy(beam_mount_point_pos_cpy[index // beam_width]) beam_mount_point_index[beam_counter] = copy.deepcopy(beam_mount_point_index_cpy[index // beam_width]) beam_placement_sequence[beam_counter] = copy.deepcopy(beam_placement_sequence_cpy[index // beam_width]) beam_head_sequence[beam_counter] = copy.deepcopy(beam_head_sequence_cpy[index // beam_width]) # 更新各集束最新扫描的的贴装点 component_index = component_result[cycle_set][head] beam_placement_sequence[beam_counter][-1][head] = \ beam_mount_point_index[beam_counter][component_index][search_beam_component_index[index]] if beam_placement_sequence[beam_counter][-1] in assigned_placement \ and beam_width - beam_counter < len(indexes) - i: continue assigned_placement.append(beam_placement_sequence[beam_counter][-1]) # 更新参考基准点 beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][ search_beam_component_index[index]] beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \ search_dir else -head * head_interval # 更新各集束贴装路径长度,移除各集束已分配的贴装点 beam_distance[beam_counter] = search_beam_distance[index] beam_mount_point_pos[beam_counter][component_index].pop(search_beam_component_index[index]) beam_mount_point_index[beam_counter][component_index].pop(search_beam_component_index[index]) beam_counter += 1 if beam_counter >= beam_width: break assert beam_counter >= beam_width # 更新头贴装顺序 for beam_counter in range(beam_width): beam_head_sequence[beam_counter].append( dynamic_programming_cycle_path(pcb_data, beam_placement_sequence[beam_counter][-1], feeder_slot_result[cycle_set])[1]) pbar.update(1 / sum(cycle_result) * 100) index = np.argmin(beam_distance) print('beam distance : ', beam_distance[index]) return beam_placement_sequence[index], beam_head_sequence[index] # @timer_wrapper def scan_based_placement_route_generation(component_data, pcb_data, component_assign, cycle_assign, feeder_slot_result, hinter=True): placement_result, head_sequence_result = [], [] mount_point_pos, mount_point_index, mount_point_angle, mount_point_part = [], [], [], [] for _, data in pcb_data.iterrows(): component_index = component_data[component_data.part == data.part].index.tolist()[0] # 记录贴装点序号索引和对应的位置坐标 mount_point_index.append(len(mount_point_index)) mount_point_pos.append([data.x + stopper_pos[0], data.y + stopper_pos[1]]) mount_point_angle.append(data.r) mount_point_part.append(component_index) left_boundary, right_boundary = min(mount_point_pos, key=lambda x: x[0])[0], max(mount_point_pos, key=lambda x: x[0])[0] search_step = max((right_boundary - left_boundary) / max_head_index / 2, 0) ref_pos_y = min(mount_point_pos, key=lambda x: x[1])[1] for cycle_index, component_cycle in enumerate(component_assign): for _ in range(cycle_assign[cycle_index]): min_dist = None tmp_assigned_placement, tmp_assigned_head_seq = [], [] tmp_mount_point_pos, tmp_mount_point_index = [], [] for search_dir in range(3): # 不同的搜索方向,贴装头和起始点的选取方法各不相同 if search_dir == 0: # 从左向右搜索 searchPoints = np.arange(left_boundary, (left_boundary + right_boundary) / 2, search_step) head_range = list(range(max_head_index)) elif search_dir == 1: # 从右向左搜索 searchPoints = np.arange(right_boundary + 1e-3, (left_boundary + right_boundary) / 2, -search_step) head_range = list(range(max_head_index - 1, -1, -1)) else: # 从中间向两边搜索 searchPoints = np.arange(left_boundary, right_boundary, search_step / 2) head_range, head_index = [], (max_head_index - 1) // 2 while head_index >= 0: if 2 * head_index != max_head_index - 1: head_range.append(max_head_index - 1 - head_index) head_range.append(head_index) head_index -= 1 for startPoint in searchPoints: mount_point_pos_cpy, mount_point_index_cpy = copy.deepcopy(mount_point_pos), copy.deepcopy( mount_point_index) mount_point_angle_cpy = copy.deepcopy(mount_point_angle) assigned_placement = [-1] * max_head_index assigned_mount_point = [[0, 0]] * max_head_index assigned_mount_angle = [0] * max_head_index head_counter, point_index = 0, -1 for head_index in head_range: if head_counter == 0: component_index = component_assign[cycle_index][head_index] if component_index == -1: continue min_horizontal_distance = None for index, mount_index in enumerate(mount_point_index_cpy): if mount_point_part[mount_index] != component_index: continue horizontal_distance = abs(mount_point_pos_cpy[index][0] - startPoint) + 0 * abs( mount_point_pos_cpy[index][1] - ref_pos_y) if min_horizontal_distance is None or horizontal_distance < min_horizontal_distance: min_horizontal_distance = horizontal_distance point_index = index else: point_index = -1 min_cheby_distance = None next_comp_index = component_assign[cycle_index][head_index] # if assigned_placement[head_index] != -1 or next_comp_index == -1: # continue for index, mount_index in enumerate(mount_point_index_cpy): if mount_point_part[mount_index] != next_comp_index: continue point_pos = [[mount_point_pos_cpy[index][0] - head_index * head_interval, mount_point_pos_cpy[index][1]]] cheby_distance, euler_distance = 0, 0 for next_head in range(max_head_index): if assigned_placement[next_head] == -1: continue point_pos.append(assigned_mount_point[next_head].copy()) point_pos[-1][0] -= next_head * head_interval point_pos = sorted(point_pos, key=lambda x: x[0]) for mount_seq in range(len(point_pos) - 1): delta_x = axis_moving_time(point_pos[mount_seq][0] - point_pos[mount_seq + 1][0], 0) delta_y = axis_moving_time(point_pos[mount_seq][1] - point_pos[mount_seq + 1][1], 1) cheby_distance += max(delta_x, delta_y) euler_distance += math.sqrt(delta_x ** 2 + delta_y ** 2) # cheby_distance += 0.01 * euler_distance if min_cheby_distance is None or cheby_distance < min_cheby_distance: min_cheby_distance, min_euler_distance = cheby_distance, euler_distance point_index = index if point_index == -1: continue head_counter += 1 assigned_placement[head_index] = mount_point_index_cpy[point_index] assigned_mount_point[head_index] = mount_point_pos_cpy[point_index].copy() assigned_mount_angle[head_index] = mount_point_angle_cpy[point_index] mount_point_index_cpy.pop(point_index) mount_point_pos_cpy.pop(point_index) mount_point_angle_cpy.pop(point_index) dist, head_seq = dynamic_programming_cycle_path(pcb_data, assigned_placement, feeder_slot_result[cycle_index]) if min_dist is None or dist < min_dist: tmp_mount_point_pos, tmp_mount_point_index = mount_point_pos_cpy, mount_point_index_cpy tmp_assigned_placement, tmp_assigned_head_seq = assigned_placement, head_seq min_dist = dist mount_point_pos, mount_point_index = tmp_mount_point_pos, tmp_mount_point_index placement_result.append(tmp_assigned_placement) head_sequence_result.append(tmp_assigned_head_seq) return placement_result, head_sequence_result # return placement_route_relink_heuristic(component_data, pcb_data, placement_result, head_sequence_result, # feeder_slot_result, cycle_assign) def placement_route_relink_heuristic(component_data, pcb_data, placement_result, head_sequence_result, feeder_slot_result, cycle_result, hinter=True): cycle_group_index = defaultdict(int) cycle_index = 0 for cycle_group, group in enumerate(cycle_result): for _ in range(group): cycle_group_index[cycle_index] = cycle_group cycle_index += 1 mount_point_pos, mount_point_angle, mount_point_index, mount_point_part = [], [], [], [] for i, data in pcb_data.iterrows(): component_index = component_data[component_data.part == data.part].index.tolist()[0] # 记录贴装点序号索引和对应的位置坐标 mount_point_index.append(i) mount_point_pos.append([data.x + stopper_pos[0], data.y + stopper_pos[1]]) mount_point_angle.append(data.r) mount_point_part.append(component_index) cycle_length, cycle_average_pos = [], [] for cycle, placement in enumerate(placement_result): # prev_pos, prev_angle = None, None cycle_pos_list = [] for idx, head in enumerate(head_sequence_result[cycle]): if point_index := placement[head] == -1: continue cycle_pos_list.append( [mount_point_pos[point_index][0] - head * head_interval, mount_point_pos[point_index][1]]) cycle_average_pos.append([sum(map(lambda pos: pos[0], cycle_pos_list)) / len(cycle_pos_list), sum(map(lambda pos: pos[1], cycle_pos_list)) / len(cycle_pos_list)]) cycle_length.append( dynamic_programming_cycle_path(pcb_data, placement, feeder_slot_result[cycle_group_index[cycle]])[0]) best_placement_result, best_head_sequence_result = copy.deepcopy(placement_result), copy.deepcopy( head_sequence_result) best_cycle_length, best_cycle_average_pos = copy.deepcopy(cycle_length), copy.deepcopy(cycle_average_pos) n_runningtime, n_iteration = 30, 0 start_time = time.time() with tqdm(total=n_runningtime) as pbar: pbar.set_description('swap heuristic process') prev_time = start_time while True: n_iteration += 1 placement_result, head_sequence_result = copy.deepcopy(best_placement_result), copy.deepcopy( best_head_sequence_result) cycle_length = best_cycle_length.copy() cycle_average_pos = copy.deepcopy(best_cycle_average_pos) cycle_index = roulette_wheel_selection(cycle_length) # 根据周期加权移动距离随机选择周期 point_dist = [] # 周期内各贴装点距离中心位置的切氏距离 for head in head_sequence_result[cycle_index]: point_index = placement_result[cycle_index][head] _delta_x = abs(mount_point_pos[point_index][0] - head * head_interval - cycle_average_pos[cycle_index][0]) _delta_y = abs(mount_point_pos[point_index][1] - cycle_average_pos[cycle_index][1]) point_dist.append(max(_delta_x, _delta_y)) # 随机选择一个异常点 head_index = head_sequence_result[cycle_index][roulette_wheel_selection(point_dist)] point_index = placement_result[cycle_index][head_index] # 找距离该异常点最近的周期 min_dist = None chg_cycle_index = -1 for idx in range(len(cycle_average_pos)): if idx == cycle_index: continue dist_ = 0 component_type_check = False for head in head_sequence_result[idx]: dist_ += max(abs(mount_point_pos[placement_result[idx][head]][0] - mount_point_pos[point_index][0]), abs(mount_point_pos[placement_result[idx][head]][1] - mount_point_pos[point_index][1])) if mount_point_part[placement_result[idx][head]] == mount_point_part[point_index]: component_type_check = True if (min_dist is None or dist_ < min_dist) and component_type_check: min_dist = dist_ chg_cycle_index = idx if chg_cycle_index == -1: continue chg_head, min_chg_dist = None, None chg_cycle_point = [] for head in head_sequence_result[chg_cycle_index]: index = placement_result[chg_cycle_index][head] chg_cycle_point.append([mount_point_pos[index][0] - head * head_interval, mount_point_pos[index][1]]) for idx, head in enumerate(head_sequence_result[chg_cycle_index]): chg_cycle_point_cpy = copy.deepcopy(chg_cycle_point) index = placement_result[chg_cycle_index][head] if mount_point_part[index] != mount_point_part[point_index]: continue chg_cycle_point_cpy[idx][0] = (mount_point_pos[index][0]) - head * head_interval chg_dist = 0 aver_chg_pos = [sum(map(lambda x: x[0], chg_cycle_point_cpy)) / len(chg_cycle_point_cpy), sum(map(lambda x: x[1], chg_cycle_point_cpy)) / len(chg_cycle_point_cpy)] for pos in chg_cycle_point_cpy: chg_dist += max(abs(aver_chg_pos[0] - pos[0]), abs(aver_chg_pos[1] - pos[1])) # 更换后各点距离中心更近 if min_chg_dist is None or chg_dist < min_chg_dist: chg_head = head min_chg_dist = chg_dist if chg_head is None: continue # === 第一轮,变更周期chg_cycle_index的贴装点重排 === chg_placement_res = placement_result[chg_cycle_index].copy() chg_placement_res[chg_head] = point_index cycle_point_list = defaultdict(list) for head, point in enumerate(chg_placement_res): if point == -1: continue cycle_point_list[mount_point_part[point]].append(point) for key, point_list in cycle_point_list.items(): cycle_point_list[key] = sorted(point_list, key=lambda p: mount_point_pos[p][0]) chg_placement_res = [] for head, point_index in enumerate(placement_result[chg_cycle_index]): if point_index == -1: chg_placement_res.append(-1) else: part = mount_point_part[point_index] chg_placement_res.append(cycle_point_list[part][0]) cycle_point_list[part].pop(0) chg_place_moving, chg_head_res = dynamic_programming_cycle_path(pcb_data, chg_placement_res, feeder_slot_result[ cycle_group_index[chg_cycle_index]]) # === 第二轮,原始周期cycle_index的贴装点重排 === placement_res = placement_result[cycle_index].copy() placement_res[head_index] = placement_result[chg_cycle_index][chg_head] for point in placement_res: if point == -1: continue cycle_point_list[mount_point_part[point]].append(point) for key, point_list in cycle_point_list.items(): cycle_point_list[key] = sorted(point_list, key=lambda p: mount_point_pos[p][0]) placement_res = [] for head, point_index in enumerate(placement_result[cycle_index]): if point_index == -1: placement_res.append(-1) else: part = mount_point_part[point_index] placement_res.append(cycle_point_list[part][0]) cycle_point_list[part].pop(0) place_moving, place_head_res = dynamic_programming_cycle_path(pcb_data, placement_res, feeder_slot_result[ cycle_group_index[cycle_index]]) # 更新贴装顺序分配结果 placement_result[cycle_index], head_sequence_result[cycle_index] = placement_res, place_head_res placement_result[chg_cycle_index], head_sequence_result[chg_cycle_index] = chg_placement_res, chg_head_res # 更新移动路径 cycle_length[cycle_index], cycle_length[chg_cycle_index] = place_moving, chg_place_moving # 更新平均坐标和最大偏离点索引 point_list, point_index_list = [], [] for head in head_sequence_result[cycle_index]: point_index_list.append(placement_result[cycle_index][head]) point_pos = mount_point_pos[point_index_list[-1]].copy() point_pos[0] -= head * head_interval point_list.append(point_pos) cycle_average_pos[cycle_index] = [sum(map(lambda x: x[0], point_list)) / len(point_list), sum(map(lambda x: x[1], point_list)) / len(point_list)] point_list, point_index_list = [], [] for head in head_sequence_result[chg_cycle_index]: point_index_list.append(placement_result[chg_cycle_index][head]) point_pos = mount_point_pos[point_index_list[-1]].copy() point_pos[0] -= head * head_interval point_list.append(point_pos) cycle_average_pos[chg_cycle_index] = [sum(map(lambda x: x[0], point_list)) / len(point_list), sum(map(lambda x: x[1], point_list)) / len(point_list)] if sum(cycle_length) < sum(best_cycle_length): best_cycle_length = cycle_length.copy() best_cycle_average_pos = copy.deepcopy(cycle_average_pos) best_placement_result, best_head_sequence_result = copy.deepcopy(placement_result), copy.deepcopy( head_sequence_result) cur_time = time.time() if cur_time - start_time > n_runningtime: break pbar.update(cur_time - prev_time) prev_time = cur_time # print("number of iteration: ", n_iteration) return best_placement_result, best_head_sequence_result class RouteNode: def __init__(self, component_data=None, pcb_data=None): self.cycle_time = 0 self.total_time = 0 self.placement_res = [] self.headseq_res = [] if component_data is not None and pcb_data is not None: self.unassigned_comp_point = [[] for _ in range(len(component_data))] for idx, data in pcb_data.iterrows(): component_index = component_data[component_data['part'] == data.part].index.tolist()[0] self.unassigned_comp_point[component_index].append(idx) else: self.unassigned_comp_point = [[] for _ in range(100)] def __lt__(self, other): return self.cycle_time < other.cycle_time @timer_wrapper def dynamic_beam_route_generation(component_data, pcb_data, component_assign, cycle_assign, feeder_assign): empty_node = RouteNode(component_data, pcb_data) max_beam_width = 4 point_pos = [Point(data.x + stopper_pos[0], data.y + stopper_pos[1], data.r) for _, data in pcb_data.iterrows()] left_boundary, right_boundary = min(point_pos, key=lambda pt: pt.x).x, max(point_pos, key=lambda pt: pt.x).x search_step = max((right_boundary - left_boundary) / max_head_index, 0) cycle_pick_pos = [] for feeder_slot in feeder_assign: slot_set = set() for head, slot in enumerate(feeder_slot): if slot == -1: continue slot_set.add(slot - head * interval_ratio) pick_pos = Point(slotf1_pos[0] + ((min(list(slot_set)) + max(list(slot_set)))/ 2 - 1) * slot_interval, slotf1_pos[1], 0) cycle_pick_pos.append(pick_pos) ref_node = None placement_result, head_sequence_result = [], [] start_time = time.time() for beam_width in range(1, max_beam_width + 1): beam_node_list = [copy.deepcopy(empty_node) for _ in range(beam_width)] cycle_index = 0 current_ref_node = RouteNode(component_data, pcb_data) for cycle_group_index, cycle_num in enumerate(cycle_assign): for _ in range(cycle_num): for beam_node in beam_node_list: beam_node.placement_res.append([-1 for _ in range(max_head_index)]) if ref_node: current_ref_node.placement_res.append([-1 for _ in range(max_head_index)]) beam_node_list_all = [] for search_dir in range(3): # 不同的搜索方向,贴装头和起始点的选取方法各不相同 if search_dir == 0: # 从左向右搜索 ref_pos_x_list = np.arange(left_boundary, (left_boundary + right_boundary) / 2, search_step) head_range = list(range(max_head_index)) elif search_dir == 1: # 从右向左搜索 ref_pos_x_list = np.arange(right_boundary + 1e-3, (left_boundary + right_boundary) / 2, -search_step) head_range = list(range(max_head_index - 1, -1, -1)) else: # 从中间向两边搜索 ref_pos_x_list = np.arange(left_boundary, right_boundary, search_step) head_range, head_index = [], (max_head_index - 1) // 2 while head_index >= 0: if 2 * head_index != max_head_index - 1: head_range.append(max_head_index - 1 - head_index) head_range.append(head_index) head_index -= 1 for ref_pos_x in ref_pos_x_list: beam_node_assigning_list = copy.deepcopy(beam_node_list) cur_assigning_ref_node = copy.deepcopy(current_ref_node) is_first_head = True for head_index in head_range: if (part_index := component_assign[cycle_group_index][head_index]) == -1: continue if is_first_head: is_first_head = False for beam_index, beam_node in enumerate(beam_node_assigning_list): point_index, min_horizontal_dist = -1, None for index in beam_node.unassigned_comp_point[part_index]: horizontal_dist = abs(point_pos[index].x - ref_pos_x) if min_horizontal_dist is None or horizontal_dist < min_horizontal_dist: min_horizontal_dist, point_index = horizontal_dist, index beam_node.placement_res[-1][head_index] = point_index beam_node.unassigned_comp_point[part_index].remove(point_index) else: beam_move_time, beam_indices, beam_point_indices = [], [], [] for beam_index, beam_node in enumerate(beam_node_assigning_list): cycle_place_pos = [] for next_head in range(max_head_index): if beam_node.placement_res[-1][next_head] == -1: continue cycle_place_pos.append(Point(point_pos[beam_node.placement_res[-1][ next_head]].x - next_head * head_interval, point_pos[ beam_node.placement_res[-1][next_head]].y, point_pos[ beam_node.placement_res[-1][next_head]].r, next_head)) cycle_place_pos.append([]) for point_index in beam_node.unassigned_comp_point[part_index]: cycle_place_pos[-1] = Point(point_pos[point_index].x - head_index * head_interval, point_pos[point_index].y, point_pos[point_index].r, head_index) move_time, _ = quick_sort_cycle_route(cycle_place_pos, cycle_pick_pos[cycle_group_index]) beam_move_time.append(move_time) beam_indices.append(beam_index) beam_point_indices.append(point_index) next_node_assigning_list, assigned_placement_res = [], [] for index in np.argsort(beam_move_time): tmp_placement_res = beam_node_assigning_list[beam_indices[index]].placement_res[-1].copy() tmp_placement_res[head_index] = beam_point_indices[index] if tmp_placement_res in assigned_placement_res: continue assigned_placement_res.append(tmp_placement_res) next_node_assigning_list.append( copy.deepcopy(beam_node_assigning_list[beam_indices[index]])) next_node_assigning_list[-1].cycle_time = beam_move_time[index] next_node_assigning_list[-1].unassigned_comp_point[part_index].remove( beam_point_indices[index]) next_node_assigning_list[-1].placement_res[-1][head_index] = beam_point_indices[index] if len(next_node_assigning_list) == beam_width: break beam_node_assigning_list = next_node_assigning_list if ref_node: point_index = ref_node.placement_res[cycle_index][head_index] cur_assigning_ref_node.placement_res[-1][head_index] = point_index cur_assigning_ref_node.unassigned_comp_point[part_index].remove(point_index) beam_node_assigning_list.append(copy.deepcopy(cur_assigning_ref_node)) beam_node_list_all.extend(beam_node_assigning_list) for beam_node in beam_node_list_all: beam_node.cycle_time = 0 cycle_place_pos = [] for head, index in enumerate(beam_node.placement_res[-1]): if index == -1: continue cycle_place_pos.append( Point(point_pos[index].x - head * head_interval, point_pos[index].y, point_pos[index].r, head)) cycle_time, headseq = dynamic_programming_cycle_route(cycle_place_pos, cycle_pick_pos[cycle_group_index]) beam_node.headseq_res.append(headseq) beam_node.total_time += cycle_time beam_node_list, assigned_placement_res = [], [] for index in np.argsort([beam_node.total_time for beam_node in beam_node_list_all]): if beam_node_list_all[index].placement_res in assigned_placement_res: continue assigned_placement_res.append(beam_node_list_all[index].placement_res) beam_node_list.append(beam_node_list_all[index]) if len(beam_node_list) == beam_width: break if ref_node: for head_index in range(max_head_index): if (part_index := component_assign[cycle_group_index][head_index]) == -1: continue point_index = ref_node.placement_res[cycle_index][head_index] current_ref_node.placement_res[-1][head_index] = point_index current_ref_node.unassigned_comp_point[part_index].remove(point_index) cycle_place_pos = [] for head, index in enumerate(current_ref_node.placement_res[-1]): if index == -1: continue cycle_place_pos.append( Point(point_pos[index].x - head * head_interval, point_pos[index].y, point_pos[index].r, head)) cycle_time, _ = dynamic_programming_cycle_route(cycle_place_pos, cycle_pick_pos[cycle_group_index]) current_ref_node.total_time += cycle_time current_ref_node.headseq_res.append(ref_node.headseq_res[cycle_index]) cycle_index += 1 min_idx = 0 for idx in range(1, len(beam_node_list)): if beam_node_list[min_idx].total_time > beam_node_list[idx].total_time: min_idx = idx print( f"current beam {beam_width}, assembly time: {beam_node_list[min_idx].total_time:.3f}, running time : " f"{time.time() - start_time:.3f} s") ref_node = copy.deepcopy(beam_node_list[min_idx]) placement_result, head_sequence_result = ref_node.placement_res, ref_node.headseq_res return placement_result, head_sequence_result class PAPSolution: def __init__(self, point_pos: list[Point], cycle_res, feeder_slot_res, placement_res, headseq_res): self.placement_res = placement_res self.headseq_res = headseq_res self.cycle_center_pos = [] self.cycle_pick_pos = [] self.cycle_move_time = [] self.unassigned_head = [] self.unassigned_component = [] cycle_index = 0 for cycle_group_index, cycle_group_num in enumerate(cycle_res): slot_set = set() for head, slot in enumerate(feeder_slot_res[cycle_group_index]): if slot == -1: continue slot_set.add(slot - head * interval_ratio) pick_pos = Point(slotf1_pos[0] + ((min(list(slot_set)) + max(list(slot_set))) / 2 - 1) * slot_interval, slotf1_pos[1], 0) for _ in range(cycle_group_num): self.cycle_pick_pos.append(pick_pos) for _ in range(cycle_group_num): center_pos = Point(0, 0) cycle_place_pos = [] for index, head in enumerate(headseq_res[cycle_index]): cycle_place_pos.append(Point(point_pos[placement_res[cycle_index][head]].x - head * head_interval, point_pos[placement_res[cycle_index][head]].y)) cycle_place_pos[-1].h = head center_pos.x = (1 - 1 / (index + 1)) * center_pos.x + cycle_place_pos[-1].x / (index + 1) center_pos.y = (1 - 1 / (index + 1)) * center_pos.y + cycle_place_pos[-1].y / (index + 1) self.cycle_center_pos.append(center_pos) self.cycle_move_time.append( dynamic_programming_cycle_route(cycle_place_pos, self.cycle_pick_pos[-1], rtn_seq=False)[0]) self.unassigned_head.append(None) self.unassigned_component.append(None) cycle_index += 1 def all_random_break(point_pos, comp_of_point, solution, ratio): destroy_solution = copy.deepcopy(solution) unassigned_points = [] num_of_cycle = len(solution.cycle_move_time) num_of_selected_cycle = round(num_of_cycle * ratio) selected_cycle = random.sample(range(0, num_of_cycle),num_of_selected_cycle) for cycle in selected_cycle: selected_head = random.sample(destroy_solution.headseq_res[cycle], 1)[0] # update center position head_num = len(destroy_solution.headseq_res[cycle]) - 1 point_index = destroy_solution.placement_res[cycle][selected_head] if head_num == 0: destroy_solution.cycle_center_pos[cycle] = Point(0, 0) else: destroy_solution.cycle_center_pos[cycle].x = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].x - (point_pos[point_index].x - selected_head * head_interval) / head_num destroy_solution.cycle_center_pos[cycle].y = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].y - point_pos[point_index].y / head_num # destroy operation selected_point = destroy_solution.placement_res[cycle][selected_head] destroy_solution.unassigned_head[cycle] = selected_head destroy_solution.unassigned_component[cycle] = comp_of_point[selected_point] destroy_solution.headseq_res[cycle].remove(selected_head) destroy_solution.placement_res[cycle][selected_head] = -1 unassigned_points.append(selected_point) return destroy_solution, unassigned_points def all_worst_break(point_pos, comp_of_point, solution, ratio): destroy_solution = copy.deepcopy(solution) unassigned_points = [] num_of_cycle = len(solution.cycle_move_time) num_of_selected_cycle = round(num_of_cycle * ratio) selected_cycle = np.argpartition(-np.array(destroy_solution.cycle_move_time), kth=num_of_selected_cycle)[ :num_of_selected_cycle] for cycle in selected_cycle: head_derivative = [] for head in destroy_solution.headseq_res[cycle]: delta_x = abs(point_pos[destroy_solution.placement_res[cycle][ head]].x - head * head_interval - destroy_solution.cycle_center_pos[cycle].x) delta_y = abs( point_pos[destroy_solution.placement_res[cycle][head]].y - destroy_solution.cycle_center_pos[cycle].y) head_derivative.append(math.sqrt(delta_x ** 2 + delta_y ** 2 + 1e-8)) selected_head = destroy_solution.headseq_res[cycle][np.argmax(head_derivative)] # update center position head_num = len(destroy_solution.headseq_res[cycle]) - 1 point_index = destroy_solution.placement_res[cycle][selected_head] if head_num == 0: destroy_solution.cycle_center_pos[cycle] = Point(0, 0) else: destroy_solution.cycle_center_pos[cycle].x = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].x - (point_pos[point_index].x - selected_head * head_interval) / head_num destroy_solution.cycle_center_pos[cycle].y = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].y - point_pos[point_index].y / head_num # destroy operation selected_point = destroy_solution.placement_res[cycle][selected_head] destroy_solution.unassigned_head[cycle] = selected_head destroy_solution.unassigned_component[cycle] = comp_of_point[selected_point] destroy_solution.headseq_res[cycle].remove(selected_head) destroy_solution.placement_res[cycle][selected_head] = -1 unassigned_points.append(selected_point) return destroy_solution, unassigned_points def all_weighted_break(point_pos, comp_of_point, solution, ratio): destroy_solution = copy.deepcopy(solution) unassigned_points = [] num_of_cycle = len(solution.cycle_move_time) num_of_selected_cycle = round(num_of_cycle * ratio) total_time = sum(solution.cycle_move_time) p = [cycle_time / total_time for cycle_time in solution.cycle_move_time] selected_cycle = np.random.choice(range(num_of_cycle), size=num_of_selected_cycle, p=p, replace=False) for cycle in selected_cycle: head_derivative = [] for head in destroy_solution.headseq_res[cycle]: delta_x = abs(point_pos[destroy_solution.placement_res[cycle][ head]].x - head * head_interval - destroy_solution.cycle_center_pos[cycle].x) delta_y = abs( point_pos[destroy_solution.placement_res[cycle][head]].y - destroy_solution.cycle_center_pos[cycle].y) head_derivative.append(math.sqrt(delta_x ** 2 + delta_y ** 2 + 1e-8)) selected_head = random.choices(destroy_solution.headseq_res[cycle], weights=head_derivative, k=1)[0] # update center position head_num = len(destroy_solution.headseq_res[cycle]) - 1 point_index = destroy_solution.placement_res[cycle][selected_head] if head_num == 0: destroy_solution.cycle_center_pos[cycle] = Point(0, 0) else: destroy_solution.cycle_center_pos[cycle].x = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].x - (point_pos[point_index].x - selected_head * head_interval) / head_num destroy_solution.cycle_center_pos[cycle].y = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].y - point_pos[point_index].y / head_num # destroy operation selected_point = destroy_solution.placement_res[cycle][selected_head] destroy_solution.unassigned_head[cycle] = selected_head destroy_solution.unassigned_component[cycle] = comp_of_point[selected_point] destroy_solution.headseq_res[cycle].remove(selected_head) destroy_solution.placement_res[cycle][selected_head] = -1 unassigned_points.append(selected_point) return destroy_solution, unassigned_points def weighted_head_random_cycle_break(point_pos, comp_of_point, solution, ratio): destroy_solution = copy.deepcopy(solution) unassigned_points = [] num_of_cycle = len(solution.cycle_move_time) num_of_selected_cycle = round(num_of_cycle * ratio) selected_cycle = random.sample(range(0, num_of_cycle), num_of_selected_cycle) for cycle in selected_cycle: head_derivative = [] for head in destroy_solution.headseq_res[cycle]: delta_x = abs(point_pos[destroy_solution.placement_res[cycle][ head]].x - head * head_interval - destroy_solution.cycle_center_pos[cycle].x) delta_y = abs( point_pos[destroy_solution.placement_res[cycle][head]].y - destroy_solution.cycle_center_pos[cycle].y) head_derivative.append(math.sqrt(delta_x ** 2 + delta_y ** 2 + 1e-8)) selected_head = random.choices(destroy_solution.headseq_res[cycle], weights=head_derivative, k=1)[0] # update center position head_num = len(destroy_solution.headseq_res[cycle]) - 1 point_index = destroy_solution.placement_res[cycle][selected_head] if head_num == 0: destroy_solution.cycle_center_pos[cycle] = Point(0, 0) else: destroy_solution.cycle_center_pos[cycle].x = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].x - (point_pos[point_index].x - selected_head * head_interval) / head_num destroy_solution.cycle_center_pos[cycle].y = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].y - point_pos[point_index].y / head_num # destroy operation selected_point = destroy_solution.placement_res[cycle][selected_head] destroy_solution.unassigned_head[cycle] = selected_head destroy_solution.unassigned_component[cycle] = comp_of_point[selected_point] destroy_solution.headseq_res[cycle].remove(selected_head) destroy_solution.placement_res[cycle][selected_head] = -1 unassigned_points.append(selected_point) return destroy_solution, unassigned_points def random_head_worst_cycle_break(point_pos, comp_of_point, solution, ratio): destroy_solution = copy.deepcopy(solution) unassigned_points = [] num_of_cycle = len(solution.cycle_move_time) num_of_selected_cycle = round(num_of_cycle * ratio) selected_cycle = np.argpartition(-np.array(destroy_solution.cycle_move_time), kth=num_of_selected_cycle)[ :num_of_selected_cycle] for cycle in selected_cycle: selected_head = random.sample(destroy_solution.headseq_res[cycle], 1)[0] # update center position head_num = len(destroy_solution.headseq_res[cycle]) - 1 point_index = destroy_solution.placement_res[cycle][selected_head] if head_num == 0: destroy_solution.cycle_center_pos[cycle] = Point(0, 0) else: destroy_solution.cycle_center_pos[cycle].x = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].x - (point_pos[point_index].x - selected_head * head_interval) / head_num destroy_solution.cycle_center_pos[cycle].y = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].y - point_pos[point_index].y / head_num # destroy operation selected_point = destroy_solution.placement_res[cycle][selected_head] destroy_solution.unassigned_head[cycle] = selected_head destroy_solution.unassigned_component[cycle] = comp_of_point[selected_point] destroy_solution.headseq_res[cycle].remove(selected_head) destroy_solution.placement_res[cycle][selected_head] = -1 unassigned_points.append(selected_point) return destroy_solution, unassigned_points def worst_head_weighted_cycle_break(point_pos, comp_of_point, solution, ratio): destroy_solution = copy.deepcopy(solution) unassigned_points = [] num_of_cycle = len(solution.cycle_move_time) num_of_selected_cycle = round(num_of_cycle * ratio) total_time = sum(solution.cycle_move_time) p = [cycle_time / total_time for cycle_time in solution.cycle_move_time] selected_cycle = np.random.choice(range(num_of_cycle), size=num_of_selected_cycle, p=p, replace=False) for cycle in selected_cycle: head_derivative = [] for head in destroy_solution.headseq_res[cycle]: delta_x = abs(point_pos[destroy_solution.placement_res[cycle][ head]].x - head * head_interval - destroy_solution.cycle_center_pos[cycle].x) delta_y = abs( point_pos[destroy_solution.placement_res[cycle][head]].y - destroy_solution.cycle_center_pos[cycle].y) head_derivative.append(math.sqrt(delta_x ** 2 + delta_y ** 2 + 1e-8)) selected_head = destroy_solution.headseq_res[cycle][np.argmax(head_derivative)] # update center position head_num = len(destroy_solution.headseq_res[cycle]) - 1 point_index = destroy_solution.placement_res[cycle][selected_head] if head_num == 0: destroy_solution.cycle_center_pos[cycle] = Point(0, 0) else: destroy_solution.cycle_center_pos[cycle].x = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].x - (point_pos[point_index].x - selected_head * head_interval) / head_num destroy_solution.cycle_center_pos[cycle].y = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].y - point_pos[point_index].y / head_num # destroy operation selected_point = destroy_solution.placement_res[cycle][selected_head] destroy_solution.unassigned_head[cycle] = selected_head destroy_solution.unassigned_component[cycle] = comp_of_point[selected_point] destroy_solution.headseq_res[cycle].remove(selected_head) destroy_solution.placement_res[cycle][selected_head] = -1 unassigned_points.append(selected_point) return destroy_solution, unassigned_points def random_head_weighted_cycle_break(point_pos, comp_of_point, solution, ratio): destroy_solution = copy.deepcopy(solution) unassigned_points = [] num_of_cycle = len(solution.cycle_move_time) num_of_selected_cycle = round(num_of_cycle * ratio) total_time = sum(solution.cycle_move_time) p = [cycle_time / total_time for cycle_time in solution.cycle_move_time] selected_cycle = np.random.choice(range(num_of_cycle), size=num_of_selected_cycle, p=p, replace=False) for cycle in selected_cycle: selected_head = random.sample(destroy_solution.headseq_res[cycle], 1)[0] # update center position head_num = len(destroy_solution.headseq_res[cycle]) - 1 point_index = destroy_solution.placement_res[cycle][selected_head] if head_num == 0: destroy_solution.cycle_center_pos[cycle] = Point(0, 0) else: destroy_solution.cycle_center_pos[cycle].x = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].x - (point_pos[point_index].x - selected_head * head_interval) / head_num destroy_solution.cycle_center_pos[cycle].y = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].y - point_pos[point_index].y / head_num # destroy operation selected_point = destroy_solution.placement_res[cycle][selected_head] destroy_solution.unassigned_head[cycle] = selected_head destroy_solution.unassigned_component[cycle] = comp_of_point[selected_point] destroy_solution.headseq_res[cycle].remove(selected_head) destroy_solution.placement_res[cycle][selected_head] = -1 unassigned_points.append(selected_point) return destroy_solution, unassigned_points def worst_head_random_cycle_break(point_pos, comp_of_point, solution, ratio): destroy_solution = copy.deepcopy(solution) unassigned_points = [] num_of_cycle = len(solution.cycle_move_time) num_of_selected_cycle = round(num_of_cycle * ratio) selected_cycle = random.sample(range(0, num_of_cycle), num_of_selected_cycle) for cycle in selected_cycle: head_derivative = [] for head in destroy_solution.headseq_res[cycle]: delta_x = abs(point_pos[destroy_solution.placement_res[cycle][ head]].x - head * head_interval - destroy_solution.cycle_center_pos[cycle].x) delta_y = abs( point_pos[destroy_solution.placement_res[cycle][head]].y - destroy_solution.cycle_center_pos[cycle].y) head_derivative.append(math.sqrt(delta_x ** 2 + delta_y ** 2 + 1e-8)) selected_head = destroy_solution.headseq_res[cycle][np.argmax(head_derivative)] # update center position head_num = len(destroy_solution.headseq_res[cycle]) - 1 point_index = destroy_solution.placement_res[cycle][selected_head] if head_num == 0: destroy_solution.cycle_center_pos[cycle] = Point(0, 0) else: destroy_solution.cycle_center_pos[cycle].x = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].x - (point_pos[point_index].x - selected_head * head_interval) / head_num destroy_solution.cycle_center_pos[cycle].y = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].y - point_pos[point_index].y / head_num # destroy operation selected_point = destroy_solution.placement_res[cycle][selected_head] destroy_solution.unassigned_head[cycle] = selected_head destroy_solution.unassigned_component[cycle] = comp_of_point[selected_point] destroy_solution.headseq_res[cycle].remove(selected_head) destroy_solution.placement_res[cycle][selected_head] = -1 unassigned_points.append(selected_point) return destroy_solution, unassigned_points def weighted_head_worst_cycle_break(point_pos, comp_of_point, solution, ratio): destroy_solution = copy.deepcopy(solution) unassigned_points = [] num_of_cycle = len(solution.cycle_move_time) num_of_selected_cycle = round(num_of_cycle * ratio) selected_cycle = np.argpartition(-np.array(destroy_solution.cycle_move_time), kth=num_of_selected_cycle)[ :num_of_selected_cycle] for cycle in selected_cycle: head_derivative = [] for head in destroy_solution.headseq_res[cycle]: delta_x = abs(point_pos[destroy_solution.placement_res[cycle][ head]].x - head * head_interval - destroy_solution.cycle_center_pos[cycle].x) delta_y = abs( point_pos[destroy_solution.placement_res[cycle][head]].y - destroy_solution.cycle_center_pos[cycle].y) head_derivative.append(math.sqrt(delta_x ** 2 + delta_y ** 2 + 1e-8)) selected_head = random.choices(destroy_solution.headseq_res[cycle], weights=head_derivative, k=1)[0] # update center position head_num = len(destroy_solution.headseq_res[cycle]) - 1 point_index = destroy_solution.placement_res[cycle][selected_head] if head_num == 0: destroy_solution.cycle_center_pos[cycle] = Point(0, 0) else: destroy_solution.cycle_center_pos[cycle].x = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].x - (point_pos[point_index].x - selected_head * head_interval) / head_num destroy_solution.cycle_center_pos[cycle].y = (1 + 1.0 / head_num) * destroy_solution.cycle_center_pos[ cycle].y - point_pos[point_index].y / head_num # destroy operation selected_point = destroy_solution.placement_res[cycle][selected_head] destroy_solution.unassigned_head[cycle] = selected_head destroy_solution.unassigned_component[cycle] = comp_of_point[selected_point] destroy_solution.headseq_res[cycle].remove(selected_head) destroy_solution.placement_res[cycle][selected_head] = -1 unassigned_points.append(selected_point) return destroy_solution, unassigned_points def random_repair(point_pos, comp_of_point, solution, unassigned_points): for point in unassigned_points: unassigned_cycle_list = [] for cycle, head in enumerate(solution.unassigned_head): if head is not None and solution.unassigned_component[cycle] == comp_of_point[point]: unassigned_cycle_list.append(cycle) selected_cycle = random.sample(unassigned_cycle_list, 1)[0] selected_head = solution.unassigned_head[selected_cycle] # update center pos、move time and head sequence cycle_place_pos = [ Point(point_pos[point].x - selected_head * head_interval, point_pos[point].y, point_pos[point].r, selected_head)] for head in solution.headseq_res[selected_cycle]: cycle_place_pos.append( Point(point_pos[solution.placement_res[selected_cycle][head]].x - head * head_interval, point_pos[solution.placement_res[selected_cycle][head]].y, point_pos[solution.placement_res[selected_cycle][head]].r, head)) solution.cycle_move_time[selected_cycle], solution.headseq_res[ selected_cycle] = dynamic_programming_cycle_route(cycle_place_pos, solution.cycle_pick_pos[selected_cycle]) num_head = len(solution.headseq_res[selected_cycle]) solution.cycle_center_pos[selected_cycle].x = (1 - 1.0 / num_head) * solution.cycle_center_pos[ selected_cycle].x + (point_pos[point].x - selected_head * head_interval) / num_head solution.cycle_center_pos[selected_cycle].y = (1 - 1.0 / num_head) * solution.cycle_center_pos[ selected_cycle].y + point_pos[point].y / num_head solution.placement_res[selected_cycle][selected_head] = point solution.unassigned_head[selected_cycle] = None solution.unassigned_component[selected_cycle] = None return solution def greedy_repair(point_pos, comp_of_point, solution, unassigned_points): for point in unassigned_points: unassigned_cycle_list, unassigned_cycle_dist = [], [] for cycle, head in enumerate(solution.unassigned_head): if head is not None and solution.unassigned_component[cycle] == comp_of_point[point]: unassigned_cycle_list.append(cycle) delta_x = abs(solution.cycle_center_pos[cycle].x - point_pos[point].x + head * head_interval) delta_y = abs(solution.cycle_center_pos[cycle].y - point_pos[point].y) unassigned_cycle_dist.append(math.sqrt(delta_x ** 2 + delta_y ** 2) + 1e-10) selected_cycle = unassigned_cycle_list[np.argmin(unassigned_cycle_dist)] selected_head = solution.unassigned_head[selected_cycle] # update center pos、move time and head sequence cycle_place_pos = [ Point(point_pos[point].x - selected_head * head_interval, point_pos[point].y, point_pos[point].r, selected_head)] for head in solution.headseq_res[selected_cycle]: cycle_place_pos.append( Point(point_pos[solution.placement_res[selected_cycle][head]].x - head * head_interval, point_pos[solution.placement_res[selected_cycle][head]].y, point_pos[solution.placement_res[selected_cycle][head]].r, head)) solution.cycle_move_time[selected_cycle], solution.headseq_res[ selected_cycle] = dynamic_programming_cycle_route(cycle_place_pos, solution.cycle_pick_pos[selected_cycle]) num_head = len(solution.headseq_res[selected_cycle]) solution.cycle_center_pos[selected_cycle].x = (1 - 1.0 / num_head) * solution.cycle_center_pos[ selected_cycle].x + (point_pos[point].x - selected_head * head_interval) / num_head solution.cycle_center_pos[selected_cycle].y = (1 - 1.0 / num_head) * solution.cycle_center_pos[ selected_cycle].y + point_pos[point].y / num_head solution.placement_res[selected_cycle][selected_head] = point solution.unassigned_head[selected_cycle] = None solution.unassigned_component[selected_cycle] = None return solution def weighted_repair(point_pos, comp_of_point, solution, unassigned_points): for point in unassigned_points: unassigned_cycle_list, unassigned_cycle_dist = [], [] for cycle, head in enumerate(solution.unassigned_head): if head is not None and solution.unassigned_component[cycle] == comp_of_point[point]: unassigned_cycle_list.append(cycle) delta_x = abs(solution.cycle_center_pos[cycle].x - point_pos[point].x + head * head_interval) delta_y = abs(solution.cycle_center_pos[cycle].y - point_pos[point].y) unassigned_cycle_dist.append(math.sqrt(delta_x ** 2 + delta_y ** 2) + 1e-10) selected_cycle = random.choices(unassigned_cycle_list, weights=unassigned_cycle_dist, k=1)[0] selected_head = solution.unassigned_head[selected_cycle] # update center pos、move time and head sequence cycle_place_pos = [ Point(point_pos[point].x - selected_head * head_interval, point_pos[point].y, point_pos[point].r, selected_head)] for head in solution.headseq_res[selected_cycle]: cycle_place_pos.append( Point(point_pos[solution.placement_res[selected_cycle][head]].x - head * head_interval, point_pos[solution.placement_res[selected_cycle][head]].y, point_pos[solution.placement_res[selected_cycle][head]].r, head)) solution.cycle_move_time[selected_cycle], solution.headseq_res[ selected_cycle] = dynamic_programming_cycle_route(cycle_place_pos, solution.cycle_pick_pos[selected_cycle]) num_head = len(solution.headseq_res[selected_cycle]) solution.cycle_center_pos[selected_cycle].x = (1 - 1.0 / num_head) * solution.cycle_center_pos[ selected_cycle].x + (point_pos[point].x - selected_head * head_interval) / num_head solution.cycle_center_pos[selected_cycle].y = (1 - 1.0 / num_head) * solution.cycle_center_pos[ selected_cycle].y + point_pos[point].y / num_head solution.placement_res[selected_cycle][selected_head] = point solution.unassigned_head[selected_cycle] = None solution.unassigned_component[selected_cycle] = None return solution def select_and_apply_destroy_operator(point_pos, comp_of_point, weight, solution, ratio=0.3): destroy_operator_map = { 0: all_random_break, 1: all_worst_break, 2: all_weighted_break, 3: weighted_head_random_cycle_break, 4: random_head_worst_cycle_break, 5: worst_head_weighted_cycle_break, 6: random_head_weighted_cycle_break, 7: worst_head_random_cycle_break, 8: weighted_head_worst_cycle_break, } assert len(destroy_operator_map) == len(weight) destroy_index, destroy_solution, unassigned_points = -1, solution, [] roulette = np.array(weight).cumsum() r = random.uniform(0, max(roulette)) for index in range(len(destroy_operator_map)): if roulette[index] >= r: destroy_index = index destroy_solution, unassigned_points = destroy_operator_map[index](point_pos, comp_of_point, solution, ratio) break return destroy_index, destroy_solution, unassigned_points def select_and_apply_repair_operator(point_pos, comp_of_point, weight, solution, unassigned_points): repair_operator_map = { 0: random_repair, 1: greedy_repair, 2: weighted_repair, } assert len(repair_operator_map) == len(weight) repair_index, repair_solution = -1, solution roulette = np.array(weight).cumsum() r = random.uniform(0, max(roulette)) for index in range(len(weight)): if roulette[index] >= r: repair_index = index repair_solution = repair_operator_map[index](point_pos, comp_of_point, solution, unassigned_points) break return repair_index, repair_solution @timer_wrapper def alns_route_reschedule(pcb_data, placement_res, headseq_res, component_res, feeder_slot_res, cycle_res, hinter=True): point_pos = [Point(data.x + stopper_pos[0], data.y + stopper_pos[1], data.r) for _, data in pcb_data.iterrows()] comp_of_point = defaultdict(int) cycle_index = 0 for cycle_group_index, cycle_group_num in enumerate(cycle_res): for _ in range(cycle_group_num): for index, head in enumerate(headseq_res[cycle_index]): comp_of_point[placement_res[cycle_index][head]] = component_res[cycle_group_index][head] cycle_index += 1 init_temperature, final_temperature = 1, 0.2 alpha, beta = 0.8, 0.5 solution = PAPSolution(point_pos, cycle_res, feeder_slot_res, placement_res, headseq_res) num_destroy_operators, num_repair_operators = 9, 3 destroy_weight, repair_weight = [1 for _ in range(num_destroy_operators)], [1 for _ in range(num_repair_operators)] destroy_score, repair_score = [1 for _ in range(num_destroy_operators)], [1 for _ in range(num_repair_operators)] destroy_times, repair_times = [0 for _ in range(num_destroy_operators)], [0 for _ in range(num_repair_operators)] best_solution = copy.deepcopy(solution) max_iteration = 2000 # === 初始化各周期的移动路径长度,中心位置坐标和未分配的贴装头 === omega_new_global_best, omega_better_than_current, omega_accept, omega_reject = 3, 2, 0.8, 0.2 best_move_time_list = [] with tqdm(total=max_iteration) as pbar: pbar.set_description('adaptive large neighbor search route reschedule') for _ in range(max_iteration): temperature = init_temperature solution = best_solution while temperature > final_temperature: destroy_index, destroy_solution, unassigned_points = select_and_apply_destroy_operator(point_pos, comp_of_point, destroy_weight, solution) repair_index, repair_solution = select_and_apply_repair_operator(point_pos, comp_of_point, repair_weight, destroy_solution, unassigned_points) if sum(repair_solution.cycle_move_time) <= sum(solution.cycle_move_time): solution = repair_solution if sum(repair_solution.cycle_move_time) <= sum(best_solution.cycle_move_time): best_solution = repair_solution destroy_score[destroy_index] += omega_new_global_best repair_score[repair_index] += omega_new_global_best else: destroy_score[destroy_index] += omega_better_than_current repair_score[repair_index] += omega_better_than_current else: if random.random() < np.exp( (sum(solution.cycle_move_time) - sum(repair_solution.cycle_move_time)) / temperature): solution = repair_solution destroy_score[destroy_index] += omega_accept repair_score[repair_index] += omega_accept else: destroy_score[destroy_index] += omega_reject repair_score[repair_index] += omega_reject best_move_time_list.append(sum(best_solution.cycle_move_time)) destroy_times[destroy_index] += 1 repair_times[repair_index] += 1 destroy_weight[destroy_index] = destroy_weight[destroy_index] * beta + (1 - beta) * destroy_score[ destroy_index] / destroy_times[destroy_index] repair_weight[repair_index] = repair_weight[repair_index] * beta + (1 - beta) * repair_score[ repair_index] / repair_times[repair_index] temperature *= alpha pbar.update(1) # plt.plot(range(len(best_move_time_list)), best_move_time_list) # plt.show() print('best move time', best_move_time_list[-1]) workbook = openpyxl.load_workbook('result/alns_route.xlsx') worksheet = workbook.active writing_colum = None for col in worksheet.iter_cols(min_col=0, max_col=100): for cell in col: if cell.value is None: writing_colum = cell.column_letter break if writing_colum: break for row_num, value in enumerate(best_move_time_list): cell_reference = f"{writing_colum}{row_num + 1}" worksheet[cell_reference] = value workbook.save('result/alns_route.xlsx') return best_solution.placement_res, best_solution.headseq_res def place_cluster_greedy_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result): placement_result, head_sequence_result = [], [] # === assign CT group to feeder slot === component_point_pos = defaultdict(list) for idx, data in pcb_data.iterrows(): component_point_pos[data.part].append([data.x + stopper_pos[0], data.y + stopper_pos[1], idx]) for pos_list in component_point_pos.values(): pos_list.sort(key=lambda x: (x[0], x[1])) component_point_index = defaultdict(int) for cycle_set in range(len(cycle_result)): for cycle in range(cycle_result[cycle_set]): placement_result.append([-1 for _ in range(max_head_index)]) for head in range(max_head_index): part_index = component_result[cycle_set][head] if part_index == -1: continue part = component_data.iloc[part_index]['part'] point_info = component_point_pos[part][component_point_index[part]] placement_result[-1][head] = point_info[2] # mount_point[head] = point_info[0:2] component_point_index[part] += 1 head_sequence_result.append( dynamic_programming_cycle_path(pcb_data, placement_result[-1], feeder_slot_result[cycle_set])[1]) return placement_result, head_sequence_result def greedy_level_placing_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result, hinter=False): placement_result, head_sequence = [], [] part_indices = defaultdict(int) for part_idx, data in component_data.iterrows(): part_indices[data.part] = part_idx mount_point_pos = defaultdict(list) for pcb_idx, data in pcb_data.iterrows(): mount_point_pos[part_indices[data.part]].append([data.x, data.y, pcb_idx]) for index_ in mount_point_pos.keys(): mount_point_pos[index_].sort(key=lambda x: (x[1], x[0])) for cycle_idx, _ in enumerate(cycle_result): for _ in range(cycle_result[cycle_idx]): placement_result.append([-1 for _ in range(max_head_index)]) for head in range(max_head_index): if component_result[cycle_idx][head] == -1: continue index_ = component_result[cycle_idx][head] placement_result[-1][head] = mount_point_pos[index_][-1][2] mount_point_pos[index_].pop() head_sequence.append( dynamic_programming_cycle_path(pcb_data, placement_result[-1], feeder_slot_result[cycle_idx])[1]) return placement_result, head_sequence