调整工程架构,增补了几种算法,初步添加神经网路训练拟合代码
This commit is contained in:
@ -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))
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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):
|
||||
|
96
base_optimizer/optimizer_interface.py
Normal file
96
base_optimizer/optimizer_interface.py
Normal 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
|
354
base_optimizer/optimizer_mathmodel.py
Normal file
354
base_optimizer/optimizer_mathmodel.py
Normal 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
|
@ -1,4 +1,3 @@
|
||||
import itertools
|
||||
from base_optimizer.optimizer_common import *
|
||||
|
||||
|
||||
|
855
base_optimizer/optimizer_twophase.py
Normal file
855
base_optimizer/optimizer_twophase.py
Normal 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
|
635
base_optimizer/result_analysis.py
Normal file
635
base_optimizer/result_analysis.py
Normal 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
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user