增加预安装供料器功能、路径规划模型支持单点、整线优化支持批量处理
This commit is contained in:
@ -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和节点j,dp边界,赋予初值
|
|
||||||
# print('j:', j)
|
|
||||||
min_path[s][j] = [j]
|
|
||||||
min_dist[s][j] = dist[0][j]
|
|
||||||
|
|
||||||
# 枚举下一个节点i,更新
|
|
||||||
for i in range(1, num_pos):
|
|
||||||
# 下一个节点i需在考虑节点集合s外
|
|
||||||
if s & (1 << i):
|
|
||||||
continue
|
|
||||||
if min_dist[s][j] + dist[j][i] < min_dist[s | (1 << i)][i]:
|
|
||||||
min_path[s | (1 << i)][i] = min_path[s][j] + [i]
|
|
||||||
min_dist[s | (1 << i)][i] = min_dist[s][j] + dist[j][i]
|
|
||||||
|
|
||||||
ans_dist = float('inf')
|
|
||||||
ans_path = []
|
|
||||||
# 求最终最短哈密顿回路
|
|
||||||
for i in range(1, num_pos):
|
|
||||||
if min_dist[(1 << num_pos) - 1][i] + dist[i][0] < ans_dist:
|
|
||||||
# 更新,回路化
|
|
||||||
ans_path = min_path[s][i]
|
|
||||||
ans_dist = min_dist[(1 << num_pos) - 1][i] + dist[i][0]
|
|
||||||
|
|
||||||
for parent in ans_path:
|
|
||||||
head_sequence.append(head_set[parent - 1])
|
|
||||||
|
|
||||||
start_head, end_head = head_sequence[0], head_sequence[-1]
|
|
||||||
if pcb_data.iloc[cycle_placement[start_head]]['x'] - start_head * head_interval > \
|
|
||||||
pcb_data.iloc[cycle_placement[end_head]]['x'] - end_head * head_interval:
|
|
||||||
head_sequence = list(reversed(head_sequence))
|
|
||||||
return head_sequence
|
|
||||||
|
|
||||||
|
|
||||||
@timer_wrapper
|
|
||||||
def greedy_placement_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
|
||||||
hinter=True):
|
|
||||||
placement_result, head_sequence_result = [], []
|
|
||||||
if len(pcb_data) == 0:
|
|
||||||
return placement_result, head_sequence_result
|
|
||||||
mount_point_index = [[] for _ in range(len(component_data))]
|
|
||||||
mount_point_pos = [[] for _ in range(len(component_data))]
|
|
||||||
|
|
||||||
for i in range(len(pcb_data)):
|
|
||||||
part = pcb_data.iloc[i]['part']
|
|
||||||
component_index = component_data[component_data['part'] == part].index.tolist()[0]
|
|
||||||
# 记录贴装点序号索引和对应的位置坐标
|
|
||||||
mount_point_index[component_index].append(i)
|
|
||||||
mount_point_pos[component_index].append([pcb_data.iloc[i]['x'], pcb_data.iloc[i]['y']])
|
|
||||||
|
|
||||||
search_dir = 1 # 0:自左向右搜索 1:自右向左搜索
|
|
||||||
for cycle_set in range(len(component_result)):
|
|
||||||
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
|
|
||||||
for cycle in range(floor_cycle, ceil_cycle):
|
|
||||||
if sum(component_result[cycle_set]) == -max_head_index:
|
|
||||||
continue
|
|
||||||
# search_dir = 1 - search_dir
|
|
||||||
assigned_placement = [-1] * max_head_index
|
|
||||||
max_pos = [max(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in
|
|
||||||
range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0]
|
|
||||||
min_pos = [min(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in
|
|
||||||
range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0]
|
|
||||||
point2head_range = min(math.floor((max_pos - min_pos) / head_interval) + 1, max_head_index)
|
|
||||||
|
|
||||||
# 最近邻确定
|
|
||||||
way_point = None
|
|
||||||
head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index)
|
|
||||||
for head_counter, head in enumerate(head_range):
|
|
||||||
if component_result[cycle_set][head] == -1:
|
|
||||||
continue
|
|
||||||
|
|
||||||
component_index = component_result[cycle_set][head]
|
|
||||||
if way_point is None or head_counter % point2head_range == 0:
|
|
||||||
index = 0
|
|
||||||
if way_point is None:
|
|
||||||
if search_dir:
|
|
||||||
index = np.argmax(mount_point_pos[component_index], axis=0)[0]
|
|
||||||
else:
|
|
||||||
index = np.argmin(mount_point_pos[component_index], axis=0)[0]
|
|
||||||
else:
|
|
||||||
for next_head in head_range:
|
|
||||||
component_index = component_result[cycle_set][next_head]
|
|
||||||
if assigned_placement[next_head] == -1 and component_index != -1:
|
|
||||||
num_points = len(mount_point_pos[component_index])
|
|
||||||
index = np.argmin(
|
|
||||||
[abs(mount_point_pos[component_index][i][0] - way_point[0]) * .1 + abs(
|
|
||||||
mount_point_pos[component_index][i][1] - way_point[1]) for i in
|
|
||||||
range(num_points)])
|
|
||||||
head = next_head
|
|
||||||
break
|
|
||||||
# index = np.argmax(mount_point_pos[component_index], axis=0)[0]
|
|
||||||
assigned_placement[head] = mount_point_index[component_index][index]
|
|
||||||
|
|
||||||
# 记录路标点
|
|
||||||
way_point = mount_point_pos[component_index][index]
|
|
||||||
way_point[0] += (max_head_index - head - 1) * head_interval if search_dir else -head * head_interval
|
|
||||||
|
|
||||||
mount_point_index[component_index].pop(index)
|
|
||||||
mount_point_pos[component_index].pop(index)
|
|
||||||
else:
|
|
||||||
head_index, point_index = -1, -1
|
|
||||||
min_cheby_distance, min_euler_distance = float('inf'), float('inf')
|
|
||||||
for next_head in range(max_head_index):
|
|
||||||
if assigned_placement[next_head] != -1 or component_result[cycle_set][next_head] == -1:
|
|
||||||
continue
|
|
||||||
next_comp_index = component_result[cycle_set][next_head]
|
|
||||||
for counter in range(len(mount_point_pos[next_comp_index])):
|
|
||||||
if search_dir:
|
|
||||||
delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0]
|
|
||||||
+ (max_head_index - next_head - 1) * head_interval)
|
|
||||||
else:
|
|
||||||
delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0]
|
|
||||||
- next_head * head_interval)
|
|
||||||
|
|
||||||
delta_y = abs(mount_point_pos[next_comp_index][counter][1] - way_point[1])
|
|
||||||
|
|
||||||
euler_distance = pow(axis_moving_time(delta_x, 0), 2) + pow(axis_moving_time(delta_y, 1), 2)
|
|
||||||
cheby_distance = max(axis_moving_time(delta_x, 0),
|
|
||||||
axis_moving_time(delta_y, 1)) + 5e-2 * euler_distance
|
|
||||||
if cheby_distance < min_cheby_distance or (abs(cheby_distance - min_cheby_distance) < 1e-9
|
|
||||||
and euler_distance < min_euler_distance):
|
|
||||||
# if euler_distance < min_euler_distance:
|
|
||||||
min_cheby_distance, min_euler_distance = cheby_distance, euler_distance
|
|
||||||
head_index, point_index = next_head, counter
|
|
||||||
|
|
||||||
component_index = component_result[cycle_set][head_index]
|
|
||||||
assert (0 <= head_index < max_head_index)
|
|
||||||
|
|
||||||
assigned_placement[head_index] = mount_point_index[component_index][point_index]
|
|
||||||
way_point = mount_point_pos[component_index][point_index]
|
|
||||||
way_point[0] += (max_head_index - head_index - 1) * head_interval if search_dir \
|
|
||||||
else -head_index * head_interval
|
|
||||||
|
|
||||||
mount_point_index[component_index].pop(point_index)
|
|
||||||
mount_point_pos[component_index].pop(point_index)
|
|
||||||
|
|
||||||
placement_result.append(assigned_placement) # 各个头上贴装的元件类型
|
|
||||||
head_sequence_result.append(
|
|
||||||
dynamic_programming_cycle_path(pcb_data, assigned_placement, feeder_slot_result[cycle_set]))
|
|
||||||
|
|
||||||
return placement_result, head_sequence_result
|
|
||||||
|
|
||||||
|
|
||||||
@timer_wrapper
|
|
||||||
def beam_search_for_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result):
|
|
||||||
beam_width = 4 # 集束宽度
|
|
||||||
base_points = [float('inf'), float('inf')]
|
|
||||||
|
|
||||||
mount_point_index = [[] for _ in range(len(component_data))]
|
|
||||||
mount_point_pos = [[] for _ in range(len(component_data))]
|
|
||||||
|
|
||||||
for i in range(len(pcb_data)):
|
|
||||||
part = pcb_data.loc[i]['part']
|
|
||||||
component_index = component_data[component_data['part'] == part].index.tolist()[0]
|
|
||||||
|
|
||||||
# 记录贴装点序号索引和对应的位置坐标
|
|
||||||
mount_point_index[component_index].append(i)
|
|
||||||
mount_point_pos[component_index].append([pcb_data.loc[i]['x'], pcb_data.loc[i]['y']])
|
|
||||||
|
|
||||||
# 记录最左下角坐标
|
|
||||||
if mount_point_pos[component_index][-1][0] < base_points[0]:
|
|
||||||
base_points[0] = mount_point_pos[component_index][-1][0]
|
|
||||||
if mount_point_pos[component_index][-1][1] < base_points[1]:
|
|
||||||
base_points[1] = mount_point_pos[component_index][-1][1]
|
|
||||||
|
|
||||||
beam_placement_sequence, beam_head_sequence = [], []
|
|
||||||
beam_mount_point_index, beam_mount_point_pos = [], []
|
|
||||||
|
|
||||||
for beam_counter in range(beam_width):
|
|
||||||
beam_mount_point_index.append(copy.deepcopy(mount_point_index))
|
|
||||||
beam_mount_point_pos.append(copy.deepcopy(mount_point_pos))
|
|
||||||
|
|
||||||
beam_placement_sequence.append([])
|
|
||||||
beam_head_sequence.append([])
|
|
||||||
|
|
||||||
beam_distance = [0 for _ in range(beam_width)] # 记录当前集束搜索点的点数
|
|
||||||
def argpartition(list, kth):
|
|
||||||
if kth < len(list):
|
|
||||||
return np.argpartition(list, kth)
|
|
||||||
else:
|
|
||||||
index, indexes = 0, []
|
|
||||||
while len(indexes) < kth:
|
|
||||||
indexes.append(index)
|
|
||||||
index += 1
|
|
||||||
if index >= len(list):
|
|
||||||
index = 0
|
|
||||||
return np.array(indexes)
|
|
||||||
|
|
||||||
with tqdm(total=100) as pbar:
|
|
||||||
search_dir = 0
|
|
||||||
pbar.set_description('route schedule')
|
|
||||||
for cycle_set in range(len(component_result)):
|
|
||||||
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
|
|
||||||
for cycle in range(floor_cycle, ceil_cycle):
|
|
||||||
search_dir = 1 - search_dir
|
|
||||||
beam_way_point = None
|
|
||||||
for beam_counter in range(beam_width):
|
|
||||||
beam_placement_sequence[beam_counter].append([-1 for _ in range(max_head_index)])
|
|
||||||
|
|
||||||
head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index)
|
|
||||||
for head in head_range:
|
|
||||||
component_index = component_result[cycle_set][head]
|
|
||||||
if component_index == -1:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if beam_way_point is None:
|
|
||||||
# 首个贴装点的选取,距离基准点最近的beam_width个点
|
|
||||||
beam_way_point = [[0, 0]] * beam_width
|
|
||||||
|
|
||||||
for beam_counter in range(beam_width):
|
|
||||||
if search_dir:
|
|
||||||
index = np.argmax(beam_mount_point_pos[beam_counter][component_index], axis=0)[0]
|
|
||||||
else:
|
|
||||||
index = np.argmin(beam_mount_point_pos[beam_counter][component_index], axis=0)[0]
|
|
||||||
|
|
||||||
beam_placement_sequence[beam_counter][-1][head] = beam_mount_point_index[beam_counter][component_index][index]
|
|
||||||
|
|
||||||
beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][index]
|
|
||||||
beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \
|
|
||||||
search_dir else -head * head_interval
|
|
||||||
|
|
||||||
beam_mount_point_index[beam_counter][component_index].pop(index)
|
|
||||||
beam_mount_point_pos[beam_counter][component_index].pop(index)
|
|
||||||
else:
|
|
||||||
# 后续贴装点
|
|
||||||
search_beam_distance = []
|
|
||||||
search_beam_index = [0] * (beam_width ** 2)
|
|
||||||
for beam_counter in range(beam_width ** 2):
|
|
||||||
search_beam_distance.append(beam_distance[beam_counter // beam_width])
|
|
||||||
|
|
||||||
for beam_counter in range(beam_width):
|
|
||||||
# 对于集束beam_counter + 1最近的beam_width个点
|
|
||||||
num_points = len(beam_mount_point_pos[beam_counter][component_index])
|
|
||||||
|
|
||||||
dist = []
|
|
||||||
for i in range(num_points):
|
|
||||||
if search_dir:
|
|
||||||
delta_x = axis_moving_time(
|
|
||||||
beam_mount_point_pos[beam_counter][component_index][i][0] -
|
|
||||||
beam_way_point[beam_counter][0] + (max_head_index - head - 1) * head_interval,
|
|
||||||
0)
|
|
||||||
else:
|
|
||||||
delta_x = axis_moving_time(
|
|
||||||
beam_mount_point_pos[beam_counter][component_index][i][0] -
|
|
||||||
beam_way_point[beam_counter][0] - head * head_interval, 0)
|
|
||||||
|
|
||||||
delta_y = axis_moving_time(beam_mount_point_pos[beam_counter][component_index][i][1] -
|
|
||||||
beam_way_point[beam_counter][1], 1)
|
|
||||||
|
|
||||||
dist.append(max(delta_x, delta_y))
|
|
||||||
|
|
||||||
indexes = argpartition(dist, kth=beam_width)[:beam_width]
|
|
||||||
|
|
||||||
# 记录中间信息
|
|
||||||
for i, index in enumerate(indexes):
|
|
||||||
search_beam_distance[i + beam_counter * beam_width] += dist[index]
|
|
||||||
search_beam_index[i + beam_counter * beam_width] = index
|
|
||||||
|
|
||||||
indexes = np.argsort(search_beam_distance)
|
|
||||||
|
|
||||||
beam_mount_point_pos_cpy = copy.deepcopy(beam_mount_point_pos)
|
|
||||||
beam_mount_point_index_cpy = copy.deepcopy(beam_mount_point_index)
|
|
||||||
|
|
||||||
beam_placement_sequence_cpy = copy.deepcopy(beam_placement_sequence)
|
|
||||||
beam_head_sequence_cpy = copy.deepcopy(beam_head_sequence)
|
|
||||||
beam_counter = 0
|
|
||||||
assigned_placement = []
|
|
||||||
|
|
||||||
for i, index in enumerate(indexes):
|
|
||||||
# 拷贝原始集束数据
|
|
||||||
beam_mount_point_pos[beam_counter] = copy.deepcopy(beam_mount_point_pos_cpy[index // beam_width])
|
|
||||||
beam_mount_point_index[beam_counter] = copy.deepcopy(beam_mount_point_index_cpy[index // beam_width])
|
|
||||||
beam_placement_sequence[beam_counter] = copy.deepcopy(beam_placement_sequence_cpy[index // beam_width])
|
|
||||||
beam_head_sequence[beam_counter] = copy.deepcopy(beam_head_sequence_cpy[index // beam_width])
|
|
||||||
|
|
||||||
# 更新各集束最新扫描的的贴装点
|
|
||||||
component_index = component_result[cycle_set][head]
|
|
||||||
|
|
||||||
beam_placement_sequence[beam_counter][-1][head] = \
|
|
||||||
beam_mount_point_index[beam_counter][component_index][search_beam_index[index]]
|
|
||||||
|
|
||||||
if beam_placement_sequence[beam_counter][
|
|
||||||
-1] in assigned_placement and beam_width - beam_counter < len(indexes) - i:
|
|
||||||
continue
|
|
||||||
|
|
||||||
assigned_placement.append(beam_placement_sequence[beam_counter][-1])
|
|
||||||
|
|
||||||
# 更新参考基准点
|
|
||||||
beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][search_beam_index[index]]
|
|
||||||
beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \
|
|
||||||
search_dir else -head * head_interval
|
|
||||||
|
|
||||||
# 更新各集束贴装路径长度,移除各集束已分配的贴装点
|
|
||||||
beam_distance[beam_counter] = search_beam_distance[index]
|
|
||||||
|
|
||||||
beam_mount_point_pos[beam_counter][component_index].pop(search_beam_index[index])
|
|
||||||
beam_mount_point_index[beam_counter][component_index].pop(search_beam_index[index])
|
|
||||||
|
|
||||||
beam_counter += 1
|
|
||||||
|
|
||||||
if beam_counter >= beam_width:
|
|
||||||
break
|
|
||||||
assert(beam_counter >= beam_width)
|
|
||||||
|
|
||||||
# 更新头贴装顺序
|
|
||||||
for beam_counter in range(beam_width):
|
|
||||||
beam_head_sequence[beam_counter].append(
|
|
||||||
dynamic_programming_cycle_path(pcb_data, beam_placement_sequence[beam_counter][-1],
|
|
||||||
feeder_slot_result[cycle_set]))
|
|
||||||
|
|
||||||
pbar.update(1 / sum(cycle_result) * 100)
|
|
||||||
|
|
||||||
index = np.argmin(beam_distance)
|
|
||||||
return beam_placement_sequence[index], beam_head_sequence[index]
|
|
||||||
|
|
||||||
|
|
||||||
def optimal_nozzle_assignment(component_data, pcb_data):
|
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
|
||||||
|
@ -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}',
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
try:
|
||||||
component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data,
|
component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data,
|
||||||
component_cell,
|
component_cell,
|
||||||
pop_generation[pop])
|
pop_generation[pop])
|
||||||
pop_val.append(
|
pop_val.append(
|
||||||
component_assign_evaluate(component_data, component_result, cycle_result, feeder_slot_result))
|
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):
|
||||||
|
try:
|
||||||
component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data,
|
component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data,
|
||||||
component_cell,
|
component_cell,
|
||||||
pop_generation[pop])
|
pop_generation[pop])
|
||||||
pop_val[pop] = component_assign_evaluate(component_data, component_result, cycle_result,
|
pop_val[pop] = component_assign_evaluate(component_data, component_result, cycle_result,
|
||||||
feeder_slot_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:
|
||||||
|
@ -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,19 +195,6 @@ 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 = ''
|
|
||||||
max_points, max_nozzle_points = 0, 0
|
|
||||||
for nozzle in set(nozzle_pattern):
|
|
||||||
if len(tmp_nozzle_component[nozzle]) == 0:
|
|
||||||
continue
|
|
||||||
part = max(tmp_nozzle_component[nozzle],
|
|
||||||
key=lambda x: tmp_feeder_points[x] / tmp_feeder_limit[x]
|
|
||||||
if tmp_feeder_points[x] != 0 else 0)
|
|
||||||
index_ = tmp_nozzle_component[nozzle].index(part)
|
|
||||||
if max_points < tmp_nozzle_component_points[nozzle][index_]:
|
|
||||||
max_points, nozzle_assign = tmp_nozzle_component_points[nozzle][index_], nozzle
|
|
||||||
else:
|
|
||||||
# 吸嘴匹配模式非空,按对应吸嘴类型进行元件分配
|
# 吸嘴匹配模式非空,按对应吸嘴类型进行元件分配
|
||||||
nozzle_assign = nozzle_pattern[idx]
|
nozzle_assign = nozzle_pattern[idx]
|
||||||
|
|
||||||
@ -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,9 +608,6 @@ 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 = 0
|
|
||||||
else:
|
|
||||||
nozzle_change = 2 * (nozzle != nozzle_cycle[head])
|
nozzle_change = 2 * (nozzle != nozzle_cycle[head])
|
||||||
|
|
||||||
if cycle_index + 1 < len(nozzle_mode):
|
if cycle_index + 1 < len(nozzle_mode):
|
||||||
@ -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,8 +727,7 @@ 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()
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
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
|
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
|
||||||
|
@ -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
|
|
706
base_optimizer/smtopt_route.py
Normal file
706
base_optimizer/smtopt_route.py
Normal 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和节点j,dp边界,赋予初值
|
||||||
|
# print('j:', j)
|
||||||
|
min_path[s][j] = [j]
|
||||||
|
min_dist[s][j] = dist[0][j]
|
||||||
|
|
||||||
|
# 枚举下一个节点i,更新
|
||||||
|
for i in range(1, num_pos):
|
||||||
|
# 下一个节点i需在考虑节点集合s外
|
||||||
|
if s & (1 << i):
|
||||||
|
continue
|
||||||
|
if min_dist[s][j] + dist[j][i] < min_dist[s | (1 << i)][i]:
|
||||||
|
min_path[s | (1 << i)][i] = min_path[s][j] + [i]
|
||||||
|
min_dist[s | (1 << i)][i] = min_dist[s][j] + dist[j][i]
|
||||||
|
|
||||||
|
ans_dist = float('inf')
|
||||||
|
ans_path = []
|
||||||
|
# 求最终最短哈密顿回路
|
||||||
|
for i in range(1, num_pos):
|
||||||
|
if min_dist[(1 << num_pos) - 1][i] + dist[i][0] < ans_dist:
|
||||||
|
# 更新,回路化
|
||||||
|
ans_path = min_path[s][i]
|
||||||
|
ans_dist = min_dist[(1 << num_pos) - 1][i] + dist[i][0]
|
||||||
|
|
||||||
|
for parent in ans_path:
|
||||||
|
head_sequence.append(head_set[parent - 1])
|
||||||
|
|
||||||
|
start_head, end_head = head_sequence[0], head_sequence[-1]
|
||||||
|
if pcb_data.iloc[cycle_placement[start_head]]['x'] - start_head * head_interval > \
|
||||||
|
pcb_data.iloc[cycle_placement[end_head]]['x'] - end_head * head_interval:
|
||||||
|
head_sequence = list(reversed(head_sequence))
|
||||||
|
return ans_dist, head_sequence
|
||||||
|
|
||||||
|
|
||||||
|
@timer_wrapper
|
||||||
|
def greedy_placement_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||||
|
hinter=True):
|
||||||
|
placement_result, head_sequence_result = [], []
|
||||||
|
if len(pcb_data) == 0:
|
||||||
|
return placement_result, head_sequence_result
|
||||||
|
mount_point_index = [[] for _ in range(len(component_data))]
|
||||||
|
mount_point_pos = [[] for _ in range(len(component_data))]
|
||||||
|
|
||||||
|
for i in range(len(pcb_data)):
|
||||||
|
part = pcb_data.iloc[i]['part']
|
||||||
|
component_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||||
|
# 记录贴装点序号索引和对应的位置坐标
|
||||||
|
mount_point_index[component_index].append(i)
|
||||||
|
mount_point_pos[component_index].append([pcb_data.iloc[i]['x'], pcb_data.iloc[i]['y']])
|
||||||
|
|
||||||
|
search_dir = 1 # 0:自左向右搜索 1:自右向左搜索
|
||||||
|
for cycle_set in range(len(component_result)):
|
||||||
|
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
|
||||||
|
for cycle in range(floor_cycle, ceil_cycle):
|
||||||
|
# search_dir = 1 - search_dir
|
||||||
|
assigned_placement = [-1] * max_head_index
|
||||||
|
max_pos = [max(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in
|
||||||
|
range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0]
|
||||||
|
min_pos = [min(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in
|
||||||
|
range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0]
|
||||||
|
point2head_range = min(math.floor((max_pos - min_pos) / head_interval) + 1, max_head_index)
|
||||||
|
|
||||||
|
# 最近邻确定
|
||||||
|
way_point = None
|
||||||
|
head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index)
|
||||||
|
for head_counter, head in enumerate(head_range):
|
||||||
|
if component_result[cycle_set][head] == -1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
component_index = component_result[cycle_set][head]
|
||||||
|
if way_point is None or head_counter % point2head_range == 0:
|
||||||
|
index = 0
|
||||||
|
if way_point is None:
|
||||||
|
if search_dir:
|
||||||
|
index = np.argmax(mount_point_pos[component_index], axis=0)[0]
|
||||||
|
else:
|
||||||
|
index = np.argmin(mount_point_pos[component_index], axis=0)[0]
|
||||||
|
else:
|
||||||
|
for next_head in head_range:
|
||||||
|
component_index = component_result[cycle_set][next_head]
|
||||||
|
if assigned_placement[next_head] == -1 and component_index != -1:
|
||||||
|
num_points = len(mount_point_pos[component_index])
|
||||||
|
index = np.argmin(
|
||||||
|
[abs(mount_point_pos[component_index][i][0] - way_point[0]) * .1 + abs(
|
||||||
|
mount_point_pos[component_index][i][1] - way_point[1]) for i in
|
||||||
|
range(num_points)])
|
||||||
|
head = next_head
|
||||||
|
break
|
||||||
|
# index = np.argmax(mount_point_pos[component_index], axis=0)[0]
|
||||||
|
assigned_placement[head] = mount_point_index[component_index][index]
|
||||||
|
|
||||||
|
# 记录路标点
|
||||||
|
way_point = mount_point_pos[component_index][index]
|
||||||
|
way_point[0] += (max_head_index - head - 1) * head_interval if search_dir else -head * head_interval
|
||||||
|
|
||||||
|
mount_point_index[component_index].pop(index)
|
||||||
|
mount_point_pos[component_index].pop(index)
|
||||||
|
else:
|
||||||
|
head_index, point_index = -1, -1
|
||||||
|
min_cheby_distance, min_euler_distance = float('inf'), float('inf')
|
||||||
|
for next_head in range(max_head_index):
|
||||||
|
if assigned_placement[next_head] != -1 or component_result[cycle_set][next_head] == -1:
|
||||||
|
continue
|
||||||
|
next_comp_index = component_result[cycle_set][next_head]
|
||||||
|
for counter in range(len(mount_point_pos[next_comp_index])):
|
||||||
|
if search_dir:
|
||||||
|
delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0]
|
||||||
|
+ (max_head_index - next_head - 1) * head_interval)
|
||||||
|
else:
|
||||||
|
delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0]
|
||||||
|
- next_head * head_interval)
|
||||||
|
|
||||||
|
delta_y = abs(mount_point_pos[next_comp_index][counter][1] - way_point[1])
|
||||||
|
|
||||||
|
euler_distance = pow(axis_moving_time(delta_x, 0), 2) + pow(axis_moving_time(delta_y, 1), 2)
|
||||||
|
cheby_distance = max(axis_moving_time(delta_x, 0),
|
||||||
|
axis_moving_time(delta_y, 1)) + 5e-2 * euler_distance
|
||||||
|
if cheby_distance < min_cheby_distance or (abs(cheby_distance - min_cheby_distance) < 1e-9
|
||||||
|
and euler_distance < min_euler_distance):
|
||||||
|
# if euler_distance < min_euler_distance:
|
||||||
|
min_cheby_distance, min_euler_distance = cheby_distance, euler_distance
|
||||||
|
head_index, point_index = next_head, counter
|
||||||
|
|
||||||
|
component_index = component_result[cycle_set][head_index]
|
||||||
|
assert 0 <= head_index < max_head_index
|
||||||
|
|
||||||
|
assigned_placement[head_index] = mount_point_index[component_index][point_index]
|
||||||
|
way_point = mount_point_pos[component_index][point_index]
|
||||||
|
way_point[0] += (max_head_index - head_index - 1) * head_interval if search_dir \
|
||||||
|
else -head_index * head_interval
|
||||||
|
|
||||||
|
mount_point_index[component_index].pop(point_index)
|
||||||
|
mount_point_pos[component_index].pop(point_index)
|
||||||
|
|
||||||
|
placement_result.append(assigned_placement) # 各个头上贴装的元件类型
|
||||||
|
head_sequence_result.append(
|
||||||
|
dynamic_programming_cycle_path(pcb_data, assigned_placement, feeder_slot_result[cycle_set])[1])
|
||||||
|
|
||||||
|
return placement_result, head_sequence_result
|
||||||
|
|
||||||
|
|
||||||
|
@timer_wrapper
|
||||||
|
def beam_search_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result):
|
||||||
|
beam_width = 10 # 集束宽度
|
||||||
|
base_points = [float('inf'), float('inf')]
|
||||||
|
|
||||||
|
mount_point_index = [[] for _ in range(len(component_data))]
|
||||||
|
mount_point_pos = [[] for _ in range(len(component_data))]
|
||||||
|
|
||||||
|
for idx, data in pcb_data.iterrows():
|
||||||
|
component_index = component_data[component_data['part'] == data.part].index.tolist()[0]
|
||||||
|
|
||||||
|
# 记录贴装点序号索引和对应的位置坐标
|
||||||
|
mount_point_index[component_index].append(idx)
|
||||||
|
mount_point_pos[component_index].append([data.x, data.y])
|
||||||
|
|
||||||
|
# 记录最左下角坐标
|
||||||
|
if mount_point_pos[component_index][-1][0] < base_points[0]:
|
||||||
|
base_points[0] = mount_point_pos[component_index][-1][0]
|
||||||
|
if mount_point_pos[component_index][-1][1] < base_points[1]:
|
||||||
|
base_points[1] = mount_point_pos[component_index][-1][1]
|
||||||
|
|
||||||
|
beam_placement_sequence, beam_head_sequence = [], []
|
||||||
|
beam_mount_point_index, beam_mount_point_pos = [], []
|
||||||
|
|
||||||
|
for beam_counter in range(beam_width):
|
||||||
|
beam_mount_point_index.append(copy.deepcopy(mount_point_index))
|
||||||
|
beam_mount_point_pos.append(copy.deepcopy(mount_point_pos))
|
||||||
|
|
||||||
|
beam_placement_sequence.append([])
|
||||||
|
beam_head_sequence.append([])
|
||||||
|
|
||||||
|
beam_distance = [0 for _ in range(beam_width)] # 记录当前集束搜索点的点数
|
||||||
|
|
||||||
|
def argpartition(list, kth):
|
||||||
|
if kth < len(list):
|
||||||
|
return np.argpartition(list, kth)
|
||||||
|
else:
|
||||||
|
index, indexes = 0, []
|
||||||
|
while len(indexes) < kth:
|
||||||
|
indexes.append(index)
|
||||||
|
index += 1
|
||||||
|
if index >= len(list):
|
||||||
|
index = 0
|
||||||
|
return np.array(indexes)
|
||||||
|
|
||||||
|
with tqdm(total=100) as pbar:
|
||||||
|
search_dir = 0
|
||||||
|
pbar.set_description('beam search route schedule')
|
||||||
|
for cycle_set in range(len(component_result)):
|
||||||
|
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
|
||||||
|
for cycle in range(floor_cycle, ceil_cycle):
|
||||||
|
search_dir = 1 - search_dir
|
||||||
|
beam_way_point = None
|
||||||
|
for beam_counter in range(beam_width):
|
||||||
|
beam_placement_sequence[beam_counter].append([-1 for _ in range(max_head_index)])
|
||||||
|
|
||||||
|
head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index)
|
||||||
|
for head in head_range:
|
||||||
|
component_index = component_result[cycle_set][head]
|
||||||
|
if component_index == -1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if beam_way_point is None:
|
||||||
|
# 首个贴装点的选取,距离基准点最近的beam_width个点
|
||||||
|
beam_way_point = [[0, 0]] * beam_width
|
||||||
|
|
||||||
|
for beam_counter in range(beam_width):
|
||||||
|
if search_dir:
|
||||||
|
index = np.argmax(beam_mount_point_pos[beam_counter][component_index], axis=0)[0]
|
||||||
|
else:
|
||||||
|
index = np.argmin(beam_mount_point_pos[beam_counter][component_index], axis=0)[0]
|
||||||
|
|
||||||
|
beam_placement_sequence[beam_counter][-1][head] = \
|
||||||
|
beam_mount_point_index[beam_counter][component_index][index]
|
||||||
|
|
||||||
|
beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][index]
|
||||||
|
beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \
|
||||||
|
search_dir else -head * head_interval
|
||||||
|
|
||||||
|
beam_mount_point_index[beam_counter][component_index].pop(index)
|
||||||
|
beam_mount_point_pos[beam_counter][component_index].pop(index)
|
||||||
|
else:
|
||||||
|
# 后续贴装点
|
||||||
|
search_beam_distance = []
|
||||||
|
search_beam_component_index = [0] * (beam_width ** 2)
|
||||||
|
for beam_counter in range(beam_width ** 2):
|
||||||
|
search_beam_distance.append(beam_distance[beam_counter // beam_width])
|
||||||
|
|
||||||
|
for beam_counter in range(beam_width):
|
||||||
|
# 对于集束beam_counter + 1最近的beam_width个点
|
||||||
|
num_points = len(beam_mount_point_pos[beam_counter][component_index])
|
||||||
|
|
||||||
|
dist = []
|
||||||
|
for i in range(num_points):
|
||||||
|
if search_dir:
|
||||||
|
delta_x = axis_moving_time(
|
||||||
|
beam_mount_point_pos[beam_counter][component_index][i][0] -
|
||||||
|
beam_way_point[beam_counter][0] + (max_head_index - head - 1) * head_interval,
|
||||||
|
0)
|
||||||
|
else:
|
||||||
|
delta_x = axis_moving_time(
|
||||||
|
beam_mount_point_pos[beam_counter][component_index][i][0] -
|
||||||
|
beam_way_point[beam_counter][0] - head * head_interval, 0)
|
||||||
|
|
||||||
|
delta_y = axis_moving_time(beam_mount_point_pos[beam_counter][component_index][i][1] -
|
||||||
|
beam_way_point[beam_counter][1], 1)
|
||||||
|
|
||||||
|
dist.append(max(delta_x, delta_y))
|
||||||
|
|
||||||
|
indexes = argpartition(dist, kth=beam_width)[:beam_width]
|
||||||
|
|
||||||
|
# 记录中间信息
|
||||||
|
for i, index in enumerate(indexes):
|
||||||
|
search_beam_distance[i + beam_counter * beam_width] += dist[index]
|
||||||
|
search_beam_component_index[i + beam_counter * beam_width] = index
|
||||||
|
|
||||||
|
indexes = np.argsort(search_beam_distance)
|
||||||
|
|
||||||
|
beam_mount_point_pos_cpy = copy.deepcopy(beam_mount_point_pos)
|
||||||
|
beam_mount_point_index_cpy = copy.deepcopy(beam_mount_point_index)
|
||||||
|
|
||||||
|
beam_placement_sequence_cpy = copy.deepcopy(beam_placement_sequence)
|
||||||
|
beam_head_sequence_cpy = copy.deepcopy(beam_head_sequence)
|
||||||
|
beam_counter = 0
|
||||||
|
assigned_placement = []
|
||||||
|
|
||||||
|
for i, index in enumerate(indexes):
|
||||||
|
# 拷贝原始集束数据
|
||||||
|
beam_mount_point_pos[beam_counter] = copy.deepcopy(beam_mount_point_pos_cpy[index // beam_width])
|
||||||
|
beam_mount_point_index[beam_counter] = copy.deepcopy(beam_mount_point_index_cpy[index // beam_width])
|
||||||
|
beam_placement_sequence[beam_counter] = copy.deepcopy(beam_placement_sequence_cpy[index // beam_width])
|
||||||
|
beam_head_sequence[beam_counter] = copy.deepcopy(beam_head_sequence_cpy[index // beam_width])
|
||||||
|
|
||||||
|
# 更新各集束最新扫描的的贴装点
|
||||||
|
component_index = component_result[cycle_set][head]
|
||||||
|
|
||||||
|
beam_placement_sequence[beam_counter][-1][head] = \
|
||||||
|
beam_mount_point_index[beam_counter][component_index][search_beam_component_index[index]]
|
||||||
|
|
||||||
|
if beam_placement_sequence[beam_counter][-1] in assigned_placement \
|
||||||
|
and beam_width - beam_counter < len(indexes) - i:
|
||||||
|
continue
|
||||||
|
|
||||||
|
assigned_placement.append(beam_placement_sequence[beam_counter][-1])
|
||||||
|
|
||||||
|
# 更新参考基准点
|
||||||
|
beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][
|
||||||
|
search_beam_component_index[index]]
|
||||||
|
beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \
|
||||||
|
search_dir else -head * head_interval
|
||||||
|
|
||||||
|
# 更新各集束贴装路径长度,移除各集束已分配的贴装点
|
||||||
|
beam_distance[beam_counter] = search_beam_distance[index]
|
||||||
|
|
||||||
|
beam_mount_point_pos[beam_counter][component_index].pop(search_beam_component_index[index])
|
||||||
|
beam_mount_point_index[beam_counter][component_index].pop(search_beam_component_index[index])
|
||||||
|
|
||||||
|
beam_counter += 1
|
||||||
|
|
||||||
|
if beam_counter >= beam_width:
|
||||||
|
break
|
||||||
|
assert beam_counter >= beam_width
|
||||||
|
|
||||||
|
# 更新头贴装顺序
|
||||||
|
for beam_counter in range(beam_width):
|
||||||
|
beam_head_sequence[beam_counter].append(
|
||||||
|
dynamic_programming_cycle_path(pcb_data, beam_placement_sequence[beam_counter][-1],
|
||||||
|
feeder_slot_result[cycle_set])[1])
|
||||||
|
|
||||||
|
pbar.update(1 / sum(cycle_result) * 100)
|
||||||
|
|
||||||
|
index = np.argmin(beam_distance)
|
||||||
|
print('beam distance : ', beam_distance[index])
|
||||||
|
return beam_placement_sequence[index], beam_head_sequence[index]
|
||||||
|
|
||||||
|
|
||||||
|
def scan_based_placement_route_generation(component_data, pcb_data, component_assign, cycle_assign, feeder_slot_result):
|
||||||
|
placement_result, head_sequence_result = [], []
|
||||||
|
|
||||||
|
mount_point_pos, mount_point_index, mount_point_angle, mount_point_part = [], [], [], []
|
||||||
|
for i, data in pcb_data.iterrows():
|
||||||
|
component_index = component_data[component_data.part == data.part].index.tolist()[0]
|
||||||
|
# 记录贴装点序号索引和对应的位置坐标
|
||||||
|
mount_point_index.append(i)
|
||||||
|
mount_point_pos.append([data.x + stopper_pos[0], data.y + stopper_pos[1]])
|
||||||
|
mount_point_angle.append(data.r)
|
||||||
|
|
||||||
|
mount_point_part.append(component_index)
|
||||||
|
|
||||||
|
lBoundary, rBoundary = min(mount_point_pos, key=lambda x: x[0])[0], max(mount_point_pos, key=lambda x: x[0])[0]
|
||||||
|
search_step = max((rBoundary - lBoundary) / max_head_index / 2, 0)
|
||||||
|
|
||||||
|
ref_pos_y = min(mount_point_pos, key=lambda x: x[1])[1]
|
||||||
|
for cycle_index, component_cycle in enumerate(component_assign):
|
||||||
|
for _ in range(cycle_assign[cycle_index]):
|
||||||
|
min_dist = None
|
||||||
|
tmp_assigned_placement, tmp_assigned_head_seq = [], []
|
||||||
|
tmp_mount_point_pos, tmp_mount_point_index = [], []
|
||||||
|
for search_dir in range(3): # 不同的搜索方向,贴装头和起始点的选取方法各不相同
|
||||||
|
if search_dir == 0:
|
||||||
|
# 从左向右搜索
|
||||||
|
searchPoints = np.arange(lBoundary, (lBoundary + rBoundary) / 2, search_step)
|
||||||
|
head_range = list(range(max_head_index))
|
||||||
|
elif search_dir == 1:
|
||||||
|
# 从右向左搜索
|
||||||
|
searchPoints = np.arange(rBoundary + 1e-3, (lBoundary + rBoundary) / 2, -search_step)
|
||||||
|
head_range = list(range(max_head_index - 1, -1, -1))
|
||||||
|
else:
|
||||||
|
# 从中间向两边搜索
|
||||||
|
searchPoints = np.arange(lBoundary, rBoundary, search_step / 2)
|
||||||
|
head_range, head_index = [], (max_head_index - 1) // 2
|
||||||
|
while head_index >= 0:
|
||||||
|
if 2 * head_index != max_head_index - 1:
|
||||||
|
head_range.append(max_head_index - 1 - head_index)
|
||||||
|
head_range.append(head_index)
|
||||||
|
head_index -= 1
|
||||||
|
|
||||||
|
for startPoint in searchPoints:
|
||||||
|
mount_point_pos_cpy, mount_point_index_cpy = copy.deepcopy(mount_point_pos), copy.deepcopy(
|
||||||
|
mount_point_index)
|
||||||
|
mount_point_angle_cpy = copy.deepcopy(mount_point_angle)
|
||||||
|
|
||||||
|
assigned_placement = [-1] * max_head_index
|
||||||
|
assigned_mount_point = [[0, 0]] * max_head_index
|
||||||
|
assigned_mount_angle = [0] * max_head_index
|
||||||
|
head_counter, point_index = 0, -1
|
||||||
|
for head_index in head_range:
|
||||||
|
if head_counter == 0:
|
||||||
|
component_index = component_assign[cycle_index][head_index]
|
||||||
|
|
||||||
|
if component_index == -1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
min_horizontal_distance = None
|
||||||
|
for index, mount_index in enumerate(mount_point_index_cpy):
|
||||||
|
if mount_point_part[mount_index] != component_index:
|
||||||
|
continue
|
||||||
|
horizontal_distance = abs(mount_point_pos_cpy[index][0] - startPoint) + 1e-3 * abs(
|
||||||
|
mount_point_pos_cpy[index][1] - ref_pos_y)
|
||||||
|
|
||||||
|
if min_horizontal_distance is None or horizontal_distance < min_horizontal_distance:
|
||||||
|
min_horizontal_distance = horizontal_distance
|
||||||
|
point_index = index
|
||||||
|
else:
|
||||||
|
point_index = -1
|
||||||
|
min_cheby_distance = None
|
||||||
|
|
||||||
|
next_comp_index = component_assign[cycle_index][head_index]
|
||||||
|
if assigned_placement[head_index] != -1 or next_comp_index == -1:
|
||||||
|
continue
|
||||||
|
for index, mount_index in enumerate(mount_point_index_cpy):
|
||||||
|
if mount_point_part[mount_index] != next_comp_index:
|
||||||
|
continue
|
||||||
|
|
||||||
|
point_pos = [[mount_point_pos_cpy[index][0] - head_index * head_interval,
|
||||||
|
mount_point_pos_cpy[index][1]]]
|
||||||
|
|
||||||
|
cheby_distance, euler_distance = 0, 0
|
||||||
|
for next_head in range(max_head_index):
|
||||||
|
if assigned_placement[next_head] == -1:
|
||||||
|
continue
|
||||||
|
point_pos.append(assigned_mount_point[next_head].copy())
|
||||||
|
point_pos[-1][0] -= next_head * head_interval
|
||||||
|
|
||||||
|
point_pos = sorted(point_pos, key=lambda x: x[0])
|
||||||
|
for mount_seq in range(len(point_pos) - 1):
|
||||||
|
cheby_distance += max(abs(point_pos[mount_seq][0] - point_pos[mount_seq + 1][0]),
|
||||||
|
abs(point_pos[mount_seq][1] - point_pos[mount_seq + 1][1]))
|
||||||
|
euler_distance += math.sqrt(
|
||||||
|
(point_pos[mount_seq][0] - point_pos[mount_seq + 1][0]) ** 2 + (
|
||||||
|
point_pos[mount_seq][1] - point_pos[mount_seq + 1][1]) ** 2)
|
||||||
|
|
||||||
|
cheby_distance += 0.01 * euler_distance
|
||||||
|
if min_cheby_distance is None or cheby_distance < min_cheby_distance:
|
||||||
|
min_cheby_distance, min_euler_distance = cheby_distance, euler_distance
|
||||||
|
point_index = index
|
||||||
|
|
||||||
|
if point_index == -1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
head_counter += 1
|
||||||
|
|
||||||
|
assigned_placement[head_index] = mount_point_index_cpy[point_index]
|
||||||
|
assigned_mount_point[head_index] = mount_point_pos_cpy[point_index].copy()
|
||||||
|
assigned_mount_angle[head_index] = mount_point_angle_cpy[point_index]
|
||||||
|
|
||||||
|
mount_point_index_cpy.pop(point_index)
|
||||||
|
mount_point_pos_cpy.pop(point_index)
|
||||||
|
mount_point_angle_cpy.pop(point_index)
|
||||||
|
|
||||||
|
dist, head_seq = dynamic_programming_cycle_path(pcb_data, assigned_placement,
|
||||||
|
feeder_slot_result[cycle_index])
|
||||||
|
|
||||||
|
if min_dist is None or dist < min_dist:
|
||||||
|
tmp_mount_point_pos, tmp_mount_point_index = mount_point_pos_cpy, mount_point_index_cpy
|
||||||
|
tmp_assigned_placement, tmp_assigned_head_seq = assigned_placement, head_seq
|
||||||
|
min_dist = dist
|
||||||
|
|
||||||
|
mount_point_pos, mount_point_index = tmp_mount_point_pos, tmp_mount_point_index
|
||||||
|
|
||||||
|
placement_result.append(tmp_assigned_placement)
|
||||||
|
head_sequence_result.append(tmp_assigned_head_seq)
|
||||||
|
|
||||||
|
return placement_result, head_sequence_result
|
||||||
|
# return placement_route_relink_heuristic(component_data, pcb_data, placement_result, head_sequence_result,
|
||||||
|
# feeder_slot_result, cycle_assign)
|
||||||
|
|
||||||
|
|
||||||
|
def placement_route_relink_heuristic(component_data, pcb_data, placement_result, head_sequence_result,
|
||||||
|
feeder_slot_result, cycle_result, hinter=True):
|
||||||
|
cycle_group_index = defaultdict(int)
|
||||||
|
cycle_index = 0
|
||||||
|
for cycle_group, group in enumerate(cycle_result):
|
||||||
|
for _ in range(group):
|
||||||
|
cycle_group_index[cycle_index] = cycle_group
|
||||||
|
cycle_index += 1
|
||||||
|
|
||||||
|
mount_point_pos, mount_point_angle, mount_point_index, mount_point_part = [], [], [], []
|
||||||
|
for i, data in pcb_data.iterrows():
|
||||||
|
component_index = component_data[component_data.part == data.part].index.tolist()[0]
|
||||||
|
# 记录贴装点序号索引和对应的位置坐标
|
||||||
|
mount_point_index.append(i)
|
||||||
|
mount_point_pos.append([data.x + stopper_pos[0], data.y + stopper_pos[1]])
|
||||||
|
mount_point_angle.append(data.r)
|
||||||
|
|
||||||
|
mount_point_part.append(component_index)
|
||||||
|
|
||||||
|
cycle_length, cycle_average_pos = [], []
|
||||||
|
for cycle, placement in enumerate(placement_result):
|
||||||
|
# prev_pos, prev_angle = None, None
|
||||||
|
cycle_pos_list = []
|
||||||
|
for idx, head in enumerate(head_sequence_result[cycle]):
|
||||||
|
if point_index := placement[head] == -1:
|
||||||
|
continue
|
||||||
|
cycle_pos_list.append(
|
||||||
|
[mount_point_pos[point_index][0] - head * head_interval, mount_point_pos[point_index][1]])
|
||||||
|
|
||||||
|
cycle_average_pos.append([sum(map(lambda pos: pos[0], cycle_pos_list)) / len(cycle_pos_list),
|
||||||
|
sum(map(lambda pos: pos[1], cycle_pos_list)) / len(cycle_pos_list)])
|
||||||
|
cycle_length.append(
|
||||||
|
dynamic_programming_cycle_path(pcb_data, placement, feeder_slot_result[cycle_group_index[cycle]])[0])
|
||||||
|
|
||||||
|
best_placement_result, best_head_sequence_result = copy.deepcopy(placement_result), copy.deepcopy(
|
||||||
|
head_sequence_result)
|
||||||
|
|
||||||
|
best_cycle_length, best_cycle_average_pos = copy.deepcopy(cycle_length), copy.deepcopy(cycle_average_pos)
|
||||||
|
|
||||||
|
n_runningtime, n_iteration = 30, 0
|
||||||
|
start_time = time.time()
|
||||||
|
with tqdm(total=n_runningtime) as pbar:
|
||||||
|
pbar.set_description('swap heuristic process')
|
||||||
|
prev_time = start_time
|
||||||
|
while True:
|
||||||
|
n_iteration += 1
|
||||||
|
placement_result, head_sequence_result = copy.deepcopy(best_placement_result), copy.deepcopy(
|
||||||
|
best_head_sequence_result)
|
||||||
|
cycle_length = best_cycle_length.copy()
|
||||||
|
cycle_average_pos = copy.deepcopy(best_cycle_average_pos)
|
||||||
|
|
||||||
|
cycle_index = roulette_wheel_selection(cycle_length) # 根据周期加权移动距离随机选择周期
|
||||||
|
|
||||||
|
point_dist = [] # 周期内各贴装点距离中心位置的切氏距离
|
||||||
|
for head in head_sequence_result[cycle_index]:
|
||||||
|
point_index = placement_result[cycle_index][head]
|
||||||
|
_delta_x = abs(mount_point_pos[point_index][0] - head * head_interval - cycle_average_pos[cycle_index][0])
|
||||||
|
_delta_y = abs(mount_point_pos[point_index][1] - cycle_average_pos[cycle_index][1])
|
||||||
|
point_dist.append(max(_delta_x, _delta_y))
|
||||||
|
|
||||||
|
# 随机选择一个异常点
|
||||||
|
head_index = head_sequence_result[cycle_index][roulette_wheel_selection(point_dist)]
|
||||||
|
point_index = placement_result[cycle_index][head_index]
|
||||||
|
|
||||||
|
# 找距离该异常点最近的周期
|
||||||
|
min_dist = None
|
||||||
|
chg_cycle_index = -1
|
||||||
|
for idx in range(len(cycle_average_pos)):
|
||||||
|
if idx == cycle_index:
|
||||||
|
continue
|
||||||
|
dist_ = 0
|
||||||
|
component_type_check = False
|
||||||
|
for head in head_sequence_result[idx]:
|
||||||
|
dist_ += max(abs(mount_point_pos[placement_result[idx][head]][0] - mount_point_pos[point_index][0]),
|
||||||
|
abs(mount_point_pos[placement_result[idx][head]][1] - mount_point_pos[point_index][1]))
|
||||||
|
if mount_point_part[placement_result[idx][head]] == mount_point_part[point_index]:
|
||||||
|
component_type_check = True
|
||||||
|
|
||||||
|
if (min_dist is None or dist_ < min_dist) and component_type_check:
|
||||||
|
min_dist = dist_
|
||||||
|
chg_cycle_index = idx
|
||||||
|
|
||||||
|
if chg_cycle_index == -1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
chg_head, min_chg_dist = None, None
|
||||||
|
chg_cycle_point = []
|
||||||
|
for head in head_sequence_result[chg_cycle_index]:
|
||||||
|
index = placement_result[chg_cycle_index][head]
|
||||||
|
chg_cycle_point.append([mount_point_pos[index][0] - head * head_interval, mount_point_pos[index][1]])
|
||||||
|
|
||||||
|
for idx, head in enumerate(head_sequence_result[chg_cycle_index]):
|
||||||
|
chg_cycle_point_cpy = copy.deepcopy(chg_cycle_point)
|
||||||
|
index = placement_result[chg_cycle_index][head]
|
||||||
|
if mount_point_part[index] != mount_point_part[point_index]:
|
||||||
|
continue
|
||||||
|
chg_cycle_point_cpy[idx][0] = (mount_point_pos[index][0]) - head * head_interval
|
||||||
|
|
||||||
|
chg_dist = 0
|
||||||
|
aver_chg_pos = [sum(map(lambda x: x[0], chg_cycle_point_cpy)) / len(chg_cycle_point_cpy),
|
||||||
|
sum(map(lambda x: x[1], chg_cycle_point_cpy)) / len(chg_cycle_point_cpy)]
|
||||||
|
|
||||||
|
for pos in chg_cycle_point_cpy:
|
||||||
|
chg_dist += max(abs(aver_chg_pos[0] - pos[0]), abs(aver_chg_pos[1] - pos[1]))
|
||||||
|
|
||||||
|
# 更换后各点距离中心更近
|
||||||
|
if min_chg_dist is None or chg_dist < min_chg_dist:
|
||||||
|
chg_head = head
|
||||||
|
min_chg_dist = chg_dist
|
||||||
|
|
||||||
|
if chg_head is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# === 第一轮,变更周期chg_cycle_index的贴装点重排 ===
|
||||||
|
chg_placement_res = placement_result[chg_cycle_index].copy()
|
||||||
|
chg_placement_res[chg_head] = point_index
|
||||||
|
|
||||||
|
cycle_point_list = defaultdict(list)
|
||||||
|
for head, point in enumerate(chg_placement_res):
|
||||||
|
if point == -1:
|
||||||
|
continue
|
||||||
|
cycle_point_list[mount_point_part[point]].append(point)
|
||||||
|
|
||||||
|
for key, point_list in cycle_point_list.items():
|
||||||
|
cycle_point_list[key] = sorted(point_list, key=lambda p: mount_point_pos[p][0])
|
||||||
|
|
||||||
|
chg_placement_res = []
|
||||||
|
for head, point_index in enumerate(placement_result[chg_cycle_index]):
|
||||||
|
if point_index == -1:
|
||||||
|
chg_placement_res.append(-1)
|
||||||
|
else:
|
||||||
|
part = mount_point_part[point_index]
|
||||||
|
chg_placement_res.append(cycle_point_list[part][0])
|
||||||
|
cycle_point_list[part].pop(0)
|
||||||
|
|
||||||
|
chg_place_moving, chg_head_res = dynamic_programming_cycle_path(pcb_data, chg_placement_res,
|
||||||
|
feeder_slot_result[
|
||||||
|
cycle_group_index[chg_cycle_index]])
|
||||||
|
|
||||||
|
# === 第二轮,原始周期cycle_index的贴装点重排 ===
|
||||||
|
placement_res = placement_result[cycle_index].copy()
|
||||||
|
placement_res[head_index] = placement_result[chg_cycle_index][chg_head]
|
||||||
|
|
||||||
|
for point in placement_res:
|
||||||
|
if point == -1:
|
||||||
|
continue
|
||||||
|
cycle_point_list[mount_point_part[point]].append(point)
|
||||||
|
|
||||||
|
for key, point_list in cycle_point_list.items():
|
||||||
|
cycle_point_list[key] = sorted(point_list, key=lambda p: mount_point_pos[p][0])
|
||||||
|
|
||||||
|
placement_res = []
|
||||||
|
for head, point_index in enumerate(placement_result[cycle_index]):
|
||||||
|
if point_index == -1:
|
||||||
|
placement_res.append(-1)
|
||||||
|
else:
|
||||||
|
part = mount_point_part[point_index]
|
||||||
|
placement_res.append(cycle_point_list[part][0])
|
||||||
|
cycle_point_list[part].pop(0)
|
||||||
|
|
||||||
|
place_moving, place_head_res = dynamic_programming_cycle_path(pcb_data, placement_res, feeder_slot_result[
|
||||||
|
cycle_group_index[cycle_index]])
|
||||||
|
|
||||||
|
# 更新贴装顺序分配结果
|
||||||
|
placement_result[cycle_index], head_sequence_result[cycle_index] = placement_res, place_head_res
|
||||||
|
placement_result[chg_cycle_index], head_sequence_result[chg_cycle_index] = chg_placement_res, chg_head_res
|
||||||
|
|
||||||
|
# 更新移动路径
|
||||||
|
cycle_length[cycle_index], cycle_length[chg_cycle_index] = place_moving, chg_place_moving
|
||||||
|
|
||||||
|
# 更新平均坐标和最大偏离点索引
|
||||||
|
point_list, point_index_list = [], []
|
||||||
|
for head in head_sequence_result[cycle_index]:
|
||||||
|
point_index_list.append(placement_result[cycle_index][head])
|
||||||
|
point_pos = mount_point_pos[point_index_list[-1]].copy()
|
||||||
|
point_pos[0] -= head * head_interval
|
||||||
|
point_list.append(point_pos)
|
||||||
|
|
||||||
|
cycle_average_pos[cycle_index] = [sum(map(lambda x: x[0], point_list)) / len(point_list),
|
||||||
|
sum(map(lambda x: x[1], point_list)) / len(point_list)]
|
||||||
|
|
||||||
|
point_list, point_index_list = [], []
|
||||||
|
for head in head_sequence_result[chg_cycle_index]:
|
||||||
|
point_index_list.append(placement_result[chg_cycle_index][head])
|
||||||
|
point_pos = mount_point_pos[point_index_list[-1]].copy()
|
||||||
|
point_pos[0] -= head * head_interval
|
||||||
|
point_list.append(point_pos)
|
||||||
|
|
||||||
|
cycle_average_pos[chg_cycle_index] = [sum(map(lambda x: x[0], point_list)) / len(point_list),
|
||||||
|
sum(map(lambda x: x[1], point_list)) / len(point_list)]
|
||||||
|
test1 = sum(cycle_length)
|
||||||
|
test2 = sum(best_cycle_length)
|
||||||
|
if sum(cycle_length) < sum(best_cycle_length):
|
||||||
|
best_cycle_length = cycle_length.copy()
|
||||||
|
best_cycle_average_pos = copy.deepcopy(cycle_average_pos)
|
||||||
|
best_placement_result, best_head_sequence_result = copy.deepcopy(placement_result), copy.deepcopy(
|
||||||
|
head_sequence_result)
|
||||||
|
|
||||||
|
cur_time = time.time()
|
||||||
|
if cur_time - start_time > n_runningtime:
|
||||||
|
break
|
||||||
|
|
||||||
|
pbar.update(cur_time - prev_time)
|
||||||
|
prev_time = cur_time
|
||||||
|
|
||||||
|
# print("number of iteration: ", n_iteration)
|
||||||
|
return best_placement_result, best_head_sequence_result
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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():
|
||||||
@ -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)
|
||||||
@ -167,7 +173,7 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True):
|
|||||||
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)
|
||||||
|
64
optimizer.py
64
optimizer.py
@ -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']
|
||||||
|
Reference in New Issue
Block a user