from base_optimizer.optimizer_common import * @timer_wrapper def feeder_allocate(component_data, pcb_data, feeder_data, figure=False): feeder_points, feeder_division_points = defaultdict(int), defaultdict(int) # 供料器贴装点数 mount_center_pos = defaultdict(int) feeder_limit, feeder_arrange = defaultdict(int), defaultdict(int) part_nozzle = defaultdict(str) feeder_base = [-2] * max_slot_index # 已安装在供料器基座上的元件(-2: 未分配,-1: 占用状态) feeder_base_points = [0] * max_slot_index # 供料器基座结余贴装点数量 for _, data in pcb_data.iterrows(): pos, part = data.x + stopper_pos[0], data.part part_index = component_data[component_data.part == part].index.tolist()[0] if part not in component_data: feeder_limit[part_index] = component_data.loc[part_index]['feeder-limit'] feeder_arrange[part_index] = 0 feeder_points[part_index] += 1 mount_center_pos[part_index] += ((pos - mount_center_pos[part_index]) / feeder_points[part_index]) part_nozzle[part_index] = component_data.loc[part_index].nz for part_index, points in feeder_points.items(): feeder_division_points[part_index] = points // feeder_limit[part_index] nozzle_component, nozzle_component_points = defaultdict(list), defaultdict(list) for part, nozzle in part_nozzle.items(): for _ in range(feeder_limit[part]): nozzle_component[nozzle].append(part) nozzle_component_points[nozzle].append(feeder_points[part]) if feeder_data is not None: for _, feeder in feeder_data.iterrows(): slot, part = feeder.slot, feeder.part part_index = component_data[component_data.part == part].index.tolist()[0] # 供料器基座分配位置和对应贴装点数 feeder_base[slot], feeder_base_points[slot] = part_index, feeder_division_points[part_index] feeder_type = component_data.loc[part_index].fdr extra_width = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval while extra_width > 0: slot += 1 feeder_base[slot] = -1 extra_width -= slot_interval feeder_limit[part_index] -= 1 feeder_arrange[part_index] += 1 if feeder_limit[part_index] < 0: info = 'the number of arranged feeder for [' + part + '] exceeds the quantity limit' raise ValueError(info) for nozzle, components in nozzle_component.items(): if part_index in components: index_ = components.index(part_index) nozzle_component[nozzle].pop(index_) nozzle_component_points[nozzle].pop(index_) break nozzle_assigned_counter = optimal_nozzle_assignment(component_data, pcb_data) head_assign_indexes = list(range(max_head_index)) nozzle_pattern, optimal_nozzle_pattern, optimal_nozzle_points = [], None, 0 # nozzle_pattern = ['CN220', 'CN065','CN065','CN065','CN065','CN220'] # 先排序 nozzle_pattern_list = [] for nozzle, counter in nozzle_assigned_counter.items(): nozzle_pattern_list.append([nozzle, sum(nozzle_component_points[nozzle]) // counter]) nozzle_pattern_list.sort(key=lambda x: x[1], reverse=True) # 后确定吸嘴分配模式 upper_head, extra_head = defaultdict(int), defaultdict(int) head_index = [] for nozzle, head in nozzle_assigned_counter.items(): # 每个吸嘴能达成同时拾取数目的上限 upper_head[nozzle] = min(len(nozzle_component[nozzle]), head) extra_head[nozzle] = head - upper_head[nozzle] head_counter = (sum(upper_head.values()) - 1) // 2 while head_counter >= 0: if head_counter != (sum(upper_head.values()) - 1) - head_counter: head_index.append((sum(upper_head.values()) - 1) - head_counter) head_index.append(head_counter) head_counter -= 1 nozzle_pattern = [None for _ in range(sum(upper_head.values()))] for nozzle in upper_head.keys(): counter = upper_head[nozzle] while counter: nozzle_pattern[head_index[0]] = nozzle counter -= 1 head_index.pop(0) head = 0 while head + sum(extra_head.values()) <= len(nozzle_pattern): extra_head_cpy = copy.deepcopy(extra_head) increment = 0 while increment < sum(extra_head.values()): extra_head_cpy[nozzle_pattern[head + increment]] -= 1 increment += 1 check_extra_head = True for head_ in extra_head_cpy.values(): if head_ != 0: check_extra_head = False # 任一项不为0, 说明不构成 break if check_extra_head: increment = 0 while increment < sum(extra_head.values()): nozzle_pattern.append(nozzle_pattern[head + increment]) increment += 1 for nozzle in extra_head.keys(): extra_head[nozzle] = 0 break head += 1 for nozzle, head_ in extra_head.items(): while head_: nozzle_pattern.append(nozzle) head_ -= 1 assert len(nozzle_pattern) == max_head_index while True: best_assign, best_assign_points = [], [] best_assign_slot, best_assign_value = -1, -np.Inf best_nozzle_component, best_nozzle_component_points = None, None for slot in range(1, max_slot_index // 2 - (max_head_index - 1) * interval_ratio + 1): nozzle_assigned_counter_cpy = copy.deepcopy(nozzle_assigned_counter) feeder_assign, feeder_assign_points = [], [] tmp_feeder_limit, tmp_feeder_points = feeder_limit.copy(), feeder_points.copy() tmp_nozzle_component, tmp_nozzle_component_points = copy.deepcopy(nozzle_component), copy.deepcopy( nozzle_component_points) # 记录扫描到的已安装的供料器元件类型 for head in range(max_head_index): feeder_assign.append(feeder_base[slot + head * interval_ratio]) if scan_part := feeder_assign[-1] >= 0: nozzle = part_nozzle[scan_part] feeder_assign_points.append(feeder_base_points[slot + head * interval_ratio]) if feeder_assign_points[-1] <= 0: feeder_assign[-1], feeder_assign_points[-1] = -1, 0 elif nozzle in nozzle_assigned_counter_cpy.keys(): nozzle_assigned_counter_cpy[nozzle] -= 1 if nozzle_assigned_counter_cpy[nozzle] == 0: nozzle_assigned_counter_cpy.pop(nozzle) else: feeder_assign_points.append(0) if -2 not in feeder_assign: # 无可用槽位 if sum(feeder_assign_points) > optimal_nozzle_points: optimal_nozzle_points = sum(feeder_assign_points) optimal_nozzle_pattern = [''] * max_head_index for head in range(max_head_index): optimal_nozzle_pattern[head] = part_nozzle[feeder_assign[head]] continue assign_part_stack, assign_part_stack_points = [], [] for idx in head_assign_indexes: if feeder_assign[idx] != -2: continue if len(nozzle_pattern) == 0: # 吸嘴匹配模式为空,优先分配元件,根据分配元件倒推吸嘴匹配模式 nozzle_assign = '' max_points, max_nozzle_points = 0, 0 for nozzle in nozzle_assigned_counter_cpy.keys(): 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] if len(tmp_nozzle_component[nozzle_assign]) == 0: # 当前头对应吸嘴类型无可用元件,将计划分配的元件压入堆栈 part = max(tmp_feeder_points.keys(), key=lambda x: tmp_feeder_points[x] / tmp_feeder_limit[x] if tmp_feeder_limit[ x] != 0 else 0) for nozzle, component_list in tmp_nozzle_component.items(): if part in component_list: nozzle_assign = nozzle assign_part_stack.append(part) assign_part_stack_points.append(feeder_division_points[part]) break else: # 当前头对应吸嘴类型有可用元件,直接分配对应类型的元件 index_ = tmp_nozzle_component[nozzle_assign].index(max(tmp_nozzle_component[nozzle_assign], key=lambda x: tmp_feeder_points[x] / tmp_feeder_limit[x] if 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 while extra_width > 0: slot_ = slot + idx * interval_ratio + extra_slot if feeder_base[slot_] != -2 or slot_ > max_slot_index // 2: slot_overlap = True break extra_width -= slot_interval extra_slot += 1 # 可用供料器数目充足且不存在和已有供料器的占位冲突 if tmp_feeder_limit[part] > 0 and not slot_overlap: feeder_assign[idx], feeder_assign_points[idx] = part, feeder_division_points[part] extra_width, extra_head = feeder_width[feeder_type][0] + feeder_width[feeder_type][ 1] - head_interval, 1 while extra_width > 0 and idx + extra_head < max_head_index: feeder_assign[idx + extra_head] = -1 extra_head += 1 extra_width -= head_interval else: part = -1 # 存在位置冲突的元件,不占用可用供料器数 # 更新吸嘴匹配模式的吸嘴数 if nozzle_assign in nozzle_assigned_counter_cpy.keys(): nozzle_assigned_counter_cpy[nozzle_assign] -= 1 if nozzle_assigned_counter_cpy[nozzle_assign] == 0: nozzle_assigned_counter_cpy.pop(nozzle_assign) if part >= 0 and tmp_feeder_limit[part] == 0: continue if part in tmp_nozzle_component[nozzle_assign]: part_index = tmp_nozzle_component[nozzle_assign].index(part) tmp_nozzle_component[nozzle_assign].pop(part_index) tmp_nozzle_component_points[nozzle_assign].pop(part_index) tmp_feeder_limit[part] -= 1 tmp_feeder_points[part] -= feeder_division_points[part] # 元件堆栈出栈,首先分配吸嘴类型一致的头 if nozzle_pattern: for head, feeder in enumerate(feeder_assign): if feeder != -2: continue for idx, part in enumerate(assign_part_stack): 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 while extra_width > 0: slot_ = slot + head * interval_ratio + extra_slot if feeder_base[slot_] != -2 or slot_ > max_slot_index // 2: slot_overlap = True break extra_width -= slot_interval extra_slot += 1 if component_data.loc[part].nz == nozzle_pattern[head] and not slot_overlap: feeder_assign[head], feeder_assign_points[head] = assign_part_stack[idx], \ assign_part_stack_points[idx] assign_part_stack.pop(idx) assign_part_stack_points.pop(idx) break # 元件堆栈,然后分配元件堆栈中未分配的其它元件 for head in head_assign_indexes: if feeder_assign[head] != -2 or len(assign_part_stack) == 0: continue part, points = assign_part_stack[0], assign_part_stack_points[0] 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 while extra_width > 0: slot_ = slot + head * interval_ratio + extra_slot if feeder_base[slot_] != -2 or slot_ > max_slot_index // 2: slot_overlap = True break extra_width -= slot_interval extra_slot += 1 if not slot_overlap: feeder_assign[head], feeder_assign_points[head] = part, points extra_width, extra_head = feeder_width[feeder_type][0] + feeder_width[feeder_type][ 1] - head_interval, 1 while extra_width > 0 and head + extra_head < max_head_index: feeder_assign[head + extra_head] = -1 extra_head += 1 extra_width -= head_interval else: # 返还由于机械限位无法分配的,压入元件堆栈中的元素 nozzle = component_data.loc[part].nz tmp_nozzle_component[nozzle].insert(0, part) tmp_nozzle_component_points[nozzle].insert(0, points) assign_part_stack.pop(0) assign_part_stack_points.pop(0) # 仍然存在由于机械限位,无法进行分配的在堆栈中的元件 while assign_part_stack: part, points = assign_part_stack[0], assign_part_stack_points[0] nozzle = component_data.loc[part].nz tmp_nozzle_component[nozzle].insert(0, part) tmp_nozzle_component_points[nozzle].insert(0, points) assign_part_stack.pop(0) assign_part_stack_points.pop(0) nozzle_change_counter, average_slot = 0, [] for head, feeder_ in enumerate(feeder_assign): if feeder_ < 0: continue average_slot.append( (mount_center_pos[feeder_] - slotf1_pos[0]) / slot_interval + 1 - head * interval_ratio) if nozzle_pattern and component_data.loc[feeder_].nz != nozzle_pattern[head]: nozzle_change_counter += 1 if len(average_slot) == 0: continue average_slot = sum(average_slot) / len(average_slot) assign_value = 0 feeder_assign_points_cpy = feeder_assign_points.copy() while True: points_filter = list(filter(lambda x: x > 0, feeder_assign_points_cpy)) if not points_filter: break assign_value += e_gang_pick * min(points_filter) * (len(points_filter) - 1) for head, _ in enumerate(feeder_assign_points_cpy): if feeder_assign_points_cpy[head] == 0: continue feeder_assign_points_cpy[head] -= min(points_filter) assign_value -= 1e2 * e_nz_change * nozzle_change_counter + 1e-5 * abs(slot - average_slot) if assign_value >= best_assign_value and sum(feeder_assign_points) != 0: best_assign_value = assign_value 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 if not best_assign_points: break if len(nozzle_pattern) == 0: nozzle_pattern = [''] * max_head_index for idx, part in enumerate(best_assign): if part < 0: continue # 新安装的供料器 if feeder_base[best_assign_slot + idx * interval_ratio] != part: # 除去分配给最大化同时拾取周期的项,保留结余项 feeder_base_points[best_assign_slot + idx * interval_ratio] += ( feeder_division_points[part] - min(filter(lambda x: x > 0, best_assign_points))) feeder_points[part] -= feeder_division_points[part] feeder_limit[part] -= 1 feeder_arrange[part] += 1 if feeder_limit[part] == 0: feeder_division_points[part] = 0 for nozzle, components in nozzle_component.items(): if part in components: index_ = components.index(part) nozzle_component[nozzle].pop(index_) nozzle_component_points[nozzle].pop(index_) break feeder_division_points[part] = 0 else: # 已有的供料器 feeder_base_points[best_assign_slot + idx * interval_ratio] -= min( filter(lambda x: x > 0, best_assign_points)) # 更新供料器基座信息 feeder_base[best_assign_slot + idx * interval_ratio] = part feeder_type, extra_slot = component_data.loc[part].fdr, 0 extra_width = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval while extra_width > 0: 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 # 标记槽位已占用 extra_width -= slot_interval # 更新吸嘴信息 nozzle_pattern[idx] = component_data.loc[part].nz # 更新头分配的先后顺序 head_assign_indexes = np.array(best_assign_points).argsort().tolist() nozzle_component, nozzle_component_points = copy.deepcopy(best_nozzle_component), copy.deepcopy( best_nozzle_component_points) if sum(best_assign_points) > optimal_nozzle_points: optimal_nozzle_points = sum(best_assign_points) optimal_nozzle_pattern = nozzle_pattern.copy() assert not list(filter(lambda x: x < 0, feeder_limit.values())) # 分配供料器数目在限制范围内 # 若所有供料器均安装在基座上,重新对基座进行扫描,确定最优吸嘴模式(有序) if not optimal_nozzle_points: feeder_base, feeder_base_points = [-2] * max_slot_index, [0] * max_slot_index for _, feeder in feeder_data.iterrows(): part_index = component_data[component_data.part == feeder.part].index.tolist()[0] # 供料器基座分配位置和对应贴装点数 feeder_base[feeder.slot], feeder_base_points[feeder.slot] = part_index, feeder_division_points[part_index] # 前基座 TODO: 后基座 for slot in range(max_slot_index // 2 - (max_head_index - 1) * interval_ratio): sum_scan_points = 0 for head in range(max_head_index): sum_scan_points += feeder_base_points[slot + head * interval_ratio] if sum_scan_points > optimal_nozzle_points: optimal_nozzle_pattern = ['' for _ in range(max_head_index)] for head in range(max_head_index): if part := feeder_base[slot + head * interval_ratio] == -2: continue optimal_nozzle_pattern[head] = part_nozzle[part] # 更新供料器占位信息 for _, data in feeder_data.iterrows(): feeder_base[data.slot] = -1 for slot, feeder in enumerate(feeder_base): if feeder < 0: continue part = component_data.loc[feeder].part feeder_data.loc[len(feeder_data.index)] = [slot, part, 0] if figure: # 绘制供料器位置布局 for slot in range(max_slot_index // 2): plt.scatter(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1], marker='x', s=12, color='black', alpha=0.5) plt.text(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1] - 45, slot + 1, ha='center', va='bottom', size=8) feeder_assign_range = [] for _, feeder in feeder_data.iterrows(): part_index = component_data[component_data.part == feeder.part].index.tolist()[0] feeder_type = component_data.loc[part_index].fdr width = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] start = slotf1_pos[0] + slot_interval * (feeder.slot - 1) - slot_interval / 2 end = slotf1_pos[0] + slot_interval * (feeder.slot - 1) - slot_interval / 2 + width rec_x = [start, end, end, start] rec_y = [slotf1_pos[1] - 40, slotf1_pos[1] - 40, slotf1_pos[1] + 10, slotf1_pos[1] + 10] c = 'red' if feeder.arg == 0 else 'black' # 黑色表示已分配,红色表示新分配 plt.text(slotf1_pos[0] + slot_interval * (feeder.slot - 1), slotf1_pos[1] + 12, feeder.part + ': ' + str(feeder_points[part_index]), ha='center', size=7, rotation=90, color=c) plt.fill(rec_x, rec_y, facecolor='yellow', alpha=0.4) feeder_assign_range.append([start, end]) # 记录重叠区间 feeder_assign_range.sort(key=lambda x: x[0]) for i in range(1, len(feeder_assign_range)): if feeder_assign_range[i][0] < feeder_assign_range[i - 1][1]: start, end = feeder_assign_range[i][0], feeder_assign_range[i - 1][1] rec_x = [start, end, end, start] rec_y = [slotf1_pos[1] - 40, slotf1_pos[1] - 40, slotf1_pos[1] + 10, slotf1_pos[1] + 10] plt.fill(rec_x, rec_y, facecolor='red') 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') 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') for counter in range(max_slot_index // 2 + 1): pos = slotf1_pos[0] + (counter - 0.5) * slot_interval plt.plot([pos, pos], [slotf1_pos[1] + 10, slotf1_pos[1] - 40], color='black', linewidth=1) plt.ylim(-10, 100) plt.show() return optimal_nozzle_pattern @timer_wrapper def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern): feeder_assign_check = set() for _, feeder in feeder_data.iterrows(): feeder_assign_check.add(feeder.part) component_points = [0] * len(component_data) for i, data in pcb_data.iterrows(): part_index = component_data[component_data.part == data.part].index.tolist()[0] component_points[part_index] += 1 nozzle_type = component_data.loc[part_index].nz if nozzle_type not in nozzle_limit.keys() or nozzle_limit[nozzle_type] <= 0: info = 'there is no available nozzle [' + nozzle_type + '] for the assembly process' raise ValueError(info) assert len(feeder_assign_check) == len(component_points) - component_points.count(0) # 所有供料器均已分配槽位 feeder_part = [-1] * max_slot_index for _, data in feeder_data.iterrows(): component_index = component_data[component_data.part == data.part].index.tolist() if len(component_index) != 1: print('unregistered component: ', data.part, ' in slot', data.slot) continue component_index = component_index[0] feeder_part[data.slot] = component_index component_result, cycle_result, feeder_slot_result = [], [], [] # 贴装点索引和拾取槽位优化结果 nozzle_mode = [nozzle_pattern] # 吸嘴匹配模式 with tqdm(total=len(pcb_data)) as pbar: pbar.set_description('feeder scan process') pbar_prev = 0 value_increment_base = 0 while True: # === 周期内循环 === assigned_part = [-1 for _ in range(max_head_index)] # 当前扫描到的头分配元件信息 assigned_cycle = [0 for _ in range(max_head_index)] # 当前扫描到的元件最大分配次数 assigned_slot = [-1 for _ in range(max_head_index)] # 当前扫描到的供料器分配信息 best_assigned_eval_func = -float('inf') nozzle_insert_cycle = 0 for cycle_index, nozzle_cycle in enumerate(nozzle_mode): scan_eval_func_list = [] # 若干次扫描得到的最优解 # nozzle_cycle 吸嘴模式下,已扫描到的最优结果 cur_scan_part = [-1 for _ in range(max_head_index)] cur_scan_cycle = [0 for _ in range(max_head_index)] cur_scan_slot = [-1 for _ in range(max_head_index)] cur_nozzle_limit = copy.deepcopy(nozzle_limit) while True: best_scan_part, best_scan_cycle = [-1 for _ in range(max_head_index)], [-1 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) scan_eval_func, search_break = -float('inf'), True # 前供料器基座扫描 for slot in range(1, max_slot_index // 2 - (max_head_index - 1) * interval_ratio + 1): scan_cycle, scan_part, scan_slot = cur_scan_cycle.copy(), cur_scan_part.copy(), cur_scan_slot.copy() scan_nozzle_limit = copy.deepcopy(cur_nozzle_limit) # 预扫描确定各类型元件拾取数目(前瞻) preview_scan_part = defaultdict(int) for head in range(max_head_index): part = feeder_part[slot + head * interval_ratio] # 贴装头和拾取槽位满足对应关系 if scan_part[head] == -1 and part != -1 and component_points[part] > 0 and scan_part.count( part) < component_points[part]: preview_scan_part[part] += 1 component_counter = 0 for head in range(max_head_index): part = feeder_part[slot + head * interval_ratio] # 1.匹配条件满足: 贴装头和拾取槽位满足对应关系 if scan_part[head] == -1 and part != -1 and component_points[part] > 0 and scan_part.count( part) < component_points[part]: # 2.匹配条件满足:不超过可用吸嘴数的限制 nozzle = component_data.loc[part].nz if scan_nozzle_limit[nozzle] <= 0: continue # 3.增量条件满足: 引入新的元件类型不会使代价函数的值减少(前瞻) if scan_cycle.count(0) == max_head_index: gang_pick_change = component_points[part] else: prev_cycle = min(filter(lambda x: x > 0, scan_cycle)) # 同时拾取数的提升 gang_pick_change = min(prev_cycle, component_points[part] // preview_scan_part[part]) # 4.拾取移动距离条件满足: 邻近元件进行同时抓取,降低移动路径长度 # reference_slot = -1 # for head_, slot_ in enumerate(scan_slot): # if slot_ != -1: # reference_slot = slot_ - head_ * interval_ratio # if reference_slot != -1 and abs(reference_slot - slot) > (max_head_index - 1) * interval_ratio: # continue # 5.同时拾取的增量 和 吸嘴更换次数比较 prev_nozzle_change = 0 if cycle_index + 1 < len(nozzle_mode): 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]) if cycle_index + 1 < len(nozzle_mode): nozzle_change += 2 * (nozzle != nozzle_mode[cycle_index + 1][head]) nozzle_change -= prev_nozzle_change 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 scan_cycle[head] = component_points[part] // preview_scan_part[part] scan_slot[head] = slot + head * interval_ratio scan_nozzle_limit[nozzle] -= 1 nozzle_counter = 0 # 吸嘴更换次数 # 上一周期 for head, nozzle in enumerate(nozzle_cycle): if scan_part[head] == -1: continue if component_data.loc[scan_part[head]].nz != nozzle and nozzle != '': nozzle_counter += 2 # 下一周期(额外增加的吸嘴更换次数) if cycle_index + 1 < len(nozzle_mode): for head, nozzle in enumerate(nozzle_mode[cycle_index + 1]): if scan_part[head] == -1: continue prev_counter, new_counter = 0, 0 if nozzle_cycle[head] != nozzle and nozzle_cycle[head] != '' and nozzle != '': prev_counter += 2 if component_data.loc[scan_part[head]].nz != nozzle and nozzle != '': new_counter += 2 nozzle_counter += new_counter - prev_counter else: for head, nozzle in enumerate(nozzle_mode[0]): if scan_part[head] == -1: continue prev_counter, new_counter = 0, 0 if nozzle_cycle[head] != nozzle and nozzle_cycle[head] != '' and nozzle != '': prev_counter += 2 if component_data.loc[scan_part[head]].nz != nozzle and nozzle != '': new_counter += 2 nozzle_counter += new_counter - prev_counter if component_counter == 0: # 当前情形下未扫描到任何元件 continue search_break = False scan_part_head = defaultdict(list) for head, part in enumerate(scan_part): if part == -1: continue scan_part_head[part].append(head) for part, heads in scan_part_head.items(): part_cycle = component_points[part] // len(heads) for head in heads: scan_cycle[head] = part_cycle # 计算扫描后的代价函数,记录扫描后的最优解 # 短期收益 cycle = min(filter(lambda x: x > 0, scan_cycle)) gang_pick_counter, gang_pick_slot_set = 0, set() for head, pick_slot in enumerate(scan_slot): gang_pick_slot_set.add(pick_slot - head * interval_ratio) eval_func_short_term = e_gang_pick * (max_head_index - scan_slot.count(-1) - len( gang_pick_slot_set)) * cycle - e_nz_change * nozzle_counter # 长期收益 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 for pick_cycle in gang_pick_slot_dict.values(): while pick_cycle: min_cycle = min(pick_cycle) eval_func_long_term += e_gang_pick * (len(pick_cycle) - 1) * min(pick_cycle) pick_cycle = list(map(lambda c: c - min_cycle, pick_cycle)) pick_cycle = list(filter(lambda c: c > 0, pick_cycle)) eval_func_long_term -= e_nz_change * nozzle_counter ratio = 0.5 eval_func = (1 - ratio) * eval_func_short_term + ratio * eval_func_long_term if eval_func >= scan_eval_func: scan_eval_func = eval_func best_scan_part, best_scan_cycle = scan_part.copy(), scan_cycle.copy() best_scan_slot = scan_slot.copy() best_scan_nozzle_limit = copy.deepcopy(scan_nozzle_limit) if search_break: break scan_eval_func_list.append(scan_eval_func) cur_scan_part = best_scan_part.copy() cur_scan_slot = best_scan_slot.copy() cur_scan_cycle = best_scan_cycle.copy() 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) assigned_part = cur_scan_part.copy() assigned_slot = cur_scan_slot.copy() assigned_cycle = cur_scan_cycle.copy() nozzle_insert_cycle = cycle_index # 从供料器基座中移除对应数量的贴装点 nonzero_cycle = [cycle for cycle in assigned_cycle if cycle > 0] if not nonzero_cycle: value_increment_base -= max_head_index continue for head, slot in enumerate(assigned_slot): if assigned_part[head] == -1: continue component_points[feeder_part[slot]] -= min(nonzero_cycle) component_result.insert(nozzle_insert_cycle, assigned_part) cycle_result.insert(nozzle_insert_cycle, min(nonzero_cycle)) feeder_slot_result.insert(nozzle_insert_cycle, assigned_slot) # 更新吸嘴匹配模式 cycle_nozzle = nozzle_mode[nozzle_insert_cycle].copy() for head, component in enumerate(assigned_part): if component == -1: continue cycle_nozzle[head] = component_data.loc[component].nz nozzle_mode.insert(nozzle_insert_cycle + 1, cycle_nozzle) pbar.update(len(pcb_data) - sum(component_points) - pbar_prev) pbar_prev = len(pcb_data) - sum(component_points) if sum(component_points) == 0: break return component_result, cycle_result, feeder_slot_result