调整工程架构,增补了几种算法,初步添加神经网路训练拟合代码

This commit is contained in:
2024-03-29 22:10:07 +08:00
parent 800057e000
commit bae7e4e2c3
18 changed files with 2459 additions and 354 deletions

View File

@ -1,8 +1,5 @@
from base_optimizer.optimizer_common import *
from gurobipy import *
from collections import defaultdict
def list_range(start, end=None):
return list(range(start)) if end is None else list(range(start, end))

View File

@ -1,5 +1,30 @@
from base_optimizer.optimizer_common import *
from result_analysis import *
def component_assign_evaluate(component_data, component_result, cycle_result, feeder_slot_result) -> float:
nozzle_change_counter = 0
for head in range(max_head_index):
nozzle = ''
for cycle in range(len(component_result)):
component_index = component_result[cycle][head]
if component_index == -1:
continue
if cycle != 0 and nozzle != component_data.loc[component_index, 'nz']:
nozzle_change_counter += 1
nozzle = component_data.loc[component_index, 'nz']
gang_pick_counter = 0
for cycle, feeder_slot in enumerate(feeder_slot_result):
pick_slot = defaultdict(int)
for head, slot in enumerate(feeder_slot):
if slot == -1:
continue
pick_slot[slot - head * interval_ratio] += 1
for _ in pick_slot.values():
gang_pick_counter += cycle_result[cycle]
return sum(cycle_result) + e_nz_change * nozzle_change_counter + e_gang_pick * gang_pick_counter
def convert_cell_2_result(pcb_data, component_data, component_cell, population):

View File

@ -1,25 +1,22 @@
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
from gurobipy import *
from sklearn.linear_model import LinearRegression
# 整线参数
max_machine_index = 3
# 时间参数
T_pp, T_tr, T_nc, T_pl = 2, 5, 25, 0
import os
import time
import math
import random
import copy
import torch
import argparse
import joblib
import pickle
import warnings
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 机器参数
max_head_index, max_slot_index = 6, 120
@ -56,6 +53,73 @@ t_nozzle_put, t_nozzle_pick = 0.9, 0.75 # 装卸吸嘴用时
t_nozzle_change = t_nozzle_put + t_nozzle_pick
t_fix_camera_check = 0.12 # 固定相机检测时间
# 时间参数(整线相关)
T_pp, T_tr, T_nc, T_pl = 2, 5, 25, 0
class OptInfo:
def __init__(self):
self.placement_time = 0
self.cycle_counter = 0
self.nozzle_change_counter = 0
self.pickup_counter = 0
self.pickup_movement = 0
self.placement_movement = 0
def optimization_assign_result(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
nozzle_hinter=False, component_hinter=False, feeder_hinter=False):
if nozzle_hinter:
columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']
nozzle_assign = pd.DataFrame(columns=columns)
for cycle, components in enumerate(component_result):
nozzle_assign.loc[cycle, 'cycle'] = cycle_result[cycle]
for head in range(max_head_index):
index = component_result[cycle][head]
if index == -1:
nozzle_assign.loc[cycle, 'H{}'.format(head + 1)] = ''
else:
nozzle_assign.loc[cycle, 'H{}'.format(head + 1)] = component_data.loc[index].nz
print(nozzle_assign)
print('')
if component_hinter:
columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']
component_assign = pd.DataFrame(columns=columns)
for cycle, components in enumerate(component_result):
component_assign.loc[cycle, 'cycle'] = cycle_result[cycle]
for head in range(max_head_index):
index = component_result[cycle][head]
if index == -1:
component_assign.loc[cycle, 'H{}'.format(head + 1)] = ''
else:
component_assign.loc[cycle, 'H{}'.format(head + 1)] = component_data.loc[index].part
print(component_assign)
print('')
if feeder_hinter:
columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']
feedr_assign = pd.DataFrame(columns=columns)
for cycle, components in enumerate(feeder_slot_result):
feedr_assign.loc[cycle, 'cycle'] = cycle_result[cycle]
for head in range(max_head_index):
slot = feeder_slot_result[cycle][head]
if slot == -1:
feedr_assign.loc[cycle, 'H{}'.format(head + 1)] = 'A'
else:
feedr_assign.loc[cycle, 'H{}'.format(head + 1)] = 'F{}'.format(
slot) if slot <= max_slot_index // 2 else 'R{}'.format(slot - max_head_index)
print(feedr_assign)
print('')
def axis_moving_time(distance, axis=0):
distance = abs(distance) * 1e-3
@ -315,8 +379,8 @@ def dynamic_programming_cycle_path(pcb_data, cycle_placement, assigned_feeder):
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]])
pos.append([pcb_data.iloc[placement]['x'] - head * head_interval + stopper_pos[0],
pcb_data.iloc[placement]['y'] + stopper_pos[1]])
feeder_set.add(feeder - head * interval_ratio)
@ -369,8 +433,8 @@ def dynamic_programming_cycle_path(pcb_data, cycle_placement, assigned_feeder):
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:
if pcb_data.iloc[cycle_placement[start_head]]['x'] - start_head * head_interval > \
pcb_data.iloc[cycle_placement[end_head]]['x'] - end_head * head_interval:
head_sequence = list(reversed(head_sequence))
return head_sequence
@ -382,11 +446,11 @@ def greedy_placement_route_generation(component_data, pcb_data, component_result
mount_point_pos = [[] for _ in range(len(component_data))]
for i in range(len(pcb_data)):
part = pcb_data.loc[i]['part']
part = pcb_data.iloc[i]['part']
component_index = component_data[component_data['part'] == part].index.tolist()[0]
# 记录贴装点序号索引和对应的位置坐标
mount_point_index[component_index].append(i)
mount_point_pos[component_index].append([pcb_data.loc[i]['x'], pcb_data.loc[i]['y']])
mount_point_pos[component_index].append([pcb_data.iloc[i]['x'], pcb_data.iloc[i]['y']])
search_dir = 1 # 0自左向右搜索 1自右向左搜索
for cycle_set in range(len(component_result)):
@ -883,18 +947,18 @@ def swap_mutation(parent):
return parent
def constraint_swap_mutation(component_points, individual):
def constraint_swap_mutation(component_points, individual, machine_number):
offspring = individual.copy()
idx, component_index = 0, random.randint(0, len(component_points) - 1)
for _, points in component_points:
if component_index == 0:
while True:
index1, index2 = random.sample(range(points + max_machine_index - 2), 2)
index1, index2 = random.sample(range(points + machine_number - 2), 2)
if offspring[idx + index1] != offspring[idx + index2]:
break
clip = offspring[idx: idx + points + max_machine_index - 1].copy()
clip = offspring[idx: idx + points + machine_number - 1].copy()
avl_machine = 0
for idx_, gene in enumerate(clip):
if gene == 0 and (idx_ == 0 or clip[idx_ - 1] != 0):
@ -912,7 +976,7 @@ def constraint_swap_mutation(component_points, individual):
break
component_index -= 1
idx += (points + max_machine_index - 1)
idx += (points + machine_number - 1)
return offspring
@ -959,3 +1023,92 @@ def get_top_k_value(pop_val, k: int, reverse=True):
res.append(j)
break
return res
def get_line_config_number(machine_number, component_number):
div_counter = 0
div_set = set()
for div1 in range(component_number - 2):
for div2 in range(div1 + 1, component_number - 1):
machine_div = [div1 + 1, div2 - div1, component_number - div2 - 1]
machine_div.sort()
div_str = "".join(str(s) + '|' for s in machine_div)
if div_str in div_set:
continue
div_set.add(div_str)
assign_component_counter = defaultdict(list)
for div in machine_div:
assign_component_counter[div] += 1
case_div_counter, case_comp_number = 1, component_number
for idx in range(machine_number - 1):
div = 1
while machine_div[idx]:
div *= (case_comp_number / machine_div[idx])
case_comp_number -= 1
machine_div[idx] -= 1
case_div_counter *= div
for key, val in assign_component_counter.values():
div = 1
while val:
div *= val
val -= 1
case_div_counter /= div
div_counter += case_div_counter
return div_counter
def partial_data_convert(pcb_data, component_data, machine_assign, machine_number):
assignment_result = copy.deepcopy(machine_assign)
partial_pcb_data, partial_component_data = defaultdict(pd.DataFrame), defaultdict(pd.DataFrame)
for machine_index in range(machine_number):
partial_pcb_data[machine_index] = pd.DataFrame(columns=pcb_data.columns)
partial_component_data[machine_index] = component_data.copy(deep=True)
# === averagely assign available feeder ===
for part_index, data in component_data.iterrows():
feeder_limit = data['feeder-limit']
feeder_points = [assignment_result[machine_index][part_index] for machine_index in range(max_machine_index)]
for machine_index in range(machine_number):
if feeder_points[machine_index] == 0:
continue
arg_feeder = max(math.floor(feeder_points[machine_index] / sum(feeder_points) * data['feeder-limit']), 1)
partial_component_data[machine_index].loc[part_index]['feeder-limit'] = arg_feeder
feeder_limit -= arg_feeder
for machine_index in range(machine_number):
if feeder_limit <= 0:
break
if feeder_points[machine_index] == 0:
continue
partial_component_data[machine_index].loc[part_index]['feeder-limit'] += 1
feeder_limit -= 1
for machine_index in range(machine_number):
if feeder_points[machine_index] > 0:
assert partial_component_data[machine_index].loc[part_index]['feeder-limit'] > 0
# === assign placements ===
component_machine_index = [0 for _ in range(len(component_data))]
for _, data in pcb_data.iterrows():
part_index = component_data[component_data['part'] == data['part']].index.tolist()[0]
while True:
machine_index = component_machine_index[part_index]
if assignment_result[machine_index][part_index] == 0:
component_machine_index[part_index] += 1
machine_index += 1
else:
break
assignment_result[machine_index][part_index] -= 1
partial_pcb_data[machine_index] = pd.concat([partial_pcb_data[machine_index], pd.DataFrame(data).T])
return partial_pcb_data, partial_component_data

View File

@ -13,20 +13,20 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
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']
for _, data in pcb_data.iterrows():
pos, part = data.x + stopper_pos[0], data.part
part_index = component_data[component_data['part'] == part].index.tolist()[0]
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']
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)
feeder_division_points[part_index] = points // feeder_limit[part_index]
nozzle_component, nozzle_component_points = defaultdict(list), defaultdict(list)
for part, nozzle in part_nozzle.items():
@ -36,13 +36,13 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
if feeder_data is not None:
for _, feeder in feeder_data.iterrows():
slot, part = feeder['slot'], feeder['part']
part_index = component_data[component_data['part'] == part].index.tolist()[0]
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']
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
@ -75,15 +75,60 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
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]
upper_head, extra_head = defaultdict(int), defaultdict(int)
head_index = []
for nozzle, head in nozzle_assigned_counter.items():
# 每个吸嘴能达成同时拾取数目的上限
upper_head[nozzle] = min(len(nozzle_component[nozzle]), head)
extra_head[nozzle] = head - upper_head[nozzle]
head_counter = (sum(upper_head.values()) - 1) // 2
while head_counter >= 0:
if head_counter != (sum(upper_head.values()) - 1) - head_counter:
head_index.append((sum(upper_head.values()) - 1) - head_counter)
head_index.append(head_counter)
head_counter -= 1
nozzle_pattern = [None for _ in range(sum(upper_head.values()))]
for nozzle in upper_head.keys():
counter = upper_head[nozzle]
while counter:
nozzle_pattern[head_index[0]] = nozzle
counter -= 1
head_index.pop(0)
head = 0
while head + sum(extra_head.values()) <= len(nozzle_pattern):
extra_head_cpy = copy.deepcopy(extra_head)
increment = 0
while increment < sum(extra_head.values()):
extra_head_cpy[nozzle_pattern[head + increment]] -= 1
increment += 1
check_extra_head = True
for head_ in extra_head_cpy.values():
if head_ != 0:
check_extra_head = False # 任一项不为0 说明不构成
break
if check_extra_head:
increment = 0
while increment < sum(extra_head.values()):
nozzle_pattern.append(nozzle_pattern[head + increment])
increment += 1
for nozzle in extra_head.keys():
extra_head[nozzle] = 0
break
head += 1
for nozzle, head_ in extra_head.items():
while head_:
nozzle_pattern.append(nozzle)
head_ -= 1
assert len(nozzle_pattern) == max_head_index
while True:
best_assign, best_assign_points = [], []
best_assign_slot, best_assign_value = -1, -np.Inf
@ -162,7 +207,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
part = tmp_nozzle_component[nozzle_assign][index_]
feeder_type = component_data.loc[part]['fdr']
feeder_type = component_data.loc[part].fdr
extra_width, extra_slot = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval, 1
slot_overlap = False
while extra_width > 0:
@ -210,7 +255,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
continue
for idx, part in enumerate(assign_part_stack):
feeder_type = component_data.loc[part]['fdr']
feeder_type = component_data.loc[part].fdr
extra_width, extra_slot = feeder_width[feeder_type][0] + feeder_width[feeder_type][
1] - slot_interval, 1
@ -223,7 +268,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
extra_width -= slot_interval
extra_slot += 1
if component_data.loc[part]['nz'] == nozzle_pattern[head] and not slot_overlap:
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]
@ -237,7 +282,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
continue
part, points = assign_part_stack[0], assign_part_stack_points[0]
feeder_type = component_data.loc[part]['fdr']
feeder_type = component_data.loc[part].fdr
extra_width, extra_slot = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval, 1
slot_overlap = False
@ -259,7 +304,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
extra_width -= head_interval
else:
# 返还由于机械限位无法分配的,压入元件堆栈中的元素
nozzle = component_data.loc[part]['nz']
nozzle = component_data.loc[part].nz
tmp_nozzle_component[nozzle].insert(0, part)
tmp_nozzle_component_points[nozzle].insert(0, points)
@ -269,7 +314,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
# 仍然存在由于机械限位,无法进行分配的在堆栈中的元件
while assign_part_stack:
part, points = assign_part_stack[0], assign_part_stack_points[0]
nozzle = component_data.loc[part]['nz']
nozzle = component_data.loc[part].nz
tmp_nozzle_component[nozzle].insert(0, part)
tmp_nozzle_component_points[nozzle].insert(0, points)
@ -283,7 +328,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
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]:
if nozzle_pattern and component_data.loc[feeder_].nz != nozzle_pattern[head]:
nozzle_change_counter += 1
if len(average_slot) == 0:
@ -349,7 +394,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
# 更新供料器基座信息
feeder_base[best_assign_slot + idx * interval_ratio] = part
feeder_type, extra_slot = component_data.loc[part]['fdr'], 0
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
@ -358,7 +403,7 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
extra_width -= slot_interval
# 更新吸嘴信息
nozzle_pattern[idx] = component_data.loc[part]['nz']
nozzle_pattern[idx] = component_data.loc[part].nz
# 更新头分配的先后顺序
head_assign_indexes = np.array(best_assign_points).argsort().tolist()
@ -376,11 +421,10 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
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]
part_index = component_data[component_data.part == feeder.part].index.tolist()[0]
# 供料器基座分配位置和对应贴装点数
feeder_base[slot], feeder_base_points[slot] = part_index, feeder_division_points[part_index]
feeder_base[feeder.slot], feeder_base_points[feeder.slot] = part_index, feeder_division_points[part_index]
# 前基座 TODO: 后基座
for slot in range(max_slot_index // 2 - (max_head_index - 1) * interval_ratio):
@ -397,12 +441,12 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
# 更新供料器占位信息
for _, data in feeder_data.iterrows():
feeder_base[data['slot']] = -1
feeder_base[data.slot] = -1
for slot, feeder in enumerate(feeder_base):
if feeder < 0:
continue
part = component_data.loc[feeder]['part']
part = component_data.loc[feeder].part
feeder_data.loc[len(feeder_data.index)] = [slot, part, 0]
@ -414,20 +458,19 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
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']
for _, feeder in feeder_data.iterrows():
part_index = component_data[component_data.part == feeder.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
start = slotf1_pos[0] + slot_interval * (feeder.slot - 1) - slot_interval / 2
end = slotf1_pos[0] + slot_interval * (feeder.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)
c = 'red' if feeder.arg == 0 else 'black' # 黑色表示已分配,红色表示新分配
plt.text(slotf1_pos[0] + slot_interval * (feeder.slot - 1), slotf1_pos[1] + 12,
feeder.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)
@ -461,30 +504,28 @@ def feeder_allocate(component_data, pcb_data, feeder_data, figure=False):
@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'])
for _, feeder in feeder_data.iterrows():
feeder_assign_check.add(feeder.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]
for i, data in pcb_data.iterrows():
part_index = component_data[component_data.part == data.part].index.tolist()[0]
component_points[part_index] += 1
nozzle_type = component_data.loc[part_index]['nz']
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)
for _, data in feeder_data.iterrows():
component_index = component_data[component_data.part == data.part].index.tolist()
if len(component_index) != 1:
print('unregistered component: ', data.part, ' in slot', data.slot)
continue
part_index = part_index[0]
feeder_part[slot] = part_index
component_index = component_index[0]
feeder_part[data.slot] = component_index
component_result, cycle_result, feeder_slot_result = [], [], [] # 贴装点索引和拾取槽位优化结果
@ -539,7 +580,7 @@ def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
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']
nozzle = component_data.loc[part].nz
if scan_nozzle_limit[nozzle] <= 0:
continue
@ -591,7 +632,7 @@ def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
for head, nozzle in enumerate(nozzle_cycle):
if scan_part[head] == -1:
continue
if component_data.loc[scan_part[head]]['nz'] != nozzle and nozzle != '':
if component_data.loc[scan_part[head]].nz != nozzle and nozzle != '':
nozzle_counter += 2
# 下一周期(额外增加的吸嘴更换次数)
@ -602,7 +643,7 @@ def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
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 != '':
if component_data.loc[scan_part[head]].nz != nozzle and nozzle != '':
new_counter += 2
nozzle_counter += new_counter - prev_counter
else:
@ -612,7 +653,7 @@ def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
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 != '':
if component_data.loc[scan_part[head]].nz != nozzle and nozzle != '':
new_counter += 2
nozzle_counter += new_counter - prev_counter
@ -708,7 +749,7 @@ def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
for head, component in enumerate(assigned_part):
if component == -1:
continue
cycle_nozzle[head] = component_data.loc[component]['nz']
cycle_nozzle[head] = component_data.loc[component].nz
nozzle_mode.insert(nozzle_insert_cycle + 1, cycle_nozzle)

View File

@ -1,12 +1,4 @@
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):

