增加超启发式线体优化算法
This commit is contained in:
@ -104,7 +104,7 @@ def optimizer_celldivision(pcb_data, component_data, hinter=True):
|
||||
point_num = len(pcb_data)
|
||||
component_cell = pd.DataFrame({'index': np.arange(len(component_data)), 'points': np.zeros(len(component_data), dtype=int)})
|
||||
for point_cnt in range(point_num):
|
||||
part = pcb_data.loc[point_cnt, 'fdr'].split(' ', 1)[1]
|
||||
part = pcb_data.loc[point_cnt, 'part']
|
||||
index = np.where(component_data['part'].values == part)
|
||||
component_cell.loc[index[0], 'points'] += 1
|
||||
component_cell = component_cell[~component_cell['points'].isin([0])]
|
||||
|
@ -29,7 +29,7 @@ head_nozzle = ['' for _ in range(max_head_index)] # 头上已经分配吸嘴
|
||||
slotf1_pos, slotr1_pos = [-31.267, 44.], [807., 810.545] # F1(前基座最左侧)、R1(后基座最右侧)位置
|
||||
fix_camera_pos = [269.531, 694.823] # 固定相机位置
|
||||
anc_marker_pos = [336.457, 626.230] # ANC基准点位置
|
||||
stopper_pos = [635.150, 124.738] # 止档块位置
|
||||
stopper_pos = [535.150, 124.738] # 止档块位置
|
||||
|
||||
# 算法权重参数
|
||||
e_nz_change, e_gang_pick = 4, 0.6
|
||||
@ -48,6 +48,7 @@ nozzle_limit = {'CN065': 6, 'CN040': 6, 'CN220': 6, 'CN400': 6, 'CN140': 6}
|
||||
|
||||
# 时间参数
|
||||
t_cycle = 0.3
|
||||
t_anc = 0.6
|
||||
t_pick, t_place = .078, .051 # 贴装/拾取用时
|
||||
t_nozzle_put, t_nozzle_pick = 0.9, 0.75 # 装卸吸嘴用时
|
||||
t_nozzle_change = t_nozzle_put + t_nozzle_pick
|
||||
@ -59,66 +60,22 @@ T_pp, T_tr, T_nc, T_pl = 2, 5, 25, 0
|
||||
|
||||
class OptInfo:
|
||||
def __init__(self):
|
||||
self.placement_time = 0
|
||||
self.total_time = .0 # 总组装时间
|
||||
self.total_points = .0 # 总贴装点数
|
||||
|
||||
self.cycle_counter = 0
|
||||
self.nozzle_change_counter = 0
|
||||
self.pickup_counter = 0
|
||||
self.pickup_time = .0 # 拾取过程运动时间
|
||||
self.round_time = .0 # 往返基座/基板运动时间
|
||||
self.place_time = .0 # 贴装过程运动时间
|
||||
self.operation_time = .0 # 拾取/贴装/换吸嘴等机械动作用时
|
||||
|
||||
self.pickup_movement = 0
|
||||
self.placement_movement = 0
|
||||
self.cycle_counter = 0 # 周期数
|
||||
self.nozzle_change_counter = 0 # 吸嘴更换次数
|
||||
self.anc_round_counter = 0 # 前往ANC次数
|
||||
self.pickup_counter = 0 # 拾取次数
|
||||
|
||||
|
||||
def optimization_assign_result(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
nozzle_hinter=False, component_hinter=False, feeder_hinter=False):
|
||||
if nozzle_hinter:
|
||||
columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']
|
||||
|
||||
nozzle_assign = pd.DataFrame(columns=columns)
|
||||
for cycle, components in enumerate(component_result):
|
||||
nozzle_assign.loc[cycle, 'cycle'] = cycle_result[cycle]
|
||||
for head in range(max_head_index):
|
||||
index = component_result[cycle][head]
|
||||
if index == -1:
|
||||
nozzle_assign.loc[cycle, 'H{}'.format(head + 1)] = ''
|
||||
else:
|
||||
nozzle_assign.loc[cycle, 'H{}'.format(head + 1)] = component_data.loc[index].nz
|
||||
|
||||
print(nozzle_assign)
|
||||
print('')
|
||||
|
||||
if component_hinter:
|
||||
columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']
|
||||
|
||||
component_assign = pd.DataFrame(columns=columns)
|
||||
for cycle, components in enumerate(component_result):
|
||||
component_assign.loc[cycle, 'cycle'] = cycle_result[cycle]
|
||||
for head in range(max_head_index):
|
||||
index = component_result[cycle][head]
|
||||
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
|
||||
|
||||
print(component_assign)
|
||||
print('')
|
||||
|
||||
if feeder_hinter:
|
||||
columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']
|
||||
|
||||
feedr_assign = pd.DataFrame(columns=columns)
|
||||
for cycle, components in enumerate(feeder_slot_result):
|
||||
feedr_assign.loc[cycle, 'cycle'] = cycle_result[cycle]
|
||||
for head in range(max_head_index):
|
||||
slot = feeder_slot_result[cycle][head]
|
||||
if slot == -1:
|
||||
feedr_assign.loc[cycle, 'H{}'.format(head + 1)] = 'A'
|
||||
else:
|
||||
feedr_assign.loc[cycle, 'H{}'.format(head + 1)] = 'F{}'.format(
|
||||
slot) if slot <= max_slot_index // 2 else 'R{}'.format(slot - max_head_index)
|
||||
|
||||
print(feedr_assign)
|
||||
print('')
|
||||
self.total_distance = .0 # 总移动路径
|
||||
self.place_distance = .0 # 贴装移动路径
|
||||
self.pickup_distance = .0 # 拾取移动路径
|
||||
|
||||
|
||||
def axis_moving_time(distance, axis=0):
|
||||
@ -172,8 +129,12 @@ def timer_wrapper(func):
|
||||
def measure_time(*args, **kwargs):
|
||||
start_time = time.time()
|
||||
result = func(*args, **kwargs)
|
||||
|
||||
print(f"function {func.__name__} running time : {time.time() - start_time:.3f} s")
|
||||
hinter = True
|
||||
for key, val in kwargs.items():
|
||||
if key == 'hinter':
|
||||
hinter = val
|
||||
if hinter:
|
||||
print(f"function {func.__name__} running time : {time.time() - start_time:.3f} s")
|
||||
return result
|
||||
|
||||
return measure_time
|
||||
@ -440,7 +401,7 @@ def dynamic_programming_cycle_path(pcb_data, cycle_placement, assigned_feeder):
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def greedy_placement_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result):
|
||||
def greedy_placement_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result, hinter=True):
|
||||
placement_result, head_sequence_result = [], []
|
||||
mount_point_index = [[] for _ in range(len(component_data))]
|
||||
mount_point_pos = [[] for _ in range(len(component_data))]
|
||||
@ -951,7 +912,7 @@ def constraint_swap_mutation(component_points, individual, machine_number):
|
||||
offspring = individual.copy()
|
||||
|
||||
idx, component_index = 0, random.randint(0, len(component_points) - 1)
|
||||
for _, points in component_points:
|
||||
for points in component_points.values():
|
||||
if component_index == 0:
|
||||
while True:
|
||||
index1, index2 = random.sample(range(points + machine_number - 2), 2)
|
||||
@ -988,6 +949,7 @@ def random_selective(data, possibility): # 依概率选择随机数
|
||||
possibility = [p / sum_val for p in possibility]
|
||||
|
||||
random_val = random.random()
|
||||
idx = 0
|
||||
for idx, val in enumerate(possibility):
|
||||
random_val -= val
|
||||
if random_val <= 0:
|
||||
@ -1061,17 +1023,25 @@ def get_line_config_number(machine_number, component_number):
|
||||
return div_counter
|
||||
|
||||
|
||||
def partial_data_convert(pcb_data, component_data, machine_assign, machine_number):
|
||||
assignment_result = copy.deepcopy(machine_assign)
|
||||
def convert_line_assigment(pcb_data, component_data, assignment_result):
|
||||
machine_number = len(assignment_result)
|
||||
|
||||
placement_points = []
|
||||
partial_pcb_data, partial_component_data = defaultdict(pd.DataFrame), defaultdict(pd.DataFrame)
|
||||
for machine_index in range(machine_number):
|
||||
partial_pcb_data[machine_index] = pd.DataFrame(columns=pcb_data.columns)
|
||||
partial_component_data[machine_index] = component_data.copy(deep=True)
|
||||
placement_points.append(sum(assignment_result[machine_index]))
|
||||
|
||||
assert sum(placement_points) == len(pcb_data)
|
||||
|
||||
# === averagely assign available feeder ===
|
||||
for part_index, data in component_data.iterrows():
|
||||
feeder_limit = data['feeder-limit']
|
||||
feeder_points = [assignment_result[machine_index][part_index] for machine_index in range(max_machine_index)]
|
||||
feeder_points = [assignment_result[machine_index][part_index] for machine_index in range(machine_number)]
|
||||
|
||||
for machine_index in range(machine_number):
|
||||
partial_component_data[machine_index].loc[part_index, 'points'] = 0
|
||||
|
||||
for machine_index in range(machine_number):
|
||||
if feeder_points[machine_index] == 0:
|
||||
@ -1079,7 +1049,7 @@ def partial_data_convert(pcb_data, component_data, machine_assign, machine_numbe
|
||||
|
||||
arg_feeder = max(math.floor(feeder_points[machine_index] / sum(feeder_points) * data['feeder-limit']), 1)
|
||||
|
||||
partial_component_data[machine_index].loc[part_index]['feeder-limit'] = arg_feeder
|
||||
partial_component_data[machine_index].loc[part_index, 'feeder-limit'] = arg_feeder
|
||||
feeder_limit -= arg_feeder
|
||||
|
||||
for machine_index in range(machine_number):
|
||||
@ -1088,27 +1058,126 @@ def partial_data_convert(pcb_data, component_data, machine_assign, machine_numbe
|
||||
|
||||
if feeder_points[machine_index] == 0:
|
||||
continue
|
||||
partial_component_data[machine_index].loc[part_index]['feeder-limit'] += 1
|
||||
partial_component_data[machine_index].loc[part_index, 'feeder-limit'] += 1
|
||||
feeder_limit -= 1
|
||||
|
||||
for machine_index in range(machine_number):
|
||||
if feeder_points[machine_index] > 0:
|
||||
assert partial_component_data[machine_index].loc[part_index]['feeder-limit'] > 0
|
||||
assert partial_component_data[machine_index].loc[part_index, 'feeder-limit'] > 0
|
||||
|
||||
# === assign placements ===
|
||||
component_machine_index = [0 for _ in range(len(component_data))]
|
||||
part2idx = defaultdict(int)
|
||||
for idx, data in component_data.iterrows():
|
||||
part2idx[data.part] = idx
|
||||
|
||||
machine_average_pos = [[0, 0] for _ in range(machine_number)]
|
||||
machine_step_counter = [0 for _ in range(machine_number)]
|
||||
part_pcb_data = defaultdict(list)
|
||||
for _, data in pcb_data.iterrows():
|
||||
part_index = component_data[component_data['part'] == data['part']].index.tolist()[0]
|
||||
while True:
|
||||
machine_index = component_machine_index[part_index]
|
||||
if assignment_result[machine_index][part_index] == 0:
|
||||
component_machine_index[part_index] += 1
|
||||
machine_index += 1
|
||||
else:
|
||||
break
|
||||
assignment_result[machine_index][part_index] -= 1
|
||||
partial_pcb_data[machine_index] = pd.concat([partial_pcb_data[machine_index], pd.DataFrame(data).T])
|
||||
part_pcb_data[part2idx[data.part]].append(data)
|
||||
|
||||
multiple_component_index = []
|
||||
for part_index in range(len(component_data)):
|
||||
machine_assign_set = []
|
||||
for machine_index in range(machine_number):
|
||||
if assignment_result[machine_index][part_index]:
|
||||
machine_assign_set.append(machine_index)
|
||||
|
||||
if len(machine_assign_set) == 1:
|
||||
for data in part_pcb_data[part_index]:
|
||||
machine_index = machine_assign_set[0]
|
||||
|
||||
machine_average_pos[machine_index][0] += data.x
|
||||
machine_average_pos[machine_index][1] += data.y
|
||||
|
||||
machine_step_counter[machine_index] += 1
|
||||
|
||||
partial_component_data[machine_index].loc[part_index, 'points'] += 1
|
||||
partial_pcb_data[machine_index] = pd.concat([partial_pcb_data[machine_index], pd.DataFrame(data).T])
|
||||
|
||||
elif len(machine_assign_set) > 1:
|
||||
multiple_component_index.append(part_index)
|
||||
|
||||
for machine_index in range(machine_number):
|
||||
if machine_step_counter[machine_index] == 0:
|
||||
continue
|
||||
machine_average_pos[machine_index][0] /= machine_step_counter[machine_index]
|
||||
machine_average_pos[machine_index][1] /= machine_step_counter[machine_index]
|
||||
|
||||
for part_index in multiple_component_index:
|
||||
for data in part_pcb_data[part_index]:
|
||||
idx = -1
|
||||
min_dist = None
|
||||
for machine_index in range(machine_number):
|
||||
if partial_component_data[machine_index].loc[part_index, 'points'] >= \
|
||||
assignment_result[machine_index][part_index]:
|
||||
continue
|
||||
dist = (data.x - machine_average_pos[machine_index][0]) ** 2 + (
|
||||
data.y - machine_average_pos[machine_index][1]) ** 2
|
||||
if min_dist is None or dist < min_dist:
|
||||
min_dist, idx = dist, machine_index
|
||||
|
||||
assert idx >= 0
|
||||
machine_step_counter[idx] += 1
|
||||
machine_average_pos[idx][0] += (1 - 1 / machine_step_counter[idx]) * machine_average_pos[idx][0] + data.x / \
|
||||
machine_step_counter[idx]
|
||||
machine_average_pos[idx][1] += (1 - 1 / machine_step_counter[idx]) * machine_average_pos[idx][1] + data.y / \
|
||||
machine_step_counter[idx]
|
||||
|
||||
partial_component_data[idx].loc[part_index, 'points'] += 1
|
||||
partial_pcb_data[idx] = pd.concat([partial_pcb_data[idx], pd.DataFrame(data).T])
|
||||
|
||||
# === adjust the number of available feeders for single optimization separately ===
|
||||
# for machine_index, data in partial_pcb_data.items():
|
||||
# part_info = [] # part info list:(part index, part points, available feeder-num, upper feeder-num)
|
||||
# for part_index, cp_data in partial_component_data[machine_index].iterrows():
|
||||
# if assignment_result[machine_index][part_index]:
|
||||
# part_info.append(
|
||||
# [part_index, assignment_result[machine_index][part_index], 1, cp_data['feeder-limit']])
|
||||
#
|
||||
# part_info = sorted(part_info, key=lambda x: x[1], reverse=True)
|
||||
# start_index, end_index = 0, min(max_head_index - 1, len(part_info) - 1)
|
||||
# while start_index < len(part_info):
|
||||
# assign_part_point, assign_part_index = [], []
|
||||
# for idx_ in range(start_index, end_index + 1):
|
||||
# for _ in range(part_info[idx_][2]):
|
||||
# assign_part_point.append(part_info[idx_][1] / part_info[idx_][2])
|
||||
# assign_part_index.append(idx_)
|
||||
#
|
||||
# variance = np.std(assign_part_point)
|
||||
# while start_index <= end_index:
|
||||
# part_info_index = assign_part_index[np.argmax(assign_part_point)]
|
||||
#
|
||||
# if part_info[part_info_index][2] < part_info[part_info_index][3]: # 供料器数目上限的限制
|
||||
# part_info[part_info_index][2] += 1
|
||||
# end_index -= 1
|
||||
#
|
||||
# new_assign_part_point, new_assign_part_index = [], []
|
||||
# for idx_ in range(start_index, end_index + 1):
|
||||
# for _ in range(part_info[idx_][2]):
|
||||
# new_assign_part_point.append(part_info[idx_][1] / part_info[idx_][2])
|
||||
# new_assign_part_index.append(idx_)
|
||||
#
|
||||
# new_variance = np.std(new_assign_part_point)
|
||||
# if variance < new_variance:
|
||||
# part_info[part_info_index][2] -= 1
|
||||
# end_index += 1
|
||||
# break
|
||||
#
|
||||
# variance = new_variance
|
||||
# assign_part_index, assign_part_point = new_assign_part_index.copy(), new_assign_part_point.copy()
|
||||
# else:
|
||||
# break
|
||||
#
|
||||
# start_index = end_index + 1
|
||||
# end_index = min(start_index + max_head_index - 1, len(part_info) - 1)
|
||||
#
|
||||
# max_avl_feeder = max(part_info, key=lambda x: x[2])[2]
|
||||
# for info in part_info:
|
||||
# partial_component_data[machine_index].loc[info[0], 'feeder-limit'] = math.ceil(info[2] / max_avl_feeder)
|
||||
|
||||
for machine_index in range(machine_number):
|
||||
partial_component_data[machine_index] = partial_component_data[machine_index][
|
||||
partial_component_data[machine_index]['points'] != 0].reset_index(drop=True)
|
||||
|
||||
return partial_pcb_data, partial_component_data
|
||||
|
||||
|
||||
|
@ -1,25 +1,121 @@
|
||||
import math
|
||||
|
||||
from base_optimizer.optimizer_common import *
|
||||
from base_optimizer.result_analysis import placement_info_evaluation
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
def feeder_priority_assignment(component_data, pcb_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'])
|
||||
# 第2步:分配供料器位置
|
||||
feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figure=False)
|
||||
# 第3步:扫描供料器基座,确定元件拾取的先后顺序
|
||||
component_assign, cycle_assign, feeder_slot_assign = feeder_base_scan(component_data, pcb_data, feeder_data)
|
||||
|
||||
info = placement_info_evaluation(component_data, pcb_data, component_assign, cycle_assign,
|
||||
feeder_slot_assign, None, None, hinter=False)
|
||||
|
||||
val = 0.4 * info.cycle_counter + 2.15 * info.nozzle_change_counter + 0.11 * info.pickup_counter \
|
||||
+ 0.005 * info.anc_round_counter
|
||||
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
|
||||
|
||||
if pbar:
|
||||
pbar.update(1)
|
||||
|
||||
return component_result, cycle_result, feeder_slot_result
|
||||
|
||||
|
||||
def feeder_nozzle_pattern(component_data):
|
||||
nozzle_pattern_list = []
|
||||
nozzle_points = defaultdict(int)
|
||||
for _, data in component_data.iterrows():
|
||||
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
|
||||
for idx, (nozzle, points) in enumerate(nozzle_points.items()):
|
||||
nozzle_heads[nozzle], nozzle_indices[idx] = 1, nozzle
|
||||
if min_points_nozzle is None or points < nozzle_points[min_points_nozzle]:
|
||||
min_points_nozzle = nozzle
|
||||
|
||||
while sum(nozzle_heads.values()) != max_head_index:
|
||||
max_cycle_nozzle = None
|
||||
|
||||
for nozzle, head_num in nozzle_heads.items():
|
||||
if max_cycle_nozzle is None or nozzle_points[nozzle] / head_num > nozzle_points[max_cycle_nozzle] / \
|
||||
nozzle_heads[max_cycle_nozzle]:
|
||||
max_cycle_nozzle = nozzle
|
||||
elif nozzle_points[nozzle] / head_num == nozzle_points[max_cycle_nozzle] / nozzle_heads[max_cycle_nozzle]:
|
||||
if head_num > nozzle_heads[max_cycle_nozzle]:
|
||||
max_cycle_nozzle = nozzle
|
||||
|
||||
assert max_cycle_nozzle is not None
|
||||
nozzle_heads[max_cycle_nozzle] += 1
|
||||
|
||||
for permu in itertools.permutations(nozzle_indices.keys()):
|
||||
nozzle_pattern_list.append([])
|
||||
for idx in permu:
|
||||
for _ in range(nozzle_heads[nozzle_indices[idx]]):
|
||||
nozzle_pattern_list[-1].append(nozzle_indices[idx])
|
||||
|
||||
if len(nozzle_points.keys()) > 1:
|
||||
nozzle_average_points = []
|
||||
for nozzle, head in nozzle_heads.items():
|
||||
nozzle_average_points.append([nozzle, head, nozzle_points[nozzle] / head])
|
||||
|
||||
nozzle_average_points = sorted(nozzle_average_points, key=lambda x: -x[2])
|
||||
idx = 0
|
||||
nozzle_pattern_list.append(['' for _ in range(max_head_index)])
|
||||
for nozzle, head, _ in nozzle_average_points:
|
||||
for _ in range(head):
|
||||
nozzle_pattern_list[-1][head_assign_indexes[idx]] = nozzle
|
||||
idx += 1
|
||||
|
||||
idx = 1
|
||||
nozzle_pattern_list.append(['' for _ in range(max_head_index)])
|
||||
for nozzle, head, _ in nozzle_average_points:
|
||||
for _ in range(head):
|
||||
nozzle_pattern_list[-1][head_assign_indexes[-idx]] = nozzle
|
||||
idx += 1
|
||||
nozzle_points.pop(min_points_nozzle)
|
||||
return nozzle_pattern_list
|
||||
|
||||
|
||||
def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figure=False, hinter=True):
|
||||
|
||||
feeder_points, feeder_division_points = defaultdict(int), defaultdict(int) # 供料器贴装点数
|
||||
mount_center_pos = defaultdict(int)
|
||||
mount_center_pos = defaultdict(float)
|
||||
|
||||
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 # 供料器基座结余贴装点数量
|
||||
component_index = defaultdict(int)
|
||||
for idx, data in component_data.iterrows():
|
||||
component_index[data.part] = idx
|
||||
|
||||
feeder_limit[idx] = data['feeder-limit']
|
||||
feeder_arrange[idx] = 0
|
||||
|
||||
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
|
||||
part_index = component_index[part]
|
||||
|
||||
feeder_points[part_index] += 1
|
||||
mount_center_pos[part_index] += ((pos - mount_center_pos[part_index]) / feeder_points[part_index])
|
||||
@ -37,7 +133,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
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]
|
||||
part_index = component_index[part]
|
||||
|
||||
# 供料器基座分配位置和对应贴装点数
|
||||
feeder_base[slot], feeder_base_points[slot] = part_index, feeder_division_points[part_index]
|
||||
@ -63,78 +159,14 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
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_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
|
||||
|
||||
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)]
|
||||
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(
|
||||
@ -144,24 +176,14 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
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]
|
||||
if feeder_assign[-1] >= 0:
|
||||
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]]
|
||||
if -2 not in feeder_assign:
|
||||
continue
|
||||
|
||||
assign_part_stack, assign_part_stack_points = [], []
|
||||
@ -172,7 +194,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
if len(nozzle_pattern) == 0: # 吸嘴匹配模式为空,优先分配元件,根据分配元件倒推吸嘴匹配模式
|
||||
nozzle_assign = ''
|
||||
max_points, max_nozzle_points = 0, 0
|
||||
for nozzle in nozzle_assigned_counter_cpy.keys():
|
||||
for nozzle in set(nozzle_pattern):
|
||||
if len(tmp_nozzle_component[nozzle]) == 0:
|
||||
continue
|
||||
part = max(tmp_nozzle_component[nozzle],
|
||||
@ -229,12 +251,6 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
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
|
||||
|
||||
@ -253,7 +269,6 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
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
|
||||
@ -282,7 +297,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
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
|
||||
extra_width = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval
|
||||
extra_slot = 1
|
||||
|
||||
slot_overlap = False
|
||||
while extra_width > 0:
|
||||
@ -295,8 +311,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
|
||||
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
|
||||
extra_width = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - head_interval
|
||||
extra_head = 1
|
||||
while extra_width > 0 and head + extra_head < max_head_index:
|
||||
feeder_assign[head + extra_head] = -1
|
||||
extra_head += 1
|
||||
@ -325,8 +341,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
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)
|
||||
average_slot.append((mount_center_pos[feeder_] - slotf1_pos[0]) / slot_interval + 1)
|
||||
|
||||
if nozzle_pattern and component_data.loc[feeder_].nz != nozzle_pattern[head]:
|
||||
nozzle_change_counter += 1
|
||||
|
||||
@ -346,7 +362,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
continue
|
||||
feeder_assign_points_cpy[head] -= min(points_filter)
|
||||
|
||||
assign_value -= 1e2 * e_nz_change * nozzle_change_counter + 1e-5 * abs(slot - average_slot)
|
||||
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:
|
||||
|
||||
@ -359,8 +375,6 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
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
|
||||
@ -410,34 +424,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
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
|
||||
@ -453,7 +441,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
# 绘制供料器位置布局
|
||||
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',
|
||||
plt.text(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1] - 45, str(slot + 1), ha='center', va='bottom',
|
||||
size=8)
|
||||
|
||||
feeder_assign_range = []
|
||||
@ -497,26 +485,31 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
plt.ylim(-10, 100)
|
||||
plt.show()
|
||||
|
||||
return optimal_nozzle_pattern
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
|
||||
def feeder_base_scan(component_data, pcb_data, feeder_data):
|
||||
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'
|
||||
component_index = defaultdict(int)
|
||||
for idx, data in component_data.iterrows():
|
||||
if data.nz not in nozzle_limit.keys() or nozzle_limit[data.nz] <= 0:
|
||||
info = 'there is no available nozzle [' + data.nz + '] for the assembly process'
|
||||
raise ValueError(info)
|
||||
component_points[idx] = data.points
|
||||
component_index[data.part] = idx
|
||||
|
||||
assert len(feeder_assign_check) == len(component_points) - component_points.count(0) # 所有供料器均已分配槽位
|
||||
|
||||
mount_center_slot = defaultdict(float)
|
||||
for _, data in pcb_data.iterrows():
|
||||
part_index = component_index[data.part]
|
||||
mount_center_slot[part_index] += (data.x - mount_center_slot[part_index])
|
||||
|
||||
for idx, pos in mount_center_slot.items():
|
||||
mount_center_slot[idx] = (pos / component_points[idx] + stopper_pos[0] - slotf1_pos[0]) / slot_interval + 1
|
||||
|
||||
feeder_part = [-1] * max_slot_index
|
||||
for _, data in feeder_data.iterrows():
|
||||
component_index = component_data[component_data.part == data.part].index.tolist()
|
||||
@ -528,233 +521,263 @@ def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
|
||||
|
||||
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)] # 当前扫描到的供料器分配信息
|
||||
sum_nozzle_points, nozzle_pattern = -1, None
|
||||
for slot in range(max_slot_index // 2 - (max_head_index - 1) * interval_ratio):
|
||||
cur_nozzle_points, cur_nozzle_pattern = 0, ['' for _ in range(max_head_index)]
|
||||
for head in range(max_head_index):
|
||||
if (part := feeder_part[slot + head * interval_ratio]) == -1:
|
||||
continue
|
||||
cur_nozzle_pattern[head] = component_data.loc[part].nz
|
||||
cur_nozzle_points += component_points[part]
|
||||
if cur_nozzle_points > sum_nozzle_points:
|
||||
sum_nozzle_points = cur_nozzle_points
|
||||
nozzle_pattern = cur_nozzle_pattern
|
||||
|
||||
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)
|
||||
nozzle_mode, nozzle_mode_cycle = [nozzle_pattern], [0] # 吸嘴匹配模式
|
||||
|
||||
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)
|
||||
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)] # 当前扫描到的供料器分配信息
|
||||
|
||||
scan_eval_func, search_break = -float('inf'), True
|
||||
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)
|
||||
|
||||
# 前供料器基座扫描
|
||||
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)
|
||||
while True:
|
||||
best_scan_part = [-1 for _ in range(max_head_index)]
|
||||
best_scan_cycle = [-1 for _ in range(max_head_index)]
|
||||
best_scan_slot = [-1 for _ in range(max_head_index)]
|
||||
|
||||
# 预扫描确定各类型元件拾取数目(前瞻)
|
||||
preview_scan_part = defaultdict(int)
|
||||
for head in range(max_head_index):
|
||||
part = feeder_part[slot + head * interval_ratio]
|
||||
best_scan_nozzle_limit = copy.deepcopy(cur_nozzle_limit)
|
||||
scan_eval_func, search_break = -float('inf'), True
|
||||
|
||||
# 贴装头和拾取槽位满足对应关系
|
||||
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
|
||||
# 前供料器基座扫描
|
||||
for slot in range(1, max_slot_index // 2 - (max_head_index - 1) * interval_ratio + 1):
|
||||
if sum(feeder_part[slot: slot + max_head_index * interval_ratio: interval_ratio]) == -max_head_index:
|
||||
continue
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
# 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])
|
||||
# 预扫描确定各类型元件拾取数目(前瞻)
|
||||
preview_scan_part = defaultdict(int)
|
||||
for head in range(max_head_index):
|
||||
part = feeder_part[slot + head * interval_ratio]
|
||||
|
||||
# 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
|
||||
# 贴装头和拾取槽位满足对应关系
|
||||
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
|
||||
|
||||
# 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])
|
||||
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
|
||||
|
||||
# 避免首个周期吸杆占用率低的问题
|
||||
if nozzle_cycle[head] == '':
|
||||
nozzle_change = 0
|
||||
else:
|
||||
nozzle_change = 2 * (nozzle != nozzle_cycle[head])
|
||||
# 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])
|
||||
|
||||
if cycle_index + 1 < len(nozzle_mode):
|
||||
nozzle_change += 2 * (nozzle != nozzle_mode[cycle_index + 1][head])
|
||||
nozzle_change -= prev_nozzle_change
|
||||
# 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
|
||||
|
||||
val = e_gang_pick * gang_pick_change - e_nz_change * nozzle_change
|
||||
if val < value_increment_base:
|
||||
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])
|
||||
|
||||
component_counter += 1
|
||||
# 避免首个周期吸杆占用率低的问题
|
||||
if nozzle_cycle[head] == '':
|
||||
nozzle_change = 0
|
||||
else:
|
||||
nozzle_change = 2 * (nozzle != nozzle_cycle[head])
|
||||
|
||||
scan_part[head] = part
|
||||
scan_cycle[head] = component_points[part] // preview_scan_part[part]
|
||||
scan_slot[head] = slot + head * interval_ratio
|
||||
if cycle_index + 1 < len(nozzle_mode):
|
||||
nozzle_change += 2 * (nozzle != nozzle_mode[cycle_index + 1][head])
|
||||
nozzle_change -= prev_nozzle_change
|
||||
|
||||
scan_nozzle_limit[nozzle] -= 1
|
||||
val = e_gang_pick * gang_pick_change - e_nz_change * nozzle_change
|
||||
if val < value_increment_base:
|
||||
continue
|
||||
|
||||
nozzle_counter = 0 # 吸嘴更换次数
|
||||
# 上一周期
|
||||
for head, nozzle in enumerate(nozzle_cycle):
|
||||
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 != '':
|
||||
nozzle_counter += 2
|
||||
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 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
|
||||
|
||||
if component_counter == 0: # 当前情形下未扫描到任何元件
|
||||
scan_part_head = defaultdict(list)
|
||||
for head, part in enumerate(scan_part):
|
||||
if part == -1:
|
||||
continue
|
||||
scan_part_head[part].append(head)
|
||||
|
||||
search_break = False
|
||||
for part, heads in scan_part_head.items():
|
||||
part_cycle = component_points[part] // len(heads)
|
||||
for head in heads:
|
||||
scan_cycle[head] = part_cycle
|
||||
|
||||
scan_part_head = defaultdict(list)
|
||||
for head, part in enumerate(scan_part):
|
||||
if part == -1:
|
||||
continue
|
||||
scan_part_head[part].append(head)
|
||||
# 计算扫描后的代价函数,记录扫描后的最优解
|
||||
# 短期收益
|
||||
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)
|
||||
|
||||
for part, heads in scan_part_head.items():
|
||||
part_cycle = component_points[part] // len(heads)
|
||||
for head in heads:
|
||||
scan_cycle[head] = part_cycle
|
||||
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
|
||||
|
||||
# 计算扫描后的代价函数,记录扫描后的最优解
|
||||
# 短期收益
|
||||
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)
|
||||
# 长期收益
|
||||
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_short_term = e_gang_pick * (max_head_index - scan_slot.count(-1) - len(
|
||||
gang_pick_slot_set)) * cycle - e_nz_change * nozzle_counter
|
||||
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
|
||||
|
||||
# 长期收益
|
||||
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])
|
||||
# 拾取过程中的移动路径
|
||||
pick_slot_set = set()
|
||||
for head, pick_slot in enumerate(scan_slot):
|
||||
if pick_slot == -1:
|
||||
continue
|
||||
pick_slot_set.add(pick_slot - head * interval_ratio)
|
||||
|
||||
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
|
||||
slot_offset = 0
|
||||
for head, part in enumerate(scan_part):
|
||||
if part == -1:
|
||||
continue
|
||||
slot_offset += abs(scan_slot[head] - mount_center_slot[part])
|
||||
|
||||
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()
|
||||
ratio = 0.5
|
||||
eval_func = (1 - ratio) * eval_func_short_term + ratio * eval_func_long_term - 1e-5 * (
|
||||
max(pick_slot_set) - min(pick_slot_set)) - 1e-5 * slot_offset
|
||||
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)
|
||||
best_scan_nozzle_limit = copy.deepcopy(scan_nozzle_limit)
|
||||
|
||||
if search_break:
|
||||
break
|
||||
if search_break:
|
||||
break
|
||||
|
||||
scan_eval_func_list.append(scan_eval_func)
|
||||
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_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)
|
||||
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) != 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()
|
||||
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]
|
||||
if not nonzero_cycle:
|
||||
value_increment_base -= max_head_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)
|
||||
|
||||
for head, slot in enumerate(assigned_slot):
|
||||
if assigned_part[head] == -1:
|
||||
continue
|
||||
component_points[feeder_part[slot]] -= min(nonzero_cycle)
|
||||
insert_cycle = sum([nozzle_mode_cycle[c] for c in range(nozzle_insert_cycle + 1)])
|
||||
|
||||
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)
|
||||
component_result.insert(insert_cycle, assigned_part)
|
||||
cycle_result.insert(insert_cycle, min(nonzero_cycle))
|
||||
feeder_slot_result.insert(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
|
||||
# 更新吸嘴匹配模式
|
||||
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
|
||||
|
||||
if cycle_nozzle == nozzle_mode[nozzle_insert_cycle]:
|
||||
nozzle_mode_cycle[nozzle_insert_cycle] += 1
|
||||
else:
|
||||
nozzle_mode.insert(nozzle_insert_cycle + 1, cycle_nozzle)
|
||||
nozzle_mode_cycle.insert(nozzle_insert_cycle + 1, 1)
|
||||
|
||||
pbar.update(len(pcb_data) - sum(component_points) - pbar_prev)
|
||||
pbar_prev = len(pcb_data) - sum(component_points)
|
||||
if sum(component_points) == 0:
|
||||
break
|
||||
if sum(component_points) == 0:
|
||||
break
|
||||
|
||||
return component_result, cycle_result, feeder_slot_result
|
||||
|
@ -12,38 +12,31 @@ from base_optimizer.result_analysis import *
|
||||
|
||||
def base_optimizer(machine_index, pcb_data, component_data, feeder_data=None, method='', hinter=False):
|
||||
|
||||
if method == 'cell_division': # 基于元胞分裂的遗传算法
|
||||
component_result, cycle_result, feeder_slot_result = optimizer_celldivision(pcb_data, component_data,
|
||||
hinter=False)
|
||||
if method == 'cell-division': # 基于元胞分裂的遗传算法
|
||||
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 method == 'feeder_scan': # 基于基座扫描的供料器优先算法
|
||||
# 第1步:分配供料器位置
|
||||
nozzle_pattern = feeder_allocate(component_data, pcb_data, feeder_data, figure=False)
|
||||
# 第2步:扫描供料器基座,确定元件拾取的先后顺序
|
||||
component_result, cycle_result, feeder_slot_result = feeder_base_scan(component_data, pcb_data, feeder_data,
|
||||
nozzle_pattern)
|
||||
|
||||
# 第3步:贴装路径规划
|
||||
elif method == 'feeder-scan': # 基于基座扫描的供料器优先算法
|
||||
component_result, cycle_result, feeder_slot_result = feeder_priority_assignment(component_data, pcb_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)
|
||||
|
||||
elif method == 'hybrid_genetic': # 基于拾取组的混合遗传算法
|
||||
elif method == 'hybrid-genetic': # 基于拾取组的混合遗传算法
|
||||
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_hybrid_genetic(
|
||||
pcb_data, component_data, hinter=False)
|
||||
|
||||
elif method == 'aggregation': # 基于batch-level的整数规划 + 启发式算法
|
||||
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_aggregation(
|
||||
component_data, pcb_data)
|
||||
elif method == 'genetic_scanning':
|
||||
elif method == 'genetic-scanning':
|
||||
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_genetic_scanning(
|
||||
component_data, pcb_data, hinter=False)
|
||||
elif method == 'mip_model':
|
||||
elif method == 'mip-model':
|
||||
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_mathmodel(
|
||||
component_data, pcb_data, hinter=True)
|
||||
elif method == "two_phase":
|
||||
elif method == "two-phase":
|
||||
component_result, feeder_slot_result, cycle_result = gurobi_optimizer(pcb_data, component_data, feeder_data,
|
||||
initial=True, partition=True,
|
||||
reduction=True, hinter=hinter)
|
||||
@ -51,32 +44,11 @@ def base_optimizer(machine_index, pcb_data, component_data, feeder_data=None, me
|
||||
placement_result, head_sequence = scan_based_placement_route_generation(component_data, pcb_data,
|
||||
component_result, cycle_result)
|
||||
else:
|
||||
raise 'method is not existed'
|
||||
raise 'machine optimizer method ' + method + ' is not existed'
|
||||
|
||||
info = OptInfo()
|
||||
assigned_nozzle = ['' if idx == -1 else component_data.loc[idx]['nz'] for idx in component_result[0]]
|
||||
info.cycle_counter = sum(cycle_result)
|
||||
|
||||
for cycle in range(len(cycle_result)):
|
||||
pick_slot = set()
|
||||
for head in range(max_head_index):
|
||||
idx = component_result[cycle][head]
|
||||
if idx == -1:
|
||||
continue
|
||||
|
||||
nozzle = component_data.loc[idx]['nz']
|
||||
if nozzle != assigned_nozzle[head]:
|
||||
if assigned_nozzle[head] != '':
|
||||
info.nozzle_change_counter += 1
|
||||
assigned_nozzle[head] = nozzle
|
||||
|
||||
pick_slot.add(feeder_slot_result[cycle][head] - head * interval_ratio)
|
||||
info.pickup_counter += len(pick_slot) * cycle_result[cycle]
|
||||
|
||||
pick_slot = list(pick_slot)
|
||||
pick_slot.sort()
|
||||
for idx in range(len(pick_slot) - 1):
|
||||
info.pickup_movement += abs(pick_slot[idx + 1] - pick_slot[idx])
|
||||
# 估算贴装用时
|
||||
info = placement_info_evaluation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
placement_result, head_sequence, hinter=False)
|
||||
|
||||
if hinter:
|
||||
optimization_assign_result(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
@ -85,12 +57,11 @@ def base_optimizer(machine_index, pcb_data, component_data, feeder_data=None, me
|
||||
print('----- Placement machine ' + str(machine_index) + ' ----- ')
|
||||
print('-Cycle counter: {}'.format(info.cycle_counter))
|
||||
|
||||
print('-Nozzle change counter: {}'.format(info.nozzle_change_counter))
|
||||
print('-Pick operation counter: {}'.format(info.pickup_counter))
|
||||
print('-Pick movement: {}'.format(info.pickup_movement))
|
||||
print(f'-Nozzle change counter: {info.nozzle_change_counter: d}')
|
||||
print(f'-ANC round: {info.anc_round_counter: d}')
|
||||
print(f'-Pick operation counter: {info.pickup_counter: d}')
|
||||
print(f'-Pick time: {info.pickup_time: .3f}, distance: {info.pickup_distance: .3f}')
|
||||
print(f'-Place time: {info.place_time: .3f}, distance: {info.place_distance: .3f}')
|
||||
print('------------------------------ ')
|
||||
|
||||
# 估算贴装用时
|
||||
info.placement_time = placement_time_estimate(component_data, pcb_data, component_result, cycle_result,
|
||||
feeder_slot_result, placement_result, head_sequence, hinter=False)
|
||||
return info
|
||||
|
@ -423,7 +423,7 @@ def optimization_assign_result(component_data, pcb_data, component_result, cycle
|
||||
component_assign.loc[cycle, 'H{}'.format(head + 1)] = ''
|
||||
else:
|
||||
part = component_data.loc[index]['part']
|
||||
component_assign.loc[cycle, 'H{}'.format(head + 1)] = part
|
||||
component_assign.loc[cycle, 'H{}'.format(head + 1)] = 'C' + str(index)
|
||||
|
||||
print(component_assign)
|
||||
print('')
|
||||
@ -446,32 +446,36 @@ def optimization_assign_result(component_data, pcb_data, component_result, cycle
|
||||
print('')
|
||||
|
||||
|
||||
def placement_time_estimate(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
placement_result, head_sequence, hinter=True) -> float:
|
||||
def placement_info_evaluation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
placement_result=None, head_sequence=None, hinter=False):
|
||||
# === 优化结果参数 ===
|
||||
info = OptInfo()
|
||||
# === 校验 ===
|
||||
total_points = 0
|
||||
info.total_points = 0
|
||||
for cycle, components in enumerate(component_result):
|
||||
for head, component in enumerate(components):
|
||||
if component == -1:
|
||||
continue
|
||||
total_points += cycle_result[cycle]
|
||||
info.total_points += cycle_result[cycle]
|
||||
|
||||
if total_points != len(pcb_data):
|
||||
if info.total_points != len(pcb_data):
|
||||
warning_info = 'the number of placement points is not match with the PCB data. '
|
||||
warnings.warn(warning_info, UserWarning)
|
||||
return 0.
|
||||
|
||||
for placements in placement_result:
|
||||
for placement in placements:
|
||||
if placement == -1:
|
||||
continue
|
||||
total_points -= 1
|
||||
if placement_result:
|
||||
total_points = info.total_points
|
||||
for placements in placement_result:
|
||||
for placement in placements:
|
||||
if placement == -1:
|
||||
continue
|
||||
total_points -= 1
|
||||
|
||||
if total_points != 0:
|
||||
warnings.warn(
|
||||
'the optimization result of component assignment result and placement result are not consistent. ',
|
||||
UserWarning)
|
||||
return 0.
|
||||
if total_points != 0:
|
||||
warnings.warn(
|
||||
'the optimization result of component assignment result and placement result are not consistent. ',
|
||||
UserWarning)
|
||||
return 0.
|
||||
|
||||
feeder_arrangement = defaultdict(set)
|
||||
for cycle, feeder_slots in enumerate(feeder_slot_result):
|
||||
@ -486,12 +490,6 @@ def placement_time_estimate(component_data, pcb_data, component_result, cycle_re
|
||||
warnings.warn(info, UserWarning)
|
||||
return 0.
|
||||
|
||||
total_pickup_time, total_round_time, total_place_time = .0, .0, 0 # 拾取用时、往返用时、贴装用时
|
||||
total_operation_time = .0 # 操作用时
|
||||
total_nozzle_change_counter = 0 # 总吸嘴更换次数
|
||||
total_pick_counter = 0 # 总拾取次数
|
||||
total_mount_distance, total_pick_distance = .0, .0 # 贴装距离、拾取距离
|
||||
total_distance = 0 # 总移动距离
|
||||
cur_pos, next_pos = anc_marker_pos, [0, 0] # 贴装头当前位置
|
||||
|
||||
# 初始化首个周期的吸嘴装配信息
|
||||
@ -503,7 +501,6 @@ def placement_time_estimate(component_data, pcb_data, component_result, cycle_re
|
||||
continue
|
||||
else:
|
||||
nozzle_assigned[head] = component_data.loc[idx]['nz']
|
||||
break
|
||||
|
||||
for cycle_set, _ in enumerate(component_result):
|
||||
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
|
||||
@ -527,9 +524,9 @@ def placement_time_estimate(component_data, pcb_data, component_result, cycle_re
|
||||
next_pos = anc_marker_pos
|
||||
move_time = max(axis_moving_time(cur_pos[0] - next_pos[0], 0),
|
||||
axis_moving_time(cur_pos[1] - next_pos[1], 1))
|
||||
total_round_time += move_time
|
||||
|
||||
total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
info.round_time += move_time
|
||||
info.anc_round_counter += 1
|
||||
info.total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
cur_pos = next_pos
|
||||
|
||||
pick_slot = list(set(pick_slot))
|
||||
@ -541,94 +538,95 @@ def placement_time_estimate(component_data, pcb_data, component_result, cycle_re
|
||||
next_pos = [slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1]]
|
||||
else:
|
||||
next_pos = [slotr1_pos[0] - slot_interval * (max_slot_index - slot - 1), slotr1_pos[1]]
|
||||
total_operation_time += t_pick
|
||||
total_pick_counter += 1
|
||||
info.operation_time += t_pick
|
||||
info.pickup_counter += 1
|
||||
|
||||
move_time = max(axis_moving_time(cur_pos[0] - next_pos[0], 0),
|
||||
axis_moving_time(cur_pos[1] - next_pos[1], 1))
|
||||
if idx == 0:
|
||||
total_round_time += move_time
|
||||
info.round_time += move_time
|
||||
else:
|
||||
total_pickup_time += move_time
|
||||
info.pickup_time += move_time
|
||||
|
||||
total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
info.total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
if slot != pick_slot[0]:
|
||||
total_pick_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
info.pickup_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
cur_pos = next_pos
|
||||
|
||||
# 固定相机检测
|
||||
for head in range(max_head_index):
|
||||
if component_result[cycle_set][head] == -1:
|
||||
continue
|
||||
camera = component_data.loc[component_result[cycle_set][head]]['camera']
|
||||
if camera == '固定相机':
|
||||
next_pos = [fix_camera_pos[0] - head * head_interval, fix_camera_pos[1]]
|
||||
move_time = max(axis_moving_time(cur_pos[0] - next_pos[0], 0),
|
||||
axis_moving_time(cur_pos[1] - next_pos[1], 1))
|
||||
total_round_time += move_time
|
||||
|
||||
total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
total_operation_time += t_fix_camera_check
|
||||
cur_pos = next_pos
|
||||
# for head in range(max_head_index):
|
||||
# if component_result[cycle_set][head] == -1:
|
||||
# continue
|
||||
# camera = component_data.loc[component_result[cycle_set][head]]['camera']
|
||||
# if camera == '固定相机':
|
||||
# next_pos = [fix_camera_pos[0] - head * head_interval, fix_camera_pos[1]]
|
||||
# move_time = max(axis_moving_time(cur_pos[0] - next_pos[0], 0),
|
||||
# axis_moving_time(cur_pos[1] - next_pos[1], 1))
|
||||
# info.round_time += move_time
|
||||
#
|
||||
# info.total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
# info.operation_time += t_fix_camera_check
|
||||
# cur_pos = next_pos
|
||||
|
||||
# 贴装路径
|
||||
for head in head_sequence[cycle]:
|
||||
index = placement_result[cycle][head]
|
||||
if index == -1:
|
||||
continue
|
||||
mount_pos.append([pcb_data.iloc[index]['x'] - head * head_interval + stopper_pos[0],
|
||||
pcb_data.iloc[index]['y'] + stopper_pos[1]])
|
||||
mount_angle.append(pcb_data.iloc[index]['r'])
|
||||
if placement_result and head_sequence:
|
||||
for head in head_sequence[cycle]:
|
||||
index = placement_result[cycle][head]
|
||||
if index == -1:
|
||||
continue
|
||||
mount_pos.append([pcb_data.iloc[index]['x'] - head * head_interval + stopper_pos[0],
|
||||
pcb_data.iloc[index]['y'] + stopper_pos[1]])
|
||||
mount_angle.append(pcb_data.iloc[index]['r'])
|
||||
|
||||
# 单独计算贴装路径
|
||||
for cntPoints in range(len(mount_pos) - 1):
|
||||
total_mount_distance += max(abs(mount_pos[cntPoints][0] - mount_pos[cntPoints + 1][0]),
|
||||
abs(mount_pos[cntPoints][1] - mount_pos[cntPoints + 1][1]))
|
||||
# 单独计算贴装路径
|
||||
for cntPoints in range(len(mount_pos) - 1):
|
||||
info.place_distance += max(abs(mount_pos[cntPoints][0] - mount_pos[cntPoints + 1][0]),
|
||||
abs(mount_pos[cntPoints][1] - mount_pos[cntPoints + 1][1]))
|
||||
|
||||
# 考虑R轴预旋转,补偿同轴角度转动带来的额外贴装用时
|
||||
total_operation_time += head_rotary_time(mount_angle[0]) # 补偿角度转动带来的额外贴装用时
|
||||
total_operation_time += t_nozzle_put * nozzle_put_counter + t_nozzle_pick * nozzle_pick_counter
|
||||
for idx, pos in enumerate(mount_pos):
|
||||
total_operation_time += t_place
|
||||
move_time = max(axis_moving_time(cur_pos[0] - pos[0], 0), axis_moving_time(cur_pos[1] - pos[1], 1))
|
||||
if idx == 0:
|
||||
total_round_time += move_time
|
||||
else:
|
||||
total_place_time += move_time
|
||||
# 考虑R轴预旋转,补偿同轴角度转动带来的额外贴装用时
|
||||
info.operation_time += head_rotary_time(mount_angle[0]) # 补偿角度转动带来的额外贴装用时
|
||||
info.operation_time += t_nozzle_put * nozzle_put_counter + t_nozzle_pick * nozzle_pick_counter
|
||||
for idx, pos in enumerate(mount_pos):
|
||||
info.operation_time += t_place
|
||||
move_time = max(axis_moving_time(cur_pos[0] - pos[0], 0), axis_moving_time(cur_pos[1] - pos[1], 1))
|
||||
if idx == 0:
|
||||
info.round_time += move_time
|
||||
else:
|
||||
info.place_time += move_time
|
||||
|
||||
total_distance += max(abs(cur_pos[0] - pos[0]), abs(cur_pos[1] - pos[1]))
|
||||
cur_pos = pos
|
||||
info.total_distance += max(abs(cur_pos[0] - pos[0]), abs(cur_pos[1] - pos[1]))
|
||||
cur_pos = pos
|
||||
|
||||
total_nozzle_change_counter += nozzle_put_counter + nozzle_pick_counter
|
||||
|
||||
total_time = total_pickup_time + total_round_time + total_place_time + total_operation_time
|
||||
minutes, seconds = int(total_time // 60), int(total_time) % 60
|
||||
millisecond = int((total_time - minutes * 60 - seconds) * 60)
|
||||
info.nozzle_change_counter += nozzle_put_counter + nozzle_pick_counter
|
||||
|
||||
info.total_time = info.pickup_time + info.round_time + info.place_time + info.operation_time
|
||||
minutes, seconds = int(info.total_time // 60), int(info.total_time) % 60
|
||||
millisecond = int((info.total_time - minutes * 60 - seconds) * 60)
|
||||
info.cycle_counter = sum(cycle_result)
|
||||
if hinter:
|
||||
optimization_assign_result(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
nozzle_hinter=False, component_hinter=False, feeder_hinter=False)
|
||||
|
||||
print('-Cycle counter: {}'.format(sum(cycle_result)))
|
||||
print('-Nozzle change counter: {}'.format(total_nozzle_change_counter // 2))
|
||||
print('-Pick operation counter: {}'.format(total_pick_counter))
|
||||
print('-Cycle counter: {}'.format(info.cycle_counter))
|
||||
print('-Nozzle change counter: {}'.format(info.nozzle_change_counter // 2))
|
||||
print('-Pick operation counter: {}'.format(info.pickup_counter))
|
||||
|
||||
print('-Expected mounting tour length: {} mm'.format(total_mount_distance))
|
||||
print('-Expected picking tour length: {} mm'.format(total_pick_distance))
|
||||
print('-Expected total tour length: {} mm'.format(total_distance))
|
||||
print('-Expected mounting tour length: {} mm'.format(info.place_distance))
|
||||
print('-Expected picking tour length: {} mm'.format(info.pickup_distance))
|
||||
print('-Expected total tour length: {} mm'.format(info.total_distance))
|
||||
|
||||
print('-Expected total moving time: {} s with pick: {}, round: {}, place = {}'.format(
|
||||
total_pickup_time + total_round_time + total_place_time, total_pickup_time, total_round_time,
|
||||
total_place_time))
|
||||
print('-Expected total operation time: {} s'.format(total_operation_time))
|
||||
info.pickup_time + info.round_time + info.place_time, info.pickup_time, info.round_time,
|
||||
info.place_time))
|
||||
print('-Expected total operation time: {} s'.format(info.operation_time))
|
||||
|
||||
if minutes > 0:
|
||||
print('-Mounting time estimation: {:d} min {} s {:2d} ms ({:.3f}s)'.format(minutes, seconds, millisecond,
|
||||
total_time))
|
||||
info.total_time))
|
||||
else:
|
||||
print('-Mounting time estimation: {} s {:2d} ms ({:.3f}s)'.format(seconds, millisecond, total_time))
|
||||
print('-Mounting time estimation: {} s {:2d} ms ({:.3f}s)'.format(seconds, millisecond, info.total_time))
|
||||
|
||||
return total_time
|
||||
return info
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user