增加超启发式线体优化算法
This commit is contained in:
@ -104,7 +104,7 @@ def optimizer_celldivision(pcb_data, component_data, hinter=True):
|
||||
point_num = len(pcb_data)
|
||||
component_cell = pd.DataFrame({'index': np.arange(len(component_data)), 'points': np.zeros(len(component_data), dtype=int)})
|
||||
for point_cnt in range(point_num):
|
||||
part = pcb_data.loc[point_cnt, 'fdr'].split(' ', 1)[1]
|
||||
part = pcb_data.loc[point_cnt, 'part']
|
||||
index = np.where(component_data['part'].values == part)
|
||||
component_cell.loc[index[0], 'points'] += 1
|
||||
component_cell = component_cell[~component_cell['points'].isin([0])]
|
||||
|
@ -29,7 +29,7 @@ head_nozzle = ['' for _ in range(max_head_index)] # 头上已经分配吸嘴
|
||||
slotf1_pos, slotr1_pos = [-31.267, 44.], [807., 810.545] # F1(前基座最左侧)、R1(后基座最右侧)位置
|
||||
fix_camera_pos = [269.531, 694.823] # 固定相机位置
|
||||
anc_marker_pos = [336.457, 626.230] # ANC基准点位置
|
||||
stopper_pos = [635.150, 124.738] # 止档块位置
|
||||
stopper_pos = [535.150, 124.738] # 止档块位置
|
||||
|
||||
# 算法权重参数
|
||||
e_nz_change, e_gang_pick = 4, 0.6
|
||||
@ -48,6 +48,7 @@ nozzle_limit = {'CN065': 6, 'CN040': 6, 'CN220': 6, 'CN400': 6, 'CN140': 6}
|
||||
|
||||
# 时间参数
|
||||
t_cycle = 0.3
|
||||
t_anc = 0.6
|
||||
t_pick, t_place = .078, .051 # 贴装/拾取用时
|
||||
t_nozzle_put, t_nozzle_pick = 0.9, 0.75 # 装卸吸嘴用时
|
||||
t_nozzle_change = t_nozzle_put + t_nozzle_pick
|
||||
@ -59,66 +60,22 @@ T_pp, T_tr, T_nc, T_pl = 2, 5, 25, 0
|
||||
|
||||
class OptInfo:
|
||||
def __init__(self):
|
||||
self.placement_time = 0
|
||||
self.total_time = .0 # 总组装时间
|
||||
self.total_points = .0 # 总贴装点数
|
||||
|
||||
self.cycle_counter = 0
|
||||
self.nozzle_change_counter = 0
|
||||
self.pickup_counter = 0
|
||||
self.pickup_time = .0 # 拾取过程运动时间
|
||||
self.round_time = .0 # 往返基座/基板运动时间
|
||||
self.place_time = .0 # 贴装过程运动时间
|
||||
self.operation_time = .0 # 拾取/贴装/换吸嘴等机械动作用时
|
||||
|
||||
self.pickup_movement = 0
|
||||
self.placement_movement = 0
|
||||
self.cycle_counter = 0 # 周期数
|
||||
self.nozzle_change_counter = 0 # 吸嘴更换次数
|
||||
self.anc_round_counter = 0 # 前往ANC次数
|
||||
self.pickup_counter = 0 # 拾取次数
|
||||
|
||||
|
||||
def optimization_assign_result(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
nozzle_hinter=False, component_hinter=False, feeder_hinter=False):
|
||||
if nozzle_hinter:
|
||||
columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']
|
||||
|
||||
nozzle_assign = pd.DataFrame(columns=columns)
|
||||
for cycle, components in enumerate(component_result):
|
||||
nozzle_assign.loc[cycle, 'cycle'] = cycle_result[cycle]
|
||||
for head in range(max_head_index):
|
||||
index = component_result[cycle][head]
|
||||
if index == -1:
|
||||
nozzle_assign.loc[cycle, 'H{}'.format(head + 1)] = ''
|
||||
else:
|
||||
nozzle_assign.loc[cycle, 'H{}'.format(head + 1)] = component_data.loc[index].nz
|
||||
|
||||
print(nozzle_assign)
|
||||
print('')
|
||||
|
||||
if component_hinter:
|
||||
columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']
|
||||
|
||||
component_assign = pd.DataFrame(columns=columns)
|
||||
for cycle, components in enumerate(component_result):
|
||||
component_assign.loc[cycle, 'cycle'] = cycle_result[cycle]
|
||||
for head in range(max_head_index):
|
||||
index = component_result[cycle][head]
|
||||
if index == -1:
|
||||
component_assign.loc[cycle, 'H{}'.format(head + 1)] = ''
|
||||
else:
|
||||
component_assign.loc[cycle, 'H{}'.format(head + 1)] = component_data.loc[index].part
|
||||
|
||||
print(component_assign)
|
||||
print('')
|
||||
|
||||
if feeder_hinter:
|
||||
columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']
|
||||
|
||||
feedr_assign = pd.DataFrame(columns=columns)
|
||||
for cycle, components in enumerate(feeder_slot_result):
|
||||
feedr_assign.loc[cycle, 'cycle'] = cycle_result[cycle]
|
||||
for head in range(max_head_index):
|
||||
slot = feeder_slot_result[cycle][head]
|
||||
if slot == -1:
|
||||
feedr_assign.loc[cycle, 'H{}'.format(head + 1)] = 'A'
|
||||
else:
|
||||
feedr_assign.loc[cycle, 'H{}'.format(head + 1)] = 'F{}'.format(
|
||||
slot) if slot <= max_slot_index // 2 else 'R{}'.format(slot - max_head_index)
|
||||
|
||||
print(feedr_assign)
|
||||
print('')
|
||||
self.total_distance = .0 # 总移动路径
|
||||
self.place_distance = .0 # 贴装移动路径
|
||||
self.pickup_distance = .0 # 拾取移动路径
|
||||
|
||||
|
||||
def axis_moving_time(distance, axis=0):
|
||||
@ -172,7 +129,11 @@ def timer_wrapper(func):
|
||||
def measure_time(*args, **kwargs):
|
||||
start_time = time.time()
|
||||
result = func(*args, **kwargs)
|
||||
|
||||
hinter = True
|
||||
for key, val in kwargs.items():
|
||||
if key == 'hinter':
|
||||
hinter = val
|
||||
if hinter:
|
||||
print(f"function {func.__name__} running time : {time.time() - start_time:.3f} s")
|
||||
return result
|
||||
|
||||
@ -440,7 +401,7 @@ def dynamic_programming_cycle_path(pcb_data, cycle_placement, assigned_feeder):
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def greedy_placement_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result):
|
||||
def greedy_placement_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result, hinter=True):
|
||||
placement_result, head_sequence_result = [], []
|
||||
mount_point_index = [[] for _ in range(len(component_data))]
|
||||
mount_point_pos = [[] for _ in range(len(component_data))]
|
||||
@ -951,7 +912,7 @@ def constraint_swap_mutation(component_points, individual, machine_number):
|
||||
offspring = individual.copy()
|
||||
|
||||
idx, component_index = 0, random.randint(0, len(component_points) - 1)
|
||||
for _, points in component_points:
|
||||
for points in component_points.values():
|
||||
if component_index == 0:
|
||||
while True:
|
||||
index1, index2 = random.sample(range(points + machine_number - 2), 2)
|
||||
@ -988,6 +949,7 @@ def random_selective(data, possibility): # 依概率选择随机数
|
||||
possibility = [p / sum_val for p in possibility]
|
||||
|
||||
random_val = random.random()
|
||||
idx = 0
|
||||
for idx, val in enumerate(possibility):
|
||||
random_val -= val
|
||||
if random_val <= 0:
|
||||
@ -1061,17 +1023,25 @@ def get_line_config_number(machine_number, component_number):
|
||||
return div_counter
|
||||
|
||||
|
||||
def partial_data_convert(pcb_data, component_data, machine_assign, machine_number):
|
||||
assignment_result = copy.deepcopy(machine_assign)
|
||||
def convert_line_assigment(pcb_data, component_data, assignment_result):
|
||||
machine_number = len(assignment_result)
|
||||
|
||||
placement_points = []
|
||||
partial_pcb_data, partial_component_data = defaultdict(pd.DataFrame), defaultdict(pd.DataFrame)
|
||||
for machine_index in range(machine_number):
|
||||
partial_pcb_data[machine_index] = pd.DataFrame(columns=pcb_data.columns)
|
||||
partial_component_data[machine_index] = component_data.copy(deep=True)
|
||||
placement_points.append(sum(assignment_result[machine_index]))
|
||||
|
||||
assert sum(placement_points) == len(pcb_data)
|
||||
|
||||
# === averagely assign available feeder ===
|
||||
for part_index, data in component_data.iterrows():
|
||||
feeder_limit = data['feeder-limit']
|
||||
feeder_points = [assignment_result[machine_index][part_index] for machine_index in range(max_machine_index)]
|
||||
feeder_points = [assignment_result[machine_index][part_index] for machine_index in range(machine_number)]
|
||||
|
||||
for machine_index in range(machine_number):
|
||||
partial_component_data[machine_index].loc[part_index, 'points'] = 0
|
||||
|
||||
for machine_index in range(machine_number):
|
||||
if feeder_points[machine_index] == 0:
|
||||
@ -1079,7 +1049,7 @@ def partial_data_convert(pcb_data, component_data, machine_assign, machine_numbe
|
||||
|
||||
arg_feeder = max(math.floor(feeder_points[machine_index] / sum(feeder_points) * data['feeder-limit']), 1)
|
||||
|
||||
partial_component_data[machine_index].loc[part_index]['feeder-limit'] = arg_feeder
|
||||
partial_component_data[machine_index].loc[part_index, 'feeder-limit'] = arg_feeder
|
||||
feeder_limit -= arg_feeder
|
||||
|
||||
for machine_index in range(machine_number):
|
||||
@ -1088,27 +1058,126 @@ def partial_data_convert(pcb_data, component_data, machine_assign, machine_numbe
|
||||
|
||||
if feeder_points[machine_index] == 0:
|
||||
continue
|
||||
partial_component_data[machine_index].loc[part_index]['feeder-limit'] += 1
|
||||
partial_component_data[machine_index].loc[part_index, 'feeder-limit'] += 1
|
||||
feeder_limit -= 1
|
||||
|
||||
for machine_index in range(machine_number):
|
||||
if feeder_points[machine_index] > 0:
|
||||
assert partial_component_data[machine_index].loc[part_index]['feeder-limit'] > 0
|
||||
assert partial_component_data[machine_index].loc[part_index, 'feeder-limit'] > 0
|
||||
|
||||
# === assign placements ===
|
||||
component_machine_index = [0 for _ in range(len(component_data))]
|
||||
part2idx = defaultdict(int)
|
||||
for idx, data in component_data.iterrows():
|
||||
part2idx[data.part] = idx
|
||||
|
||||
machine_average_pos = [[0, 0] for _ in range(machine_number)]
|
||||
machine_step_counter = [0 for _ in range(machine_number)]
|
||||
part_pcb_data = defaultdict(list)
|
||||
for _, data in pcb_data.iterrows():
|
||||
part_index = component_data[component_data['part'] == data['part']].index.tolist()[0]
|
||||
while True:
|
||||
machine_index = component_machine_index[part_index]
|
||||
if assignment_result[machine_index][part_index] == 0:
|
||||
component_machine_index[part_index] += 1
|
||||
machine_index += 1
|
||||
else:
|
||||
break
|
||||
assignment_result[machine_index][part_index] -= 1
|
||||
part_pcb_data[part2idx[data.part]].append(data)
|
||||
|
||||
multiple_component_index = []
|
||||
for part_index in range(len(component_data)):
|
||||
machine_assign_set = []
|
||||
for machine_index in range(machine_number):
|
||||
if assignment_result[machine_index][part_index]:
|
||||
machine_assign_set.append(machine_index)
|
||||
|
||||
if len(machine_assign_set) == 1:
|
||||
for data in part_pcb_data[part_index]:
|
||||
machine_index = machine_assign_set[0]
|
||||
|
||||
machine_average_pos[machine_index][0] += data.x
|
||||
machine_average_pos[machine_index][1] += data.y
|
||||
|
||||
machine_step_counter[machine_index] += 1
|
||||
|
||||
partial_component_data[machine_index].loc[part_index, 'points'] += 1
|
||||
partial_pcb_data[machine_index] = pd.concat([partial_pcb_data[machine_index], pd.DataFrame(data).T])
|
||||
|
||||
elif len(machine_assign_set) > 1:
|
||||
multiple_component_index.append(part_index)
|
||||
|
||||
for machine_index in range(machine_number):
|
||||
if machine_step_counter[machine_index] == 0:
|
||||
continue
|
||||
machine_average_pos[machine_index][0] /= machine_step_counter[machine_index]
|
||||
machine_average_pos[machine_index][1] /= machine_step_counter[machine_index]
|
||||
|
||||
for part_index in multiple_component_index:
|
||||
for data in part_pcb_data[part_index]:
|
||||
idx = -1
|
||||
min_dist = None
|
||||
for machine_index in range(machine_number):
|
||||
if partial_component_data[machine_index].loc[part_index, 'points'] >= \
|
||||
assignment_result[machine_index][part_index]:
|
||||
continue
|
||||
dist = (data.x - machine_average_pos[machine_index][0]) ** 2 + (
|
||||
data.y - machine_average_pos[machine_index][1]) ** 2
|
||||
if min_dist is None or dist < min_dist:
|
||||
min_dist, idx = dist, machine_index
|
||||
|
||||
assert idx >= 0
|
||||
machine_step_counter[idx] += 1
|
||||
machine_average_pos[idx][0] += (1 - 1 / machine_step_counter[idx]) * machine_average_pos[idx][0] + data.x / \
|
||||
machine_step_counter[idx]
|
||||
machine_average_pos[idx][1] += (1 - 1 / machine_step_counter[idx]) * machine_average_pos[idx][1] + data.y / \
|
||||
machine_step_counter[idx]
|
||||
|
||||
partial_component_data[idx].loc[part_index, 'points'] += 1
|
||||
partial_pcb_data[idx] = pd.concat([partial_pcb_data[idx], pd.DataFrame(data).T])
|
||||
|
||||
# === adjust the number of available feeders for single optimization separately ===
|
||||
# for machine_index, data in partial_pcb_data.items():
|
||||
# part_info = [] # part info list:(part index, part points, available feeder-num, upper feeder-num)
|
||||
# for part_index, cp_data in partial_component_data[machine_index].iterrows():
|
||||
# if assignment_result[machine_index][part_index]:
|
||||
# part_info.append(
|
||||
# [part_index, assignment_result[machine_index][part_index], 1, cp_data['feeder-limit']])
|
||||
#
|
||||
# part_info = sorted(part_info, key=lambda x: x[1], reverse=True)
|
||||
# start_index, end_index = 0, min(max_head_index - 1, len(part_info) - 1)
|
||||
# while start_index < len(part_info):
|
||||
# assign_part_point, assign_part_index = [], []
|
||||
# for idx_ in range(start_index, end_index + 1):
|
||||
# for _ in range(part_info[idx_][2]):
|
||||
# assign_part_point.append(part_info[idx_][1] / part_info[idx_][2])
|
||||
# assign_part_index.append(idx_)
|
||||
#
|
||||
# variance = np.std(assign_part_point)
|
||||
# while start_index <= end_index:
|
||||
# part_info_index = assign_part_index[np.argmax(assign_part_point)]
|
||||
#
|
||||
# if part_info[part_info_index][2] < part_info[part_info_index][3]: # 供料器数目上限的限制
|
||||
# part_info[part_info_index][2] += 1
|
||||
# end_index -= 1
|
||||
#
|
||||
# new_assign_part_point, new_assign_part_index = [], []
|
||||
# for idx_ in range(start_index, end_index + 1):
|
||||
# for _ in range(part_info[idx_][2]):
|
||||
# new_assign_part_point.append(part_info[idx_][1] / part_info[idx_][2])
|
||||
# new_assign_part_index.append(idx_)
|
||||
#
|
||||
# new_variance = np.std(new_assign_part_point)
|
||||
# if variance < new_variance:
|
||||
# part_info[part_info_index][2] -= 1
|
||||
# end_index += 1
|
||||
# break
|
||||
#
|
||||
# variance = new_variance
|
||||
# assign_part_index, assign_part_point = new_assign_part_index.copy(), new_assign_part_point.copy()
|
||||
# else:
|
||||
# break
|
||||
#
|
||||
# start_index = end_index + 1
|
||||
# end_index = min(start_index + max_head_index - 1, len(part_info) - 1)
|
||||
#
|
||||
# max_avl_feeder = max(part_info, key=lambda x: x[2])[2]
|
||||
# for info in part_info:
|
||||
# partial_component_data[machine_index].loc[info[0], 'feeder-limit'] = math.ceil(info[2] / max_avl_feeder)
|
||||
|
||||
for machine_index in range(machine_number):
|
||||
partial_component_data[machine_index] = partial_component_data[machine_index][
|
||||
partial_component_data[machine_index]['points'] != 0].reset_index(drop=True)
|
||||
|
||||
return partial_pcb_data, partial_component_data
|
||||
|
||||
|
||||
|
@ -1,25 +1,121 @@
|
||||
import math
|
||||
|
||||
from base_optimizer.optimizer_common import *
|
||||
from base_optimizer.result_analysis import placement_info_evaluation
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
def feeder_priority_assignment(component_data, pcb_data, hinter=True):
|
||||
feeder_allocate_val = None
|
||||
component_result, cycle_result, feeder_slot_result = None, None, None
|
||||
nozzle_pattern_list = feeder_nozzle_pattern(component_data)
|
||||
pbar = tqdm(total=len(nozzle_pattern_list), desc='feeder priority process') if hinter else None
|
||||
# 第1步:确定吸嘴分配模式
|
||||
for nozzle_pattern in nozzle_pattern_list:
|
||||
feeder_data = pd.DataFrame(columns=['slot', 'part', 'arg'])
|
||||
# 第2步:分配供料器位置
|
||||
feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figure=False)
|
||||
# 第3步:扫描供料器基座,确定元件拾取的先后顺序
|
||||
component_assign, cycle_assign, feeder_slot_assign = feeder_base_scan(component_data, pcb_data, feeder_data)
|
||||
|
||||
info = placement_info_evaluation(component_data, pcb_data, component_assign, cycle_assign,
|
||||
feeder_slot_assign, None, None, hinter=False)
|
||||
|
||||
val = 0.4 * info.cycle_counter + 2.15 * info.nozzle_change_counter + 0.11 * info.pickup_counter \
|
||||
+ 0.005 * info.anc_round_counter
|
||||
if feeder_allocate_val is None or val < feeder_allocate_val:
|
||||
feeder_allocate_val = val
|
||||
component_result, cycle_result, feeder_slot_result = component_assign, cycle_assign, feeder_slot_assign
|
||||
|
||||
if pbar:
|
||||
pbar.update(1)
|
||||
|
||||
return component_result, cycle_result, feeder_slot_result
|
||||
|
||||
|
||||
def feeder_nozzle_pattern(component_data):
|
||||
nozzle_pattern_list = []
|
||||
nozzle_points = defaultdict(int)
|
||||
for _, data in component_data.iterrows():
|
||||
if data.points == 0:
|
||||
continue
|
||||
nozzle_points[data.nz] += data.points
|
||||
|
||||
head_assign_indexes = [int(math.ceil(max_head_index + 0.5) - 4.5 - pow(-1, h) * (math.ceil(h / 2) - 0.5)) for h in
|
||||
range(1, max_head_index + 1)]
|
||||
|
||||
while len(nozzle_points):
|
||||
nozzle_heads, nozzle_indices = defaultdict(int), defaultdict(str),
|
||||
min_points_nozzle = None
|
||||
for idx, (nozzle, points) in enumerate(nozzle_points.items()):
|
||||
nozzle_heads[nozzle], nozzle_indices[idx] = 1, nozzle
|
||||
if min_points_nozzle is None or points < nozzle_points[min_points_nozzle]:
|
||||
min_points_nozzle = nozzle
|
||||
|
||||
while sum(nozzle_heads.values()) != max_head_index:
|
||||
max_cycle_nozzle = None
|
||||
|
||||
for nozzle, head_num in nozzle_heads.items():
|
||||
if max_cycle_nozzle is None or nozzle_points[nozzle] / head_num > nozzle_points[max_cycle_nozzle] / \
|
||||
nozzle_heads[max_cycle_nozzle]:
|
||||
max_cycle_nozzle = nozzle
|
||||
elif nozzle_points[nozzle] / head_num == nozzle_points[max_cycle_nozzle] / nozzle_heads[max_cycle_nozzle]:
|
||||
if head_num > nozzle_heads[max_cycle_nozzle]:
|
||||
max_cycle_nozzle = nozzle
|
||||
|
||||
assert max_cycle_nozzle is not None
|
||||
nozzle_heads[max_cycle_nozzle] += 1
|
||||
|
||||
for permu in itertools.permutations(nozzle_indices.keys()):
|
||||
nozzle_pattern_list.append([])
|
||||
for idx in permu:
|
||||
for _ in range(nozzle_heads[nozzle_indices[idx]]):
|
||||
nozzle_pattern_list[-1].append(nozzle_indices[idx])
|
||||
|
||||
if len(nozzle_points.keys()) > 1:
|
||||
nozzle_average_points = []
|
||||
for nozzle, head in nozzle_heads.items():
|
||||
nozzle_average_points.append([nozzle, head, nozzle_points[nozzle] / head])
|
||||
|
||||
nozzle_average_points = sorted(nozzle_average_points, key=lambda x: -x[2])
|
||||
idx = 0
|
||||
nozzle_pattern_list.append(['' for _ in range(max_head_index)])
|
||||
for nozzle, head, _ in nozzle_average_points:
|
||||
for _ in range(head):
|
||||
nozzle_pattern_list[-1][head_assign_indexes[idx]] = nozzle
|
||||
idx += 1
|
||||
|
||||
idx = 1
|
||||
nozzle_pattern_list.append(['' for _ in range(max_head_index)])
|
||||
for nozzle, head, _ in nozzle_average_points:
|
||||
for _ in range(head):
|
||||
nozzle_pattern_list[-1][head_assign_indexes[-idx]] = nozzle
|
||||
idx += 1
|
||||
nozzle_points.pop(min_points_nozzle)
|
||||
return nozzle_pattern_list
|
||||
|
||||
|
||||
def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figure=False, hinter=True):
|
||||
|
||||
feeder_points, feeder_division_points = defaultdict(int), defaultdict(int) # 供料器贴装点数
|
||||
mount_center_pos = defaultdict(int)
|
||||
mount_center_pos = defaultdict(float)
|
||||
|
||||
feeder_limit, feeder_arrange = defaultdict(int), defaultdict(int)
|
||||
part_nozzle = defaultdict(str)
|
||||
|
||||
feeder_base = [-2] * max_slot_index # 已安装在供料器基座上的元件(-2: 未分配,-1: 占用状态)
|
||||
feeder_base_points = [0] * max_slot_index # 供料器基座结余贴装点数量
|
||||
component_index = defaultdict(int)
|
||||
for idx, data in component_data.iterrows():
|
||||
component_index[data.part] = idx
|
||||
|
||||
feeder_limit[idx] = data['feeder-limit']
|
||||
feeder_arrange[idx] = 0
|
||||
|
||||
for _, data in pcb_data.iterrows():
|
||||
pos, part = data.x + stopper_pos[0], data.part
|
||||
|
||||
part_index = component_data[component_data.part == part].index.tolist()[0]
|
||||
if part not in component_data:
|
||||
feeder_limit[part_index] = component_data.loc[part_index]['feeder-limit']
|
||||
feeder_arrange[part_index] = 0
|
||||
part_index = component_index[part]
|
||||
|
||||
feeder_points[part_index] += 1
|
||||
mount_center_pos[part_index] += ((pos - mount_center_pos[part_index]) / feeder_points[part_index])
|
||||
@ -37,7 +133,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
if feeder_data is not None:
|
||||
for _, feeder in feeder_data.iterrows():
|
||||
slot, part = feeder.slot, feeder.part
|
||||
part_index = component_data[component_data.part == part].index.tolist()[0]
|
||||
part_index = component_index[part]
|
||||
|
||||
# 供料器基座分配位置和对应贴装点数
|
||||
feeder_base[slot], feeder_base_points[slot] = part_index, feeder_division_points[part_index]
|
||||
@ -63,78 +159,14 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
nozzle_component_points[nozzle].pop(index_)
|
||||
break
|
||||
|
||||
nozzle_assigned_counter = optimal_nozzle_assignment(component_data, pcb_data)
|
||||
head_assign_indexes = list(range(max_head_index))
|
||||
nozzle_pattern, optimal_nozzle_pattern, optimal_nozzle_points = [], None, 0
|
||||
|
||||
# 先排序
|
||||
nozzle_pattern_list = []
|
||||
for nozzle, counter in nozzle_assigned_counter.items():
|
||||
nozzle_pattern_list.append([nozzle, sum(nozzle_component_points[nozzle]) // counter])
|
||||
nozzle_pattern_list.sort(key=lambda x: x[1], reverse=True)
|
||||
|
||||
# 后确定吸嘴分配模式
|
||||
upper_head, extra_head = defaultdict(int), defaultdict(int)
|
||||
head_index = []
|
||||
for nozzle, head in nozzle_assigned_counter.items():
|
||||
# 每个吸嘴能达成同时拾取数目的上限
|
||||
upper_head[nozzle] = min(len(nozzle_component[nozzle]), head)
|
||||
extra_head[nozzle] = head - upper_head[nozzle]
|
||||
|
||||
head_counter = (sum(upper_head.values()) - 1) // 2
|
||||
while head_counter >= 0:
|
||||
if head_counter != (sum(upper_head.values()) - 1) - head_counter:
|
||||
head_index.append((sum(upper_head.values()) - 1) - head_counter)
|
||||
head_index.append(head_counter)
|
||||
head_counter -= 1
|
||||
|
||||
nozzle_pattern = [None for _ in range(sum(upper_head.values()))]
|
||||
for nozzle in upper_head.keys():
|
||||
counter = upper_head[nozzle]
|
||||
while counter:
|
||||
nozzle_pattern[head_index[0]] = nozzle
|
||||
counter -= 1
|
||||
head_index.pop(0)
|
||||
|
||||
head = 0
|
||||
while head + sum(extra_head.values()) <= len(nozzle_pattern):
|
||||
extra_head_cpy = copy.deepcopy(extra_head)
|
||||
increment = 0
|
||||
while increment < sum(extra_head.values()):
|
||||
extra_head_cpy[nozzle_pattern[head + increment]] -= 1
|
||||
increment += 1
|
||||
|
||||
check_extra_head = True
|
||||
for head_ in extra_head_cpy.values():
|
||||
if head_ != 0:
|
||||
check_extra_head = False # 任一项不为0, 说明不构成
|
||||
break
|
||||
|
||||
if check_extra_head:
|
||||
increment = 0
|
||||
while increment < sum(extra_head.values()):
|
||||
nozzle_pattern.append(nozzle_pattern[head + increment])
|
||||
increment += 1
|
||||
|
||||
for nozzle in extra_head.keys():
|
||||
extra_head[nozzle] = 0
|
||||
|
||||
break
|
||||
head += 1
|
||||
|
||||
for nozzle, head_ in extra_head.items():
|
||||
while head_:
|
||||
nozzle_pattern.append(nozzle)
|
||||
head_ -= 1
|
||||
|
||||
head_assign_indexes = [int(math.ceil(max_head_index + 0.5) - 4.5 - pow(-1, h) * (math.ceil(h / 2) - 0.5)) for h in
|
||||
range(1, max_head_index + 1)]
|
||||
assert len(nozzle_pattern) == max_head_index
|
||||
while True:
|
||||
best_assign, best_assign_points = [], []
|
||||
best_assign_slot, best_assign_value = -1, -np.Inf
|
||||
best_nozzle_component, best_nozzle_component_points = None, None
|
||||
for slot in range(1, max_slot_index // 2 - (max_head_index - 1) * interval_ratio + 1):
|
||||
nozzle_assigned_counter_cpy = copy.deepcopy(nozzle_assigned_counter)
|
||||
|
||||
feeder_assign, feeder_assign_points = [], []
|
||||
tmp_feeder_limit, tmp_feeder_points = feeder_limit.copy(), feeder_points.copy()
|
||||
tmp_nozzle_component, tmp_nozzle_component_points = copy.deepcopy(nozzle_component), copy.deepcopy(
|
||||
@ -144,24 +176,14 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
for head in range(max_head_index):
|
||||
feeder_assign.append(feeder_base[slot + head * interval_ratio])
|
||||
|
||||
if scan_part := feeder_assign[-1] >= 0:
|
||||
nozzle = part_nozzle[scan_part]
|
||||
if feeder_assign[-1] >= 0:
|
||||
feeder_assign_points.append(feeder_base_points[slot + head * interval_ratio])
|
||||
if feeder_assign_points[-1] <= 0:
|
||||
feeder_assign[-1], feeder_assign_points[-1] = -1, 0
|
||||
elif nozzle in nozzle_assigned_counter_cpy.keys():
|
||||
nozzle_assigned_counter_cpy[nozzle] -= 1
|
||||
if nozzle_assigned_counter_cpy[nozzle] == 0:
|
||||
nozzle_assigned_counter_cpy.pop(nozzle)
|
||||
else:
|
||||
feeder_assign_points.append(0)
|
||||
|
||||
if -2 not in feeder_assign: # 无可用槽位
|
||||
if sum(feeder_assign_points) > optimal_nozzle_points:
|
||||
optimal_nozzle_points = sum(feeder_assign_points)
|
||||
optimal_nozzle_pattern = [''] * max_head_index
|
||||
for head in range(max_head_index):
|
||||
optimal_nozzle_pattern[head] = part_nozzle[feeder_assign[head]]
|
||||
if -2 not in feeder_assign:
|
||||
continue
|
||||
|
||||
assign_part_stack, assign_part_stack_points = [], []
|
||||
@ -172,7 +194,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
if len(nozzle_pattern) == 0: # 吸嘴匹配模式为空,优先分配元件,根据分配元件倒推吸嘴匹配模式
|
||||
nozzle_assign = ''
|
||||
max_points, max_nozzle_points = 0, 0
|
||||
for nozzle in nozzle_assigned_counter_cpy.keys():
|
||||
for nozzle in set(nozzle_pattern):
|
||||
if len(tmp_nozzle_component[nozzle]) == 0:
|
||||
continue
|
||||
part = max(tmp_nozzle_component[nozzle],
|
||||
@ -229,12 +251,6 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
else:
|
||||
part = -1 # 存在位置冲突的元件,不占用可用供料器数
|
||||
|
||||
# 更新吸嘴匹配模式的吸嘴数
|
||||
if nozzle_assign in nozzle_assigned_counter_cpy.keys():
|
||||
nozzle_assigned_counter_cpy[nozzle_assign] -= 1
|
||||
if nozzle_assigned_counter_cpy[nozzle_assign] == 0:
|
||||
nozzle_assigned_counter_cpy.pop(nozzle_assign)
|
||||
|
||||
if part >= 0 and tmp_feeder_limit[part] == 0:
|
||||
continue
|
||||
|
||||
@ -253,7 +269,6 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
if feeder != -2:
|
||||
continue
|
||||
for idx, part in enumerate(assign_part_stack):
|
||||
|
||||
feeder_type = component_data.loc[part].fdr
|
||||
extra_width, extra_slot = feeder_width[feeder_type][0] + feeder_width[feeder_type][
|
||||
1] - slot_interval, 1
|
||||
@ -282,7 +297,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
part, points = assign_part_stack[0], assign_part_stack_points[0]
|
||||
|
||||
feeder_type = component_data.loc[part].fdr
|
||||
extra_width, extra_slot = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval, 1
|
||||
extra_width = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval
|
||||
extra_slot = 1
|
||||
|
||||
slot_overlap = False
|
||||
while extra_width > 0:
|
||||
@ -295,8 +311,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
|
||||
if not slot_overlap:
|
||||
feeder_assign[head], feeder_assign_points[head] = part, points
|
||||
extra_width, extra_head = feeder_width[feeder_type][0] + feeder_width[feeder_type][
|
||||
1] - head_interval, 1
|
||||
extra_width = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - head_interval
|
||||
extra_head = 1
|
||||
while extra_width > 0 and head + extra_head < max_head_index:
|
||||
feeder_assign[head + extra_head] = -1
|
||||
extra_head += 1
|
||||
@ -325,8 +341,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
for head, feeder_ in enumerate(feeder_assign):
|
||||
if feeder_ < 0:
|
||||
continue
|
||||
average_slot.append(
|
||||
(mount_center_pos[feeder_] - slotf1_pos[0]) / slot_interval + 1 - head * interval_ratio)
|
||||
average_slot.append((mount_center_pos[feeder_] - slotf1_pos[0]) / slot_interval + 1)
|
||||
|
||||
if nozzle_pattern and component_data.loc[feeder_].nz != nozzle_pattern[head]:
|
||||
nozzle_change_counter += 1
|
||||
|
||||
@ -346,7 +362,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
continue
|
||||
feeder_assign_points_cpy[head] -= min(points_filter)
|
||||
|
||||
assign_value -= 1e2 * e_nz_change * nozzle_change_counter + 1e-5 * abs(slot - average_slot)
|
||||
assign_value -= (1e2 * e_nz_change * nozzle_change_counter + 1e-5 * abs(slot - average_slot))
|
||||
|
||||
if assign_value >= best_assign_value and sum(feeder_assign_points) != 0:
|
||||
|
||||
@ -359,8 +375,6 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
if not best_assign_points:
|
||||
break
|
||||
|
||||
if len(nozzle_pattern) == 0:
|
||||
nozzle_pattern = [''] * max_head_index
|
||||
for idx, part in enumerate(best_assign):
|
||||
if part < 0:
|
||||
continue
|
||||
@ -410,34 +424,8 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
nozzle_component, nozzle_component_points = copy.deepcopy(best_nozzle_component), copy.deepcopy(
|
||||
best_nozzle_component_points)
|
||||
|
||||
if sum(best_assign_points) > optimal_nozzle_points:
|
||||
optimal_nozzle_points = sum(best_assign_points)
|
||||
optimal_nozzle_pattern = nozzle_pattern.copy()
|
||||
|
||||
assert not list(filter(lambda x: x < 0, feeder_limit.values())) # 分配供料器数目在限制范围内
|
||||
|
||||
# 若所有供料器均安装在基座上,重新对基座进行扫描,确定最优吸嘴模式(有序)
|
||||
if not optimal_nozzle_points:
|
||||
feeder_base, feeder_base_points = [-2] * max_slot_index, [0] * max_slot_index
|
||||
for _, feeder in feeder_data.iterrows():
|
||||
part_index = component_data[component_data.part == feeder.part].index.tolist()[0]
|
||||
|
||||
# 供料器基座分配位置和对应贴装点数
|
||||
feeder_base[feeder.slot], feeder_base_points[feeder.slot] = part_index, feeder_division_points[part_index]
|
||||
|
||||
# 前基座 TODO: 后基座
|
||||
for slot in range(max_slot_index // 2 - (max_head_index - 1) * interval_ratio):
|
||||
sum_scan_points = 0
|
||||
for head in range(max_head_index):
|
||||
sum_scan_points += feeder_base_points[slot + head * interval_ratio]
|
||||
|
||||
if sum_scan_points > optimal_nozzle_points:
|
||||
optimal_nozzle_pattern = ['' for _ in range(max_head_index)]
|
||||
for head in range(max_head_index):
|
||||
if part := feeder_base[slot + head * interval_ratio] == -2:
|
||||
continue
|
||||
optimal_nozzle_pattern[head] = part_nozzle[part]
|
||||
|
||||
# 更新供料器占位信息
|
||||
for _, data in feeder_data.iterrows():
|
||||
feeder_base[data.slot] = -1
|
||||
@ -453,7 +441,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
# 绘制供料器位置布局
|
||||
for slot in range(max_slot_index // 2):
|
||||
plt.scatter(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1], marker='x', s=12, color='black', alpha=0.5)
|
||||
plt.text(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1] - 45, slot + 1, ha='center', va='bottom',
|
||||
plt.text(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1] - 45, str(slot + 1), ha='center', va='bottom',
|
||||
size=8)
|
||||
|
||||
feeder_assign_range = []
|
||||
@ -497,26 +485,31 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
|
||||
plt.ylim(-10, 100)
|
||||
plt.show()
|
||||
|
||||
return optimal_nozzle_pattern
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
|
||||
def feeder_base_scan(component_data, pcb_data, feeder_data):
|
||||
feeder_assign_check = set()
|
||||
for _, feeder in feeder_data.iterrows():
|
||||
feeder_assign_check.add(feeder.part)
|
||||
|
||||
component_points = [0] * len(component_data)
|
||||
for i, data in pcb_data.iterrows():
|
||||
part_index = component_data[component_data.part == data.part].index.tolist()[0]
|
||||
|
||||
component_points[part_index] += 1
|
||||
nozzle_type = component_data.loc[part_index].nz
|
||||
if nozzle_type not in nozzle_limit.keys() or nozzle_limit[nozzle_type] <= 0:
|
||||
info = 'there is no available nozzle [' + nozzle_type + '] for the assembly process'
|
||||
component_index = defaultdict(int)
|
||||
for idx, data in component_data.iterrows():
|
||||
if data.nz not in nozzle_limit.keys() or nozzle_limit[data.nz] <= 0:
|
||||
info = 'there is no available nozzle [' + data.nz + '] for the assembly process'
|
||||
raise ValueError(info)
|
||||
component_points[idx] = data.points
|
||||
component_index[data.part] = idx
|
||||
|
||||
assert len(feeder_assign_check) == len(component_points) - component_points.count(0) # 所有供料器均已分配槽位
|
||||
|
||||
mount_center_slot = defaultdict(float)
|
||||
for _, data in pcb_data.iterrows():
|
||||
part_index = component_index[data.part]
|
||||
mount_center_slot[part_index] += (data.x - mount_center_slot[part_index])
|
||||
|
||||
for idx, pos in mount_center_slot.items():
|
||||
mount_center_slot[idx] = (pos / component_points[idx] + stopper_pos[0] - slotf1_pos[0]) / slot_interval + 1
|
||||
|
||||
feeder_part = [-1] * max_slot_index
|
||||
for _, data in feeder_data.iterrows():
|
||||
component_index = component_data[component_data.part == data.part].index.tolist()
|
||||
@ -528,10 +521,20 @@ def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
|
||||
|
||||
component_result, cycle_result, feeder_slot_result = [], [], [] # 贴装点索引和拾取槽位优化结果
|
||||
|
||||
nozzle_mode = [nozzle_pattern] # 吸嘴匹配模式
|
||||
with tqdm(total=len(pcb_data)) as pbar:
|
||||
pbar.set_description('feeder scan process')
|
||||
pbar_prev = 0
|
||||
sum_nozzle_points, nozzle_pattern = -1, None
|
||||
for slot in range(max_slot_index // 2 - (max_head_index - 1) * interval_ratio):
|
||||
cur_nozzle_points, cur_nozzle_pattern = 0, ['' for _ in range(max_head_index)]
|
||||
for head in range(max_head_index):
|
||||
if (part := feeder_part[slot + head * interval_ratio]) == -1:
|
||||
continue
|
||||
cur_nozzle_pattern[head] = component_data.loc[part].nz
|
||||
cur_nozzle_points += component_points[part]
|
||||
if cur_nozzle_points > sum_nozzle_points:
|
||||
sum_nozzle_points = cur_nozzle_points
|
||||
nozzle_pattern = cur_nozzle_pattern
|
||||
|
||||
nozzle_mode, nozzle_mode_cycle = [nozzle_pattern], [0] # 吸嘴匹配模式
|
||||
|
||||
value_increment_base = 0
|
||||
while True:
|
||||
# === 周期内循环 ===
|
||||
@ -550,15 +553,18 @@ def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
|
||||
cur_nozzle_limit = copy.deepcopy(nozzle_limit)
|
||||
|
||||
while True:
|
||||
best_scan_part, best_scan_cycle = [-1 for _ in range(max_head_index)], [-1 for _ in
|
||||
range(max_head_index)]
|
||||
best_scan_part = [-1 for _ in range(max_head_index)]
|
||||
best_scan_cycle = [-1 for _ in range(max_head_index)]
|
||||
best_scan_slot = [-1 for _ in range(max_head_index)]
|
||||
best_scan_nozzle_limit = copy.deepcopy(cur_nozzle_limit)
|
||||
|
||||
best_scan_nozzle_limit = copy.deepcopy(cur_nozzle_limit)
|
||||
scan_eval_func, search_break = -float('inf'), True
|
||||
|
||||
# 前供料器基座扫描
|
||||
for slot in range(1, max_slot_index // 2 - (max_head_index - 1) * interval_ratio + 1):
|
||||
if sum(feeder_part[slot: slot + max_head_index * interval_ratio: interval_ratio]) == -max_head_index:
|
||||
continue
|
||||
|
||||
scan_cycle, scan_part, scan_slot = cur_scan_cycle.copy(), cur_scan_part.copy(), cur_scan_slot.copy()
|
||||
scan_nozzle_limit = copy.deepcopy(cur_nozzle_limit)
|
||||
|
||||
@ -658,7 +664,6 @@ def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
|
||||
|
||||
if component_counter == 0: # 当前情形下未扫描到任何元件
|
||||
continue
|
||||
|
||||
search_break = False
|
||||
|
||||
scan_part_head = defaultdict(list)
|
||||
@ -698,8 +703,22 @@ def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
|
||||
pick_cycle = list(filter(lambda c: c > 0, pick_cycle))
|
||||
eval_func_long_term -= e_nz_change * nozzle_counter
|
||||
|
||||
# 拾取过程中的移动路径
|
||||
pick_slot_set = set()
|
||||
for head, pick_slot in enumerate(scan_slot):
|
||||
if pick_slot == -1:
|
||||
continue
|
||||
pick_slot_set.add(pick_slot - head * interval_ratio)
|
||||
|
||||
slot_offset = 0
|
||||
for head, part in enumerate(scan_part):
|
||||
if part == -1:
|
||||
continue
|
||||
slot_offset += abs(scan_slot[head] - mount_center_slot[part])
|
||||
|
||||
ratio = 0.5
|
||||
eval_func = (1 - ratio) * eval_func_short_term + ratio * eval_func_long_term
|
||||
eval_func = (1 - ratio) * eval_func_short_term + ratio * eval_func_long_term - 1e-5 * (
|
||||
max(pick_slot_set) - min(pick_slot_set)) - 1e-5 * slot_offset
|
||||
if eval_func >= scan_eval_func:
|
||||
scan_eval_func = eval_func
|
||||
best_scan_part, best_scan_cycle = scan_part.copy(), scan_cycle.copy()
|
||||
@ -719,7 +738,7 @@ def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
|
||||
cur_nozzle_limit = copy.deepcopy(best_scan_nozzle_limit)
|
||||
|
||||
if len(scan_eval_func_list) != 0:
|
||||
if sum(scan_eval_func_list) >= best_assigned_eval_func:
|
||||
if sum(scan_eval_func_list) > best_assigned_eval_func:
|
||||
best_assigned_eval_func = sum(scan_eval_func_list)
|
||||
|
||||
assigned_part = cur_scan_part.copy()
|
||||
@ -739,9 +758,11 @@ def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
|
||||
continue
|
||||
component_points[feeder_part[slot]] -= min(nonzero_cycle)
|
||||
|
||||
component_result.insert(nozzle_insert_cycle, assigned_part)
|
||||
cycle_result.insert(nozzle_insert_cycle, min(nonzero_cycle))
|
||||
feeder_slot_result.insert(nozzle_insert_cycle, assigned_slot)
|
||||
insert_cycle = sum([nozzle_mode_cycle[c] for c in range(nozzle_insert_cycle + 1)])
|
||||
|
||||
component_result.insert(insert_cycle, assigned_part)
|
||||
cycle_result.insert(insert_cycle, min(nonzero_cycle))
|
||||
feeder_slot_result.insert(insert_cycle, assigned_slot)
|
||||
|
||||
# 更新吸嘴匹配模式
|
||||
cycle_nozzle = nozzle_mode[nozzle_insert_cycle].copy()
|
||||
@ -750,10 +771,12 @@ def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
|
||||
continue
|
||||
cycle_nozzle[head] = component_data.loc[component].nz
|
||||
|
||||
if cycle_nozzle == nozzle_mode[nozzle_insert_cycle]:
|
||||
nozzle_mode_cycle[nozzle_insert_cycle] += 1
|
||||
else:
|
||||
nozzle_mode.insert(nozzle_insert_cycle + 1, cycle_nozzle)
|
||||
nozzle_mode_cycle.insert(nozzle_insert_cycle + 1, 1)
|
||||
|
||||
pbar.update(len(pcb_data) - sum(component_points) - pbar_prev)
|
||||
pbar_prev = len(pcb_data) - sum(component_points)
|
||||
if sum(component_points) == 0:
|
||||
break
|
||||
|
||||
|
@ -12,38 +12,31 @@ from base_optimizer.result_analysis import *
|
||||
|
||||
def base_optimizer(machine_index, pcb_data, component_data, feeder_data=None, method='', hinter=False):
|
||||
|
||||
if method == 'cell_division': # 基于元胞分裂的遗传算法
|
||||
component_result, cycle_result, feeder_slot_result = optimizer_celldivision(pcb_data, component_data,
|
||||
hinter=False)
|
||||
if method == 'cell-division': # 基于元胞分裂的遗传算法
|
||||
component_result, cycle_result, feeder_slot_result = optimizer_celldivision(pcb_data, component_data)
|
||||
placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
|
||||
cycle_result, feeder_slot_result)
|
||||
elif method == 'feeder_scan': # 基于基座扫描的供料器优先算法
|
||||
# 第1步:分配供料器位置
|
||||
nozzle_pattern = feeder_allocate(component_data, pcb_data, feeder_data, figure=False)
|
||||
# 第2步:扫描供料器基座,确定元件拾取的先后顺序
|
||||
component_result, cycle_result, feeder_slot_result = feeder_base_scan(component_data, pcb_data, feeder_data,
|
||||
nozzle_pattern)
|
||||
|
||||
# 第3步:贴装路径规划
|
||||
elif method == 'feeder-scan': # 基于基座扫描的供料器优先算法
|
||||
component_result, cycle_result, feeder_slot_result = feeder_priority_assignment(component_data, pcb_data)
|
||||
placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
|
||||
cycle_result, feeder_slot_result)
|
||||
# placement_result, head_sequence = beam_search_for_route_generation(component_data, pcb_data, component_result,
|
||||
# cycle_result, feeder_slot_result)
|
||||
|
||||
elif method == 'hybrid_genetic': # 基于拾取组的混合遗传算法
|
||||
elif method == 'hybrid-genetic': # 基于拾取组的混合遗传算法
|
||||
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_hybrid_genetic(
|
||||
pcb_data, component_data, hinter=False)
|
||||
|
||||
elif method == 'aggregation': # 基于batch-level的整数规划 + 启发式算法
|
||||
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_aggregation(
|
||||
component_data, pcb_data)
|
||||
elif method == 'genetic_scanning':
|
||||
elif method == 'genetic-scanning':
|
||||
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_genetic_scanning(
|
||||
component_data, pcb_data, hinter=False)
|
||||
elif method == 'mip_model':
|
||||
elif method == 'mip-model':
|
||||
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_mathmodel(
|
||||
component_data, pcb_data, hinter=True)
|
||||
elif method == "two_phase":
|
||||
elif method == "two-phase":
|
||||
component_result, feeder_slot_result, cycle_result = gurobi_optimizer(pcb_data, component_data, feeder_data,
|
||||
initial=True, partition=True,
|
||||
reduction=True, hinter=hinter)
|
||||
@ -51,32 +44,11 @@ def base_optimizer(machine_index, pcb_data, component_data, feeder_data=None, me
|
||||
placement_result, head_sequence = scan_based_placement_route_generation(component_data, pcb_data,
|
||||
component_result, cycle_result)
|
||||
else:
|
||||
raise 'method is not existed'
|
||||
raise 'machine optimizer method ' + method + ' is not existed'
|
||||
|
||||
info = OptInfo()
|
||||
assigned_nozzle = ['' if idx == -1 else component_data.loc[idx]['nz'] for idx in component_result[0]]
|
||||
info.cycle_counter = sum(cycle_result)
|
||||
|
||||
for cycle in range(len(cycle_result)):
|
||||
pick_slot = set()
|
||||
for head in range(max_head_index):
|
||||
idx = component_result[cycle][head]
|
||||
if idx == -1:
|
||||
continue
|
||||
|
||||
nozzle = component_data.loc[idx]['nz']
|
||||
if nozzle != assigned_nozzle[head]:
|
||||
if assigned_nozzle[head] != '':
|
||||
info.nozzle_change_counter += 1
|
||||
assigned_nozzle[head] = nozzle
|
||||
|
||||
pick_slot.add(feeder_slot_result[cycle][head] - head * interval_ratio)
|
||||
info.pickup_counter += len(pick_slot) * cycle_result[cycle]
|
||||
|
||||
pick_slot = list(pick_slot)
|
||||
pick_slot.sort()
|
||||
for idx in range(len(pick_slot) - 1):
|
||||
info.pickup_movement += abs(pick_slot[idx + 1] - pick_slot[idx])
|
||||
# 估算贴装用时
|
||||
info = placement_info_evaluation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
placement_result, head_sequence, hinter=False)
|
||||
|
||||
if hinter:
|
||||
optimization_assign_result(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
@ -85,12 +57,11 @@ def base_optimizer(machine_index, pcb_data, component_data, feeder_data=None, me
|
||||
print('----- Placement machine ' + str(machine_index) + ' ----- ')
|
||||
print('-Cycle counter: {}'.format(info.cycle_counter))
|
||||
|
||||
print('-Nozzle change counter: {}'.format(info.nozzle_change_counter))
|
||||
print('-Pick operation counter: {}'.format(info.pickup_counter))
|
||||
print('-Pick movement: {}'.format(info.pickup_movement))
|
||||
print(f'-Nozzle change counter: {info.nozzle_change_counter: d}')
|
||||
print(f'-ANC round: {info.anc_round_counter: d}')
|
||||
print(f'-Pick operation counter: {info.pickup_counter: d}')
|
||||
print(f'-Pick time: {info.pickup_time: .3f}, distance: {info.pickup_distance: .3f}')
|
||||
print(f'-Place time: {info.place_time: .3f}, distance: {info.place_distance: .3f}')
|
||||
print('------------------------------ ')
|
||||
|
||||
# 估算贴装用时
|
||||
info.placement_time = placement_time_estimate(component_data, pcb_data, component_result, cycle_result,
|
||||
feeder_slot_result, placement_result, head_sequence, hinter=False)
|
||||
return info
|
||||
|
@ -423,7 +423,7 @@ def optimization_assign_result(component_data, pcb_data, component_result, cycle
|
||||
component_assign.loc[cycle, 'H{}'.format(head + 1)] = ''
|
||||
else:
|
||||
part = component_data.loc[index]['part']
|
||||
component_assign.loc[cycle, 'H{}'.format(head + 1)] = part
|
||||
component_assign.loc[cycle, 'H{}'.format(head + 1)] = 'C' + str(index)
|
||||
|
||||
print(component_assign)
|
||||
print('')
|
||||
@ -446,21 +446,25 @@ def optimization_assign_result(component_data, pcb_data, component_result, cycle
|
||||
print('')
|
||||
|
||||
|
||||
def placement_time_estimate(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
placement_result, head_sequence, hinter=True) -> float:
|
||||
def placement_info_evaluation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
placement_result=None, head_sequence=None, hinter=False):
|
||||
# === 优化结果参数 ===
|
||||
info = OptInfo()
|
||||
# === 校验 ===
|
||||
total_points = 0
|
||||
info.total_points = 0
|
||||
for cycle, components in enumerate(component_result):
|
||||
for head, component in enumerate(components):
|
||||
if component == -1:
|
||||
continue
|
||||
total_points += cycle_result[cycle]
|
||||
info.total_points += cycle_result[cycle]
|
||||
|
||||
if total_points != len(pcb_data):
|
||||
if info.total_points != len(pcb_data):
|
||||
warning_info = 'the number of placement points is not match with the PCB data. '
|
||||
warnings.warn(warning_info, UserWarning)
|
||||
return 0.
|
||||
|
||||
if placement_result:
|
||||
total_points = info.total_points
|
||||
for placements in placement_result:
|
||||
for placement in placements:
|
||||
if placement == -1:
|
||||
@ -486,12 +490,6 @@ def placement_time_estimate(component_data, pcb_data, component_result, cycle_re
|
||||
warnings.warn(info, UserWarning)
|
||||
return 0.
|
||||
|
||||
total_pickup_time, total_round_time, total_place_time = .0, .0, 0 # 拾取用时、往返用时、贴装用时
|
||||
total_operation_time = .0 # 操作用时
|
||||
total_nozzle_change_counter = 0 # 总吸嘴更换次数
|
||||
total_pick_counter = 0 # 总拾取次数
|
||||
total_mount_distance, total_pick_distance = .0, .0 # 贴装距离、拾取距离
|
||||
total_distance = 0 # 总移动距离
|
||||
cur_pos, next_pos = anc_marker_pos, [0, 0] # 贴装头当前位置
|
||||
|
||||
# 初始化首个周期的吸嘴装配信息
|
||||
@ -503,7 +501,6 @@ def placement_time_estimate(component_data, pcb_data, component_result, cycle_re
|
||||
continue
|
||||
else:
|
||||
nozzle_assigned[head] = component_data.loc[idx]['nz']
|
||||
break
|
||||
|
||||
for cycle_set, _ in enumerate(component_result):
|
||||
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
|
||||
@ -527,9 +524,9 @@ def placement_time_estimate(component_data, pcb_data, component_result, cycle_re
|
||||
next_pos = anc_marker_pos
|
||||
move_time = max(axis_moving_time(cur_pos[0] - next_pos[0], 0),
|
||||
axis_moving_time(cur_pos[1] - next_pos[1], 1))
|
||||
total_round_time += move_time
|
||||
|
||||
total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
info.round_time += move_time
|
||||
info.anc_round_counter += 1
|
||||
info.total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
cur_pos = next_pos
|
||||
|
||||
pick_slot = list(set(pick_slot))
|
||||
@ -541,37 +538,38 @@ def placement_time_estimate(component_data, pcb_data, component_result, cycle_re
|
||||
next_pos = [slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1]]
|
||||
else:
|
||||
next_pos = [slotr1_pos[0] - slot_interval * (max_slot_index - slot - 1), slotr1_pos[1]]
|
||||
total_operation_time += t_pick
|
||||
total_pick_counter += 1
|
||||
info.operation_time += t_pick
|
||||
info.pickup_counter += 1
|
||||
|
||||
move_time = max(axis_moving_time(cur_pos[0] - next_pos[0], 0),
|
||||
axis_moving_time(cur_pos[1] - next_pos[1], 1))
|
||||
if idx == 0:
|
||||
total_round_time += move_time
|
||||
info.round_time += move_time
|
||||
else:
|
||||
total_pickup_time += move_time
|
||||
info.pickup_time += move_time
|
||||
|
||||
total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
info.total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
if slot != pick_slot[0]:
|
||||
total_pick_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
info.pickup_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
cur_pos = next_pos
|
||||
|
||||
# 固定相机检测
|
||||
for head in range(max_head_index):
|
||||
if component_result[cycle_set][head] == -1:
|
||||
continue
|
||||
camera = component_data.loc[component_result[cycle_set][head]]['camera']
|
||||
if camera == '固定相机':
|
||||
next_pos = [fix_camera_pos[0] - head * head_interval, fix_camera_pos[1]]
|
||||
move_time = max(axis_moving_time(cur_pos[0] - next_pos[0], 0),
|
||||
axis_moving_time(cur_pos[1] - next_pos[1], 1))
|
||||
total_round_time += move_time
|
||||
|
||||
total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
total_operation_time += t_fix_camera_check
|
||||
cur_pos = next_pos
|
||||
# for head in range(max_head_index):
|
||||
# if component_result[cycle_set][head] == -1:
|
||||
# continue
|
||||
# camera = component_data.loc[component_result[cycle_set][head]]['camera']
|
||||
# if camera == '固定相机':
|
||||
# next_pos = [fix_camera_pos[0] - head * head_interval, fix_camera_pos[1]]
|
||||
# move_time = max(axis_moving_time(cur_pos[0] - next_pos[0], 0),
|
||||
# axis_moving_time(cur_pos[1] - next_pos[1], 1))
|
||||
# info.round_time += move_time
|
||||
#
|
||||
# info.total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
|
||||
# info.operation_time += t_fix_camera_check
|
||||
# cur_pos = next_pos
|
||||
|
||||
# 贴装路径
|
||||
if placement_result and head_sequence:
|
||||
for head in head_sequence[cycle]:
|
||||
index = placement_result[cycle][head]
|
||||
if index == -1:
|
||||
@ -582,53 +580,53 @@ def placement_time_estimate(component_data, pcb_data, component_result, cycle_re
|
||||
|
||||
# 单独计算贴装路径
|
||||
for cntPoints in range(len(mount_pos) - 1):
|
||||
total_mount_distance += max(abs(mount_pos[cntPoints][0] - mount_pos[cntPoints + 1][0]),
|
||||
info.place_distance += max(abs(mount_pos[cntPoints][0] - mount_pos[cntPoints + 1][0]),
|
||||
abs(mount_pos[cntPoints][1] - mount_pos[cntPoints + 1][1]))
|
||||
|
||||
# 考虑R轴预旋转,补偿同轴角度转动带来的额外贴装用时
|
||||
total_operation_time += head_rotary_time(mount_angle[0]) # 补偿角度转动带来的额外贴装用时
|
||||
total_operation_time += t_nozzle_put * nozzle_put_counter + t_nozzle_pick * nozzle_pick_counter
|
||||
info.operation_time += head_rotary_time(mount_angle[0]) # 补偿角度转动带来的额外贴装用时
|
||||
info.operation_time += t_nozzle_put * nozzle_put_counter + t_nozzle_pick * nozzle_pick_counter
|
||||
for idx, pos in enumerate(mount_pos):
|
||||
total_operation_time += t_place
|
||||
info.operation_time += t_place
|
||||
move_time = max(axis_moving_time(cur_pos[0] - pos[0], 0), axis_moving_time(cur_pos[1] - pos[1], 1))
|
||||
if idx == 0:
|
||||
total_round_time += move_time
|
||||
info.round_time += move_time
|
||||
else:
|
||||
total_place_time += move_time
|
||||
info.place_time += move_time
|
||||
|
||||
total_distance += max(abs(cur_pos[0] - pos[0]), abs(cur_pos[1] - pos[1]))
|
||||
info.total_distance += max(abs(cur_pos[0] - pos[0]), abs(cur_pos[1] - pos[1]))
|
||||
cur_pos = pos
|
||||
|
||||
total_nozzle_change_counter += nozzle_put_counter + nozzle_pick_counter
|
||||
|
||||
total_time = total_pickup_time + total_round_time + total_place_time + total_operation_time
|
||||
minutes, seconds = int(total_time // 60), int(total_time) % 60
|
||||
millisecond = int((total_time - minutes * 60 - seconds) * 60)
|
||||
info.nozzle_change_counter += nozzle_put_counter + nozzle_pick_counter
|
||||
|
||||
info.total_time = info.pickup_time + info.round_time + info.place_time + info.operation_time
|
||||
minutes, seconds = int(info.total_time // 60), int(info.total_time) % 60
|
||||
millisecond = int((info.total_time - minutes * 60 - seconds) * 60)
|
||||
info.cycle_counter = sum(cycle_result)
|
||||
if hinter:
|
||||
optimization_assign_result(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
nozzle_hinter=False, component_hinter=False, feeder_hinter=False)
|
||||
|
||||
print('-Cycle counter: {}'.format(sum(cycle_result)))
|
||||
print('-Nozzle change counter: {}'.format(total_nozzle_change_counter // 2))
|
||||
print('-Pick operation counter: {}'.format(total_pick_counter))
|
||||
print('-Cycle counter: {}'.format(info.cycle_counter))
|
||||
print('-Nozzle change counter: {}'.format(info.nozzle_change_counter // 2))
|
||||
print('-Pick operation counter: {}'.format(info.pickup_counter))
|
||||
|
||||
print('-Expected mounting tour length: {} mm'.format(total_mount_distance))
|
||||
print('-Expected picking tour length: {} mm'.format(total_pick_distance))
|
||||
print('-Expected total tour length: {} mm'.format(total_distance))
|
||||
print('-Expected mounting tour length: {} mm'.format(info.place_distance))
|
||||
print('-Expected picking tour length: {} mm'.format(info.pickup_distance))
|
||||
print('-Expected total tour length: {} mm'.format(info.total_distance))
|
||||
|
||||
print('-Expected total moving time: {} s with pick: {}, round: {}, place = {}'.format(
|
||||
total_pickup_time + total_round_time + total_place_time, total_pickup_time, total_round_time,
|
||||
total_place_time))
|
||||
print('-Expected total operation time: {} s'.format(total_operation_time))
|
||||
info.pickup_time + info.round_time + info.place_time, info.pickup_time, info.round_time,
|
||||
info.place_time))
|
||||
print('-Expected total operation time: {} s'.format(info.operation_time))
|
||||
|
||||
if minutes > 0:
|
||||
print('-Mounting time estimation: {:d} min {} s {:2d} ms ({:.3f}s)'.format(minutes, seconds, millisecond,
|
||||
total_time))
|
||||
info.total_time))
|
||||
else:
|
||||
print('-Mounting time estimation: {} s {:2d} ms ({:.3f}s)'.format(seconds, millisecond, total_time))
|
||||
print('-Mounting time estimation: {} s {:2d} ms ({:.3f}s)'.format(seconds, millisecond, info.total_time))
|
||||
|
||||
return total_time
|
||||
return info
|
||||
|
||||
|
||||
|
||||
|
@ -2,10 +2,39 @@ from base_optimizer.optimizer_common import *
|
||||
|
||||
|
||||
def load_data(filename: str, default_feeder_limit=1, load_cp_data=True, load_feeder_data=True, cp_auto_register=False):
|
||||
|
||||
# 读取PCB数据
|
||||
filename = 'data/' + filename
|
||||
pcb_data = pd.DataFrame(pd.read_csv(filepath_or_buffer=filename, sep='\t', header=None))
|
||||
part_content, step_content = False, False
|
||||
part_start_line, step_start_line, part_end_line, step_end_line = -1, -1, -1, -1
|
||||
line_counter = 0
|
||||
with open(filename, 'r') as file:
|
||||
line = file.readline()
|
||||
while line:
|
||||
if line == '[Part]\n':
|
||||
part_content = True
|
||||
part_start_line = line_counter
|
||||
elif line == '[Step]\n':
|
||||
step_content = True
|
||||
step_start_line = line_counter
|
||||
elif line == '\n':
|
||||
if part_content:
|
||||
part_content = False
|
||||
part_end_line = line_counter
|
||||
elif step_content:
|
||||
step_content = False
|
||||
step_end_line = line_counter
|
||||
line_counter += 1
|
||||
line = file.readline()
|
||||
|
||||
if part_content:
|
||||
part_end_line = line_counter
|
||||
elif step_content:
|
||||
step_end_line = line_counter
|
||||
|
||||
pcb_data = pd.DataFrame(
|
||||
pd.read_csv(filepath_or_buffer=filename, skiprows=step_start_line + 1, nrows=step_end_line - step_start_line + 1,
|
||||
sep='\t', header=None))
|
||||
|
||||
if len(pcb_data.columns) <= 17:
|
||||
step_col = ["ref", "x", "y", "z", "r", "part", "desc", "fdr", "nz", "hd", "cs", "cy", "sk", "bl", "ar",
|
||||
"pl", "lv"]
|
||||
@ -21,20 +50,25 @@ def load_data(filename: str, default_feeder_limit=1, load_cp_data=True, load_fee
|
||||
|
||||
# 坐标系处理
|
||||
# pcb_data = pcb_data.sort_values(by = ['x', 'y'], ascending = True)
|
||||
# pcb_data["x"] = pcb_data["x"].apply(lambda x: -x)
|
||||
# pcb_data["x"] = pcb_data["x"].apply(lambda x: -100+x)
|
||||
|
||||
# 注册元件检查
|
||||
part_feeder_assign = defaultdict(set)
|
||||
part_col = ["part", "desc", "fdr", "nz", 'camera', 'group', 'feeder-limit', 'points']
|
||||
part_col = ["part", "fdr", "nz", 'feeder-limit']
|
||||
|
||||
try:
|
||||
if load_cp_data:
|
||||
component_data = pd.DataFrame(pd.read_csv(filepath_or_buffer='component.txt', sep='\t', header=None),
|
||||
columns=part_col)
|
||||
if part_start_line != -1:
|
||||
component_data = pd.DataFrame(
|
||||
pd.read_csv(filepath_or_buffer=filename, sep='\t', header=None, skiprows=part_start_line + 1,
|
||||
nrows=part_end_line - part_start_line - 1))
|
||||
component_data.columns = part_col
|
||||
else:
|
||||
component_data = pd.DataFrame(columns=part_col)
|
||||
except:
|
||||
component_data = pd.DataFrame(columns=part_col)
|
||||
|
||||
component_data['points'] = 0
|
||||
part_col.append('points')
|
||||
for _, data in pcb_data.iterrows():
|
||||
part, nozzle = data.part, data.nz.split(' ')[1]
|
||||
slot = data['fdr'].split(' ')[0]
|
||||
@ -43,10 +77,10 @@ def load_data(filename: str, default_feeder_limit=1, load_cp_data=True, load_fee
|
||||
raise Exception("unregistered component: " + component_data['part'].values)
|
||||
else:
|
||||
component_data = pd.concat([component_data, pd.DataFrame(
|
||||
[part, '', 'SM8', nozzle, '飞行相机1', 'CHIP-Rect', default_feeder_limit, 0], index=part_col).T],
|
||||
[part, '', 'SM8', nozzle, default_feeder_limit, 0], index=part_col).T],
|
||||
ignore_index=True)
|
||||
# warning_info = 'register component ' + part + ' with default feeder type'
|
||||
# warnings.warn(warning_info, UserWarning)
|
||||
warning_info = 'register component ' + part + ' with default feeder type'
|
||||
warnings.warn(warning_info, UserWarning)
|
||||
part_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
part_feeder_assign[part].add(slot)
|
||||
component_data.loc[part_index, 'points'] += 1
|
||||
@ -54,7 +88,8 @@ def load_data(filename: str, default_feeder_limit=1, load_cp_data=True, load_fee
|
||||
if nozzle != 'A' and component_data.loc[part_index, 'nz'] != nozzle:
|
||||
warning_info = 'the nozzle type of component ' + part + ' is not consistent with the pcb data'
|
||||
warnings.warn(warning_info, UserWarning)
|
||||
|
||||
# 清除点数为0的数据
|
||||
component_data = component_data[component_data['points'] != 0].reset_index(drop=True)
|
||||
for idx, data in component_data.iterrows():
|
||||
if data['fdr'][0:3] == 'SME': # 电动供料器和气动供料器参数一致
|
||||
component_data.at[idx, 'fdr'] = data['fdr'][0:2] + data['fdr'][3:]
|
||||
|
241
estimator.py
Normal file
241
estimator.py
Normal file
@ -0,0 +1,241 @@
|
||||
from generator import *
|
||||
from base_optimizer.optimizer_interface import *
|
||||
|
||||
|
||||
class Net(torch.nn.Module):
|
||||
def __init__(self, input_size, hidden_size=1000, output_size=1):
|
||||
super(Net, self).__init__()
|
||||
self.fc1 = torch.nn.Linear(input_size, hidden_size)
|
||||
self.relu = torch.nn.ReLU() # 激活函数
|
||||
self.fc2 = torch.nn.Linear(hidden_size, hidden_size)
|
||||
# self.relu1 = torch.nn.ReLU() # 激活函数
|
||||
self.fc3 = torch.nn.Linear(hidden_size, output_size)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.fc1(x)
|
||||
# x = self.relu(x)
|
||||
x = self.fc2(x)
|
||||
x = self.relu(x)
|
||||
x = self.fc3(x)
|
||||
return x
|
||||
|
||||
|
||||
class LSTMNet(torch.nn.Module):
|
||||
def __init__(self, input_size, hidden_size=256, output_size=1, num_layers=1):
|
||||
super(LSTMNet, self).__init__()
|
||||
|
||||
self.lstm = torch.nn.LSTM(input_size, hidden_size, num_layers)
|
||||
self.fc = torch.nn.Linear(hidden_size, output_size)
|
||||
|
||||
def forward(self, x):
|
||||
x, _ = self.lstm(x) # x is input with size (seq_len, batch_size, input_size)
|
||||
x = self.fc(x)
|
||||
return x[-1, :, ]
|
||||
|
||||
|
||||
class Estimator:
|
||||
def __init__(self, task_block_weight=None):
|
||||
self.data_mgr = DataMgr()
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
|
||||
self.net = Net(input_size=self.data_mgr.get_feature(), output_size=1).to(device)
|
||||
self.net.load_state_dict(torch.load('model/net_model.pth'))
|
||||
self.task_block_weight = task_block_weight
|
||||
|
||||
with open('model/lr_model.pkl', 'rb') as f:
|
||||
self.lr = pickle.load(f)
|
||||
|
||||
def convert(self, pcb_data, component_data, assignment_result):
|
||||
machine_num, component_num = len(assignment_result), len(component_data)
|
||||
|
||||
component_machine_index = [0 for _ in range(component_num)]
|
||||
machine_points = [[[] for _ in range(component_num)] for _ in range(machine_num)]
|
||||
|
||||
component2idx = defaultdict(int)
|
||||
for i, data in component_data.iterrows():
|
||||
component2idx[data.part] = i
|
||||
|
||||
for i in range(len(pcb_data)):
|
||||
part_index = component2idx[pcb_data.iat[i, 5]]
|
||||
while True:
|
||||
machine_index = component_machine_index[part_index]
|
||||
if assignment_result[machine_index][part_index] == len(machine_points[machine_index][part_index]):
|
||||
component_machine_index[part_index] += 1
|
||||
machine_index += 1
|
||||
else:
|
||||
break
|
||||
|
||||
for _, data in pcb_data.iterrows():
|
||||
part_index = component2idx[data.part]
|
||||
while True:
|
||||
machine_index = component_machine_index[part_index]
|
||||
if assignment_result[machine_index][part_index] == len(machine_points[machine_index][part_index]):
|
||||
component_machine_index[part_index] += 1
|
||||
machine_index += 1
|
||||
else:
|
||||
break
|
||||
machine_points[machine_index][part_index].append([data.x, data.y])
|
||||
|
||||
res = []
|
||||
for machine_index in range(machine_num):
|
||||
cp_points, cp_nozzle = defaultdict(int), defaultdict(str)
|
||||
cp_width, cp_height = defaultdict(float), defaultdict(float)
|
||||
board_right_pos, board_left_pos, board_top_pos, board_bottom_pos = None, None, None, None
|
||||
|
||||
for part_index in range(component_num):
|
||||
if assignment_result[machine_index][part_index] == 0:
|
||||
continue
|
||||
|
||||
cp_points[part_index] = assignment_result[machine_index][part_index]
|
||||
cp_nozzle[part_index] = component_data.iloc[part_index]['nz']
|
||||
|
||||
cp_right_pos, cp_left_pos = max([p[0] for p in machine_points[machine_index][part_index]]), min(
|
||||
[p[0] for p in machine_points[machine_index][part_index]])
|
||||
|
||||
cp_top_pos, cp_bottom_pos = max([p[1] for p in machine_points[machine_index][part_index]]), min(
|
||||
[p[1] for p in machine_points[machine_index][part_index]])
|
||||
|
||||
cp_width[part_index] = cp_right_pos - cp_left_pos
|
||||
cp_height[part_index] = cp_top_pos - cp_bottom_pos
|
||||
|
||||
if board_right_pos is None or cp_right_pos > board_right_pos:
|
||||
board_right_pos = cp_right_pos
|
||||
|
||||
if board_left_pos is None or cp_left_pos < board_left_pos:
|
||||
board_left_pos = cp_left_pos
|
||||
|
||||
if board_top_pos is None or cp_top_pos > board_top_pos:
|
||||
board_top_pos = cp_top_pos
|
||||
|
||||
if board_bottom_pos is None or cp_bottom_pos < board_bottom_pos:
|
||||
board_bottom_pos = cp_bottom_pos
|
||||
|
||||
res.append([cp_points, cp_nozzle, cp_width, cp_height, board_right_pos - board_left_pos,
|
||||
board_top_pos - board_bottom_pos])
|
||||
return res
|
||||
|
||||
def neural_network(self, cp_points, cp_nozzle, board_width, board_height):
|
||||
encoding = np.array(self.data_mgr.encode(cp_points, cp_nozzle, board_width, board_height))
|
||||
encoding = torch.from_numpy(encoding.reshape((-1, np.shape(encoding)[0]))).float().to("cuda")
|
||||
return self.net(encoding)[0, 0].item()
|
||||
|
||||
def heuristic_reconfiguration(self, cp_points, cp_nozzle):
|
||||
task_block_number, total_point_number = 0, sum(cp_points.values())
|
||||
nozzle_points, nozzle_heads = defaultdict(int), defaultdict(int)
|
||||
|
||||
for part, points in cp_points.items():
|
||||
nozzle_points[cp_nozzle[part]] += points
|
||||
nozzle_heads[cp_nozzle[part]] = 1
|
||||
remaining_head = max_head_index - len(nozzle_heads)
|
||||
|
||||
nozzle_fraction = []
|
||||
for nozzle, points in nozzle_points.items():
|
||||
val = remaining_head * points / total_point_number
|
||||
nozzle_heads[nozzle] += math.floor(val)
|
||||
nozzle_fraction.append([nozzle, val - math.floor(val)])
|
||||
|
||||
remaining_head = max_head_index - sum(nozzle_heads.values())
|
||||
sorted(nozzle_fraction, key=lambda x: x[1])
|
||||
nozzle_fraction_index = 0
|
||||
while remaining_head > 0:
|
||||
nozzle_heads[nozzle_fraction[nozzle_fraction_index][0]] += 1
|
||||
remaining_head -= 1
|
||||
|
||||
for nozzle, heads_number in nozzle_heads.items():
|
||||
task_block_number = max(self.task_block_weight, math.ceil(nozzle_points[nozzle] / heads_number))
|
||||
|
||||
return (t_pick + t_place) * total_point_number + task_block_number * self.task_block_weight
|
||||
|
||||
def heuristic_genetic(self, cp_points, cp_nozzle):
|
||||
nozzle_points, nozzle_component_points = defaultdict(int), defaultdict(list)
|
||||
for idx, nozzle in cp_nozzle.items():
|
||||
if cp_points[idx] == 0:
|
||||
continue
|
||||
nozzle_points[nozzle] += cp_points[idx]
|
||||
|
||||
nozzle_component_points[cp_nozzle[idx]] = [0] * len(cp_points)
|
||||
|
||||
for idx, (part_index, points) in enumerate(cp_points.items()):
|
||||
nozzle_component_points[cp_nozzle[part_index]][idx] = points
|
||||
|
||||
total_points = sum(cp_points.values()) # num of placement points
|
||||
ul = math.ceil(len(nozzle_points) * 1.0 / max_head_index) - 1 # num of nozzle set
|
||||
|
||||
# assignments of nozzles to heads
|
||||
wl = 0 # num of workload
|
||||
total_heads = (1 + ul) * max_head_index - len(nozzle_points)
|
||||
nozzle_heads = defaultdict(int)
|
||||
for nozzle in nozzle_points.keys():
|
||||
if nozzle_points[nozzle] == 0:
|
||||
continue
|
||||
nozzle_heads[nozzle] = math.floor(nozzle_points[nozzle] * 1.0 / total_points * total_heads)
|
||||
nozzle_heads[nozzle] += 1
|
||||
|
||||
total_heads = (1 + ul) * max_head_index
|
||||
for heads in nozzle_heads.values():
|
||||
total_heads -= heads
|
||||
|
||||
while True:
|
||||
nozzle = max(nozzle_heads, key=lambda x: nozzle_points[x] / nozzle_heads[x])
|
||||
if total_heads == 0:
|
||||
break
|
||||
nozzle_heads[nozzle] += 1
|
||||
total_heads -= 1
|
||||
|
||||
# averagely assign placements to heads
|
||||
heads_placement = []
|
||||
for nozzle in nozzle_heads.keys():
|
||||
points = math.floor(nozzle_points[nozzle] / nozzle_heads[nozzle])
|
||||
|
||||
heads_placement += [[nozzle, points] for _ in range(nozzle_heads[nozzle])]
|
||||
nozzle_points[nozzle] -= (nozzle_heads[nozzle] * points)
|
||||
for idx in range(len(heads_placement) - 1, -1, -1):
|
||||
if nozzle_points[nozzle] <= 0:
|
||||
break
|
||||
nozzle_points[nozzle] -= 1
|
||||
heads_placement[idx][1] += 1
|
||||
heads_placement = sorted(heads_placement, key=lambda x: x[1], reverse=True)
|
||||
|
||||
# the number of pick-up operations
|
||||
# (under the assumption of the number of feeder available for each comp. type is equal 1)
|
||||
pl = 0
|
||||
heads_placement_points = [0 for _ in range(max_head_index)]
|
||||
while True:
|
||||
head_assign_point = []
|
||||
for head in range(max_head_index):
|
||||
if heads_placement_points[head] != 0 or heads_placement[head] == 0:
|
||||
continue
|
||||
|
||||
nozzle, points = heads_placement[head]
|
||||
max_comp_index = np.argmax(nozzle_component_points[nozzle])
|
||||
|
||||
heads_placement_points[head] = min(points, nozzle_component_points[nozzle][max_comp_index])
|
||||
nozzle_component_points[nozzle][max_comp_index] -= heads_placement_points[head]
|
||||
|
||||
head_assign_point.append(heads_placement_points[head])
|
||||
|
||||
min_points_list = list(filter(lambda x: x > 0, heads_placement_points))
|
||||
if len(min_points_list) == 0 or len(head_assign_point) == 0:
|
||||
break
|
||||
|
||||
pl += max(head_assign_point)
|
||||
|
||||
for head in range(max_head_index):
|
||||
heads_placement[head][1] -= min(min_points_list)
|
||||
heads_placement_points[head] -= min(min_points_list)
|
||||
|
||||
# every max_head_index heads in the non-decreasing order are grouped together as nozzle set
|
||||
for idx in range(len(heads_placement) // max_head_index):
|
||||
wl += heads_placement[idx][1]
|
||||
|
||||
return T_pp * total_points + T_tr * wl + T_nc * ul + T_pl * pl
|
||||
|
||||
def linear_regression(self, pcb_data, component_data):
|
||||
component_result, cycle_result, feeder_slot_result = feeder_priority_assignment(component_data, pcb_data,
|
||||
hinter=False)
|
||||
|
||||
info = placement_info_evaluation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result)
|
||||
regression_info = [[info.cycle_counter, info.nozzle_change_counter, info.anc_round_counter,
|
||||
info.pickup_counter, info.total_points]]
|
||||
return self.lr.predict(regression_info)[0, 0]
|
||||
|
358
generator.py
358
generator.py
@ -11,31 +11,31 @@ class DataMgr:
|
||||
self.min_placement_points = 100
|
||||
self.max_placement_points = 800
|
||||
|
||||
self.max_component_types = 30
|
||||
self.max_component_types = 40
|
||||
self.default_feeder_limit = 1
|
||||
self.nozzle_type_list = ['CN065', 'CN140', 'CN220', 'CN040']
|
||||
self.max_nozzle_types = 4
|
||||
|
||||
# self.x_range = [50, 100, 150, 200, 300, 400, 500]
|
||||
# self.y_range = [50, 100, 150, 200, 300, 400, 500]
|
||||
|
||||
self.x_range = [400]
|
||||
self.y_range = [200]
|
||||
self.x_range = [50, 100, 150, 200, 300, 400, 500]
|
||||
self.y_range = [50, 100, 150, 200, 300, 400, 500]
|
||||
|
||||
self.counter = 0
|
||||
self.update = 10
|
||||
self.update = 1
|
||||
self.pre_file = None
|
||||
self.part_col = ["part", "desc", "fdr", "nz", 'camera', 'group', 'feeder-limit', 'points']
|
||||
self.component_data = pd.DataFrame(columns=self.part_col) # the component list update for several rounds
|
||||
|
||||
def generator(self, mode='Train'):
|
||||
boundary = [random.choice(self.x_range), random.choice(self.y_range)]
|
||||
if boundary[0] < boundary[-1]:
|
||||
boundary[0], boundary[-1] = boundary[-1], boundary[0]
|
||||
|
||||
nozzle_type_list = random.sample(['CN065', 'CN220', 'CN040', 'CN140'], self.max_nozzle_types)
|
||||
# determine the nozzle type of component
|
||||
if self.counter % 10 == 0 or mode == 'test':
|
||||
if self.counter % self.get_update_round() == 0 or mode == 'test':
|
||||
self.component_data = self.component_data.loc[[]]
|
||||
total_points = random.randint(self.min_placement_points, self.max_placement_points)
|
||||
total_nozzles = random.randint(1, len(self.nozzle_type_list))
|
||||
selected_nozzle = random.sample(self.nozzle_type_list, total_nozzles)
|
||||
total_nozzles = random.randint(1, self.max_nozzle_types)
|
||||
selected_nozzle = random.sample(nozzle_type_list, total_nozzles)
|
||||
for cp_idx in range(min(random.randint(1, self.max_component_types), total_points)):
|
||||
part, nozzle = 'C' + str(cp_idx), random.choice(selected_nozzle)
|
||||
self.component_data = pd.concat([self.component_data, pd.DataFrame(
|
||||
@ -63,17 +63,19 @@ class DataMgr:
|
||||
return pcb_data, self.component_data
|
||||
|
||||
def recorder(self, file_handle, info: OptInfo, pcb_data, component_data):
|
||||
lineinfo = '{:.6f}'.format(info.placement_time) + '\t' + str(info.cycle_counter) + '\t' + str(
|
||||
info.nozzle_change_counter) + '\t' + str(info.pickup_counter) + '\t' + '{:.3f}'.format(
|
||||
info.pickup_movement) + '\t' + '{:.3f}'.format(info.placement_movement)
|
||||
# 7个参数:总时间,周期数,吸嘴更换数,ANC往返次数,拾取次数,拾取路径,贴装路径
|
||||
lineinfo = '{:.3f}'.format(info.total_time) + '\t' + str(info.cycle_counter) + '\t' + str(
|
||||
info.nozzle_change_counter) + '\t' + str(info.anc_round_counter) + '\t' + str(
|
||||
info.pickup_counter) + '\t' + '{:.3f}'.format(info.pickup_distance) + '\t' + '{:.3f}'.format(
|
||||
info.place_distance)
|
||||
|
||||
# 2个参数: PCB尺寸
|
||||
lineinfo += '\t' + '{:.3f}'.format(pcb_data['x'].max() - pcb_data['x'].min()) + '\t' + '{:.3f}'.format(
|
||||
pcb_data['y'].max() - pcb_data['y'].min())
|
||||
|
||||
part_xposition, part_yposition = defaultdict(list), defaultdict(list)
|
||||
for _, data in pcb_data.iterrows():
|
||||
part_xposition[data['part']].append(data['x'])
|
||||
part_yposition[data['part']].append(data['y'])
|
||||
# part_position = defaultdict(list)
|
||||
# for _, data in pcb_data.iterrows():
|
||||
# part_position[data['part']].append([data['x'], data['y']])
|
||||
|
||||
point_counter, component_counter = 0, 0
|
||||
nozzle_type = set()
|
||||
@ -84,17 +86,20 @@ class DataMgr:
|
||||
point_counter += data.points
|
||||
component_counter += 1
|
||||
|
||||
# 3个参数:总点数,总元件数,总吸嘴数
|
||||
lineinfo += '\t' + str(point_counter) + '\t' + str(component_counter) + '\t' + str(len(nozzle_type))
|
||||
|
||||
# 5 x 元件种类数 个参数: 元件名,吸嘴类型,点数,布局宽度,布局高度
|
||||
for _, data in component_data.iterrows():
|
||||
if data.points == 0:
|
||||
continue
|
||||
lineinfo += '\t' + data.part + '\t' + data.nz + '\t' + str(data.points)
|
||||
# lineinfo += '\t' + str(
|
||||
# round((np.average(part_xposition[data.part]) + stopper_pos[0] - slotf1_pos[0]) / slot_interval))
|
||||
# lineinfo += '\t' + '{:.3f}'.format(np.ptp([pos[0] for pos in part_position[data.part]]))
|
||||
# lineinfo += '\t' + '{:.3f}'.format(np.ptp([pos[1] for pos in part_position[data.part]]))
|
||||
|
||||
lineinfo += '\n'
|
||||
file_handle.write(lineinfo)
|
||||
|
||||
|
||||
def saver(self, file_path: str, pcb_data):
|
||||
lineinfo = ''
|
||||
for _, data in pcb_data.iterrows():
|
||||
@ -112,43 +117,260 @@ class DataMgr:
|
||||
os.remove(self.pre_file)
|
||||
self.pre_file = None
|
||||
|
||||
def encode(self, cp_points: defaultdict[str], cp_nozzle: defaultdict[int], width, height):
|
||||
cp2nz = defaultdict(int)
|
||||
for idx, nozzle in enumerate(self.nozzle_type_list):
|
||||
cp2nz[nozzle] = idx
|
||||
def encode(self, cp_points: defaultdict[str], cp_nozzle: defaultdict[str], board_width, board_height):
|
||||
|
||||
# === general info ===
|
||||
total_points = sum(points for points in cp_points.values())
|
||||
total_component_types, total_nozzle_types = len(cp_points.keys()), len(set(cp_nozzle.values()))
|
||||
|
||||
data = [total_points, total_component_types, total_nozzle_types]
|
||||
data.extend([width, height])
|
||||
data.extend([board_width, board_height])
|
||||
|
||||
# === heuristic info ===
|
||||
cycle, nozzle_change, anc_move, pickup = self.heuristic_estimator(cp_points, cp_nozzle)
|
||||
data.extend([cycle, nozzle_change, anc_move, pickup])
|
||||
|
||||
# === nozzle info ===
|
||||
data_slice = [0 for _ in range(len(self.nozzle_type_list))]
|
||||
for component, points in cp_points.items():
|
||||
idx = cp2nz[cp_nozzle[component]]
|
||||
data_slice[idx] += points
|
||||
data.extend(data_slice)
|
||||
nozzle_points = defaultdict(int)
|
||||
for cp_idx, nozzle in cp_nozzle.items():
|
||||
nozzle_points[cp_nozzle[cp_idx]] += cp_points[cp_idx] # points for different nozzle type
|
||||
nozzle_items = [[nozzle, points] for nozzle, points in nozzle_points.items()]
|
||||
nozzle_items = sorted(nozzle_items, key=lambda x: x[1], reverse=True)
|
||||
|
||||
nz2idx = defaultdict(int)
|
||||
nozzle_slice = [0 for _ in range(self.max_nozzle_types)]
|
||||
for idx, [nozzle, points] in enumerate(nozzle_items):
|
||||
nz2idx[nozzle] = idx
|
||||
nozzle_slice[idx] = points
|
||||
|
||||
data.extend(nozzle_slice)
|
||||
|
||||
# === component info ===
|
||||
# cp_items = [[component, points] for component, points in cp_points.items()]
|
||||
# cp_items = sorted(cp_items, key=lambda x: (x[1], nz2idx[cp_nozzle[x[0]]] * 0.1 + x[1]), reverse=True)
|
||||
# for component, points in cp_items:
|
||||
# nozzle = cp_nozzle[component]
|
||||
#
|
||||
# data_slice = [0 for _ in range(self.max_nozzle_types)]
|
||||
# data_slice[nz2idx[nozzle]] = points
|
||||
# data.extend(data_slice)
|
||||
#
|
||||
# assert self.max_component_types >= total_component_types
|
||||
# for _ in range(self.max_component_types - total_component_types):
|
||||
# data.extend([0 for _ in range(self.max_nozzle_types)])
|
||||
|
||||
# === new component info ===
|
||||
comp_data_slice = defaultdict(list)
|
||||
for idx in range(self.max_nozzle_types):
|
||||
comp_data_slice[idx] = []
|
||||
|
||||
cp_items = [[component, points] for component, points in cp_points.items()]
|
||||
cp_items = sorted(cp_items, key=lambda x: (-x[1], x[0]))
|
||||
cp_items = sorted(cp_items, key=lambda x: (x[1], nz2idx[cp_nozzle[x[0]]] * 0.1 + x[1]), reverse=True)
|
||||
for component, points in cp_items:
|
||||
nozzle = cp_nozzle[component]
|
||||
comp_data_slice[nz2idx[nozzle]].append(points)
|
||||
|
||||
data_slice = [0 for _ in range(len(self.nozzle_type_list))]
|
||||
data_slice[cp2nz[nozzle]] = points
|
||||
data_slice = [0 for _ in range(self.max_nozzle_types)]
|
||||
for idx in range(self.max_nozzle_types):
|
||||
data_slice[idx] = len(comp_data_slice[idx])
|
||||
data.extend(data_slice)
|
||||
|
||||
for _ in range(self.max_component_types - total_component_types):
|
||||
data.extend([0 for _ in range(len(self.nozzle_type_list))])
|
||||
for idx in range(self.max_nozzle_types):
|
||||
comp_data_slice[idx].extend([0 for _ in range(self.max_component_types - len(comp_data_slice[idx]))])
|
||||
data.extend(comp_data_slice[idx])
|
||||
|
||||
return data
|
||||
def heuristic_estimator(self, cp_points, cp_nozzle):
|
||||
nozzle_heads, nozzle_points = defaultdict(int), defaultdict(int)
|
||||
for idx, points in cp_points.items():
|
||||
if points == 0:
|
||||
continue
|
||||
nozzle = cp_nozzle[idx]
|
||||
nozzle_points[nozzle] += points
|
||||
nozzle_heads[nozzle] = 1
|
||||
|
||||
anc_round_counter = 0
|
||||
while sum(nozzle_heads.values()) != max_head_index:
|
||||
max_cycle_nozzle = None
|
||||
|
||||
for nozzle, head_num in nozzle_heads.items():
|
||||
if max_cycle_nozzle is None or nozzle_points[nozzle] / head_num > nozzle_points[max_cycle_nozzle] / \
|
||||
nozzle_heads[max_cycle_nozzle]:
|
||||
max_cycle_nozzle = nozzle
|
||||
|
||||
assert max_cycle_nozzle is not None
|
||||
nozzle_heads[max_cycle_nozzle] += 1
|
||||
|
||||
head_nozzle_assignment, min_cost = None, None
|
||||
|
||||
# generate initial nozzle group
|
||||
nozzle_group = []
|
||||
# averagely assign for the same type of nozzles, and generate nozzle group
|
||||
nozzle_points_cpy = copy.deepcopy(nozzle_points)
|
||||
for nozzle, heads in nozzle_heads.items():
|
||||
points = nozzle_points_cpy[nozzle] // heads
|
||||
for _ in range(heads):
|
||||
nozzle_group.append([nozzle, points])
|
||||
nozzle_points_cpy[nozzle] -= heads * points
|
||||
|
||||
for idx, [nozzle, _] in enumerate(nozzle_group):
|
||||
if nozzle_points_cpy[nozzle]:
|
||||
nozzle_group[idx][1] += 1
|
||||
nozzle_points_cpy[nozzle] -= 1
|
||||
|
||||
while True:
|
||||
# assign nozzle group to each head
|
||||
nozzle_group.sort(key=lambda x: -x[1])
|
||||
|
||||
tmp_head_nozzle_assignment = []
|
||||
head_total_points = [0 for _ in range(max_head_index)]
|
||||
for idx, nozzle_item in enumerate(nozzle_group):
|
||||
if idx < max_head_index:
|
||||
tmp_head_nozzle_assignment.append([nozzle_item.copy()])
|
||||
head_total_points[idx] += nozzle_item[1]
|
||||
else:
|
||||
min_head = np.argmin(head_total_points)
|
||||
tmp_head_nozzle_assignment[min_head].append(nozzle_item.copy())
|
||||
head_total_points[min_head] += nozzle_item[1]
|
||||
|
||||
cost = t_cycle * max(head_total_points)
|
||||
for head in range(max_head_index):
|
||||
for cycle in range(len(tmp_head_nozzle_assignment[head])):
|
||||
if cycle + 1 == len(tmp_head_nozzle_assignment[head]):
|
||||
if tmp_head_nozzle_assignment[head][cycle][0] != tmp_head_nozzle_assignment[head][-1][0]:
|
||||
cost += t_nozzle_change
|
||||
else:
|
||||
if tmp_head_nozzle_assignment[head][cycle][0] != tmp_head_nozzle_assignment[head][cycle + 1][0]:
|
||||
cost += t_nozzle_change
|
||||
|
||||
while True:
|
||||
min_head, max_head = np.argmin(head_total_points), np.argmax(head_total_points)
|
||||
min_head_nozzle, max_head_nozzle = tmp_head_nozzle_assignment[min_head][-1][0], \
|
||||
tmp_head_nozzle_assignment[max_head][-1][0]
|
||||
if min_head_nozzle == max_head_nozzle:
|
||||
break
|
||||
|
||||
min_head_list, max_head_list = [min_head], [max_head]
|
||||
minmax_head_points = 0
|
||||
for head in range(max_head_index):
|
||||
if head in min_head_list or head in max_head_list:
|
||||
minmax_head_points += head_total_points[head]
|
||||
continue
|
||||
|
||||
# the max/min heads with the sum nozzle type
|
||||
if tmp_head_nozzle_assignment[head][-1][0] == tmp_head_nozzle_assignment[min_head][-1][0]:
|
||||
min_head_list.append(head)
|
||||
minmax_head_points += head_total_points[head]
|
||||
|
||||
if tmp_head_nozzle_assignment[head][-1][0] == tmp_head_nozzle_assignment[max_head][-1][0]:
|
||||
max_head_list.append(head)
|
||||
minmax_head_points += head_total_points[head]
|
||||
|
||||
# todo: restriction of available nozzle
|
||||
# the reduction of cycles is not offset the cost of nozzle change
|
||||
average_points = minmax_head_points // (len(min_head_list) + len(max_head_list))
|
||||
reminder_points = minmax_head_points % (len(min_head_list) + len(max_head_list))
|
||||
max_cycle = average_points + (1 if reminder_points > 0 else 0)
|
||||
for head in range(max_head_index):
|
||||
if head in min_head_list or head in max_head_list:
|
||||
continue
|
||||
max_cycle = max(max_cycle, head_total_points[head])
|
||||
|
||||
nozzle_change_counter = 0
|
||||
for head in min_head_list:
|
||||
if tmp_head_nozzle_assignment[head][0] == tmp_head_nozzle_assignment[head][-1]:
|
||||
nozzle_change_counter += 2
|
||||
else:
|
||||
nozzle_change_counter += 1
|
||||
|
||||
if t_cycle * (max(head_total_points) - max_cycle) < t_nozzle_change * nozzle_change_counter:
|
||||
break
|
||||
|
||||
cost -= t_cycle * (max(head_total_points) - max_cycle) - t_nozzle_change * nozzle_change_counter
|
||||
|
||||
required_points = 0 # 待均摊的贴装点数较多的吸嘴类型
|
||||
for head in min_head_list:
|
||||
points = average_points - head_total_points[head]
|
||||
tmp_head_nozzle_assignment[head].append([max_head_nozzle, points])
|
||||
head_total_points[head] = average_points
|
||||
required_points += points
|
||||
|
||||
for head in max_head_list:
|
||||
tmp_head_nozzle_assignment[head][-1][1] -= required_points // len(max_head_list)
|
||||
head_total_points[head] -= required_points // len(max_head_list)
|
||||
|
||||
required_points -= (required_points // len(max_head_list)) * len(max_head_list)
|
||||
|
||||
for head in max_head_list:
|
||||
if required_points <= 0:
|
||||
break
|
||||
tmp_head_nozzle_assignment[head][-1][1] -= 1
|
||||
head_total_points[head] -= 1
|
||||
required_points -= 1
|
||||
|
||||
if min_cost is None or cost < min_cost:
|
||||
min_cost = cost
|
||||
head_nozzle_assignment = copy.deepcopy(tmp_head_nozzle_assignment)
|
||||
else:
|
||||
break
|
||||
|
||||
# 在吸嘴组中增加一个吸嘴
|
||||
idx, nozzle = 0, nozzle_group[0][0]
|
||||
for idx, [nozzle_, _] in enumerate(nozzle_group):
|
||||
if nozzle_ != nozzle:
|
||||
break
|
||||
|
||||
average_points, remainder_points = nozzle_points[nozzle] // (idx + 1), nozzle_points[nozzle] % (idx + 1)
|
||||
nozzle_group.append([nozzle, 0])
|
||||
for idx, [nozzle_, _] in enumerate(nozzle_group):
|
||||
if nozzle_ == nozzle:
|
||||
nozzle_group[idx][1] = average_points + (1 if remainder_points > 0 else 0)
|
||||
remainder_points -= 1
|
||||
|
||||
cycle_counter, nozzle_change_counter = 0, 0
|
||||
for head in range(max_head_index):
|
||||
head_cycle_counter = 0
|
||||
for cycle in range(len(head_nozzle_assignment[head])):
|
||||
if cycle + 1 == len(head_nozzle_assignment[head]):
|
||||
if head_nozzle_assignment[head][0][0] != head_nozzle_assignment[head][-1][0]:
|
||||
nozzle_change_counter += 1
|
||||
else:
|
||||
if head_nozzle_assignment[head][cycle][0] != head_nozzle_assignment[head][cycle + 1][0]:
|
||||
nozzle_change_counter += 1
|
||||
head_cycle_counter += head_nozzle_assignment[head][cycle][1]
|
||||
cycle_counter = max(cycle_counter, head_cycle_counter)
|
||||
|
||||
# === 元件拾取次数预估 ===
|
||||
cp_info = []
|
||||
for idx, points in cp_points.items():
|
||||
if points == 0:
|
||||
continue
|
||||
feeder_limit = 1 # todo: 暂时仅考虑一种吸嘴的情形
|
||||
reminder_points = points % feeder_limit
|
||||
for _ in range(feeder_limit):
|
||||
cp_info.append([idx, points // feeder_limit + (1 if reminder_points > 0 else 0), cp_nozzle[idx]])
|
||||
reminder_points -= 1
|
||||
|
||||
cp_info.sort(key=lambda x: -x[1])
|
||||
|
||||
nozzle_level, nozzle_counter = defaultdict(int), defaultdict(int)
|
||||
level_points = defaultdict(int)
|
||||
for info in cp_info:
|
||||
nozzle = info[2]
|
||||
if nozzle_counter[nozzle] and nozzle_counter[nozzle] % nozzle_heads[nozzle] == 0:
|
||||
nozzle_level[nozzle] += 1
|
||||
level = nozzle_level[nozzle]
|
||||
level_points[level] = max(level_points[level], info[1])
|
||||
nozzle_counter[nozzle] += 1
|
||||
|
||||
pickup_counter = sum(points for points in level_points.values())
|
||||
|
||||
return cycle_counter, nozzle_change_counter, anc_round_counter, pickup_counter
|
||||
|
||||
def decode(self, line_info):
|
||||
boundary = [random.choice(self.x_range), random.choice(self.y_range)]
|
||||
items = line_info.split('\t')
|
||||
total_points, total_component_types = int(items[8]), int(items[9])
|
||||
board_width, board_height = float(items[7]), float(items[8])
|
||||
total_points, total_component_types = int(items[9]), int(items[10])
|
||||
|
||||
part_col = ["part", "desc", "fdr", "nz", 'camera', 'group', 'feeder-limit', 'points']
|
||||
step_col = ["ref", "x", "y", "z", "r", "part", "desc", "fdr", "nz", "hd", "cs", "cy", "sk", "bl", "ar", "pl",
|
||||
@ -159,53 +381,67 @@ class DataMgr:
|
||||
|
||||
idx = 1
|
||||
for cp_counter in range(total_component_types):
|
||||
# todo: 这里为了调试暂时未修改
|
||||
part, nozzle = items[11 + cp_counter * 3], items[12 + cp_counter * 3]
|
||||
points = int(items[13 + cp_counter * 3])
|
||||
part, nozzle = items[12 + cp_counter * 3], items[13 + cp_counter * 3]
|
||||
points = int(items[14 + cp_counter * 3])
|
||||
|
||||
pos_list = []
|
||||
for _ in range(points):
|
||||
pos_list.append([np.random.uniform(0, board_width), np.random.uniform(0, board_height)])
|
||||
|
||||
component_data = pd.concat([component_data, pd.DataFrame(
|
||||
[part, '', 'SM8', nozzle, '飞行相机1', 'CHIP-Rect', self.default_feeder_limit, points], index=part_col).T],
|
||||
ignore_index=True)
|
||||
|
||||
for _ in range(points):
|
||||
pos_x, pos_y = np.random.uniform(0, boundary[0]), np.random.uniform(0, boundary[1])
|
||||
pcb_data = pd.concat([pcb_data, pd.DataFrame([['R' + str(idx), -pos_x, pos_y, 0.000, 0.000, part, '',
|
||||
'A', '1-0 ' + nozzle, 1, 1, 1, 0, 1, 1, 1, 'L0']],
|
||||
columns=pcb_data.columns)], ignore_index=True)
|
||||
for pos_x, pos_y in pos_list:
|
||||
pcb_data = pd.concat([pcb_data, pd.DataFrame([['R' + str(idx), - pos_x, pos_y,
|
||||
0.000, 0.000, part, '', 'A', '1-0 ' + nozzle, 1, 1, 1, 0,
|
||||
1, 1, 1, 'L0']], columns=pcb_data.columns)],
|
||||
ignore_index=True)
|
||||
return pcb_data, component_data
|
||||
|
||||
def loader(self, file_path):
|
||||
train_data, time_data = [], []
|
||||
cycle_data, nozzle_change_data, pickup_data, movement_data, point_data = [], [], [], [], []
|
||||
cycle_data, nozzle_change_data, anc_move_data, pickup_data, movement_data, point_data = [], [], [], [], [], []
|
||||
with open(file_path, 'r') as file:
|
||||
line = file.readline()
|
||||
while line:
|
||||
items = line.split('\t')
|
||||
total_points, total_component_types = float(items[8]), float(items[9])
|
||||
|
||||
cp_points, cp_nozzle = defaultdict(int), defaultdict(str)
|
||||
# cp_width, cp_height = defaultdict(float), defaultdict(float)
|
||||
for cp_idx in range((len(items) - 12) // 3):
|
||||
points = int(items[14 + cp_idx * 3])
|
||||
if points == 0:
|
||||
continue
|
||||
|
||||
component_type, nozzle_type = items[12 + cp_idx * 3], items[13 + cp_idx * 3]
|
||||
cp_points[component_type], cp_nozzle[component_type] = points, nozzle_type
|
||||
# cp_width[component_type], cp_height[component_type] = float(items[15 + cp_idx * 5]), float(
|
||||
# items[16 + cp_idx * 5])
|
||||
|
||||
# if len(set(cp_nozzle.values())) > 2 or len(set(cp_nozzle.keys())) > 3:
|
||||
if len(cp_points.keys()) > 30:
|
||||
line = file.readline()
|
||||
continue
|
||||
|
||||
cycle_data.append(float(items[1]))
|
||||
nozzle_change_data.append(float(items[2]))
|
||||
pickup_data.append(float(items[3]))
|
||||
movement_data.append(float(items[4]))
|
||||
point_data.append(total_points)
|
||||
anc_move_data.append(float(items[3]))
|
||||
pickup_data.append(float(items[4]))
|
||||
movement_data.append(float(items[5]) + float(items[6]))
|
||||
point_data.append(sum(pt for pt in cp_points.values()))
|
||||
|
||||
# assembly time data
|
||||
time_data.append(float(items[0]))
|
||||
|
||||
cp_points, cp_nozzle = defaultdict(int), defaultdict(str)
|
||||
for cp_counter in range(int(total_component_types)):
|
||||
component_type, nozzle_type = items[11 + cp_counter * 3], items[12 + cp_counter * 3]
|
||||
points = int(items[13 + cp_counter * 3])
|
||||
|
||||
cp_points[component_type], cp_nozzle[component_type] = points, nozzle_type
|
||||
|
||||
train_data.append(self.encode(cp_points, cp_nozzle, float(items[6]), float(items[7])))
|
||||
train_data.append(self.encode(cp_points, cp_nozzle, float(items[7]), float(items[8])))
|
||||
# train_data[-1].extend([cycle_data[-1], nozzle_change_data[-1], anc_move_data[-1], pickup_data[-1]])
|
||||
line = file.readline()
|
||||
|
||||
return train_data, time_data, cycle_data, nozzle_change_data, pickup_data, movement_data, point_data
|
||||
return train_data, time_data, cycle_data, nozzle_change_data, anc_move_data, pickup_data, point_data
|
||||
|
||||
def get_feature(self):
|
||||
return (self.max_component_types + 1) * len(self.nozzle_type_list) + 5
|
||||
return (self.max_component_types + 2) * self.max_nozzle_types + 5 + 4
|
||||
|
||||
def get_update_round(self):
|
||||
return self.update
|
||||
|
224
optimizer.py
224
optimizer.py
@ -1,160 +1,54 @@
|
||||
import random
|
||||
|
||||
import numpy as np
|
||||
|
||||
from dataloader import *
|
||||
from optimizer_genetic import *
|
||||
from optimizer_heuristic import *
|
||||
from optimizer_reconfiguration import *
|
||||
from optimizer_genetic import line_optimizer_genetic
|
||||
from optimizer_heuristic import line_optimizer_heuristic
|
||||
from optimizer_reconfiguration import line_optimizer_reconfiguration
|
||||
from optimizer_hyperheuristic import line_optimizer_hyperheuristic
|
||||
|
||||
from base_optimizer.optimizer_interface import *
|
||||
|
||||
|
||||
def deviation(data):
|
||||
assert len(data) > 0
|
||||
average, variance = sum(data) / len(data), 0
|
||||
for v in data:
|
||||
variance += (v - average) ** 2
|
||||
return variance / len(data)
|
||||
|
||||
|
||||
def optimizer(pcb_data, component_data, line_optimizer, machine_optimizer, machine_number):
|
||||
if line_optimizer == "heuristic":
|
||||
assignment_result = assemblyline_optimizer_heuristic(pcb_data, component_data, machine_number)
|
||||
if machine_number > 1:
|
||||
if line_optimizer == 'hyper-heuristic':
|
||||
assignment_result = line_optimizer_hyperheuristic(component_data, pcb_data, machine_number)
|
||||
elif line_optimizer == "heuristic":
|
||||
assignment_result = line_optimizer_heuristic(component_data, machine_number)
|
||||
elif line_optimizer == "genetic":
|
||||
assignment_result = assemblyline_optimizer_genetic(pcb_data, component_data, machine_number)
|
||||
assignment_result = line_optimizer_genetic(component_data, machine_number)
|
||||
elif line_optimizer == "reconfiguration":
|
||||
assignment_result = reconfiguration_optimizer(pcb_data, component_data, machine_number)
|
||||
assignment_result = line_optimizer_reconfiguration(component_data, pcb_data, machine_number)
|
||||
else:
|
||||
return
|
||||
|
||||
assignment_result_cpy = copy.deepcopy(assignment_result)
|
||||
placement_points, assembly_info = [], []
|
||||
partial_pcb_data, partial_component_data = defaultdict(pd.DataFrame), defaultdict(pd.DataFrame)
|
||||
for machine_index in range(machine_number):
|
||||
partial_pcb_data[machine_index] = pd.DataFrame(columns=pcb_data.columns)
|
||||
partial_component_data[machine_index] = component_data.copy(deep=True)
|
||||
placement_points.append(sum(assignment_result[machine_index]))
|
||||
|
||||
assert sum(placement_points) == len(pcb_data)
|
||||
|
||||
# === averagely assign available feeder ===
|
||||
for part_index, data in component_data.iterrows():
|
||||
feeder_limit = data['feeder-limit']
|
||||
feeder_points = [assignment_result[machine_index][part_index] for machine_index in range(machine_number)]
|
||||
|
||||
for machine_index in range(machine_number):
|
||||
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
|
||||
feeder_limit -= 1
|
||||
|
||||
for machine_index in range(machine_number):
|
||||
if feeder_points[machine_index] > 0:
|
||||
assert partial_component_data[machine_index].loc[part_index, 'feeder-limit'] > 0
|
||||
|
||||
# === assign placements ===
|
||||
component_machine_index = [0 for _ in range(len(component_data))]
|
||||
for _, data in pcb_data.iterrows():
|
||||
part_index = component_data[component_data['part'] == data['part']].index.tolist()[0]
|
||||
while True:
|
||||
machine_index = component_machine_index[part_index]
|
||||
if assignment_result[machine_index][part_index] == 0:
|
||||
component_machine_index[part_index] += 1
|
||||
machine_index += 1
|
||||
raise 'line optimizer method is not existed'
|
||||
else:
|
||||
break
|
||||
assignment_result[machine_index][part_index] -= 1
|
||||
partial_pcb_data[machine_index] = pd.concat([partial_pcb_data[machine_index], pd.DataFrame(data).T])
|
||||
assignment_result = [[]]
|
||||
for _, data in component_data.iterrows():
|
||||
assignment_result[-1].append(data.points)
|
||||
|
||||
# === adjust the number of available feeders for single optimization separately ===
|
||||
for machine_index, data in partial_pcb_data.items():
|
||||
data = data.reset_index(drop=True)
|
||||
if len(data) == 0:
|
||||
continue
|
||||
|
||||
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_cpy[machine_index][part_index]:
|
||||
part_info.append(
|
||||
[part_index, assignment_result_cpy[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 = deviation(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 = deviation(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)
|
||||
|
||||
# update available feeder number
|
||||
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)
|
||||
|
||||
assembly_info.append(base_optimizer(machine_index + 1, data, partial_component_data[machine_index],
|
||||
feeder_data=pd.DataFrame(columns=['slot', 'part', 'arg']),
|
||||
method=machine_optimizer, hinter=True))
|
||||
|
||||
with open('model/lr_model.pkl', 'rb') as f:
|
||||
lr = pickle.load(f)
|
||||
|
||||
average_time, standard_deviation_time = sum(
|
||||
[assembly_info[m].placement_time for m in range(machine_number)]) / machine_number, 0
|
||||
partial_pcb_data, partial_component_data = convert_line_assigment(pcb_data, component_data, assignment_result)
|
||||
assembly_info = []
|
||||
for machine_index in range(machine_number):
|
||||
total_component_types = sum(1 if pt else 0 for pt in assignment_result_cpy[machine_index])
|
||||
placement_time = assembly_info[machine_index].placement_time
|
||||
assembly_info.append(
|
||||
base_optimizer(machine_index + 1, partial_pcb_data[machine_index], partial_component_data[machine_index],
|
||||
feeder_data=pd.DataFrame(columns=['slot', 'part', 'arg']), method=machine_optimizer,
|
||||
hinter=True))
|
||||
|
||||
regression_time = lr.coef_[0][0] * assembly_info[machine_index].cycle_counter + lr.coef_[0][1] * assembly_info[
|
||||
machine_index].nozzle_change_counter + lr.coef_[0][2] * assembly_info[machine_index].pickup_counter + \
|
||||
lr.coef_[0][3] * assembly_info[machine_index].pickup_movement + lr.coef_[0][4] * \
|
||||
placement_points[machine_index] + lr.intercept_[0]
|
||||
for machine_index in range(machine_number):
|
||||
total_component_types = sum(1 if pt else 0 for pt in assignment_result[machine_index])
|
||||
total_placement_points = sum(assignment_result[machine_index])
|
||||
total_time = assembly_info[machine_index].total_time
|
||||
print(f'assembly time for machine {machine_index + 1: d}: {total_time: .3f} s, total placement: '
|
||||
f'{total_placement_points}, total component types {total_component_types: d}', end='')
|
||||
for part_index in range(len(assignment_result[machine_index])):
|
||||
if assignment_result[machine_index][part_index]:
|
||||
print(', ', part_index, end='')
|
||||
print('')
|
||||
|
||||
print(f'assembly time for machine {machine_index + 1: d}: {placement_time: .3f} s, total placement: '
|
||||
f'{placement_points[machine_index]}, total component types {total_component_types: d}', end=', ')
|
||||
print(f'regression time: {regression_time: .3f} s')
|
||||
standard_deviation_time += pow(placement_time - average_time, 2)
|
||||
standard_deviation_time /= machine_number
|
||||
standard_deviation_time = math.sqrt(standard_deviation_time)
|
||||
|
||||
print(f'finial assembly time: {max(info.placement_time for info in assembly_info): .3f} s, '
|
||||
f'standard deviation: {standard_deviation_time: .3f}')
|
||||
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}')
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
@ -165,10 +59,10 @@ def main():
|
||||
parser.add_argument('--filename', default='PCB.txt', type=str, help='load pcb data')
|
||||
parser.add_argument('--auto_register', default=1, type=int, help='register the component according the pcb data')
|
||||
parser.add_argument('--machine_number', default=3, 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('--line_optimizer', default='genetic', 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('--machine_optimizer', default='feeder-scan', 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='genetic', 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')
|
||||
params = parser.parse_args()
|
||||
|
||||
# 结果输出显示所有行和列
|
||||
@ -181,6 +75,40 @@ def main():
|
||||
|
||||
optimizer(pcb_data, component_data, params.line_optimizer, params.machine_optimizer, params.machine_number)
|
||||
|
||||
# index_list, part_list = [1, 4, 8, 9, 12, 13, 14, 18, 20, 22, 23, 25, 33, 35, 38, 39, 40], []
|
||||
# for idx in index_list:
|
||||
# part_list.append(component_data.iloc[idx].part)
|
||||
# pcb_data = pcb_data[pcb_data['part'].isin(part_list)].reset_index(drop=True)
|
||||
# component_data = component_data.iloc[index_list].reset_index(drop=True)
|
||||
# optimizer(pcb_data, component_data, params.line_optimizer, params.machine_optimizer, 1)
|
||||
#
|
||||
# from optimizer_hyperheuristic import DataMgr, Net
|
||||
# data_mgr = DataMgr()
|
||||
|
||||
# cp_points, cp_nozzle = defaultdict(int), defaultdict(str)
|
||||
# for _, data in component_data.iterrows():
|
||||
# cp_points[data.part], cp_nozzle[data.part] = data.points, data.nz
|
||||
|
||||
# idx = 1832
|
||||
# data = data_mgr.loader(file_name)
|
||||
# encoding = np.array(data[0][idx])
|
||||
|
||||
# device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
# net = Net(input_size=data_mgr.get_feature(), output_size=1).to(device)
|
||||
#
|
||||
# net.load_state_dict(torch.load('model/net_model.pth'))
|
||||
# board_width, board_height = pcb_data['x'].max() - pcb_data['x'].min(), pcb_data['y'].max() - pcb_data['y'].min()
|
||||
# encoding = np.array(data_mgr.encode(cp_points, cp_nozzle, board_width, board_height))
|
||||
# encoding = torch.from_numpy(encoding.reshape((-1, np.shape(encoding)[0]))).float().to("cuda")
|
||||
# print(f'net pred time: {net(encoding)[0, 0].item():.3f}')
|
||||
|
||||
# with open('model/lr_model.pkl', 'rb') as f:
|
||||
# lr = pickle.load(f)
|
||||
#
|
||||
# print('lr model train data: ', np.array(data[2:]).T[idx].reshape(1, -1))
|
||||
# print('lr model pred time: ', lr.predict(np.array(data[2:]).T[idx].reshape(1, -1)))
|
||||
# print('real time: ', data[-1][idx] * 3600 / data[1][idx])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@ -49,7 +49,7 @@ def selective_crossover(component_points, component_feeders, mother, father, mac
|
||||
one_counter, feasible_cut_line = 0, []
|
||||
|
||||
idx = 0
|
||||
for part_index, points in component_points:
|
||||
for part_index, points in component_points.items():
|
||||
one_counter = 0
|
||||
|
||||
idx_, mother_cut_line, father_cut_line = 0, [-1], [-1]
|
||||
@ -131,13 +131,12 @@ def selective_crossover(component_points, component_feeders, mother, father, mac
|
||||
return offspring1, offspring2
|
||||
|
||||
|
||||
def cal_individual_val(component_points, component_feeders, component_nozzle, machine_number, individual, data_mgr, net):
|
||||
def cal_individual_val(component_points, component_nozzle, machine_number, individual, estimator):
|
||||
idx, objective_val = 0, []
|
||||
machine_component_points = [[] for _ in range(machine_number)]
|
||||
nozzle_component_points = defaultdict(list)
|
||||
|
||||
# decode the component allocation
|
||||
for comp_idx, points in component_points:
|
||||
for part_index, points in component_points.items():
|
||||
component_gene = individual[idx: idx + points + machine_number - 1]
|
||||
machine_idx, component_counter = 0, 0
|
||||
for gene in component_gene:
|
||||
@ -150,108 +149,19 @@ def cal_individual_val(component_points, component_feeders, component_nozzle, ma
|
||||
machine_component_points[-1].append(component_counter)
|
||||
idx += (points + machine_number - 1)
|
||||
|
||||
nozzle_component_points[component_nozzle[comp_idx]] = [0] * len(component_points) # 初始化元件-吸嘴点数列表
|
||||
|
||||
# ======== 新加的开始 ========
|
||||
objective_val = 0
|
||||
for machine_idx in range(machine_number):
|
||||
cp_points, cp_nozzle = defaultdict(int), defaultdict(str)
|
||||
for comp_idx, _ in component_points:
|
||||
if machine_component_points[machine_idx][comp_idx] == 0:
|
||||
continue
|
||||
cp_points['C' + str(comp_idx)] = machine_component_points[machine_idx][comp_idx]
|
||||
cp_nozzle['C' + str(comp_idx)] = component_nozzle[comp_idx]
|
||||
|
||||
encoding = np.array(data_mgr.encode(cp_points, cp_nozzle, 45, 150))
|
||||
encoding = torch.from_numpy(encoding.reshape((-1, np.shape(encoding)[0]))).float().to("cuda")
|
||||
# pred_time = net(encoding)[0, 0].item()
|
||||
# objective_val.append(pred_time * sum(points for points in cp_points.values()))
|
||||
objective_val.append(net(encoding)[0, 0].item())
|
||||
|
||||
return objective_val, machine_component_points
|
||||
# ======== 新加的结束(以下内容弃用) =====
|
||||
for comp_idx, points in component_points:
|
||||
nozzle_component_points[component_nozzle[comp_idx]][comp_idx] = points
|
||||
|
||||
for machine_idx in range(machine_number):
|
||||
nozzle_points = defaultdict(int)
|
||||
for idx, nozzle in component_nozzle.items():
|
||||
if component_points[idx] == 0:
|
||||
continue
|
||||
nozzle_points[nozzle] += machine_component_points[machine_idx][idx]
|
||||
|
||||
machine_points = sum(machine_component_points[machine_idx]) # num of placement points
|
||||
if machine_points == 0:
|
||||
continue
|
||||
ul = math.ceil(len(nozzle_points) * 1.0 / max_head_index) - 1 # num of nozzle set
|
||||
|
||||
# assignments of nozzles to heads
|
||||
wl = 0 # num of workload
|
||||
total_heads = (1 + ul) * max_head_index - len(nozzle_points)
|
||||
nozzle_heads = defaultdict(int)
|
||||
for nozzle in nozzle_points.keys():
|
||||
if nozzle_points[nozzle] == 0:
|
||||
cp_points, cp_nozzle = defaultdict(int), defaultdict(str)
|
||||
for part_index, points in enumerate(machine_component_points[machine_idx]):
|
||||
if points == 0:
|
||||
continue
|
||||
nozzle_heads[nozzle] = math.floor(nozzle_points[nozzle] * 1.0 / machine_points * total_heads)
|
||||
nozzle_heads[nozzle] += 1
|
||||
|
||||
total_heads = (1 + ul) * max_head_index
|
||||
for heads in nozzle_heads.values():
|
||||
total_heads -= heads
|
||||
|
||||
while True:
|
||||
nozzle = max(nozzle_heads, key=lambda x: nozzle_points[x] / nozzle_heads[x])
|
||||
if total_heads == 0:
|
||||
break
|
||||
nozzle_heads[nozzle] += 1
|
||||
total_heads -= 1
|
||||
|
||||
# averagely assign placements to heads
|
||||
heads_placement = []
|
||||
for nozzle in nozzle_heads.keys():
|
||||
points = math.floor(nozzle_points[nozzle] / nozzle_heads[nozzle])
|
||||
|
||||
heads_placement += [[nozzle, points] for _ in range(nozzle_heads[nozzle])]
|
||||
nozzle_points[nozzle] -= (nozzle_heads[nozzle] * points)
|
||||
for idx in range(len(heads_placement) - 1, -1, -1):
|
||||
if nozzle_points[nozzle] <= 0:
|
||||
break
|
||||
nozzle_points[nozzle] -= 1
|
||||
heads_placement[idx][1] += 1
|
||||
heads_placement = sorted(heads_placement, key=lambda x: x[1], reverse=True)
|
||||
|
||||
# the number of pick-up operations
|
||||
# (under the assumption of the number of feeder available for each comp. type is equal 1)
|
||||
pl = 0
|
||||
heads_placement_points = [0 for _ in range(max_head_index)]
|
||||
while True:
|
||||
head_assign_point = []
|
||||
for head in range(max_head_index):
|
||||
if heads_placement_points[head] != 0 or heads_placement[head] == 0:
|
||||
continue
|
||||
|
||||
nozzle, points = heads_placement[head]
|
||||
max_comp_index = np.argmax(nozzle_component_points[nozzle])
|
||||
|
||||
heads_placement_points[head] = min(points, nozzle_component_points[nozzle][max_comp_index])
|
||||
nozzle_component_points[nozzle][max_comp_index] -= heads_placement_points[head]
|
||||
|
||||
head_assign_point.append(heads_placement_points[head])
|
||||
|
||||
min_points_list = list(filter(lambda x: x > 0, heads_placement_points))
|
||||
if len(min_points_list) == 0 or len(head_assign_point) == 0:
|
||||
break
|
||||
|
||||
pl += max(head_assign_point)
|
||||
|
||||
for head in range(max_head_index):
|
||||
heads_placement[head][1] -= min(min_points_list)
|
||||
heads_placement_points[head] -= min(min_points_list)
|
||||
|
||||
# every max_head_index heads in the non-decreasing order are grouped together as nozzle set
|
||||
for idx in range(len(heads_placement) // max_head_index):
|
||||
wl += heads_placement[idx][1]
|
||||
objective_val.append(T_pp * machine_points + T_tr * wl + T_nc * ul + T_pl * pl)
|
||||
|
||||
cp_points[part_index], cp_nozzle[part_index] = points, component_nozzle[part_index]
|
||||
# objective_val = max(objective_val, estimator.neural_network(cp_points, cp_nozzle, 237.542, 223.088))
|
||||
objective_val = max(objective_val, estimator.heuristic_genetic(cp_points, cp_nozzle))
|
||||
return objective_val, machine_component_points
|
||||
|
||||
|
||||
@ -276,35 +186,25 @@ def individual_convert(component_points, individual):
|
||||
return machine_component_points
|
||||
|
||||
|
||||
def assemblyline_optimizer_genetic(pcb_data, component_data, machine_number):
|
||||
def line_optimizer_genetic(component_data, machine_number):
|
||||
# basic parameter
|
||||
# crossover rate & mutation rate: 80% & 10%
|
||||
# crossover rate & mutation rate: 80% & 10%cizh
|
||||
# population size: 200
|
||||
# the number of generation: 500
|
||||
crossover_rate, mutation_rate = 0.8, 0.1
|
||||
population_size, n_generations = 200, 500
|
||||
|
||||
estimator = Estimator()
|
||||
# the number of placement points, the number of available feeders, and nozzle type of component respectively
|
||||
component_points, component_feeders, component_nozzle = defaultdict(int), defaultdict(int), defaultdict(str)
|
||||
for data in pcb_data.iterrows():
|
||||
part_index = component_data[component_data['part'] == data[1]['part']].index.tolist()[0]
|
||||
nozzle = component_data.loc[part_index]['nz']
|
||||
|
||||
component_points[part_index] += 1
|
||||
component_feeders[part_index] = component_data.loc[part_index]['feeder-limit']
|
||||
component_nozzle[part_index] = nozzle
|
||||
|
||||
component_points = sorted(component_points.items(), key=lambda x: x[0]) # 决定染色体排列顺序
|
||||
data_mgr = DataMgr()
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
net = Net(input_size=data_mgr.get_feature(), output_size=1).to(device)
|
||||
|
||||
net.load_state_dict(torch.load('model/net_model.pth'))
|
||||
# optimizer = torch.optim.Adam(net.parameters(), lr=0.1)
|
||||
# optimizer.load_state_dict(torch.load('optimizer_state.pth'))
|
||||
cp_points, cp_feeders, cp_nozzle = defaultdict(int), defaultdict(int), defaultdict(int)
|
||||
for part_index, data in component_data.iterrows():
|
||||
cp_points[part_index] += data['points']
|
||||
cp_feeders[part_index] = data['feeder-limit']
|
||||
cp_nozzle[part_index] = data['nz']
|
||||
|
||||
# population initialization
|
||||
population = selective_initialization(component_points, component_feeders, population_size, machine_number)
|
||||
population = selective_initialization(sorted(cp_points.items(), key=lambda x: x[0]), cp_feeders, population_size,
|
||||
machine_number)
|
||||
with tqdm(total=n_generations) as pbar:
|
||||
pbar.set_description('genetic algorithm process for PCB assembly line balance')
|
||||
|
||||
@ -313,9 +213,8 @@ def assemblyline_optimizer_genetic(pcb_data, component_data, machine_number):
|
||||
# calculate fitness value
|
||||
pop_val = []
|
||||
for individual in population:
|
||||
val, assigned_points = cal_individual_val(component_points, component_feeders, component_nozzle,
|
||||
machine_number, individual, data_mgr, net)
|
||||
pop_val.append(max(val))
|
||||
val, assigned_points = cal_individual_val(cp_points, cp_nozzle, machine_number, individual, estimator)
|
||||
pop_val.append(val)
|
||||
|
||||
select_index = get_top_k_value(pop_val, population_size - len(new_population), reverse=False)
|
||||
population = [population[idx] for idx in select_index]
|
||||
@ -323,9 +222,8 @@ def assemblyline_optimizer_genetic(pcb_data, component_data, machine_number):
|
||||
|
||||
population += new_population
|
||||
for individual in new_population:
|
||||
val, _ = cal_individual_val(component_points, component_feeders, component_nozzle, machine_number,
|
||||
individual, data_mgr, net)
|
||||
pop_val.append(max(val))
|
||||
val, _ = cal_individual_val(cp_points, cp_nozzle, machine_number, individual, estimator)
|
||||
pop_val.append(val)
|
||||
|
||||
# min-max convert
|
||||
max_val = max(pop_val)
|
||||
@ -343,14 +241,14 @@ def assemblyline_optimizer_genetic(pcb_data, component_data, machine_number):
|
||||
if index1 != index2:
|
||||
break
|
||||
|
||||
offspring1, offspring2 = selective_crossover(component_points, component_feeders,
|
||||
offspring1, offspring2 = selective_crossover(cp_points, cp_feeders,
|
||||
population[index1], population[index2], machine_number)
|
||||
|
||||
if np.random.random() < mutation_rate:
|
||||
offspring1 = constraint_swap_mutation(component_points, offspring1, machine_number)
|
||||
offspring1 = constraint_swap_mutation(cp_points, offspring1, machine_number)
|
||||
|
||||
if np.random.random() < mutation_rate:
|
||||
offspring2 = constraint_swap_mutation(component_points, offspring2, machine_number)
|
||||
offspring2 = constraint_swap_mutation(cp_points, offspring2, machine_number)
|
||||
|
||||
new_population.append(offspring1)
|
||||
new_population.append(offspring2)
|
||||
@ -358,8 +256,7 @@ def assemblyline_optimizer_genetic(pcb_data, component_data, machine_number):
|
||||
pbar.update(1)
|
||||
|
||||
best_individual = population[np.argmax(pop_val)]
|
||||
val, assignment_result = cal_individual_val(component_points, component_feeders, component_nozzle, machine_number,
|
||||
best_individual, data_mgr, net)
|
||||
val, assignment_result = cal_individual_val(cp_points, cp_nozzle, machine_number, best_individual, estimator)
|
||||
|
||||
print('final value: ', val)
|
||||
# available feeder check
|
||||
|
@ -11,13 +11,13 @@ from base_optimizer.result_analysis import *
|
||||
|
||||
# TODO: nozzle tool available restriction
|
||||
# TODO: consider with the PCB placement topology
|
||||
def assembly_time_estimator(assignment_points, component_feeders, component_nozzle):
|
||||
def assembly_time_estimator(assignment_points, arranged_feeders, component_data):
|
||||
nozzle_heads, nozzle_points = defaultdict(int), defaultdict(int)
|
||||
for idx, points in enumerate(assignment_points):
|
||||
if points == 0:
|
||||
continue
|
||||
nozzle_points[component_nozzle[idx]] += points
|
||||
nozzle_heads[component_nozzle[idx]] = 1
|
||||
nozzle_points[component_data.iloc[idx]['nz']] += points
|
||||
nozzle_heads[component_data.iloc[idx]['nz']] = 1
|
||||
|
||||
while sum(nozzle_heads.values()) != max_head_index:
|
||||
max_cycle_nozzle = None
|
||||
@ -174,10 +174,11 @@ def assembly_time_estimator(assignment_points, component_feeders, component_nozz
|
||||
for idx, points in enumerate(assignment_points):
|
||||
if points == 0:
|
||||
continue
|
||||
reminder_points = points % component_feeders[idx]
|
||||
for _ in range(component_feeders[idx]):
|
||||
feeder_limit = int(component_data.iloc[idx]['feeder-limit'])
|
||||
reminder_points = points % feeder_limit
|
||||
for _ in range(feeder_limit):
|
||||
cp_info.append(
|
||||
[idx, points // component_feeders[idx] + (1 if reminder_points > 0 else 0), component_nozzle[idx]])
|
||||
[idx, points // feeder_limit + (1 if reminder_points > 0 else 0), component_data.iloc[idx]['nz']])
|
||||
reminder_points -= 1
|
||||
|
||||
cp_info.sort(key=lambda x: -x[1])
|
||||
@ -204,46 +205,35 @@ def assembly_time_estimator(assignment_points, component_feeders, component_nozz
|
||||
t_place * placement_counter + 0.1 * pickup_movement
|
||||
|
||||
|
||||
def assemblyline_optimizer_heuristic(pcb_data, component_data, machine_number):
|
||||
def line_optimizer_heuristic(component_data, machine_number):
|
||||
# the number of placement points, the number of available feeders, and nozzle type of component respectively
|
||||
component_number = len(component_data)
|
||||
|
||||
component_points = [0 for _ in range(component_number)]
|
||||
component_feeders = [0 for _ in range(component_number)]
|
||||
component_nozzle = [0 for _ in range(component_number)]
|
||||
component_part = [0 for _ in range(component_number)]
|
||||
|
||||
nozzle_points = defaultdict(int) # the number of placements of nozzle
|
||||
|
||||
for _, data in pcb_data.iterrows():
|
||||
part_index = component_data[component_data['part'] == data['part']].index.tolist()[0]
|
||||
nozzle = component_data.loc[part_index]['nz']
|
||||
|
||||
component_points[part_index] += 1
|
||||
component_feeders[part_index] = component_data.loc[part_index]['feeder-limit']
|
||||
# component_feeders[part_index] = math.ceil(component_data.loc[part_index]['feeder-limit'] / max_feeder_limit)
|
||||
component_nozzle[part_index] = nozzle
|
||||
component_part[part_index] = data['part']
|
||||
|
||||
nozzle_points[nozzle] += 1
|
||||
total_points = 0
|
||||
for _, data in component_data.iterrows():
|
||||
nozzle = data['nz']
|
||||
nozzle_points[nozzle] += data['points']
|
||||
total_points += data['point']
|
||||
|
||||
# first step: generate the initial solution with equalized workload
|
||||
assignment_result = [[0 for _ in range(len(component_points))] for _ in range(machine_number)]
|
||||
assignment_result = [[0 for _ in range(len(component_data))] for _ in range(machine_number)]
|
||||
assignment_points = [0 for _ in range(machine_number)]
|
||||
average_points = len(pcb_data) // machine_number
|
||||
average_points = total_points // machine_number
|
||||
|
||||
weighted_points = list(
|
||||
map(lambda x: x[1] + 1e-5 * nozzle_points[component_nozzle[x[0]]], enumerate(component_points)))
|
||||
map(lambda _, data: data['points'] + 1e-5 * nozzle_points[data['nz']], component_data.iterrows()))
|
||||
|
||||
# for part_index in np.argsort(weighted_points)[::-1]:
|
||||
for part_index in np.argsort(weighted_points)[::-1]:
|
||||
if (total_points := component_points[part_index]) == 0: # total placements for each component type
|
||||
if (total_points := component_data.iloc[part_index]['points']) == 0: # total placements for each component type
|
||||
continue
|
||||
machine_set = []
|
||||
|
||||
# define the machine that assigning placement points (considering the feeder limitation)
|
||||
for machine_index in np.argsort(assignment_points):
|
||||
if len(machine_set) >= component_points[part_index] or len(machine_set) >= component_feeders[part_index]:
|
||||
if len(machine_set) >= component_data.iloc[part_index]['points'] or len(machine_set) >= \
|
||||
component_data.iloc[part_index]['feeder-limit']:
|
||||
break
|
||||
|
||||
machine_set.append(machine_index)
|
||||
@ -308,7 +298,7 @@ def assemblyline_optimizer_heuristic(pcb_data, component_data, machine_number):
|
||||
arranged_feeders[machine_index] = [0 for _ in range(len(component_data))]
|
||||
|
||||
for part_index in range(len(component_data)):
|
||||
feeder_limit = component_feeders[part_index] # 总体可用数
|
||||
feeder_limit = component_data.iloc[part_index]['feeder-limit'] # 总体可用数
|
||||
for machine_index in range(machine_number):
|
||||
if assignment_result[machine_index][part_index] == 0:
|
||||
continue
|
||||
@ -318,7 +308,7 @@ def assemblyline_optimizer_heuristic(pcb_data, component_data, machine_number):
|
||||
assert feeder_limit >= 0
|
||||
|
||||
for part_index in range(len(component_data)):
|
||||
total_feeder_limit = component_feeders[part_index] - sum(
|
||||
total_feeder_limit = component_data.iloc[part_index]['feeder-limit'] - sum(
|
||||
[arranged_feeders[machine_index][part_index] for machine_index in range(machine_number)])
|
||||
while total_feeder_limit > 0:
|
||||
max_ratio, max_ratio_machine = None, -1
|
||||
@ -336,7 +326,7 @@ def assemblyline_optimizer_heuristic(pcb_data, component_data, machine_number):
|
||||
for machine_index in range(machine_number):
|
||||
assembly_time.append(
|
||||
assembly_time_estimator(assignment_result[machine_index], arranged_feeders[machine_index],
|
||||
component_nozzle))
|
||||
component_data))
|
||||
chip_per_hour.append(sum(assignment_result[machine_index]) / (assembly_time[-1] + 1e-10))
|
||||
|
||||
max_assembly_time = max(assembly_time)
|
||||
@ -351,7 +341,6 @@ def assemblyline_optimizer_heuristic(pcb_data, component_data, machine_number):
|
||||
|
||||
# third step: adjust the assignment results to reduce maximal assembly time among all machines
|
||||
# ideal averagely assigned points
|
||||
total_points = len(pcb_data)
|
||||
average_assign_points = [round(total_points * chip_per_hour[mi] / sum(chip_per_hour)) for mi in
|
||||
range(machine_number)]
|
||||
|
||||
@ -391,7 +380,7 @@ def assemblyline_optimizer_heuristic(pcb_data, component_data, machine_number):
|
||||
tmp_reallocate_result[supply_mi] -= reallocate_points
|
||||
tmp_reallocate_result[demand_mi] += reallocate_points
|
||||
|
||||
if sum(1 for pt in tmp_reallocate_result if pt > 0) > component_feeders[part_index]:
|
||||
if sum(1 for pt in tmp_reallocate_result if pt > 0) > component_data.iloc[part_index]['feeder-limit']:
|
||||
continue
|
||||
|
||||
assignment_result[supply_mi][part_index] -= reallocate_points
|
||||
|
@ -1,58 +1,317 @@
|
||||
import os
|
||||
import pickle
|
||||
import random
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import torch.nn
|
||||
|
||||
from base_optimizer.optimizer_interface import *
|
||||
from generator import *
|
||||
from estimator import *
|
||||
|
||||
os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'
|
||||
|
||||
|
||||
class Net(torch.nn.Module):
|
||||
def __init__(self, input_size, hidden_size=1024, output_size=1):
|
||||
super(Net, self).__init__()
|
||||
self.fc1 = torch.nn.Linear(input_size, hidden_size)
|
||||
self.relu = torch.nn.ReLU() # 激活函数
|
||||
self.fc2 = torch.nn.Linear(hidden_size, output_size)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.fc1(x)
|
||||
x = self.relu(x)
|
||||
x = self.fc2(x)
|
||||
return x
|
||||
class Heuristic:
|
||||
@staticmethod
|
||||
def apply(cp_points, cp_nozzle, cp_assign):
|
||||
return -1
|
||||
|
||||
|
||||
class LSTMNet(torch.nn.Module):
|
||||
def __init__(self, input_size, hidden_size=256, output_size=1, num_layers=1):
|
||||
super(LSTMNet, self).__init__()
|
||||
|
||||
self.lstm = torch.nn.LSTM(input_size, hidden_size, num_layers)
|
||||
self.fc = torch.nn.Linear(hidden_size, output_size)
|
||||
|
||||
def forward(self, x):
|
||||
x, _ = self.lstm(x) # x is input with size (seq_len, batch_size, input_size)
|
||||
x = self.fc(x)
|
||||
return x[-1, :, ]
|
||||
class LeastPoints(Heuristic):
|
||||
@staticmethod
|
||||
def apply(cp_points, cp_nozzle, cp_assign):
|
||||
machine_points = []
|
||||
for machine_idx in range(len(cp_assign)):
|
||||
if len(cp_assign[machine_idx]) == 0:
|
||||
return machine_idx
|
||||
machine_points.append(sum([cp_points[cp_idx] for cp_idx in cp_assign[machine_idx]]))
|
||||
return np.argmin(machine_points)
|
||||
|
||||
|
||||
def selective_initialization(component_points, population_size, machine_number):
|
||||
# assignment_result = [[0 for _ in range(len(component_points))] for _ in range(machine_number)]
|
||||
assignment_result = []
|
||||
|
||||
return assignment_result
|
||||
class LeastNzTypes(Heuristic):
|
||||
@staticmethod
|
||||
def apply(cp_points, cp_nozzle, cp_assign):
|
||||
machine_nozzle = []
|
||||
for machine_idx in range(len(cp_assign)):
|
||||
if len(cp_assign[machine_idx]) == 0:
|
||||
return machine_idx
|
||||
machine_nozzle.append([cp_nozzle[cp_idx] for cp_idx in cp_assign[machine_idx]])
|
||||
return np.argmin([len(set(nozzle)) for nozzle in machine_nozzle])
|
||||
|
||||
|
||||
def optimizer_hyperheuristc(pcb_data, component_data, machine_number):
|
||||
class LeastCpTypes(Heuristic):
|
||||
@staticmethod
|
||||
def apply(cp_points, cp_nozzle, cp_assign):
|
||||
return np.argmin([len(cp) for cp in cp_assign])
|
||||
|
||||
|
||||
class LeastCpNzRatio(Heuristic):
|
||||
@staticmethod
|
||||
def apply(cp_points, cp_nozzle, cp_assign):
|
||||
machine_nz_type, machine_cp_type = [], []
|
||||
for machine_idx in range(len(cp_assign)):
|
||||
if len(cp_assign[machine_idx]) == 0:
|
||||
return machine_idx
|
||||
machine_nz_type.append([cp_nozzle[cp_idx] for cp_idx in cp_assign[machine_idx]])
|
||||
machine_cp_type.append(cp_assign[machine_idx])
|
||||
return np.argmin(
|
||||
[len(machine_cp_type[machine_idx]) / (len(machine_nz_type[machine_idx]) + 1e-5) for machine_idx in
|
||||
range(len(cp_assign))])
|
||||
|
||||
|
||||
def nozzle_assignment(cp_points, cp_nozzle, cp_assign):
|
||||
nozzle_heads, nozzle_points = defaultdict(int), defaultdict(int)
|
||||
|
||||
for cp_idx in cp_assign:
|
||||
nozzle_points[cp_nozzle[cp_idx]] += cp_points[cp_idx]
|
||||
nozzle_heads[cp_nozzle[cp_idx]] = 1
|
||||
|
||||
while sum(nozzle_heads.values()) != max_head_index:
|
||||
max_cycle_nozzle = None
|
||||
for nozzle, head_num in nozzle_heads.items():
|
||||
if max_cycle_nozzle is None or nozzle_points[nozzle] / head_num > nozzle_points[max_cycle_nozzle] / \
|
||||
nozzle_heads[max_cycle_nozzle]:
|
||||
max_cycle_nozzle = nozzle
|
||||
|
||||
assert max_cycle_nozzle is not None
|
||||
nozzle_heads[max_cycle_nozzle] += 1
|
||||
return nozzle_heads, nozzle_points
|
||||
|
||||
|
||||
class LeastCycle(Heuristic):
|
||||
@staticmethod
|
||||
def apply(cp_points, cp_nozzle, cp_assign):
|
||||
machine_cycle = []
|
||||
for machine_idx, assign_component in enumerate(cp_assign):
|
||||
if len(assign_component) == 0:
|
||||
return machine_idx
|
||||
|
||||
nozzle_heads, nozzle_points = nozzle_assignment(cp_points, cp_nozzle, assign_component)
|
||||
machine_cycle.append(max(nozzle_points[nozzle] / head for nozzle, head in nozzle_heads.items()))
|
||||
|
||||
return np.argmin(machine_cycle)
|
||||
|
||||
|
||||
class LeastNzChange(Heuristic):
|
||||
@staticmethod
|
||||
def apply(cp_points, cp_nozzle, cp_assign):
|
||||
machine_nozzle_change = []
|
||||
for machine_idx, assign_component in enumerate(cp_assign):
|
||||
if len(assign_component) == 0:
|
||||
return machine_idx
|
||||
|
||||
heads_points = []
|
||||
nozzle_heads, nozzle_points = nozzle_assignment(cp_points, cp_nozzle, assign_component)
|
||||
for nozzle, head in nozzle_heads.items():
|
||||
for _ in range(head):
|
||||
heads_points.append(nozzle_points[nozzle] / nozzle_heads[nozzle])
|
||||
machine_nozzle_change.append(np.std(heads_points))
|
||||
|
||||
return np.argmin(machine_nozzle_change)
|
||||
|
||||
|
||||
class LeastPickup(Heuristic):
|
||||
@staticmethod
|
||||
def apply(cp_points, cp_nozzle, cp_assign):
|
||||
machine_pick_up = []
|
||||
for machine_idx, assign_component in enumerate(cp_assign):
|
||||
if len(assign_component) == 0:
|
||||
return machine_idx
|
||||
nozzle_heads, nozzle_points = nozzle_assignment(cp_points, cp_nozzle, assign_component)
|
||||
|
||||
nozzle_level, nozzle_counter = defaultdict(int), defaultdict(int)
|
||||
level_points = defaultdict(int)
|
||||
|
||||
for cp_idx in sorted(assign_component, key=lambda x: cp_points[x], reverse=True):
|
||||
nozzle, points = cp_nozzle[cp_idx], cp_points[cp_idx]
|
||||
if nozzle_counter[nozzle] and nozzle_counter[nozzle] % nozzle_heads[nozzle] == 0:
|
||||
nozzle_level[nozzle] += 1
|
||||
level = nozzle_level[nozzle]
|
||||
level_points[level] = max(level_points[level], points)
|
||||
nozzle_counter[nozzle] += 1
|
||||
|
||||
machine_pick_up.append(sum(points for points in level_points.values()))
|
||||
return np.argmin(machine_pick_up)
|
||||
|
||||
|
||||
def generate_pattern(heuristic_map, cp_points):
|
||||
"""
|
||||
Generates a random pattern.
|
||||
:return: The generated pattern string.
|
||||
"""
|
||||
return "".join([random.choice(list(heuristic_map.keys())) for _ in range(random.randrange(1, len(cp_points)))])
|
||||
|
||||
|
||||
def crossover(parent1, parent2):
|
||||
"""
|
||||
Attempt to perform crossover between two chromosomes.
|
||||
:param parent1: The first parent.
|
||||
:param parent2: The second parent.
|
||||
:return: The two individuals after crossover has been performed.
|
||||
"""
|
||||
point1, point2 = random.randrange(len(parent1)), random.randrange(len(parent2))
|
||||
substr1, substr2 = parent1[point1:], parent2[point2:]
|
||||
offspring1, offspring2 = "".join((parent1[:point1], substr2)), "".join((parent2[:point2], substr1))
|
||||
return offspring1, offspring2
|
||||
|
||||
|
||||
def mutation(heuristic_map, cp_points, individual):
|
||||
"""
|
||||
Attempts to mutate the individual by replacing a random heuristic in the chromosome by a generated pattern.
|
||||
:param individual: The individual to mutate.
|
||||
:return: The mutated individual.
|
||||
"""
|
||||
pattern = list(individual)
|
||||
mutation_point = random.randrange(len(pattern))
|
||||
pattern[mutation_point] = generate_pattern(heuristic_map, cp_points)
|
||||
return ''.join(pattern)
|
||||
|
||||
|
||||
def population_initialization(population_size, heuristic_map, cp_points):
|
||||
return [generate_pattern(heuristic_map, cp_points) for _ in range(population_size)]
|
||||
|
||||
|
||||
def convert_assignment_result(heuristic_map, cp_points, cp_nozzle, component_list, individual, machine_number):
|
||||
machine_cp_assign = [[] for _ in range(machine_number)]
|
||||
for idx, cp_idx in enumerate(component_list):
|
||||
h = individual[idx % len(individual)]
|
||||
machine_idx = heuristic_map[h].apply(cp_points, cp_nozzle, machine_cp_assign)
|
||||
machine_cp_assign[machine_idx].append(cp_idx)
|
||||
|
||||
return machine_cp_assign
|
||||
|
||||
|
||||
def cal_individual_val(heuristic_map, cp_points, cp_nozzle, board_width, board_height, component_list,
|
||||
individual, machine_number, estimator):
|
||||
machine_cp_assign = convert_assignment_result(heuristic_map, cp_points, cp_nozzle, component_list,
|
||||
individual, machine_number)
|
||||
objective_val = []
|
||||
for machine_idx in range(machine_number):
|
||||
machine_cp_points, machine_cp_nozzle = defaultdict(int), defaultdict(str)
|
||||
for cp_idx in machine_cp_assign[machine_idx]:
|
||||
machine_cp_points[cp_idx] = cp_points[cp_idx]
|
||||
machine_cp_nozzle[cp_idx] = cp_nozzle[cp_idx]
|
||||
|
||||
objective_val.append(estimator.neural_network(machine_cp_points, machine_cp_nozzle, board_width, board_height))
|
||||
# objective_val.append(estimator.heuristic_genetic(machine_cp_points, machine_cp_nozzle))
|
||||
|
||||
return objective_val
|
||||
|
||||
|
||||
def line_optimizer_hyperheuristic(component_data, pcb_data, machine_number):
|
||||
heuristic_map = {
|
||||
'p': LeastPoints,
|
||||
'n': LeastNzChange,
|
||||
'c': LeastCpTypes,
|
||||
'r': LeastCpNzRatio,
|
||||
'k': LeastCycle,
|
||||
'g': LeastNzChange,
|
||||
'u': LeastPickup,
|
||||
}
|
||||
|
||||
# genetic-based hyper-heuristic
|
||||
crossover_rate, mutation_rate = 0.8, 0.1
|
||||
population_size, n_generations = 200, 500
|
||||
population_size, n_generations = 20, 100
|
||||
n_iterations = 10
|
||||
|
||||
# todo: how to generate initial population (random?)
|
||||
# assignment_result = selective_initialization(component_points, population_size, machine_number)
|
||||
assignment_result = []
|
||||
estimator = Estimator()
|
||||
|
||||
best_val, best_component_list = None, None
|
||||
best_individual = None
|
||||
|
||||
division_component_data = pd.DataFrame(columns=component_data.columns)
|
||||
for _, data in component_data.iterrows():
|
||||
feeder_limit = data['feeder-limit']
|
||||
data['feeder-limit'], data['points'] = 1, int(data['points'] / data['feeder-limit'])
|
||||
for _ in range(feeder_limit):
|
||||
division_component_data = pd.concat([division_component_data, pd.DataFrame(data).T])
|
||||
division_component_data = division_component_data.reset_index()
|
||||
|
||||
component_list = [idx for idx, data in division_component_data.iterrows() if data['points'] > 0]
|
||||
cp_points, cp_nozzle = defaultdict(int), defaultdict(str)
|
||||
for idx, data in division_component_data.iterrows():
|
||||
cp_points[idx], cp_nozzle[idx] = data['points'], data['nz']
|
||||
|
||||
board_width, board_height = pcb_data['x'].max() - pcb_data['x'].min(), pcb_data['y'].max() - pcb_data['y'].min()
|
||||
|
||||
with tqdm(total=n_generations * n_iterations) as pbar:
|
||||
pbar.set_description('hyper-heuristic algorithm process for PCB assembly line balance')
|
||||
for _ in range(n_iterations):
|
||||
random.shuffle(component_list)
|
||||
new_population = []
|
||||
population = population_initialization(population_size, heuristic_map, cp_points)
|
||||
|
||||
# calculate fitness value
|
||||
pop_val = []
|
||||
for individual in population:
|
||||
val = cal_individual_val(heuristic_map, cp_points, cp_nozzle, board_width, board_height,
|
||||
component_list, individual, machine_number, estimator)
|
||||
pop_val.append(max(val))
|
||||
|
||||
for _ in range(n_generations):
|
||||
select_index = get_top_k_value(pop_val, population_size - len(new_population), reverse=False)
|
||||
population = [population[idx] for idx in select_index]
|
||||
pop_val = [pop_val[idx] for idx in select_index]
|
||||
|
||||
population += new_population
|
||||
for individual in new_population:
|
||||
val = cal_individual_val(heuristic_map, cp_points, cp_nozzle, board_width, board_height,
|
||||
component_list, individual, machine_number, estimator)
|
||||
pop_val.append(max(val))
|
||||
|
||||
# min-max convert
|
||||
max_val = max(pop_val)
|
||||
sel_pop_val = list(map(lambda v: max_val - v, pop_val))
|
||||
sum_pop_val = sum(sel_pop_val) + 1e-10
|
||||
sel_pop_val = [v / sum_pop_val + 1e-3 for v in sel_pop_val]
|
||||
|
||||
# crossover and mutation
|
||||
new_population = []
|
||||
for pop in range(population_size):
|
||||
if pop % 2 == 0 and np.random.random() < crossover_rate:
|
||||
index1 = roulette_wheel_selection(sel_pop_val)
|
||||
while True:
|
||||
index2 = roulette_wheel_selection(sel_pop_val)
|
||||
if index1 != index2:
|
||||
break
|
||||
|
||||
offspring1, offspring2 = crossover(population[index1], population[index2])
|
||||
|
||||
if np.random.random() < mutation_rate:
|
||||
offspring1 = mutation(heuristic_map, cp_points, offspring1)
|
||||
|
||||
if np.random.random() < mutation_rate:
|
||||
offspring2 = mutation(heuristic_map, cp_points, offspring2)
|
||||
|
||||
new_population.append(offspring1)
|
||||
new_population.append(offspring2)
|
||||
|
||||
pbar.update(1)
|
||||
|
||||
val = cal_individual_val(heuristic_map, cp_points, cp_nozzle, board_width, board_height,
|
||||
component_list, population[0], machine_number, estimator)
|
||||
|
||||
val = max(val)
|
||||
if best_val is None or val < best_val:
|
||||
best_val = val
|
||||
best_individual = population[0]
|
||||
best_component_list = component_list.copy()
|
||||
|
||||
machine_cp_points = convert_assignment_result(heuristic_map, cp_points, cp_nozzle, best_component_list,
|
||||
best_individual, machine_number)
|
||||
|
||||
val = cal_individual_val(heuristic_map, cp_points, cp_nozzle, board_width, board_height,
|
||||
best_component_list, best_individual, machine_number, estimator)
|
||||
print(val)
|
||||
|
||||
assignment_result = [[0 for _ in range(len(component_data))] for _ in range(machine_number)]
|
||||
for machine_idx in range(machine_number):
|
||||
for cp_idx in machine_cp_points[machine_idx]:
|
||||
idx = division_component_data.iloc[cp_idx]['index']
|
||||
assignment_result[machine_idx][idx] += cp_points[cp_idx]
|
||||
print(assignment_result)
|
||||
return assignment_result
|
||||
|
||||
|
||||
@ -67,9 +326,9 @@ if __name__ == '__main__':
|
||||
help='determine whether overwriting the training and testing data')
|
||||
parser.add_argument('--train_file', default='train_data.txt', type=str, help='training file path')
|
||||
parser.add_argument('--test_file', default='test_data.txt', type=str, help='testing file path')
|
||||
parser.add_argument('--num_epochs', default=15000, type=int, help='number of epochs for training process')
|
||||
parser.add_argument('--batch_size', default=100000, type=int, help='size of training batch')
|
||||
parser.add_argument('--lr', default=1e-4, type=float, help='learning rate for the network')
|
||||
parser.add_argument('--num_epochs', default=8000, type=int, help='number of epochs for training process')
|
||||
parser.add_argument('--batch_size', default=10000, type=int, help='size of training batch')
|
||||
parser.add_argument('--lr', default=1e-5, type=float, help='learning rate for the network')
|
||||
|
||||
params = parser.parse_args()
|
||||
|
||||
@ -80,8 +339,9 @@ if __name__ == '__main__':
|
||||
file = {params.train_file: params.batch_size,
|
||||
params.test_file: params.batch_size // data_mgr.get_update_round() // 5}
|
||||
for file_name, file_batch_size in file.items():
|
||||
for _ in range(int(file_batch_size)):
|
||||
with open('opt/' + file_name, 'a') as f:
|
||||
for _ in range(int(file_batch_size)):
|
||||
|
||||
mode = file_name.split('.')[0].split('_')[0]
|
||||
pcb_data, component_data = data_mgr.generator(mode) # random generate a PCB data
|
||||
# data_mgr.remover() # remove the last saved data
|
||||
@ -89,27 +349,27 @@ if __name__ == '__main__':
|
||||
|
||||
info = base_optimizer(1, pcb_data, component_data,
|
||||
feeder_data=pd.DataFrame(columns=['slot', 'part', 'arg']),
|
||||
method='feeder_scan',
|
||||
hinter=True)
|
||||
method='feeder-scan', hinter=True)
|
||||
|
||||
data_mgr.recorder(f, info, pcb_data, component_data)
|
||||
f.close()
|
||||
|
||||
net = Net(input_size=data_mgr.get_feature(), output_size=1).to(device)
|
||||
if params.train:
|
||||
data = data_mgr.loader('opt/' + params.train_file)
|
||||
if params.train:
|
||||
x_fit, y_fit = np.array(data[2:]).T, np.array([data[1]]).T
|
||||
lr = LinearRegression()
|
||||
lr.fit(x_fit, y_fit)
|
||||
|
||||
x_train, y_train = np.array(data[0][::10]), lr.predict(x_fit[::10])
|
||||
# x_train, y_train = np.array(data[0]), np.array(data[2])
|
||||
x_train = np.array(data[0][::data_mgr.get_update_round()])
|
||||
# y_train = lr.predict(x_fit[::data_mgr.get_update_round()])
|
||||
y_train = np.array(data[1][::data_mgr.get_update_round()])
|
||||
|
||||
x_train = torch.from_numpy(x_train.reshape((-1, np.shape(x_train)[1]))).float().to(device)
|
||||
y_train = torch.from_numpy(y_train.reshape((-1, 1))).float().to(device)
|
||||
|
||||
optimizer = torch.optim.Adam(net.parameters(), lr=params.lr)
|
||||
# scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=6000, gamma=0.8)
|
||||
# scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5000, gamma=0.1)
|
||||
|
||||
loss_func = torch.nn.MSELoss()
|
||||
|
||||
@ -120,7 +380,7 @@ if __name__ == '__main__':
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
# scheduler.step()
|
||||
if epoch % 50 == 0:
|
||||
if epoch % 100 == 0:
|
||||
print('Epoch: ', epoch, ', Loss: ', loss.item())
|
||||
if loss.item() < 1e-4:
|
||||
break
|
||||
@ -144,35 +404,43 @@ if __name__ == '__main__':
|
||||
torch.save(net.state_dict(), 'model/net_model.pth')
|
||||
with open('model/lr_model.pkl', 'wb') as f:
|
||||
pickle.dump(lr, f)
|
||||
# torch.save(optimizer.state_dict(), 'model/optimizer_state.pth')
|
||||
torch.save(optimizer.state_dict(), 'model/optimizer_state.pth')
|
||||
else:
|
||||
with open('model/lr_model.pkl', 'rb') as f:
|
||||
lr = pickle.load(f)
|
||||
net.load_state_dict(torch.load('model/net_model.pth'))
|
||||
# optimizer = torch.optim.Adam(net.parameters(), lr=learning_rate)
|
||||
# optimizer.load_state_dict(torch.load('model/optimizer_state.pth'))
|
||||
|
||||
data = data_mgr.loader('opt/' + params.test_file)
|
||||
# x_test, y_test = np.array(data[0]), np.array(data[1])
|
||||
x_test, y_test = np.array(data[0]), lr.predict(np.array(data[2:]).T)
|
||||
|
||||
x_test, y_test = torch.from_numpy(x_test.reshape((-1, np.shape(x_test)[1]))).float().to(device), \
|
||||
torch.from_numpy(y_test.reshape((-1, 1))).float().to(device)
|
||||
x_test, y_test = np.array(data[0]), np.array(data[1])
|
||||
# x_test, y_test = np.array(data[0]), lr.predict(np.array(data[2:]).T)
|
||||
x_test = torch.from_numpy(x_test.reshape((-1, np.shape(x_test)[1]))).float().to(device)
|
||||
|
||||
net.eval()
|
||||
with torch.no_grad():
|
||||
net_predict = net(x_test).view(-1)
|
||||
pred_time, real_time = net_predict.cpu().detach().numpy(), y_test.view(-1).cpu().detach().numpy()
|
||||
pred_time = net(x_test).view(-1).cpu().detach().numpy()
|
||||
x_test = x_test.cpu().detach().numpy()
|
||||
|
||||
pred_error = np.array([])
|
||||
for t1, t2 in np.nditer([pred_time, real_time]):
|
||||
over_set = []
|
||||
pred_idx, pred_error = 0, np.array([])
|
||||
for t1, t2 in np.nditer([pred_time, y_test.reshape(-1)]):
|
||||
pred_error = np.append(pred_error, abs(t1 - t2) / (t2 + 1e-10) * 100)
|
||||
print(pred_time)
|
||||
print(real_time)
|
||||
print('--------------------------------------')
|
||||
print(f'average prediction error for test data : {np.average(pred_error): .2f}% ')
|
||||
print(f'maximum prediction error for test data : {np.max(pred_error): .2f}% ')
|
||||
|
||||
mse = np.linalg.norm(pred_time - real_time)
|
||||
if pred_error[-1] > 5:
|
||||
over_set.append(pred_idx + 1)
|
||||
print(f'\033[0;31;31midx: {pred_idx + 1: d}, net: {t1: .3f}, real: {t2: .3f}, '
|
||||
f'gap: {pred_error[-1]: .3f}\033[0m')
|
||||
else:
|
||||
pass
|
||||
# print(f'idx: {pred_idx + 1: d}, net: {t1: .3f}, real: {t2: .3f}, gap: {pred_error[-1]: .3f}')
|
||||
|
||||
pred_idx += 1
|
||||
print('over:', over_set)
|
||||
print('size:', len(over_set))
|
||||
|
||||
print('--------------------------------------')
|
||||
print(f'average prediction error for test data : {np.average(pred_error): .3f}% ')
|
||||
print(f'maximum prediction error for test data : {np.max(pred_error): .3f}% ')
|
||||
|
||||
mse = np.linalg.norm(pred_time - y_test.reshape(-1))
|
||||
print(f'mean square error for test data result : {mse: 2f} ')
|
||||
|
||||
|
@ -1,49 +1,18 @@
|
||||
from base_optimizer.optimizer_common import *
|
||||
from estimator import *
|
||||
|
||||
|
||||
# 生产过程中不允许吸嘴更换/点的拾取贴装仅与供料器槽位/模组相关
|
||||
def objective_value_calculate(component_assignment, component_nozzle, task_block_weight, machine_number):
|
||||
machine_assembly_time = []
|
||||
for machine_index in range(max_machine_index):
|
||||
task_block_number, total_point_number = 0, sum(component_assignment[machine_index])
|
||||
nozzle_points, nozzle_heads = defaultdict(int), defaultdict(int)
|
||||
|
||||
for part, points in enumerate(component_assignment[machine_index]):
|
||||
nozzle = component_nozzle[part]
|
||||
nozzle_points[nozzle] += points
|
||||
nozzle_heads[nozzle] = 1
|
||||
remaining_head = max_head_index - len(nozzle_heads)
|
||||
|
||||
nozzle_fraction = []
|
||||
for nozzle, points in nozzle_points.items():
|
||||
val = remaining_head * points / total_point_number
|
||||
nozzle_heads[nozzle] += math.floor(val)
|
||||
nozzle_fraction.append([nozzle, val - math.floor(val)])
|
||||
|
||||
remaining_head = max_head_index - sum(nozzle_heads.values())
|
||||
sorted(nozzle_fraction, key=lambda x: x[1])
|
||||
nozzle_fraction_index = 0
|
||||
while remaining_head > 0:
|
||||
nozzle_heads[nozzle_fraction[nozzle_fraction_index][0]] += 1
|
||||
remaining_head -= 1
|
||||
|
||||
for nozzle, heads_number in nozzle_heads.items():
|
||||
task_block_number = max(task_block_weight, math.ceil(nozzle_points[nozzle] / heads_number))
|
||||
|
||||
machine_assembly_time.append(
|
||||
(t_pick + t_place) * sum(component_assignment[machine_index]) + task_block_number * task_block_weight)
|
||||
return max(machine_assembly_time)
|
||||
|
||||
|
||||
def random_component_assignment(component_points, component_nozzle, component_feeders, task_block_weight, machine_number):
|
||||
component_points_cpy = copy.deepcopy(component_points)
|
||||
component_number = len(component_points_cpy)
|
||||
assignment_result = [[0 for _ in range(component_number)] for _ in range(machine_number)]
|
||||
|
||||
def random_component_assignment(pcb_data, component_data, machine_number, estimator=None):
|
||||
# == the set of feasible component type for each nozzle type
|
||||
nozzle_part_list = defaultdict(list)
|
||||
for index, nozzle in enumerate(component_nozzle):
|
||||
nozzle_part_list[nozzle].append(index)
|
||||
component_points = []
|
||||
for idx, data in component_data.iterrows():
|
||||
component_points.append(data.points)
|
||||
nozzle_part_list[data.nz].append(idx)
|
||||
|
||||
component_number = len(component_data)
|
||||
assignment_result = [[0 for _ in range(component_number)] for _ in range(machine_number)]
|
||||
|
||||
# === ensure every nozzle types ===
|
||||
selected_part = []
|
||||
for part_list in nozzle_part_list.values():
|
||||
@ -51,7 +20,7 @@ def random_component_assignment(component_points, component_nozzle, component_fe
|
||||
machine_index = random.randint(0, machine_number - 1)
|
||||
|
||||
assignment_result[machine_index][part] += 1
|
||||
component_points_cpy[part] -= 1
|
||||
component_points[part] -= 1
|
||||
selected_part.append(part)
|
||||
|
||||
# === assign one placement which has not been selected ===
|
||||
@ -60,7 +29,7 @@ def random_component_assignment(component_points, component_nozzle, component_fe
|
||||
continue
|
||||
|
||||
assignment_result[random.randint(0, machine_number - 1)][part] += 1
|
||||
component_points_cpy[part] -= 1
|
||||
component_points[part] -= 1
|
||||
|
||||
machine_assign = list(range(machine_number))
|
||||
random.shuffle(machine_assign)
|
||||
@ -73,62 +42,74 @@ def random_component_assignment(component_points, component_nozzle, component_fe
|
||||
if assignment_result[idx][part] > 0 or idx == machine_index:
|
||||
feeder_counter += 1
|
||||
|
||||
if component_points_cpy[part] == 0 or feeder_counter > component_feeders[part]:
|
||||
if component_points[part] == 0 or feeder_counter > component_data.iloc[part]['feeder-limit']:
|
||||
continue
|
||||
|
||||
# feeder limit restriction
|
||||
points = random.randint(1, component_points_cpy[part])
|
||||
points = random.randint(1, component_points[part])
|
||||
assignment_result[machine_index][part] += points
|
||||
component_points_cpy[part] -= points
|
||||
if component_points_cpy[part] == 0:
|
||||
component_points[part] -= points
|
||||
if component_points[part] == 0:
|
||||
finished_assign_counter += 1
|
||||
|
||||
assert sum(component_points_cpy) == 0
|
||||
assert sum(component_points) == 0
|
||||
val = 0
|
||||
if estimator:
|
||||
cp_items = estimator.convert(pcb_data, component_data, assignment_result)
|
||||
for machine_index in range(machine_number):
|
||||
cp_points, cp_nozzle, cp_width, cp_height, board_width, board_height = cp_items[machine_index]
|
||||
# objective_value.append(
|
||||
# estimator.neural_network(cp_points, cp_nozzle, cp_width, cp_height, board_width, board_height))
|
||||
val = max(val, estimator.heuristic(cp_points, cp_nozzle))
|
||||
|
||||
return objective_value_calculate(assignment_result, component_nozzle, task_block_weight), assignment_result
|
||||
return val, assignment_result
|
||||
|
||||
|
||||
def greedy_component_assignment(component_points, component_nozzle, component_feeders, task_block_weight):
|
||||
pass # 不清楚原文想说什么
|
||||
|
||||
|
||||
def local_search_component_assignment(component_points, component_nozzle, component_feeders, task_block_weight,
|
||||
machine_number):
|
||||
def local_search_component_assignment(pcb_data, component_data, machine_number, estimator):
|
||||
# maximum number of iterations : 5000
|
||||
# maximum number of unsuccessful iterations: 50
|
||||
component_number = len(component_points)
|
||||
component_number = len(component_data)
|
||||
iteration_counter, unsuccessful_iteration_counter = 5000, 50
|
||||
optimal_val, optimal_assignment = random_component_assignment(component_points, component_nozzle, component_feeders,
|
||||
task_block_weight, machine_number)
|
||||
optimal_val, optimal_assignment = random_component_assignment(pcb_data, component_data, machine_number, estimator)
|
||||
for _ in range(iteration_counter):
|
||||
machine_index = random.randint(0, machine_number - 1)
|
||||
if sum(optimal_assignment[machine_index]) == 0:
|
||||
machine_idx = random.randint(0, machine_number - 1)
|
||||
if sum(optimal_assignment[machine_idx]) == 0:
|
||||
continue
|
||||
|
||||
part_set = []
|
||||
for component_index in range(component_number):
|
||||
if optimal_assignment[machine_index][component_index] != 0:
|
||||
part_set.append(component_index)
|
||||
component_index = random.sample(part_set, 1)[0]
|
||||
r = random.randint(1, optimal_assignment[machine_index][component_index])
|
||||
for part_idx in range(component_number):
|
||||
if optimal_assignment[machine_idx][part_idx] != 0:
|
||||
part_set.append(part_idx)
|
||||
part_idx = random.sample(part_set, 1)[0]
|
||||
r = random.randint(1, optimal_assignment[machine_idx][part_idx])
|
||||
|
||||
assignment = copy.deepcopy(optimal_assignment)
|
||||
cyclic_counter = 0
|
||||
swap_machine_index = None
|
||||
while cyclic_counter <= 2 * machine_index:
|
||||
swap_machine_idx = None
|
||||
while cyclic_counter <= 2 * machine_idx:
|
||||
cyclic_counter += 1
|
||||
swap_machine_index = random.randint(0, machine_number - 1)
|
||||
swap_machine_idx = random.randint(0, machine_number - 1)
|
||||
feeder_available = 0
|
||||
for machine in range(machine_number):
|
||||
if optimal_assignment[machine][component_index] or machine == swap_machine_index:
|
||||
if optimal_assignment[machine][part_idx] or machine == swap_machine_idx:
|
||||
feeder_available += 1
|
||||
|
||||
if feeder_available <= component_feeders[component_index] and swap_machine_index != machine_index:
|
||||
if feeder_available <= component_data.iloc[part_idx]['feeder-limit'] and swap_machine_idx != machine_idx:
|
||||
break
|
||||
assert swap_machine_index is not None
|
||||
assignment[machine_index][component_index] -= r
|
||||
assignment[swap_machine_index][component_index] += r
|
||||
val = objective_value_calculate(assignment, component_nozzle, task_block_weight)
|
||||
assert swap_machine_idx is not None
|
||||
assignment[machine_idx][part_idx] -= r
|
||||
assignment[swap_machine_idx][part_idx] += r
|
||||
|
||||
val = 0
|
||||
cp_items = estimator.convert(pcb_data, component_data, assignment)
|
||||
for machine_index in range(machine_number):
|
||||
cp_points, cp_nozzle, _, _, _, _ = cp_items[machine_index]
|
||||
val = max(val, estimator.heuristic(cp_points, cp_nozzle))
|
||||
|
||||
if val < optimal_val:
|
||||
optimal_assignment, optimal_val = assignment, val
|
||||
unsuccessful_iteration_counter = 50
|
||||
@ -140,9 +121,9 @@ def local_search_component_assignment(component_points, component_nozzle, compon
|
||||
return optimal_val, optimal_assignment
|
||||
|
||||
|
||||
def reconfig_crossover_operation(component_points, component_feeders, parent1, parent2, machine_number):
|
||||
def reconfig_crossover_operation(component_data, parent1, parent2, machine_number):
|
||||
offspring1, offspring2 = copy.deepcopy(parent1), copy.deepcopy(parent2)
|
||||
component_number = len(component_points)
|
||||
component_number = len(component_data)
|
||||
|
||||
# === crossover ===
|
||||
mask_bit = []
|
||||
@ -161,57 +142,57 @@ def reconfig_crossover_operation(component_points, component_feeders, parent1, p
|
||||
|
||||
# === balancing ===
|
||||
# equally to reach the correct number
|
||||
for component_index in range(component_number):
|
||||
for part_index in range(component_number):
|
||||
for offspring in [offspring1, offspring2]:
|
||||
additional_points = sum([offspring[mt][component_index] for mt in range(machine_number)]) - \
|
||||
component_points[component_index]
|
||||
additional_points = sum([offspring[mt][part_index] for mt in range(machine_number)]) - \
|
||||
component_data.iloc[part_index]['points']
|
||||
if additional_points > 0:
|
||||
# if a component type has more placements, decrease the assigned values on every head equally keeping
|
||||
# the proportion of the number of placement among the heads
|
||||
points_list = []
|
||||
for machine_index in range(machine_number):
|
||||
points = math.floor(
|
||||
additional_points * offspring[machine_index][component_index] / component_points[component_index])
|
||||
additional_points * offspring[machine_index][part_index] / component_data[part_index]['points'])
|
||||
points_list.append(points)
|
||||
offspring[machine_index][component_index] -= points
|
||||
offspring[machine_index][part_index] -= points
|
||||
additional_points -= sum(points_list)
|
||||
|
||||
for machine_index in range(machine_number):
|
||||
if additional_points == 0:
|
||||
break
|
||||
if offspring[machine_index][component_index] == 0:
|
||||
if offspring[machine_index][part_index] == 0:
|
||||
continue
|
||||
offspring[machine_index][component_index] -= 1
|
||||
offspring[machine_index][part_index] -= 1
|
||||
additional_points += 1
|
||||
elif additional_points < 0:
|
||||
# otherwise, increase the assigned nonzero values equally
|
||||
machine_set = []
|
||||
for machine_index in range(machine_number):
|
||||
if offspring[machine_index][component_index] == 0:
|
||||
if offspring[machine_index][part_index] == 0:
|
||||
continue
|
||||
machine_set.append(machine_index)
|
||||
|
||||
points = -math.ceil(additional_points / len(machine_set))
|
||||
for machine_index in machine_set:
|
||||
offspring[machine_index][component_index] += points
|
||||
offspring[machine_index][part_index] += points
|
||||
additional_points += points
|
||||
|
||||
for machine_index in machine_set:
|
||||
if additional_points == 0:
|
||||
break
|
||||
offspring[machine_index][component_index] += 1
|
||||
offspring[machine_index][part_index] += 1
|
||||
additional_points -= 1
|
||||
|
||||
# === 结果校验 ===
|
||||
for offspring in [offspring1, offspring2]:
|
||||
for part in range(component_number):
|
||||
pt = sum(offspring[mt][part] for mt in range(machine_number))
|
||||
assert pt == component_points[part]
|
||||
assert pt == component_data.iloc[part]['points']
|
||||
|
||||
return offspring1, offspring2
|
||||
|
||||
|
||||
def reconfig_mutation_operation(component_feeders, parent, machine_number):
|
||||
def reconfig_mutation_operation(component_data, parent, machine_number):
|
||||
offspring = copy.deepcopy(parent)
|
||||
|
||||
swap_direction = random.randint(0, 1)
|
||||
@ -228,10 +209,10 @@ def reconfig_mutation_operation(component_feeders, parent, machine_number):
|
||||
swap_points = random.randint(1, offspring[swap_machine1][swap_component_index])
|
||||
|
||||
feeder_counter = 0
|
||||
for machine_index in range(max_machine_index):
|
||||
for machine_index in range(machine_number):
|
||||
if offspring[swap_machine1][swap_component_index] < swap_points or machine_index == swap_machine2:
|
||||
feeder_counter += 1
|
||||
if feeder_counter > component_feeders[swap_component_index]:
|
||||
if feeder_counter > component_data.iloc[swap_component_index]['feeder-limit']:
|
||||
return offspring
|
||||
|
||||
offspring[swap_machine1][swap_component_index] -= swap_points
|
||||
@ -239,7 +220,7 @@ def reconfig_mutation_operation(component_feeders, parent, machine_number):
|
||||
return offspring
|
||||
|
||||
|
||||
def evolutionary_component_assignment(component_points, component_nozzle, component_feeders, task_block_weight, machine_number):
|
||||
def evolutionary_component_assignment(pcb_data, component_data, machine_number, estimator):
|
||||
# population size: 10
|
||||
# probability of the mutation: 0.1
|
||||
# probability of the crossover: 0.8
|
||||
@ -250,9 +231,7 @@ def evolutionary_component_assignment(component_points, component_nozzle, compon
|
||||
|
||||
population = []
|
||||
for _ in range(population_size):
|
||||
population.append(
|
||||
random_component_assignment(component_points, component_nozzle, component_feeders, task_block_weight,
|
||||
machine_number)[1])
|
||||
population.append(random_component_assignment(pcb_data, component_data, machine_number, None)[1])
|
||||
|
||||
with tqdm(total=generation_number) as pbar:
|
||||
pbar.set_description('evolutionary algorithm process for PCB assembly line balance')
|
||||
@ -262,7 +241,12 @@ def evolutionary_component_assignment(component_points, component_nozzle, compon
|
||||
# calculate fitness value
|
||||
pop_val = []
|
||||
for individual in population:
|
||||
pop_val.append(objective_value_calculate(individual, component_nozzle, task_block_weight, machine_number))
|
||||
val = 0
|
||||
cp_items = estimator.convert(pcb_data, component_data, individual)
|
||||
for machine_index in range(machine_number):
|
||||
cp_points, cp_nozzle, _, _, _, _ = cp_items[machine_index]
|
||||
val = max(val, estimator.heuristic(cp_points, cp_nozzle))
|
||||
pop_val.append(val)
|
||||
|
||||
select_index = get_top_k_value(pop_val, population_size - len(new_population), reverse=False)
|
||||
population = [population[idx] for idx in select_index]
|
||||
@ -270,7 +254,12 @@ def evolutionary_component_assignment(component_points, component_nozzle, compon
|
||||
|
||||
population += new_population
|
||||
for individual in new_population:
|
||||
pop_val.append(objective_value_calculate(individual, component_nozzle, task_block_weight, machine_number))
|
||||
cp_items = estimator.convert(pcb_data, component_data, individual)
|
||||
val = 0
|
||||
for machine_index in range(machine_index):
|
||||
cp_points, cp_nozzle, _, _, _, _ = cp_items[machine_index]
|
||||
val = max(val, estimator.heuristic(cp_points, cp_nozzle))
|
||||
pop_val.append(val)
|
||||
|
||||
# min-max convert
|
||||
max_val = max(pop_val)
|
||||
@ -288,15 +277,14 @@ def evolutionary_component_assignment(component_points, component_nozzle, compon
|
||||
if index1 != index2:
|
||||
break
|
||||
|
||||
offspring1, offspring2 = reconfig_crossover_operation(component_points, component_feeders,
|
||||
population[index1], population[index2],
|
||||
machine_number)
|
||||
offspring1, offspring2 = reconfig_crossover_operation(component_data, population[index1],
|
||||
population[index2], machine_number)
|
||||
|
||||
if np.random.random() < mutation_rate:
|
||||
offspring1 = reconfig_mutation_operation(component_feeders, offspring1, machine_number)
|
||||
offspring1 = reconfig_mutation_operation(component_data, offspring1, machine_number)
|
||||
|
||||
if np.random.random() < mutation_rate:
|
||||
offspring2 = reconfig_mutation_operation(component_feeders, offspring2, machine_number)
|
||||
offspring2 = reconfig_mutation_operation(component_data, offspring2, machine_number)
|
||||
|
||||
new_population.append(offspring1)
|
||||
new_population.append(offspring2)
|
||||
@ -306,47 +294,26 @@ def evolutionary_component_assignment(component_points, component_nozzle, compon
|
||||
return min(pop_val), population[np.argmin(pop_val)]
|
||||
|
||||
|
||||
def reconfiguration_optimizer(pcb_data, component_data, machine_number):
|
||||
# === data preparation ===
|
||||
component_number = len(component_data)
|
||||
|
||||
component_points = [0 for _ in range(component_number)]
|
||||
component_nozzle = [0 for _ in range(component_number)]
|
||||
component_feeders = [0 for _ in range(component_number)]
|
||||
component_part = [0 for _ in range(component_number)]
|
||||
|
||||
for _, data in pcb_data.iterrows():
|
||||
part_index = component_data[component_data['part'] == data['part']].index.tolist()[0]
|
||||
nozzle = component_data.loc[part_index]['nz']
|
||||
|
||||
component_points[part_index] += 1
|
||||
component_nozzle[part_index] = nozzle
|
||||
component_part[part_index] = data['part']
|
||||
|
||||
component_feeders[part_index] = component_data.loc[part_index]['feeder-limit']
|
||||
|
||||
@timer_wrapper
|
||||
def line_optimizer_reconfiguration(component_data, pcb_data, machine_number):
|
||||
# === assignment of heads to modules is omitted ===
|
||||
optimal_assignment, optimal_val = [], None
|
||||
|
||||
task_block_weight = 5 # element from list [0, 1, 2, 5, 10] task_block ~= cycle
|
||||
estimator = Estimator(task_block_weight=5) # element from list [0, 1, 2, 5, 10] task_block ~= cycle
|
||||
# === assignment of components to heads
|
||||
for i in range(5):
|
||||
if i == 0:
|
||||
# random
|
||||
val, assignment = random_component_assignment(component_points, component_nozzle, component_feeders,
|
||||
task_block_weight, machine_number)
|
||||
val, assignment = random_component_assignment(pcb_data, component_data, machine_number, estimator)
|
||||
elif i == 1:
|
||||
# brute force
|
||||
# which is proved to be useless, since it only ran in reasonable time for the smaller test instances
|
||||
continue
|
||||
elif i == 2:
|
||||
# local search
|
||||
val, assignment = local_search_component_assignment(component_points, component_nozzle, component_feeders,
|
||||
task_block_weight, machine_number)
|
||||
val, assignment = local_search_component_assignment(pcb_data, component_data, machine_number, estimator)
|
||||
elif i == 3:
|
||||
# evolutionary
|
||||
val, assignment = evolutionary_component_assignment(component_points, component_nozzle, component_feeders,
|
||||
task_block_weight, machine_number)
|
||||
val, assignment = evolutionary_component_assignment(pcb_data, component_data, machine_number, estimator)
|
||||
else:
|
||||
# greedy: unclear description
|
||||
continue
|
||||
|
Reference in New Issue
Block a user