增加预安装供料器功能、路径规划模型支持单点、整线优化支持批量处理

This commit is contained in:
2024-11-01 09:14:44 +08:00
parent 37f4e5b02c
commit 045f2f394d
15 changed files with 990 additions and 936 deletions

View File

@ -47,10 +47,10 @@ x_max_velocity, y_max_velocity = 1.4, 1.2
x_max_acceleration, y_max_acceleration = x_max_velocity / 0.079, y_max_velocity / 0.079 x_max_acceleration, y_max_acceleration = x_max_velocity / 0.079, y_max_velocity / 0.079
# TODO: 不同种类供料器宽度 # TODO: 不同种类供料器宽度
feeder_width = {'SM8': (7.25, 7.25), 'SM12': (7.25, 7.25), 'SM16': (7.25, 7.25), # feeder_width = {'SM8': (7.25, 7.25), 'SM12': (7.25, 7.25), 'SM16': (7.25, 7.25),
'SM24': (7.25, 7.25), 'SM32': (7.25, 7.25)} # 'SM24': (7.25, 7.25), 'SM32': (7.25, 7.25)}
# feeder_width = {'SM8': (7.25, 7.25), 'SM12': (7.00, 20.00), 'SM16': (7.00, 22.00), feeder_width = {'SM8': (7.25, 7.25), 'SM12': (7.00, 20.00), 'SM16': (7.00, 22.00),
# 'SM24': (7.00, 29.00), 'SM32': (7.00, 44.00)} 'SM24': (7.00, 29.00), 'SM32': (7.00, 44.00)}
# 可用吸嘴数量限制 # 可用吸嘴数量限制
nozzle_limit = {'CN065': 6, 'CN040': 6, 'CN020': 6, 'CN400': 6, 'CN140': 6} nozzle_limit = {'CN065': 6, 'CN040': 6, 'CN020': 6, 'CN400': 6, 'CN140': 6}
@ -67,7 +67,7 @@ t_fix_camera_check = 0.12 # 固定相机检测时间
T_pp, T_tr, T_nc, T_pl = 2, 5, 25, 0 T_pp, T_tr, T_nc, T_pl = 2, 5, 25, 0
# 时间参数 (数据拟合获得) # 时间参数 (数据拟合获得)
Fit_cy, Fit_nz, Fit_pu, Fit_pl, Fit_mv = 0.326, 0.8694, 0.159, 0.041, 0.001 Fit_cy, Fit_nz, Fit_pu, Fit_pl, Fit_mv = 0.326, 0.870, 0.159, 0.041, 0.001
class OptResult: class OptResult:
@ -108,7 +108,7 @@ class OptInfo:
print(f'-Pick time: {self.pickup_time: .3f}, Pick distance: {self.pickup_distance: .3f}') print(f'-Pick time: {self.pickup_time: .3f}, Pick distance: {self.pickup_distance: .3f}')
print(f'-Place time: {self.place_time: .3f}, Place distance: {self.place_distance: .3f}') print(f'-Place time: {self.place_time: .3f}, Place distance: {self.place_distance: .3f}')
print( print(
f'-Round time: {self.total_time - self.place_time - self.place_time: .3f}, Place distance: ' f'-Round time: {self.total_time - self.place_time - self.place_time: .3f}, Round distance: '
f'{self.total_distance - self.pickup_distance - self.place_distance: .3f}') f'{self.total_distance - self.pickup_distance - self.place_distance: .3f}')
minutes, seconds = int(self.total_time // 60), int(self.total_time) % 60 minutes, seconds = int(self.total_time // 60), int(self.total_time) % 60
@ -372,364 +372,6 @@ def feeder_assignment(component_data, pcb_data, component_result, cycle_result):
return feeder_slot_result return feeder_slot_result
def dynamic_programming_cycle_path(pcb_data, cycle_placement, assigned_feeder):
head_sequence = []
num_pos = sum([placement != -1 for placement in cycle_placement]) + 1
pos, head_set = [], []
feeder_set = set()
for head, feeder in enumerate(assigned_feeder):
if feeder == -1:
continue
head_set.append(head)
placement = cycle_placement[head]
if feeder != -1 and placement == -1:
print(assigned_feeder)
print(cycle_placement)
pos.append([pcb_data.iloc[placement]['x'] - head * head_interval + stopper_pos[0],
pcb_data.iloc[placement]['y'] + stopper_pos[1]])
feeder_set.add(feeder - head * interval_ratio)
pos.insert(0, [slotf1_pos[0] + ((min(list(feeder_set)) + max(list(feeder_set))) / 2 - 1) * slot_interval,
slotf1_pos[1]])
def get_distance(pos_1, pos_2):
return math.sqrt((pos_1[0] - pos_2[0]) ** 2 + (pos_1[1] - pos_2[1]) ** 2)
# 各节点之间的距离
dist = [[get_distance(pos_1, pos_2) for pos_2 in pos] for pos_1 in pos]
min_dist = [[np.inf for _ in range(num_pos)] for s in range(1 << num_pos)]
min_path = [[[] for _ in range(num_pos)] for s in range(1 << num_pos)]
# 状压dp搜索
for s in range(1, 1 << num_pos, 2):
# 考虑节点集合s必须包括节点0
if not (s & 1):
continue
for j in range(1, num_pos):
# 终点j需在当前考虑节点集合s内
if not (s & (1 << j)):
continue
if s == int((1 << j) | 1):
# 若考虑节点集合s仅含节点0和节点jdp边界赋予初值
# print('j:', j)
min_path[s][j] = [j]
min_dist[s][j] = dist[0][j]
# 枚举下一个节点i更新
for i in range(1, num_pos):
# 下一个节点i需在考虑节点集合s外
if s & (1 << i):
continue
if min_dist[s][j] + dist[j][i] < min_dist[s | (1 << i)][i]:
min_path[s | (1 << i)][i] = min_path[s][j] + [i]
min_dist[s | (1 << i)][i] = min_dist[s][j] + dist[j][i]
ans_dist = float('inf')
ans_path = []
# 求最终最短哈密顿回路
for i in range(1, num_pos):
if min_dist[(1 << num_pos) - 1][i] + dist[i][0] < ans_dist:
# 更新,回路化
ans_path = min_path[s][i]
ans_dist = min_dist[(1 << num_pos) - 1][i] + dist[i][0]
for parent in ans_path:
head_sequence.append(head_set[parent - 1])
start_head, end_head = head_sequence[0], head_sequence[-1]
if pcb_data.iloc[cycle_placement[start_head]]['x'] - start_head * head_interval > \
pcb_data.iloc[cycle_placement[end_head]]['x'] - end_head * head_interval:
head_sequence = list(reversed(head_sequence))
return head_sequence
@timer_wrapper
def greedy_placement_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
hinter=True):
placement_result, head_sequence_result = [], []
if len(pcb_data) == 0:
return placement_result, head_sequence_result
mount_point_index = [[] for _ in range(len(component_data))]
mount_point_pos = [[] for _ in range(len(component_data))]
for i in range(len(pcb_data)):
part = pcb_data.iloc[i]['part']
component_index = component_data[component_data['part'] == part].index.tolist()[0]
# 记录贴装点序号索引和对应的位置坐标
mount_point_index[component_index].append(i)
mount_point_pos[component_index].append([pcb_data.iloc[i]['x'], pcb_data.iloc[i]['y']])
search_dir = 1 # 0自左向右搜索 1自右向左搜索
for cycle_set in range(len(component_result)):
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
for cycle in range(floor_cycle, ceil_cycle):
if sum(component_result[cycle_set]) == -max_head_index:
continue
# search_dir = 1 - search_dir
assigned_placement = [-1] * max_head_index
max_pos = [max(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in
range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0]
min_pos = [min(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in
range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0]
point2head_range = min(math.floor((max_pos - min_pos) / head_interval) + 1, max_head_index)
# 最近邻确定
way_point = None
head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index)
for head_counter, head in enumerate(head_range):
if component_result[cycle_set][head] == -1:
continue
component_index = component_result[cycle_set][head]
if way_point is None or head_counter % point2head_range == 0:
index = 0
if way_point is None:
if search_dir:
index = np.argmax(mount_point_pos[component_index], axis=0)[0]
else:
index = np.argmin(mount_point_pos[component_index], axis=0)[0]
else:
for next_head in head_range:
component_index = component_result[cycle_set][next_head]
if assigned_placement[next_head] == -1 and component_index != -1:
num_points = len(mount_point_pos[component_index])
index = np.argmin(
[abs(mount_point_pos[component_index][i][0] - way_point[0]) * .1 + abs(
mount_point_pos[component_index][i][1] - way_point[1]) for i in
range(num_points)])
head = next_head
break
# index = np.argmax(mount_point_pos[component_index], axis=0)[0]
assigned_placement[head] = mount_point_index[component_index][index]
# 记录路标点
way_point = mount_point_pos[component_index][index]
way_point[0] += (max_head_index - head - 1) * head_interval if search_dir else -head * head_interval
mount_point_index[component_index].pop(index)
mount_point_pos[component_index].pop(index)
else:
head_index, point_index = -1, -1
min_cheby_distance, min_euler_distance = float('inf'), float('inf')
for next_head in range(max_head_index):
if assigned_placement[next_head] != -1 or component_result[cycle_set][next_head] == -1:
continue
next_comp_index = component_result[cycle_set][next_head]
for counter in range(len(mount_point_pos[next_comp_index])):
if search_dir:
delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0]
+ (max_head_index - next_head - 1) * head_interval)
else:
delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0]
- next_head * head_interval)
delta_y = abs(mount_point_pos[next_comp_index][counter][1] - way_point[1])
euler_distance = pow(axis_moving_time(delta_x, 0), 2) + pow(axis_moving_time(delta_y, 1), 2)
cheby_distance = max(axis_moving_time(delta_x, 0),
axis_moving_time(delta_y, 1)) + 5e-2 * euler_distance
if cheby_distance < min_cheby_distance or (abs(cheby_distance - min_cheby_distance) < 1e-9
and euler_distance < min_euler_distance):
# if euler_distance < min_euler_distance:
min_cheby_distance, min_euler_distance = cheby_distance, euler_distance
head_index, point_index = next_head, counter
component_index = component_result[cycle_set][head_index]
assert (0 <= head_index < max_head_index)
assigned_placement[head_index] = mount_point_index[component_index][point_index]
way_point = mount_point_pos[component_index][point_index]
way_point[0] += (max_head_index - head_index - 1) * head_interval if search_dir \
else -head_index * head_interval
mount_point_index[component_index].pop(point_index)
mount_point_pos[component_index].pop(point_index)
placement_result.append(assigned_placement) # 各个头上贴装的元件类型
head_sequence_result.append(
dynamic_programming_cycle_path(pcb_data, assigned_placement, feeder_slot_result[cycle_set]))
return placement_result, head_sequence_result
@timer_wrapper
def beam_search_for_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result):
beam_width = 4 # 集束宽度
base_points = [float('inf'), float('inf')]
mount_point_index = [[] for _ in range(len(component_data))]
mount_point_pos = [[] for _ in range(len(component_data))]
for i in range(len(pcb_data)):
part = pcb_data.loc[i]['part']
component_index = component_data[component_data['part'] == part].index.tolist()[0]
# 记录贴装点序号索引和对应的位置坐标
mount_point_index[component_index].append(i)
mount_point_pos[component_index].append([pcb_data.loc[i]['x'], pcb_data.loc[i]['y']])
# 记录最左下角坐标
if mount_point_pos[component_index][-1][0] < base_points[0]:
base_points[0] = mount_point_pos[component_index][-1][0]
if mount_point_pos[component_index][-1][1] < base_points[1]:
base_points[1] = mount_point_pos[component_index][-1][1]
beam_placement_sequence, beam_head_sequence = [], []
beam_mount_point_index, beam_mount_point_pos = [], []
for beam_counter in range(beam_width):
beam_mount_point_index.append(copy.deepcopy(mount_point_index))
beam_mount_point_pos.append(copy.deepcopy(mount_point_pos))
beam_placement_sequence.append([])
beam_head_sequence.append([])
beam_distance = [0 for _ in range(beam_width)] # 记录当前集束搜索点的点数
def argpartition(list, kth):
if kth < len(list):
return np.argpartition(list, kth)
else:
index, indexes = 0, []
while len(indexes) < kth:
indexes.append(index)
index += 1
if index >= len(list):
index = 0
return np.array(indexes)
with tqdm(total=100) as pbar:
search_dir = 0
pbar.set_description('route schedule')
for cycle_set in range(len(component_result)):
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
for cycle in range(floor_cycle, ceil_cycle):
search_dir = 1 - search_dir
beam_way_point = None
for beam_counter in range(beam_width):
beam_placement_sequence[beam_counter].append([-1 for _ in range(max_head_index)])
head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index)
for head in head_range:
component_index = component_result[cycle_set][head]
if component_index == -1:
continue
if beam_way_point is None:
# 首个贴装点的选取距离基准点最近的beam_width个点
beam_way_point = [[0, 0]] * beam_width
for beam_counter in range(beam_width):
if search_dir:
index = np.argmax(beam_mount_point_pos[beam_counter][component_index], axis=0)[0]
else:
index = np.argmin(beam_mount_point_pos[beam_counter][component_index], axis=0)[0]
beam_placement_sequence[beam_counter][-1][head] = beam_mount_point_index[beam_counter][component_index][index]
beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][index]
beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \
search_dir else -head * head_interval
beam_mount_point_index[beam_counter][component_index].pop(index)
beam_mount_point_pos[beam_counter][component_index].pop(index)
else:
# 后续贴装点
search_beam_distance = []
search_beam_index = [0] * (beam_width ** 2)
for beam_counter in range(beam_width ** 2):
search_beam_distance.append(beam_distance[beam_counter // beam_width])
for beam_counter in range(beam_width):
# 对于集束beam_counter + 1最近的beam_width个点
num_points = len(beam_mount_point_pos[beam_counter][component_index])
dist = []
for i in range(num_points):
if search_dir:
delta_x = axis_moving_time(
beam_mount_point_pos[beam_counter][component_index][i][0] -
beam_way_point[beam_counter][0] + (max_head_index - head - 1) * head_interval,
0)
else:
delta_x = axis_moving_time(
beam_mount_point_pos[beam_counter][component_index][i][0] -
beam_way_point[beam_counter][0] - head * head_interval, 0)
delta_y = axis_moving_time(beam_mount_point_pos[beam_counter][component_index][i][1] -
beam_way_point[beam_counter][1], 1)
dist.append(max(delta_x, delta_y))
indexes = argpartition(dist, kth=beam_width)[:beam_width]
# 记录中间信息
for i, index in enumerate(indexes):
search_beam_distance[i + beam_counter * beam_width] += dist[index]
search_beam_index[i + beam_counter * beam_width] = index
indexes = np.argsort(search_beam_distance)
beam_mount_point_pos_cpy = copy.deepcopy(beam_mount_point_pos)
beam_mount_point_index_cpy = copy.deepcopy(beam_mount_point_index)
beam_placement_sequence_cpy = copy.deepcopy(beam_placement_sequence)
beam_head_sequence_cpy = copy.deepcopy(beam_head_sequence)
beam_counter = 0
assigned_placement = []
for i, index in enumerate(indexes):
# 拷贝原始集束数据
beam_mount_point_pos[beam_counter] = copy.deepcopy(beam_mount_point_pos_cpy[index // beam_width])
beam_mount_point_index[beam_counter] = copy.deepcopy(beam_mount_point_index_cpy[index // beam_width])
beam_placement_sequence[beam_counter] = copy.deepcopy(beam_placement_sequence_cpy[index // beam_width])
beam_head_sequence[beam_counter] = copy.deepcopy(beam_head_sequence_cpy[index // beam_width])
# 更新各集束最新扫描的的贴装点
component_index = component_result[cycle_set][head]
beam_placement_sequence[beam_counter][-1][head] = \
beam_mount_point_index[beam_counter][component_index][search_beam_index[index]]
if beam_placement_sequence[beam_counter][
-1] in assigned_placement and beam_width - beam_counter < len(indexes) - i:
continue
assigned_placement.append(beam_placement_sequence[beam_counter][-1])
# 更新参考基准点
beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][search_beam_index[index]]
beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \
search_dir else -head * head_interval
# 更新各集束贴装路径长度,移除各集束已分配的贴装点
beam_distance[beam_counter] = search_beam_distance[index]
beam_mount_point_pos[beam_counter][component_index].pop(search_beam_index[index])
beam_mount_point_index[beam_counter][component_index].pop(search_beam_index[index])
beam_counter += 1
if beam_counter >= beam_width:
break
assert(beam_counter >= beam_width)
# 更新头贴装顺序
for beam_counter in range(beam_width):
beam_head_sequence[beam_counter].append(
dynamic_programming_cycle_path(pcb_data, beam_placement_sequence[beam_counter][-1],
feeder_slot_result[cycle_set]))
pbar.update(1 / sum(cycle_result) * 100)
index = np.argmin(beam_distance)
return beam_placement_sequence[index], beam_head_sequence[index]
def optimal_nozzle_assignment(component_data, pcb_data): def optimal_nozzle_assignment(component_data, pcb_data):
# === Nozzle Assignment === # === Nozzle Assignment ===
# number of points for nozzle & number of heads for nozzle # number of points for nozzle & number of heads for nozzle

View File

@ -6,6 +6,7 @@ from base_optimizer.smopt_feederpriority import *
from base_optimizer.smopt_aggregation import * from base_optimizer.smopt_aggregation import *
from base_optimizer.smopt_twophase import * from base_optimizer.smopt_twophase import *
from base_optimizer.smopt_mathmodel import * from base_optimizer.smopt_mathmodel import *
from base_optimizer.smtopt_route import *
from base_optimizer.result_analysis import * from base_optimizer.result_analysis import *
@ -16,12 +17,16 @@ def base_optimizer(machine_index, pcb_data, component_data, feeder_data, params,
component_result, cycle_result, feeder_slot_result = optimizer_celldivision(pcb_data, component_data) 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, placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
cycle_result, feeder_slot_result) cycle_result, feeder_slot_result)
elif params.machine_optimizer == 'feeder-scan': # 基于基座扫描的供料器优先算法 elif params.machine_optimizer == 'feeder-priority': # 基于基座扫描的供料器优先算法
component_result, cycle_result, feeder_slot_result = feeder_priority_assignment(component_data, pcb_data) component_result, cycle_result, feeder_slot_result = feeder_priority_assignment(component_data, pcb_data,
feeder_data)
placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result, placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
cycle_result, feeder_slot_result) cycle_result, feeder_slot_result)
# placement_result, head_sequence = beam_search_for_route_generation(component_data, pcb_data, component_result, # placement_result, head_sequence = beam_search_route_generation(component_data, pcb_data, component_result,
# cycle_result, feeder_slot_result) # cycle_result, feeder_slot_result)
# placement_result, head_sequence = scan_based_placement_route_generation(component_data, pcb_data,
# component_result, cycle_result,
# feeder_slot_result)
elif params.machine_optimizer == 'hybrid-genetic': # 基于拾取组的混合遗传算法 elif params.machine_optimizer == 'hybrid-genetic': # 基于拾取组的混合遗传算法
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_hybrid_genetic( component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_hybrid_genetic(
@ -41,8 +46,8 @@ def base_optimizer(machine_index, pcb_data, component_data, feeder_data, params,
initial=True, partition=True, initial=True, partition=True,
reduction=True, hinter=hinter) reduction=True, hinter=hinter)
placement_result, head_sequence = scan_based_placement_route_generation(component_data, pcb_data, placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
component_result, cycle_result) cycle_result, feeder_slot_result)
else: else:
raise 'machine optimizer method ' + params.method + ' is not existed' raise 'machine optimizer method ' + params.method + ' is not existed'
@ -56,6 +61,7 @@ def base_optimizer(machine_index, pcb_data, component_data, feeder_data, params,
info.print() info.print()
print('------------------------------ ') print('------------------------------ ')
# placement_route_schematic(pcb_data, component_data, opt_res, 1)
if params.save: if params.save:
output_optimize_result( output_optimize_result(
f'result/{params.filename[:-4]}-{params.line_optimizer}-M0{machine_index} {params.save_suffix}', f'result/{params.filename[:-4]}-{params.line_optimizer}-M0{machine_index} {params.save_suffix}',

View File

@ -77,7 +77,7 @@ def pickup_cycle_schematic(optimizer_result):
plt.show() plt.show()
def placement_route_schematic(pcb_data, optimizer_result, cycle=-1): def placement_route_schematic(pcb_data, component_data, optimizer_result, cycle=-1):
plt.figure('cycle {}'.format(cycle + 1)) plt.figure('cycle {}'.format(cycle + 1))
pos_x, pos_y = [], [] pos_x, pos_y = [], []
@ -128,7 +128,7 @@ def placement_route_schematic(pcb_data, optimizer_result, cycle=-1):
continue continue
placement = optimizer_result.placement_assign[placement_cycle][head] placement = optimizer_result.placement_assign[placement_cycle][head]
slot = optimizer_result.feeder_slot_assign[cycle_][head] slot = optimizer_result.feeder_slot_assign[cycle_][head]
feeder_part[slot] = pcb_data.loc[placement]['part'] feeder_part[slot] = pcb_data.loc[placement]['part'] + component_data.iloc[optimizer_result.component_assign[cycle_][head]].fdr
if slot not in feeder_counter.keys(): if slot not in feeder_counter.keys():
feeder_counter[slot] = 0 feeder_counter[slot] = 0
@ -140,9 +140,9 @@ def placement_route_schematic(pcb_data, optimizer_result, cycle=-1):
part + ': ' + str(feeder_counter[slot]), ha='center', size=7, rotation=90) part + ': ' + str(feeder_counter[slot]), ha='center', size=7, rotation=90)
plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)], plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
[slotf1_pos[1] + 10, slotf1_pos[1] + 10], color = 'black') [slotf1_pos[1] + 10, slotf1_pos[1] + 10], color='black')
plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)], plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
[slotf1_pos[1] - 40, slotf1_pos[1] - 40], color = 'black') [slotf1_pos[1] - 40, slotf1_pos[1] - 40], color='black')
for counter in range(max_slot_index // 2 + 1): for counter in range(max_slot_index // 2 + 1):
pos = slotf1_pos[0] + (counter - 0.5) * slot_interval pos = slotf1_pos[0] + (counter - 0.5) * slot_interval
@ -402,8 +402,8 @@ def optimization_assign_result(component_data, pcb_data, optimizer_result, nozzl
if index == -1: if index == -1:
component_assign.loc[cycle, 'H{}'.format(head + 1)] = '' component_assign.loc[cycle, 'H{}'.format(head + 1)] = ''
else: else:
component_assign.loc[cycle, 'H{}'.format(head + 1)] = component_data.loc[index]['part'] # component_assign.loc[cycle, 'H{}'.format(head + 1)] = component_data.loc[index]['part']
# component_assign.loc[cycle, 'H{}'.format(head + 1)] = 'C' + str(index) component_assign.loc[cycle, 'H{}'.format(head + 1)] = 'C' + str(index)
print(component_assign) print(component_assign)
print('') print('')
@ -432,7 +432,6 @@ def optimization_assign_result(component_data, pcb_data, optimizer_result, nozzl
def placement_info_evaluation(component_data, pcb_data, optimizer_result, hinter=False): def placement_info_evaluation(component_data, pcb_data, optimizer_result, hinter=False):
# === 优化结果参数 === # === 优化结果参数 ===
info = OptInfo() info = OptInfo()
# === 校验 === # === 校验 ===
info.total_points = 0 info.total_points = 0
for cycle, components in enumerate(optimizer_result.component_assign): for cycle, components in enumerate(optimizer_result.component_assign):

View File

@ -123,12 +123,14 @@ def optimizer_celldivision(pcb_data, component_data, hinter=True):
pop_val = [] pop_val = []
for pop in range(population_size): for pop in range(population_size):
component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data, try:
component_cell, component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data,
pop_generation[pop]) component_cell,
pop_val.append( pop_generation[pop])
component_assign_evaluate(component_data, component_result, cycle_result, feeder_slot_result)) pop_val.append(
component_assign_evaluate(component_data, component_result, cycle_result, feeder_slot_result))
except:
pop_val.append(1e4)
# 初始化随机生成种群 # 初始化随机生成种群
Upit = int(1.5 * np.sqrt(len(component_cell))) Upit = int(1.5 * np.sqrt(len(component_cell)))
@ -168,11 +170,14 @@ def optimizer_celldivision(pcb_data, component_data, hinter=True):
# 将元件元胞分配到各个吸杆上,计算价值函数 # 将元件元胞分配到各个吸杆上,计算价值函数
for pop in range(population_size): for pop in range(population_size):
component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data, try:
component_cell, component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data,
pop_generation[pop]) component_cell,
pop_val[pop] = component_assign_evaluate(component_data, component_result, cycle_result, pop_generation[pop])
feeder_slot_result) pop_val[pop] = component_assign_evaluate(component_data, component_result, cycle_result,
feeder_slot_result)
except:
pop_val[pop] = 1e4
assert(pop_val[pop] > 0) assert(pop_val[pop] > 0)
if min(pop_val) < min_pop_val: if min(pop_val) < min_pop_val:

