修改文件名属性

This commit is contained in:
2024-06-05 22:10:21 +08:00
parent 7c9a900b95
commit cbeba48da0
21 changed files with 1466 additions and 839 deletions

View File

@ -3,6 +3,7 @@ from collections import defaultdict
from tqdm import tqdm
from gurobipy import *
from sklearn.linear_model import LinearRegression
from sklearn.svm import SVR
import os
import time
@ -14,9 +15,13 @@ import argparse
import joblib
import pickle
import warnings
import heapq
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('TkAgg')
# 机器参数
max_head_index, max_slot_index = 6, 120
@ -29,7 +34,7 @@ head_nozzle = ['' for _ in range(max_head_index)] # 头上已经分配吸嘴
slotf1_pos, slotr1_pos = [-31.267, 44.], [807., 810.545] # F1(前基座最左侧)、R1(后基座最右侧)位置
fix_camera_pos = [269.531, 694.823] # 固定相机位置
anc_marker_pos = [336.457, 626.230] # ANC基准点位置
stopper_pos = [535.150, 124.738] # 止档块位置
stopper_pos = [665.150, 124.738] # 止档块位置
# 算法权重参数
e_nz_change, e_gang_pick = 4, 0.6
@ -39,12 +44,14 @@ head_rotary_velocity = 8e-5 # 贴装头R轴旋转时间
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
# 不同种类供料器宽度
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)}
# TODO: 不同种类供料器宽度
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)}
# 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)}
# 可用吸嘴数量限制
nozzle_limit = {'CN065': 6, 'CN040': 6, 'CN220': 6, 'CN400': 6, 'CN140': 6}
nozzle_limit = {'CN065': 6, 'CN040': 6, 'CN020': 6, 'CN400': 6, 'CN140': 6}
# 时间参数
t_cycle = 0.3
@ -61,21 +68,31 @@ T_pp, T_tr, T_nc, T_pl = 2, 5, 25, 0
class OptInfo:
def __init__(self):
self.total_time = .0 # 总组装时间
self.total_points = .0 # 总贴装点数
self.total_points = 0 # 总贴装点数
self.total_components = 0 # 总元件数
self.pickup_time = .0 # 拾取过程运动时间
self.round_time = .0 # 往返基座/基板运动时间
self.place_time = .0 # 贴装过程运动时间
self.operation_time = .0 # 拾取/贴装/换吸嘴等机械动作用时
self.cycle_counter = 0 # 周期数
self.nozzle_change_counter = 0 # 吸嘴更换次数
self.anc_round_counter = 0 # 前往ANC次数
self.pickup_counter = 0 # 拾取次数
self.cycle_counter = 0 # 周期数
self.nozzle_change_counter = 0 # 吸嘴更换次数
self.anc_round_counter = 0 # 前往ANC次数
self.pickup_counter = 0 # 拾取次数
self.total_distance = .0 # 总移动路径
self.place_distance = .0 # 贴装移动路径
self.pickup_distance = .0 # 拾取移动路径
self.total_distance = .0 # 总移动路径
self.place_distance = .0 # 贴装移动路径
self.pickup_distance = .0 # 拾取移动路径
def print(self):
print('-Cycle counter: {}'.format(self.cycle_counter))
print(f'-Nozzle change counter: {self.nozzle_change_counter: d}')
print(f'-ANC round: {self.anc_round_counter: d}')
print(f'-Pick operation counter: {self.pickup_counter: d}')
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}')
def axis_moving_time(distance, axis=0):
@ -145,12 +162,13 @@ def feeder_assignment(component_data, pcb_data, component_result, cycle_result):
feeder_slot_result, feeder_group_result = [], []
feeder_limit = defaultdict(int)
for component in range(len(component_data)):
feeder_limit[component] = component_data.loc[component]['feeder-limit']
feeder_limit[component] = component_data.loc[component].fdn
for component_cycle in component_result:
new_feeder_group = []
for component in component_cycle:
if component == -1 or feeder_limit[component] == 0 or new_feeder_group.count(component) >= feeder_limit[component]:
if component == -1 or feeder_limit[component] == 0 or new_feeder_group.count(component) >= feeder_limit[
component]:
new_feeder_group.append(-1)
else:
new_feeder_group.append(component)
@ -401,8 +419,11 @@ def dynamic_programming_cycle_path(pcb_data, cycle_placement, assigned_feeder):
@timer_wrapper
def greedy_placement_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result, hinter=True):
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))]
@ -1037,7 +1058,7 @@ def convert_line_assigment(pcb_data, component_data, assignment_result):
# === averagely assign available feeder ===
for part_index, data in component_data.iterrows():
feeder_limit = data['feeder-limit']
feeder_limit = data.fdn
feeder_points = [assignment_result[machine_index][part_index] for machine_index in range(machine_number)]
for machine_index in range(machine_number):
@ -1047,23 +1068,30 @@ def convert_line_assigment(pcb_data, component_data, assignment_result):
if feeder_points[machine_index] == 0:
continue
arg_feeder = max(math.floor(feeder_points[machine_index] / sum(feeder_points) * data['feeder-limit']), 1)
partial_component_data[machine_index].loc[part_index, 'feeder-limit'] = arg_feeder
feeder_limit -= arg_feeder
for machine_index in range(machine_number):
if feeder_limit <= 0:
break
if feeder_points[machine_index] == 0:
continue
partial_component_data[machine_index].loc[part_index, 'feeder-limit'] += 1
partial_component_data[machine_index].loc[part_index].fdn = 1
feeder_limit -= 1
while feeder_limit:
assign_machine = None
for machine_index in range(machine_number):
if feeder_limit <= 0:
break
if feeder_points[machine_index] == 0:
continue
if assign_machine is None or feeder_points[machine_index] / \
partial_component_data[machine_index].loc[part_index].fdn > feeder_points[
assign_machine] / partial_component_data[assign_machine].loc[part_index].fdn:
assign_machine = machine_index
partial_component_data[assign_machine].loc[part_index, 'fdn'] += 1
feeder_limit -= 1
assert assign_machine is not None
for machine_index in range(machine_number):
if feeder_points[machine_index] > 0:
assert partial_component_data[machine_index].loc[part_index, 'feeder-limit'] > 0
assert partial_component_data[machine_index].loc[part_index].fdn > 0
# === assign placements ===
part2idx = defaultdict(int)
@ -1127,57 +1155,23 @@ def convert_line_assigment(pcb_data, component_data, assignment_result):
partial_component_data[idx].loc[part_index, 'points'] += 1
partial_pcb_data[idx] = pd.concat([partial_pcb_data[idx], pd.DataFrame(data).T])
# === adjust the number of available feeders for single optimization separately ===
# for machine_index, data in partial_pcb_data.items():
# part_info = [] # part info list(part index, part points, available feeder-num, upper feeder-num)
# for part_index, cp_data in partial_component_data[machine_index].iterrows():
# if assignment_result[machine_index][part_index]:
# part_info.append(
# [part_index, assignment_result[machine_index][part_index], 1, cp_data['feeder-limit']])
#
# part_info = sorted(part_info, key=lambda x: x[1], reverse=True)
# start_index, end_index = 0, min(max_head_index - 1, len(part_info) - 1)
# while start_index < len(part_info):
# assign_part_point, assign_part_index = [], []
# for idx_ in range(start_index, end_index + 1):
# for _ in range(part_info[idx_][2]):
# assign_part_point.append(part_info[idx_][1] / part_info[idx_][2])
# assign_part_index.append(idx_)
#
# variance = np.std(assign_part_point)
# while start_index <= end_index:
# part_info_index = assign_part_index[np.argmax(assign_part_point)]
#
# if part_info[part_info_index][2] < part_info[part_info_index][3]: # 供料器数目上限的限制
# part_info[part_info_index][2] += 1
# end_index -= 1
#
# new_assign_part_point, new_assign_part_index = [], []
# for idx_ in range(start_index, end_index + 1):
# for _ in range(part_info[idx_][2]):
# new_assign_part_point.append(part_info[idx_][1] / part_info[idx_][2])
# new_assign_part_index.append(idx_)
#
# new_variance = np.std(new_assign_part_point)
# if variance < new_variance:
# part_info[part_info_index][2] -= 1
# end_index += 1
# break
#
# variance = new_variance
# assign_part_index, assign_part_point = new_assign_part_index.copy(), new_assign_part_point.copy()
# else:
# break
#
# start_index = end_index + 1
# end_index = min(start_index + max_head_index - 1, len(part_info) - 1)
#
# max_avl_feeder = max(part_info, key=lambda x: x[2])[2]
# for info in part_info:
# partial_component_data[machine_index].loc[info[0], 'feeder-limit'] = math.ceil(info[2] / max_avl_feeder)
for machine_index in range(machine_number):
partial_component_data[machine_index] = partial_component_data[machine_index][
partial_component_data[machine_index]['points'] != 0].reset_index(drop=True)
return partial_pcb_data, partial_component_data
def random_division(num, div):
assert num >= div
res = [1 for _ in range(num)]
while sum(res) < num:
pos = random.randint(0, num - 1)
val = random.randint(1, num - sum(res))
res[pos] = val
return res
def list_range(start, end=None):
return list(range(start)) if end is None else list(range(start, end))