View File

@ -0,0 +1,96 @@
# 用于提供对外接口
from base_optimizer.optimizer_scanbased import *
from base_optimizer.optimizer_celldivision import *
from base_optimizer.optimizer_hybridgenetic import *
from base_optimizer.optimizer_feederpriority import *
from base_optimizer.optimizer_aggregation import *
from base_optimizer.optimizer_twophase import *
from base_optimizer.optimizer_mathmodel import *
from base_optimizer.result_analysis import *
def base_optimizer(machine_index, pcb_data, component_data, feeder_data=None, method='', hinter=False):
if method == 'cell_division': # 基于元胞分裂的遗传算法
component_result, cycle_result, feeder_slot_result = optimizer_celldivision(pcb_data, component_data,
hinter=False)
placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
cycle_result, feeder_slot_result)
elif method == 'feeder_scan': # 基于基座扫描的供料器优先算法
# 第1步分配供料器位置
nozzle_pattern = feeder_allocate(component_data, pcb_data, feeder_data, figure=False)
# 第2步扫描供料器基座确定元件拾取的先后顺序
component_result, cycle_result, feeder_slot_result = feeder_base_scan(component_data, pcb_data, feeder_data,
nozzle_pattern)
# 第3步贴装路径规划
placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
cycle_result, feeder_slot_result)
# placement_result, head_sequence = beam_search_for_route_generation(component_data, pcb_data, component_result,
# cycle_result, feeder_slot_result)
elif method == 'hybrid_genetic': # 基于拾取组的混合遗传算法
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_hybrid_genetic(
pcb_data, component_data, hinter=False)
elif method == 'aggregation': # 基于batch-level的整数规划 + 启发式算法
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_aggregation(
component_data, pcb_data)
elif method == 'genetic_scanning':
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_genetic_scanning(
component_data, pcb_data, hinter=False)
elif method == 'mip_model':
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_mathmodel(
component_data, pcb_data, hinter=True)
elif method == "two_phase":
component_result, feeder_slot_result, cycle_result = gurobi_optimizer(pcb_data, component_data, feeder_data,
initial=True, partition=True,
reduction=True, hinter=hinter)
placement_result, head_sequence = scan_based_placement_route_generation(component_data, pcb_data,
component_result, cycle_result)
else:
raise 'method is not existed'
info = OptInfo()
assigned_nozzle = ['' if idx == -1 else component_data.loc[idx]['nz'] for idx in component_result[0]]
info.cycle_counter = sum(cycle_result)
for cycle in range(len(cycle_result)):
pick_slot = set()
for head in range(max_head_index):
idx = component_result[cycle][head]
if idx == -1:
continue
nozzle = component_data.loc[idx]['nz']
if nozzle != assigned_nozzle[head]:
if assigned_nozzle[head] != '':
info.nozzle_change_counter += 1
assigned_nozzle[head] = nozzle
pick_slot.add(feeder_slot_result[cycle][head] - head * interval_ratio)
info.pickup_counter += len(pick_slot) * cycle_result[cycle]
pick_slot = list(pick_slot)
pick_slot.sort()
for idx in range(len(pick_slot) - 1):
info.pickup_movement += abs(pick_slot[idx + 1] - pick_slot[idx])
if hinter:
optimization_assign_result(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
nozzle_hinter=True, component_hinter=False, feeder_hinter=True)
print('----- Placement machine ' + str(machine_index) + ' ----- ')
print('-Cycle counter: {}'.format(info.cycle_counter))
print('-Nozzle change counter: {}'.format(info.nozzle_change_counter))
print('-Pick operation counter: {}'.format(info.pickup_counter))
print('-Pick movement: {}'.format(info.pickup_movement))
print('------------------------------ ')
# 估算贴装用时
info.placement_time = placement_time_estimate(component_data, pcb_data, component_result, cycle_result,
feeder_slot_result, placement_result, head_sequence, hinter=False)
return info

View File