View File

@ -1,3 +1,4 @@
import copy
import math import math
from functools import reduce from functools import reduce
@ -6,23 +7,23 @@ from base_optimizer.result_analysis import placement_info_evaluation
@timer_wrapper @timer_wrapper
def feeder_priority_assignment(component_data, pcb_data, hinter=True): def feeder_priority_assignment(component_data, pcb_data, feeder_data, hinter=True):
feeder_allocate_val = None feeder_allocate_val = None
component_result, cycle_result, feeder_slot_result = None, None, None component_result, cycle_result, feeder_slot_result = None, None, None
nozzle_pattern_list = feeder_nozzle_pattern(component_data) nozzle_pattern_list = feeder_nozzle_pattern(component_data)
pbar = tqdm(total=len(nozzle_pattern_list), desc='feeder priority process') if hinter else None pbar = tqdm(total=len(nozzle_pattern_list), desc='feeder priority process') if hinter else None
# 第1步确定吸嘴分配模式 # 第1步确定吸嘴分配模式
for nozzle_pattern in nozzle_pattern_list: for nozzle_pattern in nozzle_pattern_list:
feeder_data = pd.DataFrame(columns=['slot', 'part', 'arg']) feeder_data_cpy = copy.deepcopy(feeder_data)
# 第2步分配供料器位置 # 第2步分配供料器位置
feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figure=False) feeder_allocate(component_data, pcb_data, feeder_data_cpy, nozzle_pattern, figure=False)
# 第3步扫描供料器基座确定元件拾取的先后顺序 # 第3步扫描供料器基座确定元件拾取的先后顺序
component_assign, cycle_assign, feeder_slot_assign = feeder_base_scan(component_data, pcb_data, feeder_data) component_assign, cycle_assign, feeder_slot_assign = feeder_base_scan(component_data, pcb_data, feeder_data_cpy)
info = placement_info_evaluation(component_data, pcb_data, OptResult(component_assign, cycle_assign, info = placement_info_evaluation(component_data, pcb_data, OptResult(component_assign, cycle_assign,
feeder_slot_assign), hinter=False) feeder_slot_assign), hinter=False)
val = Fit_cy * info.cycle_counter + Fit_nz * info.nozzle_change_counter + Fit_pu * info.pickup_counter\
val = 0.356 * info.cycle_counter + 0.949 * info.nozzle_change_counter + 0.159 * info.pickup_counter \ + Fit_mv * info.pickup_distance
+ 0.002 * info.pickup_distance
if feeder_allocate_val is None or val < feeder_allocate_val: if feeder_allocate_val is None or val < feeder_allocate_val:
feeder_allocate_val = val feeder_allocate_val = val
component_result, cycle_result, feeder_slot_result = component_assign, cycle_assign, feeder_slot_assign component_result, cycle_result, feeder_slot_result = component_assign, cycle_assign, feeder_slot_assign
@ -40,10 +41,8 @@ def feeder_nozzle_pattern(component_data):
if data.points == 0: if data.points == 0:
continue continue
nozzle_points[data.nz] += data.points 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 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)] range(1, max_head_index + 1)]
while len(nozzle_points): while len(nozzle_points):
nozzle_heads, nozzle_indices = defaultdict(int), defaultdict(str), nozzle_heads, nozzle_indices = defaultdict(int), defaultdict(str),
min_points_nozzle = None min_points_nozzle = None
@ -97,7 +96,6 @@ def feeder_nozzle_pattern(component_data):
idx += 1 idx += 1
nozzle_points.pop(min_points_nozzle) nozzle_points.pop(min_points_nozzle)
return nozzle_pattern_list return nozzle_pattern_list
@ -197,21 +195,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur
if feeder_assign[idx] != -2: if feeder_assign[idx] != -2:
continue continue
if len(nozzle_pattern) == 0: # 吸嘴匹配模式空,优先分配元件,根据分配元件倒推吸嘴匹配模式 # 吸嘴匹配模式空,按对应吸嘴类型进行元件分配
nozzle_assign = '' nozzle_assign = nozzle_pattern[idx]
max_points, max_nozzle_points = 0, 0
for nozzle in set(nozzle_pattern):
if len(tmp_nozzle_component[nozzle]) == 0:
continue
part = max(tmp_nozzle_component[nozzle],
key=lambda x: tmp_feeder_points[x] / tmp_feeder_limit[x]
if tmp_feeder_points[x] != 0 else 0)
index_ = tmp_nozzle_component[nozzle].index(part)
if max_points < tmp_nozzle_component_points[nozzle][index_]:
max_points, nozzle_assign = tmp_nozzle_component_points[nozzle][index_], nozzle
else:
# 吸嘴匹配模式非空,按对应吸嘴类型进行元件分配
nozzle_assign = nozzle_pattern[idx]
if len(tmp_nozzle_component[nozzle_assign]) == 0: if len(tmp_nozzle_component[nozzle_assign]) == 0:
# 当前头对应吸嘴类型无可用元件,将计划分配的元件压入堆栈 # 当前头对应吸嘴类型无可用元件,将计划分配的元件压入堆栈
@ -233,6 +218,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur
tmp_feeder_limit[x] != 0 else 0)) tmp_feeder_limit[x] != 0 else 0))
part = tmp_nozzle_component[nozzle_assign][index_] part = tmp_nozzle_component[nozzle_assign][index_]
feeder_type = component_data.loc[part].fdr 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, extra_slot = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval, 1
slot_overlap = False slot_overlap = False
@ -241,6 +227,9 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur
if feeder_base[slot_] != -2 or slot_ > max_slot_index // 2: if feeder_base[slot_] != -2 or slot_ > max_slot_index // 2:
slot_overlap = True slot_overlap = True
break break
if idx + extra_slot // 2 < max_head_index and feeder_assign[idx + extra_slot // 2] >= 0:
slot_overlap = True
break
extra_width -= slot_interval extra_width -= slot_interval
extra_slot += 1 extra_slot += 1
@ -376,7 +365,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur
best_assign = feeder_assign.copy() best_assign = feeder_assign.copy()
best_assign_points = feeder_assign_points.copy() best_assign_points = feeder_assign_points.copy()
best_assign_slot = slot best_assign_slot = slot
best_nozzle_component, best_nozzle_component_points = tmp_nozzle_component, tmp_nozzle_component_points best_nozzle_component, best_nozzle_component_points = \
tmp_nozzle_component.copy(), tmp_nozzle_component_points.copy()
if not best_assign_points: if not best_assign_points:
break break
@ -384,7 +374,6 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur
for idx, part in enumerate(best_assign): for idx, part in enumerate(best_assign):
if part < 0: if part < 0:
continue continue
# 新安装的供料器 # 新安装的供料器
if feeder_base[best_assign_slot + idx * interval_ratio] != part: if feeder_base[best_assign_slot + idx * interval_ratio] != part:
# 除去分配给最大化同时拾取周期的项,保留结余项 # 除去分配给最大化同时拾取周期的项,保留结余项
@ -419,6 +408,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur
extra_slot += 1 extra_slot += 1
if feeder_base[best_assign_slot + idx * interval_ratio + extra_slot] == -2: if feeder_base[best_assign_slot + idx * interval_ratio + extra_slot] == -2:
feeder_base[best_assign_slot + idx * interval_ratio + extra_slot] = -1 # 标记槽位已占用 feeder_base[best_assign_slot + idx * interval_ratio + extra_slot] = -1 # 标记槽位已占用
else:
assert 'feeder allocation conflict'
extra_width -= slot_interval extra_width -= slot_interval
# 更新吸嘴信息 # 更新吸嘴信息
@ -441,7 +432,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur
continue continue
part = component_data.loc[feeder].part part = component_data.loc[feeder].part
feeder_data.loc[len(feeder_data.index)] = [slot, part, 0] feeder_data.loc[len(feeder_data.index)] = [slot, part]
if figure: if figure:
# 绘制供料器位置布局 # 绘制供料器位置布局
@ -505,9 +496,7 @@ def feeder_base_scan(component_data, pcb_data, feeder_data):
raise ValueError(info) raise ValueError(info)
component_points[idx] = data.points component_points[idx] = data.points
component_index[data.part] = idx component_index[data.part] = idx
if len(feeder_assign_check) != len(component_points) - component_points.count(0):
print(feeder_assign_check)
print(component_points)
assert len(feeder_assign_check) == len(component_points) - component_points.count(0) # 所有供料器均已分配槽位 assert len(feeder_assign_check) == len(component_points) - component_points.count(0) # 所有供料器均已分配槽位
mount_center_slot = defaultdict(float) mount_center_slot = defaultdict(float)
@ -562,7 +551,7 @@ def feeder_base_scan(component_data, pcb_data, feeder_data):
while True: while True:
best_scan_part = [-1 for _ in range(max_head_index)] best_scan_part = [-1 for _ in range(max_head_index)]
best_scan_cycle = [-1 for _ in range(max_head_index)] best_scan_cycle = [0 for _ in range(max_head_index)]
best_scan_slot = [-1 for _ in range(max_head_index)] best_scan_slot = [-1 for _ in range(max_head_index)]
best_scan_nozzle_limit = copy.deepcopy(cur_nozzle_limit) best_scan_nozzle_limit = copy.deepcopy(cur_nozzle_limit)
@ -619,10 +608,7 @@ def feeder_base_scan(component_data, pcb_data, feeder_data):
prev_nozzle_change = 2 * (nozzle_cycle[head] != nozzle_mode[cycle_index + 1][head]) prev_nozzle_change = 2 * (nozzle_cycle[head] != nozzle_mode[cycle_index + 1][head])
# 避免首个周期吸杆占用率低的问题 # 避免首个周期吸杆占用率低的问题
if nozzle_cycle[head] == '': nozzle_change = 2 * (nozzle != nozzle_cycle[head])
nozzle_change = 0
else:
nozzle_change = 2 * (nozzle != nozzle_cycle[head])
if cycle_index + 1 < len(nozzle_mode): if cycle_index + 1 < len(nozzle_mode):
nozzle_change += 2 * (nozzle != nozzle_mode[cycle_index + 1][head]) nozzle_change += 2 * (nozzle != nozzle_mode[cycle_index + 1][head])
@ -631,7 +617,6 @@ def feeder_base_scan(component_data, pcb_data, feeder_data):
val = e_gang_pick * gang_pick_change - e_nz_change * nozzle_change val = e_gang_pick * gang_pick_change - e_nz_change * nozzle_change
if val < value_increment_base: if val < value_increment_base:
continue continue
component_counter += 1 component_counter += 1
scan_part[head] = part scan_part[head] = part
@ -698,8 +683,6 @@ def feeder_base_scan(component_data, pcb_data, feeder_data):
# 长期收益 # 长期收益
gang_pick_slot_dict = defaultdict(list) gang_pick_slot_dict = defaultdict(list)
for head, pick_slot in enumerate(scan_slot): 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]) gang_pick_slot_dict[pick_slot - head * interval_ratio].append(scan_cycle[head])
eval_func_long_term = 0 eval_func_long_term = 0
@ -736,7 +719,6 @@ def feeder_base_scan(component_data, pcb_data, feeder_data):
if search_break: if search_break:
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_part = best_scan_part.copy()
@ -745,15 +727,14 @@ def feeder_base_scan(component_data, pcb_data, feeder_data):
cur_nozzle_limit = copy.deepcopy(best_scan_nozzle_limit) cur_nozzle_limit = copy.deepcopy(best_scan_nozzle_limit)
if len(scan_eval_func_list) != 0: if len(scan_eval_func_list) and sum(scan_eval_func_list) > best_assigned_eval_func:
if sum(scan_eval_func_list) > best_assigned_eval_func: best_assigned_eval_func = sum(scan_eval_func_list)
best_assigned_eval_func = sum(scan_eval_func_list)
assigned_part = cur_scan_part.copy() assigned_part = cur_scan_part.copy()
assigned_slot = cur_scan_slot.copy() assigned_slot = cur_scan_slot.copy()
assigned_cycle = cur_scan_cycle.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] nonzero_cycle = [cycle for cycle in assigned_cycle if cycle > 0]