View File

@ -1,11 +1,11 @@
# 用于提供对外接口
from base_optimizer.optimizer_scanbased import *
from base_optimizer.optimizer_celldivision import *
from base_optimizer.optimizer_hybridgenetic import *
from base_optimizer.optimizer_feederpriority import *
from base_optimizer.optimizer_aggregation import *
from base_optimizer.optimizer_twophase import *
from base_optimizer.optimizer_mathmodel import *
from base_optimizer.smopt_scanbased import *
from base_optimizer.smopt_celldivision import *
from base_optimizer.smopt_hybridgenetic import *
from base_optimizer.smopt_feederpriority import *
from base_optimizer.smopt_aggregation import *
from base_optimizer.smopt_twophase import *
from base_optimizer.smopt_mathmodel import *
from base_optimizer.result_analysis import *
@ -25,17 +25,17 @@ def base_optimizer(machine_index, pcb_data, component_data, feeder_data=None, me
elif method == 'hybrid-genetic': # 基于拾取组的混合遗传算法
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_hybrid_genetic(
pcb_data, component_data, hinter=False)
pcb_data, component_data, hinter=hinter)
elif method == 'aggregation': # 基于batch-level的整数规划 + 启发式算法
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_aggregation(
component_data, pcb_data)
elif method == 'genetic-scanning':
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_genetic_scanning(
component_data, pcb_data, hinter=False)
component_data, pcb_data, hinter=hinter)
elif method == 'mip-model':
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_mathmodel(
component_data, pcb_data, hinter=True)
component_data, pcb_data, hinter=hinter)
elif method == "two-phase":
component_result, feeder_slot_result, cycle_result = gurobi_optimizer(pcb_data, component_data, feeder_data,
initial=True, partition=True,
@ -46,22 +46,15 @@ def base_optimizer(machine_index, pcb_data, component_data, feeder_data=None, me
else:
raise 'machine optimizer method ' + method + ' is not existed'
print('----- Placement machine ' + str(machine_index) + ' ----- ')
# 估算贴装用时
info = placement_info_evaluation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
placement_result, head_sequence, hinter=False)
if hinter:
optimization_assign_result(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
nozzle_hinter=True, component_hinter=True, feeder_hinter=True)
info.print()
print('----- Placement machine ' + str(machine_index) + ' ----- ')
print('-Cycle counter: {}'.format(info.cycle_counter))
print(f'-Nozzle change counter: {info.nozzle_change_counter: d}')
print(f'-ANC round: {info.anc_round_counter: d}')
print(f'-Pick operation counter: {info.pickup_counter: d}')
print(f'-Pick time: {info.pickup_time: .3f}, distance: {info.pickup_distance: .3f}')
print(f'-Place time: {info.place_time: .3f}, distance: {info.place_distance: .3f}')
print('------------------------------ ')
print('------------------------------ ')
return info

View File

@ -40,7 +40,6 @@ def convert_pcbdata_to_result(pcb_data, component_data):
if slot == 'A':
slot, part = 0, pcb_data.loc[point_cnt].part
else:
slot, part = int(slot[1:]), pcb_data.loc[point_cnt].fdr.split(' ', 1)[1]
head = pcb_data.loc[point_cnt].hd - 1
@ -423,7 +422,8 @@ def optimization_assign_result(component_data, pcb_data, component_result, cycle
component_assign.loc[cycle, 'H{}'.format(head + 1)] = ''
else:
part = component_data.loc[index]['part']
component_assign.loc[cycle, 'H{}'.format(head + 1)] = 'C' + str(index)
component_assign.loc[cycle, 'H{}'.format(head + 1)] = part
# component_assign.loc[cycle, 'H{}'.format(head + 1)] = 'C' + str(index)
print(component_assign)
print('')
@ -450,6 +450,7 @@ def placement_info_evaluation(component_data, pcb_data, component_result, cycle_
placement_result=None, head_sequence=None, hinter=False):
# === 优化结果参数 ===
info = OptInfo()
# === 校验 ===
info.total_points = 0
for cycle, components in enumerate(component_result):
@ -461,7 +462,7 @@ def placement_info_evaluation(component_data, pcb_data, component_result, cycle_
if info.total_points != len(pcb_data):
warning_info = 'the number of placement points is not match with the PCB data. '
warnings.warn(warning_info, UserWarning)
return 0.
return OptInfo()
if placement_result:
total_points = info.total_points
@ -475,7 +476,7 @@ def placement_info_evaluation(component_data, pcb_data, component_result, cycle_
warnings.warn(
'the optimization result of component assignment result and placement result are not consistent. ',
UserWarning)
return 0.
return OptInfo()
feeder_arrangement = defaultdict(set)
for cycle, feeder_slots in enumerate(feeder_slot_result):
@ -484,8 +485,9 @@ def placement_info_evaluation(component_data, pcb_data, component_result, cycle_
continue
feeder_arrangement[component_result[cycle][head]].add(slot)
info.total_components = len(feeder_arrangement.keys())
for part, data in component_data.iterrows():
if part in feeder_arrangement.keys() and data['feeder-limit'] < len(feeder_arrangement[part]):
if part in feeder_arrangement.keys() and data.fdn < len(feeder_arrangement[part]):
info = 'the number of arranged feeder of [' + data['part'] + '] exceeds the quantity limit'
warnings.warn(info, UserWarning)
return 0.

View File

@ -21,8 +21,8 @@ def feeder_priority_assignment(component_data, pcb_data, hinter=True):
info = placement_info_evaluation(component_data, pcb_data, component_assign, cycle_assign,
feeder_slot_assign, None, None, hinter=False)
val = 0.4 * info.cycle_counter + 2.15 * info.nozzle_change_counter + 0.11 * info.pickup_counter \
+ 0.005 * info.anc_round_counter
val = 0.356 * info.cycle_counter + 0.949 * info.nozzle_change_counter + 0.159 * info.pickup_counter \
+ 0.002 * info.pickup_distance
if feeder_allocate_val is None or val < feeder_allocate_val:
feeder_allocate_val = val
component_result, cycle_result, feeder_slot_result = component_assign, cycle_assign, feeder_slot_assign
@ -91,14 +91,17 @@ def feeder_nozzle_pattern(component_data):
for _ in range(head):
nozzle_pattern_list[-1][head_assign_indexes[-idx]] = nozzle
idx += 1
nozzle_points.pop(min_points_nozzle)
# nozzle_pattern_list = []
# nozzle_pattern_list.append(['CN220', 'CN220', 'CN065', 'CN065', 'CN140', 'CN140'])
return nozzle_pattern_list
def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figure=False, hinter=True):
feeder_points, feeder_division_points = defaultdict(int), defaultdict(int) # 供料器贴装点数
mount_center_pos = defaultdict(float)
feeder_center_pos = defaultdict(float)
feeder_limit, feeder_arrange = defaultdict(int), defaultdict(int)
part_nozzle = defaultdict(str)
@ -109,7 +112,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur
for idx, data in component_data.iterrows():
component_index[data.part] = idx
feeder_limit[idx] = data['feeder-limit']
feeder_limit[idx] = data.fdn
feeder_arrange[idx] = 0
for _, data in pcb_data.iterrows():
@ -118,7 +121,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur
part_index = component_index[part]
feeder_points[part_index] += 1
mount_center_pos[part_index] += ((pos - mount_center_pos[part_index]) / feeder_points[part_index])
feeder_center_pos[part_index] += ((pos - feeder_center_pos[part_index]) / feeder_points[part_index])
part_nozzle[part_index] = component_data.loc[part_index].nz
for part_index, points in feeder_points.items():
@ -198,8 +201,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur
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)
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
@ -210,8 +213,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur
if len(tmp_nozzle_component[nozzle_assign]) == 0:
# 当前头对应吸嘴类型无可用元件,将计划分配的元件压入堆栈
part = max(tmp_feeder_points.keys(),
key=lambda x: tmp_feeder_points[x] / tmp_feeder_limit[x] if tmp_feeder_limit[
x] != 0 else 0)
key=lambda x: tmp_feeder_points[x] / tmp_feeder_limit[x]
if tmp_feeder_limit[x] != 0 else 0)
for nozzle, component_list in tmp_nozzle_component.items():
if part in component_list:
nozzle_assign = nozzle
@ -227,7 +230,6 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur
tmp_feeder_limit[x] != 0 else 0))
part = tmp_nozzle_component[nozzle_assign][index_]
feeder_type = component_data.loc[part].fdr
extra_width, extra_slot = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval, 1
slot_overlap = False
@ -341,7 +343,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figur
for head, feeder_ in enumerate(feeder_assign):
if feeder_ < 0:
continue
average_slot.append((mount_center_pos[feeder_] - slotf1_pos[0]) / slot_interval + 1)
average_slot.append((feeder_center_pos[feeder_] - slotf1_pos[0]) / slot_interval + 1)
if nozzle_pattern and component_data.loc[feeder_].nz != nozzle_pattern[head]:
nozzle_change_counter += 1
@ -499,7 +501,9 @@ def feeder_base_scan(component_data, pcb_data, feeder_data):
raise ValueError(info)
component_points[idx] = data.points
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) # 所有供料器均已分配槽位
mount_center_slot = defaultdict(float)

View File

@ -313,7 +313,7 @@ def optimizer_hybrid_genetic(pcb_data, component_data, hinter=True):
idx = component_data[component_data['part'] == part].index.tolist()[0]
nozzle = component_data.loc[idx]['nz']
component_feeder_limit[part] = component_data.loc[idx]['feeder-limit']
component_feeder_limit[part] = component_data.loc[idx].fdn
component_points[part] += 1
if nozzle_components[nozzle].count(part) < component_feeder_limit[part]:
nozzle_components[nozzle].append(part)

View File

@ -1,10 +1,6 @@
from base_optimizer.optimizer_common import *
def list_range(start, end=None):
return list(range(start)) if end is None else list(range(start, end))
def head_task_model(component_data, pcb_data, hinter=True):
mdl = Model('pick_route')