增加单机优化方法

This commit is contained in:
2023-03-15 21:14:56 +08:00
parent 6b031dc486
commit 13c1b18e1d
16 changed files with 3320 additions and 893 deletions

View File

@ -0,0 +1,220 @@
from base_optimizer.optimizer_common import *
from ortools.sat.python import cp_model
from collections import defaultdict
@timer_wrapper
def optimizer_aggregation(component_data, pcb_data):
# === phase 0: data preparation ===
M = 1000 # a sufficient large number
a, b = 1, 6 # coefficient
K, I, J, L = max_head_index, 0, 0, 0 # the maximum number of heads, component types, nozzle types and batch level
component_list, nozzle_list = defaultdict(int), defaultdict(int)
cpidx_2_part, nzidx_2_nozzle = {}, {}
for _, data in pcb_data.iterrows():
part = data['part']
if part not in cpidx_2_part.values():
cpidx_2_part[len(cpidx_2_part)] = part
component_list[part] += 1
idx = component_data[component_data['part'] == part].index.tolist()[0]
nozzle = component_data.loc[idx]['nz']
if nozzle not in nzidx_2_nozzle.values():
nzidx_2_nozzle[len(nzidx_2_nozzle)] = nozzle
nozzle_list[nozzle] += 1
I, J = len(component_list.keys()), len(nozzle_list.keys())
L = I + 1
HC = [[M for _ in range(J)] for _ in range(I)] # the handing class when component i is handled by nozzle type j
# represent the nozzle-component compatibility
for i in range(I):
for _, item in enumerate(cpidx_2_part.items()):
index, part = item
cp_idx = component_data[component_data['part'] == part].index.tolist()[0]
nozzle = component_data.loc[cp_idx]['nz']
for j in range(J):
if nzidx_2_nozzle[j] == nozzle:
HC[index][j] = 0
# === phase 1: mathematical model solver ===
model = cp_model.CpModel()
solver = cp_model.CpSolver()
# === Decision Variables ===
# the number of components of type i that are placed by nozzle type j on placement head k
X = {}
for i in range(I):
for j in range(J):
for k in range(K):
X[i, j, k] = model.NewIntVar(0, component_list[cpidx_2_part[i]], 'X_{}_{}_{}'.format(i, j, k))
# the total number of nozzle changes on placement head k
N = {}
for k in range(K):
N[k] = model.NewIntVar(0, J, 'N_{}'.format(k))
# the largest workload of all placement heads
WL = model.NewIntVar(0, len(pcb_data), 'WL')
# whether batch Xijk is placed on level l
Z = {}
for i in range(I):
for j in range(J):
for l in range(L):
for k in range(K):
Z[i, j, l, k] = model.NewBoolVar('Z_{}_{}_{}_{}'.format(i, j, l, k))
# Dlk := 2 if a change of nozzles in the level l + 1 on placement head k
# Dlk := 1 if there are no batches placed on levels higher than l
D = {}
for l in range(L):
for k in range(K):
D[l, k] = model.NewIntVar(0, 2, 'D_{}_{}'.format(l, k))
D_abs = {}
for l in range(L):
for j in range(J):
for k in range(K):
D_abs[l, j, k] = model.NewIntVar(0, M, 'D_abs_{}_{}_{}'.format(l, j, k))
# == Objective function ===
model.Minimize(a * WL + b * sum(N[k] for k in range(K)))
# === Constraint ===
for i in range(I):
model.Add(sum(X[i, j, k] for j in range(J) for k in range(K)) == component_list[cpidx_2_part[i]])
for k in range(K):
model.Add(sum(X[i, j, k] for i in range(I) for j in range(J)) <= WL)
for i in range(I):
for j in range(J):
for k in range(K):
model.Add(X[i, j, k] <= M * sum(Z[i, j, l, k] for l in range(L)))
for i in range(I):
for j in range(J):
for k in range(K):
model.Add(sum(Z[i, j, l, k] for l in range(L)) <= 1)
for i in range(I):
for j in range(J):
for k in range(K):
model.Add(sum(Z[i, j, l, k] for l in range(L)) <= X[i, j, k])
for k in range(K):
for l in range(L - 1):
model.Add(sum(Z[i, j, l, k] for j in range(J) for i in range(I)) >= sum(
Z[i, j, l + 1, k] for j in range(J) for i in range(I)))
for l in range(I):
for k in range(K):
model.Add(sum(Z[i, j, l, k] for i in range(I) for j in range(J)) <= 1)
for l in range(L - 1):
for j in range(J):
for k in range(K):
model.AddAbsEquality(D_abs[l, j, k],
sum(Z[i, j, l, k] for i in range(I)) - sum(Z[i, j, l + 1, k] for i in range(I)))
for k in range(K):
for l in range(L):
model.Add(D[l, k] == sum(D_abs[l, j, k] for j in range(J)))
for k in range(K):
model.Add(N[k] == sum(D[l, k] for l in range(L)) - 1)
for l in range(L):
for k in range(K):
model.Add(0 >= sum(HC[i][j] * Z[i, j, l, k] for i in range(I) for j in range(J)))
# === Main Process ===
component_result, cycle_result = [], []
feeder_slot_result, placement_result, head_sequence = [], [], []
solver.parameters.max_time_in_seconds = 20.0
status = solver.Solve(model)
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
print('total cost = {}'.format(solver.ObjectiveValue()))
# convert cp model solution to standard output
model_cycle_result, model_component_result = [], []
for l in range(L):
model_component_result.append([None for _ in range(K)])
model_cycle_result.append([0 for _ in range(K)])
for k in range(K):
for i in range(I):
for j in range(J):
if solver.BooleanValue(Z[i, j, l, k]) != 0:
model_component_result[-1][k] = cpidx_2_part[i]
model_cycle_result[-1][k] = solver.Value(X[i, j, k])
# remove redundant term
if sum(model_cycle_result[-1]) == 0:
model_component_result.pop()
model_cycle_result.pop()
head_component_index = [0 for _ in range(max_head_index)]
while True:
head_cycle = []
for head, index in enumerate(head_component_index):
head_cycle.append(model_cycle_result[index][head])
if len([cycle for cycle in head_cycle if cycle > 0]) == 0:
break
component_result.append([None for _ in range(max_head_index)])
min_cycle = min([cycle for cycle in head_cycle if cycle > 0])
for head, index in enumerate(head_component_index):
if model_cycle_result[index][head] != 0:
component_result[-1][head] = model_component_result[index][head]
else:
continue
model_cycle_result[index][head] -= min_cycle
if model_cycle_result[index][head] == 0 and index + 1 < len(model_cycle_result):
head_component_index[head] += 1
cycle_result.append(min_cycle)
part_2_index = {}
for index, data in component_data.iterrows():
part_2_index[data['part']] = index
for cycle in range(len(component_result)):
for head in range(max_head_index):
part = component_result[cycle][head]
component_result[cycle][head] = -1 if part is None else part_2_index[part]
feeder_slot_result = feeder_assignment(component_data, pcb_data, component_result, cycle_result)
# === phase 2: heuristic method ===
mount_point_pos = defaultdict(list)
for pcb_idx, data in pcb_data.iterrows():
part = data['part']
part_index = component_data[component_data['part'] == part].index.tolist()[0]
mount_point_pos[part_index].append([data['x'], data['y'], pcb_idx])
for index_ in mount_point_pos.keys():
mount_point_pos[index_].sort(key=lambda x: (x[1], x[0]))
for cycle_idx, _ in enumerate(cycle_result):
for _ in range(cycle_result[cycle_idx]):
placement_result.append([-1 for _ in range(max_head_index)])
for head in range(max_head_index):
if component_result[cycle_idx][head] == -1:
continue
index_ = component_result[cycle_idx][head]
placement_result[-1][head] = mount_point_pos[index_][-1][2]
mount_point_pos[index_].pop()
head_sequence.append(dynamic_programming_cycle_path(pcb_data, placement_result[-1], feeder_slot_result[cycle_idx]))
else:
warnings.warn('No solution found!', UserWarning)
return component_result, cycle_result, feeder_slot_result, placement_result, head_sequence

View File

@ -0,0 +1,204 @@
from base_optimizer.optimizer_common import *
from result_analysis import *
def convert_cell_2_result(pcb_data, component_data, component_cell, population):
assert component_cell['points'].sum() == len(pcb_data)
head_assignment = [[] for _ in range(max_head_index)]
wl = [0 for _ in range(max_head_index)] # workload
e1, e2, e3 = 1, 0.5, 1. / 6
component_result, cycle_result, feeder_slot_result = [], [], []
for index in population:
if component_cell.loc[index]['points'] == 0:
continue
# 元胞对应的元件类型和贴装点数
component_type, component_points = component_cell.loc[index, 'index'], component_cell.loc[index, 'points']
nozzle_change, maxwl = [0 for _ in range(max_head_index)], [0 for _ in range(max_head_index)]
for head in range(max_head_index):
if head_assignment[head]:
assigned_part = head_assignment[head][-1][0]
if component_data.loc[assigned_part]['nz'] != component_data.loc[component_type]['nz']:
nozzle_change[head] = 1
wl1 = wl.copy()
wl1[head] += component_points
maxwl[head] = max(wl1) + e1 * nozzle_change[head]
awl, wl2 = min(maxwl), wl.copy()
for idx, val in enumerate(maxwl):
if val > awl:
wl2[idx] += e3
head_ = wl2.index(min(wl2))
wl[head_] += component_points
head_assignment[head_].append([component_type, component_points])
head_assignment_counter = [0 for _ in range(max_head_index)]
while True:
assigned_part, assigned_cycle = [-1 for _ in range(max_head_index)], [0 for _ in range(max_head_index)]
for head in range(max_head_index):
counter = head_assignment_counter[head]
if head_assignment[head] and head_assignment[head][counter][1] > 0:
assigned_part[head] = head_assignment[head][counter][0]
assigned_cycle[head] = head_assignment[head][counter][1]
nonzero_cycle = [cycle for cycle in assigned_cycle if cycle > 0]
if not nonzero_cycle:
break
cycle = min(nonzero_cycle)
cycle_result.append(cycle)
component_result.append(assigned_part)
for head in range(max_head_index):
counter = head_assignment_counter[head]
if head_assignment[head] and head_assignment[head][counter][1] > 0:
head_assignment[head][counter][1] -= cycle_result[-1]
if head_assignment[head][counter][1] == 0 and counter < len(head_assignment[head]) - 1:
head_assignment_counter[head] += 1
feeder_slot_result = feeder_assignment(component_data, pcb_data, component_result, cycle_result)
return component_result, cycle_result, feeder_slot_result
@timer_wrapper
def optimizer_celldivision(pcb_data, component_data, hinter=True):
# Crossover method: Two-point crossover
# Mutation method: Swap
# Parent selection method: Roulette wheel
# Termination condition: 20 successive non-improvement iterations
population_size = 40 # 种群规模
crossover_rate, mutation_rate = .6, .02
golden_section = 0.618
# 获取元件元胞
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]
index = np.where(component_data['part'].values == part)
component_cell.loc[index[0], 'points'] += 1
component_cell = component_cell[~component_cell['points'].isin([0])]
# component_cell.sort_values(by = "points" , inplace = True, ascending = False)
best_population, best_component_cell = [], []
min_pop_val = float('inf') # 最优种群价值
Div, Imp = 0, 0
while True:
# randomly generate permutations
generation_ = np.array(component_cell.index)
pop_generation = []
for _ in range(population_size):
np.random.shuffle(generation_)
pop_generation.append(generation_.tolist())
pop_val = []
for pop in range(population_size):
component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data,
component_cell,
pop_generation[pop])
pop_val.append(
component_assign_evaluate(component_data, component_result, cycle_result, feeder_slot_result))
# 初始化随机生成种群
Upit = int(1.5 * np.sqrt(len(component_cell)))
while Div < Upit:
if hinter:
print('----- current div : ' + str(Div) + ' , total div : ' + str(Upit) + ' -----')
# 选择
new_pop_generation, new_pop_val = [], []
top_k_index = get_top_k_value(pop_val, int(population_size * 0.3))
for index in top_k_index:
new_pop_generation.append(pop_generation[index])
new_pop_val.append(pop_val[index])
index = [i for i in range(population_size)]
select_index = random.choices(index, weights=pop_val, k=population_size - int(population_size * 0.3))
for index in select_index:
new_pop_generation.append(pop_generation[index])
new_pop_val.append(pop_val[index])
pop_generation, pop_val = new_pop_generation, new_pop_val
# 交叉
for pop in range(population_size):
if pop % 2 == 0 and np.random.random() < crossover_rate:
index1, index2 = roulette_wheel_selection(pop_val), -1
while True:
index2 = roulette_wheel_selection(pop_val)
if index1 != index2:
break
# 两点交叉算子
pop_generation[index1], pop_generation[index2] = partially_mapped_crossover(pop_generation[index1],
pop_generation[index2])
if np.random.random() < mutation_rate:
index_ = roulette_wheel_selection(pop_val)
swap_mutation(pop_generation[index_])
# 将元件元胞分配到各个吸杆上,计算价值函数
for pop in range(population_size):
component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data,
component_cell,
pop_generation[pop])
pop_val[pop] = component_assign_evaluate(component_data, component_result, cycle_result,
feeder_slot_result)
assert(pop_val[pop] > 0)
if min(pop_val) < min_pop_val:
min_pop_val = min(pop_val)
best_population = copy.deepcopy(pop_generation[np.argmin(pop_val)])
best_component_cell = copy.deepcopy(component_cell)
Div, Imp = 0, 1
else:
Div += 1
if Imp == 1:
Div, Imp = 0, 0
# Section: cell division operation
if hinter:
print(' ------------- cell division operation ------------- ')
division_component_cell = pd.DataFrame()
for idx, rows in component_cell.iterrows():
if component_cell.loc[idx, 'points'] <= 1:
division_component_cell = pd.concat([division_component_cell, pd.DataFrame([rows])],
ignore_index=True)
else:
division_component_cell = pd.concat([division_component_cell, pd.DataFrame([rows] * 2)],
ignore_index=True)
rows_counter = len(division_component_cell)
division_points = int(max(np.ceil(division_component_cell.loc[rows_counter - 2,
'points'] * golden_section), 1))
# 避免出现空元胞的情形
if division_points == 0 or division_points == division_component_cell.loc[
rows_counter - 2, 'points']:
division_component_cell.loc[rows_counter - 2, 'points'] = 1
else:
division_component_cell.loc[rows_counter - 2, 'points'] = division_points
division_component_cell.loc[rows_counter - 1, 'points'] -= division_component_cell.loc[
rows_counter - 2, 'points']
if division_component_cell.loc[rows_counter - 2, 'points'] == 0 or division_component_cell.loc[
rows_counter - 1, 'points'] == 0:
raise ValueError
component_cell = division_component_cell
# 完成分裂后重新生成染色体组
generation_ = np.array(range(len(component_cell)))
pop_generation = []
for _ in range(population_size):
np.random.shuffle(generation_)
pop_generation.append(generation_.tolist())
else:
break
assert(len(best_component_cell) == len(best_population))
return convert_cell_2_result(pcb_data, component_data, best_component_cell, best_population)