View File

@ -66,7 +66,7 @@ def dynamic_programming_cycle_path(cycle_placement, cycle_points):
return head_sequence return head_sequence
def pickup_group_combination(component_nozzle, designated_nozzle, supply, supply_cycle, demand, demand_cycle): def pickup_group_combination(designated_nozzle, supply, supply_cycle, demand, demand_cycle):
combination, combination_cycle = demand.copy(), demand_cycle.copy() combination, combination_cycle = demand.copy(), demand_cycle.copy()
supply_cpy = supply.copy() supply_cpy = supply.copy()
@ -101,6 +101,9 @@ def pickup_group_combination(component_nozzle, designated_nozzle, supply, supply
combination_cycle[idx + max_match_offset] = supply_cycle[idx] combination_cycle[idx + max_match_offset] = supply_cycle[idx]
supply_cpy[idx] = None supply_cpy[idx] = None
if max_match_counter == 0:
break
return combination, combination_cycle return combination, combination_cycle
@ -158,7 +161,7 @@ def cal_individual_val(component_nozzle, component_point_pos, designated_nozzle,
if is_combinable: if is_combinable:
cost = cost - t0 cost = cost - t0
# combine sequenced pickup ρb and ps into ρu(union pickup) # combine sequenced pickup ρb and ps into ρu(union pickup)
Pu, Pu_cycle = pickup_group_combination(component_nozzle, designated_nozzle, Ps, Ps_cycle, Pd, Pd_cycle) Pu, Pu_cycle = pickup_group_combination(designated_nozzle, Ps, Ps_cycle, Pd, Pd_cycle)
# decide the placement cluster and sequencing of pickup ρu # decide the placement cluster and sequencing of pickup ρu
pickup_action_counter, place_action_counter = 0, max_head_index - Pu.count(None) pickup_action_counter, place_action_counter = 0, max_head_index - Pu.count(None)

View File

@ -38,73 +38,79 @@ def head_task_model(component_data, pcb_data, hinter=True):
# objective related # objective related
g = mdl.addVars(list_range(K), vtype=GRB.BINARY) g = mdl.addVars(list_range(K), vtype=GRB.BINARY)
d = mdl.addVars(list_range(K - 1), list_range(H), vtype=GRB.CONTINUOUS) d = mdl.addVars(list_range(K), list_range(H), vtype=GRB.CONTINUOUS)
u = mdl.addVars(list_range(K), vtype=GRB.INTEGER) u = mdl.addVars(list_range(K), vtype=GRB.INTEGER)
d_plus = mdl.addVars(list_range(J), list_range(H), list_range(K - 1), vtype=GRB.CONTINUOUS) d_plus = mdl.addVars(list_range(J), list_range(H), list_range(K), vtype=GRB.CONTINUOUS)
d_minus = mdl.addVars(list_range(J), list_range(H), list_range(K - 1), vtype=GRB.CONTINUOUS) d_minus = mdl.addVars(list_range(J), list_range(H), list_range(K), vtype=GRB.CONTINUOUS)
e = mdl.addVars(list_range(-(H - 1) * r, S), list_range(K), vtype=GRB.BINARY) e = mdl.addVars(list_range(-(H - 1) * r, S), list_range(K), vtype=GRB.BINARY)
f = mdl.addVars(list_range(S), list_range(I), vtype=GRB.BINARY, name='') f = mdl.addVars(list_range(S), list_range(I), vtype=GRB.BINARY, name='')
x = mdl.addVars(list_range(I), list_range(S), list_range(K), list_range(H), vtype=GRB.BINARY)
n = mdl.addVars(list_range(H), vtype=GRB.CONTINUOUS)
mdl.addConstrs(g[k] <= g[k + 1] for k in range(K - 1)) x = mdl.addVars(list_range(I), list_range(K), list_range(H), vtype=GRB.BINARY)
y = mdl.addVars(list_range(S), list_range(K), list_range(H), vtype=GRB.BINARY)
z = mdl.addVars(list_range(J), list_range(K), list_range(H), vtype=GRB.BINARY)
mdl.addConstrs(g[k] >= g[k + 1] for k in range(K - 1))
mdl.addConstrs( mdl.addConstrs(
quicksum(x[i, s, k, h] for i in range(I) for s in range(S)) <= g[k] for k in range(K) for h in range(H)) quicksum(x[i, k, h] for i in range(I)) <= g[k] for k in range(K) for h in range(H))
# nozzle no more than 1 for head h and cycle k # nozzle no more than 1 for head h and cycle k
mdl.addConstrs( mdl.addConstrs(quicksum(z[j, k ,h] for j in range(J)) <= 1 for k in range(K) for h in range(H))
quicksum(CompOfNozzle[i][j] * x[i, s, k, h] for i in range(I) for s in range(S) for j in range(J)) <= 1 for k in
range(K) for h in range(H))
# nozzle available number constraint # nozzle available number constraint
mdl.addConstrs( mdl.addConstrs(quicksum(z[j, k, h] for h in range(H)) <= H for k in range(K) for j in range(J))
quicksum(CompOfNozzle[i][j] * x[i, s, k, h] for i in range(I) for s in range(S) for h in range(H)) <= H for k in
range(K) for j in range(J))
# work completion # work completion
mdl.addConstrs( mdl.addConstrs(quicksum(x[i, k, h] for k in range(K) for h in range(H)) == component_point[i] for i in range(I))
quicksum(x[i, s, k, h] for s in range(S) for k in range(K) for h in range(H)) == component_point[i] for i in
range(I))
# nozzle change # nozzle change
mdl.addConstrs(quicksum(CompOfNozzle[i][j] * x[i, s, k, h] for i in range(I) for s in range(S)) - quicksum( mdl.addConstrs(
CompOfNozzle[i][j] * x[i, s, k + 1, h] for i in range(I) for s in range(S)) == d_plus[j, h, k] - d_minus[ x[i, k, h] <= quicksum(CompOfNozzle[i][j] * z[j, k, h] for j in range(J)) for i in range(I) for k in range(K)
j, h, k] for k in range(K - 1) for j in range(J) for h in range(H)) for h in range(H))
mdl.addConstrs( mdl.addConstrs(
2 * d[k, h] == quicksum(d_plus[j, h, k] for j in range(J)) + quicksum(d_minus[j, h, k] for j in range(J)) for k z[j, k, h] - z[j, k + 1, h] == d_plus[j, h, k] - d_minus[j, h, k] for k in range(K - 1) for j in range(J) for h
in range(K - 1) for h in range(H)) in range(H))
mdl.addConstrs(n[h] == quicksum(d[k, h] for k in range(K - 1)) - 0.5 for h in range(H)) mdl.addConstrs(
z[j, 0, h] - z[j, K - 1, h] == d_plus[j, h, K - 1] - d_minus[j, h, K - 1] for j in range(J) for h in range(H))
mdl.addConstrs(
d[k, h] == quicksum(d_plus[j, h, k] for j in range(J)) + quicksum(d_minus[j, h, k] for j in range(J)) for k
in range(K) for h in range(H))
# simultaneous pick # simultaneous pick
for s in range(-(H - 1) * r, S): for s in range(-(H - 1) * r, S):
rng = list(range(max(0, -math.floor(s / r)), min(H, math.ceil((S - s) / r)))) rng = list(range(max(0, -math.floor(s / r)), min(H, math.ceil((S - s) / r))))
for k in range(K): for k in range(K):
mdl.addConstr(quicksum(x[i, s + h * r, k, h] for h in rng for i in range(I)) <= M * e[s, k], name='') mdl.addConstr(quicksum(y[s + h * r, k, h] for h in rng) <= M * e[s, k], name='')
mdl.addConstr(quicksum(x[i, s + h * r, k, h] for h in rng for i in range(I)) >= e[s, k], name='') mdl.addConstr(quicksum(y[s + h * r, k, h] for h in rng) >= e[s, k], name='')
# pickup movement # pickup movement
mdl.addConstrs( mdl.addConstrs(
u[k] >= s1 * e[s1, k] - s2 * e[s2, k] for s1 in range(-(H - 1) * r, S) for s2 in range(-(H - 1) * r, S) for k in u[k] >= s1 * e[s1, k] - s2 * e[s2, k] + M * (e[s1, k] + e[s2, k] - 2) for s1 in range(-(H - 1) * r, S) for s2 in
range(K)) range(-(H - 1) * r, S) for k in range(K))
# feeder related # feeder related
mdl.addConstrs(quicksum(f[s, i] for s in range(S)) <= 1 for i in range(I)) mdl.addConstrs(quicksum(f[s, i] for s in range(S)) <= 1 for i in range(I))
mdl.addConstrs(quicksum(f[s, i] for i in range(I)) <= 1 for s in range(S)) mdl.addConstrs(quicksum(f[s, i] for i in range(I)) <= 1 for s in range(S))
mdl.addConstrs( mdl.addConstrs(
quicksum(x[i, s, k, h] for h in range(H) for k in range(K)) >= f[s, i] for i in range(I) for s in range(S)) quicksum(x[i, k, h] * y[s, k, h] for h in range(H) for k in range(K)) >= f[s, i] for i in range(I) for s in range(S))
mdl.addConstrs( mdl.addConstrs(
quicksum(x[i, s, k, h] for h in range(H) for k in range(K)) <= M * f[s, i] for i in range(I) for s in quicksum(x[i, k, h] * y[s, k, h] for h in range(H) for k in range(K)) <= M * f[s, i] for i in range(I) for s in
range(S)) range(S))
# relationship
mdl.addConstrs(
quicksum(x[i, k, h] for i in range(I)) == quicksum(y[s, k, h] for s in range(S)) for k in range(K) for h in
range(H))
# objective # objective
t_c, t_n, t_p, t_m = 2, 6, 1, 0.1 mdl.setObjective(Fit_cy * quicksum(g[k] for k in range(K)) + Fit_nz * quicksum(
mdl.setObjective(t_c * quicksum(g[k] for k in range(K)) + t_n * quicksum( d[k, h] for h in range(H) for k in range(K)) + Fit_pu * quicksum(
d[k, h] for h in range(H) for k in range(K - 1)) + t_p * quicksum( e[s, k] for s in range(-(H - 1) * r, S) for k in range(K)) + Fit_mv * head_interval * quicksum(u[k] for k in range(K)),
e[s, k] for s in range(-(H - 1) * r, S) for k in range(K)) + t_m * quicksum(u[k] for k in range(K)),
GRB.MINIMIZE) GRB.MINIMIZE)
mdl.optimize() mdl.optimize()
@ -119,14 +125,17 @@ def head_task_model(component_data, pcb_data, hinter=True):
cycle_result.append(1) cycle_result.append(1)
for h in range(H): for h in range(H):
for i in range(I): for i in range(I):
for s in range(S): if abs(x[i, k, h].x) > 1e-6:
if abs(x[i, s, k, h].x) > 1e-6: component_result[-1][h] = i
component_result[-1][h] = i
feeder_slot_result[-1][h] = slot_start + s * interval_ratio - 1 for s in range(S):
if abs(y[s, k, h].x) > 1e-6:
feeder_slot_result[-1][h] = slot_start + s * interval_ratio - 1
if hinter: if hinter:
print(component_result) print(component_result)
print(feeder_slot_result) print(feeder_slot_result)
return component_result, cycle_result, feeder_slot_result return component_result, cycle_result, feeder_slot_result
@ -134,7 +143,7 @@ def place_route_model(component_data, pcb_data, component_result, feeder_slot_re
mdl = Model('place_route') mdl = Model('place_route')
mdl.setParam('Seed', 0) mdl.setParam('Seed', 0)
mdl.setParam('OutputFlag', hinter) # set whether output the debug information mdl.setParam('OutputFlag', hinter) # set whether output the debug information
# mdl.setParam('TimeLimit', 20) mdl.setParam('TimeLimit', 10)
component_type = [] component_type = []
for _, data in component_data.iterrows(): for _, data in component_data.iterrows():
@ -213,21 +222,23 @@ def place_route_model(component_data, pcb_data, component_result, feeder_slot_re
for h in range(H): for h in range(H):
if component_result[k][h] == -1: if component_result[k][h] == -1:
# no components on the head # no components on the head
mdl.addConstr(quicksum(w[p, q, k, a] for a in A_contain(h) for q in range(P) for p in range(P)) == 0) mdl.addConstrs(quicksum(w[p, q, k, a] for a in A_from(h) for q in range(P)) + quicksum(
w[q, p, k, a] for a in A_to(h) for q in range(P)) + y[p, k, h] + z[p, k, h] <= 0 for p in range(P))
else: else:
# there are components on the head # there are components on the head
mdl.addConstrs((quicksum(w[p, q, k, a] for a in A_from(h) for q in range(P)) + quicksum( mdl.addConstrs(quicksum(w[p, q, k, a] for a in A_from(h) for q in range(P)) + quicksum(
w[q, p, k, a] for a in A_to(h) for q in range(P))) / 2 <= CompOfPoint[component_result[k][h]][p] for w[q, p, k, a] for a in A_to(h) for q in range(P)) + y[p, k, h] + z[p, k, h] <= 4 *
p in range(P)) CompOfPoint[component_result[k][h]][p] for p in range(P))
# each head corresponds to a maximum of one point in each cycle # each head corresponds to a maximum of one point in each cycle
mdl.addConstrs( mdl.addConstrs(
quicksum(w[p, q, k, a] for p in range(P) for q in range(P) for a in A_contain(h)) <= 2 for k in range(K) for h quicksum(w[p, q, k, a] for p in range(P) for q in range(P) for a in A_contain(h))
+ quicksum(y[p, k, h] + z[p, k, h] for p in range(P)) <= 2 for k in range(K) for h
in range(H)) in range(H))
mdl.addConstrs( # mdl.addConstrs(
quicksum((y[p, k, h] + z[p, k, h]) for p in range(P)) <= 1 for k in range(K) for h in # quicksum((y[p, k, h] + z[p, k, h]) for p in range(P)) <= 2 for k in range(K) for h in
range(H)) # range(H))
# task continuity (for the same point the entering head and the leaving head should be same) # task continuity (for the same point the entering head and the leaving head should be same)
mdl.addConstrs(quicksum(w[p, q, k, a] for p in range(P) for a in A_to(h)) + y[q, k, h] == quicksum( mdl.addConstrs(quicksum(w[p, q, k, a] for p in range(P) for a in A_to(h)) + y[q, k, h] == quicksum(
@ -235,11 +246,11 @@ def place_route_model(component_data, pcb_data, component_result, feeder_slot_re
range(P)) range(P))
mdl.addConstrs( mdl.addConstrs(
y[p, k, h] <= quicksum(w[p, q, k, a] for q in range(P) for a in A_from(h)) for h in range(H) for p in y[p, k, h] <= quicksum(w[p, q, k, a] for q in range(P) for a in A_from(h)) + z[p, k, h] for h in range(H) for p
range(P) for k in range(K)) in range(P) for k in range(K))
mdl.addConstrs( mdl.addConstrs(
z[p, k, h] <= quicksum(w[q, p, k, a] for q in range(P) for a in A_to(h)) for h in range(H) for p in z[p, k, h] <= quicksum(w[q, p, k, a] for q in range(P) for a in A_to(h)) + y[p, k, h] for h in range(H) for p in
range(P) for k in range(K)) range(P) for k in range(K))
# one arrival point per cycle # one arrival point per cycle
@ -309,8 +320,7 @@ def place_route_model(component_data, pcb_data, component_result, feeder_slot_re
plt.show() plt.show()
# convert model result into standard form # convert model result into standard form
placement_result, head_sequence = [[-1 for _ in range(H)] for _ in range(K)], [[] for _ in placement_result, head_sequence = [[-1 for _ in range(H)] for _ in range(K)], [[] for _ in range(K)]
range(K)]
for k in range(K): for k in range(K):
arc_list = [] arc_list = []
for p in range(P): for p in range(P):
@ -327,6 +337,14 @@ def place_route_model(component_data, pcb_data, component_result, feeder_slot_re
for h in range(H): for h in range(H):
if abs(y[p, k, h].x) > 1e-6: if abs(y[p, k, h].x) > 1e-6:
head = h head = h
if placement_result[k][h] == -1:
placement_result[k][h] = p
assert placement_result[k][h] == p
if abs(z[p, k, h].x) > 1e-6:
if placement_result[k][h] == -1:
placement_result[k][h] = p
assert placement_result[k][h] == p
while idx < len(arc_list): while idx < len(arc_list):
for i, arc in enumerate(arc_list): for i, arc in enumerate(arc_list):
@ -344,7 +362,7 @@ def place_route_model(component_data, pcb_data, component_result, feeder_slot_re
def optimizer_mathmodel(component_data, pcb_data, hinter=True): def optimizer_mathmodel(component_data, pcb_data, hinter=True):
component_result, cycle_result, feeder_slot_result = head_task_model(component_data, pcb_data, hinter) component_result, cycle_result, feeder_slot_result = head_task_model(component_data, pcb_data, hinter)
# placement_result, head_sequence = place_route_model(component_data, pcb_data, component_result, feeder_slot_result) placement_result, head_sequence = place_route_model(component_data, pcb_data, component_result, feeder_slot_result)
placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result, # placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
cycle_result) # cycle_result)
return component_result, cycle_result, feeder_slot_result, placement_result, head_sequence return component_result, cycle_result, feeder_slot_result, placement_result, head_sequence

View File

@ -7,8 +7,10 @@ def list_range(start, end=None):
@timer_wrapper @timer_wrapper
def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, partition=True, initial=False, hinter=True): def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, partition=True, initial=False, hinter=True):
# data preparation: convert data to index # data preparation: convert data to index
component_list, nozzle_list = defaultdict(int), defaultdict(int) component_list, nozzle_list = defaultdict(int), defaultdict(int)
component_feeder = defaultdict(int)
cpidx_2_part, nzidx_2_nozzle, cpidx_2_nzidx = {}, {}, {} cpidx_2_part, nzidx_2_nozzle, cpidx_2_nzidx = {}, {}, {}
arg_slot_rng = None if len(feeder_data) == 0 else [feeder_data.iloc[0].slot, feeder_data.iloc[-1].slot] arg_slot_rng = None if len(feeder_data) == 0 else [feeder_data.iloc[0].slot, feeder_data.iloc[-1].slot]
for idx, data in component_data.iterrows(): for idx, data in component_data.iterrows():
@ -21,6 +23,7 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
nzidx_2_nozzle[nz_idx] = nozzle nzidx_2_nozzle[nz_idx] = nozzle
component_list[part] = 0 component_list[part] = 0
component_feeder[part] = data.fdn
cpidx_2_nzidx[idx] = nz_idx cpidx_2_nzidx[idx] = nz_idx
for _, data in pcb_data.iterrows(): for _, data in pcb_data.iterrows():
@ -40,13 +43,7 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
assert idx != -1 assert idx != -1
part_feederbase[idx] = data.slot # part index - slot part_feederbase[idx] = data.slot # part index - slot
if not reduction: ratio = 1 if reduction else 2
ratio = 2 # 直接导入飞达数据时,采用正常吸杆间隔
else:
if len(component_list) <= 1.5 * max_head_index:
ratio = 1
else:
ratio = 2
I, J = len(cpidx_2_part.keys()), len(nzidx_2_nozzle.keys()) I, J = len(cpidx_2_part.keys()), len(nzidx_2_nozzle.keys())
# === determine the hyper-parameter of L === # === determine the hyper-parameter of L ===
# first phase: calculate the number of heads for each type of nozzle # first phase: calculate the number of heads for each type of nozzle
@ -80,13 +77,12 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
pre_objbst, pre_changetime = None, None pre_objbst, pre_changetime = None, None
def terminate_condition(mdl, where): def terminate_condition(mdl, where):
if where == GRB.Callback.MIP: if where == GRB.Callback.MIP:
objbst = mdl.cbGet(GRB.Callback.MIP_OBJBST) objbst, objbnd = mdl.cbGet(GRB.Callback.MIP_OBJBST), mdl.cbGet(GRB.Callback.MIP_OBJBND)
changetime = mdl.cbGet(GRB.Callback.RUNTIME) changetime = mdl.cbGet(GRB.Callback.RUNTIME)
nonlocal pre_objbst, pre_changetime nonlocal pre_objbst, pre_changetime
# condition: value change # condition: value change
if pre_objbst and abs(pre_objbst - objbst) < 1e-3: if pre_objbst and abs(pre_objbst - objbst) < 1e-3:
if pre_changetime and changetime - pre_changetime > 45: if pre_changetime and changetime - pre_changetime > 100 * (1 - objbnd / objbst):
# pass
mdl.terminate() mdl.terminate()
else: else:
pre_changetime = changetime pre_changetime = changetime
@ -150,10 +146,8 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
break break
level += 1 level += 1
weight_cycle, weight_nz_change, weight_pick = 2, 3, 2
L = len(cycle_assignment) if partition else len(pcb_data) L = len(cycle_assignment) if partition else len(pcb_data)
S = ratio * I if len(feeder_data) == 0 else arg_slot_rng[-1] - arg_slot_rng[0] + 1 # the available feeder num S = ratio * sum(component_feeder.values()) * 2 if len(feeder_data) == 0 else arg_slot_rng[-1] - arg_slot_rng[0] + 1 # the available feeder num
M = len(pcb_data) # a sufficiently large number (number of placement points) M = len(pcb_data) # a sufficiently large number (number of placement points)
HC = [[0 for _ in range(J)] for _ in range(I)] HC = [[0 for _ in range(J)] for _ in range(I)]
for i in range(I): for i in range(I):
@ -247,8 +241,8 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
v[part_feederbase[i], h, idx].Start = 1 v[part_feederbase[i], h, idx].Start = 1
# === Objective === # === Objective ===
mdl.setObjective(weight_cycle * quicksum(WL[l] for l in range(L)) + weight_nz_change * quicksum( mdl.setObjective(Fit_cy * quicksum(WL[l] for l in range(L)) + 2 * Fit_nz * quicksum(
NC[h] for h in range(max_head_index)) + weight_pick * quicksum( NC[h] for h in range(max_head_index)) + Fit_pu * quicksum(
PU[s, l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L))) PU[s, l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L)))
# === Constraint === # === Constraint ===
@ -256,13 +250,13 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
mdl.addConstrs(WL[l] <= 1 for l in range(L)) mdl.addConstrs(WL[l] <= 1 for l in range(L))
# work completion # work completion
# mdl.addConstrs(c[i, h, l] == WL[l] * y[i, h, l] for i in range(I) for h in range(max_head_index) for l in range(L)) mdl.addConstrs(c[i, h, l] == WL[l] * y[i, h, l] for i in range(I) for h in range(max_head_index) for l in range(L))
mdl.addConstrs( # mdl.addConstrs(
c[i, h, l] <= max_cycle * y[i, h, l] for i in range(I) for h in range(max_head_index) for l in range(L)) # c[i, h, l] <= max_cycle * y[i, h, l] for i in range(I) for h in range(max_head_index) for l in range(L))
mdl.addConstrs(c[i, h, l] <= WL[l] for i in range(I) for h in range(max_head_index) for l in range(L)) # mdl.addConstrs(c[i, h, l] <= WL[l] for i in range(I) for h in range(max_head_index) for l in range(L))
mdl.addConstrs( # mdl.addConstrs(
c[i, h, l] >= WL[l] - max_cycle * (1 - y[i, h, l]) for i in range(I) for h in range(max_head_index) for l in # c[i, h, l] >= WL[l] - max_cycle * (1 - y[i, h, l]) for i in range(I) for h in range(max_head_index) for l in
range(L)) # range(L))
mdl.addConstrs( mdl.addConstrs(
quicksum(c[i, h, l] for h in range(max_head_index) for l in range(L)) == component_list[cpidx_2_part[i]] for i quicksum(c[i, h, l] for h in range(max_head_index) for l in range(L)) == component_list[cpidx_2_part[i]] for i
@ -278,12 +272,12 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
mdl.addConstr(quicksum(v[s + h * ratio, h, l] for h in rng) <= max_head_index * p[s, l]) mdl.addConstr(quicksum(v[s + h * ratio, h, l] for h in rng) <= max_head_index * p[s, l])
mdl.addConstr(quicksum(v[s + h * ratio, h, l] for h in rng) >= p[s, l]) mdl.addConstr(quicksum(v[s + h * ratio, h, l] for h in rng) >= p[s, l])
# mdl.addConstrs(PU[s, l] == p[s, l] * WL[l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L)) mdl.addConstrs(PU[s, l] == p[s, l] * WL[l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L))
mdl.addConstrs(PU[s, l] <= max_cycle * p[s, l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L)) # mdl.addConstrs(PU[s, l] <= max_cycle * p[s, l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L))
mdl.addConstrs(PU[s, l] <= WL[l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L)) # mdl.addConstrs(PU[s, l] <= WL[l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L))
mdl.addConstrs( # mdl.addConstrs(
PU[s, l] >= WL[l] - max_cycle * (1 - p[s, l]) for s in range(-(max_head_index - 1) * ratio, S) for l in # PU[s, l] >= WL[l] - max_cycle * (1 - p[s, l]) for s in range(-(max_head_index - 1) * ratio, S) for l in
range(L)) # range(L))
# nozzle change # nozzle change
mdl.addConstrs( mdl.addConstrs(
@ -307,7 +301,7 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
for l in range(L)) for l in range(L))
# available number of feeder # available number of feeder
mdl.addConstrs(quicksum(f[s, i] for s in range(S)) <= 1 for i in range(I)) mdl.addConstrs(quicksum(f[s, i] for s in range(S)) <= component_feeder[cpidx_2_part[i]] for i in range(I))
# available number of nozzle # available number of nozzle
mdl.addConstrs(quicksum(z[j, h, l] for h in range(max_head_index)) <= max_head_index for j in range(J) for l in range(L)) mdl.addConstrs(quicksum(z[j, h, l] for h in range(max_head_index)) <= max_head_index for j in range(J) for l in range(L))
@ -320,19 +314,19 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
# others # others
mdl.addConstrs(quicksum(z[j, h, l] for j in range(J)) <= 1 for h in range(max_head_index) for l in range(L)) mdl.addConstrs(quicksum(z[j, h, l] for j in range(J)) <= 1 for h in range(max_head_index) for l in range(L))
# mdl.addConstrs(
# quicksum(x[i, s, h, l] for h in range(max_head_index) for l in range(L)) >= f[s, i] for i in range(I)
# for s in range(S))
# mdl.addConstrs(
# quicksum(x[i, s, h, l] for h in range(max_head_index) for l in range(L)) <= M * f[s, i] for i in
# range(I) for s in range(S))
mdl.addConstrs( mdl.addConstrs(
f[s, i] >= x[i, s, h, l] for s in range(S) for i in range(I) for h in range(max_head_index) for l in range(L)) quicksum(x[i, s, h, l] for h in range(max_head_index) for l in range(L)) >= f[s, i] for i in range(I)
for s in range(S))
mdl.addConstrs( mdl.addConstrs(
quicksum(x[i, s, h, l] for h in range(max_head_index) for l in range(L)) >= f[s, i] for s in quicksum(x[i, s, h, l] for h in range(max_head_index) for l in range(L)) <= M * f[s, i] for i in
range(S) for i in range(I)) range(I) for s in range(S))
# mdl.addConstrs(
# f[s, i] >= x[i, s, h, l] for s in range(S) for i in range(I) for h in range(max_head_index) for l in range(L))
#
# mdl.addConstrs(
# quicksum(x[i, s, h, l] for h in range(max_head_index) for l in range(L)) >= f[s, i] for s in
# range(S) for i in range(I))
# the constraints to speed up the search process # the constraints to speed up the search process
mdl.addConstrs( mdl.addConstrs(
@ -340,13 +334,13 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
in range(L)) in range(L))
if reduction: if reduction:
mdl.addConstrs(WL[l] >= WL[l + 1] for l in range(L - 1))
# mdl.addConstr(quicksum(WL[l] for l in range(L)) <= sum(cycle_assignment))
mdl.addConstr(quicksum(WL[l] for l in range(L)) >= math.ceil(len(pcb_data) / max_head_index))
# mdl.addConstrs(WL[l] >= WL[l + 1] for l in range(L - 1)) # mdl.addConstrs(WL[l] >= WL[l + 1] for l in range(L - 1))
# mdl.addConstrs(quicksum(z[j, h, l] for j in range(J) for h in range(max_head_index)) >= quicksum( mdl.addConstr(quicksum(WL[l] for l in range(L)) <= sum(cycle_assignment))
# z[j, h, l + 1] for j in range(J) for h in range(max_head_index)) for l in range(L - 1)) mdl.addConstr(quicksum(WL[l] for l in range(L)) >= math.ceil(len(pcb_data) / max_head_index))
# mdl.addConstrs(quicksum(z[j, h, l] for j in range(J) for h in range(max_head_index)) >= quicksum(
z[j, h, l + 1] for j in range(J) for h in range(max_head_index)) for l in range(L - 1))
mdl.addConstrs(y[i, h, l] <= WL[l] for i in range(I) for h in range(max_head_index) for l in range(L)) mdl.addConstrs(y[i, h, l] <= WL[l] for i in range(I) for h in range(max_head_index) for l in range(L))
mdl.addConstrs(v[s, h, l] <= WL[l] for s in range(S) for h in range(max_head_index) for l in range(L)) mdl.addConstrs(v[s, h, l] <= WL[l] for s in range(S) for h in range(max_head_index) for l in range(L))
@ -368,6 +362,7 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
print('num of constrs: ', str(len(mdl.getConstrs())), ', num of vars: ', str(len(mdl.getVars()))) print('num of constrs: ', str(len(mdl.getConstrs())), ', num of vars: ', str(len(mdl.getVars())))
mdl.optimize(terminate_condition) mdl.optimize(terminate_condition)
# mdl.optimize()
# === result generation === # === result generation ===
nozzle_assign, component_assign = [], [] nozzle_assign, component_assign = [], []
@ -505,348 +500,5 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
print('component assignment: ', component_assign) print('component assignment: ', component_assign)
print('feeder assignment: ', feeder_assign) print('feeder assignment: ', feeder_assign)
print('cycle assignment: ', cycle_assign) print('cycle assignment: ', cycle_assign)
return component_assign, feeder_assign, cycle_assign return component_assign, feeder_assign, cycle_assign
def scan_based_placement_route_generation(component_data, pcb_data, component_assign, cycle_assign):
placement_result, head_sequence_result = [], []
mount_point_pos, mount_point_index, mount_point_angle, mount_point_part = [], [], [], []
for i, data in pcb_data.iterrows():
component_index = component_data[component_data.part == data.part].index.tolist()[0]
# 记录贴装点序号索引和对应的位置坐标
mount_point_index.append(i)
mount_point_pos.append([data.x + stopper_pos[0], data.y + stopper_pos[1]])
mount_point_angle.append(data.r)
mount_point_part.append(component_index)
lBoundary, rBoundary = min(mount_point_pos, key=lambda x: x[0])[0], max(mount_point_pos, key=lambda x: x[0])[0]
search_step = max((rBoundary - lBoundary) / max_head_index / 2, 0)
ref_pos_y = min(mount_point_pos, key=lambda x: x[1])[1]
for cycle_index, component_cycle in enumerate(component_assign):
for _ in range(cycle_assign[cycle_index]):
min_dist = None
tmp_assigned_placement, tmp_assigned_head_seq = [], []
tmp_mount_point_pos, tmp_mount_point_index = [], []
for search_dir in range(3): # 不同的搜索方向,贴装头和起始点的选取方法各不相同
if search_dir == 0:
# 从左向右搜索
searchPoints = np.arange(lBoundary, (lBoundary + rBoundary) / 2, search_step)
head_range = list(range(max_head_index))
elif search_dir == 1:
# 从右向左搜索
searchPoints = np.arange(rBoundary + 1e-3, (lBoundary + rBoundary) / 2, -search_step)
head_range = list(range(max_head_index - 1, -1, -1))
else:
# 从中间向两边搜索
searchPoints = np.arange(lBoundary, rBoundary, search_step / 2)
head_range, head_index = [], (max_head_index - 1) // 2
while head_index >= 0:
if 2 * head_index != max_head_index - 1:
head_range.append(max_head_index - 1 - head_index)
head_range.append(head_index)
head_index -= 1
for startPoint in searchPoints:
mount_point_pos_cpy, mount_point_index_cpy = copy.deepcopy(mount_point_pos), copy.deepcopy(
mount_point_index)
mount_point_angle_cpy = copy.deepcopy(mount_point_angle)
assigned_placement = [-1] * max_head_index
assigned_mount_point = [[0, 0]] * max_head_index
assigned_mount_angle = [0] * max_head_index
head_counter, point_index = 0, -1
for head_index in head_range:
if head_counter == 0:
component_index = component_assign[cycle_index][head_index]
if component_index == -1:
continue
min_horizontal_distance = None
for index, mount_index in enumerate(mount_point_index_cpy):
if mount_point_part[mount_index] != component_index:
continue
horizontal_distance = abs(mount_point_pos_cpy[index][0] - startPoint) + 1e-3 * abs(
mount_point_pos_cpy[index][1] - ref_pos_y)
if min_horizontal_distance is None or horizontal_distance < min_horizontal_distance:
min_horizontal_distance = horizontal_distance
point_index = index
else:
point_index = -1
min_cheby_distance = None
next_comp_index = component_assign[cycle_index][head_index]
if assigned_placement[head_index] != -1 or next_comp_index == -1:
continue
for index, mount_index in enumerate(mount_point_index_cpy):
if mount_point_part[mount_index] != next_comp_index:
continue
point_pos = [[mount_point_pos_cpy[index][0] - head_index * head_interval,
mount_point_pos_cpy[index][1]]]
cheby_distance, euler_distance = 0, 0
for next_head in range(max_head_index):
if assigned_placement[next_head] == -1:
continue
point_pos.append(assigned_mount_point[next_head].copy())
point_pos[-1][0] -= next_head * head_interval
point_pos = sorted(point_pos, key=lambda x: x[0])
for mount_seq in range(len(point_pos) - 1):
cheby_distance += max(abs(point_pos[mount_seq][0] - point_pos[mount_seq + 1][0]),
abs(point_pos[mount_seq][1] - point_pos[mount_seq + 1][1]))
euler_distance += math.sqrt(
(point_pos[mount_seq][0] - point_pos[mount_seq + 1][0]) ** 2 + (
point_pos[mount_seq][1] - point_pos[mount_seq + 1][1]) ** 2)
cheby_distance += 0.01 * euler_distance
if min_cheby_distance is None or cheby_distance < min_cheby_distance:
min_cheby_distance, min_euler_distance = cheby_distance, euler_distance
point_index = index
if point_index == -1:
continue
head_counter += 1
assigned_placement[head_index] = mount_point_index_cpy[point_index]
assigned_mount_point[head_index] = mount_point_pos_cpy[point_index].copy()
assigned_mount_angle[head_index] = mount_point_angle_cpy[point_index]
mount_point_index_cpy.pop(point_index)
mount_point_pos_cpy.pop(point_index)
mount_point_angle_cpy.pop(point_index)
dist, head_seq = dynamic_programming_cycle_path(assigned_placement, assigned_mount_point,
assigned_mount_angle)
if min_dist is None or dist < min_dist:
tmp_mount_point_pos, tmp_mount_point_index = mount_point_pos_cpy, mount_point_index_cpy
tmp_assigned_placement, tmp_assigned_head_seq = assigned_placement, head_seq
min_dist = dist
mount_point_pos, mount_point_index = tmp_mount_point_pos, tmp_mount_point_index
placement_result.append(tmp_assigned_placement)
head_sequence_result.append(tmp_assigned_head_seq)
return placement_result, head_sequence_result
# return placement_route_relink_heuristic(component_data, pcb_data, placement_result, head_sequence_result)
def placement_route_relink_heuristic(component_data, pcb_data, placement_result, head_sequence_result, hinter=True):
mount_point_pos, mount_point_angle, mount_point_index, mount_point_part = [], [], [], []
for i, data in pcb_data.iterrows():
component_index = component_data[component_data.part == data.part].index.tolist()[0]
# 记录贴装点序号索引和对应的位置坐标
mount_point_index.append(i)
mount_point_pos.append([data.x + stopper_pos[0], data.y + stopper_pos[1]])
mount_point_angle.append(data.r)
mount_point_part.append(component_index)
cycle_length, cycle_average_pos = [], []
for cycle, placement in enumerate(placement_result):
prev_pos, prev_angle = None, None
cycle_pos_list = []
cycle_length.append(0)
for idx, head in enumerate(head_sequence_result[cycle]):
point_index = placement[head]
if point_index == -1:
continue
pos = [mount_point_pos[point_index][0] - head * head_interval, mount_point_pos[point_index][1]]
angle = mount_point_angle[point_index]
cycle_pos_list.append(pos)
if prev_pos is not None:
if head_sequence_result[cycle][idx - 1] // 2 == head_sequence_result[cycle][idx] // 2: # 同轴
rotary_angle = prev_angle - angle
else:
rotary_angle = 0
cycle_length[-1] += max(axis_moving_time(prev_pos[0] - pos[0], 0),
axis_moving_time(prev_pos[1] - pos[1], 1), head_rotary_time(rotary_angle))
prev_pos, prev_angle = pos, angle
cycle_average_pos.append([sum(map(lambda pos: pos[0], cycle_pos_list)) / len(cycle_pos_list),
sum(map(lambda pos: pos[1], cycle_pos_list)) / len(cycle_pos_list)])
best_placement_result, best_head_sequence_result = copy.deepcopy(placement_result), copy.deepcopy(
head_sequence_result)
best_cycle_length, best_cycle_average_pos = copy.deepcopy(cycle_length), copy.deepcopy(cycle_average_pos)
n_runningtime, n_iteration = 10, 0
start_time = time.time()
with tqdm(total=n_runningtime, leave=False) as pbar:
pbar.set_description('swap heuristic process')
prev_time = start_time
while True:
n_iteration += 1
placement_result, head_sequence_result = copy.deepcopy(best_placement_result), copy.deepcopy(
best_head_sequence_result)
cycle_length = best_cycle_length.copy()
cycle_average_pos = copy.deepcopy(best_cycle_average_pos)
cycle_index = roulette_wheel_selection(cycle_length) # 根据周期加权移动距离随机选择周期
point_dist = [] # 周期内各贴装点距离中心位置的切氏距离
for head in head_sequence_result[cycle_index]:
point_index = placement_result[cycle_index][head]
_delta_x = abs(mount_point_pos[point_index][0] - head * head_interval - cycle_average_pos[cycle_index][0])
_delta_y = abs(mount_point_pos[point_index][1] - cycle_average_pos[cycle_index][1])
point_dist.append(max(_delta_x, _delta_y))
# 随机选择一个异常点
head_index = head_sequence_result[cycle_index][roulette_wheel_selection(point_dist)]
point_index = placement_result[cycle_index][head_index]
# 找距离该异常点最近的周期
min_dist = None
chg_cycle_index = -1
for idx in range(len(cycle_average_pos)):
if idx == cycle_index:
continue
dist_ = 0
component_type_check = False
for head in head_sequence_result[idx]:
dist_ += max(abs(mount_point_pos[placement_result[idx][head]][0] - mount_point_pos[point_index][0]),
abs(mount_point_pos[placement_result[idx][head]][1] - mount_point_pos[point_index][1]))
if mount_point_part[placement_result[idx][head]] == mount_point_part[point_index]:
component_type_check = True
if (min_dist is None or dist_ < min_dist) and component_type_check:
min_dist = dist_
chg_cycle_index = idx
assert chg_cycle_index != -1
chg_head, min_chg_dist = None, None
chg_cycle_point = []
for head in head_sequence_result[chg_cycle_index]:
index = placement_result[chg_cycle_index][head]
chg_cycle_point.append([mount_point_pos[index][0] - head * head_interval, mount_point_pos[index][1]])
for idx, head in enumerate(head_sequence_result[chg_cycle_index]):
chg_cycle_point_cpy = copy.deepcopy(chg_cycle_point)
index = placement_result[chg_cycle_index][head]
if mount_point_part[index] != mount_point_part[point_index]:
continue
chg_cycle_point_cpy[idx][0] = (mount_point_pos[index][0]) - head * head_interval
chg_dist = 0
aver_chg_pos = [sum(map(lambda x: x[0], chg_cycle_point_cpy)) / len(chg_cycle_point_cpy),
sum(map(lambda x: x[1], chg_cycle_point_cpy)) / len(chg_cycle_point_cpy)]
for pos in chg_cycle_point_cpy:
chg_dist += max(abs(aver_chg_pos[0] - pos[0]), abs(aver_chg_pos[1] - pos[1]))
# 更换后各点距离中心更近
if min_chg_dist is None or chg_dist < min_chg_dist:
chg_head = head
min_chg_dist = chg_dist
assert chg_head is not None
# === 第一轮变更周期chg_cycle_index的贴装点重排 ===
chg_placement_res = placement_result[chg_cycle_index].copy()
chg_placement_res[chg_head] = point_index
cycle_point_list = defaultdict(list)
for head, point in enumerate(chg_placement_res):
if point == -1:
continue
cycle_point_list[mount_point_part[point]].append(point)
for key, point_list in cycle_point_list.items():
cycle_point_list[key] = sorted(point_list, key=lambda p: mount_point_pos[p][0])
chg_placement_res, chg_point_assign_res = [], [[0, 0]] * max_head_index
chg_angle_res = [0] * max_head_index
for head, point_index in enumerate(placement_result[chg_cycle_index]):
if point_index == -1:
chg_placement_res.append(-1)
else:
part = mount_point_part[point_index]
chg_placement_res.append(cycle_point_list[part][0])
chg_point_assign_res[head] = mount_point_pos[cycle_point_list[part][0]].copy()
chg_angle_res[head] = mount_point_angle[cycle_point_list[part][0]]
cycle_point_list[part].pop(0)
chg_place_moving, chg_head_res = dynamic_programming_cycle_path(chg_placement_res, chg_point_assign_res, chg_angle_res)
# === 第二轮原始周期cycle_index的贴装点重排 ===
placement_res = placement_result[cycle_index].copy()
placement_res[head_index] = placement_result[chg_cycle_index][chg_head]
for point in placement_res:
if point == -1:
continue
cycle_point_list[mount_point_part[point]].append(point)
for key, point_list in cycle_point_list.items():
cycle_point_list[key] = sorted(point_list, key=lambda p: mount_point_pos[p][0])
placement_res, point_assign_res = [], [[0, 0]] * max_head_index
angle_assign_res = [0] * max_head_index
for head, point_index in enumerate(placement_result[cycle_index]):
if point_index == -1:
placement_res.append(-1)
else:
part = mount_point_part[point_index]
placement_res.append(cycle_point_list[part][0])
point_assign_res[head] = mount_point_pos[cycle_point_list[part][0]].copy()
angle_assign_res[head] = mount_point_angle[cycle_point_list[part][0]]
cycle_point_list[part].pop(0)
place_moving, place_head_res = dynamic_programming_cycle_path(placement_res, point_assign_res, angle_assign_res)
# 更新贴装顺序分配结果
placement_result[cycle_index], head_sequence_result[cycle_index] = placement_res, place_head_res
placement_result[chg_cycle_index], head_sequence_result[chg_cycle_index] = chg_placement_res, chg_head_res
# 更新移动路径
cycle_length[cycle_index], cycle_length[chg_cycle_index] = place_moving, chg_place_moving
# 更新平均坐标和最大偏离点索引
point_list, point_index_list = [], []
for head in head_sequence_result[cycle_index]:
point_index_list.append(placement_result[cycle_index][head])
point_pos = mount_point_pos[point_index_list[-1]].copy()
point_pos[0] -= head * head_interval
point_list.append(point_pos)
cycle_average_pos[cycle_index] = [sum(map(lambda x: x[0], point_list)) / len(point_list),
sum(map(lambda x: x[1], point_list)) / len(point_list)]
point_list, point_index_list = [], []
for head in head_sequence_result[chg_cycle_index]:
point_index_list.append(placement_result[chg_cycle_index][head])
point_pos = mount_point_pos[point_index_list[-1]].copy()
point_pos[0] -= head * head_interval
point_list.append(point_pos)
cycle_average_pos[chg_cycle_index] = [sum(map(lambda x: x[0], point_list)) / len(point_list),
sum(map(lambda x: x[1], point_list)) / len(point_list)]
if sum(cycle_length) < sum(best_cycle_length):
best_cycle_length = cycle_length.copy()
best_cycle_average_pos = copy.deepcopy(cycle_average_pos)
best_placement_result, best_head_sequence_result = copy.deepcopy(placement_result), copy.deepcopy(
head_sequence_result)
cur_time = time.time()
if cur_time - start_time > n_runningtime:
break
pbar.update(cur_time - prev_time)
prev_time = cur_time
# print("number of iteration: ", n_iteration)
return best_placement_result, best_head_sequence_result

View File

@ -0,0 +1,706 @@
from base_optimizer.optimizer_common import *
def dynamic_programming_cycle_path(pcb_data, cycle_placement, assigned_feeder):
head_sequence = []
num_pos = sum([placement != -1 for placement in cycle_placement]) + 1
pos, head_set = [], []
feeder_set = set()
for head, feeder in enumerate(assigned_feeder):
if feeder == -1:
continue
head_set.append(head)
placement = cycle_placement[head]
if feeder != -1 and placement == -1:
print(assigned_feeder)
print(cycle_placement)
pos.append([pcb_data.iloc[placement]['x'] - head * head_interval + stopper_pos[0],
pcb_data.iloc[placement]['y'] + stopper_pos[1], pcb_data.iloc[placement]['r'], head])
feeder_set.add(feeder - head * interval_ratio)
pos.insert(0, [slotf1_pos[0] + ((min(list(feeder_set)) + max(list(feeder_set))) / 2 - 1) * slot_interval,
slotf1_pos[1], None, 0])
def get_distance(pos_1, pos_2):
# 拾取起始与终止位置 或 非同轴
if pos_1[2] is None or pos_2[2] is None or pos_1[3] + (1 if pos_1[3] % 2 == 0 else -1) != pos_2[3]:
return max(axis_moving_time(pos_1[0] - pos_2[0], 0), axis_moving_time(pos_1[1] - pos_2[1], 1))
else:
return max(axis_moving_time(pos_1[0] - pos_2[0], 0), axis_moving_time(pos_1[1] - pos_2[1], 1),
head_rotary_time(pos_1[2] - pos_2[2]))
# 各节点之间的距离
dist = [[get_distance(pos_1, pos_2) for pos_2 in pos] for pos_1 in pos]
min_dist = [[np.inf for _ in range(num_pos)] for s in range(1 << num_pos)]
min_path = [[[] for _ in range(num_pos)] for s in range(1 << num_pos)]
# 状压dp搜索
for s in range(1, 1 << num_pos, 2):
# 考虑节点集合s必须包括节点0
if not (s & 1):
continue
for j in range(1, num_pos):
# 终点j需在当前考虑节点集合s内
if not (s & (1 << j)):
continue
if s == int((1 << j) | 1):
# 若考虑节点集合s仅含节点0和节点jdp边界赋予初值
# print('j:', j)
min_path[s][j] = [j]
min_dist[s][j] = dist[0][j]
# 枚举下一个节点i更新
for i in range(1, num_pos):
# 下一个节点i需在考虑节点集合s外
if s & (1 << i):
continue
if min_dist[s][j] + dist[j][i] < min_dist[s | (1 << i)][i]:
min_path[s | (1 << i)][i] = min_path[s][j] + [i]
min_dist[s | (1 << i)][i] = min_dist[s][j] + dist[j][i]
ans_dist = float('inf')
ans_path = []
# 求最终最短哈密顿回路
for i in range(1, num_pos):
if min_dist[(1 << num_pos) - 1][i] + dist[i][0] < ans_dist:
# 更新,回路化
ans_path = min_path[s][i]
ans_dist = min_dist[(1 << num_pos) - 1][i] + dist[i][0]
for parent in ans_path:
head_sequence.append(head_set[parent - 1])
start_head, end_head = head_sequence[0], head_sequence[-1]
if pcb_data.iloc[cycle_placement[start_head]]['x'] - start_head * head_interval > \
pcb_data.iloc[cycle_placement[end_head]]['x'] - end_head * head_interval:
head_sequence = list(reversed(head_sequence))
return ans_dist, head_sequence
@timer_wrapper
def greedy_placement_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
hinter=True):
placement_result, head_sequence_result = [], []
if len(pcb_data) == 0:
return placement_result, head_sequence_result
mount_point_index = [[] for _ in range(len(component_data))]
mount_point_pos = [[] for _ in range(len(component_data))]
for i in range(len(pcb_data)):
part = pcb_data.iloc[i]['part']
component_index = component_data[component_data['part'] == part].index.tolist()[0]
# 记录贴装点序号索引和对应的位置坐标
mount_point_index[component_index].append(i)
mount_point_pos[component_index].append([pcb_data.iloc[i]['x'], pcb_data.iloc[i]['y']])
search_dir = 1 # 0自左向右搜索 1自右向左搜索
for cycle_set in range(len(component_result)):
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
for cycle in range(floor_cycle, ceil_cycle):
# search_dir = 1 - search_dir
assigned_placement = [-1] * max_head_index
max_pos = [max(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in
range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0]
min_pos = [min(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in
range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0]
point2head_range = min(math.floor((max_pos - min_pos) / head_interval) + 1, max_head_index)
# 最近邻确定
way_point = None
head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index)
for head_counter, head in enumerate(head_range):
if component_result[cycle_set][head] == -1:
continue
component_index = component_result[cycle_set][head]
if way_point is None or head_counter % point2head_range == 0:
index = 0
if way_point is None:
if search_dir:
index = np.argmax(mount_point_pos[component_index], axis=0)[0]
else:
index = np.argmin(mount_point_pos[component_index], axis=0)[0]
else:
for next_head in head_range:
component_index = component_result[cycle_set][next_head]
if assigned_placement[next_head] == -1 and component_index != -1:
num_points = len(mount_point_pos[component_index])
index = np.argmin(
[abs(mount_point_pos[component_index][i][0] - way_point[0]) * .1 + abs(
mount_point_pos[component_index][i][1] - way_point[1]) for i in
range(num_points)])
head = next_head
break
# index = np.argmax(mount_point_pos[component_index], axis=0)[0]
assigned_placement[head] = mount_point_index[component_index][index]
# 记录路标点
way_point = mount_point_pos[component_index][index]
way_point[0] += (max_head_index - head - 1) * head_interval if search_dir else -head * head_interval
mount_point_index[component_index].pop(index)
mount_point_pos[component_index].pop(index)
else:
head_index, point_index = -1, -1
min_cheby_distance, min_euler_distance = float('inf'), float('inf')
for next_head in range(max_head_index):
if assigned_placement[next_head] != -1 or component_result[cycle_set][next_head] == -1:
continue
next_comp_index = component_result[cycle_set][next_head]
for counter in range(len(mount_point_pos[next_comp_index])):
if search_dir:
delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0]
+ (max_head_index - next_head - 1) * head_interval)
else:
delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0]
- next_head * head_interval)
delta_y = abs(mount_point_pos[next_comp_index][counter][1] - way_point[1])
euler_distance = pow(axis_moving_time(delta_x, 0), 2) + pow(axis_moving_time(delta_y, 1), 2)
cheby_distance = max(axis_moving_time(delta_x, 0),
axis_moving_time(delta_y, 1)) + 5e-2 * euler_distance
if cheby_distance < min_cheby_distance or (abs(cheby_distance - min_cheby_distance) < 1e-9
and euler_distance < min_euler_distance):
# if euler_distance < min_euler_distance:
min_cheby_distance, min_euler_distance = cheby_distance, euler_distance
head_index, point_index = next_head, counter
component_index = component_result[cycle_set][head_index]
assert 0 <= head_index < max_head_index
assigned_placement[head_index] = mount_point_index[component_index][point_index]
way_point = mount_point_pos[component_index][point_index]
way_point[0] += (max_head_index - head_index - 1) * head_interval if search_dir \
else -head_index * head_interval
mount_point_index[component_index].pop(point_index)
mount_point_pos[component_index].pop(point_index)
placement_result.append(assigned_placement) # 各个头上贴装的元件类型
head_sequence_result.append(
dynamic_programming_cycle_path(pcb_data, assigned_placement, feeder_slot_result[cycle_set])[1])
return placement_result, head_sequence_result
@timer_wrapper
def beam_search_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result):
beam_width = 10 # 集束宽度
base_points = [float('inf'), float('inf')]
mount_point_index = [[] for _ in range(len(component_data))]
mount_point_pos = [[] for _ in range(len(component_data))]
for idx, data in pcb_data.iterrows():
component_index = component_data[component_data['part'] == data.part].index.tolist()[0]
# 记录贴装点序号索引和对应的位置坐标
mount_point_index[component_index].append(idx)
mount_point_pos[component_index].append([data.x, data.y])
# 记录最左下角坐标
if mount_point_pos[component_index][-1][0] < base_points[0]:
base_points[0] = mount_point_pos[component_index][-1][0]
if mount_point_pos[component_index][-1][1] < base_points[1]:
base_points[1] = mount_point_pos[component_index][-1][1]
beam_placement_sequence, beam_head_sequence = [], []
beam_mount_point_index, beam_mount_point_pos = [], []
for beam_counter in range(beam_width):
beam_mount_point_index.append(copy.deepcopy(mount_point_index))
beam_mount_point_pos.append(copy.deepcopy(mount_point_pos))
beam_placement_sequence.append([])
beam_head_sequence.append([])
beam_distance = [0 for _ in range(beam_width)] # 记录当前集束搜索点的点数
def argpartition(list, kth):
if kth < len(list):
return np.argpartition(list, kth)
else:
index, indexes = 0, []
while len(indexes) < kth:
indexes.append(index)
index += 1
if index >= len(list):
index = 0
return np.array(indexes)
with tqdm(total=100) as pbar:
search_dir = 0
pbar.set_description('beam search route schedule')
for cycle_set in range(len(component_result)):
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
for cycle in range(floor_cycle, ceil_cycle):
search_dir = 1 - search_dir
beam_way_point = None
for beam_counter in range(beam_width):
beam_placement_sequence[beam_counter].append([-1 for _ in range(max_head_index)])
head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index)
for head in head_range:
component_index = component_result[cycle_set][head]
if component_index == -1:
continue
if beam_way_point is None:
# 首个贴装点的选取距离基准点最近的beam_width个点
beam_way_point = [[0, 0]] * beam_width
for beam_counter in range(beam_width):
if search_dir:
index = np.argmax(beam_mount_point_pos[beam_counter][component_index], axis=0)[0]
else:
index = np.argmin(beam_mount_point_pos[beam_counter][component_index], axis=0)[0]
beam_placement_sequence[beam_counter][-1][head] = \
beam_mount_point_index[beam_counter][component_index][index]
beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][index]
beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \
search_dir else -head * head_interval
beam_mount_point_index[beam_counter][component_index].pop(index)
beam_mount_point_pos[beam_counter][component_index].pop(index)
else:
# 后续贴装点
search_beam_distance = []
search_beam_component_index = [0] * (beam_width ** 2)
for beam_counter in range(beam_width ** 2):
search_beam_distance.append(beam_distance[beam_counter // beam_width])
for beam_counter in range(beam_width):
# 对于集束beam_counter + 1最近的beam_width个点
num_points = len(beam_mount_point_pos[beam_counter][component_index])
dist = []
for i in range(num_points):
if search_dir:
delta_x = axis_moving_time(
beam_mount_point_pos[beam_counter][component_index][i][0] -
beam_way_point[beam_counter][0] + (max_head_index - head - 1) * head_interval,
0)
else:
delta_x = axis_moving_time(
beam_mount_point_pos[beam_counter][component_index][i][0] -
beam_way_point[beam_counter][0] - head * head_interval, 0)
delta_y = axis_moving_time(beam_mount_point_pos[beam_counter][component_index][i][1] -
beam_way_point[beam_counter][1], 1)
dist.append(max(delta_x, delta_y))
indexes = argpartition(dist, kth=beam_width)[:beam_width]
# 记录中间信息
for i, index in enumerate(indexes):
search_beam_distance[i + beam_counter * beam_width] += dist[index]
search_beam_component_index[i + beam_counter * beam_width] = index
indexes = np.argsort(search_beam_distance)
beam_mount_point_pos_cpy = copy.deepcopy(beam_mount_point_pos)
beam_mount_point_index_cpy = copy.deepcopy(beam_mount_point_index)
beam_placement_sequence_cpy = copy.deepcopy(beam_placement_sequence)
beam_head_sequence_cpy = copy.deepcopy(beam_head_sequence)
beam_counter = 0
assigned_placement = []
for i, index in enumerate(indexes):
# 拷贝原始集束数据
beam_mount_point_pos[beam_counter] = copy.deepcopy(beam_mount_point_pos_cpy[index // beam_width])
beam_mount_point_index[beam_counter] = copy.deepcopy(beam_mount_point_index_cpy[index // beam_width])
beam_placement_sequence[beam_counter] = copy.deepcopy(beam_placement_sequence_cpy[index // beam_width])
beam_head_sequence[beam_counter] = copy.deepcopy(beam_head_sequence_cpy[index // beam_width])
# 更新各集束最新扫描的的贴装点
component_index = component_result[cycle_set][head]
beam_placement_sequence[beam_counter][-1][head] = \
beam_mount_point_index[beam_counter][component_index][search_beam_component_index[index]]
if beam_placement_sequence[beam_counter][-1] in assigned_placement \
and beam_width - beam_counter < len(indexes) - i:
continue
assigned_placement.append(beam_placement_sequence[beam_counter][-1])
# 更新参考基准点
beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][
search_beam_component_index[index]]
beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \
search_dir else -head * head_interval
# 更新各集束贴装路径长度,移除各集束已分配的贴装点
beam_distance[beam_counter] = search_beam_distance[index]
beam_mount_point_pos[beam_counter][component_index].pop(search_beam_component_index[index])
beam_mount_point_index[beam_counter][component_index].pop(search_beam_component_index[index])
beam_counter += 1
if beam_counter >= beam_width:
break
assert beam_counter >= beam_width
# 更新头贴装顺序
for beam_counter in range(beam_width):
beam_head_sequence[beam_counter].append(
dynamic_programming_cycle_path(pcb_data, beam_placement_sequence[beam_counter][-1],
feeder_slot_result[cycle_set])[1])
pbar.update(1 / sum(cycle_result) * 100)
index = np.argmin(beam_distance)
print('beam distance : ', beam_distance[index])
return beam_placement_sequence[index], beam_head_sequence[index]
def scan_based_placement_route_generation(component_data, pcb_data, component_assign, cycle_assign, feeder_slot_result):
placement_result, head_sequence_result = [], []
mount_point_pos, mount_point_index, mount_point_angle, mount_point_part = [], [], [], []
for i, data in pcb_data.iterrows():
component_index = component_data[component_data.part == data.part].index.tolist()[0]
# 记录贴装点序号索引和对应的位置坐标
mount_point_index.append(i)
mount_point_pos.append([data.x + stopper_pos[0], data.y + stopper_pos[1]])
mount_point_angle.append(data.r)
mount_point_part.append(component_index)
lBoundary, rBoundary = min(mount_point_pos, key=lambda x: x[0])[0], max(mount_point_pos, key=lambda x: x[0])[0]
search_step = max((rBoundary - lBoundary) / max_head_index / 2, 0)
ref_pos_y = min(mount_point_pos, key=lambda x: x[1])[1]
for cycle_index, component_cycle in enumerate(component_assign):
for _ in range(cycle_assign[cycle_index]):
min_dist = None
tmp_assigned_placement, tmp_assigned_head_seq = [], []
tmp_mount_point_pos, tmp_mount_point_index = [], []
for search_dir in range(3): # 不同的搜索方向,贴装头和起始点的选取方法各不相同
if search_dir == 0:
# 从左向右搜索
searchPoints = np.arange(lBoundary, (lBoundary + rBoundary) / 2, search_step)
head_range = list(range(max_head_index))
elif search_dir == 1:
# 从右向左搜索
searchPoints = np.arange(rBoundary + 1e-3, (lBoundary + rBoundary) / 2, -search_step)
head_range = list(range(max_head_index - 1, -1, -1))
else:
# 从中间向两边搜索
searchPoints = np.arange(lBoundary, rBoundary, search_step / 2)
head_range, head_index = [], (max_head_index - 1) // 2
while head_index >= 0:
if 2 * head_index != max_head_index - 1:
head_range.append(max_head_index - 1 - head_index)
head_range.append(head_index)
head_index -= 1
for startPoint in searchPoints:
mount_point_pos_cpy, mount_point_index_cpy = copy.deepcopy(mount_point_pos), copy.deepcopy(
mount_point_index)
mount_point_angle_cpy = copy.deepcopy(mount_point_angle)
assigned_placement = [-1] * max_head_index
assigned_mount_point = [[0, 0]] * max_head_index
assigned_mount_angle = [0] * max_head_index
head_counter, point_index = 0, -1
for head_index in head_range:
if head_counter == 0:
component_index = component_assign[cycle_index][head_index]
if component_index == -1:
continue
min_horizontal_distance = None
for index, mount_index in enumerate(mount_point_index_cpy):
if mount_point_part[mount_index] != component_index:
continue
horizontal_distance = abs(mount_point_pos_cpy[index][0] - startPoint) + 1e-3 * abs(
mount_point_pos_cpy[index][1] - ref_pos_y)
if min_horizontal_distance is None or horizontal_distance < min_horizontal_distance:
min_horizontal_distance = horizontal_distance
point_index = index
else:
point_index = -1
min_cheby_distance = None
next_comp_index = component_assign[cycle_index][head_index]
if assigned_placement[head_index] != -1 or next_comp_index == -1:
continue
for index, mount_index in enumerate(mount_point_index_cpy):
if mount_point_part[mount_index] != next_comp_index:
continue
point_pos = [[mount_point_pos_cpy[index][0] - head_index * head_interval,
mount_point_pos_cpy[index][1]]]
cheby_distance, euler_distance = 0, 0
for next_head in range(max_head_index):
if assigned_placement[next_head] == -1:
continue
point_pos.append(assigned_mount_point[next_head].copy())
point_pos[-1][0] -= next_head * head_interval
point_pos = sorted(point_pos, key=lambda x: x[0])
for mount_seq in range(len(point_pos) - 1):
cheby_distance += max(abs(point_pos[mount_seq][0] - point_pos[mount_seq + 1][0]),
abs(point_pos[mount_seq][1] - point_pos[mount_seq + 1][1]))
euler_distance += math.sqrt(
(point_pos[mount_seq][0] - point_pos[mount_seq + 1][0]) ** 2 + (
point_pos[mount_seq][1] - point_pos[mount_seq + 1][1]) ** 2)
cheby_distance += 0.01 * euler_distance
if min_cheby_distance is None or cheby_distance < min_cheby_distance:
min_cheby_distance, min_euler_distance = cheby_distance, euler_distance
point_index = index
if point_index == -1:
continue
head_counter += 1
assigned_placement[head_index] = mount_point_index_cpy[point_index]
assigned_mount_point[head_index] = mount_point_pos_cpy[point_index].copy()
assigned_mount_angle[head_index] = mount_point_angle_cpy[point_index]
mount_point_index_cpy.pop(point_index)
mount_point_pos_cpy.pop(point_index)
mount_point_angle_cpy.pop(point_index)
dist, head_seq = dynamic_programming_cycle_path(pcb_data, assigned_placement,
feeder_slot_result[cycle_index])
if min_dist is None or dist < min_dist:
tmp_mount_point_pos, tmp_mount_point_index = mount_point_pos_cpy, mount_point_index_cpy
tmp_assigned_placement, tmp_assigned_head_seq = assigned_placement, head_seq
min_dist = dist
mount_point_pos, mount_point_index = tmp_mount_point_pos, tmp_mount_point_index
placement_result.append(tmp_assigned_placement)
head_sequence_result.append(tmp_assigned_head_seq)
return placement_result, head_sequence_result
# return placement_route_relink_heuristic(component_data, pcb_data, placement_result, head_sequence_result,
# feeder_slot_result, cycle_assign)
def placement_route_relink_heuristic(component_data, pcb_data, placement_result, head_sequence_result,
feeder_slot_result, cycle_result, hinter=True):
cycle_group_index = defaultdict(int)
cycle_index = 0
for cycle_group, group in enumerate(cycle_result):
for _ in range(group):
cycle_group_index[cycle_index] = cycle_group
cycle_index += 1
mount_point_pos, mount_point_angle, mount_point_index, mount_point_part = [], [], [], []
for i, data in pcb_data.iterrows():
component_index = component_data[component_data.part == data.part].index.tolist()[0]
# 记录贴装点序号索引和对应的位置坐标
mount_point_index.append(i)
mount_point_pos.append([data.x + stopper_pos[0], data.y + stopper_pos[1]])
mount_point_angle.append(data.r)
mount_point_part.append(component_index)
cycle_length, cycle_average_pos = [], []
for cycle, placement in enumerate(placement_result):
# prev_pos, prev_angle = None, None
cycle_pos_list = []
for idx, head in enumerate(head_sequence_result[cycle]):
if point_index := placement[head] == -1:
continue
cycle_pos_list.append(
[mount_point_pos[point_index][0] - head * head_interval, mount_point_pos[point_index][1]])
cycle_average_pos.append([sum(map(lambda pos: pos[0], cycle_pos_list)) / len(cycle_pos_list),
sum(map(lambda pos: pos[1], cycle_pos_list)) / len(cycle_pos_list)])
cycle_length.append(
dynamic_programming_cycle_path(pcb_data, placement, feeder_slot_result[cycle_group_index[cycle]])[0])
best_placement_result, best_head_sequence_result = copy.deepcopy(placement_result), copy.deepcopy(
head_sequence_result)
best_cycle_length, best_cycle_average_pos = copy.deepcopy(cycle_length), copy.deepcopy(cycle_average_pos)
n_runningtime, n_iteration = 30, 0
start_time = time.time()
with tqdm(total=n_runningtime) as pbar:
pbar.set_description('swap heuristic process')
prev_time = start_time
while True:
n_iteration += 1
placement_result, head_sequence_result = copy.deepcopy(best_placement_result), copy.deepcopy(
best_head_sequence_result)
cycle_length = best_cycle_length.copy()
cycle_average_pos = copy.deepcopy(best_cycle_average_pos)
cycle_index = roulette_wheel_selection(cycle_length) # 根据周期加权移动距离随机选择周期
point_dist = [] # 周期内各贴装点距离中心位置的切氏距离
for head in head_sequence_result[cycle_index]:
point_index = placement_result[cycle_index][head]
_delta_x = abs(mount_point_pos[point_index][0] - head * head_interval - cycle_average_pos[cycle_index][0])
_delta_y = abs(mount_point_pos[point_index][1] - cycle_average_pos[cycle_index][1])
point_dist.append(max(_delta_x, _delta_y))
# 随机选择一个异常点
head_index = head_sequence_result[cycle_index][roulette_wheel_selection(point_dist)]
point_index = placement_result[cycle_index][head_index]
# 找距离该异常点最近的周期
min_dist = None
chg_cycle_index = -1
for idx in range(len(cycle_average_pos)):
if idx == cycle_index:
continue
dist_ = 0
component_type_check = False
for head in head_sequence_result[idx]:
dist_ += max(abs(mount_point_pos[placement_result[idx][head]][0] - mount_point_pos[point_index][0]),
abs(mount_point_pos[placement_result[idx][head]][1] - mount_point_pos[point_index][1]))
if mount_point_part[placement_result[idx][head]] == mount_point_part[point_index]:
component_type_check = True
if (min_dist is None or dist_ < min_dist) and component_type_check:
min_dist = dist_
chg_cycle_index = idx
if chg_cycle_index == -1:
continue
chg_head, min_chg_dist = None, None
chg_cycle_point = []
for head in head_sequence_result[chg_cycle_index]:
index = placement_result[chg_cycle_index][head]
chg_cycle_point.append([mount_point_pos[index][0] - head * head_interval, mount_point_pos[index][1]])
for idx, head in enumerate(head_sequence_result[chg_cycle_index]):
chg_cycle_point_cpy = copy.deepcopy(chg_cycle_point)
index = placement_result[chg_cycle_index][head]
if mount_point_part[index] != mount_point_part[point_index]:
continue
chg_cycle_point_cpy[idx][0] = (mount_point_pos[index][0]) - head * head_interval
chg_dist = 0
aver_chg_pos = [sum(map(lambda x: x[0], chg_cycle_point_cpy)) / len(chg_cycle_point_cpy),
sum(map(lambda x: x[1], chg_cycle_point_cpy)) / len(chg_cycle_point_cpy)]
for pos in chg_cycle_point_cpy:
chg_dist += max(abs(aver_chg_pos[0] - pos[0]), abs(aver_chg_pos[1] - pos[1]))
# 更换后各点距离中心更近
if min_chg_dist is None or chg_dist < min_chg_dist:
chg_head = head
min_chg_dist = chg_dist
if chg_head is None:
continue
# === 第一轮变更周期chg_cycle_index的贴装点重排 ===
chg_placement_res = placement_result[chg_cycle_index].copy()
chg_placement_res[chg_head] = point_index
cycle_point_list = defaultdict(list)
for head, point in enumerate(chg_placement_res):
if point == -1:
continue
cycle_point_list[mount_point_part[point]].append(point)
for key, point_list in cycle_point_list.items():
cycle_point_list[key] = sorted(point_list, key=lambda p: mount_point_pos[p][0])
chg_placement_res = []
for head, point_index in enumerate(placement_result[chg_cycle_index]):
if point_index == -1:
chg_placement_res.append(-1)
else:
part = mount_point_part[point_index]
chg_placement_res.append(cycle_point_list[part][0])
cycle_point_list[part].pop(0)
chg_place_moving, chg_head_res = dynamic_programming_cycle_path(pcb_data, chg_placement_res,
feeder_slot_result[
cycle_group_index[chg_cycle_index]])
# === 第二轮原始周期cycle_index的贴装点重排 ===
placement_res = placement_result[cycle_index].copy()
placement_res[head_index] = placement_result[chg_cycle_index][chg_head]
for point in placement_res:
if point == -1:
continue
cycle_point_list[mount_point_part[point]].append(point)
for key, point_list in cycle_point_list.items():
cycle_point_list[key] = sorted(point_list, key=lambda p: mount_point_pos[p][0])
placement_res = []
for head, point_index in enumerate(placement_result[cycle_index]):
if point_index == -1:
placement_res.append(-1)
else:
part = mount_point_part[point_index]
placement_res.append(cycle_point_list[part][0])
cycle_point_list[part].pop(0)
place_moving, place_head_res = dynamic_programming_cycle_path(pcb_data, placement_res, feeder_slot_result[
cycle_group_index[cycle_index]])
# 更新贴装顺序分配结果
placement_result[cycle_index], head_sequence_result[cycle_index] = placement_res, place_head_res
placement_result[chg_cycle_index], head_sequence_result[chg_cycle_index] = chg_placement_res, chg_head_res
# 更新移动路径
cycle_length[cycle_index], cycle_length[chg_cycle_index] = place_moving, chg_place_moving
# 更新平均坐标和最大偏离点索引
point_list, point_index_list = [], []
for head in head_sequence_result[cycle_index]:
point_index_list.append(placement_result[cycle_index][head])
point_pos = mount_point_pos[point_index_list[-1]].copy()
point_pos[0] -= head * head_interval
point_list.append(point_pos)
cycle_average_pos[cycle_index] = [sum(map(lambda x: x[0], point_list)) / len(point_list),
sum(map(lambda x: x[1], point_list)) / len(point_list)]
point_list, point_index_list = [], []
for head in head_sequence_result[chg_cycle_index]:
point_index_list.append(placement_result[chg_cycle_index][head])
point_pos = mount_point_pos[point_index_list[-1]].copy()
point_pos[0] -= head * head_interval
point_list.append(point_pos)
cycle_average_pos[chg_cycle_index] = [sum(map(lambda x: x[0], point_list)) / len(point_list),
sum(map(lambda x: x[1], point_list)) / len(point_list)]
test1 = sum(cycle_length)
test2 = sum(best_cycle_length)
if sum(cycle_length) < sum(best_cycle_length):
best_cycle_length = cycle_length.copy()
best_cycle_average_pos = copy.deepcopy(cycle_average_pos)
best_placement_result, best_head_sequence_result = copy.deepcopy(placement_result), copy.deepcopy(
head_sequence_result)
cur_time = time.time()
if cur_time - start_time > n_runningtime:
break
pbar.update(cur_time - prev_time)
prev_time = cur_time
# print("number of iteration: ", n_iteration)
return best_placement_result, best_head_sequence_result

View File

@ -3,32 +3,36 @@ import copy
from base_optimizer.optimizer_common import * from base_optimizer.optimizer_common import *
def load_data(filename: str, load_feeder=True, auto_register=True): def load_data(filename: str, load_feeder=False, auto_register=True):
filename = 'data/' + filename filename = 'data/' + filename
part_content, step_content = False, False part_content, step_content, feeder_content = False, False, False
part_start_line, step_start_line, part_end_line, step_end_line = -1, -1, -1, -1 part_start_line, step_start_line, feeder_start_line = -1, -1, -1
part_end_line, step_end_line, feeder_end_line = -1, -1, -1
line_counter = 0 line_counter = 0
with open(filename, 'r') as file: with open(filename, 'r') as file:
line = file.readline() line = file.readline()
while line: while line:
if line == '[Part]\n': if line == '[Part]\n':
part_content = True part_content, part_start_line = True, line_counter
part_start_line = line_counter
elif line == '[Step]\n': elif line == '[Step]\n':
step_content = True step_content, step_start_line = True, line_counter
step_start_line = line_counter elif line == '[Feeder]\n':
feeder_content, feeder_start_line = True, line_counter
elif line == '\n': elif line == '\n':
if part_content: if part_content:
part_content = False part_content, part_end_line = False, line_counter
part_end_line = line_counter
elif step_content: elif step_content:
step_content = False step_content, step_end_line = False, line_counter
step_end_line = line_counter elif feeder_content:
feeder_content, feeder_end_line = False, line_counter
line_counter += 1 line_counter += 1
line = file.readline() line = file.readline()
if part_content: if part_content:
part_end_line = line_counter part_end_line = line_counter
elif feeder_content:
feeder_end_line = line_counter
else: else:
step_end_line = line_counter step_end_line = line_counter
@ -64,7 +68,8 @@ def load_data(filename: str, load_feeder=True, auto_register=True):
machine_index = machine_name[data['machine']] machine_index = machine_name[data['machine']]
else: else:
machine_index = 0 machine_index = 0
pcb_data[machine_index] = pcb_data[machine_index]._append(data[step_col], ignore_index=True) # pcb_data[machine_index] = pcb_data[machine_index]._append(data[step_col], ignore_index=True)
pcb_data[machine_index] = pd.concat([pcb_data[machine_index], pd.DataFrame(data[step_col]).T], ignore_index=True)
part_col = ["part", "fdr", "nz", 'fdn'] part_col = ["part", "fdr", "nz", 'fdn']
try: try:
@ -121,8 +126,10 @@ def load_data(filename: str, load_feeder=True, auto_register=True):
part_index = component_data[machine_index][component_data[machine_index].part == data.part].index.tolist()[ part_index = component_data[machine_index][component_data[machine_index].part == data.part].index.tolist()[
0] 0]
if component_data[machine_index].loc[part_index].nz != data.nz: if component_data[machine_index].loc[part_index].nz != data.nz:
component_data[machine_index].loc[part_index].nz = data.nz
warning_info = 'the nozzle type of component ' + data.part + ' is not consistent with the pcb data' warning_info = 'the nozzle type of component ' + data.part + ' is not consistent with the pcb data'
warnings.warn(warning_info, UserWarning) warnings.warn(warning_info, UserWarning)
component_data[machine_index].loc[part_index].fdr = data.fdr
if data.fdn == 0: if data.fdn == 0:
continue continue
@ -140,24 +147,21 @@ def load_data(filename: str, load_feeder=True, auto_register=True):
# pcb_data[machine_index].reset_index(inplace=True) # pcb_data[machine_index].reset_index(inplace=True)
# 读取供料器基座数据 # 读取供料器基座数据
feeder_data = defaultdict(pd.DataFrame) feeder_columns = ['slot', 'part']
if load_feeder: if load_feeder:
feeder_columns = ['slot', 'part', 'arg'] try:
for machine_index in range(machine_num): if feeder_start_line != -1:
feeder_data[machine_index] = pd.DataFrame(columns=feeder_columns) # arg表示是否为预分配不表示分配数目 feeder_data = pd.DataFrame(
for _, data in pcb_data[machine_index].iterrows(): pd.read_csv(filepath_or_buffer=filename, sep='\t', header=None, skiprows=feeder_start_line + 1,
slot, part = data['fdr'].split(' ') nrows=feeder_end_line - feeder_start_line - 1))
if slot[0] != 'F' and slot[0] != 'R': feeder_data.columns = feeder_columns
continue else:
slot = int(slot[1:]) if slot[0] == 'F' else int(slot[1:]) + max_slot_index // 2 feeder_data = pd.DataFrame(columns=feeder_columns)
feeder_data[machine_index] = pd.concat([feeder_data[machine_index], pd.DataFrame([slot, part, 1], index=feeder_columns).T], ignore_index=True) except:
feeder_data = pd.DataFrame(columns=feeder_columns)
feeder_data[machine_index].drop_duplicates(subset='slot', inplace=True, ignore_index=True) else:
# 随机移除部分已安装的供料器 feeder_data = pd.DataFrame(columns=feeder_columns)
# drop_index = random.sample(list(range(len(feeder_data))), len(feeder_data) // 2) feeder_data.sort_values(by='slot', ascending=True, inplace=True, ignore_index=True)
# feeder_data[machine_index].drop(index=drop_index, inplace=True)
feeder_data[machine_index].sort_values(by='slot', ascending=True, inplace=True, ignore_index=True)
return pcb_data, component_data, feeder_data return pcb_data, component_data, feeder_data

View File

@ -3,8 +3,9 @@ from base_optimizer.optimizer_interface import *
def exact_assembly_time(pcb_data, component_data): def exact_assembly_time(pcb_data, component_data):
feeder_data = pd.DataFrame(columns=['slot', 'part'])
component_result, cycle_result, feeder_slot_result = feeder_priority_assignment(component_data, pcb_data, component_result, cycle_result, feeder_slot_result = feeder_priority_assignment(component_data, pcb_data,
hinter=False) feeder_data, hinter=False)
placement_result, head_sequence_result = greedy_placement_route_generation(component_data, pcb_data, placement_result, head_sequence_result = greedy_placement_route_generation(component_data, pcb_data,
component_result, cycle_result, component_result, cycle_result,
feeder_slot_result, hinter=False) feeder_slot_result, hinter=False)

View File

@ -174,7 +174,7 @@ class DataMgr:
def heuristic_objective(self, cp_points, cp_nozzle): def heuristic_objective(self, cp_points, cp_nozzle):
if len(cp_points.keys()) == 0: if len(cp_points.keys()) == 0:
return 0 return 0, 0, 0, 0
nozzle_heads, nozzle_points = defaultdict(int), defaultdict(int) nozzle_heads, nozzle_points = defaultdict(int), defaultdict(int)
for idx, points in cp_points.items(): for idx, points in cp_points.items():
if points == 0: if points == 0:

View File

@ -410,9 +410,6 @@ def line_optimizer_hyperheuristic(component_data, pcb_data, machine_number):
best_heuristic_list = population[0] best_heuristic_list = population[0]
best_component_list = component_list.copy() best_component_list = component_list.copy()
val = cal_individual_val(heuristic_map, cp_index, cp_points, cp_nozzle, cp_feeders, board_width, board_height,
best_component_list, best_heuristic_list, machine_number, estimator)
machine_cp_points = convert_assignment_result(heuristic_map, cp_index, cp_points, cp_nozzle, cp_feeders, machine_cp_points = convert_assignment_result(heuristic_map, cp_index, cp_points, cp_nozzle, cp_feeders,
best_component_list, best_heuristic_list, machine_number) best_component_list, best_heuristic_list, machine_number)

View File

@ -1,12 +1,13 @@
from base_optimizer.optimizer_common import * from base_optimizer.optimizer_common import *
from base_optimizer.result_analysis import * from base_optimizer.result_analysis import *
from base_optimizer.smtopt_route import *
def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True): def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True):
mdl = Model('pcb assembly line optimizer') mdl = Model('pcb assembly line optimizer')
mdl.setParam('Seed', 0) mdl.setParam('Seed', 0)
mdl.setParam('OutputFlag', hinter) # set whether output the debug information mdl.setParam('OutputFlag', hinter) # set whether output the debug information
# mdl.setParam('TimeLimit', 0.01) mdl.setParam('TimeLimit', 600 * 3)
nozzle_type, component_type = [], [] nozzle_type, component_type = [], []
for _, data in component_data.iterrows(): for _, data in component_data.iterrows():
@ -24,7 +25,7 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True):
S = min(len(component_data) * ratio, 60) S = min(len(component_data) * ratio, 60)
K = math.ceil(len(pcb_data) * 1.0 / H / M) + 1 K = math.ceil(len(pcb_data) * 1.0 / H / M) + 1
# K = 3 # K = 3
CompOfNozzle = [[0 for _ in range(J)] for _ in range(I)] # Compatibility CompOfNozzle = [[0 for _ in range(J)] for _ in range(I)] # Compatibility
component_point = [0 for _ in range(I)] component_point = [0 for _ in range(I)]
for idx, data in component_data.iterrows(): for idx, data in component_data.iterrows():
@ -41,7 +42,7 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True):
d_minus = mdl.addVars(list_range(J), list_range(K), list_range(H), list_range(M), lb=0, vtype=GRB.CONTINUOUS) d_minus = mdl.addVars(list_range(J), list_range(K), list_range(H), list_range(M), lb=0, vtype=GRB.CONTINUOUS)
w = mdl.addVars(list_range(K), list_range(M), vtype=GRB.CONTINUOUS) w = mdl.addVars(list_range(K), list_range(M), vtype=GRB.CONTINUOUS)
e = mdl.addVars(list_range(-(H - 1) * ratio, S), list_range(K), list_range(M), vtype=GRB.BINARY) e = mdl.addVars(list_range(-(H - 1) * ratio, S), list_range(K), list_range(M), vtype=GRB.BINARY)
f = mdl.addVars(list_range(S), list_range(I), list_range(M), vtype=GRB.BINARY, name='') f = mdl.addVars(list_range(S), list_range(I), list_range(M), vtype=GRB.BINARY, name='')
obj = mdl.addVar(lb=0, ub=N, vtype=GRB.CONTINUOUS) obj = mdl.addVar(lb=0, ub=N, vtype=GRB.CONTINUOUS)
@ -87,7 +88,8 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True):
in range(K) for m in range(M)) in range(K) for m in range(M))
# feeder related # feeder related
mdl.addConstrs(quicksum(f[s, i, m] for s in range(S) for m in range(M)) <= component_data.iloc[i].fdn for i in range(I)) mdl.addConstrs(
quicksum(f[s, i, m] for s in range(S) for m in range(M)) <= component_data.iloc[i].fdn for i in range(I))
mdl.addConstrs(quicksum(f[s, i, m] for i in range(I)) <= 1 for s in range(S) for m in range(M)) mdl.addConstrs(quicksum(f[s, i, m] for i in range(I)) <= 1 for s in range(S) for m in range(M))
mdl.addConstrs( mdl.addConstrs(
quicksum(u[i, k, h, m] * v[s, k, h, m] for h in range(H) for k in range(K)) >= f[s, i, m] for i in range(I) for quicksum(u[i, k, h, m] * v[s, k, h, m] for h in range(H) for k in range(K)) >= f[s, i, m] for i in range(I) for
@ -153,6 +155,10 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True):
if abs(v[s, k, h, m].x) < 1e-3: if abs(v[s, k, h, m].x) < 1e-3:
continue continue
feeder_slot_result[-1][h] = s feeder_slot_result[-1][h] = s
if sum(component_result[-1]) == -max_head_index:
component_result.pop(-1)
cycle_result.pop(-1)
feeder_slot_result.pop(-1)
average_pos = round( average_pos = round(
(sum(head_place_pos) / len(head_place_pos) + stopper_pos[0] - slotf1_pos[0] + 1) / slot_interval) (sum(head_place_pos) / len(head_place_pos) + stopper_pos[0] - slotf1_pos[0] + 1) / slot_interval)
@ -161,13 +167,13 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True):
for h in range(H): for h in range(H):
if feeder_slot_result[k][h] == -1: if feeder_slot_result[k][h] == -1:
continue continue
feeder_slot_result[k][h] = feeder_slot_result[k][h] * 2 + average_pos feeder_slot_result[k][h] = feeder_slot_result[k][h] * 2 + average_pos
placement_result, head_sequence = greedy_placement_route_generation(partial_component_data, partial_pcb_data, placement_result, head_sequence = greedy_placement_route_generation(partial_component_data, partial_pcb_data,
component_result, cycle_result, component_result, cycle_result,
feeder_slot_result, hinter=False) feeder_slot_result, hinter=False)
print('----- Placement machine ' + str(m + 1) + ' ----- ') print('----- Placement machine ' + str(m + 1) + ' ----- ')
opt_res = OptResult(component_result, cycle_result,feeder_slot_result, placement_result, head_sequence) opt_res = OptResult(component_result, cycle_result, feeder_slot_result, placement_result, head_sequence)
info = placement_info_evaluation(partial_component_data, partial_pcb_data, opt_res, hinter=False) info = placement_info_evaluation(partial_component_data, partial_pcb_data, opt_res, hinter=False)
optimization_assign_result(partial_component_data, partial_pcb_data, opt_res, nozzle_hinter=True, optimization_assign_result(partial_component_data, partial_pcb_data, opt_res, nozzle_hinter=True,
component_hinter=True, feeder_hinter=True) component_hinter=True, feeder_hinter=True)

View File

@ -1,5 +1,7 @@
import time import time
import pandas as pd
from dataloader import * from dataloader import *
from lineopt_genetic import line_optimizer_genetic from lineopt_genetic import line_optimizer_genetic
from lineopt_heuristic import line_optimizer_heuristic from lineopt_heuristic import line_optimizer_heuristic
@ -10,11 +12,9 @@ from lineopt_model import line_optimizer_model
from base_optimizer.optimizer_interface import * from base_optimizer.optimizer_interface import *
def optimizer(pcb_data, component_data, params): def optimizer(pcb_data, component_data, feeder_data, params):
if params.machine_number == 1: if params.machine_number == 1:
assembly_info = [ assembly_info = [base_optimizer(1, pcb_data, component_data, feeder_data, params, hinter=True)]
base_optimizer(1, pcb_data, component_data, pd.DataFrame(columns=['slot', 'part', 'arg']), params,
hinter=True)]
return assembly_info return assembly_info
if params.line_optimizer == 'hyper-heuristic' or params.line_optimizer == 'heuristic' or params.line_optimizer \ if params.line_optimizer == 'hyper-heuristic' or params.line_optimizer == 'heuristic' or params.line_optimizer \
@ -33,8 +33,8 @@ def optimizer(pcb_data, component_data, params):
for machine_index in range(params.machine_number): for machine_index in range(params.machine_number):
assembly_info.append(base_optimizer(machine_index + 1, partial_pcb_data[machine_index], assembly_info.append(base_optimizer(machine_index + 1, partial_pcb_data[machine_index],
partial_component_data[machine_index], partial_component_data[machine_index],
pd.DataFrame(columns=['slot', 'part', 'arg']), params, hinter=True)) pd.DataFrame(columns=['slot', 'part']), params, hinter=True))
elif params.line_optimizer == 'model': elif params.line_optimizer == 'mip-model':
assembly_info = line_optimizer_model(component_data, pcb_data, params.machine_number) assembly_info = line_optimizer_model(component_data, pcb_data, params.machine_number)
else: else:
raise 'line optimizer method is not existed' raise 'line optimizer method is not existed'
@ -44,19 +44,20 @@ def optimizer(pcb_data, component_data, params):
@timer_wrapper @timer_wrapper
def main(): def main():
warnings.simplefilter(action='ignore', category=FutureWarning)
# 参数解析 # 参数解析
parser = argparse.ArgumentParser(description='assembly line optimizer implementation') parser = argparse.ArgumentParser(description='assembly line optimizer implementation')
parser.add_argument('--mode', default=1, type=int, help='mode: 0 -directly load pcb data without optimization ' parser.add_argument('--mode', default=1, type=int, help='mode: 0 -directly load pcb data without optimization '
'for data analysis, 1 -optimize pcb data, 2 -batch test') 'for data analysis, 1 -optimize pcb data, 2 -batch test')
parser.add_argument('--filename', default='L01/KAN3-Z2.txt', type=str, help='load pcb data') parser.add_argument('--filename', default='PCB.txt', type=str, help='load pcb data')
# parser.add_argument('--filename', default='chapter3-2/PCB2-8 Arg1.txt', type=str, help='load pcb data')
parser.add_argument('--comp_register', default=1, type=int, help='register the component according the pcb data') parser.add_argument('--comp_register', default=1, type=int, help='register the component according the pcb data')
parser.add_argument('--machine_number', default=2, type=int, help='the number of machine in the assembly line') parser.add_argument('--machine_number', default=1, type=int, help='the number of machine in the assembly line')
parser.add_argument('--machine_optimizer', default='feeder-scan', type=str, help='optimizer for single machine') # parser.add_argument('--machine_optimizer', default='mip-model', type=str, help='optimizer for single machine')
parser.add_argument('--machine_optimizer', default='feeder-priority', type=str, help='optimizer for single machine')
parser.add_argument('--line_optimizer', default='hyper-heuristic', type=str, help='optimizer for PCB assembly line') parser.add_argument('--line_optimizer', default='hyper-heuristic', type=str, help='optimizer for PCB assembly line')
# parser.add_argument('--line_optimizer', default='model', type=str, help='optimizer for PCB assembly line') # parser.add_argument('--line_optimizer', default='model', type=str, help='optimizer for PCB assembly line')
parser.add_argument('--feeder_limit', default=1, type=int, help='the upper feeder limit for each type of component') parser.add_argument('--feeder_limit', default=1, type=int, help='the upper feeder limit for each type of component')
parser.add_argument('--save', default=1, type=int, help='save the optimization result') parser.add_argument('--save', default=0, type=int, help='save the optimization result')
parser.add_argument('--save_suffix', default='(10)', type=str, help='load pcb data') parser.add_argument('--save_suffix', default='(10)', type=str, help='load pcb data')
params = parser.parse_args() params = parser.parse_args()
@ -88,14 +89,16 @@ def main():
f'standard deviation: {np.std([info.total_time for info in assembly_info]): .3f}') f'standard deviation: {np.std([info.total_time for info in assembly_info]): .3f}')
elif params.mode == 1: elif params.mode == 1:
sys.stdout = open(f'record/{params.filename[:-4]}-{params.line_optimizer}.txt', 'w') # sys.stdout = open(f'record/{params.filename[:-4]}-{params.line_optimizer}.txt', 'w')
# 加载PCB数据 # 加载PCB数据
partial_pcb_data, partial_component_data, _ = load_data(params.filename) partial_pcb_data, partial_component_data, feeder_data = load_data(params.filename, load_feeder=True)
pcb_data, component_data = merge_data(partial_pcb_data, partial_component_data) pcb_data, component_data = merge_data(partial_pcb_data, partial_component_data)
start_time = time.time() start_time = time.time()
assembly_info = optimizer(pcb_data, component_data, params)
sys.stdout = sys.__stdout__ assembly_info = optimizer(pcb_data, component_data, feeder_data, params)
# sys.stdout = sys.__stdout__
print(f'optimizer running time: {time.time() - start_time: .3f}') print(f'optimizer running time: {time.time() - start_time: .3f}')
for machine_idx, info in enumerate(assembly_info): for machine_idx, info in enumerate(assembly_info):
print(f'assembly time for machine {machine_idx + 1: d}: {info.total_time: .3f} s, total placement: ' print(f'assembly time for machine {machine_idx + 1: d}: {info.total_time: .3f} s, total placement: '
@ -105,6 +108,37 @@ def main():
print(f'finial assembly time: {max(info.total_time for info in assembly_info): .3f} s, ' print(f'finial assembly time: {max(info.total_time for info in assembly_info): .3f} s, '
f'standard deviation: {np.std([info.total_time for info in assembly_info]): .3f}') f'standard deviation: {np.std([info.total_time for info in assembly_info]): .3f}')
elif params.mode == 2:
machine_optimizer = ['two-phase', 'hybrid-genetic', 'cell-division', 'feeder-priority', 'aggregation']
running_round = 10
opt_columns = ['Cycle', 'Pick', 'Nozzle-Change', 'Running-Time']
opt_result, opt_runtime = defaultdict(pd.DataFrame), defaultdict(pd.DataFrame)
for opt in machine_optimizer:
opt_result[opt] =pd.DataFrame(columns=opt_columns)
opt_result[opt].index.name = 'file'
for _, file in enumerate(os.listdir('data/')):
if file[-3:] != 'txt':
continue
partial_pcb_data, partial_component_data, feeder_data = load_data(file)
pcb_data, component_data = merge_data(partial_pcb_data, partial_component_data)
for opt in machine_optimizer:
for round_idx in range(running_round):
print(f'--- file {file}, round {round_idx}, optimizer {opt} --- ')
params = parser.parse_args(['--machine_optimizer', opt, '--machine_number', str(1)])
start_time = time.time()
assembly_info = optimizer(pcb_data, component_data, feeder_data, params)
opt_result[opt].loc[file + str(round_idx + 1), 'Cycle'] = assembly_info[0].cycle_counter
opt_result[opt].loc[file + str(round_idx + 1), 'Pick'] = assembly_info[0].pickup_counter
opt_result[opt].loc[file + str(round_idx + 1), 'Nozzle-Change'] = assembly_info[0].nozzle_change_counter
opt_result[opt].loc[file + str(round_idx + 1), 'Running-Time'] = time.time() - start_time
with pd.ExcelWriter('result/machine_optimizer.xlsx', engine='openpyxl') as writer:
for opt, result in opt_result.items():
result.to_excel(writer, sheet_name=opt, float_format='%.3f', na_rep='')
else: else:
# line_optimizer = ['T-Solution', 'hyper-heuristic', 'genetic', 'reconfiguration'] # line_optimizer = ['T-Solution', 'hyper-heuristic', 'genetic', 'reconfiguration']
line_optimizer = ['genetic'] line_optimizer = ['genetic']