@ -0,0 +1,354 @@
from base_optimizer.optimizer_common import *
def list_range(start, end=None):
return list(range(start)) if end is None else list(range(start, end))
def head_task_model(component_data, pcb_data, hinter=True):
mdl = Model('pick_route')
mdl.setParam('Seed', 0)
mdl.setParam('OutputFlag', hinter) # set whether output the debug information
mdl.setParam('TimeLimit', 600)
H = max_head_index
I = len(component_data)
S = len(component_data)
K = len(pcb_data)
nozzle_type, component_type = [], []
for _, data in component_data.iterrows():
if not data.nz in nozzle_type:
nozzle_type.append(data.nz)
component_type.append(data.part)
average_pos = 0
for _, data in pcb_data.iterrows():
average_pos += data.x
slot_start = int(round(average_pos / len(pcb_data) + stopper_pos[0] - slotf1_pos[0]) / slot_interval) + 1
r = 1
J = len(nozzle_type)
M = 10000
CompOfNozzle = [[0 for _ in range(J)] for _ in range(I)] # Compatibility
component_point = [0 for _ in range(I)]
for _, data in pcb_data.iterrows():
idx = component_data[component_data.part == data.part].index.tolist()[0]
nozzle = component_data.iloc[idx].nz
CompOfNozzle[idx][nozzle_type.index(nozzle)] = 1
component_point[idx] += 1
# objective related
g = mdl.addVars(list_range(K), vtype=GRB.BINARY)
d = mdl.addVars(list_range(K - 1), list_range(H), vtype=GRB.CONTINUOUS)
u = mdl.addVars(list_range(K), vtype=GRB.INTEGER)
d_plus = mdl.addVars(list_range(J), list_range(H), list_range(K - 1), vtype=GRB.CONTINUOUS)
d_minus = mdl.addVars(list_range(J), list_range(H), list_range(K - 1), vtype=GRB.CONTINUOUS)
e = mdl.addVars(list_range(-(H - 1) * r, S), list_range(K), vtype=GRB.BINARY)
f = mdl.addVars(list_range(S), list_range(I), vtype=GRB.BINARY, name='')
x = mdl.addVars(list_range(I), list_range(S), list_range(K), list_range(H), vtype=GRB.BINARY)
n = mdl.addVars(list_range(H), vtype=GRB.CONTINUOUS)
mdl.addConstrs(g[k] <= g[k + 1] for k in range(K - 1))
mdl.addConstrs(
quicksum(x[i, s, k, h] for i in range(I) for s in range(S)) <= g[k] for k in range(K) for h in range(H))
# nozzle no more than 1 for head h and cycle k
mdl.addConstrs(
quicksum(CompOfNozzle[i][j] * x[i, s, k, h] for i in range(I) for s in range(S) for j in range(J)) <= 1 for k in
range(K) for h in range(H))
# nozzle available number constraint
mdl.addConstrs(
quicksum(CompOfNozzle[i][j] * x[i, s, k, h] for i in range(I) for s in range(S) for h in range(H)) <= H for k in
range(K) for j in range(J))
# work completion
mdl.addConstrs(
quicksum(x[i, s, k, h] for s in range(S) for k in range(K) for h in range(H)) == component_point[i] for i in
range(I))
# nozzle change
mdl.addConstrs(quicksum(CompOfNozzle[i][j] * x[i, s, k, h] for i in range(I) for s in range(S)) - quicksum(
CompOfNozzle[i][j] * x[i, s, k + 1, h] for i in range(I) for s in range(S)) == d_plus[j, h, k] - d_minus[
j, h, k] for k in range(K - 1) for j in range(J) for h in range(H))
mdl.addConstrs(
2 * d[k, h] == quicksum(d_plus[j, h, k] for j in range(J)) + quicksum(d_minus[j, h, k] for j in range(J)) for k
in range(K - 1) for h in range(H))
mdl.addConstrs(n[h] == quicksum(d[k, h] for k in range(K - 1)) - 0.5 for h in range(H))
# simultaneous pick
for s in range(-(H - 1) * r, S):
rng = list(range(max(0, -math.floor(s / r)), min(H, math.ceil((S - s) / r))))
for k in range(K):
mdl.addConstr(quicksum(x[i, s + h * r, k, h] for h in rng for i in range(I)) <= M * e[s, k], name='')
mdl.addConstr(quicksum(x[i, s + h * r, k, h] for h in rng for i in range(I)) >= e[s, k], name='')
# pickup movement
mdl.addConstrs(
u[k] >= s1 * e[s1, k] - s2 * e[s2, k] for s1 in range(-(H - 1) * r, S) for s2 in range(-(H - 1) * r, S) for k in
range(K))
# feeder related
mdl.addConstrs(quicksum(f[s, i] for s in range(S)) <= 1 for i in range(I))
mdl.addConstrs(quicksum(f[s, i] for i in range(I)) <= 1 for s in range(S))
mdl.addConstrs(
quicksum(x[i, s, k, h] for h in range(H) for k in range(K)) >= f[s, i] for i in range(I) for s in range(S))
mdl.addConstrs(
quicksum(x[i, s, k, h] for h in range(H) for k in range(K)) <= M * f[s, i] for i in range(I) for s in
range(S))
# objective
t_c, t_n, t_p, t_m = 2, 6, 1, 0.1
mdl.setObjective(t_c * quicksum(g[k] for k in range(K)) + t_n * quicksum(
d[k, h] for h in range(H) for k in range(K - 1)) + t_p * quicksum(
e[s, k] for s in range(-(H - 1) * r, S) for k in range(K)) + t_m * quicksum(u[k] for k in range(K)),
GRB.MINIMIZE)
mdl.optimize()
component_result, cycle_result, feeder_slot_result = [], [], []
for k in range(K):
if abs(g[k].x) < 1e-6:
continue
component_result.append([-1 for _ in range(H)])
feeder_slot_result.append([-1 for _ in range(H)])
cycle_result.append(1)
for h in range(H):
for i in range(I):
for s in range(S):
if abs(x[i, s, k, h].x) > 1e-6:
component_result[-1][h] = i
feeder_slot_result[-1][h] = slot_start + s * interval_ratio - 1
if hinter:
print(component_result)
print(feeder_slot_result)
return component_result, cycle_result, feeder_slot_result
def place_route_model(component_data, pcb_data, component_result, feeder_slot_result, figure=False, hinter=True):
mdl = Model('place_route')
mdl.setParam('Seed', 0)
mdl.setParam('OutputFlag', hinter) # set whether output the debug information
# mdl.setParam('TimeLimit', 20)
component_type = []
for _, data in component_data.iterrows():
component_type.append(data.part)
pos = []
for _, data in pcb_data.iterrows():
pos.append([data.x + stopper_pos[0], data.y + stopper_pos[1]])
I, P, H = len(component_data), len(pcb_data), max_head_index
A = []
for h1 in range(H):
for h2 in range(H):
if h1 == h2:
continue
A.append([h1, h2])
K = len(component_result)
CompOfPoint = [[0 for _ in range(P)] for _ in range(I)]
for row, data in pcb_data.iterrows():
idx = component_type.index(data.part)
CompOfPoint[idx][row] = 1
d_FW, d_PL, d_BW = np.zeros([P, K, H]), np.zeros([P, P, len(A)]), np.zeros([P, K, H])
for k in range(K):
min_slot, max_slot = float('inf'), float('-inf')
for h in range(H):
if feeder_slot_result[k][h] == -1:
continue
min_slot = min(min_slot, feeder_slot_result[k][h] - h * interval_ratio)
max_slot = max(max_slot, feeder_slot_result[k][h] - h * interval_ratio)
for p in range(P):
for h in range(H):
d_FW[p, k, h] = max(
abs(slotf1_pos[0] + (max_slot - 1) * slot_interval - pos[p][0] + h * head_interval),
abs(slotf1_pos[1] - pos[p][1]))
d_BW[p, k, h] = max(
abs(slotf1_pos[0] + (min_slot - 1) * slot_interval - pos[p][0] + h * head_interval),
abs(slotf1_pos[1] - pos[p][1]))
for p in range(P):
for q in range(P):
for idx, arc in enumerate(A):
h1, h2 = arc
d_PL[p, q, idx] = max(abs(pos[p][0] - pos[q][0] - (h1 - h2) * head_interval), abs(pos[p][1] - pos[q][1]))
w = mdl.addVars(list_range(P), list_range(P), list_range(K), list_range(len(A)), vtype=GRB.BINARY)
y = mdl.addVars(list_range(P), list_range(K), list_range(H), vtype=GRB.BINARY)
z = mdl.addVars(list_range(P), list_range(K), list_range(H), vtype=GRB.BINARY)
def A_from(h):
res = []
for idx, arc in enumerate(A):
if arc[0] == h:
res.append(idx)
return res
def A_to(h):
res = []
for idx, arc in enumerate(A):
if arc[1] == h:
res.append(idx)
return res
def A_contain(h):
res = []
for idx, arc in enumerate(A):
if h in arc:
res.append(idx)
return res
# constraints on component assignment type, assigned points cannot conflict with the corresponding component type
for k in range(K):
for h in range(H):
if component_result[k][h] == -1:
# no components on the head
mdl.addConstr(quicksum(w[p, q, k, a] for a in A_contain(h) for q in range(P) for p in range(P)) == 0)
else:
# there are components on the head
mdl.addConstrs((quicksum(w[p, q, k, a] for a in A_from(h) for q in range(P)) + quicksum(
w[q, p, k, a] for a in A_to(h) for q in range(P))) / 2 <= CompOfPoint[component_result[k][h]][p] for
p in range(P))
# each head corresponds to a maximum of one point in each cycle
mdl.addConstrs(
quicksum(w[p, q, k, a] for p in range(P) for q in range(P) for a in A_contain(h)) <= 2 for k in range(K) for h
in range(H))
mdl.addConstrs(
quicksum((y[p, k, h] + z[p, k, h]) for p in range(P)) <= 1 for k in range(K) for h in
range(H))
# task continuity (for the same point the entering head and the leaving head should be same)
mdl.addConstrs(quicksum(w[p, q, k, a] for p in range(P) for a in A_to(h)) + y[q, k, h] == quicksum(
w[q, p, k, a] for p in range(P) for a in A_from(h)) + z[q, k, h] for k in range(K) for h in range(H) for q in
range(P))
mdl.addConstrs(
y[p, k, h] <= quicksum(w[p, q, k, a] for q in range(P) for a in A_from(h)) for h in range(H) for p in
range(P) for k in range(K))
mdl.addConstrs(
z[p, k, h] <= quicksum(w[q, p, k, a] for q in range(P) for a in A_to(h)) for h in range(H) for p in
range(P) for k in range(K))
# one arrival point per cycle
mdl.addConstrs(quicksum(y[p, k, h] for p in range(P) for h in range(H)) == 1 for k in range(K))
# one departure point per cycle
mdl.addConstrs(quicksum(z[p, k, h] for p in range(P) for h in range(H)) == 1 for k in range(K))
# one enter edge per point
mdl.addConstrs(quicksum(y[q, k, h] for h in range(H) for k in range(K)) + quicksum(
w[p, q, k, a] for p in range(P) for a in range(len(A)) for k in range(K)) == 1 for q in range(P))
# one leaving edge per point
mdl.addConstrs(quicksum(z[q, k, h] for h in range(H) for k in range(K)) + quicksum(
w[q, p, k, a] for p in range(P) for a in range(len(A)) for k in range(K)) == 1 for q in range(P))
# subtour eliminate constraint
n = mdl.addVars(list_range(P), vtype=GRB.CONTINUOUS)
m = mdl.addVars(list_range(P), vtype=GRB.CONTINUOUS)
v = mdl.addVars(list_range(P), list_range(P), vtype=GRB.CONTINUOUS)
mdl.addConstrs(
m[p] + quicksum(v[p, q] for q in range(P)) - n[p] - quicksum(v[q, p] for q in range(P)) == 1 for p in range(P))
mdl.addConstrs(
v[p, q] <= (P - K + 1) * quicksum(w[p, q, k, a] for a in range(len(A)) for k in range(K)) for p in range(P) for
q in range(P))
mdl.addConstrs(n[p] <= (P - K + 1) * quicksum(y[p, k, h] for h in range(H) for k in range(K)) for p in range(P))
mdl.addConstrs(m[p] <= (P - K + 1) * quicksum(z[p, k, h] for h in range(H) for k in range(K)) for p in range(P))
# objective
mdl.setObjective(
quicksum(d_FW[p, k, h] * y[p, k, h] for p in range(P) for k in range(K) for h in range(H)) + quicksum(
d_PL[p, q, a] * w[p, q, k, a] for k in range(K) for p in range(P) for q in range(P) for a in
range(len(A))) + quicksum(d_BW[p, k, h] * z[p, k, h] for p in range(P) for k in range(K) for h in range(H)),
GRB.MINIMIZE)
mdl.optimize()
if figure:
for k in range(K):
plt.scatter([p[0] for p in pos[0:8]], [p[1] for p in pos[0:8]], color='red')
plt.scatter([p[0] for p in pos[8:]], [p[1] for p in pos[8:]], color='blue')
for p in range(P):
for q in range(P):
for idx, arc in enumerate(A):
if abs(w[p, q, k, idx].x) > 1e-6:
h1, h2 = arc
plt.plot([pos[p][0] - h1 * head_interval, pos[q][0] - h2 * head_interval],
[pos[p][1], pos[q][1]], linestyle='-.', color='black', linewidth=1)
plt.text(pos[p][0] - h1 * head_interval, pos[p][1], 'H%d' % (h1 + 1), ha='center',
va='bottom', size=10)
for h in range(H):
if abs(y[p, k, h].x) > 1e-6:
plt.plot([pos[p][0] - h * head_interval, 500], [pos[p][1], 100], linestyle='-.', color='black',
linewidth=1)
plt.text(pos[p][0] - h * head_interval, pos[p][1], 'H%d' % (h + 1), ha='center', va='bottom',
size=10)
for h in range(H):
if abs(z[p, k, h].x) > 1e-6:
plt.plot([pos[p][0] - h * head_interval, 900], [pos[p][1], 100], linestyle='-.', color='black',
linewidth=1)
plt.text(pos[p][0] - h * head_interval, pos[p][1], 'H%d' % (h + 1), ha='center', va='bottom',
size=10)
plt.show()
# convert model result into standard form
placement_result, head_sequence = [[-1 for _ in range(H)] for _ in range(K)], [[] for _ in
range(K)]
for k in range(K):
arc_list = []
for p in range(P):
for q in range(P):
for idx, arc in enumerate(A):
if abs(w[p, q, k, idx].x) > 1e-6:
plt.plot([pos[p][0], pos[q][0]], [pos[p][1], pos[q][1]], linestyle='-.', color='black',
linewidth=1)
placement_result[k][arc[0]], placement_result[k][arc[1]] = p, q
arc_list.append(arc)
head, idx = -1, 0
for p in range(P):
for h in range(H):
if abs(y[p, k, h].x) > 1e-6:
head = h
while idx < len(arc_list):
for i, arc in enumerate(arc_list):
if arc[0] == head:
head_sequence[k].append(head)
head = arc[1]
idx += 1
break
head_sequence[k].append(head)
return placement_result, head_sequence
@timer_wrapper
def optimizer_mathmodel(component_data, pcb_data, hinter=True):
component_result, cycle_result, feeder_slot_result = head_task_model(component_data, pcb_data, hinter)
# placement_result, head_sequence = place_route_model(component_data, pcb_data, component_result, feeder_slot_result)
placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
cycle_result)
return component_result, cycle_result, feeder_slot_result, placement_result, head_sequence