View File

@ -0,0 +1,954 @@
import copy
import time
import math
import random
import argparse
import os
import warnings
import copy
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from functools import wraps
from collections import defaultdict
from tqdm import tqdm
# 整线参数
max_machine_index = 3
# 时间参数
T_pp, T_tr, T_nc = 2, 5, 25
# 机器参数
max_head_index, max_slot_index = 6, 120
interval_ratio = 2
slot_interval = 15
head_interval = slot_interval * interval_ratio
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] # 止档块位置
# 算法权重参数
e_nz_change, e_gang_pick = 4, 0.6
# 电机参数
head_rotary_velocity = 8e-5 # 贴装头R轴旋转时间
x_max_velocity, y_max_velocity = 1.4, 1.2
x_max_acceleration, y_max_acceleration = x_max_velocity / 0.079, y_max_velocity / 0.079
# 不同种类供料器宽度
feeder_width = {'SM8': (7.25, 7.25), 'SM12': (7.00, 20.00), 'SM16': (7.00, 22.00),
'SM24': (7.00, 29.00), 'SM32': (7.00, 44.00)}
# 可用吸嘴数量限制
nozzle_limit = {'CN065': 6, 'CN040': 6, 'CN220': 6, 'CN400': 6, 'CN140': 6}
def axis_moving_time(distance, axis=0):
distance = abs(distance) * 1e-3
Lamax = x_max_velocity ** 2 / x_max_acceleration if axis == 0 else y_max_velocity ** 2 / y_max_acceleration
Tmax = x_max_velocity / x_max_acceleration if axis == 0 else y_max_velocity / y_max_acceleration
if axis == 0:
return 2 * math.sqrt(distance / x_max_acceleration) if distance < Lamax else 2 * Tmax + (
distance - Lamax) / x_max_velocity
else:
return 2 * math.sqrt(distance / y_max_acceleration) if distance < Lamax else 2 * Tmax + (
distance - Lamax) / y_max_velocity
def head_rotary_time(angle):
while -180 > angle > 180:
if angle > 180:
angle -= 360
else:
angle += 360
return abs(angle) * head_rotary_velocity
def find_commonpart(head_group, feeder_group):
feeder_group_len = len(feeder_group)
max_length, max_common_part = -1, []
for offset in range(-max_head_index + 1, feeder_group_len - 1):
# offset: head_group相对于feeder_group的偏移量
length, common_part = 0, []
for hd_index in range(max_head_index):
fd_index = hd_index + offset
if fd_index < 0 or fd_index >= feeder_group_len:
common_part.append(-1)
continue
if head_group[hd_index] == feeder_group[fd_index] and head_group[hd_index] != -1:
length += 1
common_part.append(head_group[hd_index])
else:
common_part.append(-1)
if length > max_length:
max_length = length
max_common_part = common_part
return max_common_part
def timer_wrapper(func):
@wraps(func)
def measure_time(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
print("function {} running time : {} s".format(func.__name__, time.time() - start_time))
return result
return measure_time
def feeder_assignment(component_data, pcb_data, component_result, cycle_result):
# Section: 供料器分配结果
feeder_slot_result, feeder_group_result = [], []
feeder_limit = defaultdict(int)
for component in range(len(component_data)):
feeder_limit[component] = component_data.loc[component]['feeder-limit']
for component_cycle in component_result:
new_feeder_group = []
for component in component_cycle:
if component == -1 or feeder_limit[component] == 0 or new_feeder_group.count(component) >= feeder_limit[component]:
new_feeder_group.append(-1)
else:
new_feeder_group.append(component)
if len(new_feeder_group) == 0:
continue
while sum(i >= 0 for i in new_feeder_group) != 0:
max_common_part, index = [], -1
max_common_length = -1
for feeder_index in range(len(feeder_group_result)):
common_part = find_commonpart(new_feeder_group, feeder_group_result[feeder_index])
if sum(i > 0 for i in common_part) > max_common_length:
max_common_length = sum(i > 0 for i in common_part)
max_common_part, index = common_part, feeder_index
new_feeder_length = 0
for feeder in new_feeder_group:
if feeder != -1 and feeder_limit[feeder] > 0:
new_feeder_length += 1
if new_feeder_length > max_common_length:
# 新分配供料器
feeder_group_result.append([])
for feeder_index in range(len(new_feeder_group)):
feeder = new_feeder_group[feeder_index]
if feeder != -1 and feeder_limit[feeder] > 0:
feeder_group_result[-1].append(feeder)
new_feeder_group[feeder_index] = -1
feeder_limit[feeder] -= 1
else:
feeder_group_result[-1].append(-1)
else:
# 使用旧供料器
for feeder_index, feeder_part in enumerate(max_common_part):
if feeder_part != -1:
new_feeder_group[feeder_index] = -1
# 去除多余的元素
for feeder_group in feeder_group_result:
while len(feeder_group) > 0 and feeder_group[0] == -1:
feeder_group.pop(0)
while len(feeder_group) > 0 and feeder_group[-1] == -1:
feeder_group.pop(-1)
# 确定供料器组的安装位置
point_num = len(pcb_data)
component_pos = [[] for _ in range(len(component_data))]
for point_cnt in range(point_num):
part = pcb_data.loc[point_cnt, 'part']
index = np.where(component_data['part'].values == part)[0]
component_pos[index[0]].append(pcb_data.loc[point_cnt, 'x'] + stopper_pos[0])
# 元件使用的头
CT_Head = defaultdict(list)
for component_cycle in component_result:
for head, component in enumerate(component_cycle):
if component == -1:
continue
if component not in CT_Head:
CT_Head[component] = [head, head]
CT_Head[component][0] = min(CT_Head[component][0], head)
CT_Head[component][1] = max(CT_Head[component][1], head)
# 供料器组分配的优先顺序
feeder_assign_sequence = []
for i in range(len(feeder_group_result)):
for j in range(len(feeder_group_result)):
if j in feeder_assign_sequence:
continue
if len(feeder_assign_sequence) == i:
feeder_assign_sequence.append(j)
else:
seq = feeder_assign_sequence[-1]
if cycle_result[seq] * len([k for k in feeder_group_result[seq] if k >= 0]) < cycle_result[j] * len(
[k for k in feeder_group_result[seq] if k >= 0]):
feeder_assign_sequence.pop(-1)
feeder_assign_sequence.append(j)
# TODO: 暂未考虑机械限位
feeder_group_slot = [-1] * len(feeder_group_result)
feeder_lane_state = [0] * max_slot_index # 0表示空1表示已占有
for index in feeder_assign_sequence:
feeder_group = feeder_group_result[index]
best_slot = []
for cp_index, component in enumerate(feeder_group):
if component == -1:
continue
best_slot.append(round((sum(component_pos[component]) / len(component_pos[component]) - slotf1_pos[
0]) / slot_interval) + 1 - cp_index * interval_ratio)
best_slot = round(sum(best_slot) / len(best_slot))
search_dir, step = 0, 0 # dir: 1-向右, 0-向左
left_out_range, right_out_range = False, False
while True:
assign_slot = best_slot + step if search_dir else best_slot - step
# 出现越界,反向搜索
if assign_slot + (len(feeder_group) - 1) * interval_ratio >= max_slot_index / 2:
right_out_range = True
search_dir = 0
step += 1
elif assign_slot < 0:
left_out_range = True
search_dir = 1
step += 1
else:
if left_out_range or right_out_range:
step += 1 # 单向搜索
else:
search_dir = 1 - search_dir # 双向搜索
if search_dir == 0:
step += 1
assign_available = True
# === 分配对应槽位 ===
for slot in range(assign_slot, assign_slot + interval_ratio * len(feeder_group), interval_ratio):
feeder_index = int((slot - assign_slot) / interval_ratio)
pick_part = feeder_group[feeder_index]
if feeder_lane_state[slot] == 1 and pick_part != -1:
assign_available = False
break
if pick_part != -1 and (slot - CT_Head[pick_part][0] * interval_ratio <= 0 or
slot + (max_head_index - CT_Head[pick_part][1] - 1) * interval_ratio > max_slot_index // 2):
assign_available = False
break
if assign_available:
for idx, part in enumerate(feeder_group):
if part != -1:
feeder_lane_state[assign_slot + idx * interval_ratio] = 1
feeder_group_slot[index] = assign_slot
break
if feeder_group_slot[index] == -1:
raise Exception('feeder assign error!')
# 按照最大匹配原则,确定各元件周期拾取槽位
for component_cycle in component_result:
feeder_slot_result.append([-1] * max_head_index)
head_index = [head for head, component in enumerate(component_cycle) if component >= 0]
while head_index:
max_overlap_counter = 0
overlap_feeder_group_index, overlap_feeder_group_offset = -1, -1
for feeder_group_idx, feeder_group in enumerate(feeder_group_result):
# offset 头1 相对于 供料器组第一个元件的偏移量
for offset in range(-max_head_index + 1, max_head_index + len(feeder_group)):
overlap_counter = 0
for head in head_index:
if 0 <= head + offset < len(feeder_group) and component_cycle[head] == \
feeder_group[head + offset]:
overlap_counter += 1
if overlap_counter > max_overlap_counter:
max_overlap_counter = overlap_counter
overlap_feeder_group_index, overlap_feeder_group_offset = feeder_group_idx, offset
feeder_group = feeder_group_result[overlap_feeder_group_index]
head_index_cpy = copy.deepcopy(head_index)
for idx, head in enumerate(head_index_cpy):
if 0 <= head + overlap_feeder_group_offset < len(feeder_group) and component_cycle[head] == \
feeder_group[head + overlap_feeder_group_offset]:
feeder_slot_result[-1][head] = feeder_group_slot[overlap_feeder_group_index] + interval_ratio * (
head + overlap_feeder_group_offset)
head_index.remove(head)
return feeder_slot_result
def dynamic_programming_cycle_path(pcb_data, cycle_placement, assigned_feeder):
head_sequence = []
num_pos = sum([placement != -1 for placement in cycle_placement]) + 1
pos, head_set = [], []
feeder_set = set()
for head, feeder in enumerate(assigned_feeder):
if feeder == -1:
continue
head_set.append(head)
placement = cycle_placement[head]
if feeder != -1 and placement == -1:
print(assigned_feeder)
print(cycle_placement)
pos.append([pcb_data.loc[placement]['x'] - head * head_interval + stopper_pos[0],
pcb_data.loc[placement]['y'] + stopper_pos[1]])
feeder_set.add(feeder - head * interval_ratio)
pos.insert(0, [slotf1_pos[0] + ((min(list(feeder_set)) + max(list(feeder_set))) / 2 - 1) * slot_interval,
slotf1_pos[1]])
def get_distance(pos_1, pos_2):
return math.sqrt((pos_1[0] - pos_2[0]) ** 2 + (pos_1[1] - pos_2[1]) ** 2)
# 各节点之间的距离
dist = [[get_distance(pos_1, pos_2) for pos_2 in pos] for pos_1 in pos]
min_dist = [[np.inf for _ in range(num_pos)] for s in range(1 << num_pos)]
min_path = [[[] for _ in range(num_pos)] for s in range(1 << num_pos)]
# 状压dp搜索
for s in range(1, 1 << num_pos, 2):
# 考虑节点集合s必须包括节点0
if not (s & 1):
continue
for j in range(1, num_pos):
# 终点j需在当前考虑节点集合s内
if not (s & (1 << j)):
continue
if s == int((1 << j) | 1):
# 若考虑节点集合s仅含节点0和节点jdp边界赋予初值
# print('j:', j)
min_path[s][j] = [j]
min_dist[s][j] = dist[0][j]
# 枚举下一个节点i更新
for i in range(1, num_pos):
# 下一个节点i需在考虑节点集合s外
if s & (1 << i):
continue
if min_dist[s][j] + dist[j][i] < min_dist[s | (1 << i)][i]:
min_path[s | (1 << i)][i] = min_path[s][j] + [i]
min_dist[s | (1 << i)][i] = min_dist[s][j] + dist[j][i]
ans_dist = float('inf')
ans_path = []
# 求最终最短哈密顿回路
for i in range(1, num_pos):
if min_dist[(1 << num_pos) - 1][i] + dist[i][0] < ans_dist:
# 更新,回路化
ans_path = min_path[s][i]
ans_dist = min_dist[(1 << num_pos) - 1][i] + dist[i][0]
for parent in ans_path:
head_sequence.append(head_set[parent - 1])
start_head, end_head = head_sequence[0], head_sequence[-1]
if pcb_data.loc[cycle_placement[start_head]]['x'] - start_head * head_interval > \
pcb_data.loc[cycle_placement[end_head]]['x'] - end_head * head_interval:
head_sequence = list(reversed(head_sequence))
return head_sequence
@timer_wrapper
def greedy_placement_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result):
placement_result, head_sequence_result = [], []
mount_point_index = [[] for _ in range(len(component_data))]
mount_point_pos = [[] for _ in range(len(component_data))]
for i in range(len(pcb_data)):
part = pcb_data.loc[i]['part']
component_index = component_data[component_data['part'] == part].index.tolist()[0]
# 记录贴装点序号索引和对应的位置坐标
mount_point_index[component_index].append(i)
mount_point_pos[component_index].append([pcb_data.loc[i]['x'], pcb_data.loc[i]['y']])
search_dir = 1 # 0自左向右搜索 1自右向左搜索
for cycle_set in range(len(component_result)):
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
for cycle in range(floor_cycle, ceil_cycle):
# search_dir = 1 - search_dir
assigned_placement = [-1] * max_head_index
max_pos = [max(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in
range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0]
min_pos = [min(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in
range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0]
point2head_range = min(math.floor((max_pos - min_pos) / head_interval) + 1, max_head_index)
# 最近邻确定
way_point = None
head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index)
for head_counter, head in enumerate(head_range):
if component_result[cycle_set][head] == -1:
continue
component_index = component_result[cycle_set][head]
if way_point is None or head_counter % point2head_range == 0:
index = 0
if way_point is None:
if search_dir:
index = np.argmax(mount_point_pos[component_index], axis=0)[0]
else:
index = np.argmin(mount_point_pos[component_index], axis=0)[0]
else:
for next_head in head_range:
component_index = component_result[cycle_set][next_head]
if assigned_placement[next_head] == -1 and component_index != -1:
num_points = len(mount_point_pos[component_index])
index = np.argmin(
[abs(mount_point_pos[component_index][i][0] - way_point[0]) * .1 + abs(
mount_point_pos[component_index][i][1] - way_point[1]) for i in
range(num_points)])
head = next_head
break
# index = np.argmax(mount_point_pos[component_index], axis=0)[0]
assigned_placement[head] = mount_point_index[component_index][index]
# 记录路标点
way_point = mount_point_pos[component_index][index]
way_point[0] += (max_head_index - head - 1) * head_interval if search_dir else -head * head_interval
mount_point_index[component_index].pop(index)
mount_point_pos[component_index].pop(index)
else:
head_index, point_index = -1, -1
min_cheby_distance, min_euler_distance = float('inf'), float('inf')
for next_head in range(max_head_index):
if assigned_placement[next_head] != -1 or component_result[cycle_set][next_head] == -1:
continue
next_comp_index = component_result[cycle_set][next_head]
for counter in range(len(mount_point_pos[next_comp_index])):
if search_dir:
delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0]
+ (max_head_index - next_head - 1) * head_interval)
else:
delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0]
- next_head * head_interval)
delta_y = abs(mount_point_pos[next_comp_index][counter][1] - way_point[1])
euler_distance = pow(axis_moving_time(delta_x, 0), 2) + pow(axis_moving_time(delta_y, 1), 2)
cheby_distance = max(axis_moving_time(delta_x, 0),
axis_moving_time(delta_y, 1)) + 5e-2 * euler_distance
if cheby_distance < min_cheby_distance or (abs(cheby_distance - min_cheby_distance) < 1e-9
and euler_distance < min_euler_distance):
# if euler_distance < min_euler_distance:
min_cheby_distance, min_euler_distance = cheby_distance, euler_distance
head_index, point_index = next_head, counter
component_index = component_result[cycle_set][head_index]
assert (0 <= head_index < max_head_index)
assigned_placement[head_index] = mount_point_index[component_index][point_index]
way_point = mount_point_pos[component_index][point_index]
way_point[0] += (max_head_index - head_index - 1) * head_interval if search_dir \
else -head_index * head_interval
mount_point_index[component_index].pop(point_index)
mount_point_pos[component_index].pop(point_index)
placement_result.append(assigned_placement) # 各个头上贴装的元件类型
head_sequence_result.append(
dynamic_programming_cycle_path(pcb_data, assigned_placement, feeder_slot_result[cycle_set]))
return placement_result, head_sequence_result
@timer_wrapper
def beam_search_for_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result):
beam_width = 4 # 集束宽度
base_points = [float('inf'), float('inf')]
mount_point_index = [[] for _ in range(len(component_data))]
mount_point_pos = [[] for _ in range(len(component_data))]
for i in range(len(pcb_data)):
part = pcb_data.loc[i]['part']
component_index = component_data[component_data['part'] == part].index.tolist()[0]
# 记录贴装点序号索引和对应的位置坐标
mount_point_index[component_index].append(i)
mount_point_pos[component_index].append([pcb_data.loc[i]['x'], pcb_data.loc[i]['y']])
# 记录最左下角坐标
if mount_point_pos[component_index][-1][0] < base_points[0]:
base_points[0] = mount_point_pos[component_index][-1][0]
if mount_point_pos[component_index][-1][1] < base_points[1]:
base_points[1] = mount_point_pos[component_index][-1][1]
beam_placement_sequence, beam_head_sequence = [], []
beam_mount_point_index, beam_mount_point_pos = [], []
for beam_counter in range(beam_width):
beam_mount_point_index.append(copy.deepcopy(mount_point_index))
beam_mount_point_pos.append(copy.deepcopy(mount_point_pos))
beam_placement_sequence.append([])
beam_head_sequence.append([])
beam_distance = [0 for _ in range(beam_width)] # 记录当前集束搜索点的点数
def argpartition(list, kth):
if kth < len(list):
return np.argpartition(list, kth)
else:
index, indexes = 0, []
while len(indexes) < kth:
indexes.append(index)
index += 1
if index >= len(list):
index = 0
return np.array(indexes)
with tqdm(total=100) as pbar:
search_dir = 0
pbar.set_description('route schedule')
for cycle_set in range(len(component_result)):
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
for cycle in range(floor_cycle, ceil_cycle):
search_dir = 1 - search_dir
beam_way_point = None
for beam_counter in range(beam_width):
beam_placement_sequence[beam_counter].append([-1 for _ in range(max_head_index)])
head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index)
for head in head_range:
component_index = component_result[cycle_set][head]
if component_index == -1:
continue
if beam_way_point is None:
# 首个贴装点的选取距离基准点最近的beam_width个点
beam_way_point = [[0, 0]] * beam_width
for beam_counter in range(beam_width):
if search_dir:
index = np.argmax(beam_mount_point_pos[beam_counter][component_index], axis=0)[0]
else:
index = np.argmin(beam_mount_point_pos[beam_counter][component_index], axis=0)[0]
beam_placement_sequence[beam_counter][-1][head] = beam_mount_point_index[beam_counter][component_index][index]
beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][index]
beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \
search_dir else -head * head_interval
beam_mount_point_index[beam_counter][component_index].pop(index)
beam_mount_point_pos[beam_counter][component_index].pop(index)
else:
# 后续贴装点
search_beam_distance = []
search_beam_index = [0] * (beam_width ** 2)
for beam_counter in range(beam_width ** 2):
search_beam_distance.append(beam_distance[beam_counter // beam_width])
for beam_counter in range(beam_width):
# 对于集束beam_counter + 1最近的beam_width个点
num_points = len(beam_mount_point_pos[beam_counter][component_index])
dist = []
for i in range(num_points):
if search_dir:
delta_x = axis_moving_time(
beam_mount_point_pos[beam_counter][component_index][i][0] -
beam_way_point[beam_counter][0] + (max_head_index - head - 1) * head_interval,
0)
else:
delta_x = axis_moving_time(
beam_mount_point_pos[beam_counter][component_index][i][0] -
beam_way_point[beam_counter][0] - head * head_interval, 0)
delta_y = axis_moving_time(beam_mount_point_pos[beam_counter][component_index][i][1] -
beam_way_point[beam_counter][1], 1)
dist.append(max(delta_x, delta_y))
indexes = argpartition(dist, kth=beam_width)[:beam_width]
# 记录中间信息
for i, index in enumerate(indexes):
search_beam_distance[i + beam_counter * beam_width] += dist[index]
search_beam_index[i + beam_counter * beam_width] = index
indexes = np.argsort(search_beam_distance)
beam_mount_point_pos_cpy = copy.deepcopy(beam_mount_point_pos)
beam_mount_point_index_cpy = copy.deepcopy(beam_mount_point_index)
beam_placement_sequence_cpy = copy.deepcopy(beam_placement_sequence)
beam_head_sequence_cpy = copy.deepcopy(beam_head_sequence)
beam_counter = 0
assigned_placement = []
for i, index in enumerate(indexes):
# 拷贝原始集束数据
beam_mount_point_pos[beam_counter] = copy.deepcopy(beam_mount_point_pos_cpy[index // beam_width])
beam_mount_point_index[beam_counter] = copy.deepcopy(beam_mount_point_index_cpy[index // beam_width])
beam_placement_sequence[beam_counter] = copy.deepcopy(beam_placement_sequence_cpy[index // beam_width])
beam_head_sequence[beam_counter] = copy.deepcopy(beam_head_sequence_cpy[index // beam_width])
# 更新各集束最新扫描的的贴装点
component_index = component_result[cycle_set][head]
beam_placement_sequence[beam_counter][-1][head] = \
beam_mount_point_index[beam_counter][component_index][search_beam_index[index]]
if beam_placement_sequence[beam_counter][
-1] in assigned_placement and beam_width - beam_counter < len(indexes) - i:
continue
assigned_placement.append(beam_placement_sequence[beam_counter][-1])
# 更新参考基准点
beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][search_beam_index[index]]
beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \
search_dir else -head * head_interval
# 更新各集束贴装路径长度,移除各集束已分配的贴装点
beam_distance[beam_counter] = search_beam_distance[index]
beam_mount_point_pos[beam_counter][component_index].pop(search_beam_index[index])
beam_mount_point_index[beam_counter][component_index].pop(search_beam_index[index])
beam_counter += 1
if beam_counter >= beam_width:
break
assert(beam_counter >= beam_width)
# 更新头贴装顺序
for beam_counter in range(beam_width):
beam_head_sequence[beam_counter].append(
dynamic_programming_cycle_path(pcb_data, beam_placement_sequence[beam_counter][-1],
feeder_slot_result[cycle_set]))
pbar.update(1 / sum(cycle_result) * 100)
index = np.argmin(beam_distance)
return beam_placement_sequence[index], beam_head_sequence[index]
def optimal_nozzle_assignment(component_data, pcb_data):
# === Nozzle Assignment ===
# number of points for nozzle & number of heads for nozzle
nozzle_points, nozzle_assigned_counter = defaultdict(int), defaultdict(int)
if len(pcb_data) == 0:
return nozzle_assigned_counter
for _, step in pcb_data.iterrows():
part = step['part']
idx = component_data[component_data['part'] == part].index.tolist()[0]
nozzle = component_data.loc[idx]['nz']
nozzle_assigned_counter[nozzle] = 0
nozzle_points[nozzle] += 1
assert len(nozzle_points.keys()) <= max_head_index
total_points, available_head = len(pcb_data), max_head_index
# S1: set of nozzle types which are sufficient to assign one nozzle to the heads
# S2: temporary nozzle set
# S3: set of nozzle types which already have the maximum reasonable nozzle amounts.
S1, S2, S3 = [], [], []
for nozzle in nozzle_points.keys(): # Phase 1
if nozzle_points[nozzle] * max_head_index < total_points:
nozzle_assigned_counter[nozzle] = 1
available_head -= 1
total_points -= nozzle_points[nozzle]
S1.append(nozzle)
else:
S2.append(nozzle)
available_head_ = available_head # Phase 2
for nozzle in S2:
nozzle_assigned_counter[nozzle] = math.floor(available_head * nozzle_points[nozzle] / total_points)
available_head_ = available_head_ - nozzle_assigned_counter[nozzle]
S2.sort(key=lambda x: nozzle_points[x] / (nozzle_assigned_counter[x] + 1e-10), reverse=True)
while available_head_ > 0:
nozzle = S2[0]
nozzle_assigned_counter[nozzle] += 1
S2.remove(nozzle)
S3.append(nozzle)
available_head_ -= 1
phase_iteration = len(S2) - 1
while phase_iteration > 0: # Phase 3
nozzle_i_val, nozzle_j_val = 0, 0
nozzle_i, nozzle_j = None, None
for nozzle in S2:
if nozzle_i is None or nozzle_points[nozzle] / nozzle_assigned_counter[nozzle] > nozzle_i_val:
nozzle_i_val = nozzle_points[nozzle] / nozzle_assigned_counter[nozzle]
nozzle_i = nozzle
if nozzle_assigned_counter[nozzle] > 1:
if nozzle_j is None or nozzle_points[nozzle] / (nozzle_assigned_counter[nozzle] - 1) < nozzle_j_val:
nozzle_j_val = nozzle_points[nozzle] / (nozzle_assigned_counter[nozzle] - 1)
nozzle_j = nozzle
if nozzle_i and nozzle_j and nozzle_points[nozzle_j] / (nozzle_assigned_counter[nozzle_j] - 1) < \
nozzle_points[nozzle_i] / nozzle_assigned_counter[nozzle_i]:
nozzle_assigned_counter[nozzle_j] -= 1
nozzle_assigned_counter[nozzle_i] += 1
S2.remove(nozzle_i)
S3.append(nozzle_i)
else:
break
return nozzle_assigned_counter
# === 遗传算法公用函数 ===
def sigma_scaling(pop_val, c: float):
# function: f' = max(f - (avg(f) - c · sigma(f), 0)
avg_val = sum(pop_val) / len(pop_val)
sigma_val = math.sqrt(sum(abs(v - avg_val) for v in pop_val) / len(pop_val))
for idx, val in enumerate(pop_val):
pop_val[idx] = max(val - (avg_val - c * sigma_val), 0)
return pop_val
def directed_edge_recombination_crossover(c, individual1, individual2):
assert len(individual1) == len(individual2)
left_edge_list, right_edge_list = defaultdict(list), defaultdict(list)
for index in range(len(individual1) - 1):
elem1, elem2 = individual1[index], individual1[index + 1]
right_edge_list[elem1].append(elem2)
left_edge_list[elem2].append(elem1)
for index in range(len(individual2) - 1):
elem1, elem2 = individual2[index], individual2[index + 1]
right_edge_list[elem1].append(elem2)
left_edge_list[elem2].append(elem1)
offspring = []
while len(offspring) != len(individual1):
while True:
center_element = np.random.choice(individual1)
if center_element not in offspring: # 避免重复选取
break
direction, candidate = 1, [center_element]
parent = center_element
for edge_list in left_edge_list.values():
while parent in edge_list:
edge_list.remove(parent)
for edge_list in right_edge_list.values():
while parent in edge_list:
edge_list.remove(parent)
while True:
max_len, max_len_neighbor = -1, 0
if direction == 1:
if len(right_edge_list[parent]) == 0:
direction, parent = -1, center_element
continue
for neighbor in right_edge_list[parent]:
if max_len < len(right_edge_list[neighbor]):
max_len_neighbor = neighbor
max_len = len(right_edge_list[neighbor])
candidate.append(max_len_neighbor)
parent = max_len_neighbor
elif direction == -1:
if len(left_edge_list[parent]) == 0:
direction, parent = 0, center_element
continue
for neighbor in left_edge_list[parent]:
if max_len < len(left_edge_list[neighbor]):
max_len_neighbor = neighbor
max_len = len(left_edge_list[neighbor])
candidate.insert(0, max_len_neighbor)
parent = max_len_neighbor
else:
break
# 移除重复元素
for edge_list in left_edge_list.values():
while max_len_neighbor in edge_list:
edge_list.remove(max_len_neighbor)
for edge_list in right_edge_list.values():
while max_len_neighbor in edge_list:
edge_list.remove(max_len_neighbor)
offspring += candidate
return offspring
def partially_mapped_crossover(parent1, parent2):
range_ = np.random.randint(0, len(parent1), 2) # 前闭后开
range_ = sorted(range_)
parent1_cpy, parent2_cpy = [-1 for _ in range(len(parent1))], [-1 for _ in range(len(parent2))]
parent1_cpy[range_[0]: range_[1] + 1] = copy.deepcopy(parent2[range_[0]: range_[1] + 1])
parent2_cpy[range_[0]: range_[1] + 1] = copy.deepcopy(parent1[range_[0]: range_[1] + 1])
for index in range(len(parent1)):
if range_[0] <= index <= range_[1]:
continue
cur_ptr, cur_elem = 0, parent1[index]
while True:
parent1_cpy[index] = cur_elem
if parent1_cpy.count(cur_elem) == 1:
break
parent1_cpy[index] = -1
if cur_ptr == 0:
cur_ptr, cur_elem = 1, parent2[index]
else:
index_ = parent1_cpy.index(cur_elem)
cur_elem = parent2[index_]
for index in range(len(parent2)):
if range_[0] <= index <= range_[1]:
continue
cur_ptr, cur_elem = 0, parent2[index]
while True:
parent2_cpy[index] = cur_elem
if parent2_cpy.count(cur_elem) == 1:
break
parent2_cpy[index] = -1
if cur_ptr == 0:
cur_ptr, cur_elem = 1, parent1[index]
else:
index_ = parent2_cpy.index(cur_elem)
cur_elem = parent1[index_]
return parent1_cpy, parent2_cpy
def cycle_crossover(parent1, parent2):
offspring1, offspring2 = [-1 for _ in range(len(parent1))], [-1 for _ in range(len(parent2))]
idx = 0
while True:
if offspring1[idx] != -1:
break
offspring1[idx] = parent1[idx]
idx = parent1.index(parent2[idx])
for idx, gene in enumerate(offspring1):
if gene == -1:
offspring1[idx] = parent2[idx]
idx = 0
while True:
if offspring2[idx] != -1:
break
offspring2[idx] = parent2[idx]
idx = parent2.index(parent1[idx])
for idx, gene in enumerate(offspring2):
if gene == -1:
offspring2[idx] = parent1[idx]
return offspring1, offspring2
def swap_mutation(parent):
range_ = np.random.randint(0, len(parent), 2)
parent[range_[0]], parent[range_[1]] = parent[range_[1]], parent[range_[0]]
return parent
def constraint_swap_mutation(component_points, individual):
offspring = individual.copy()
idx, component_index = 0, random.randint(0, len(component_points) - 1)
for points in component_points.values():
if component_index == 0:
while True:
index1, index2 = random.sample(range(points + max_machine_index - 2), 2)
if offspring[idx + index1] != offspring[idx + index2]:
break
clip = offspring[idx: idx + points + max_machine_index - 1].copy()
avl_machine = 0
for idx_, gene in enumerate(clip):
if gene == 0 and (idx_ == 0 or clip[idx_ - 1] != 0):
avl_machine += 1
clip[index1], clip[index2] = clip[index2], clip[index1]
for idx_, gene in enumerate(clip):
if gene == 0 and (idx_ == 0 or clip[idx_ - 1] != 0):
avl_machine -= 1
if avl_machine != 0:
return offspring
offspring[idx + index1], offspring[idx + index2] = offspring[idx + index2], offspring[idx + index1]
break
component_index -= 1
idx += (points + max_machine_index - 1)
return offspring
def random_selective(data, possibility): # 依概率选择随机数
assert len(data) == len(possibility) and len(data) > 0
sum_val = sum(possibility)
possibility = [p / sum_val for p in possibility]
random_val = random.random()
for idx, val in enumerate(possibility):
random_val -= val
if random_val <= 0:
break
return data[idx]
def insert_mutation(parent):
pos, val = np.random.randint(0, len(parent), 1), parent[-1]
parent[pos: len(parent) - 1] = parent[pos + 1:]
parent[pos] = val
return parent
def roulette_wheel_selection(pop_eval):
# Roulette wheel
random_val = np.random.random() * sum(pop_eval)
for idx, val in enumerate(pop_eval):
random_val -= val
if random_val <= 0:
return idx
return len(pop_eval) - 1
def get_top_k_value(pop_val, k: int, reverse=True):
res = []
pop_val_cpy = copy.deepcopy(pop_val)
pop_val_cpy.sort(reverse=reverse)
for i in range(min(len(pop_val_cpy), k)):
for j in range(len(pop_val)):
if abs(pop_val_cpy[i] - pop_val[j]) < 1e-9 and j not in res:
res.append(j)
break
return res

View File

@ -0,0 +1,720 @@
from base_optimizer.optimizer_common import *
@timer_wrapper
def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figure=False):
feeder_points, feeder_division_points = defaultdict(int), defaultdict(int) # 供料器贴装点数
mount_center_pos = defaultdict(int)
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 # 供料器基座结余贴装点数量
for data in pcb_data.iterrows():
pos, part = data[1]['x'] + stopper_pos[0], data[1]['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
feeder_points[part_index] += 1
mount_center_pos[part_index] += ((pos - mount_center_pos[part_index]) / feeder_points[part_index])
part_nozzle[part_index] = component_data.loc[part_index]['nz']
for part_index, points in feeder_points.items():
feeder_division_points[part_index] = max(points // feeder_limit[part_index], 1)
nozzle_component, nozzle_component_points = defaultdict(list), defaultdict(list)
for part, nozzle in part_nozzle.items():
for _ in range(feeder_limit[part]):
nozzle_component[nozzle].append(part)
nozzle_component_points[nozzle].append(feeder_points[part])
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]
# 供料器基座分配位置和对应贴装点数
feeder_base[slot], feeder_base_points[slot] = part_index, feeder_division_points[part_index]
feeder_type = component_data.loc[part_index]['fdr']
extra_width = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval
while extra_width > 0:
slot += 1
feeder_base[slot] = -1
extra_width -= slot_interval
feeder_limit[part_index] -= 1
feeder_arrange[part_index] += 1
if feeder_limit[part_index] < 0:
info = 'the number of arranged feeder for [' + part + '] exceeds the quantity limit'
raise ValueError(info)
for nozzle, components in nozzle_component.items():
if part_index in components:
index_ = components.index(part_index)
nozzle_component[nozzle].pop(index_)
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 = ['CN220', 'CN065','CN065','CN065','CN065','CN220']
# 先排序
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)
# 后确定吸嘴分配模式
head_index = [3, 2, 4, 1, 5, 0]
nozzle_pattern = [0] * max_head_index
for nozzle, _ in nozzle_pattern_list:
counter = nozzle_assigned_counter[nozzle]
while counter:
nozzle_pattern[head_index[0]] = nozzle
counter -= 1
head_index.pop(0)
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(
nozzle_component_points)
# 记录扫描到的已安装的供料器元件类型
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]
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]]
continue
assign_part_stack, assign_part_stack_points = [], []
for idx in head_assign_indexes:
if feeder_assign[idx] != -2:
continue
if len(nozzle_pattern) == 0: # 吸嘴匹配模式为空,优先分配元件,根据分配元件倒推吸嘴匹配模式
nozzle_assign = ''
max_points, max_nozzle_points = 0, 0
for nozzle in nozzle_assigned_counter_cpy.keys():
if len(tmp_nozzle_component[nozzle]) == 0:
continue
part = max(tmp_nozzle_component[nozzle],
key=lambda x: tmp_feeder_points[x] / tmp_feeder_limit[x] if
tmp_feeder_points[x] != 0 else 0)
index_ = tmp_nozzle_component[nozzle].index(part)
if max_points < tmp_nozzle_component_points[nozzle][index_]:
max_points, nozzle_assign = tmp_nozzle_component_points[nozzle][index_], nozzle
else:
# 吸嘴匹配模式非空,按对应吸嘴类型进行元件分配
nozzle_assign = nozzle_pattern[idx]
if len(tmp_nozzle_component[nozzle_assign]) == 0:
# 当前头对应吸嘴类型无可用元件,将计划分配的元件压入堆栈
part = max(tmp_feeder_points.keys(),
key=lambda x: tmp_feeder_points[x] / tmp_feeder_limit[x] if tmp_feeder_limit[
x] != 0 else 0)
for nozzle, component_list in tmp_nozzle_component.items():
if part in component_list:
nozzle_assign = nozzle
assign_part_stack.append(part)
assign_part_stack_points.append(feeder_division_points[part])
break
else:
# 当前头对应吸嘴类型有可用元件,直接分配对应类型的元件
index_ = tmp_nozzle_component[nozzle_assign].index(max(tmp_nozzle_component[nozzle_assign],
key=lambda x: tmp_feeder_points[x] /
tmp_feeder_limit[x] if
tmp_feeder_limit[x] != 0 else 0))
part = tmp_nozzle_component[nozzle_assign][index_]
feeder_type = component_data.loc[part]['fdr']
extra_width, extra_slot = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval, 1
slot_overlap = False
while extra_width > 0:
slot_ = slot + idx * interval_ratio + extra_slot
if feeder_base[slot_] != -2 or slot_ > max_slot_index // 2:
slot_overlap = True
break
extra_width -= slot_interval
extra_slot += 1
# 可用供料器数目充足且不存在和已有供料器的占位冲突
if tmp_feeder_limit[part] > 0 and not slot_overlap:
feeder_assign[idx], feeder_assign_points[idx] = part, feeder_division_points[part]
extra_width, extra_head = feeder_width[feeder_type][0] + feeder_width[feeder_type][
1] - head_interval, 1
while extra_width > 0 and idx + extra_head < max_head_index:
feeder_assign[idx + extra_head] = -1
extra_head += 1
extra_width -= head_interval
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
if part in tmp_nozzle_component[nozzle_assign]:
part_index = tmp_nozzle_component[nozzle_assign].index(part)
tmp_nozzle_component[nozzle_assign].pop(part_index)
tmp_nozzle_component_points[nozzle_assign].pop(part_index)
tmp_feeder_limit[part] -= 1
tmp_feeder_points[part] -= feeder_division_points[part]
# 元件堆栈出栈,首先分配吸嘴类型一致的头
if nozzle_pattern:
for head, feeder in enumerate(feeder_assign):
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
slot_overlap = False
while extra_width > 0:
slot_ = slot + head * interval_ratio + extra_slot
if feeder_base[slot_] != -2 or slot_ > max_slot_index // 2:
slot_overlap = True
break
extra_width -= slot_interval
extra_slot += 1
if component_data.loc[part]['nz'] == nozzle_pattern[head] and not slot_overlap:
feeder_assign[head], feeder_assign_points[head] = assign_part_stack[idx], \
assign_part_stack_points[idx]
assign_part_stack.pop(idx)
assign_part_stack_points.pop(idx)
break
# 元件堆栈,然后分配元件堆栈中未分配的其它元件
for head in head_assign_indexes:
if feeder_assign[head] != -2 or len(assign_part_stack) == 0:
continue
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
slot_overlap = False
while extra_width > 0:
slot_ = slot + head * interval_ratio + extra_slot
if feeder_base[slot_] != -2 or slot_ > max_slot_index // 2:
slot_overlap = True
break
extra_width -= slot_interval
extra_slot += 1
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
while extra_width > 0 and head + extra_head < max_head_index:
feeder_assign[head + extra_head] = -1
extra_head += 1
extra_width -= head_interval
else:
# 返还由于机械限位无法分配的,压入元件堆栈中的元素
nozzle = component_data.loc[part]['nz']
tmp_nozzle_component[nozzle].insert(0, part)
tmp_nozzle_component_points[nozzle].insert(0, points)
assign_part_stack.pop(0)
assign_part_stack_points.pop(0)
# 仍然存在由于机械限位,无法进行分配的在堆栈中的元件
while assign_part_stack:
part, points = assign_part_stack[0], assign_part_stack_points[0]
nozzle = component_data.loc[part]['nz']
tmp_nozzle_component[nozzle].insert(0, part)
tmp_nozzle_component_points[nozzle].insert(0, points)
assign_part_stack.pop(0)
assign_part_stack_points.pop(0)
nozzle_change_counter, average_slot = 0, []
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)
if nozzle_pattern and component_data.loc[feeder_]['nz'] != nozzle_pattern[head]:
nozzle_change_counter += 1
if len(average_slot) == 0:
continue
average_slot = sum(average_slot) / len(average_slot)
assign_value = 0
feeder_assign_points_cpy = feeder_assign_points.copy()
while True:
points_filter = list(filter(lambda x: x > 0, feeder_assign_points_cpy))
if not points_filter:
break
assign_value += e_gang_pick * min(points_filter) * (len(points_filter) - 1)
for head, _ in enumerate(feeder_assign_points_cpy):
if feeder_assign_points_cpy[head] == 0:
continue
feeder_assign_points_cpy[head] -= min(points_filter)
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:
best_assign_value = assign_value
best_assign = feeder_assign.copy()
best_assign_points = feeder_assign_points.copy()
best_assign_slot = slot
best_nozzle_component, best_nozzle_component_points = tmp_nozzle_component, tmp_nozzle_component_points
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
# 新安装的供料器
if feeder_base[best_assign_slot + idx * interval_ratio] != part:
# 除去分配给最大化同时拾取周期的项,保留结余项
feeder_base_points[best_assign_slot + idx * interval_ratio] += (
feeder_division_points[part] - min(filter(lambda x: x > 0, best_assign_points)))
feeder_points[part] -= feeder_division_points[part]
feeder_limit[part] -= 1
feeder_arrange[part] += 1
if feeder_limit[part] == 0:
feeder_division_points[part] = 0
for nozzle, components in nozzle_component.items():
if part in components:
index_ = components.index(part)
nozzle_component[nozzle].pop(index_)
nozzle_component_points[nozzle].pop(index_)
break
feeder_division_points[part] = 0
else:
# 已有的供料器
feeder_base_points[best_assign_slot + idx * interval_ratio] -= min(
filter(lambda x: x > 0, best_assign_points))
# 更新供料器基座信息
feeder_base[best_assign_slot + idx * interval_ratio] = part
feeder_type, extra_slot = component_data.loc[part]['fdr'], 0
extra_width = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval
while extra_width > 0:
extra_slot += 1
if feeder_base[best_assign_slot + idx * interval_ratio + extra_slot] == -2:
feeder_base[best_assign_slot + idx * interval_ratio + extra_slot] = -1 # 标记槽位已占用
extra_width -= slot_interval
# 更新吸嘴信息
nozzle_pattern[idx] = component_data.loc[part]['nz']
# 更新头分配的先后顺序
head_assign_indexes = np.array(best_assign_points).argsort().tolist()
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():
slot, part = feeder['slot'], feeder['part']
part_index = component_data[component_data['part'] == part].index.tolist()[0]
# 供料器基座分配位置和对应贴装点数
feeder_base[slot], feeder_base_points[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
for slot, feeder in enumerate(feeder_base):
if feeder < 0:
continue
part = component_data.loc[feeder]['part']
feeder_data.loc[len(feeder_data.index)] = [slot, part, 0]
if figure:
# 绘制供料器位置布局
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',
size=8)
feeder_assign_range = []
for feeder in feeder_data.iterrows():
slot, part = feeder[1]['slot'], feeder[1]['part']
part_index = component_data[component_data['part'] == part].index.tolist()[0]
feeder_type = component_data.loc[part_index]['fdr']
width = feeder_width[feeder_type][0] + feeder_width[feeder_type][1]
start = slotf1_pos[0] + slot_interval * (slot - 1) - slot_interval / 2
end = slotf1_pos[0] + slot_interval * (slot - 1) - slot_interval / 2 + width
rec_x = [start, end, end, start]
rec_y = [slotf1_pos[1] - 40, slotf1_pos[1] - 40, slotf1_pos[1] + 10, slotf1_pos[1] + 10]
c = 'red' if feeder[1]['arg'] == 0 else 'black' # 黑色表示已分配,红色表示新分配
plt.text(slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1] + 12,
part + ': ' + str(feeder_points[part_index]), ha='center', size=7, rotation=90, color=c)
plt.fill(rec_x, rec_y, facecolor='yellow', alpha=0.4)
feeder_assign_range.append([start, end])
# 记录重叠区间
feeder_assign_range.sort(key=lambda x: x[0])
for i in range(1, len(feeder_assign_range)):
if feeder_assign_range[i][0] < feeder_assign_range[i - 1][1]:
start, end = feeder_assign_range[i][0], feeder_assign_range[i - 1][1]
rec_x = [start, end, end, start]
rec_y = [slotf1_pos[1] - 40, slotf1_pos[1] - 40, slotf1_pos[1] + 10, slotf1_pos[1] + 10]
plt.fill(rec_x, rec_y, facecolor='red')
plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
[slotf1_pos[1] + 10, slotf1_pos[1] + 10], color='black')
plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
[slotf1_pos[1] - 40, slotf1_pos[1] - 40], color='black')
for counter in range(max_slot_index // 2 + 1):
pos = slotf1_pos[0] + (counter - 0.5) * slot_interval
plt.plot([pos, pos], [slotf1_pos[1] + 10, slotf1_pos[1] - 40], color='black', linewidth=1)
plt.ylim(-10, 100)
plt.show()
return optimal_nozzle_pattern
@timer_wrapper
def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
feeder_assign_check = set()
for feeder in feeder_data.iterrows():
feeder_assign_check.add(feeder[1]['part'])
component_points = [0] * len(component_data)
for step in pcb_data.iterrows():
part = step[1]['part']
part_index = component_data[component_data['part'] == 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'
raise ValueError(info)
assert len(feeder_assign_check) == len(component_points) - component_points.count(0) # 所有供料器均已分配槽位
feeder_part = [-1] * max_slot_index
for feeder in feeder_data.iterrows():
part, slot = feeder[1]['part'], feeder[1]['slot']
part_index = component_data[component_data['part'] == part].index.tolist()
if len(part_index) != 1:
print('unregistered component: ', part, ' in slot', slot)
continue
part_index = part_index[0]
feeder_part[slot] = part_index
component_result, cycle_result, feeder_slot_result = [], [], [] # 贴装点索引和拾取槽位优化结果
nozzle_mode = [nozzle_pattern] # 吸嘴匹配模式
with tqdm(total=len(pcb_data)) as pbar:
pbar.set_description('feeder scan process')
pbar_prev = 0
value_increment_base = 0
while True:
# === 周期内循环 ===
assigned_part = [-1 for _ in range(max_head_index)] # 当前扫描到的头分配元件信息
assigned_cycle = [0 for _ in range(max_head_index)] # 当前扫描到的元件最大分配次数
assigned_slot = [-1 for _ in range(max_head_index)] # 当前扫描到的供料器分配信息
best_assigned_eval_func = -float('inf')
nozzle_insert_cycle = 0
for cycle_index, nozzle_cycle in enumerate(nozzle_mode):
scan_eval_func_list = [] # 若干次扫描得到的最优解
# nozzle_cycle 吸嘴模式下,已扫描到的最优结果
cur_scan_part = [-1 for _ in range(max_head_index)]
cur_scan_cycle = [0 for _ in range(max_head_index)]
cur_scan_slot = [-1 for _ in range(max_head_index)]
cur_nozzle_limit = copy.deepcopy(nozzle_limit)
while True:
best_scan_part, best_scan_cycle = [-1 for _ in range(max_head_index)], [-1 for _ in
range(max_head_index)]
best_scan_slot = [-1 for _ in range(max_head_index)]
best_scan_nozzle_limit = copy.deepcopy(cur_nozzle_limit)
scan_eval_func, search_break = -float('inf'), True
# 前供料器基座扫描
for slot in range(1, max_slot_index // 2 - (max_head_index - 1) * interval_ratio + 1):
scan_cycle, scan_part, scan_slot = cur_scan_cycle.copy(), cur_scan_part.copy(), cur_scan_slot.copy()
scan_nozzle_limit = copy.deepcopy(cur_nozzle_limit)
# 预扫描确定各类型元件拾取数目(前瞻)
preview_scan_part = defaultdict(int)
for head in range(max_head_index):
part = feeder_part[slot + head * interval_ratio]
# 贴装头和拾取槽位满足对应关系
if scan_part[head] == -1 and part != -1 and component_points[part] > 0 and scan_part.count(
part) < component_points[part]:
preview_scan_part[part] += 1
component_counter = 0
for head in range(max_head_index):
part = feeder_part[slot + head * interval_ratio]
# 1.匹配条件满足: 贴装头和拾取槽位满足对应关系
if scan_part[head] == -1 and part != -1 and component_points[part] > 0 and scan_part.count(
part) < component_points[part]:
# 2.匹配条件满足:不超过可用吸嘴数的限制
nozzle = component_data.loc[part]['nz']
if scan_nozzle_limit[nozzle] <= 0:
continue
# 3.增量条件满足: 引入新的元件类型不会使代价函数的值减少(前瞻)
if scan_cycle.count(0) == max_head_index:
gang_pick_change = component_points[part]
else:
prev_cycle = min(filter(lambda x: x > 0, scan_cycle))
# 同时拾取数的提升
gang_pick_change = min(prev_cycle, component_points[part] // preview_scan_part[part])
# 4.拾取移动距离条件满足: 邻近元件进行同时抓取,降低移动路径长度
# reference_slot = -1
# for head_, slot_ in enumerate(scan_slot):
# if slot_ != -1:
# reference_slot = slot_ - head_ * interval_ratio
# if reference_slot != -1 and abs(reference_slot - slot) > (max_head_index - 1) * interval_ratio:
# continue
# 5.同时拾取的增量 和 吸嘴更换次数比较
prev_nozzle_change = 0
if cycle_index + 1 < len(nozzle_mode):
prev_nozzle_change = 2 * (nozzle_cycle[head] != nozzle_mode[cycle_index + 1][head])
# 避免首个周期吸杆占用率低的问题
if nozzle_cycle[head] == '':
nozzle_change = 0
else:
nozzle_change = 2 * (nozzle != nozzle_cycle[head])
if cycle_index + 1 < len(nozzle_mode):
nozzle_change += 2 * (nozzle != nozzle_mode[cycle_index + 1][head])
nozzle_change -= prev_nozzle_change
val = e_gang_pick * gang_pick_change - e_nz_change * nozzle_change
if val < value_increment_base:
continue
component_counter += 1
scan_part[head] = part
scan_cycle[head] = component_points[part] // preview_scan_part[part]
scan_slot[head] = slot + head * interval_ratio
scan_nozzle_limit[nozzle] -= 1
nozzle_counter = 0 # 吸嘴更换次数
# 上一周期
for head, nozzle in enumerate(nozzle_cycle):
if scan_part[head] == -1:
continue
if component_data.loc[scan_part[head]]['nz'] != nozzle and nozzle != '':
nozzle_counter += 2
# 下一周期(额外增加的吸嘴更换次数)
if cycle_index + 1 < len(nozzle_mode):
for head, nozzle in enumerate(nozzle_mode[cycle_index + 1]):
if scan_part[head] == -1:
continue
prev_counter, new_counter = 0, 0
if nozzle_cycle[head] != nozzle and nozzle_cycle[head] != '' and nozzle != '':
prev_counter += 2
if component_data.loc[scan_part[head]]['nz'] != nozzle and nozzle != '':
new_counter += 2
nozzle_counter += new_counter - prev_counter
else:
for head, nozzle in enumerate(nozzle_mode[0]):
if scan_part[head] == -1:
continue
prev_counter, new_counter = 0, 0
if nozzle_cycle[head] != nozzle and nozzle_cycle[head] != '' and nozzle != '':
prev_counter += 2
if component_data.loc[scan_part[head]]['nz'] != nozzle and nozzle != '':
new_counter += 2
nozzle_counter += new_counter - prev_counter
if component_counter == 0: # 当前情形下未扫描到任何元件
continue
search_break = False
scan_part_head = defaultdict(list)
for head, part in enumerate(scan_part):
if part == -1:
continue
scan_part_head[part].append(head)
for part, heads in scan_part_head.items():
part_cycle = component_points[part] // len(heads)
for head in heads:
scan_cycle[head] = part_cycle
# 计算扫描后的代价函数,记录扫描后的最优解
# 短期收益
cycle = min(filter(lambda x: x > 0, scan_cycle))
gang_pick_counter, gang_pick_slot_set = 0, set()
for head, pick_slot in enumerate(scan_slot):
gang_pick_slot_set.add(pick_slot - head * interval_ratio)
eval_func_short_term = e_gang_pick * (max_head_index - scan_slot.count(-1) - len(
gang_pick_slot_set)) * cycle - e_nz_change * nozzle_counter
# 长期收益
gang_pick_slot_dict = defaultdict(list)
for head, pick_slot in enumerate(scan_slot):
if pick_slot == -1:
continue
gang_pick_slot_dict[pick_slot - head * interval_ratio].append(scan_cycle[head])
eval_func_long_term = 0
for pick_cycle in gang_pick_slot_dict.values():
while pick_cycle:
min_cycle = min(pick_cycle)
eval_func_long_term += e_gang_pick * (len(pick_cycle) - 1) * min(pick_cycle)
pick_cycle = list(map(lambda c: c - min_cycle, pick_cycle))
pick_cycle = list(filter(lambda c: c > 0, pick_cycle))
eval_func_long_term -= e_nz_change * nozzle_counter
ratio = 0.5
eval_func = (1 - ratio) * eval_func_short_term + ratio * eval_func_long_term
if eval_func >= scan_eval_func:
scan_eval_func = eval_func
best_scan_part, best_scan_cycle = scan_part.copy(), scan_cycle.copy()
best_scan_slot = scan_slot.copy()
best_scan_nozzle_limit = copy.deepcopy(scan_nozzle_limit)
if search_break:
break
scan_eval_func_list.append(scan_eval_func)
cur_scan_part = best_scan_part.copy()
cur_scan_slot = best_scan_slot.copy()
cur_scan_cycle = best_scan_cycle.copy()
cur_nozzle_limit = copy.deepcopy(best_scan_nozzle_limit)
if len(scan_eval_func_list) != 0:
if sum(scan_eval_func_list) >= best_assigned_eval_func:
best_assigned_eval_func = sum(scan_eval_func_list)
assigned_part = cur_scan_part.copy()
assigned_slot = cur_scan_slot.copy()
assigned_cycle = cur_scan_cycle.copy()
nozzle_insert_cycle = cycle_index
# 从供料器基座中移除对应数量的贴装点
nonzero_cycle = [cycle for cycle in assigned_cycle if cycle > 0]
if not nonzero_cycle:
value_increment_base -= max_head_index
continue
for head, slot in enumerate(assigned_slot):
if assigned_part[head] == -1:
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)
# 更新吸嘴匹配模式
cycle_nozzle = nozzle_mode[nozzle_insert_cycle].copy()
for head, component in enumerate(assigned_part):
if component == -1:
continue
cycle_nozzle[head] = component_data.loc[component]['nz']
nozzle_mode.insert(nozzle_insert_cycle + 1, cycle_nozzle)
pbar.update(len(pcb_data) - sum(component_points) - pbar_prev)
pbar_prev = len(pcb_data) - sum(component_points)
if sum(component_points) == 0:
break
return component_result, cycle_result, feeder_slot_result

View File

@ -0,0 +1,564 @@
import copy
import random
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from base_optimizer.optimizer_common import *
from collections import defaultdict
def dynamic_programming_cycle_path(cycle_placement, cycle_points):
head_sequence = []
num_pos = sum([placement != -1 for placement in cycle_placement]) + 1
pos, head_set = [], []
average_pos_x, counter = 0, 1
for head, placement in enumerate(cycle_placement):
if placement == -1:
continue
head_set.append(head)
pos.append([cycle_points[head][0], cycle_points[head][1]])
average_pos_x = average_pos_x + (pos[-1][0] - average_pos_x) / counter
counter += 1
pos.insert(0, [average_pos_x, slotf1_pos[1]])
def get_distance(pos_1, pos_2):
return math.sqrt((pos_1[0] - pos_2[0]) ** 2 + (pos_1[1] - pos_2[1]) ** 2)
# 各节点之间的距离
dist = [[get_distance(pos_1, pos_2) for pos_2 in pos] for pos_1 in pos]
min_dist = [[np.inf for i in range(num_pos)] for s in range(1 << num_pos)]
min_path = [[[] for i in range(num_pos)] for s in range(1 << num_pos)]
# 状压dp搜索
for s in range(1, 1 << num_pos, 2):
# 考虑节点集合s必须包括节点0
if not (s & 1):
continue
for j in range(1, num_pos):
# 终点i需在当前考虑节点集合s内
if not (s & (1 << j)):
continue
if s == int((1 << j) | 1):
# 若考虑节点集合s仅含节点0和节点jdp边界赋予初值
# print('j:', j)
min_path[s][j] = [j]
min_dist[s][j] = dist[0][j]
# 枚举下一个节点i更新
for i in range(1, num_pos):
# 下一个节点i需在考虑节点集合s外
if s & (1 << i):
continue
if min_dist[s][j] + dist[j][i] < min_dist[s | (1 << i)][i]:
min_path[s | (1 << i)][i] = min_path[s][j] + [i]
min_dist[s | (1 << i)][i] = min_dist[s][j] + dist[j][i]
ans_dist = np.inf
ans_path = []
# 求最终最短哈密顿回路
for i in range(1, num_pos):
if min_dist[(1 << num_pos) - 1][i] + dist[i][0] < ans_dist:
# 更新,回路化
ans_path = min_path[s][i]
ans_dist = min_dist[(1 << num_pos) - 1][i] + dist[i][0]
for element in ans_path:
head_sequence.append(head_set[element - 1])
return head_sequence
def pickup_group_combination(component_nozzle, designated_nozzle, supply, supply_cycle, demand, demand_cycle):
combination, combination_cycle = demand.copy(), demand_cycle.copy()
supply_cpy = supply.copy()
while True:
supply_cpy_bits = max_head_index - supply_cpy.count(None)
if supply_cpy_bits == 0:
break
max_match_offset, max_match_counter = 0, 0
supply_cpy_index = [idx for idx, part in enumerate(supply_cpy) if part] # 加快搜索速度
for offset in range(-supply_cpy_index[-1], max_head_index - supply_cpy_index[0]):
match_counter = 0
for idx, part in enumerate(supply_cpy):
if 0 <= idx + offset < max_head_index:
if part is None:
continue
if combination[idx + offset] is None and designated_nozzle[idx + offset] == designated_nozzle[idx]:
match_counter += 1
if match_counter > max_match_counter:
max_match_counter = match_counter
max_match_offset = offset
if match_counter == supply_cpy_bits:
break
for idx, part in enumerate(supply_cpy):
if 0 <= idx + max_match_offset < max_head_index:
if part is None:
continue
if demand[idx + max_match_offset] is None:
combination[idx + max_match_offset] = part
combination_cycle[idx + max_match_offset] = supply_cycle[idx]
supply_cpy[idx] = None
return combination, combination_cycle
def cal_individual_val(component_nozzle, component_point_pos, designated_nozzle, pickup_group, pickup_group_cycle,
pair_group, feeder_part_arrange, individual):
place_time, pick_time = 0.234, 0.4
x_moving_speed, y_moving_speed = 300, 300 # mm/s
prev_pair_index = None
sequenced_pickup_group, sequenced_pickup_cycle = [], []
for gene in individual:
pickup = pickup_group[gene]
pair_index = None
for idx, pair in enumerate(pair_group):
if gene in pair:
pair_index = idx
break
if pair_index is not None and pair_index == prev_pair_index:
for idx, component in enumerate(pickup):
sequenced_pickup_group[-1][idx] = component
else:
sequenced_pickup_group.append(pickup.copy())
sequenced_pickup_cycle.append(pickup_group_cycle[gene])
V = [float('inf') for _ in range(len(sequenced_pickup_group) + 1)] # Node Value
V[0] = 0
V_SNode = [-1 for _ in range(len(sequenced_pickup_group) + 1)]
nozzle_assigned_heads = defaultdict(int)
for nozzle in designated_nozzle:
nozzle_assigned_heads[nozzle] += 1
pickup_result, pickup_cycle_result = [[] for _ in range(len(V))], [[] for _ in range(len(V))]
component_point_index = defaultdict(int)
for i in range(1, len(V)):
cost, t0 = 0, 0
load = defaultdict(int)
Pd, Pd_cycle = [None for _ in range(max_head_index)], [0 for _ in range(max_head_index)] # demand pickup
j = i
while j < len(V):
Ps, Ps_cycle = sequenced_pickup_group[j - 1], [sequenced_pickup_cycle[j - 1] for _ in
range(max_head_index)] # supply pickup and its cycle
for part in Ps:
if part:
load[component_nozzle[part]] += 1
is_combinable = True
for nozzle, counter in load.items():
if counter > nozzle_assigned_heads[nozzle]:
is_combinable = False
if is_combinable:
cost = cost - t0
# combine sequenced pickup ρb and ps into ρu(union pickup)
Pu, Pu_cycle = pickup_group_combination(component_nozzle, designated_nozzle, Ps, Ps_cycle, Pd, Pd_cycle)
# decide the placement cluster and sequencing of pickup ρu
pickup_action_counter, place_action_counter = 0, max_head_index - Pu.count(None)
right_most_slot, left_most_slot = 0, max_slot_index // 2 # most left and right pickup slot
# === TODO: 机械限位、后槽位分配未处理 ===
for head in range(max_head_index):
if not Pu[head]:
continue
assert Pu[head] in feeder_part_arrange.keys()
for slot in feeder_part_arrange[Pu[head]]:
left_most_slot = min(slot - head * interval_ratio, left_most_slot)
right_most_slot = max(slot - head * interval_ratio, right_most_slot)
# calculate forward, backward, pick and place traveling time
t_FW, t_BW, t_PL, t_PU = 0, 0, 0, 0
cycle = 0
while cycle < max(Pu_cycle):
mount_points = []
for head, part in enumerate(Pu):
if part is None or cycle > Pu_cycle[head]:
continue
idx = component_point_index[part]
mount_points.append([component_point_pos[part][idx][0] - head * head_interval + stopper_pos[0],
component_point_pos[part][idx][0] + stopper_pos[1]])
assert len(mount_points) > 0
# calculate cycle moving distance
mount_points.sort(key=lambda x: x[0])
t_FW += max(
abs(slotf1_pos[0] + (left_most_slot - 1) * slot_interval - mount_points[0][0]) / x_moving_speed,
abs(slotf1_pos[1] - mount_points[0][1]) / y_moving_speed)
t_BW += max(
abs(slotf1_pos[0] + (right_most_slot - 1) * slot_interval - mount_points[-1][0]) / x_moving_speed,
abs(slotf1_pos[1] - mount_points[-1][1]) / y_moving_speed)
# pick up moving time
t_PU += (right_most_slot - left_most_slot) * slot_interval / x_moving_speed
# place moving time
for idx_points in range(len(mount_points) - 1):
t_PL += max(abs(mount_points[idx_points][0] - mount_points[idx_points + 1][0]) / x_moving_speed,
abs(mount_points[idx_points][1] - mount_points[idx_points + 1][1]) / y_moving_speed)
cycle += 1
t0 = t_FW + (t_PL + place_action_counter * place_time) + t_BW
cost += (t_PU + pickup_action_counter * pick_time) + t0
if V[i - 1] + cost < V[j]:
pickup_result[j], pickup_cycle_result[j] = Pu, Pu_cycle
V_SNode[j] = i - 1
V[j] = V[i - 1] + cost
Pd, Pd_cycle = Pu, Pu_cycle
j += 1
else:
break
node = len(V) - 1
while True:
prev_node = V_SNode[node]
if prev_node == -1:
break
for k in range(prev_node + 1, node):
pickup_result[k], pickup_cycle_result[k] = [], []
node = prev_node
return V[-1], pickup_result, pickup_cycle_result
def convert_individual_2_result(component_data, component_point_pos, designated_nozzle, pickup_group, pickup_group_cycle,
pair_group, feeder_lane, individual):
component_result, cycle_result, feeder_slot_result = [], [], []
placement_result, head_sequence_result = [], []
# === 记录不同元件对应的槽位 ===
feeder_part_arrange = defaultdict(list)
for slot in range(1, max_slot_index // 2 + 1):
if feeder_lane[slot]:
feeder_part_arrange[feeder_lane[slot]].append(slot)
# === 记录不同元件的注册吸嘴类型 ===
component_nozzle = defaultdict(str)
for pickup in pickup_group:
for part in pickup:
if part is None or part in component_nozzle.keys():
continue
component_nozzle[part] = component_data[component_data['part'] == part]['nz'].tolist()[0]
# initial result
_, pickup_result, pickup_cycle_result = cal_individual_val(component_nozzle, component_point_pos, designated_nozzle,
pickup_group, pickup_group_cycle,
pair_group, feeder_part_arrange, individual)
for idx, pickup in enumerate(pickup_result):
while pickup and max(pickup_cycle_result[idx]) != 0:
cycle = min([cycle_ for cycle_ in pickup_cycle_result[idx] if cycle_ > 0])
feeder_part_arrange_index = defaultdict(int)
component_result.append([-1 for _ in range(max_head_index)])
feeder_slot_result.append([-1 for _ in range(max_head_index)])
cycle_result.append(cycle)
for head, part in enumerate(pickup):
if part is None or pickup_cycle_result[idx][head] == 0:
continue
part_index = component_data[component_data['part'] == part].index.tolist()[0]
component_result[-1][head] = part_index
feeder_slot_result[-1][head] = feeder_part_arrange[part][feeder_part_arrange_index[part]]
feeder_part_arrange_index[part] += 1
if feeder_part_arrange_index[part] >= len(feeder_part_arrange[part]):
feeder_part_arrange_index[part] = 0
pickup_cycle_result[idx][head] -= cycle
component_point_index = defaultdict(int)
for cycle_set in range(len(cycle_result)):
for cycle in range(cycle_result[cycle_set]):
placement_result.append([-1 for _ in range(max_head_index)])
mount_point = [[0, 0] for _ in range(max_head_index)]
for head in range(max_head_index):
part_index = component_result[cycle_set][head]
if part_index == -1:
continue
part = component_data.iloc[part_index]['part']
point_info = component_point_pos[part][component_point_index[part]]
placement_result[-1][head] = point_info[2]
mount_point[head] = point_info[0:2]
component_point_index[part] += 1
head_sequence_result.append(dynamic_programming_cycle_path(placement_result[-1], mount_point))
return component_result, cycle_result, feeder_slot_result, placement_result, head_sequence_result
@timer_wrapper
def optimizer_hybrid_genetic(pcb_data, component_data, hinter=True):
random.seed(0)
np.random.seed(0)
nozzle_assigned_counter = optimal_nozzle_assignment(component_data, pcb_data)
# nozzle assignment result:
designated_nozzle = [''] * max_head_index
head_index = 0
for nozzle, num in nozzle_assigned_counter.items():
while num > 0:
designated_nozzle[head_index] = nozzle
head_index += 1
num -= 1
# === component assignment ===
component_points, nozzle_components = defaultdict(int), defaultdict(list) # 元件贴装点数,吸嘴-元件对应关系
component_feeder_limit, component_divided_points = defaultdict(int), defaultdict(list)
for step in pcb_data.iterrows():
part = step[1]['part']
idx = component_data[component_data['part'] == part].index.tolist()[0]
nozzle = component_data.loc[idx]['nz']
component_feeder_limit[part] = component_data.loc[idx]['feeder-limit']
component_points[part] += 1
if nozzle_components[nozzle].count(part) < component_feeder_limit[part]:
nozzle_components[nozzle].append(part)
for part, feeder_limit in component_feeder_limit.items():
for _ in range(feeder_limit):
component_divided_points[part].append(component_points[part] // feeder_limit)
for part, divided_points in component_divided_points.items():
index = 0
while sum(divided_points) < component_points[part]:
divided_points[index] += 1
index += 1
CT_Group, CT_Points = [], [] # CT: Component Type
while sum(len(nozzle_components[nozzle]) for nozzle in nozzle_components.keys()) != 0:
CT_Group.append([None for _ in range(max_head_index)])
CT_Points.append([0 for _ in range(max_head_index)])
for head_index in range(max_head_index):
nozzle = designated_nozzle[head_index] # 分配的吸嘴
if len(nozzle_components[nozzle]) == 0: # 无可用元件
continue
max_points, designated_part = 0, None
for part in nozzle_components[nozzle]:
if component_points[part] > max_points:
max_points = component_points[part]
designated_part = part
component_points[designated_part] -= component_divided_points[designated_part][-1]
CT_Group[-1][head_index] = designated_part
CT_Points[-1][head_index] = component_divided_points[designated_part][-1]
component_divided_points[designated_part].pop()
nozzle_components[nozzle].remove(designated_part)
# === assign CT group to feeder slot ===
point_num = len(pcb_data)
component_point_pos = defaultdict(list)
for point_cnt in range(point_num):
part = pcb_data.loc[point_cnt, 'part']
component_point_pos[part].append(
[pcb_data.loc[point_cnt, 'x'] + stopper_pos[0], pcb_data.loc[point_cnt, 'y'] + stopper_pos[1], point_cnt])
for pos_list in component_point_pos.values():
pos_list.sort(key=lambda x: (x[0], x[1]))
CT_Group_slot = [-1] * len(CT_Group)
feeder_lane = [None] * max_slot_index # 供料器基座上已分配的元件类型
CT_Head = defaultdict(list)
for pickup in CT_Group:
for head, CT in enumerate(pickup):
if CT is None:
continue
if CT not in CT_Head:
CT_Head[CT] = [head, head]
CT_Head[CT][0] = min(CT_Head[CT][0], head)
CT_Head[CT][1] = max(CT_Head[CT][1], head)
for CTIdx, pickup in enumerate(CT_Group):
best_slot = []
for cp_index, component in enumerate(pickup):
if component is None:
continue
best_slot.append(round((sum(pos[0] for pos in component_point_pos[component]) / len(
component_point_pos[component]) - slotf1_pos[0]) / slot_interval) + 1 - cp_index * interval_ratio)
best_slot = round(sum(best_slot) / len(best_slot))
search_dir, step = 0, 0 # dir: 1-向右, 0-向左
prev_assign_available = True
while True:
assign_slot = best_slot + step if search_dir else best_slot - step
if assign_slot + (len(pickup) - 1) * interval_ratio >= max_slot_index / 2 or assign_slot < 0:
if not prev_assign_available:
raise Exception('feeder assign error!')
# prev_assign_available = False
search_dir = 1 - search_dir
if search_dir == 1:
step += 1
continue
prev_assign_available = True
assign_available = True
# 分配对应槽位
for slot in range(assign_slot, assign_slot + interval_ratio * len(pickup), interval_ratio):
pickup_index = int((slot - assign_slot) / interval_ratio)
pick_part = pickup[pickup_index]
# 检查槽位占用情况
if feeder_lane[slot] is not None and pick_part is not None:
assign_available = False
break
# 检查机械限位冲突
if pick_part is not None and (slot - CT_Head[pick_part][0] * interval_ratio <= 0 or
slot + (max_head_index - CT_Head[pick_part][1] - 1) * interval_ratio > max_slot_index // 2):
assign_available = False
break
if assign_available:
for idx, component in enumerate(pickup):
if component is not None:
feeder_lane[assign_slot + idx * interval_ratio] = component
CT_Group_slot[CTIdx] = assign_slot
break
search_dir = 1 - search_dir
if search_dir == 1:
step += 1
# === Initial Pickup Group ===
initial_pickup, initial_pickup_cycle = [], []
for index, CT in enumerate(CT_Group):
while True:
if CT_Points[index].count(0) == max_head_index:
break
min_element = min([Points for Points in CT_Points[index] if Points > 0])
initial_pickup.append(copy.deepcopy(CT_Group[index]))
initial_pickup_cycle.append(min_element)
for head in range(max_head_index):
if CT_Points[index][head] >= min_element:
CT_Points[index][head] -= min_element
if CT_Points[index][head] == 0:
CT_Group[index][head] = None
# pickup partition rule
partition_probability = 0.1
pickup_group, pair_group = [], [] # pair_group: pickups from same initial group
pickup_group_cycle = []
for idx, Pickup in enumerate(initial_pickup):
pickup_num = len([element for element in Pickup if element is not None])
if 2 <= pickup_num <= max_head_index / 3 or (
max_head_index / 3 <= pickup_num <= max_head_index / 2 and np.random.rand() < partition_probability):
# partitioned into single component pickups
# or partition the potentially inefficient initial pickups with a small probability
pair_index = []
for index, CT in enumerate(Pickup):
if CT is not None:
pair_index.append(len(pickup_group))
pickup_group.append([None for _ in range(max_head_index)])
pickup_group[-1][index] = CT
pickup_group_cycle.append(initial_pickup_cycle[idx])
pair_group.append(pair_index)
else:
pickup_group.append(Pickup)
pickup_group_cycle.append(initial_pickup_cycle[idx])
# basic parameter
# crossover rate & mutation rate: 80% & 10%
# population size: 200
# the number of generation: 500
crossover_rate, mutation_rate = 0.8, 0.1
population_size, n_generations = 200, 500
# initial solution
population = []
for _ in range(population_size):
pop_permutation = list(range(len(pickup_group)))
np.random.shuffle(pop_permutation)
population.append(pop_permutation)
best_individual, best_pop_val = [], []
# === 记录不同元件对应的槽位 ===
feeder_part_arrange = defaultdict(list)
for slot in range(1, max_slot_index // 2 + 1):
if feeder_lane[slot]:
feeder_part_arrange[feeder_lane[slot]].append(slot)
# === 记录不同元件的注册吸嘴类型 ===
component_nozzle = defaultdict(str)
for pickup in pickup_group:
for part in pickup:
if part is None or part in component_nozzle.keys():
continue
component_nozzle[part] = component_data[component_data['part'] == part]['nz'].tolist()[0]
with tqdm(total=n_generations) as pbar:
pbar.set_description('hybrid genetic process')
for _ in range(n_generations):
# calculate fitness value
pop_val = []
for pop_idx, individual in enumerate(population):
val, _, _ = cal_individual_val(component_nozzle, component_point_pos, designated_nozzle, pickup_group,
pickup_group_cycle, pair_group, feeder_part_arrange, individual)
pop_val.append(val)
idx = np.argmin(pop_val)
if len(best_pop_val) == 0 or pop_val[idx] < best_pop_val[-1]:
best_individual = copy.deepcopy(population[idx])
best_pop_val.append(pop_val[idx])
# min-max convert
max_val = 1.5 * max(pop_val)
pop_val = list(map(lambda v: max_val - v, pop_val))
# crossover and mutation
c = 0
new_population = []
for pop in range(population_size):
if pop % 2 == 0 and np.random.random() < crossover_rate:
index1, index2 = roulette_wheel_selection(pop_val), -1
while True:
index2 = roulette_wheel_selection(pop_val)
if index1 != index2:
break
# 两点交叉算子
offspring1 = directed_edge_recombination_crossover(c, population[index1], population[index2])
c += 1
offspring2 = directed_edge_recombination_crossover(c, population[index2], population[index1])
c += 1
if np.random.random() < mutation_rate:
swap_mutation(offspring1)
if np.random.random() < mutation_rate:
swap_mutation(offspring2)
new_population.append(offspring1)
new_population.append(offspring2)
# selection
top_k_index = get_top_k_value(pop_val, population_size - len(new_population))
for index in top_k_index:
new_population.append(population[index])
population = new_population
pbar.update(1)
return convert_individual_2_result(component_data, component_point_pos, designated_nozzle, pickup_group,
pickup_group_cycle, pair_group, feeder_lane, best_individual)

View File

@ -0,0 +1,166 @@
import itertools
from base_optimizer.optimizer_common import *
@timer_wrapper
def optimizer_scanbased(component_data, pcb_data, hinter):
population_size = 200 # 种群规模
crossover_rate, mutation_rate = .4, .02
n_generation = 5
component_points = [0] * len(component_data)
for i in range(len(pcb_data)):
part = pcb_data.loc[i]['part']
part_index = component_data[component_data['part'] == 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'
raise ValueError(info)
# randomly generate permutations
generation_ = np.array([i for i in range(max_slot_index // 2)]) # 仅考虑前基座
pop_individual, pop_val = [], []
for _ in range(population_size):
np.random.shuffle(generation_)
pop_individual.append(generation_.tolist())
_, cycle_result, feeder_slot_result = convert_individual_2_result(component_points, pop_individual[-1])
pop_val.append(feeder_arrange_evaluate(feeder_slot_result, cycle_result))
# todo: 过程写的有问题,暂时不想改
with tqdm(total=n_generation) as pbar:
pbar.set_description('hybrid genetic process')
for _ in range(n_generation):
# 交叉
for pop in range(population_size):
if pop % 2 == 0 and np.random.random() < crossover_rate:
index1, index2 = roulette_wheel_selection(pop_val), -1
while True:
index2 = roulette_wheel_selection(pop_val)
if index1 != index2:
break
# 两点交叉算子
offspring1, offspring2 = cycle_crossover(pop_individual[index1], pop_individual[index2])
_, cycle_result, feeder_slot_result = convert_individual_2_result(component_points, offspring1)
pop_val.append(feeder_arrange_evaluate(feeder_slot_result, cycle_result))
pop_individual.append(offspring1)
_, cycle_result, feeder_slot_result = convert_individual_2_result(component_points, offspring2)
pop_val.append(feeder_arrange_evaluate(feeder_slot_result, cycle_result))
pop_individual.append(offspring2)
sigma_scaling(pop_val, 1)
# 变异
if np.random.random() < mutation_rate:
index_ = roulette_wheel_selection(pop_val)
offspring = swap_mutation(pop_individual[index_])
_, cycle_result, feeder_slot_result = convert_individual_2_result(component_points, offspring)
pop_val.append(feeder_arrange_evaluate(feeder_slot_result, cycle_result))
pop_individual.append(offspring)
sigma_scaling(pop_val, 1)
new_population, new_popval = [], []
for index in get_top_k_value(pop_val, population_size):
new_population.append(pop_individual[index])
new_popval.append(pop_val[index])
pop_individual, pop_val = new_population, new_popval
# select the best individual
pop = np.argmin(pop_val)
component_result, cycle_result, feeder_slot_result = convert_individual_2_result(component_points, pop_individual[pop])
placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
cycle_result, feeder_slot_result)
return component_result, cycle_result, feeder_slot_result, placement_result, head_sequence
def convert_individual_2_result(component_points, pop):
component_result, cycle_result, feeder_slot_result = [], [], []
feeder_part = [-1] * (max_slot_index // 2) # 已安装在供料器基座上的元件0: 未分配)
feeder_base_points = [0] * (max_slot_index // 2) # 供料器基座结余贴装点数量
# 将基因信息转换为供料器基座安装结果
for idx, gene in enumerate(pop):
if idx >= len(component_points):
break
feeder_part[gene], feeder_base_points[gene] = idx, component_points[idx]
# TODO: 暂时未考虑可用吸嘴数的限制
# for _ in range(math.ceil(sum(component_points) / max_head_index)):
while True:
# === 周期内循环 ===
assigned_part = [-1 for _ in range(max_head_index)] # 当前扫描到的头分配元件信息
assigned_slot = [-1 for _ in range(max_head_index)] # 当前扫描到的供料器分配信息
prev_scan_slot = len(feeder_part) // 2 # 前一轮扫描的位置
while True:
best_scan_part, best_scan_slot = [-1 for _ in range(max_head_index)], [-1 for _ in range(max_head_index)]
best_slot_index = -1
for slot in range(max_slot_index // 2 - (max_head_index - 1) * interval_ratio):
scan_part, scan_slot = assigned_part.copy(), assigned_slot.copy()
for head in range(max_head_index):
part = feeder_part[slot + head * interval_ratio]
# 贴装头和拾取槽位满足对应关系
if scan_part[head] == -1 and part != -1 and feeder_base_points[slot + head * interval_ratio] > 0:
scan_part[head], scan_slot[head] = part, slot + head * interval_ratio + 1
if scan_part.count(-1) < best_scan_part.count(-1) or (scan_part.count(-1) == best_scan_part.count(-1)
and abs(slot - prev_scan_slot) <
abs(best_slot_index - prev_scan_slot)):
best_slot_index = slot
best_scan_part, best_scan_slot = scan_part.copy(), scan_slot.copy()
assigned_points = 0
for idx, slot in enumerate(best_scan_slot):
if slot != -1 and assigned_slot[idx] == -1:
feeder_base_points[slot - 1] -= 1
assigned_points += 1
assigned_part, assigned_slot = best_scan_part.copy(), best_scan_slot.copy()
prev_scan_slot = best_slot_index
if assigned_part.count(-1) == 0 or assigned_points == 0:
break
if len(cycle_result) == 0 or component_result[-1] != assigned_part:
cycle_result.append(1)
component_result.append(assigned_part)
feeder_slot_result.append(assigned_slot)
else:
cycle_result[-1] += 1
if sum(feeder_base_points) == 0:
break
return component_result, cycle_result, feeder_slot_result
def feeder_arrange_evaluate(feeder_slot_result, cycle_result):
assert len(feeder_slot_result) == len(cycle_result)
arrange_val = 0
for cycle, feeder_slot in enumerate(feeder_slot_result):
pick_slot = set()
for head, slot in enumerate(feeder_slot):
pick_slot.add(slot - head * interval_ratio)
arrange_val += len(pick_slot) * t_pick * cycle_result[cycle]
pick_slot = list(pick_slot)
pick_slot.sort()
arrange_val += axis_moving_time(pick_slot[0] - pick_slot[-1]) * cycle_result[cycle]
return arrange_val