Files
smt-optimizer/base_optimizer/optimizer_feederpriority.py

762 lines
38 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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