From 045f2f394d23361eb922a38a025611a6be194b61 Mon Sep 17 00:00:00 2001 From: hit_lu Date: Fri, 1 Nov 2024 09:14:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=A2=84=E5=AE=89=E8=A3=85?= =?UTF-8?q?=E4=BE=9B=E6=96=99=E5=99=A8=E5=8A=9F=E8=83=BD=E3=80=81=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E8=A7=84=E5=88=92=E6=A8=A1=E5=9E=8B=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=8D=95=E7=82=B9=E3=80=81=E6=95=B4=E7=BA=BF=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=89=B9=E9=87=8F=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base_optimizer/optimizer_common.py | 370 +------------ base_optimizer/optimizer_interface.py | 18 +- base_optimizer/result_analysis.py | 13 +- base_optimizer/smopt_celldivision.py | 27 +- base_optimizer/smopt_feederpriority.py | 75 +-- base_optimizer/smopt_hybridgenetic.py | 7 +- base_optimizer/smopt_mathmodel.py | 126 +++-- base_optimizer/smopt_twophase.py | 430 ++------------- base_optimizer/smtopt_route.py | 706 +++++++++++++++++++++++++ dataloader.py | 62 ++- estimator.py | 3 +- generator.py | 2 +- lineopt_hyperheuristic.py | 3 - lineopt_model.py | 20 +- optimizer.py | 64 ++- 15 files changed, 990 insertions(+), 936 deletions(-) create mode 100644 base_optimizer/smtopt_route.py diff --git a/base_optimizer/optimizer_common.py b/base_optimizer/optimizer_common.py index 60a075d..a6c7232 100644 --- a/base_optimizer/optimizer_common.py +++ b/base_optimizer/optimizer_common.py @@ -47,10 +47,10 @@ x_max_velocity, y_max_velocity = 1.4, 1.2 x_max_acceleration, y_max_acceleration = x_max_velocity / 0.079, y_max_velocity / 0.079 # TODO: 不同种类供料器宽度 -feeder_width = {'SM8': (7.25, 7.25), 'SM12': (7.25, 7.25), 'SM16': (7.25, 7.25), - 'SM24': (7.25, 7.25), 'SM32': (7.25, 7.25)} -# feeder_width = {'SM8': (7.25, 7.25), 'SM12': (7.00, 20.00), 'SM16': (7.00, 22.00), -# 'SM24': (7.00, 29.00), 'SM32': (7.00, 44.00)} +# feeder_width = {'SM8': (7.25, 7.25), 'SM12': (7.25, 7.25), 'SM16': (7.25, 7.25), + # 'SM24': (7.25, 7.25), 'SM32': (7.25, 7.25)} +feeder_width = {'SM8': (7.25, 7.25), 'SM12': (7.00, 20.00), 'SM16': (7.00, 22.00), + 'SM24': (7.00, 29.00), 'SM32': (7.00, 44.00)} # 可用吸嘴数量限制 nozzle_limit = {'CN065': 6, 'CN040': 6, 'CN020': 6, 'CN400': 6, 'CN140': 6} @@ -67,7 +67,7 @@ t_fix_camera_check = 0.12 # 固定相机检测时间 T_pp, T_tr, T_nc, T_pl = 2, 5, 25, 0 # 时间参数 (数据拟合获得) -Fit_cy, Fit_nz, Fit_pu, Fit_pl, Fit_mv = 0.326, 0.8694, 0.159, 0.041, 0.001 +Fit_cy, Fit_nz, Fit_pu, Fit_pl, Fit_mv = 0.326, 0.870, 0.159, 0.041, 0.001 class OptResult: @@ -108,7 +108,7 @@ class OptInfo: print(f'-Pick time: {self.pickup_time: .3f}, Pick distance: {self.pickup_distance: .3f}') print(f'-Place time: {self.place_time: .3f}, Place distance: {self.place_distance: .3f}') print( - f'-Round time: {self.total_time - self.place_time - self.place_time: .3f}, Place distance: ' + f'-Round time: {self.total_time - self.place_time - self.place_time: .3f}, Round distance: ' f'{self.total_distance - self.pickup_distance - self.place_distance: .3f}') minutes, seconds = int(self.total_time // 60), int(self.total_time) % 60 @@ -372,364 +372,6 @@ def feeder_assignment(component_data, pcb_data, component_result, cycle_result): return feeder_slot_result -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] - if feeder != -1 and placement == -1: - print(assigned_feeder) - print(cycle_placement) - - pos.append([pcb_data.iloc[placement]['x'] - head * head_interval + stopper_pos[0], - pcb_data.iloc[placement]['y'] + stopper_pos[1]]) - - 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]]) - - def get_distance(pos_1, pos_2): - return math.sqrt((pos_1[0] - pos_2[0]) ** 2 + (pos_1[1] - pos_2[1]) ** 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 head_sequence - - -@timer_wrapper -def greedy_placement_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 = 1 # 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): - if sum(component_result[cycle_set]) == -max_head_index: - continue - # 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 or head_counter % point2head_range == 0: - 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)]) - 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) - - 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])) - - return placement_result, head_sequence_result - - -@timer_wrapper -def beam_search_for_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 i in range(len(pcb_data)): - part = pcb_data.loc[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.loc[i]['x'], pcb_data.loc[i]['y']]) - - # 记录最左下角坐标 - if mount_point_pos[component_index][-1][0] < base_points[0]: - base_points[0] = mount_point_pos[component_index][-1][0] - if mount_point_pos[component_index][-1][1] < base_points[1]: - 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('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_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_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_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_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_index[index]) - beam_mount_point_index[beam_counter][component_index].pop(search_beam_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])) - - pbar.update(1 / sum(cycle_result) * 100) - - index = np.argmin(beam_distance) - return beam_placement_sequence[index], beam_head_sequence[index] - - def optimal_nozzle_assignment(component_data, pcb_data): # === Nozzle Assignment === # number of points for nozzle & number of heads for nozzle diff --git a/base_optimizer/optimizer_interface.py b/base_optimizer/optimizer_interface.py index 96fc8b6..a524dec 100644 --- a/base_optimizer/optimizer_interface.py +++ b/base_optimizer/optimizer_interface.py @@ -6,6 +6,7 @@ from base_optimizer.smopt_feederpriority import * from base_optimizer.smopt_aggregation import * from base_optimizer.smopt_twophase import * from base_optimizer.smopt_mathmodel import * +from base_optimizer.smtopt_route import * from base_optimizer.result_analysis import * @@ -16,12 +17,16 @@ def base_optimizer(machine_index, pcb_data, component_data, feeder_data, params, component_result, cycle_result, feeder_slot_result = optimizer_celldivision(pcb_data, component_data) placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result) - elif params.machine_optimizer == 'feeder-scan': # 基于基座扫描的供料器优先算法 - component_result, cycle_result, feeder_slot_result = feeder_priority_assignment(component_data, pcb_data) + elif params.machine_optimizer == 'feeder-priority': # 基于基座扫描的供料器优先算法 + component_result, cycle_result, feeder_slot_result = feeder_priority_assignment(component_data, pcb_data, + feeder_data) placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result) - # placement_result, head_sequence = beam_search_for_route_generation(component_data, pcb_data, component_result, - # cycle_result, feeder_slot_result) + # placement_result, head_sequence = beam_search_route_generation(component_data, pcb_data, component_result, + # cycle_result, feeder_slot_result) + # placement_result, head_sequence = scan_based_placement_route_generation(component_data, pcb_data, + # component_result, cycle_result, + # feeder_slot_result) elif params.machine_optimizer == 'hybrid-genetic': # 基于拾取组的混合遗传算法 component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_hybrid_genetic( @@ -41,8 +46,8 @@ def base_optimizer(machine_index, pcb_data, component_data, feeder_data, params, initial=True, partition=True, reduction=True, hinter=hinter) - placement_result, head_sequence = scan_based_placement_route_generation(component_data, pcb_data, - component_result, cycle_result) + placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result, + cycle_result, feeder_slot_result) else: raise 'machine optimizer method ' + params.method + ' is not existed' @@ -56,6 +61,7 @@ def base_optimizer(machine_index, pcb_data, component_data, feeder_data, params, info.print() print('------------------------------ ') + # placement_route_schematic(pcb_data, component_data, opt_res, 1) if params.save: output_optimize_result( f'result/{params.filename[:-4]}-{params.line_optimizer}-M0{machine_index} {params.save_suffix}', diff --git a/base_optimizer/result_analysis.py b/base_optimizer/result_analysis.py index 4eeb7a4..22431b5 100644 --- a/base_optimizer/result_analysis.py +++ b/base_optimizer/result_analysis.py @@ -77,7 +77,7 @@ def pickup_cycle_schematic(optimizer_result): plt.show() -def placement_route_schematic(pcb_data, optimizer_result, cycle=-1): +def placement_route_schematic(pcb_data, component_data, optimizer_result, cycle=-1): plt.figure('cycle {}'.format(cycle + 1)) pos_x, pos_y = [], [] @@ -128,7 +128,7 @@ def placement_route_schematic(pcb_data, optimizer_result, cycle=-1): continue placement = optimizer_result.placement_assign[placement_cycle][head] slot = optimizer_result.feeder_slot_assign[cycle_][head] - feeder_part[slot] = pcb_data.loc[placement]['part'] + feeder_part[slot] = pcb_data.loc[placement]['part'] + component_data.iloc[optimizer_result.component_assign[cycle_][head]].fdr if slot not in feeder_counter.keys(): feeder_counter[slot] = 0 @@ -140,9 +140,9 @@ def placement_route_schematic(pcb_data, optimizer_result, cycle=-1): part + ': ' + str(feeder_counter[slot]), ha='center', size=7, rotation=90) plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)], - [slotf1_pos[1] + 10, slotf1_pos[1] + 10], color = 'black') + [slotf1_pos[1] + 10, slotf1_pos[1] + 10], color='black') plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)], - [slotf1_pos[1] - 40, slotf1_pos[1] - 40], color = 'black') + [slotf1_pos[1] - 40, slotf1_pos[1] - 40], color='black') for counter in range(max_slot_index // 2 + 1): pos = slotf1_pos[0] + (counter - 0.5) * slot_interval @@ -402,8 +402,8 @@ def optimization_assign_result(component_data, pcb_data, optimizer_result, nozzl if index == -1: component_assign.loc[cycle, 'H{}'.format(head + 1)] = '' else: - component_assign.loc[cycle, 'H{}'.format(head + 1)] = component_data.loc[index]['part'] - # component_assign.loc[cycle, 'H{}'.format(head + 1)] = 'C' + str(index) + # component_assign.loc[cycle, 'H{}'.format(head + 1)] = component_data.loc[index]['part'] + component_assign.loc[cycle, 'H{}'.format(head + 1)] = 'C' + str(index) print(component_assign) print('') @@ -432,7 +432,6 @@ def optimization_assign_result(component_data, pcb_data, optimizer_result, nozzl def placement_info_evaluation(component_data, pcb_data, optimizer_result, hinter=False): # === 优化结果参数 === info = OptInfo() - # === 校验 === info.total_points = 0 for cycle, components in enumerate(optimizer_result.component_assign): diff --git a/base_optimizer/smopt_celldivision.py b/base_optimizer/smopt_celldivision.py index 52610ef..789f2fc 100644 --- a/base_optimizer/smopt_celldivision.py +++ b/base_optimizer/smopt_celldivision.py @@ -123,12 +123,14 @@ def optimizer_celldivision(pcb_data, component_data, hinter=True): pop_val = [] for pop in range(population_size): - component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data, - component_cell, - pop_generation[pop]) - pop_val.append( - component_assign_evaluate(component_data, component_result, cycle_result, feeder_slot_result)) - + try: + component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data, + component_cell, + pop_generation[pop]) + pop_val.append( + component_assign_evaluate(component_data, component_result, cycle_result, feeder_slot_result)) + except: + pop_val.append(1e4) # 初始化随机生成种群 Upit = int(1.5 * np.sqrt(len(component_cell))) @@ -168,11 +170,14 @@ def optimizer_celldivision(pcb_data, component_data, hinter=True): # 将元件元胞分配到各个吸杆上,计算价值函数 for pop in range(population_size): - component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data, - component_cell, - pop_generation[pop]) - pop_val[pop] = component_assign_evaluate(component_data, component_result, cycle_result, - feeder_slot_result) + try: + component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data, + component_cell, + pop_generation[pop]) + pop_val[pop] = component_assign_evaluate(component_data, component_result, cycle_result, + feeder_slot_result) + except: + pop_val[pop] = 1e4 assert(pop_val[pop] > 0) if min(pop_val) < min_pop_val: diff --git a/base_optimizer/smopt_feederpriority.py b/base_optimizer/smopt_feederpriority.py index 20c6969..e9f5288 100644 --- a/base_optimizer/smopt_feederpriority.py +++ b/base_optimizer/smopt_feederpriority.py @@ -1,3 +1,4 @@ +import copy import math from functools import reduce @@ -6,23 +7,23 @@ from base_optimizer.result_analysis import placement_info_evaluation @timer_wrapper -def feeder_priority_assignment(component_data, pcb_data, hinter=True): +def feeder_priority_assignment(component_data, pcb_data, feeder_data, hinter=True): feeder_allocate_val = None component_result, cycle_result, feeder_slot_result = None, None, None nozzle_pattern_list = feeder_nozzle_pattern(component_data) pbar = tqdm(total=len(nozzle_pattern_list), desc='feeder priority process') if hinter else None + # 第1步:确定吸嘴分配模式 for nozzle_pattern in nozzle_pattern_list: - feeder_data = pd.DataFrame(columns=['slot', 'part', 'arg']) + feeder_data_cpy = copy.deepcopy(feeder_data) # 第2步:分配供料器位置 - feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figure=False) + feeder_allocate(component_data, pcb_data, feeder_data_cpy, nozzle_pattern, figure=False) # 第3步:扫描供料器基座,确定元件拾取的先后顺序 - component_assign, cycle_assign, feeder_slot_assign = feeder_base_scan(component_data, pcb_data, feeder_data) + component_assign, cycle_assign, feeder_slot_assign = feeder_base_scan(component_data, pcb_data, feeder_data_cpy) info = placement_info_evaluation(component_data, pcb_data, OptResult(component_assign, cycle_assign, feeder_slot_assign), hinter=False) - - val = 0.356 * info.cycle_counter + 0.949 * info.nozzle_change_counter + 0.159 * info.pickup_counter \ - + 0.002 * info.pickup_distance + val = Fit_cy * info.cycle_counter + Fit_nz * info.nozzle_change_counter + Fit_pu * info.pickup_counter\ + + Fit_mv * info.pickup_distance if feeder_allocate_val is None or val < feeder_allocate_val: feeder_allocate_val = val component_result, cycle_result, feeder_slot_result = component_assign, cycle_assign, feeder_slot_assign @@ -40,10 +41,8 @@ def feeder_nozzle_pattern(component_data): if data.points == 0: continue nozzle_points[data.nz] += data.points - head_assign_indexes = [int(math.ceil(max_head_index + 0.5) - 4.5 - pow(-1, h) * (math.ceil(h / 2) - 0.5)) for h in range(1, max_head_index + 1)] - while len(nozzle_points): nozzle_heads, nozzle_indices = defaultdict(int), defaultdict(str), min_points_nozzle = None @@ -97,7 +96,6 @@ def feeder_nozzle_pattern(component_data): idx += 1 nozzle_points.pop(min_points_nozzle) - return nozzle_pattern_list @@ -197,21 +195,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur if feeder_assign[idx] != -2: continue - if len(nozzle_pattern) == 0: # 吸嘴匹配模式为空,优先分配元件,根据分配元件倒推吸嘴匹配模式 - nozzle_assign = '' - max_points, max_nozzle_points = 0, 0 - for nozzle in set(nozzle_pattern): - if len(tmp_nozzle_component[nozzle]) == 0: - continue - part = max(tmp_nozzle_component[nozzle], - key=lambda x: tmp_feeder_points[x] / tmp_feeder_limit[x] - if tmp_feeder_points[x] != 0 else 0) - index_ = tmp_nozzle_component[nozzle].index(part) - if max_points < tmp_nozzle_component_points[nozzle][index_]: - max_points, nozzle_assign = tmp_nozzle_component_points[nozzle][index_], nozzle - else: - # 吸嘴匹配模式非空,按对应吸嘴类型进行元件分配 - nozzle_assign = nozzle_pattern[idx] + # 吸嘴匹配模式非空,按对应吸嘴类型进行元件分配 + nozzle_assign = nozzle_pattern[idx] if len(tmp_nozzle_component[nozzle_assign]) == 0: # 当前头对应吸嘴类型无可用元件,将计划分配的元件压入堆栈 @@ -233,6 +218,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur tmp_feeder_limit[x] != 0 else 0)) part = tmp_nozzle_component[nozzle_assign][index_] + feeder_type = component_data.loc[part].fdr extra_width, extra_slot = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval, 1 slot_overlap = False @@ -241,6 +227,9 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur if feeder_base[slot_] != -2 or slot_ > max_slot_index // 2: slot_overlap = True break + if idx + extra_slot // 2 < max_head_index and feeder_assign[idx + extra_slot // 2] >= 0: + slot_overlap = True + break extra_width -= slot_interval extra_slot += 1 @@ -376,7 +365,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur best_assign = feeder_assign.copy() best_assign_points = feeder_assign_points.copy() best_assign_slot = slot - best_nozzle_component, best_nozzle_component_points = tmp_nozzle_component, tmp_nozzle_component_points + best_nozzle_component, best_nozzle_component_points = \ + tmp_nozzle_component.copy(), tmp_nozzle_component_points.copy() if not best_assign_points: break @@ -384,7 +374,6 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur for idx, part in enumerate(best_assign): if part < 0: continue - # 新安装的供料器 if feeder_base[best_assign_slot + idx * interval_ratio] != part: # 除去分配给最大化同时拾取周期的项,保留结余项 @@ -419,6 +408,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur extra_slot += 1 if feeder_base[best_assign_slot + idx * interval_ratio + extra_slot] == -2: feeder_base[best_assign_slot + idx * interval_ratio + extra_slot] = -1 # 标记槽位已占用 + else: + assert 'feeder allocation conflict' extra_width -= slot_interval # 更新吸嘴信息 @@ -441,7 +432,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur continue part = component_data.loc[feeder].part - feeder_data.loc[len(feeder_data.index)] = [slot, part, 0] + feeder_data.loc[len(feeder_data.index)] = [slot, part] if figure: # 绘制供料器位置布局 @@ -505,9 +496,7 @@ def feeder_base_scan(component_data, pcb_data, feeder_data): raise ValueError(info) component_points[idx] = data.points component_index[data.part] = idx - if len(feeder_assign_check) != len(component_points) - component_points.count(0): - print(feeder_assign_check) - print(component_points) + assert len(feeder_assign_check) == len(component_points) - component_points.count(0) # 所有供料器均已分配槽位 mount_center_slot = defaultdict(float) @@ -562,7 +551,7 @@ def feeder_base_scan(component_data, pcb_data, feeder_data): while True: best_scan_part = [-1 for _ in range(max_head_index)] - best_scan_cycle = [-1 for _ in range(max_head_index)] + best_scan_cycle = [0 for _ in range(max_head_index)] best_scan_slot = [-1 for _ in range(max_head_index)] best_scan_nozzle_limit = copy.deepcopy(cur_nozzle_limit) @@ -619,10 +608,7 @@ def feeder_base_scan(component_data, pcb_data, feeder_data): prev_nozzle_change = 2 * (nozzle_cycle[head] != nozzle_mode[cycle_index + 1][head]) # 避免首个周期吸杆占用率低的问题 - if nozzle_cycle[head] == '': - nozzle_change = 0 - else: - nozzle_change = 2 * (nozzle != nozzle_cycle[head]) + nozzle_change = 2 * (nozzle != nozzle_cycle[head]) if cycle_index + 1 < len(nozzle_mode): nozzle_change += 2 * (nozzle != nozzle_mode[cycle_index + 1][head]) @@ -631,7 +617,6 @@ def feeder_base_scan(component_data, pcb_data, feeder_data): val = e_gang_pick * gang_pick_change - e_nz_change * nozzle_change if val < value_increment_base: continue - component_counter += 1 scan_part[head] = part @@ -698,8 +683,6 @@ def feeder_base_scan(component_data, pcb_data, feeder_data): # 长期收益 gang_pick_slot_dict = defaultdict(list) for head, pick_slot in enumerate(scan_slot): - if pick_slot == -1: - continue gang_pick_slot_dict[pick_slot - head * interval_ratio].append(scan_cycle[head]) eval_func_long_term = 0 @@ -736,7 +719,6 @@ def feeder_base_scan(component_data, pcb_data, feeder_data): if search_break: break - scan_eval_func_list.append(scan_eval_func) cur_scan_part = best_scan_part.copy() @@ -745,15 +727,14 @@ def feeder_base_scan(component_data, pcb_data, feeder_data): cur_nozzle_limit = copy.deepcopy(best_scan_nozzle_limit) - if len(scan_eval_func_list) != 0: - if sum(scan_eval_func_list) > best_assigned_eval_func: - best_assigned_eval_func = sum(scan_eval_func_list) + if len(scan_eval_func_list) and sum(scan_eval_func_list) > best_assigned_eval_func: + best_assigned_eval_func = sum(scan_eval_func_list) - assigned_part = cur_scan_part.copy() - assigned_slot = cur_scan_slot.copy() - assigned_cycle = cur_scan_cycle.copy() + assigned_part = cur_scan_part.copy() + assigned_slot = cur_scan_slot.copy() + assigned_cycle = cur_scan_cycle.copy() - nozzle_insert_cycle = cycle_index + nozzle_insert_cycle = cycle_index # 从供料器基座中移除对应数量的贴装点 nonzero_cycle = [cycle for cycle in assigned_cycle if cycle > 0] diff --git a/base_optimizer/smopt_hybridgenetic.py b/base_optimizer/smopt_hybridgenetic.py index d8c59c7..04281ff 100644 --- a/base_optimizer/smopt_hybridgenetic.py +++ b/base_optimizer/smopt_hybridgenetic.py @@ -66,7 +66,7 @@ def dynamic_programming_cycle_path(cycle_placement, cycle_points): return head_sequence -def pickup_group_combination(component_nozzle, designated_nozzle, supply, supply_cycle, demand, demand_cycle): +def pickup_group_combination(designated_nozzle, supply, supply_cycle, demand, demand_cycle): combination, combination_cycle = demand.copy(), demand_cycle.copy() supply_cpy = supply.copy() @@ -101,6 +101,9 @@ def pickup_group_combination(component_nozzle, designated_nozzle, supply, supply combination_cycle[idx + max_match_offset] = supply_cycle[idx] supply_cpy[idx] = None + if max_match_counter == 0: + break + return combination, combination_cycle @@ -158,7 +161,7 @@ def cal_individual_val(component_nozzle, component_point_pos, designated_nozzle, if is_combinable: cost = cost - t0 # combine sequenced pickup ρb and ps into ρu(union pickup) - Pu, Pu_cycle = pickup_group_combination(component_nozzle, designated_nozzle, Ps, Ps_cycle, Pd, Pd_cycle) + Pu, Pu_cycle = pickup_group_combination(designated_nozzle, Ps, Ps_cycle, Pd, Pd_cycle) # decide the placement cluster and sequencing of pickup ρu pickup_action_counter, place_action_counter = 0, max_head_index - Pu.count(None) diff --git a/base_optimizer/smopt_mathmodel.py b/base_optimizer/smopt_mathmodel.py index 495c354..fbac9e3 100644 --- a/base_optimizer/smopt_mathmodel.py +++ b/base_optimizer/smopt_mathmodel.py @@ -38,73 +38,79 @@ def head_task_model(component_data, pcb_data, hinter=True): # objective related g = mdl.addVars(list_range(K), vtype=GRB.BINARY) - d = mdl.addVars(list_range(K - 1), list_range(H), vtype=GRB.CONTINUOUS) + d = mdl.addVars(list_range(K), list_range(H), vtype=GRB.CONTINUOUS) u = mdl.addVars(list_range(K), vtype=GRB.INTEGER) - d_plus = mdl.addVars(list_range(J), list_range(H), list_range(K - 1), vtype=GRB.CONTINUOUS) - d_minus = mdl.addVars(list_range(J), list_range(H), list_range(K - 1), vtype=GRB.CONTINUOUS) + d_plus = mdl.addVars(list_range(J), list_range(H), list_range(K), vtype=GRB.CONTINUOUS) + d_minus = mdl.addVars(list_range(J), list_range(H), list_range(K), vtype=GRB.CONTINUOUS) e = mdl.addVars(list_range(-(H - 1) * r, S), list_range(K), vtype=GRB.BINARY) f = mdl.addVars(list_range(S), list_range(I), vtype=GRB.BINARY, name='') - x = mdl.addVars(list_range(I), list_range(S), list_range(K), list_range(H), vtype=GRB.BINARY) - n = mdl.addVars(list_range(H), vtype=GRB.CONTINUOUS) - mdl.addConstrs(g[k] <= g[k + 1] for k in range(K - 1)) + x = mdl.addVars(list_range(I), list_range(K), list_range(H), vtype=GRB.BINARY) + y = mdl.addVars(list_range(S), list_range(K), list_range(H), vtype=GRB.BINARY) + z = mdl.addVars(list_range(J), list_range(K), list_range(H), vtype=GRB.BINARY) + + mdl.addConstrs(g[k] >= g[k + 1] for k in range(K - 1)) mdl.addConstrs( - quicksum(x[i, s, k, h] for i in range(I) for s in range(S)) <= g[k] for k in range(K) for h in range(H)) + quicksum(x[i, k, h] for i in range(I)) <= g[k] for k in range(K) for h in range(H)) # nozzle no more than 1 for head h and cycle k - mdl.addConstrs( - quicksum(CompOfNozzle[i][j] * x[i, s, k, h] for i in range(I) for s in range(S) for j in range(J)) <= 1 for k in - range(K) for h in range(H)) + mdl.addConstrs(quicksum(z[j, k ,h] for j in range(J)) <= 1 for k in range(K) for h in range(H)) # nozzle available number constraint - mdl.addConstrs( - quicksum(CompOfNozzle[i][j] * x[i, s, k, h] for i in range(I) for s in range(S) for h in range(H)) <= H for k in - range(K) for j in range(J)) + mdl.addConstrs(quicksum(z[j, k, h] for h in range(H)) <= H for k in range(K) for j in range(J)) # work completion - mdl.addConstrs( - quicksum(x[i, s, k, h] for s in range(S) for k in range(K) for h in range(H)) == component_point[i] for i in - range(I)) + mdl.addConstrs(quicksum(x[i, k, h] for k in range(K) for h in range(H)) == component_point[i] for i in range(I)) # nozzle change - mdl.addConstrs(quicksum(CompOfNozzle[i][j] * x[i, s, k, h] for i in range(I) for s in range(S)) - quicksum( - CompOfNozzle[i][j] * x[i, s, k + 1, h] for i in range(I) for s in range(S)) == d_plus[j, h, k] - d_minus[ - j, h, k] for k in range(K - 1) for j in range(J) for h in range(H)) + mdl.addConstrs( + x[i, k, h] <= quicksum(CompOfNozzle[i][j] * z[j, k, h] for j in range(J)) for i in range(I) for k in range(K) + for h in range(H)) mdl.addConstrs( - 2 * d[k, h] == quicksum(d_plus[j, h, k] for j in range(J)) + quicksum(d_minus[j, h, k] for j in range(J)) for k - in range(K - 1) for h in range(H)) + z[j, k, h] - z[j, k + 1, h] == d_plus[j, h, k] - d_minus[j, h, k] for k in range(K - 1) for j in range(J) for h + in range(H)) - mdl.addConstrs(n[h] == quicksum(d[k, h] for k in range(K - 1)) - 0.5 for h in range(H)) + mdl.addConstrs( + z[j, 0, h] - z[j, K - 1, h] == d_plus[j, h, K - 1] - d_minus[j, h, K - 1] for j in range(J) for h in range(H)) + + mdl.addConstrs( + d[k, h] == quicksum(d_plus[j, h, k] for j in range(J)) + quicksum(d_minus[j, h, k] for j in range(J)) for k + in range(K) for h in range(H)) # simultaneous pick for s in range(-(H - 1) * r, S): rng = list(range(max(0, -math.floor(s / r)), min(H, math.ceil((S - s) / r)))) for k in range(K): - mdl.addConstr(quicksum(x[i, s + h * r, k, h] for h in rng for i in range(I)) <= M * e[s, k], name='') - mdl.addConstr(quicksum(x[i, s + h * r, k, h] for h in rng for i in range(I)) >= e[s, k], name='') + mdl.addConstr(quicksum(y[s + h * r, k, h] for h in rng) <= M * e[s, k], name='') + mdl.addConstr(quicksum(y[s + h * r, k, h] for h in rng) >= e[s, k], name='') + # pickup movement mdl.addConstrs( - u[k] >= s1 * e[s1, k] - s2 * e[s2, k] for s1 in range(-(H - 1) * r, S) for s2 in range(-(H - 1) * r, S) for k in - range(K)) + u[k] >= s1 * e[s1, k] - s2 * e[s2, k] + M * (e[s1, k] + e[s2, k] - 2) for s1 in range(-(H - 1) * r, S) for s2 in + range(-(H - 1) * r, S) for k in range(K)) # feeder related mdl.addConstrs(quicksum(f[s, i] for s in range(S)) <= 1 for i in range(I)) mdl.addConstrs(quicksum(f[s, i] for i in range(I)) <= 1 for s in range(S)) mdl.addConstrs( - quicksum(x[i, s, k, h] for h in range(H) for k in range(K)) >= f[s, i] for i in range(I) for s in range(S)) + quicksum(x[i, k, h] * y[s, k, h] for h in range(H) for k in range(K)) >= f[s, i] for i in range(I) for s in range(S)) mdl.addConstrs( - quicksum(x[i, s, k, h] for h in range(H) for k in range(K)) <= M * f[s, i] for i in range(I) for s in + quicksum(x[i, k, h] * y[s, k, h] for h in range(H) for k in range(K)) <= M * f[s, i] for i in range(I) for s in range(S)) + # relationship + mdl.addConstrs( + quicksum(x[i, k, h] for i in range(I)) == quicksum(y[s, k, h] for s in range(S)) for k in range(K) for h in + range(H)) + # objective - t_c, t_n, t_p, t_m = 2, 6, 1, 0.1 - mdl.setObjective(t_c * quicksum(g[k] for k in range(K)) + t_n * quicksum( - d[k, h] for h in range(H) for k in range(K - 1)) + t_p * quicksum( - e[s, k] for s in range(-(H - 1) * r, S) for k in range(K)) + t_m * quicksum(u[k] for k in range(K)), + mdl.setObjective(Fit_cy * quicksum(g[k] for k in range(K)) + Fit_nz * quicksum( + d[k, h] for h in range(H) for k in range(K)) + Fit_pu * quicksum( + e[s, k] for s in range(-(H - 1) * r, S) for k in range(K)) + Fit_mv * head_interval * quicksum(u[k] for k in range(K)), GRB.MINIMIZE) mdl.optimize() @@ -119,14 +125,17 @@ def head_task_model(component_data, pcb_data, hinter=True): cycle_result.append(1) for h in range(H): for i in range(I): - for s in range(S): - if abs(x[i, s, k, h].x) > 1e-6: - component_result[-1][h] = i - feeder_slot_result[-1][h] = slot_start + s * interval_ratio - 1 + if abs(x[i, k, h].x) > 1e-6: + component_result[-1][h] = i + + for s in range(S): + if abs(y[s, k, h].x) > 1e-6: + feeder_slot_result[-1][h] = slot_start + s * interval_ratio - 1 + + if hinter: print(component_result) print(feeder_slot_result) - return component_result, cycle_result, feeder_slot_result @@ -134,7 +143,7 @@ def place_route_model(component_data, pcb_data, component_result, feeder_slot_re mdl = Model('place_route') mdl.setParam('Seed', 0) mdl.setParam('OutputFlag', hinter) # set whether output the debug information - # mdl.setParam('TimeLimit', 20) + mdl.setParam('TimeLimit', 10) component_type = [] for _, data in component_data.iterrows(): @@ -213,21 +222,23 @@ def place_route_model(component_data, pcb_data, component_result, feeder_slot_re for h in range(H): if component_result[k][h] == -1: # no components on the head - mdl.addConstr(quicksum(w[p, q, k, a] for a in A_contain(h) for q in range(P) for p in range(P)) == 0) + mdl.addConstrs(quicksum(w[p, q, k, a] for a in A_from(h) for q in range(P)) + quicksum( + w[q, p, k, a] for a in A_to(h) for q in range(P)) + y[p, k, h] + z[p, k, h] <= 0 for p in range(P)) else: # there are components on the head - mdl.addConstrs((quicksum(w[p, q, k, a] for a in A_from(h) for q in range(P)) + quicksum( - w[q, p, k, a] for a in A_to(h) for q in range(P))) / 2 <= CompOfPoint[component_result[k][h]][p] for - p in range(P)) + mdl.addConstrs(quicksum(w[p, q, k, a] for a in A_from(h) for q in range(P)) + quicksum( + w[q, p, k, a] for a in A_to(h) for q in range(P)) + y[p, k, h] + z[p, k, h] <= 4 * + CompOfPoint[component_result[k][h]][p] for p in range(P)) # each head corresponds to a maximum of one point in each cycle mdl.addConstrs( - quicksum(w[p, q, k, a] for p in range(P) for q in range(P) for a in A_contain(h)) <= 2 for k in range(K) for h + quicksum(w[p, q, k, a] for p in range(P) for q in range(P) for a in A_contain(h)) + + quicksum(y[p, k, h] + z[p, k, h] for p in range(P)) <= 2 for k in range(K) for h in range(H)) - mdl.addConstrs( - quicksum((y[p, k, h] + z[p, k, h]) for p in range(P)) <= 1 for k in range(K) for h in - range(H)) + # mdl.addConstrs( + # quicksum((y[p, k, h] + z[p, k, h]) for p in range(P)) <= 2 for k in range(K) for h in + # range(H)) # task continuity (for the same point the entering head and the leaving head should be same) mdl.addConstrs(quicksum(w[p, q, k, a] for p in range(P) for a in A_to(h)) + y[q, k, h] == quicksum( @@ -235,11 +246,11 @@ def place_route_model(component_data, pcb_data, component_result, feeder_slot_re range(P)) mdl.addConstrs( - y[p, k, h] <= quicksum(w[p, q, k, a] for q in range(P) for a in A_from(h)) for h in range(H) for p in - range(P) for k in range(K)) + y[p, k, h] <= quicksum(w[p, q, k, a] for q in range(P) for a in A_from(h)) + z[p, k, h] for h in range(H) for p + in range(P) for k in range(K)) mdl.addConstrs( - z[p, k, h] <= quicksum(w[q, p, k, a] for q in range(P) for a in A_to(h)) for h in range(H) for p in + z[p, k, h] <= quicksum(w[q, p, k, a] for q in range(P) for a in A_to(h)) + y[p, k, h] for h in range(H) for p in range(P) for k in range(K)) # one arrival point per cycle @@ -309,8 +320,7 @@ def place_route_model(component_data, pcb_data, component_result, feeder_slot_re plt.show() # convert model result into standard form - placement_result, head_sequence = [[-1 for _ in range(H)] for _ in range(K)], [[] for _ in - range(K)] + placement_result, head_sequence = [[-1 for _ in range(H)] for _ in range(K)], [[] for _ in range(K)] for k in range(K): arc_list = [] for p in range(P): @@ -327,6 +337,14 @@ def place_route_model(component_data, pcb_data, component_result, feeder_slot_re for h in range(H): if abs(y[p, k, h].x) > 1e-6: head = h + if placement_result[k][h] == -1: + placement_result[k][h] = p + assert placement_result[k][h] == p + + if abs(z[p, k, h].x) > 1e-6: + if placement_result[k][h] == -1: + placement_result[k][h] = p + assert placement_result[k][h] == p while idx < len(arc_list): for i, arc in enumerate(arc_list): @@ -344,7 +362,7 @@ def place_route_model(component_data, pcb_data, component_result, feeder_slot_re def optimizer_mathmodel(component_data, pcb_data, hinter=True): component_result, cycle_result, feeder_slot_result = head_task_model(component_data, pcb_data, hinter) - # placement_result, head_sequence = place_route_model(component_data, pcb_data, component_result, feeder_slot_result) - placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result, - cycle_result) + placement_result, head_sequence = place_route_model(component_data, pcb_data, component_result, feeder_slot_result) + # placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result, + # cycle_result) return component_result, cycle_result, feeder_slot_result, placement_result, head_sequence diff --git a/base_optimizer/smopt_twophase.py b/base_optimizer/smopt_twophase.py index 6b787f4..edab124 100644 --- a/base_optimizer/smopt_twophase.py +++ b/base_optimizer/smopt_twophase.py @@ -7,8 +7,10 @@ def list_range(start, end=None): @timer_wrapper def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, partition=True, initial=False, hinter=True): + # data preparation: convert data to index component_list, nozzle_list = defaultdict(int), defaultdict(int) + component_feeder = defaultdict(int) cpidx_2_part, nzidx_2_nozzle, cpidx_2_nzidx = {}, {}, {} arg_slot_rng = None if len(feeder_data) == 0 else [feeder_data.iloc[0].slot, feeder_data.iloc[-1].slot] for idx, data in component_data.iterrows(): @@ -21,6 +23,7 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part nzidx_2_nozzle[nz_idx] = nozzle component_list[part] = 0 + component_feeder[part] = data.fdn cpidx_2_nzidx[idx] = nz_idx for _, data in pcb_data.iterrows(): @@ -40,13 +43,7 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part assert idx != -1 part_feederbase[idx] = data.slot # part index - slot - if not reduction: - ratio = 2 # 直接导入飞达数据时,采用正常吸杆间隔 - else: - if len(component_list) <= 1.5 * max_head_index: - ratio = 1 - else: - ratio = 2 + ratio = 1 if reduction else 2 I, J = len(cpidx_2_part.keys()), len(nzidx_2_nozzle.keys()) # === determine the hyper-parameter of L === # first phase: calculate the number of heads for each type of nozzle @@ -80,13 +77,12 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part pre_objbst, pre_changetime = None, None def terminate_condition(mdl, where): if where == GRB.Callback.MIP: - objbst = mdl.cbGet(GRB.Callback.MIP_OBJBST) + objbst, objbnd = mdl.cbGet(GRB.Callback.MIP_OBJBST), mdl.cbGet(GRB.Callback.MIP_OBJBND) changetime = mdl.cbGet(GRB.Callback.RUNTIME) nonlocal pre_objbst, pre_changetime # condition: value change if pre_objbst and abs(pre_objbst - objbst) < 1e-3: - if pre_changetime and changetime - pre_changetime > 45: - # pass + if pre_changetime and changetime - pre_changetime > 100 * (1 - objbnd / objbst): mdl.terminate() else: pre_changetime = changetime @@ -150,10 +146,8 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part break level += 1 - weight_cycle, weight_nz_change, weight_pick = 2, 3, 2 - L = len(cycle_assignment) if partition else len(pcb_data) - S = ratio * I if len(feeder_data) == 0 else arg_slot_rng[-1] - arg_slot_rng[0] + 1 # the available feeder num + S = ratio * sum(component_feeder.values()) * 2 if len(feeder_data) == 0 else arg_slot_rng[-1] - arg_slot_rng[0] + 1 # the available feeder num M = len(pcb_data) # a sufficiently large number (number of placement points) HC = [[0 for _ in range(J)] for _ in range(I)] for i in range(I): @@ -247,8 +241,8 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part v[part_feederbase[i], h, idx].Start = 1 # === Objective === - mdl.setObjective(weight_cycle * quicksum(WL[l] for l in range(L)) + weight_nz_change * quicksum( - NC[h] for h in range(max_head_index)) + weight_pick * quicksum( + mdl.setObjective(Fit_cy * quicksum(WL[l] for l in range(L)) + 2 * Fit_nz * quicksum( + NC[h] for h in range(max_head_index)) + Fit_pu * quicksum( PU[s, l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L))) # === Constraint === @@ -256,13 +250,13 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part mdl.addConstrs(WL[l] <= 1 for l in range(L)) # work completion - # mdl.addConstrs(c[i, h, l] == WL[l] * y[i, h, l] for i in range(I) for h in range(max_head_index) for l in range(L)) - mdl.addConstrs( - c[i, h, l] <= max_cycle * y[i, h, l] for i in range(I) for h in range(max_head_index) for l in range(L)) - mdl.addConstrs(c[i, h, l] <= WL[l] for i in range(I) for h in range(max_head_index) for l in range(L)) - mdl.addConstrs( - c[i, h, l] >= WL[l] - max_cycle * (1 - y[i, h, l]) for i in range(I) for h in range(max_head_index) for l in - range(L)) + mdl.addConstrs(c[i, h, l] == WL[l] * y[i, h, l] for i in range(I) for h in range(max_head_index) for l in range(L)) + # mdl.addConstrs( + # c[i, h, l] <= max_cycle * y[i, h, l] for i in range(I) for h in range(max_head_index) for l in range(L)) + # mdl.addConstrs(c[i, h, l] <= WL[l] for i in range(I) for h in range(max_head_index) for l in range(L)) + # mdl.addConstrs( + # c[i, h, l] >= WL[l] - max_cycle * (1 - y[i, h, l]) for i in range(I) for h in range(max_head_index) for l in + # range(L)) mdl.addConstrs( quicksum(c[i, h, l] for h in range(max_head_index) for l in range(L)) == component_list[cpidx_2_part[i]] for i @@ -278,12 +272,12 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part mdl.addConstr(quicksum(v[s + h * ratio, h, l] for h in rng) <= max_head_index * p[s, l]) mdl.addConstr(quicksum(v[s + h * ratio, h, l] for h in rng) >= p[s, l]) - # mdl.addConstrs(PU[s, l] == p[s, l] * WL[l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L)) - mdl.addConstrs(PU[s, l] <= max_cycle * p[s, l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L)) - mdl.addConstrs(PU[s, l] <= WL[l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L)) - mdl.addConstrs( - PU[s, l] >= WL[l] - max_cycle * (1 - p[s, l]) for s in range(-(max_head_index - 1) * ratio, S) for l in - range(L)) + mdl.addConstrs(PU[s, l] == p[s, l] * WL[l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L)) + # mdl.addConstrs(PU[s, l] <= max_cycle * p[s, l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L)) + # mdl.addConstrs(PU[s, l] <= WL[l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L)) + # mdl.addConstrs( + # PU[s, l] >= WL[l] - max_cycle * (1 - p[s, l]) for s in range(-(max_head_index - 1) * ratio, S) for l in + # range(L)) # nozzle change mdl.addConstrs( @@ -307,7 +301,7 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part for l in range(L)) # available number of feeder - mdl.addConstrs(quicksum(f[s, i] for s in range(S)) <= 1 for i in range(I)) + mdl.addConstrs(quicksum(f[s, i] for s in range(S)) <= component_feeder[cpidx_2_part[i]] for i in range(I)) # available number of nozzle mdl.addConstrs(quicksum(z[j, h, l] for h in range(max_head_index)) <= max_head_index for j in range(J) for l in range(L)) @@ -320,19 +314,19 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part # others mdl.addConstrs(quicksum(z[j, h, l] for j in range(J)) <= 1 for h in range(max_head_index) for l in range(L)) - # mdl.addConstrs( - # quicksum(x[i, s, h, l] for h in range(max_head_index) for l in range(L)) >= f[s, i] for i in range(I) - # for s in range(S)) - # mdl.addConstrs( - # quicksum(x[i, s, h, l] for h in range(max_head_index) for l in range(L)) <= M * f[s, i] for i in - # range(I) for s in range(S)) - mdl.addConstrs( - f[s, i] >= x[i, s, h, l] for s in range(S) for i in range(I) for h in range(max_head_index) for l in range(L)) - + quicksum(x[i, s, h, l] for h in range(max_head_index) for l in range(L)) >= f[s, i] for i in range(I) + for s in range(S)) mdl.addConstrs( - quicksum(x[i, s, h, l] for h in range(max_head_index) for l in range(L)) >= f[s, i] for s in - range(S) for i in range(I)) + quicksum(x[i, s, h, l] for h in range(max_head_index) for l in range(L)) <= M * f[s, i] for i in + range(I) for s in range(S)) + + # mdl.addConstrs( + # f[s, i] >= x[i, s, h, l] for s in range(S) for i in range(I) for h in range(max_head_index) for l in range(L)) + # + # mdl.addConstrs( + # quicksum(x[i, s, h, l] for h in range(max_head_index) for l in range(L)) >= f[s, i] for s in + # range(S) for i in range(I)) # the constraints to speed up the search process mdl.addConstrs( @@ -340,13 +334,13 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part in range(L)) if reduction: - mdl.addConstrs(WL[l] >= WL[l + 1] for l in range(L - 1)) - # mdl.addConstr(quicksum(WL[l] for l in range(L)) <= sum(cycle_assignment)) - mdl.addConstr(quicksum(WL[l] for l in range(L)) >= math.ceil(len(pcb_data) / max_head_index)) # mdl.addConstrs(WL[l] >= WL[l + 1] for l in range(L - 1)) - # mdl.addConstrs(quicksum(z[j, h, l] for j in range(J) for h in range(max_head_index)) >= quicksum( - # z[j, h, l + 1] for j in range(J) for h in range(max_head_index)) for l in range(L - 1)) - # + mdl.addConstr(quicksum(WL[l] for l in range(L)) <= sum(cycle_assignment)) + mdl.addConstr(quicksum(WL[l] for l in range(L)) >= math.ceil(len(pcb_data) / max_head_index)) + mdl.addConstrs(quicksum(z[j, h, l] for j in range(J) for h in range(max_head_index)) >= quicksum( + z[j, h, l + 1] for j in range(J) for h in range(max_head_index)) for l in range(L - 1)) + + mdl.addConstrs(y[i, h, l] <= WL[l] for i in range(I) for h in range(max_head_index) for l in range(L)) mdl.addConstrs(v[s, h, l] <= WL[l] for s in range(S) for h in range(max_head_index) for l in range(L)) @@ -368,6 +362,7 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part print('num of constrs: ', str(len(mdl.getConstrs())), ', num of vars: ', str(len(mdl.getVars()))) mdl.optimize(terminate_condition) + # mdl.optimize() # === result generation === nozzle_assign, component_assign = [], [] @@ -505,348 +500,5 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part print('component assignment: ', component_assign) print('feeder assignment: ', feeder_assign) print('cycle assignment: ', cycle_assign) - return component_assign, feeder_assign, cycle_assign - -def scan_based_placement_route_generation(component_data, pcb_data, component_assign, cycle_assign): - placement_result, head_sequence_result = [], [] - - mount_point_pos, mount_point_index, mount_point_angle, 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) - - lBoundary, rBoundary = min(mount_point_pos, key=lambda x: x[0])[0], max(mount_point_pos, key=lambda x: x[0])[0] - search_step = max((rBoundary - lBoundary) / 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(lBoundary, (lBoundary + rBoundary) / 2, search_step) - head_range = list(range(max_head_index)) - elif search_dir == 1: - # 从右向左搜索 - searchPoints = np.arange(rBoundary + 1e-3, (lBoundary + rBoundary) / 2, -search_step) - head_range = list(range(max_head_index - 1, -1, -1)) - else: - # 从中间向两边搜索 - searchPoints = np.arange(lBoundary, rBoundary, 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) + 1e-3 * 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): - cheby_distance += max(abs(point_pos[mount_seq][0] - point_pos[mount_seq + 1][0]), - abs(point_pos[mount_seq][1] - point_pos[mount_seq + 1][1])) - euler_distance += math.sqrt( - (point_pos[mount_seq][0] - point_pos[mount_seq + 1][0]) ** 2 + ( - point_pos[mount_seq][1] - point_pos[mount_seq + 1][1]) ** 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(assigned_placement, assigned_mount_point, - assigned_mount_angle) - - 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) - - -def placement_route_relink_heuristic(component_data, pcb_data, placement_result, head_sequence_result, hinter=True): - 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 = [] - cycle_length.append(0) - for idx, head in enumerate(head_sequence_result[cycle]): - point_index = placement[head] - if point_index == -1: - continue - pos = [mount_point_pos[point_index][0] - head * head_interval, mount_point_pos[point_index][1]] - angle = mount_point_angle[point_index] - cycle_pos_list.append(pos) - if prev_pos is not None: - if head_sequence_result[cycle][idx - 1] // 2 == head_sequence_result[cycle][idx] // 2: # 同轴 - rotary_angle = prev_angle - angle - else: - rotary_angle = 0 - - cycle_length[-1] += max(axis_moving_time(prev_pos[0] - pos[0], 0), - axis_moving_time(prev_pos[1] - pos[1], 1), head_rotary_time(rotary_angle)) - prev_pos, prev_angle = pos, angle - - 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)]) - - 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 = 10, 0 - start_time = time.time() - with tqdm(total=n_runningtime, leave=False) 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 - - assert chg_cycle_index != -1 - - 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 - - assert chg_head is not None - - # === 第一轮,变更周期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, chg_point_assign_res = [], [[0, 0]] * max_head_index - chg_angle_res = [0] * max_head_index - 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]) - chg_point_assign_res[head] = mount_point_pos[cycle_point_list[part][0]].copy() - chg_angle_res[head] = mount_point_angle[cycle_point_list[part][0]] - cycle_point_list[part].pop(0) - - chg_place_moving, chg_head_res = dynamic_programming_cycle_path(chg_placement_res, chg_point_assign_res, chg_angle_res) - - # === 第二轮,原始周期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, point_assign_res = [], [[0, 0]] * max_head_index - angle_assign_res = [0] * max_head_index - 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]) - point_assign_res[head] = mount_point_pos[cycle_point_list[part][0]].copy() - angle_assign_res[head] = mount_point_angle[cycle_point_list[part][0]] - cycle_point_list[part].pop(0) - - place_moving, place_head_res = dynamic_programming_cycle_path(placement_res, point_assign_res, angle_assign_res) - - # 更新贴装顺序分配结果 - 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 \ No newline at end of file diff --git a/base_optimizer/smtopt_route.py b/base_optimizer/smtopt_route.py new file mode 100644 index 0000000..279bad5 --- /dev/null +++ b/base_optimizer/smtopt_route.py @@ -0,0 +1,706 @@ +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] + if feeder != -1 and placement == -1: + print(assigned_feeder) + print(cycle_placement) + + 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 + + +@timer_wrapper +def greedy_placement_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 = 1 # 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 or head_counter % point2head_range == 0: + 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)]) + 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 + + 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 = 10 # 集束宽度 + 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]) + + # 记录最左下角坐标 + if mount_point_pos[component_index][-1][0] < base_points[0]: + base_points[0] = mount_point_pos[component_index][-1][0] + if mount_point_pos[component_index][-1][1] < base_points[1]: + 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] + + +def scan_based_placement_route_generation(component_data, pcb_data, component_assign, cycle_assign, feeder_slot_result): + placement_result, head_sequence_result = [], [] + + mount_point_pos, mount_point_index, mount_point_angle, 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) + + lBoundary, rBoundary = min(mount_point_pos, key=lambda x: x[0])[0], max(mount_point_pos, key=lambda x: x[0])[0] + search_step = max((rBoundary - lBoundary) / 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(lBoundary, (lBoundary + rBoundary) / 2, search_step) + head_range = list(range(max_head_index)) + elif search_dir == 1: + # 从右向左搜索 + searchPoints = np.arange(rBoundary + 1e-3, (lBoundary + rBoundary) / 2, -search_step) + head_range = list(range(max_head_index - 1, -1, -1)) + else: + # 从中间向两边搜索 + searchPoints = np.arange(lBoundary, rBoundary, 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) + 1e-3 * 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): + cheby_distance += max(abs(point_pos[mount_seq][0] - point_pos[mount_seq + 1][0]), + abs(point_pos[mount_seq][1] - point_pos[mount_seq + 1][1])) + euler_distance += math.sqrt( + (point_pos[mount_seq][0] - point_pos[mount_seq + 1][0]) ** 2 + ( + point_pos[mount_seq][1] - point_pos[mount_seq + 1][1]) ** 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)] + test1 = sum(cycle_length) + test2 = sum(best_cycle_length) + 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 \ No newline at end of file diff --git a/dataloader.py b/dataloader.py index 2e2bfa6..c96f387 100644 --- a/dataloader.py +++ b/dataloader.py @@ -3,32 +3,36 @@ import copy from base_optimizer.optimizer_common import * -def load_data(filename: str, load_feeder=True, auto_register=True): +def load_data(filename: str, load_feeder=False, auto_register=True): filename = 'data/' + filename - part_content, step_content = False, False - part_start_line, step_start_line, part_end_line, step_end_line = -1, -1, -1, -1 + part_content, step_content, feeder_content = False, False, False + part_start_line, step_start_line, feeder_start_line = -1, -1, -1 + part_end_line, step_end_line, feeder_end_line = -1, -1, -1 + line_counter = 0 with open(filename, 'r') as file: line = file.readline() while line: if line == '[Part]\n': - part_content = True - part_start_line = line_counter + part_content, part_start_line = True, line_counter elif line == '[Step]\n': - step_content = True - step_start_line = line_counter + step_content, step_start_line = True, line_counter + elif line == '[Feeder]\n': + feeder_content, feeder_start_line = True, line_counter elif line == '\n': if part_content: - part_content = False - part_end_line = line_counter + part_content, part_end_line = False, line_counter elif step_content: - step_content = False - step_end_line = line_counter + step_content, step_end_line = False, line_counter + elif feeder_content: + feeder_content, feeder_end_line = False, line_counter line_counter += 1 line = file.readline() if part_content: part_end_line = line_counter + elif feeder_content: + feeder_end_line = line_counter else: step_end_line = line_counter @@ -64,7 +68,8 @@ def load_data(filename: str, load_feeder=True, auto_register=True): machine_index = machine_name[data['machine']] else: machine_index = 0 - pcb_data[machine_index] = pcb_data[machine_index]._append(data[step_col], ignore_index=True) + # pcb_data[machine_index] = pcb_data[machine_index]._append(data[step_col], ignore_index=True) + pcb_data[machine_index] = pd.concat([pcb_data[machine_index], pd.DataFrame(data[step_col]).T], ignore_index=True) part_col = ["part", "fdr", "nz", 'fdn'] try: @@ -121,8 +126,10 @@ def load_data(filename: str, load_feeder=True, auto_register=True): part_index = component_data[machine_index][component_data[machine_index].part == data.part].index.tolist()[ 0] if component_data[machine_index].loc[part_index].nz != data.nz: + component_data[machine_index].loc[part_index].nz = data.nz warning_info = 'the nozzle type of component ' + data.part + ' is not consistent with the pcb data' warnings.warn(warning_info, UserWarning) + component_data[machine_index].loc[part_index].fdr = data.fdr if data.fdn == 0: continue @@ -140,24 +147,21 @@ def load_data(filename: str, load_feeder=True, auto_register=True): # pcb_data[machine_index].reset_index(inplace=True) # 读取供料器基座数据 - feeder_data = defaultdict(pd.DataFrame) + feeder_columns = ['slot', 'part'] if load_feeder: - feeder_columns = ['slot', 'part', 'arg'] - for machine_index in range(machine_num): - feeder_data[machine_index] = pd.DataFrame(columns=feeder_columns) # arg表示是否为预分配,不表示分配数目 - for _, data in pcb_data[machine_index].iterrows(): - slot, part = data['fdr'].split(' ') - if slot[0] != 'F' and slot[0] != 'R': - continue - slot = int(slot[1:]) if slot[0] == 'F' else int(slot[1:]) + max_slot_index // 2 - feeder_data[machine_index] = pd.concat([feeder_data[machine_index], pd.DataFrame([slot, part, 1], index=feeder_columns).T], ignore_index=True) - - feeder_data[machine_index].drop_duplicates(subset='slot', inplace=True, ignore_index=True) - # 随机移除部分已安装的供料器 - # drop_index = random.sample(list(range(len(feeder_data))), len(feeder_data) // 2) - # feeder_data[machine_index].drop(index=drop_index, inplace=True) - - feeder_data[machine_index].sort_values(by='slot', ascending=True, inplace=True, ignore_index=True) + try: + if feeder_start_line != -1: + feeder_data = pd.DataFrame( + pd.read_csv(filepath_or_buffer=filename, sep='\t', header=None, skiprows=feeder_start_line + 1, + nrows=feeder_end_line - feeder_start_line - 1)) + feeder_data.columns = feeder_columns + else: + feeder_data = pd.DataFrame(columns=feeder_columns) + except: + feeder_data = pd.DataFrame(columns=feeder_columns) + else: + feeder_data = pd.DataFrame(columns=feeder_columns) + feeder_data.sort_values(by='slot', ascending=True, inplace=True, ignore_index=True) return pcb_data, component_data, feeder_data diff --git a/estimator.py b/estimator.py index 38bec09..73c5bff 100644 --- a/estimator.py +++ b/estimator.py @@ -3,8 +3,9 @@ from base_optimizer.optimizer_interface import * def exact_assembly_time(pcb_data, component_data): + feeder_data = pd.DataFrame(columns=['slot', 'part']) component_result, cycle_result, feeder_slot_result = feeder_priority_assignment(component_data, pcb_data, - hinter=False) + feeder_data, hinter=False) placement_result, head_sequence_result = greedy_placement_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result, hinter=False) diff --git a/generator.py b/generator.py index 5ead023..b341789 100644 --- a/generator.py +++ b/generator.py @@ -174,7 +174,7 @@ class DataMgr: def heuristic_objective(self, cp_points, cp_nozzle): if len(cp_points.keys()) == 0: - return 0 + return 0, 0, 0, 0 nozzle_heads, nozzle_points = defaultdict(int), defaultdict(int) for idx, points in cp_points.items(): if points == 0: diff --git a/lineopt_hyperheuristic.py b/lineopt_hyperheuristic.py index f3301eb..5ee74c1 100644 --- a/lineopt_hyperheuristic.py +++ b/lineopt_hyperheuristic.py @@ -410,9 +410,6 @@ def line_optimizer_hyperheuristic(component_data, pcb_data, machine_number): best_heuristic_list = population[0] best_component_list = component_list.copy() - val = cal_individual_val(heuristic_map, cp_index, cp_points, cp_nozzle, cp_feeders, board_width, board_height, - best_component_list, best_heuristic_list, machine_number, estimator) - machine_cp_points = convert_assignment_result(heuristic_map, cp_index, cp_points, cp_nozzle, cp_feeders, best_component_list, best_heuristic_list, machine_number) diff --git a/lineopt_model.py b/lineopt_model.py index b8689b3..4472466 100644 --- a/lineopt_model.py +++ b/lineopt_model.py @@ -1,12 +1,13 @@ from base_optimizer.optimizer_common import * from base_optimizer.result_analysis import * +from base_optimizer.smtopt_route import * def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True): mdl = Model('pcb assembly line optimizer') mdl.setParam('Seed', 0) - mdl.setParam('OutputFlag', hinter) # set whether output the debug information - # mdl.setParam('TimeLimit', 0.01) + mdl.setParam('OutputFlag', hinter) # set whether output the debug information + mdl.setParam('TimeLimit', 600 * 3) nozzle_type, component_type = [], [] for _, data in component_data.iterrows(): @@ -24,7 +25,7 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True): S = min(len(component_data) * ratio, 60) K = math.ceil(len(pcb_data) * 1.0 / H / M) + 1 # K = 3 - CompOfNozzle = [[0 for _ in range(J)] for _ in range(I)] # Compatibility + CompOfNozzle = [[0 for _ in range(J)] for _ in range(I)] # Compatibility component_point = [0 for _ in range(I)] for idx, data in component_data.iterrows(): @@ -41,7 +42,7 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True): d_minus = mdl.addVars(list_range(J), list_range(K), list_range(H), list_range(M), lb=0, vtype=GRB.CONTINUOUS) w = mdl.addVars(list_range(K), list_range(M), vtype=GRB.CONTINUOUS) - e = mdl.addVars(list_range(-(H - 1) * ratio, S), list_range(K), list_range(M), vtype=GRB.BINARY) + e = mdl.addVars(list_range(-(H - 1) * ratio, S), list_range(K), list_range(M), vtype=GRB.BINARY) f = mdl.addVars(list_range(S), list_range(I), list_range(M), vtype=GRB.BINARY, name='') obj = mdl.addVar(lb=0, ub=N, vtype=GRB.CONTINUOUS) @@ -87,7 +88,8 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True): in range(K) for m in range(M)) # feeder related - mdl.addConstrs(quicksum(f[s, i, m] for s in range(S) for m in range(M)) <= component_data.iloc[i].fdn for i in range(I)) + mdl.addConstrs( + quicksum(f[s, i, m] for s in range(S) for m in range(M)) <= component_data.iloc[i].fdn for i in range(I)) mdl.addConstrs(quicksum(f[s, i, m] for i in range(I)) <= 1 for s in range(S) for m in range(M)) mdl.addConstrs( quicksum(u[i, k, h, m] * v[s, k, h, m] for h in range(H) for k in range(K)) >= f[s, i, m] for i in range(I) for @@ -153,6 +155,10 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True): if abs(v[s, k, h, m].x) < 1e-3: continue feeder_slot_result[-1][h] = s + if sum(component_result[-1]) == -max_head_index: + component_result.pop(-1) + cycle_result.pop(-1) + feeder_slot_result.pop(-1) average_pos = round( (sum(head_place_pos) / len(head_place_pos) + stopper_pos[0] - slotf1_pos[0] + 1) / slot_interval) @@ -161,13 +167,13 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True): for h in range(H): if feeder_slot_result[k][h] == -1: continue - feeder_slot_result[k][h] = feeder_slot_result[k][h] * 2 + average_pos + feeder_slot_result[k][h] = feeder_slot_result[k][h] * 2 + average_pos placement_result, head_sequence = greedy_placement_route_generation(partial_component_data, partial_pcb_data, component_result, cycle_result, feeder_slot_result, hinter=False) print('----- Placement machine ' + str(m + 1) + ' ----- ') - opt_res = OptResult(component_result, cycle_result,feeder_slot_result, placement_result, head_sequence) + opt_res = OptResult(component_result, cycle_result, feeder_slot_result, placement_result, head_sequence) info = placement_info_evaluation(partial_component_data, partial_pcb_data, opt_res, hinter=False) optimization_assign_result(partial_component_data, partial_pcb_data, opt_res, nozzle_hinter=True, component_hinter=True, feeder_hinter=True) diff --git a/optimizer.py b/optimizer.py index c3310ad..225af6e 100644 --- a/optimizer.py +++ b/optimizer.py @@ -1,5 +1,7 @@ import time +import pandas as pd + from dataloader import * from lineopt_genetic import line_optimizer_genetic from lineopt_heuristic import line_optimizer_heuristic @@ -10,11 +12,9 @@ from lineopt_model import line_optimizer_model from base_optimizer.optimizer_interface import * -def optimizer(pcb_data, component_data, params): +def optimizer(pcb_data, component_data, feeder_data, params): if params.machine_number == 1: - assembly_info = [ - base_optimizer(1, pcb_data, component_data, pd.DataFrame(columns=['slot', 'part', 'arg']), params, - hinter=True)] + assembly_info = [base_optimizer(1, pcb_data, component_data, feeder_data, params, hinter=True)] return assembly_info if params.line_optimizer == 'hyper-heuristic' or params.line_optimizer == 'heuristic' or params.line_optimizer \ @@ -33,8 +33,8 @@ def optimizer(pcb_data, component_data, params): for machine_index in range(params.machine_number): assembly_info.append(base_optimizer(machine_index + 1, partial_pcb_data[machine_index], partial_component_data[machine_index], - pd.DataFrame(columns=['slot', 'part', 'arg']), params, hinter=True)) - elif params.line_optimizer == 'model': + pd.DataFrame(columns=['slot', 'part']), params, hinter=True)) + elif params.line_optimizer == 'mip-model': assembly_info = line_optimizer_model(component_data, pcb_data, params.machine_number) else: raise 'line optimizer method is not existed' @@ -44,19 +44,20 @@ def optimizer(pcb_data, component_data, params): @timer_wrapper def main(): - warnings.simplefilter(action='ignore', category=FutureWarning) # 参数解析 parser = argparse.ArgumentParser(description='assembly line optimizer implementation') parser.add_argument('--mode', default=1, type=int, help='mode: 0 -directly load pcb data without optimization ' 'for data analysis, 1 -optimize pcb data, 2 -batch test') - parser.add_argument('--filename', default='L01/KAN3-Z2.txt', type=str, help='load pcb data') + parser.add_argument('--filename', default='PCB.txt', type=str, help='load pcb data') + # parser.add_argument('--filename', default='chapter3-2/PCB2-8 Arg1.txt', type=str, help='load pcb data') parser.add_argument('--comp_register', default=1, type=int, help='register the component according the pcb data') - parser.add_argument('--machine_number', default=2, type=int, help='the number of machine in the assembly line') - parser.add_argument('--machine_optimizer', default='feeder-scan', type=str, help='optimizer for single machine') + parser.add_argument('--machine_number', default=1, type=int, help='the number of machine in the assembly line') + # parser.add_argument('--machine_optimizer', default='mip-model', type=str, help='optimizer for single machine') + parser.add_argument('--machine_optimizer', default='feeder-priority', type=str, help='optimizer for single machine') parser.add_argument('--line_optimizer', default='hyper-heuristic', type=str, help='optimizer for PCB assembly line') # parser.add_argument('--line_optimizer', default='model', type=str, help='optimizer for PCB assembly line') parser.add_argument('--feeder_limit', default=1, type=int, help='the upper feeder limit for each type of component') - parser.add_argument('--save', default=1, type=int, help='save the optimization result') + parser.add_argument('--save', default=0, type=int, help='save the optimization result') parser.add_argument('--save_suffix', default='(10)', type=str, help='load pcb data') params = parser.parse_args() @@ -88,14 +89,16 @@ def main(): f'standard deviation: {np.std([info.total_time for info in assembly_info]): .3f}') elif params.mode == 1: - sys.stdout = open(f'record/{params.filename[:-4]}-{params.line_optimizer}.txt', 'w') + # sys.stdout = open(f'record/{params.filename[:-4]}-{params.line_optimizer}.txt', 'w') # 加载PCB数据 - partial_pcb_data, partial_component_data, _ = load_data(params.filename) + partial_pcb_data, partial_component_data, feeder_data = load_data(params.filename, load_feeder=True) pcb_data, component_data = merge_data(partial_pcb_data, partial_component_data) start_time = time.time() - assembly_info = optimizer(pcb_data, component_data, params) - sys.stdout = sys.__stdout__ + + assembly_info = optimizer(pcb_data, component_data, feeder_data, params) + + # sys.stdout = sys.__stdout__ print(f'optimizer running time: {time.time() - start_time: .3f}') for machine_idx, info in enumerate(assembly_info): print(f'assembly time for machine {machine_idx + 1: d}: {info.total_time: .3f} s, total placement: ' @@ -105,6 +108,37 @@ def main(): print(f'finial assembly time: {max(info.total_time for info in assembly_info): .3f} s, ' f'standard deviation: {np.std([info.total_time for info in assembly_info]): .3f}') + elif params.mode == 2: + machine_optimizer = ['two-phase', 'hybrid-genetic', 'cell-division', 'feeder-priority', 'aggregation'] + running_round = 10 + opt_columns = ['Cycle', 'Pick', 'Nozzle-Change', 'Running-Time'] + opt_result, opt_runtime = defaultdict(pd.DataFrame), defaultdict(pd.DataFrame) + for opt in machine_optimizer: + opt_result[opt] =pd.DataFrame(columns=opt_columns) + opt_result[opt].index.name = 'file' + + for _, file in enumerate(os.listdir('data/')): + if file[-3:] != 'txt': + continue + partial_pcb_data, partial_component_data, feeder_data = load_data(file) + pcb_data, component_data = merge_data(partial_pcb_data, partial_component_data) + for opt in machine_optimizer: + for round_idx in range(running_round): + print(f'--- file : {file}, round : {round_idx}, optimizer : {opt} --- ') + + params = parser.parse_args(['--machine_optimizer', opt, '--machine_number', str(1)]) + + start_time = time.time() + assembly_info = optimizer(pcb_data, component_data, feeder_data, params) + + opt_result[opt].loc[file + str(round_idx + 1), 'Cycle'] = assembly_info[0].cycle_counter + opt_result[opt].loc[file + str(round_idx + 1), 'Pick'] = assembly_info[0].pickup_counter + opt_result[opt].loc[file + str(round_idx + 1), 'Nozzle-Change'] = assembly_info[0].nozzle_change_counter + opt_result[opt].loc[file + str(round_idx + 1), 'Running-Time'] = time.time() - start_time + + with pd.ExcelWriter('result/machine_optimizer.xlsx', engine='openpyxl') as writer: + for opt, result in opt_result.items(): + result.to_excel(writer, sheet_name=opt, float_format='%.3f', na_rep='') else: # line_optimizer = ['T-Solution', 'hyper-heuristic', 'genetic', 'reconfiguration'] line_optimizer = ['genetic']