View File

@ -1,4 +1,3 @@
import itertools
from base_optimizer.optimizer_common import *

View File

@ -0,0 +1,855 @@
from base_optimizer.optimizer_common import *
def list_range(start, end=None):
return list(range(start)) if end is None else list(range(start, end))
@timer_wrapper
def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, partition=True, initial=False, hinter=True):
# data preparation: convert data to index
component_list, nozzle_list = defaultdict(int), defaultdict(int)
cpidx_2_part, nzidx_2_nozzle, cpidx_2_nzidx = {}, {}, {}
arg_slot_rng = None if len(feeder_data) == 0 else [feeder_data.iloc[0].slot, feeder_data.iloc[-1].slot]
for idx, data in component_data.iterrows():
part, nozzle = data.part, data.nz
cpidx_2_part[idx] = part
nz_key = [key for key, val in nzidx_2_nozzle.items() if val == nozzle]
nz_idx = len(nzidx_2_nozzle) if len(nz_key) == 0 else nz_key[0]
nzidx_2_nozzle[nz_idx] = nozzle
component_list[part] = 0
cpidx_2_nzidx[idx] = nz_idx
for _, data in pcb_data.iterrows():
idx = component_data[component_data.part == data.part].index.tolist()[0]
nozzle = component_data.loc[idx].nz
nozzle_list[nozzle] += 1
component_list[data.part] += 1
part_feederbase = defaultdict(int)
if feeder_data is not None:
for _, data in feeder_data.iterrows():
idx = -1
for idx, part_ in cpidx_2_part.items():
if data.part == part_:
break
assert idx != -1
part_feederbase[idx] = data.slot # part index - slot
if not reduction:
ratio = 2 # 直接导入飞达数据时,采用正常吸杆间隔
else:
if len(component_list) <= 1.5 * max_head_index:
ratio = 1
else:
ratio = 2
I, J = len(cpidx_2_part.keys()), len(nzidx_2_nozzle.keys())
# === determine the hyper-parameter of L ===
# first phase: calculate the number of heads for each type of nozzle
nozzle_heads = defaultdict(int)
for nozzle in nozzle_list.keys():
nozzle_heads[nozzle] = 1
while sum(nozzle_heads.values()) != max_head_index:
max_cycle_nozzle = None
for nozzle, head_num in nozzle_heads.items():
if max_cycle_nozzle is None or nozzle_list[nozzle] / head_num > nozzle_list[max_cycle_nozzle] / \
nozzle_heads[max_cycle_nozzle]:
max_cycle_nozzle = nozzle
assert max_cycle_nozzle is not None
nozzle_heads[max_cycle_nozzle] += 1
nozzle_comp_points = defaultdict(list)
for part, points in component_list.items():
idx = component_data[component_data.part == part].index.tolist()[0]
nozzle = component_data.loc[idx].nz
nozzle_comp_points[nozzle].append([part, points])
level = 1 if len(component_list) == 1 or len(component_list) % max_head_index == 0 else 2
part_assignment, cycle_assignment = [], []
def aux_func(info):
return max(map(lambda points: max([p[1] for p in points]), info))
pre_objbst, pre_changetime = None, None
def terminate_condition(mdl, where):
if where == GRB.Callback.MIP:
objbst = mdl.cbGet(GRB.Callback.MIP_OBJBST)
changetime = mdl.cbGet(GRB.Callback.RUNTIME)
nonlocal pre_objbst, pre_changetime
# condition: value change
if pre_objbst and abs(pre_objbst - objbst) < 1e-3:
if pre_changetime and changetime - pre_changetime > 45:
# pass
mdl.terminate()
else:
pre_changetime = changetime
pre_objbst = objbst
def recursive_assign(assign_points, nozzle_compo_points, cur_level, total_level) -> int:
def func(points):
return map(lambda points: max([p[1] for p in points]), points)
if cur_level > total_level and sum(func(nozzle_compo_points.values())) == 0:
return 0
elif assign_points <= 0 and cur_level == 1:
return -1 # backtrack
elif assign_points <= 0 or cur_level > total_level:
return 1 # fail
nozzle_compo_points_cpy = copy.deepcopy(nozzle_compo_points)
prev_assign = 0
for part in part_assignment[cur_level - 1]:
if part != -1:
prev_assign += 1
head_idx = 0
for nozzle, head in nozzle_heads.items():
while head:
min_idx = -1
for idx, (part, points) in enumerate(nozzle_compo_points_cpy[nozzle]):
if points >= assign_points and (
min_idx == -1 or points < nozzle_compo_points_cpy[nozzle][min_idx][1]):
min_idx = idx
part_assignment[cur_level - 1][head_idx] = -1 if min_idx == -1 else \
nozzle_compo_points_cpy[nozzle][min_idx][0]
if min_idx != -1:
nozzle_compo_points_cpy[nozzle][min_idx][1] -= assign_points
head -= 1
head_idx += 1
cycle_assignment[cur_level - 1] = assign_points
for part in part_assignment[cur_level - 1]:
if part != -1:
prev_assign -= 1
if prev_assign == 0:
res = 1
else:
points = min(len(pcb_data) // max_head_index + 1, aux_func(nozzle_compo_points_cpy.values()))
res = recursive_assign(points, nozzle_compo_points_cpy, cur_level + 1, total_level)
if res == 0:
return 0
elif res == 1:
# All cycles have been completed, but there are still points left to be allocated
return recursive_assign(assign_points - 1, nozzle_compo_points, cur_level, total_level)
# second phase: (greedy) recursive search to assign points for each cycle set and obtain an initial solution
while True:
part_assignment = [[-1 for _ in range(max_head_index)] for _ in range(level)]
cycle_assignment = [-1 for _ in range(level)]
points = min(len(pcb_data) // max_head_index + 1, max(component_list.values()))
if recursive_assign(points, nozzle_comp_points, 1, level) == 0:
break
level += 1
weight_cycle, weight_nz_change, weight_pick = 2, 3, 2
L = len(cycle_assignment) if partition else len(pcb_data)
S = ratio * I if len(feeder_data) == 0 else arg_slot_rng[-1] - arg_slot_rng[0] + 1 # the available feeder num
M = len(pcb_data) # a sufficiently large number (number of placement points)
HC = [[0 for _ in range(J)] for _ in range(I)]
for i in range(I):
for j in range(J):
HC[i][j] = 1 if cpidx_2_nzidx[i] == j else 0
mdl = Model('SMT')
mdl.setParam('Seed', 0)
mdl.setParam('OutputFlag', hinter) # set whether output the debug information
mdl.setParam('TimeLimit', 3600 * 3)
mdl.setParam('PoolSearchMode', 2)
mdl.setParam('PoolSolutions', 3e2)
mdl.setParam('PoolGap', 1e-4)
# mdl.setParam('MIPFocus', 2)
# mdl.setParam("Heuristics", 0.5)
# Use only if other methods, including exploring the tree with the default settings, do not yield a viable solution
# mdl.setParam("ZeroObjNodes", 100)
# === Decision Variables ===
x = mdl.addVars(list_range(I), list_range(S), list_range(max_head_index), list_range(L), vtype=GRB.BINARY, name='x')
y = mdl.addVars(list_range(I), list_range(max_head_index), list_range(L), vtype=GRB.BINARY, name='y')
v = mdl.addVars(list_range(S), list_range(max_head_index), list_range(L), vtype=GRB.BINARY, name='v')
c = mdl.addVars(list_range(I), list_range(max_head_index), list_range(L), vtype=GRB.INTEGER, name='c')
mdl.addConstrs(
c[i, h, l] <= component_list[cpidx_2_part[i]] for i in range(I) for h in range(max_head_index) for l in
range(L))
# todo: the condition for upper limits of feeders exceed 1
f = {}
for i in range(I):
if i not in part_feederbase.keys():
for s in range(S):
f[s, i] = mdl.addVar(vtype=GRB.BINARY, name='f_' + str(s) + '_' + str(i))
else:
for s in range(S):
f[s, i] = 1 if part_feederbase[i] == s + arg_slot_rng[0] else 0
p = mdl.addVars(list_range(-(max_head_index - 1) * ratio, S), list_range(L), vtype=GRB.BINARY, name='p')
z = mdl.addVars(list_range(J), list_range(max_head_index), list_range(L), vtype=GRB.BINARY)
d = mdl.addVars(list_range(L), list_range(max_head_index), vtype=GRB.INTEGER, name='d')
d_plus = mdl.addVars(list_range(J), list_range(max_head_index), list_range(L), vtype=GRB.INTEGER,
name='d_plus')
d_minus = mdl.addVars(list_range(J), list_range(max_head_index), list_range(L), vtype=GRB.INTEGER,
name='d_minus')
max_cycle = math.ceil(len(pcb_data) / max_head_index)
PU = mdl.addVars(list_range(-(max_head_index - 1) * ratio, S), list_range(L), vtype=GRB.INTEGER, name='PU')
WL = mdl.addVars(list_range(L), vtype=GRB.INTEGER, ub=max_cycle, name='WL')
NC = mdl.addVars(list_range(max_head_index), vtype=GRB.INTEGER, name='NC')
part_2_cpidx = defaultdict(int)
for idx, part in cpidx_2_part.items():
part_2_cpidx[part] = idx
if initial:
# initial some variables to speed up the search process
# ensure the priority of the workload assignment
cycle_index = sorted(range(len(cycle_assignment)), key=lambda k: cycle_assignment[k], reverse=True)
part_list = []
for cycle in cycle_index:
cycle_part = part_assignment[cycle]
for part in cycle_part:
if part != -1 and part not in part_list:
part_list.append(part)
slot = 0
for part in part_list:
if feeder_data is not None:
while slot in feeder_data.keys():
slot += 1 # skip assigned feeder slot
if part_2_cpidx[part] in part_feederbase.keys():
continue
part_feederbase[part_2_cpidx[part]] = slot
# f[slot, part_2_cpidx[part]].Start = 1
slot += 1
for idx, cycle in enumerate(cycle_index):
WL[idx].Start = cycle_assignment[cycle]
for h in range(max_head_index):
part = part_assignment[cycle][h]
if part == -1:
continue
i = part_2_cpidx[part]
y[i, h, idx].Start = 1
v[part_feederbase[i], h, idx].Start = 1
# === Objective ===
mdl.setObjective(weight_cycle * quicksum(WL[l] for l in range(L)) + weight_nz_change * quicksum(
NC[h] for h in range(max_head_index)) + weight_pick * quicksum(
PU[s, l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L)))
# === Constraint ===
if not partition:
mdl.addConstrs(WL[l] <= 1 for l in range(L))
# work completion
# mdl.addConstrs(c[i, h, l] == WL[l] * y[i, h, l] for i in range(I) for h in range(max_head_index) for l in range(L))
mdl.addConstrs(
c[i, h, l] <= max_cycle * y[i, h, l] for i in range(I) for h in range(max_head_index) for l in range(L))
mdl.addConstrs(c[i, h, l] <= WL[l] for i in range(I) for h in range(max_head_index) for l in range(L))
mdl.addConstrs(
c[i, h, l] >= WL[l] - max_cycle * (1 - y[i, h, l]) for i in range(I) for h in range(max_head_index) for l in
range(L))
mdl.addConstrs(
quicksum(c[i, h, l] for h in range(max_head_index) for l in range(L)) == component_list[cpidx_2_part[i]] for i
in range(I))
# variable constraint
mdl.addConstrs(quicksum(y[i, h, l] for i in range(I)) <= 1 for h in range(max_head_index) for l in range(L))
# simultaneous pick
for s in range(-(max_head_index - 1) * ratio, S):
rng = list(range(max(0, -math.floor(s / ratio)), min(max_head_index, math.ceil((S - s) / ratio))))
for l in range(L):
mdl.addConstr(quicksum(v[s + h * ratio, h, l] for h in rng) <= max_head_index * p[s, l])
mdl.addConstr(quicksum(v[s + h * ratio, h, l] for h in rng) >= p[s, l])
# mdl.addConstrs(PU[s, l] == p[s, l] * WL[l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L))
mdl.addConstrs(PU[s, l] <= max_cycle * p[s, l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L))
mdl.addConstrs(PU[s, l] <= WL[l] for s in range(-(max_head_index - 1) * ratio, S) for l in range(L))
mdl.addConstrs(
PU[s, l] >= WL[l] - max_cycle * (1 - p[s, l]) for s in range(-(max_head_index - 1) * ratio, S) for l in
range(L))
# nozzle change
mdl.addConstrs(
z[j, h, l] - z[j, h, l + 1] == d_plus[j, h, l] - d_minus[j, h, l] for l in range(L - 1) for j in range(J) for h
in range(max_head_index))
mdl.addConstrs(z[j, h, 0] - z[j, h, L - 1] == d_plus[j, h, L - 1] - d_minus[j, h, L - 1] for j in range(J) for h
in range(max_head_index))
mdl.addConstrs(
2 * d[l, h] == quicksum(d_plus[j, h, l] for j in range(J)) + quicksum(d_minus[j, h, l] for j in range(J)) for l
in range(L - 1) for h in range(max_head_index))
mdl.addConstrs(2 * d[L - 1, h] == quicksum(d_plus[j, h, L - 1] for j in range(J)) + quicksum(
d_minus[j, h, L - 1] for j in range(J)) for h in range(max_head_index))
mdl.addConstrs(NC[h] == quicksum(d[l, h] for l in range(L)) for h in range(max_head_index))
mdl.addConstrs(quicksum(y[i, h, l] for i in range(I) for h in range(max_head_index)) * M >= WL[l] for l in range(L))
# nozzle-component compatibility
mdl.addConstrs(
y[i, h, l] <= quicksum(HC[i][j] * z[j, h, l] for j in range(J)) for i in range(I) for h in range(max_head_index)
for l in range(L))
# available number of feeder
mdl.addConstrs(quicksum(f[s, i] for s in range(S)) <= 1 for i in range(I))
# available number of nozzle
mdl.addConstrs(quicksum(z[j, h, l] for h in range(max_head_index)) <= max_head_index for j in range(J) for l in range(L))
# upper limit for occupation for feeder slot
mdl.addConstrs(quicksum(f[s, i] for i in range(I)) <= 1 for s in range(S))
mdl.addConstrs(
quicksum(v[s, h, l] for s in range(S)) >= quicksum(y[i, h, l] for i in range(I)) for h in range(max_head_index)
for l in range(L))
# others
mdl.addConstrs(quicksum(z[j, h, l] for j in range(J)) <= 1 for h in range(max_head_index) for l in range(L))
# mdl.addConstrs(
# quicksum(x[i, s, h, l] for h in range(max_head_index) for l in range(L)) >= f[s, i] for i in range(I)
# for s in range(S))
# mdl.addConstrs(
# quicksum(x[i, s, h, l] for h in range(max_head_index) for l in range(L)) <= M * f[s, i] for i in
# range(I) for s in range(S))
mdl.addConstrs(
f[s, i] >= x[i, s, h, l] for s in range(S) for i in range(I) for h in range(max_head_index) for l in range(L))
mdl.addConstrs(
quicksum(x[i, s, h, l] for h in range(max_head_index) for l in range(L)) >= f[s, i] for s in
range(S) for i in range(I))
# the constraints to speed up the search process
mdl.addConstrs(
quicksum(x[i, s, h, l] for i in range(I) for s in range(S)) <= 1 for h in range(max_head_index) for l
in range(L))
if reduction:
mdl.addConstrs(WL[l] >= WL[l + 1] for l in range(L - 1))
# mdl.addConstr(quicksum(WL[l] for l in range(L)) <= sum(cycle_assignment))
mdl.addConstr(quicksum(WL[l] for l in range(L)) >= math.ceil(len(pcb_data) / max_head_index))
# mdl.addConstrs(WL[l] >= WL[l + 1] for l in range(L - 1))
# mdl.addConstrs(quicksum(z[j, h, l] for j in range(J) for h in range(max_head_index)) >= quicksum(
# z[j, h, l + 1] for j in range(J) for h in range(max_head_index)) for l in range(L - 1))
#
mdl.addConstrs(y[i, h, l] <= WL[l] for i in range(I) for h in range(max_head_index) for l in range(L))
mdl.addConstrs(v[s, h, l] <= WL[l] for s in range(S) for h in range(max_head_index) for l in range(L))
mdl.addConstrs(
x[i, s, h, l] >= y[i, h, l] + v[s, h, l] - 1 for i in range(I) for s in range(S) for h in range(max_head_index)
for l in range(L))
mdl.addConstrs(
x[i, s, h, l] <= y[i, h, l] for i in range(I) for s in range(S) for h in range(max_head_index)
for l in range(L))
mdl.addConstrs(
x[i, s, h, l] <= v[s, h, l] for i in range(I) for s in range(S) for h in range(max_head_index)
for l in range(L))
# === search process ===
mdl.update()
# mdl.write('mdl.lp')
if hinter:
print('num of constrs: ', str(len(mdl.getConstrs())), ', num of vars: ', str(len(mdl.getVars())))
mdl.optimize(terminate_condition)
# === result generation ===
nozzle_assign, component_assign = [], []
feeder_assign, cycle_assign = [], []
if mdl.Status == GRB.OPTIMAL or mdl.Status == GRB.INTERRUPTED or mdl.Status == GRB.TIME_LIMIT:
# === selection from solution pool ===
component_pos, component_avg_pos = defaultdict(list), defaultdict(list)
for _, data in pcb_data.iterrows():
component_index = component_data[component_data.part == data.part].index.tolist()[0]
component_pos[component_index].append([data.x, data.y])
for i in component_pos.keys():
component_pos[i] = sorted(component_pos[i], key=lambda pos: (pos[0], pos[1]))
component_avg_pos[i] = [sum(map(lambda pos: pos[0], component_pos[i])) / len(component_pos[i]),
sum(map(lambda pos: pos[1], component_pos[i])) / len(component_pos[i])]
min_dist, solution_number = None, -1
for sol_counter in range(mdl.SolCount):
nozzle_assign, component_assign = [], []
feeder_assign, cycle_assign = [], []
mdl.Params.SolutionNumber = sol_counter
pos_counter = defaultdict(int)
dist = 0
cycle_placement, cycle_points = defaultdict(list), defaultdict(list)
for l in range(L):
if abs(WL[l].Xn) <= 1e-4:
continue
cycle_placement[l], cycle_points[l] = [-1] * max_head_index, [None] * max_head_index
for h in range(max_head_index):
for l in range(L):
if abs(WL[l].Xn) <= 1e-4:
continue
pos_list = []
for i in range(I):
if abs(y[i, h, l].Xn) <= 1e-4:
continue
for _ in range(round(WL[l].Xn)):
pos_list.append(component_pos[i][pos_counter[i]])
pos_counter[i] += 1
cycle_placement[l][h] = i
cycle_points[l][h] = [sum(map(lambda pos: pos[0], pos_list)) / len(pos_list),
sum(map(lambda pos: pos[1], pos_list)) / len(pos_list)]
for l in range(L):
if abs(WL[l].Xn) <= 1e-4:
continue
if min_dist is None or dist < min_dist:
min_dist = dist
solution_number = sol_counter
mdl.Params.SolutionNumber = solution_number
# === 更新吸嘴、元件、周期数优化结果 ===
for l in range(L):
nozzle_assign.append([-1 for _ in range(max_head_index)])
component_assign.append([-1 for _ in range(max_head_index)])
feeder_assign.append([-1 for _ in range(max_head_index)])
cycle_assign.append(round(WL[l].Xn))
if abs(WL[l].Xn) <= 1e-4:
continue
for h in range(max_head_index):
for i in range(I):
if abs(y[i, h, l].Xn - 1) < 1e-4:
component_assign[-1][h] = i
for j in range(J):
if HC[i][j]:
nozzle_assign[-1][h] = j
for s in range(S):
if abs(v[s, h, l].Xn - 1) < 1e-4 and component_assign[l][h] != -1:
feeder_assign[l][h] = s
# === 更新供料器分配结果 ==
component_head = defaultdict(int)
for i in range(I):
cycle_num = 0
for l, component_cycle in enumerate(component_assign):
for head, component in enumerate(component_cycle):
if component == i:
component_head[i] += cycle_assign[l] * head
cycle_num += cycle_assign[l]
component_head[i] /= cycle_num # 不同元件的加权拾取贴装头
average_pos = 0
for _, data in pcb_data.iterrows():
average_pos += (data.x - component_head[part_2_cpidx[data.part]] * head_interval)
average_pos /= len(pcb_data) # 实际贴装位置的加权平均
average_slot = 0
for l in range(L):
if abs(WL[l].Xn) <= 1e-4:
continue
min_slot, max_slot = None, None
for head in range(max_head_index):
if abs(WL[l].Xn) <= 1e-4 or feeder_assign[l][head] == -1:
continue
slot = feeder_assign[l][head] - head * ratio
if min_slot is None or slot < min_slot:
min_slot = slot
if max_slot is None or slot > max_slot:
max_slot = slot
average_slot += (max_slot - min_slot) * cycle_assign[l]
average_slot /= sum(cycle_assign)
start_slot = round((average_pos + stopper_pos[0] - slotf1_pos[0]) / slot_interval + average_slot / 2) + 1
for l in range(L):
if abs(WL[l].Xn) <= 1e-4:
continue
for h in range(max_head_index):
for s in range(S):
if abs(v[s, h, l].Xn - 1) < 1e-4 and component_assign[l][h] != -1:
feeder_assign[l][h] = start_slot + s * (2 if ratio == 1 else 1)
if hinter:
print('total cost = {}'.format(mdl.objval))
print('cycle = {}, nozzle change = {}, pick up = {}'.format(quicksum(WL[l].Xn for l in range(L)), quicksum(
NC[h].Xn for h in range(max_head_index)), quicksum(
PU[s, l].Xn for s in range(-(max_head_index - 1) * ratio, S) for l in range(L))))
print('workload: ')
for l in range(L):
print(WL[l].Xn, end=', ')
print('')
print('result')
print('nozzle assignment: ', nozzle_assign)
print('component assignment: ', component_assign)
print('feeder assignment: ', feeder_assign)
print('cycle assignment: ', cycle_assign)
return component_assign, feeder_assign, cycle_assign
def scan_based_placement_route_generation(component_data, pcb_data, component_assign, cycle_assign):
placement_result, head_sequence_result = [], []
mount_point_pos, mount_point_index, mount_point_angle, mount_point_part = [], [], [], []
for i, data in pcb_data.iterrows():
component_index = component_data[component_data.part == data.part].index.tolist()[0]
# 记录贴装点序号索引和对应的位置坐标
mount_point_index.append(i)
mount_point_pos.append([data.x + stopper_pos[0], data.y + stopper_pos[1]])
mount_point_angle.append(data.r)
mount_point_part.append(component_index)
lBoundary, rBoundary = min(mount_point_pos, key=lambda x: x[0])[0], max(mount_point_pos, key=lambda x: x[0])[0]
search_step = max((rBoundary - lBoundary) / max_head_index / 2, 0)
ref_pos_y = min(mount_point_pos, key=lambda x: x[1])[1]
for cycle_index, component_cycle in enumerate(component_assign):
for _ in range(cycle_assign[cycle_index]):
min_dist = None
tmp_assigned_placement, tmp_assigned_head_seq = [], []
tmp_mount_point_pos, tmp_mount_point_index = [], []
for search_dir in range(3): # 不同的搜索方向,贴装头和起始点的选取方法各不相同
if search_dir == 0:
# 从左向右搜索
searchPoints = np.arange(lBoundary, (lBoundary + rBoundary) / 2, search_step)
head_range = list(range(max_head_index))
elif search_dir == 1:
# 从右向左搜索
searchPoints = np.arange(rBoundary + 1e-3, (lBoundary + rBoundary) / 2, -search_step)
head_range = list(range(max_head_index - 1, -1, -1))
else:
# 从中间向两边搜索
searchPoints = np.arange(lBoundary, rBoundary, search_step / 2)
head_range, head_index = [], (max_head_index - 1) // 2
while head_index >= 0:
if 2 * head_index != max_head_index - 1:
head_range.append(max_head_index - 1 - head_index)
head_range.append(head_index)
head_index -= 1
for startPoint in searchPoints:
mount_point_pos_cpy, mount_point_index_cpy = copy.deepcopy(mount_point_pos), copy.deepcopy(
mount_point_index)
mount_point_angle_cpy = copy.deepcopy(mount_point_angle)
assigned_placement = [-1] * max_head_index
assigned_mount_point = [[0, 0]] * max_head_index
assigned_mount_angle = [0] * max_head_index
head_counter, point_index = 0, -1
for head_index in head_range:
if head_counter == 0:
component_index = component_assign[cycle_index][head_index]
if component_index == -1:
continue
min_horizontal_distance = None
for index, mount_index in enumerate(mount_point_index_cpy):
if mount_point_part[mount_index] != component_index:
continue
horizontal_distance = abs(mount_point_pos_cpy[index][0] - startPoint) + 1e-3 * abs(
mount_point_pos_cpy[index][1] - ref_pos_y)
if min_horizontal_distance is None or horizontal_distance < min_horizontal_distance:
min_horizontal_distance = horizontal_distance
point_index = index
else:
point_index = -1
min_cheby_distance = None
next_comp_index = component_assign[cycle_index][head_index]
if assigned_placement[head_index] != -1 or next_comp_index == -1:
continue
for index, mount_index in enumerate(mount_point_index_cpy):
if mount_point_part[mount_index] != next_comp_index:
continue
point_pos = [[mount_point_pos_cpy[index][0] - head_index * head_interval,
mount_point_pos_cpy[index][1]]]
cheby_distance, euler_distance = 0, 0
for next_head in range(max_head_index):
if assigned_placement[next_head] == -1:
continue
point_pos.append(assigned_mount_point[next_head].copy())
point_pos[-1][0] -= next_head * head_interval
point_pos = sorted(point_pos, key=lambda x: x[0])
for mount_seq in range(len(point_pos) - 1):
cheby_distance += max(abs(point_pos[mount_seq][0] - point_pos[mount_seq + 1][0]),
abs(point_pos[mount_seq][1] - point_pos[mount_seq + 1][1]))
euler_distance += math.sqrt(
(point_pos[mount_seq][0] - point_pos[mount_seq + 1][0]) ** 2 + (
point_pos[mount_seq][1] - point_pos[mount_seq + 1][1]) ** 2)
cheby_distance += 0.01 * euler_distance
if min_cheby_distance is None or cheby_distance < min_cheby_distance:
min_cheby_distance, min_euler_distance = cheby_distance, euler_distance
point_index = index
if point_index == -1:
continue
head_counter += 1
assigned_placement[head_index] = mount_point_index_cpy[point_index]
assigned_mount_point[head_index] = mount_point_pos_cpy[point_index].copy()
assigned_mount_angle[head_index] = mount_point_angle_cpy[point_index]
mount_point_index_cpy.pop(point_index)
mount_point_pos_cpy.pop(point_index)
mount_point_angle_cpy.pop(point_index)
dist, head_seq = dynamic_programming_cycle_path(assigned_placement, assigned_mount_point,
assigned_mount_angle)
if min_dist is None or dist < min_dist:
tmp_mount_point_pos, tmp_mount_point_index = mount_point_pos_cpy, mount_point_index_cpy
tmp_assigned_placement, tmp_assigned_head_seq = assigned_placement, head_seq
min_dist = dist
mount_point_pos, mount_point_index = tmp_mount_point_pos, tmp_mount_point_index
placement_result.append(tmp_assigned_placement)
head_sequence_result.append(tmp_assigned_head_seq)
return placement_result, head_sequence_result
# return placement_route_relink_heuristic(component_data, pcb_data, placement_result, head_sequence_result)
def placement_route_relink_heuristic(component_data, pcb_data, placement_result, head_sequence_result, hinter=True):
mount_point_pos, mount_point_angle, mount_point_index, mount_point_part = [], [], [], []
for i, data in pcb_data.iterrows():
component_index = component_data[component_data.part == data.part].index.tolist()[0]
# 记录贴装点序号索引和对应的位置坐标
mount_point_index.append(i)
mount_point_pos.append([data.x + stopper_pos[0], data.y + stopper_pos[1]])
mount_point_angle.append(data.r)
mount_point_part.append(component_index)
cycle_length, cycle_average_pos = [], []
for cycle, placement in enumerate(placement_result):
prev_pos, prev_angle = None, None
cycle_pos_list = []
cycle_length.append(0)
for idx, head in enumerate(head_sequence_result[cycle]):
point_index = placement[head]
if point_index == -1:
continue
pos = [mount_point_pos[point_index][0] - head * head_interval, mount_point_pos[point_index][1]]
angle = mount_point_angle[point_index]
cycle_pos_list.append(pos)
if prev_pos is not None:
if head_sequence_result[cycle][idx - 1] // 2 == head_sequence_result[cycle][idx] // 2: # 同轴
rotary_angle = prev_angle - angle
else:
rotary_angle = 0
cycle_length[-1] += max(axis_moving_time(prev_pos[0] - pos[0], 0),
axis_moving_time(prev_pos[1] - pos[1], 1), head_rotary_time(rotary_angle))
prev_pos, prev_angle = pos, angle
cycle_average_pos.append([sum(map(lambda pos: pos[0], cycle_pos_list)) / len(cycle_pos_list),
sum(map(lambda pos: pos[1], cycle_pos_list)) / len(cycle_pos_list)])
best_placement_result, best_head_sequence_result = copy.deepcopy(placement_result), copy.deepcopy(
head_sequence_result)
best_cycle_length, best_cycle_average_pos = copy.deepcopy(cycle_length), copy.deepcopy(cycle_average_pos)
n_runningtime, n_iteration = 10, 0
start_time = time.time()
with tqdm(total=n_runningtime, leave=False) as pbar:
pbar.set_description('swap heuristic process')
prev_time = start_time
while True:
n_iteration += 1
placement_result, head_sequence_result = copy.deepcopy(best_placement_result), copy.deepcopy(
best_head_sequence_result)
cycle_length = best_cycle_length.copy()
cycle_average_pos = copy.deepcopy(best_cycle_average_pos)
cycle_index = roulette_wheel_selection(cycle_length) # 根据周期加权移动距离随机选择周期
point_dist = [] # 周期内各贴装点距离中心位置的切氏距离
for head in head_sequence_result[cycle_index]:
point_index = placement_result[cycle_index][head]
_delta_x = abs(mount_point_pos[point_index][0] - head * head_interval - cycle_average_pos[cycle_index][0])
_delta_y = abs(mount_point_pos[point_index][1] - cycle_average_pos[cycle_index][1])
point_dist.append(max(_delta_x, _delta_y))
# 随机选择一个异常点
head_index = head_sequence_result[cycle_index][roulette_wheel_selection(point_dist)]
point_index = placement_result[cycle_index][head_index]
# 找距离该异常点最近的周期
min_dist = None
chg_cycle_index = -1
for idx in range(len(cycle_average_pos)):
if idx == cycle_index:
continue
dist_ = 0
component_type_check = False
for head in head_sequence_result[idx]:
dist_ += max(abs(mount_point_pos[placement_result[idx][head]][0] - mount_point_pos[point_index][0]),
abs(mount_point_pos[placement_result[idx][head]][1] - mount_point_pos[point_index][1]))
if mount_point_part[placement_result[idx][head]] == mount_point_part[point_index]:
component_type_check = True
if (min_dist is None or dist_ < min_dist) and component_type_check:
min_dist = dist_
chg_cycle_index = idx
assert chg_cycle_index != -1
chg_head, min_chg_dist = None, None
chg_cycle_point = []
for head in head_sequence_result[chg_cycle_index]:
index = placement_result[chg_cycle_index][head]
chg_cycle_point.append([mount_point_pos[index][0] - head * head_interval, mount_point_pos[index][1]])
for idx, head in enumerate(head_sequence_result[chg_cycle_index]):
chg_cycle_point_cpy = copy.deepcopy(chg_cycle_point)
index = placement_result[chg_cycle_index][head]
if mount_point_part[index] != mount_point_part[point_index]:
continue
chg_cycle_point_cpy[idx][0] = (mount_point_pos[index][0]) - head * head_interval
chg_dist = 0
aver_chg_pos = [sum(map(lambda x: x[0], chg_cycle_point_cpy)) / len(chg_cycle_point_cpy),
sum(map(lambda x: x[1], chg_cycle_point_cpy)) / len(chg_cycle_point_cpy)]
for pos in chg_cycle_point_cpy:
chg_dist += max(abs(aver_chg_pos[0] - pos[0]), abs(aver_chg_pos[1] - pos[1]))
# 更换后各点距离中心更近
if min_chg_dist is None or chg_dist < min_chg_dist:
chg_head = head
min_chg_dist = chg_dist
assert chg_head is not None
# === 第一轮变更周期chg_cycle_index的贴装点重排 ===
chg_placement_res = placement_result[chg_cycle_index].copy()
chg_placement_res[chg_head] = point_index
cycle_point_list = defaultdict(list)
for head, point in enumerate(chg_placement_res):
if point == -1:
continue
cycle_point_list[mount_point_part[point]].append(point)
for key, point_list in cycle_point_list.items():
cycle_point_list[key] = sorted(point_list, key=lambda p: mount_point_pos[p][0])
chg_placement_res, chg_point_assign_res = [], [[0, 0]] * max_head_index
chg_angle_res = [0] * max_head_index
for head, point_index in enumerate(placement_result[chg_cycle_index]):
if point_index == -1:
chg_placement_res.append(-1)
else:
part = mount_point_part[point_index]
chg_placement_res.append(cycle_point_list[part][0])
chg_point_assign_res[head] = mount_point_pos[cycle_point_list[part][0]].copy()
chg_angle_res[head] = mount_point_angle[cycle_point_list[part][0]]
cycle_point_list[part].pop(0)
chg_place_moving, chg_head_res = dynamic_programming_cycle_path(chg_placement_res, chg_point_assign_res, chg_angle_res)
# === 第二轮原始周期cycle_index的贴装点重排 ===
placement_res = placement_result[cycle_index].copy()
placement_res[head_index] = placement_result[chg_cycle_index][chg_head]
for point in placement_res:
if point == -1:
continue
cycle_point_list[mount_point_part[point]].append(point)
for key, point_list in cycle_point_list.items():
cycle_point_list[key] = sorted(point_list, key=lambda p: mount_point_pos[p][0])
placement_res, point_assign_res = [], [[0, 0]] * max_head_index
angle_assign_res = [0] * max_head_index
for head, point_index in enumerate(placement_result[cycle_index]):
if point_index == -1:
placement_res.append(-1)
else:
part = mount_point_part[point_index]
placement_res.append(cycle_point_list[part][0])
point_assign_res[head] = mount_point_pos[cycle_point_list[part][0]].copy()
angle_assign_res[head] = mount_point_angle[cycle_point_list[part][0]]
cycle_point_list[part].pop(0)
place_moving, place_head_res = dynamic_programming_cycle_path(placement_res, point_assign_res, angle_assign_res)
# 更新贴装顺序分配结果
placement_result[cycle_index], head_sequence_result[cycle_index] = placement_res, place_head_res
placement_result[chg_cycle_index], head_sequence_result[chg_cycle_index] = chg_placement_res, chg_head_res
# 更新移动路径
cycle_length[cycle_index], cycle_length[chg_cycle_index] = place_moving, chg_place_moving
# 更新平均坐标和最大偏离点索引
point_list, point_index_list = [], []
for head in head_sequence_result[cycle_index]:
point_index_list.append(placement_result[cycle_index][head])
point_pos = mount_point_pos[point_index_list[-1]].copy()
point_pos[0] -= head * head_interval
point_list.append(point_pos)
cycle_average_pos[cycle_index] = [sum(map(lambda x: x[0], point_list)) / len(point_list),
sum(map(lambda x: x[1], point_list)) / len(point_list)]
point_list, point_index_list = [], []
for head in head_sequence_result[chg_cycle_index]:
point_index_list.append(placement_result[chg_cycle_index][head])
point_pos = mount_point_pos[point_index_list[-1]].copy()
point_pos[0] -= head * head_interval
point_list.append(point_pos)
cycle_average_pos[chg_cycle_index] = [sum(map(lambda x: x[0], point_list)) / len(point_list),
sum(map(lambda x: x[1], point_list)) / len(point_list)]
if sum(cycle_length) < sum(best_cycle_length):
best_cycle_length = cycle_length.copy()
best_cycle_average_pos = copy.deepcopy(cycle_average_pos)
best_placement_result, best_head_sequence_result = copy.deepcopy(placement_result), copy.deepcopy(
head_sequence_result)
cur_time = time.time()
if cur_time - start_time > n_runningtime:
break
pbar.update(cur_time - prev_time)
prev_time = cur_time
# print("number of iteration: ", n_iteration)
return best_placement_result, best_head_sequence_result

View File

@ -0,0 +1,635 @@
from base_optimizer.optimizer_common import *
def convert_pcbdata_to_result(pcb_data, component_data):
component_result, cycle_result, feeder_slot_result = [], [], []
placement_result, head_sequence_result = [], []
assigned_part = [-1 for _ in range(max_head_index)]
assigned_slot = [-1 for _ in range(max_head_index)]
assigned_point = [-1 for _ in range(max_head_index)]
assigned_sequence = []
point_num = len(pcb_data) # total mount points num
for point_cnt in range(point_num + 1):
cycle_start = 1 if point_cnt == point_num else pcb_data.loc[point_cnt, 'cs']
if (cycle_start and point_cnt != 0) or -1 not in assigned_part:
if len(component_result) != 0 and component_result[-1] == assigned_part:
cycle_result[-1] += 1
else:
component_result.append(assigned_part)
feeder_slot_result.append(assigned_slot)
cycle_result.append(1)
# assigned_sequence = list(reversed(assigned_sequence)) # Samsung拾取顺序相反
placement_result.append(assigned_point)
head_sequence_result.append(assigned_sequence)
assigned_part = [-1 for _ in range(max_head_index)]
assigned_slot = [-1 for _ in range(max_head_index)]
assigned_point = [-1 for _ in range(max_head_index)]
assigned_sequence = []
if point_cnt == point_num:
break
slot = pcb_data.loc[point_cnt, 'fdr'].split(' ')[0]
if slot == 'A':
slot, part = 0, pcb_data.loc[point_cnt].part
else:
slot, part = int(slot[1:]), pcb_data.loc[point_cnt].fdr.split(' ', 1)[1]
head = pcb_data.loc[point_cnt].hd - 1
part_index = component_data[component_data.part == part].index.tolist()[0]
assigned_part[head] = part_index
assigned_slot[head] = slot
assigned_point[head] = point_cnt
assigned_sequence.append(head)
return component_result, cycle_result, feeder_slot_result, placement_result, head_sequence_result
# 绘制各周期从供料器周期拾取的元件位置
def pickup_cycle_schematic(feeder_slot_result, cycle_result):
plt.rcParams['font.sans-serif'] = ['KaiTi'] # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
# data
bar_width = .7
feeder_part = np.zeros(int(max_slot_index / 2), dtype=np.int)
for cycle in range(len(feeder_slot_result)):
label_str = '周期' + str(cycle + 1)
cur_feeder_part = np.zeros(int(max_slot_index / 2), dtype=np.int)
for slot in feeder_slot_result[cycle]:
if slot > 0:
cur_feeder_part[slot] += cycle_result[cycle]
plt.bar(np.arange(max_slot_index / 2), cur_feeder_part, bar_width, edgecolor='black', bottom=feeder_part,
label=label_str)
for slot in feeder_slot_result[cycle]:
if slot > 0:
feeder_part[slot] += cycle_result[cycle]
plt.legend()
plt.show()
def placement_route_schematic(pcb_data, component_result, cycle_result, feeder_slot_result, placement_result,
head_sequence, cycle=-1):
plt.figure('cycle {}'.format(cycle + 1))
pos_x, pos_y = [], []
for i in range(len(pcb_data)):
pos_x.append(pcb_data.loc[i]['x'] + stopper_pos[0])
pos_y.append(pcb_data.loc[i]['y'] + stopper_pos[1])
# plt.text(pcb_data.loc[i]['x'], pcb_data.loc[i]['y'] + 0.1, '%d' % i, ha='center', va = 'bottom', size = 8)
mount_pos = []
for head in head_sequence[cycle]:
index = placement_result[cycle][head]
plt.text(pos_x[index], pos_y[index] + 0.1, 'HD%d' % (head + 1), ha='center', va='bottom', size=10)
plt.plot([pos_x[index], pos_x[index] - head * head_interval], [pos_y[index], pos_y[index]], linestyle='-.',
color='black', linewidth=1)
mount_pos.append([pos_x[index] - head * head_interval, pos_y[index]])
plt.plot(mount_pos[-1][0], mount_pos[-1][1], marker='^', color='red', markerfacecolor='white')
# plt.text(mount_pos[-1][0], mount_pos[-1][1], '%d' % index, size=8)
# 绘制贴装路径
for i in range(len(mount_pos) - 1):
plt.plot([mount_pos[i][0], mount_pos[i + 1][0]], [mount_pos[i][1], mount_pos[i + 1][1]], color='blue',
linewidth=1)
draw_x, draw_y = [], []
for c in range(cycle, len(placement_result)):
for h in range(max_head_index):
i = placement_result[c][h]
if i == -1:
continue
draw_x.append(pcb_data.loc[i]['x'] + stopper_pos[0])
draw_y.append(pcb_data.loc[i]['y'] + stopper_pos[1])
# plt.text(draw_x[-1], draw_y[-1] - 5, '%d' % i, ha='center', va='bottom', size=10)
plt.scatter(draw_x, draw_y, s=8)
# 绘制供料器位置布局
for slot in range(max_slot_index // 2):
plt.scatter(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1], marker='x', s=12, color='green')
plt.text(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1] - 50, slot + 1, ha='center', va='bottom', size=8)
feeder_part, feeder_counter = {}, {}
placement_cycle = 0
for cycle_, components in enumerate(component_result):
for head, component in enumerate(components):
if component == -1:
continue
placement = placement_result[placement_cycle][head]
slot = feeder_slot_result[cycle_][head]
feeder_part[slot] = pcb_data.loc[placement]['part']
if slot not in feeder_counter.keys():
feeder_counter[slot] = 0
feeder_counter[slot] += cycle_result[cycle_]
placement_cycle += cycle_result[cycle_]
for slot, part in feeder_part.items():
plt.text(slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1] + 15,
part + ': ' + str(feeder_counter[slot]), ha='center', size=7, rotation=90)
plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
[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)
# 绘制拾取路径
pick_slot = []
cycle_group = 0
while sum(cycle_result[0: cycle_group + 1]) < cycle:
cycle_group += 1
for head, slot in enumerate(feeder_slot_result[cycle_group]):
if slot == -1:
continue
pick_slot.append(slot - head * interval_ratio)
pick_slot = list(set(pick_slot))
pick_slot = sorted(pick_slot)
next_cycle_group = 0
next_pick_slot = max_slot_index
while sum(cycle_result[0: next_cycle_group + 1]) < cycle + 1:
next_cycle_group += 1
if next_cycle_group < len(feeder_slot_result):
for head, slot in enumerate(feeder_slot_result[cycle_group]):
if slot == -1:
continue
next_pick_slot = min(next_pick_slot, slot - head * interval_ratio)
# 前往PCB贴装
plt.plot([mount_pos[-1][0], slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)], [mount_pos[-1][1], slotf1_pos[1]],
color='blue', linewidth=1)
# 基座移动路径
plt.plot([slotf1_pos[0] + slot_interval * (pick_slot[0] - 1), slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)],
[slotf1_pos[1], slotf1_pos[1]], color='blue', linewidth=1)
# 返回基座取料
plt.plot([mount_pos[0][0], slotf1_pos[0] + slot_interval * (next_pick_slot - 1)], [mount_pos[0][1], slotf1_pos[1]],
color='blue', linewidth=1)
plt.show()
def save_placement_route_figure(file_name, pcb_data, component_result, cycle_result, feeder_slot_result,
placement_result, head_sequence):
path = 'result/' + file_name[:file_name.find('.')]
if not os.path.exists(path):
os.mkdir(path)
pos_x, pos_y = [], []
for i in range(len(pcb_data)):
pos_x.append(pcb_data.loc[i]['x'] + stopper_pos[0])
pos_y.append(pcb_data.loc[i]['y'] + stopper_pos[1])
# plt.text(pcb_data.loc[i]['x'], pcb_data.loc[i]['y'] + 0.1, '%d' % i, ha='center', va = 'bottom', size = 8)
with tqdm(total=100) as pbar:
pbar.set_description('save figure')
for cycle in range(len(placement_result)):
plt.figure(cycle)
mount_pos = []
for head in head_sequence[cycle]:
index = placement_result[cycle][head]
plt.text(pos_x[index], pos_y[index] + 0.1, 'HD%d' % (head + 1), ha='center', va='bottom', size=10)
plt.plot([pos_x[index], pos_x[index] - head * head_interval], [pos_y[index], pos_y[index]],
linestyle='-.', color='black', linewidth=1)
mount_pos.append([pos_x[index] - head * head_interval, pos_y[index]])
plt.plot(mount_pos[-1][0], mount_pos[-1][1], marker='^', color='red', markerfacecolor='white')
# 绘制贴装路径
for i in range(len(mount_pos) - 1):
plt.plot([mount_pos[i][0], mount_pos[i + 1][0]], [mount_pos[i][1], mount_pos[i + 1][1]], color='blue',
linewidth=1)
draw_x, draw_y = [], []
for c in range(cycle, len(placement_result)):
for h in range(max_head_index):
i = placement_result[c][h]
if i == -1:
continue
draw_x.append(pcb_data.loc[i]['x'] + stopper_pos[0])
draw_y.append(pcb_data.loc[i]['y'] + stopper_pos[1])
# plt.text(draw_x[-1], draw_y[-1] - 5, '%d' % i, ha='center', va='bottom', size=10)
plt.scatter(pos_x, pos_y, s=8)
# 绘制供料器位置布局
for slot in range(max_slot_index // 2):
plt.scatter(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1], marker='x', s=12, color='green')
plt.text(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1] - 50, slot + 1, ha='center', va='bottom', size=8)
feeder_part, feeder_counter = {}, {}
placement_cycle = 0
for cycle_, components in enumerate(component_result):
for head, component in enumerate(components):
if component == -1:
continue
placement = placement_result[placement_cycle][head]
slot = feeder_slot_result[cycle_][head]
feeder_part[slot] = pcb_data.loc[placement]['part']
if slot not in feeder_counter.keys():
feeder_counter[slot] = 0
feeder_counter[slot] += cycle_result[cycle_]
placement_cycle += cycle_result[cycle_]
for slot, part in feeder_part.items():
plt.text(slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1] + 15,
part + ': ' + str(feeder_counter[slot]), ha='center', size=7, rotation=90)
plt.plot(
[slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
[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)
# 绘制拾取路径
pick_slot = []
cycle_group = 0
while sum(cycle_result[0: cycle_group + 1]) < cycle:
cycle_group += 1
for head, slot in enumerate(feeder_slot_result[cycle_group]):
if slot == -1:
continue
pick_slot.append(slot - head * interval_ratio)
pick_slot = list(set(pick_slot))
pick_slot = sorted(pick_slot)
plt.plot([mount_pos[0][0], slotf1_pos[0] + slot_interval * (pick_slot[0] - 1)],
[mount_pos[0][1], slotf1_pos[1]], color='blue', linewidth=1)
plt.plot([mount_pos[-1][0], slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)],
[mount_pos[-1][1], slotf1_pos[1]], color='blue', linewidth=1)
plt.plot([slotf1_pos[0] + slot_interval * (pick_slot[0] - 1),
slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)], [slotf1_pos[1], slotf1_pos[1]],
color='blue', linewidth=1)
plt.savefig(path + '/cycle_{}'.format(cycle + 1))
plt.close(cycle)
pbar.update(100 / len(placement_result))
def output_optimize_result(file_name, method, component_data, pcb_data, feeder_data, component_result, cycle_result,
feeder_slot_result, placement_result, head_sequence):
assert len(component_result) == len(feeder_slot_result)
if feeder_data is None:
warning_info = 'file: ' + file_name + ' optimize result is not existed!'
warnings.warn(warning_info, UserWarning)
return
output_data = pcb_data.copy(deep=True)
# 默认ANC参数
anc_list = defaultdict(list)
anc_list['CN065'] = list(range(14, 25, 2))
anc_list['CN220'] = list(range(15, 26, 2))
anc_list['CN140'] = list(range(26, 37, 2))
anc_list['CN400'] = list(range(27, 38, 2))
# 更新供料器组参数
for cycle_set in range(len(cycle_result)):
for head, component in enumerate(component_result[cycle_set]):
if component == -1:
continue
if feeder_data[feeder_data['slot'] == feeder_slot_result[cycle_set][head]].index.empty:
part = component_data.loc[component]['part']
feeder_data.loc[len(feeder_data.index)] = [feeder_slot_result[cycle_set][head], part, 0]
feeder_data.sort_values('slot', inplace=True, ascending=True, ignore_index=True)
placement_index = []
assigned_nozzle, assigned_anc_hole = ['' for _ in range(max_head_index)], [-1 for _ in range(max_head_index)]
for cycle_set in range(len(cycle_result)):
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
for cycle in range(floor_cycle, ceil_cycle):
cycle_start = True
cycle_nozzle = ['' for _ in range(max_head_index)]
head_indexes = [-1 for _ in range(max_head_index)]
for head in head_sequence[cycle]:
index_ = placement_result[cycle][head]
if index_ == -1:
continue
head_indexes[head] = index_
placement_index.append(index_)
output_data.loc[index_, 'cs'] = 1 if cycle_start else 0
output_data.loc[index_, 'cy'] = cycle + 1
output_data.loc[index_, 'hd'] = head + 1
cycle_start = False
# 供料器信息
slot = feeder_slot_result[cycle_set][head]
fdr = 'F' + str(slot) if slot < max_slot_index // 2 else 'R' + str(slot - max_slot_index // 2)
feeder_index = feeder_data[feeder_data['slot'] == slot].index.tolist()[0]
output_data.loc[index_, 'fdr'] = fdr + ' ' + feeder_data.loc[feeder_index, 'part']
# ANC信息
cycle_nozzle[head] = component_data.loc[component_result[cycle_set][head], 'nz']
for head in range(max_head_index):
nozzle = cycle_nozzle[head]
if nozzle == '':
continue
if nozzle != assigned_nozzle[head]:
# 已分配有吸嘴,卸载原吸嘴
if assigned_nozzle[head] != '':
anc_list[assigned_nozzle[head]].append(assigned_anc_hole[head])
anc_list[assigned_nozzle[head]] = sorted(anc_list[assigned_nozzle[head]])
# 安装新的吸嘴
assigned_nozzle[head] = nozzle
try:
assigned_anc_hole[head] = anc_list[nozzle][0]
except IndexError:
info = 'the number of nozzle for [' + nozzle + '] exceeds the quantity limit'
raise IndexError(info)
anc_list[nozzle].pop(0)
output_data.loc[head_indexes[head], 'nz'] = '1-' + str(assigned_anc_hole[head]) + ' ' + nozzle
output_data = output_data.reindex(placement_index)
output_data = output_data.reset_index(drop=True)
if 'desc' not in output_data.columns:
column_index = int(np.where(output_data.columns.values.reshape(-1) == 'part')[0][0])
output_data.insert(loc=column_index + 1, column='desc', value='')
if not os.path.exists('result/' + method):
os.makedirs('result/' + method)
file_name = method + '/' + file_name.split('.')[0] + '.xlsx'
output_data.to_excel('result/' + file_name, sheet_name='tb1', float_format='%.3f', na_rep='')
def optimization_assign_result(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
nozzle_hinter=False, component_hinter=False, feeder_hinter=False):
if nozzle_hinter:
columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']
nozzle_assign = pd.DataFrame(columns=columns)
for cycle, components in enumerate(component_result):
nozzle_assign_row = len(nozzle_assign)
nozzle_assign.loc[nozzle_assign_row, 'cycle'] = cycle_result[cycle]
for head in range(max_head_index):
index = component_result[cycle][head]
if index == -1:
nozzle_assign.loc[nozzle_assign_row, 'H{}'.format(head + 1)] = ''
else:
nozzle = component_data.loc[index]['nz']
nozzle_assign.loc[nozzle_assign_row, 'H{}'.format(head + 1)] = nozzle
for head in range(max_head_index):
if nozzle_assign_row == 0 or nozzle_assign.loc[nozzle_assign_row - 1, 'H{}'.format(head + 1)] != \
nozzle_assign.loc[nozzle_assign_row, 'H{}'.format(head + 1)]:
break
else:
nozzle_assign.loc[nozzle_assign_row - 1, 'cycle'] += nozzle_assign.loc[nozzle_assign_row, 'cycle']
nozzle_assign.drop([len(nozzle_assign) - 1], inplace=True)
print(nozzle_assign)
print('')
if component_hinter:
columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']
component_assign = pd.DataFrame(columns=columns)
for cycle, components in enumerate(component_result):
component_assign.loc[cycle, 'cycle'] = cycle_result[cycle]
for head in range(max_head_index):
index = component_result[cycle][head]
if index == -1:
component_assign.loc[cycle, 'H{}'.format(head + 1)] = ''
else:
part = component_data.loc[index]['part']
component_assign.loc[cycle, 'H{}'.format(head + 1)] = part
print(component_assign)
print('')
if feeder_hinter:
columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']
feedr_assign = pd.DataFrame(columns=columns)
for cycle, components in enumerate(feeder_slot_result):
feedr_assign.loc[cycle, 'cycle'] = cycle_result[cycle]
for head in range(max_head_index):
slot = feeder_slot_result[cycle][head]
if slot == -1:
feedr_assign.loc[cycle, 'H{}'.format(head + 1)] = 'A'
else:
feedr_assign.loc[cycle, 'H{}'.format(head + 1)] = 'F{}'.format(
slot) if slot <= max_slot_index // 2 else 'R{}'.format(slot - max_head_index)
print(feedr_assign)
print('')
def placement_time_estimate(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
placement_result, head_sequence, hinter=True) -> float:
# === 校验 ===
total_points = 0
for cycle, components in enumerate(component_result):
for head, component in enumerate(components):
if component == -1:
continue
total_points += cycle_result[cycle]
if total_points != len(pcb_data):
warning_info = 'the number of placement points is not match with the PCB data. '
warnings.warn(warning_info, UserWarning)
return 0.
for placements in placement_result:
for placement in placements:
if placement == -1:
continue
total_points -= 1
if total_points != 0:
warnings.warn(
'the optimization result of component assignment result and placement result are not consistent. ',
UserWarning)
return 0.
feeder_arrangement = defaultdict(set)
for cycle, feeder_slots in enumerate(feeder_slot_result):
for head, slot in enumerate(feeder_slots):
if slot == -1:
continue
feeder_arrangement[component_result[cycle][head]].add(slot)
for part, data in component_data.iterrows():
if part in feeder_arrangement.keys() and data['feeder-limit'] < len(feeder_arrangement[part]):
info = 'the number of arranged feeder of [' + data['part'] + '] exceeds the quantity limit'
warnings.warn(info, UserWarning)
return 0.
total_pickup_time, total_round_time, total_place_time = .0, .0, 0 # 拾取用时、往返用时、贴装用时
total_operation_time = .0 # 操作用时
total_nozzle_change_counter = 0 # 总吸嘴更换次数
total_pick_counter = 0 # 总拾取次数
total_mount_distance, total_pick_distance = .0, .0 # 贴装距离、拾取距离
total_distance = 0 # 总移动距离
cur_pos, next_pos = anc_marker_pos, [0, 0] # 贴装头当前位置
# 初始化首个周期的吸嘴装配信息
nozzle_assigned = ['Empty' for _ in range(max_head_index)]
for head in range(max_head_index):
for cycle in range(len(component_result)):
idx = component_result[cycle][head]
if idx == -1:
continue
else:
nozzle_assigned[head] = component_data.loc[idx]['nz']
break
for cycle_set, _ in enumerate(component_result):
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
for cycle in range(floor_cycle, ceil_cycle):
pick_slot, mount_pos, mount_angle = [], [], []
nozzle_pick_counter, nozzle_put_counter = 0, 0 # 吸嘴更换次数统计(拾取/放置分别算一次)
for head in range(max_head_index):
if feeder_slot_result[cycle_set][head] != -1:
pick_slot.append(feeder_slot_result[cycle_set][head] - interval_ratio * head)
if component_result[cycle_set][head] == -1:
continue
nozzle = component_data.loc[component_result[cycle_set][head]]['nz']
if nozzle != nozzle_assigned[head]:
if nozzle_assigned[head] != 'Empty':
nozzle_put_counter += 1
nozzle_pick_counter += 1
nozzle_assigned[head] = nozzle
# ANC处进行吸嘴更换
if nozzle_pick_counter + nozzle_put_counter > 0:
next_pos = anc_marker_pos
move_time = max(axis_moving_time(cur_pos[0] - next_pos[0], 0),
axis_moving_time(cur_pos[1] - next_pos[1], 1))
total_round_time += move_time
total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
cur_pos = next_pos
pick_slot = list(set(pick_slot))
pick_slot = sorted(pick_slot, reverse=True)
# 拾取路径(自右向左)
for idx, slot in enumerate(pick_slot):
if slot < max_slot_index // 2:
next_pos = [slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1]]
else:
next_pos = [slotr1_pos[0] - slot_interval * (max_slot_index - slot - 1), slotr1_pos[1]]
total_operation_time += t_pick
total_pick_counter += 1
move_time = max(axis_moving_time(cur_pos[0] - next_pos[0], 0),
axis_moving_time(cur_pos[1] - next_pos[1], 1))
if idx == 0:
total_round_time += move_time
else:
total_pickup_time += move_time
total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
if slot != pick_slot[0]:
total_pick_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
cur_pos = next_pos
# 固定相机检测
for head in range(max_head_index):
if component_result[cycle_set][head] == -1:
continue
camera = component_data.loc[component_result[cycle_set][head]]['camera']
if camera == '固定相机':
next_pos = [fix_camera_pos[0] - head * head_interval, fix_camera_pos[1]]
move_time = max(axis_moving_time(cur_pos[0] - next_pos[0], 0),
axis_moving_time(cur_pos[1] - next_pos[1], 1))
total_round_time += move_time
total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1]))
total_operation_time += t_fix_camera_check
cur_pos = next_pos
# 贴装路径
for head in head_sequence[cycle]:
index = placement_result[cycle][head]
if index == -1:
continue
mount_pos.append([pcb_data.iloc[index]['x'] - head * head_interval + stopper_pos[0],
pcb_data.iloc[index]['y'] + stopper_pos[1]])
mount_angle.append(pcb_data.iloc[index]['r'])
# 单独计算贴装路径
for cntPoints in range(len(mount_pos) - 1):
total_mount_distance += max(abs(mount_pos[cntPoints][0] - mount_pos[cntPoints + 1][0]),
abs(mount_pos[cntPoints][1] - mount_pos[cntPoints + 1][1]))
# 考虑R轴预旋转补偿同轴角度转动带来的额外贴装用时
total_operation_time += head_rotary_time(mount_angle[0]) # 补偿角度转动带来的额外贴装用时
total_operation_time += t_nozzle_put * nozzle_put_counter + t_nozzle_pick * nozzle_pick_counter
for idx, pos in enumerate(mount_pos):
total_operation_time += t_place
move_time = max(axis_moving_time(cur_pos[0] - pos[0], 0), axis_moving_time(cur_pos[1] - pos[1], 1))
if idx == 0:
total_round_time += move_time
else:
total_place_time += move_time
total_distance += max(abs(cur_pos[0] - pos[0]), abs(cur_pos[1] - pos[1]))
cur_pos = pos
total_nozzle_change_counter += nozzle_put_counter + nozzle_pick_counter
total_time = total_pickup_time + total_round_time + total_place_time + total_operation_time
minutes, seconds = int(total_time // 60), int(total_time) % 60
millisecond = int((total_time - minutes * 60 - seconds) * 60)
if hinter:
optimization_assign_result(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
nozzle_hinter=False, component_hinter=False, feeder_hinter=False)
print('-Cycle counter: {}'.format(sum(cycle_result)))
print('-Nozzle change counter: {}'.format(total_nozzle_change_counter // 2))
print('-Pick operation counter: {}'.format(total_pick_counter))
print('-Expected mounting tour length: {} mm'.format(total_mount_distance))
print('-Expected picking tour length: {} mm'.format(total_pick_distance))
print('-Expected total tour length: {} mm'.format(total_distance))
print('-Expected total moving time: {} s with pick: {}, round: {}, place = {}'.format(
total_pickup_time + total_round_time + total_place_time, total_pickup_time, total_round_time,
total_place_time))
print('-Expected total operation time: {} s'.format(total_operation_time))
if minutes > 0:
print('-Mounting time estimation: {:d} min {} s {:2d} ms ({:.3f}s)'.format(minutes, seconds, millisecond,
total_time))
else:
print('-Mounting time estimation: {} s {:2d} ms ({:.3f}s)'.format(seconds, millisecond, total_time))
return total_time