增加了HSMO整线优化方法,读取数据增加了供料器部分
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,3 +9,4 @@ Scripts/
|
||||
*.pkl
|
||||
*.pth
|
||||
*.m
|
||||
opt/
|
@@ -22,6 +22,7 @@ import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib
|
||||
import traceback
|
||||
import openpyxl
|
||||
|
||||
matplotlib.use('TkAgg')
|
||||
|
||||
@@ -67,7 +68,16 @@ t_fix_camera_check = 0.12 # 固定相机检测时间
|
||||
T_pp, T_tr, T_nc, T_pl = 2, 5, 25, 0
|
||||
|
||||
# 时间参数 (数据拟合获得)
|
||||
Fit_cy, Fit_nz, Fit_pu, Fit_pl, Fit_mv = 0.326, 0.870, 0.159, 0.041, 0.001
|
||||
# Fit_cy, Fit_nz, Fit_pu, Fit_pl, Fit_mv = 0.326, 0.870, 0.159, 0.041, 0.030
|
||||
Fit_cy, Fit_nz, Fit_pu, Fit_pl, Fit_mv = 0.326, 0.870, 0.159, 0.041, 0.035
|
||||
|
||||
|
||||
class Point:
|
||||
def __init__(self, _x, _y, _r=0, _h=None):
|
||||
self.x = _x
|
||||
self.y = _y
|
||||
self.r = _r
|
||||
self.h = _h
|
||||
|
||||
|
||||
class OptResult:
|
||||
@@ -108,9 +118,12 @@ class OptInfo:
|
||||
print(f'-Pick time: {self.pickup_time: .3f}, Pick distance: {self.pickup_distance: .3f}')
|
||||
print(f'-Place time: {self.place_time: .3f}, Place distance: {self.place_distance: .3f}')
|
||||
print(
|
||||
f'-Round time: {self.total_time - self.place_time - self.place_time: .3f}, Round distance: '
|
||||
f'-Round time: {self.total_time - self.operation_time - self.pickup_time - self.place_time: .3f}, Round distance: '
|
||||
f'{self.total_distance - self.pickup_distance - self.place_distance: .3f}')
|
||||
|
||||
print(f'-Round & place time per cycle: {(self.total_time - self.pickup_time - self.operation_time) * 1000.0 / (self.cycle_counter + 1e-10): .3f}, ', end='')
|
||||
print(f'-Round & place distance per cycle: {(self.total_distance - self.pickup_distance) / (self.cycle_counter + 1e-10): .3f}')
|
||||
|
||||
minutes, seconds = int(self.total_time // 60), int(self.total_time) % 60
|
||||
millisecond = int((self.total_time - minutes * 60 - seconds) * 60)
|
||||
|
||||
@@ -856,3 +869,10 @@ def random_division(num, div):
|
||||
|
||||
def list_range(start, end=None):
|
||||
return list(range(start)) if end is None else list(range(start, end))
|
||||
|
||||
|
||||
def kth_indices_partition(num, kth):
|
||||
if len(num) > kth:
|
||||
return np.argpartition(num, kth)
|
||||
else:
|
||||
return np.array(range(len(num)))
|
@@ -15,26 +15,22 @@ def base_optimizer(machine_index, pcb_data, component_data, feeder_data, params,
|
||||
|
||||
if params.machine_optimizer == 'cell-division': # 基于元胞分裂的遗传算法
|
||||
component_result, cycle_result, feeder_slot_result = optimizer_celldivision(pcb_data, component_data)
|
||||
placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
|
||||
cycle_result, feeder_slot_result)
|
||||
placement_result, head_sequence = place_allocate_sequence_route_generation(component_data, pcb_data,
|
||||
component_result, cycle_result,
|
||||
feeder_slot_result)
|
||||
elif params.machine_optimizer == 'feeder-priority': # 基于基座扫描的供料器优先算法
|
||||
component_result, cycle_result, feeder_slot_result = feeder_priority_assignment(component_data, pcb_data,
|
||||
feeder_data)
|
||||
placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
|
||||
cycle_result, feeder_slot_result)
|
||||
# placement_result, head_sequence = beam_search_route_generation(component_data, pcb_data, component_result,
|
||||
# cycle_result, feeder_slot_result)
|
||||
# placement_result, head_sequence = scan_based_placement_route_generation(component_data, pcb_data,
|
||||
# component_result, cycle_result,
|
||||
# feeder_slot_result)
|
||||
|
||||
placement_result, head_sequence = scan_based_placement_route_generation(component_data, pcb_data,
|
||||
component_result, cycle_result,
|
||||
feeder_slot_result)
|
||||
elif params.machine_optimizer == 'hybrid-genetic': # 基于拾取组的混合遗传算法
|
||||
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_hybrid_genetic(
|
||||
pcb_data, component_data, hinter=hinter)
|
||||
|
||||
elif params.machine_optimizer == 'aggregation': # 基于batch-level的整数规划 + 启发式算法
|
||||
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_aggregation(
|
||||
component_data, pcb_data)
|
||||
component_data, pcb_data, hinter=hinter)
|
||||
elif params.machine_optimizer == 'genetic-scanning':
|
||||
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_genetic_scanning(
|
||||
component_data, pcb_data, hinter=hinter)
|
||||
@@ -43,15 +39,16 @@ def base_optimizer(machine_index, pcb_data, component_data, feeder_data, params,
|
||||
component_data, pcb_data, hinter=hinter)
|
||||
elif params.machine_optimizer == "two-phase":
|
||||
component_result, feeder_slot_result, cycle_result = gurobi_optimizer(pcb_data, component_data, feeder_data,
|
||||
initial=True, partition=True,
|
||||
initial=True, partition=False,
|
||||
reduction=True, hinter=hinter)
|
||||
|
||||
placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
|
||||
cycle_result, feeder_slot_result)
|
||||
placement_result, head_sequence = place_allocate_sequence_route_generation(component_data, pcb_data,
|
||||
component_result, cycle_result,
|
||||
feeder_slot_result)
|
||||
else:
|
||||
raise 'machine optimizer method ' + params.method + ' is not existed'
|
||||
|
||||
print('----- Placement machine ' + str(machine_index) + ' ----- ')
|
||||
# print('----- Placement machine ' + str(machine_index) + ' ----- ')
|
||||
opt_res = OptResult(component_result, cycle_result, feeder_slot_result, placement_result, head_sequence)
|
||||
# 估算贴装用时
|
||||
info = placement_info_evaluation(component_data, pcb_data, opt_res, hinter=False)
|
||||
@@ -67,4 +64,6 @@ def base_optimizer(machine_index, pcb_data, component_data, feeder_data, params,
|
||||
f'result/{params.filename[:-4]}-{params.line_optimizer}-M0{machine_index} {params.save_suffix}',
|
||||
component_data, pcb_data, opt_res)
|
||||
|
||||
# output_optimize_result(f'{params.filename[:-4]}', component_data, pcb_data, opt_res)
|
||||
|
||||
return info
|
||||
|
@@ -355,15 +355,12 @@ def output_optimize_result(file_path, component_data, pcb_data, optimizer_result
|
||||
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='')
|
||||
file_dir = file_path[:file_path.rfind('/') + 1]
|
||||
if not os.path.exists(file_dir):
|
||||
os.makedirs(file_dir)
|
||||
|
||||
output_data.to_excel(file_path + '.xlsx', sheet_name='tb1', float_format='%.3f', na_rep='')
|
||||
output_data.to_csv('result/' + file_path + '.txt', sep='\t', float_format='%.3f', header=False, index=False)
|
||||
|
||||
|
||||
def optimization_assign_result(component_data, pcb_data, optimizer_result, nozzle_hinter=False, component_hinter=False,
|
||||
feeder_hinter=False):
|
||||
feeder_hinter=False, placement_hinter=False):
|
||||
if nozzle_hinter:
|
||||
columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']
|
||||
|
||||
@@ -428,6 +425,29 @@ def optimization_assign_result(component_data, pcb_data, optimizer_result, nozzl
|
||||
print(feedr_assign)
|
||||
print('')
|
||||
|
||||
if placement_hinter:
|
||||
columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle']
|
||||
|
||||
placement_assign = pd.DataFrame(columns=columns)
|
||||
for cycle, _ in enumerate(optimizer_result.placement_assign):
|
||||
placement_assign.loc[cycle, 'cycle'] = 1
|
||||
for head in range(max_head_index):
|
||||
point = optimizer_result.placement_assign[cycle][head]
|
||||
if point != -1:
|
||||
placement_assign.loc[cycle, 'H{}'.format(head + 1)] = 'P{}'.format(point)
|
||||
else:
|
||||
placement_assign.loc[cycle, 'H{}'.format(head + 1)] = ''
|
||||
|
||||
headseq_assign = pd.DataFrame(columns=columns)
|
||||
for cycle, headseq in enumerate(optimizer_result.head_sequence):
|
||||
headseq_assign.loc[cycle, 'cycle'] = 1
|
||||
for head in range(len(headseq)):
|
||||
headseq_assign.loc[cycle, 'H{}'.format(head + 1)] = 'H{}'.format(headseq[head])
|
||||
|
||||
print(placement_assign)
|
||||
print(headseq_assign)
|
||||
print('')
|
||||
|
||||
|
||||
def placement_info_evaluation(component_data, pcb_data, optimizer_result, hinter=False):
|
||||
# === 优化结果参数 ===
|
||||
@@ -555,28 +575,43 @@ def placement_info_evaluation(component_data, pcb_data, optimizer_result, hinter
|
||||
|
||||
# 贴装路径
|
||||
if optimizer_result.placement_assign and optimizer_result.head_sequence:
|
||||
head_angle = [0 for _ in range(max_head_index)]
|
||||
for head in optimizer_result.head_sequence[cycle]:
|
||||
index = optimizer_result.placement_assign[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'])
|
||||
head_angle[head] = pcb_data.iloc[index]['r']
|
||||
|
||||
# 单独计算贴装路径
|
||||
for cntPoints in range(len(mount_pos) - 1):
|
||||
info.place_distance += max(abs(mount_pos[cntPoints][0] - mount_pos[cntPoints + 1][0]),
|
||||
abs(mount_pos[cntPoints][1] - mount_pos[cntPoints + 1][1]))
|
||||
|
||||
if mount_pos[0][0] < mount_pos[-1][0]:
|
||||
mount_pos = reversed(mount_pos)
|
||||
|
||||
# 考虑R轴预旋转,补偿同轴角度转动带来的额外贴装用时
|
||||
info.operation_time += head_rotary_time(mount_angle[0]) # 补偿角度转动带来的额外贴装用时
|
||||
info.operation_time += t_nozzle_put * nozzle_put_counter + t_nozzle_pick * nozzle_pick_counter
|
||||
for idx, pos in enumerate(mount_pos):
|
||||
info.operation_time += t_place
|
||||
move_time = max(axis_moving_time(cur_pos[0] - pos[0], 0), axis_moving_time(cur_pos[1] - pos[1], 1))
|
||||
|
||||
if idx == 0:
|
||||
move_time = max(axis_moving_time(cur_pos[0] - pos[0], 0),
|
||||
axis_moving_time(cur_pos[1] - pos[1], 1))
|
||||
info.round_time += move_time
|
||||
else:
|
||||
|
||||
cur_head = optimizer_result.head_sequence[cycle][idx]
|
||||
side_head = cur_head - 1 if cur_head % 2 else cur_head + 1
|
||||
if optimizer_result.head_sequence[cycle][idx - 1] != side_head:
|
||||
move_time = max(axis_moving_time(cur_pos[0] - pos[0], 0),
|
||||
axis_moving_time(cur_pos[1] - pos[1], 1))
|
||||
else:
|
||||
move_time = max(axis_moving_time(cur_pos[0] - pos[0], 0),
|
||||
axis_moving_time(cur_pos[1] - pos[1], 1),
|
||||
head_rotary_time(head_angle[cur_head] - head_angle[side_head]))
|
||||
info.place_time += move_time
|
||||
|
||||
info.total_distance += max(abs(cur_pos[0] - pos[0]), abs(cur_pos[1] - pos[1]))
|
||||
@@ -594,5 +629,3 @@ def placement_info_evaluation(component_data, pcb_data, optimizer_result, hinter
|
||||
return info
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -1,12 +1,12 @@
|
||||
from base_optimizer.optimizer_common import *
|
||||
|
||||
from base_optimizer.smtopt_route import *
|
||||
|
||||
def list_range(start, end=None):
|
||||
return list(range(start)) if end is None else list(range(start, end))
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def optimizer_aggregation(component_data, pcb_data):
|
||||
def optimizer_aggregation(component_data, pcb_data, hinter=True):
|
||||
# === phase 0: data preparation ===
|
||||
M = 1000 # a sufficient large number
|
||||
a, b = 1, 6 # coefficient
|
||||
@@ -43,7 +43,7 @@ def optimizer_aggregation(component_data, pcb_data):
|
||||
|
||||
# === phase 1: mathematical model solver ===
|
||||
mdl = Model('SMT')
|
||||
mdl.setParam('OutputFlag', 0)
|
||||
mdl.setParam('OutputFlag', hinter)
|
||||
|
||||
# === Decision Variables ===
|
||||
# the number of components of type i that are placed by nozzle type j on placement head k
|
||||
@@ -104,8 +104,7 @@ def optimizer_aggregation(component_data, pcb_data):
|
||||
mdl.setParam("TimeLimit", 100)
|
||||
|
||||
mdl.optimize()
|
||||
|
||||
if mdl.Status == GRB.OPTIMAL:
|
||||
if mdl.Status == GRB.OPTIMAL or mdl.Status == GRB.TIME_LIMIT:
|
||||
print('total cost = {}'.format(mdl.objval))
|
||||
|
||||
# convert cp model solution to standard output
|
||||
@@ -160,25 +159,9 @@ def optimizer_aggregation(component_data, pcb_data):
|
||||
feeder_slot_result = feeder_assignment(component_data, pcb_data, component_result, cycle_result)
|
||||
|
||||
# === phase 2: heuristic method ===
|
||||
mount_point_pos = defaultdict(list)
|
||||
for pcb_idx, data in pcb_data.iterrows():
|
||||
part = data['part']
|
||||
part_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
mount_point_pos[part_index].append([data['x'], data['y'], pcb_idx])
|
||||
|
||||
for index_ in mount_point_pos.keys():
|
||||
mount_point_pos[index_].sort(key=lambda x: (x[1], x[0]))
|
||||
|
||||
for cycle_idx, _ in enumerate(cycle_result):
|
||||
for _ in range(cycle_result[cycle_idx]):
|
||||
placement_result.append([-1 for _ in range(max_head_index)])
|
||||
for head in range(max_head_index):
|
||||
if component_result[cycle_idx][head] == -1:
|
||||
continue
|
||||
index_ = component_result[cycle_idx][head]
|
||||
placement_result[-1][head] = mount_point_pos[index_][-1][2]
|
||||
mount_point_pos[index_].pop()
|
||||
head_sequence.append(dynamic_programming_cycle_path(pcb_data, placement_result[-1], feeder_slot_result[cycle_idx]))
|
||||
placement_result, head_sequence = greedy_level_placing_route_generation(component_data, pcb_data,
|
||||
component_result, cycle_result,
|
||||
feeder_slot_result)
|
||||
|
||||
else:
|
||||
warnings.warn('No solution found!', UserWarning)
|
||||
|
@@ -33,7 +33,7 @@ def convert_cell_2_result(pcb_data, component_data, component_cell, population):
|
||||
|
||||
wl = [0 for _ in range(max_head_index)] # workload
|
||||
|
||||
e1, e2, e3 = 1, 0.5, 1. / 6
|
||||
e1, e2, e3 = 1, 2, 1. / 6
|
||||
|
||||
component_result, cycle_result, feeder_slot_result = [], [], []
|
||||
for index in population:
|
||||
@@ -101,12 +101,16 @@ def optimizer_celldivision(pcb_data, component_data, hinter=True):
|
||||
golden_section = 0.618
|
||||
|
||||
# 获取元件元胞
|
||||
point_num = len(pcb_data)
|
||||
component_cell = pd.DataFrame({'index': np.arange(len(component_data)), 'points': np.zeros(len(component_data), dtype=int)})
|
||||
for point_cnt in range(point_num):
|
||||
part = pcb_data.loc[point_cnt, 'part']
|
||||
index = np.where(component_data['part'].values == part)
|
||||
component_cell.loc[index[0], 'points'] += 1
|
||||
feeder_num = sum(component_data['fdn'])
|
||||
component_cell = pd.DataFrame({'index': np.arange(feeder_num), 'points': np.zeros(feeder_num, dtype=int)})
|
||||
cell_index = 0
|
||||
for part_index, data in component_data.iterrows():
|
||||
total_points, division_points = data.points, math.ceil(data.points / data.fdn)
|
||||
for _ in range(data.fdn):
|
||||
component_cell.loc[cell_index, 'index'] = part_index
|
||||
component_cell.loc[cell_index, 'points'] = min(division_points, total_points)
|
||||
total_points -= division_points
|
||||
cell_index += 1
|
||||
component_cell = component_cell[~component_cell['points'].isin([0])]
|
||||
|
||||
# component_cell.sort_values(by = "points" , inplace = True, ascending = False)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from base_optimizer.optimizer_common import *
|
||||
|
||||
from base_optimizer.smtopt_route import *
|
||||
|
||||
def dynamic_programming_cycle_path(cycle_placement, cycle_points):
|
||||
head_sequence = []
|
||||
@@ -231,8 +231,8 @@ def cal_individual_val(component_nozzle, component_point_pos, designated_nozzle,
|
||||
|
||||
def convert_individual_2_result(component_data, component_point_pos, designated_nozzle, pickup_group,
|
||||
pickup_group_cycle, pair_group, feeder_lane, individual):
|
||||
|
||||
component_result, cycle_result, feeder_slot_result = [], [], []
|
||||
placement_result, head_sequence_result = [], []
|
||||
|
||||
# === 记录不同元件对应的槽位 ===
|
||||
feeder_part_arrange = defaultdict(list)
|
||||
@@ -273,26 +273,7 @@ def convert_individual_2_result(component_data, component_point_pos, designated_
|
||||
|
||||
pickup_cycle_result[idx][head] -= cycle
|
||||
|
||||
component_point_index = defaultdict(int)
|
||||
for cycle_set in range(len(cycle_result)):
|
||||
for cycle in range(cycle_result[cycle_set]):
|
||||
placement_result.append([-1 for _ in range(max_head_index)])
|
||||
mount_point = [[0, 0] for _ in range(max_head_index)]
|
||||
for head in range(max_head_index):
|
||||
part_index = component_result[cycle_set][head]
|
||||
if part_index == -1:
|
||||
continue
|
||||
|
||||
part = component_data.iloc[part_index]['part']
|
||||
point_info = component_point_pos[part][component_point_index[part]]
|
||||
|
||||
placement_result[-1][head] = point_info[2]
|
||||
mount_point[head] = point_info[0:2]
|
||||
|
||||
component_point_index[part] += 1
|
||||
head_sequence_result.append(dynamic_programming_cycle_path(placement_result[-1], mount_point))
|
||||
|
||||
return component_result, cycle_result, feeder_slot_result, placement_result, head_sequence_result
|
||||
return component_result, cycle_result, feeder_slot_result
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
@@ -510,11 +491,6 @@ def optimizer_hybrid_genetic(pcb_data, component_data, hinter=True):
|
||||
pop_val.append(val) # val is related to assembly time
|
||||
|
||||
for _ in range(n_generations):
|
||||
# idx = np.argmin(pop_val)
|
||||
# if len(best_pop_val) == 0 or pop_val[idx] < best_pop_val[-1]:
|
||||
# best_individual = copy.deepcopy(population[idx])
|
||||
# best_pop_val.append(pop_val[idx])
|
||||
|
||||
# min-max convert
|
||||
max_val = 1.5 * max(pop_val)
|
||||
convert_pop_val = list(map(lambda v: max_val - v, pop_val))
|
||||
@@ -565,6 +541,15 @@ def optimizer_hybrid_genetic(pcb_data, component_data, hinter=True):
|
||||
pbar.update(1)
|
||||
|
||||
best_individual = population[np.argmin(pop_val)]
|
||||
component_result, cycle_result, feeder_slot_result = convert_individual_2_result(component_data,
|
||||
component_point_pos,
|
||||
designated_nozzle, pickup_group,
|
||||
pickup_group_cycle, pair_group,
|
||||
feeder_lane, best_individual)
|
||||
placement_result, head_sequence_result = place_cluster_greedy_route_generation(component_data, pcb_data,
|
||||
component_result, cycle_result,
|
||||
feeder_slot_result)
|
||||
|
||||
|
||||
return component_result, cycle_result, feeder_slot_result, placement_result, head_sequence_result
|
||||
|
||||
return convert_individual_2_result(component_data, component_point_pos, designated_nozzle, pickup_group,
|
||||
pickup_group_cycle, pair_group, feeder_lane, best_individual)
|
||||
|
@@ -1,4 +1,5 @@
|
||||
from base_optimizer.optimizer_common import *
|
||||
from base_optimizer.smtopt_route import *
|
||||
|
||||
|
||||
def head_task_model(component_data, pcb_data, hinter=True):
|
||||
@@ -6,11 +7,10 @@ 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)
|
||||
mdl.setParam('TimeLimit', 1000)
|
||||
|
||||
H = max_head_index
|
||||
I = len(component_data)
|
||||
S = len(component_data)
|
||||
K = len(pcb_data)
|
||||
|
||||
nozzle_type, component_type = [], []
|
||||
@@ -29,12 +29,16 @@ def head_task_model(component_data, pcb_data, hinter=True):
|
||||
M = 10000
|
||||
CompOfNozzle = [[0 for _ in range(J)] for _ in range(I)] # Compatibility
|
||||
|
||||
component_point = [0 for _ in range(I)]
|
||||
component_point, component_fdn = [0 for _ in range(I)], [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
|
||||
component_fdn[idx] = component_data.iloc[idx].fdn
|
||||
|
||||
S = sum(component_fdn)
|
||||
|
||||
# objective related
|
||||
g = mdl.addVars(list_range(K), vtype=GRB.BINARY)
|
||||
@@ -94,7 +98,7 @@ def head_task_model(component_data, pcb_data, hinter=True):
|
||||
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 s in range(S)) <= component_fdn[i] 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, k, h] * y[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))
|
||||
@@ -110,7 +114,7 @@ def head_task_model(component_data, pcb_data, hinter=True):
|
||||
# objective
|
||||
mdl.setObjective(Fit_cy * quicksum(g[k] for k in range(K)) + Fit_nz * quicksum(
|
||||
d[k, h] for h in range(H) for k in range(K)) + Fit_pu * quicksum(
|
||||
e[s, k] for s in range(-(H - 1) * r, S) for k in range(K)) + Fit_mv * head_interval * quicksum(u[k] for k in range(K)),
|
||||
e[s, k] for s in range(-(H - 1) * r, S) for k in range(K)) + Fit_mv * quicksum(u[k] for k in range(K)),
|
||||
GRB.MINIMIZE)
|
||||
|
||||
mdl.optimize()
|
||||
@@ -119,19 +123,21 @@ def head_task_model(component_data, pcb_data, hinter=True):
|
||||
for k in range(K):
|
||||
if abs(g[k].x) < 1e-6:
|
||||
continue
|
||||
component_assign, feeder_slot_assign = [-1 for _ in range(H)], [-1 for _ in range(H)]
|
||||
|
||||
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):
|
||||
if abs(x[i, k, h].x) > 1e-6:
|
||||
component_result[-1][h] = i
|
||||
component_assign[h] = i
|
||||
|
||||
for s in range(S):
|
||||
if abs(y[s, k, h].x) > 1e-6:
|
||||
feeder_slot_result[-1][h] = slot_start + s * interval_ratio - 1
|
||||
feeder_slot_assign[h] = slot_start + s * interval_ratio - 1
|
||||
|
||||
if sum(component_assign) != -H:
|
||||
component_result.append(component_assign)
|
||||
feeder_slot_result.append(feeder_slot_assign)
|
||||
cycle_result.append(1)
|
||||
|
||||
if hinter:
|
||||
print(component_result)
|
||||
@@ -143,7 +149,7 @@ def place_route_model(component_data, pcb_data, component_result, feeder_slot_re
|
||||
mdl = Model('place_route')
|
||||
mdl.setParam('Seed', 0)
|
||||
mdl.setParam('OutputFlag', hinter) # set whether output the debug information
|
||||
mdl.setParam('TimeLimit', 10)
|
||||
mdl.setParam('TimeLimit', 1000)
|
||||
|
||||
component_type = []
|
||||
for _, data in component_data.iterrows():
|
||||
@@ -227,7 +233,7 @@ def place_route_model(component_data, pcb_data, component_result, feeder_slot_re
|
||||
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)) + y[p, k, h] + z[p, k, h] <= 4 *
|
||||
w[q, p, k, a] for a in A_to(h) for q in range(P)) + y[p, k, h] + z[p, k, h] <= 2 *
|
||||
CompOfPoint[component_result[k][h]][p] for p in range(P))
|
||||
|
||||
# each head corresponds to a maximum of one point in each cycle
|
||||
@@ -362,7 +368,9 @@ def place_route_model(component_data, pcb_data, component_result, feeder_slot_re
|
||||
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)
|
||||
# placement_result, head_sequence = place_route_model(component_data, pcb_data, component_result, feeder_slot_result)
|
||||
placement_result, head_sequence = place_allocate_sequence_route_generation(component_data, pcb_data,
|
||||
component_result, cycle_result,
|
||||
feeder_slot_result)
|
||||
|
||||
return component_result, cycle_result, feeder_slot_result, placement_result, head_sequence
|
||||
|
@@ -1,4 +1,6 @@
|
||||
from base_optimizer.optimizer_common import *
|
||||
from base_optimizer.smtopt_route import *
|
||||
from base_optimizer.result_analysis import *
|
||||
|
||||
|
||||
def list_range(start, end=None):
|
||||
@@ -81,8 +83,9 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
|
||||
changetime = mdl.cbGet(GRB.Callback.RUNTIME)
|
||||
nonlocal pre_objbst, pre_changetime
|
||||
# condition: value change
|
||||
if abs(objbst - 1e+100) > 1: # 避免未找到可行解提前退出
|
||||
if pre_objbst and abs(pre_objbst - objbst) < 1e-3:
|
||||
if pre_changetime and changetime - pre_changetime > 100 * (1 - objbnd / objbst):
|
||||
if pre_changetime and changetime - pre_changetime > 90 * (1 - objbnd / objbst):
|
||||
mdl.terminate()
|
||||
else:
|
||||
pre_changetime = changetime
|
||||
@@ -147,7 +150,7 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
|
||||
level += 1
|
||||
|
||||
L = len(cycle_assignment) if partition else len(pcb_data)
|
||||
S = ratio * sum(component_feeder.values()) * 2 if len(feeder_data) == 0 else arg_slot_rng[-1] - arg_slot_rng[0] + 1 # the available feeder num
|
||||
S = ratio * sum(component_feeder.values()) 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):
|
||||
@@ -157,12 +160,12 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
|
||||
mdl = Model('SMT')
|
||||
mdl.setParam('Seed', 0)
|
||||
mdl.setParam('OutputFlag', hinter) # set whether output the debug information
|
||||
mdl.setParam('TimeLimit', 3600 * 3)
|
||||
mdl.setParam('TimeLimit', 3600)
|
||||
mdl.setParam('PoolSearchMode', 2)
|
||||
mdl.setParam('PoolSolutions', 3e2)
|
||||
mdl.setParam('PoolSolutions', 100)
|
||||
mdl.setParam('PoolGap', 1e-4)
|
||||
# mdl.setParam('MIPFocus', 2)
|
||||
# mdl.setParam("Heuristics", 0.5)
|
||||
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)
|
||||
@@ -178,7 +181,6 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
|
||||
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():
|
||||
@@ -227,7 +229,7 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
|
||||
continue
|
||||
|
||||
part_feederbase[part_2_cpidx[part]] = slot
|
||||
# f[slot, part_2_cpidx[part]].Start = 1
|
||||
f[slot, part_2_cpidx[part]].Start = 1
|
||||
slot += 1
|
||||
|
||||
for idx, cycle in enumerate(cycle_index):
|
||||
@@ -334,13 +336,12 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
|
||||
in range(L))
|
||||
|
||||
if reduction:
|
||||
# mdl.addConstrs(WL[l] >= WL[l + 1] for l in range(L - 1))
|
||||
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(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))
|
||||
|
||||
@@ -365,125 +366,126 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
|
||||
# mdl.optimize()
|
||||
|
||||
# === result generation ===
|
||||
nozzle_assign, component_assign = [], []
|
||||
feeder_assign, cycle_assign = [], []
|
||||
opt_res_list = defaultdict(OptResult)
|
||||
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)
|
||||
component_pos = defaultdict(list[Point])
|
||||
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])
|
||||
component_pos[component_index].append(Point(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])]
|
||||
for part in component_pos.keys():
|
||||
component_pos[part] = sorted(component_pos[part], key=lambda pos: (pos.x, pos.y))
|
||||
|
||||
min_dist, solution_number = None, -1
|
||||
min_dist, solution_number = None, 0
|
||||
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)
|
||||
opt_res = OptResult()
|
||||
# == 转换标准的贴装头分配的解 ===
|
||||
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
|
||||
opt_res.cycle_assign.append(round(WL[l].Xn))
|
||||
opt_res.component_assign.append([-1] * max_head_index)
|
||||
opt_res.feeder_slot_assign.append([-1] * 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
|
||||
opt_res.component_assign[-1][h] = i
|
||||
|
||||
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
|
||||
if abs(v[s, h, l].Xn - 1) < 1e-4 and opt_res.component_assign[-1][h] != -1:
|
||||
opt_res.feeder_slot_assign[-1][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:
|
||||
# 根据贴装头位置,转换供料器槽位
|
||||
cp_avg_head, cp_sum_cycle = defaultdict(float), defaultdict(int)
|
||||
for cycle, component_assign in enumerate(opt_res.component_assign):
|
||||
for head, part in enumerate(component_assign):
|
||||
if part == -1:
|
||||
continue
|
||||
min_slot, max_slot = None, None
|
||||
cp_avg_head[part] += opt_res.cycle_assign[cycle] * head
|
||||
cp_sum_cycle[part] += opt_res.cycle_assign[cycle]
|
||||
|
||||
for part, head in cp_avg_head.items():
|
||||
cp_avg_head[part] = head / cp_sum_cycle[part]
|
||||
|
||||
avg_position = sum([data.x - cp_avg_head[part_2_cpidx[data.part]] * head_interval for _, data in
|
||||
pcb_data.iterrows()]) / len(pcb_data)
|
||||
avg_slot = 0
|
||||
D_PU, D_PL, D_BW, D_FW = 0, 0, 0, 0
|
||||
for cycle, slots in enumerate(opt_res.feeder_slot_assign):
|
||||
min_slot, max_slot = max_slot_index, 0
|
||||
for head, slot in enumerate(slots):
|
||||
if slot == -1:
|
||||
continue
|
||||
min_slot = min(min_slot, slot - head * ratio)
|
||||
max_slot = max(max_slot, slot - head * ratio)
|
||||
avg_slot += (max_slot - min_slot) * opt_res.cycle_assign[cycle]
|
||||
D_PU += (max_slot - min_slot) * slot_interval * opt_res.cycle_assign[cycle] # 拾取路径
|
||||
|
||||
avg_slot /= sum(opt_res.cycle_assign)
|
||||
start_slot = round((avg_position + stopper_pos[0] - slotf1_pos[0]) / slot_interval + avg_slot / 2) + 1
|
||||
|
||||
for cycle in range(len(opt_res.feeder_slot_assign)):
|
||||
for head in range(max_head_index):
|
||||
if abs(WL[l].Xn) <= 1e-4 or feeder_assign[l][head] == -1:
|
||||
if (slot := opt_res.feeder_slot_assign[cycle][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
|
||||
opt_res.feeder_slot_assign[cycle][head] = start_slot + slot * (2 if ratio == 1 else 1)
|
||||
|
||||
for l in range(L):
|
||||
if abs(WL[l].Xn) <= 1e-4:
|
||||
component_pos_counter = defaultdict(int)
|
||||
cycle_place_pos = defaultdict(list[Point])
|
||||
for head in range(max_head_index):
|
||||
for cycle in range(len(opt_res.cycle_assign)):
|
||||
if (part := opt_res.component_assign[cycle][head]) == -1:
|
||||
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)
|
||||
avg_place_pos = Point(0, 0, _h=head)
|
||||
for counter in range(round(opt_res.cycle_assign[cycle])):
|
||||
avg_place_pos.x = (1 - 1.0 / (counter + 1)) * avg_place_pos.x + (
|
||||
component_pos[part][component_pos_counter[part]].x - head * head_interval) / (
|
||||
counter + 1)
|
||||
avg_place_pos.y = (1 - 1.0 / (counter + 1)) * avg_place_pos.y + component_pos[part][
|
||||
component_pos_counter[part]].y / (counter + 1)
|
||||
component_pos_counter[part] += 1
|
||||
avg_place_pos.x += stopper_pos[0]
|
||||
avg_place_pos.y += stopper_pos[1]
|
||||
cycle_place_pos[cycle].append(avg_place_pos)
|
||||
|
||||
for cycle in range(len(opt_res.cycle_assign)):
|
||||
min_slot, max_slot = max_slot_index, 0
|
||||
for head in range(max_head_index):
|
||||
if (slot := opt_res.feeder_slot_assign[cycle][head]) == -1:
|
||||
continue
|
||||
min_slot = min(min_slot, slot - head * interval_ratio)
|
||||
max_slot = max(max_slot, slot - head * interval_ratio)
|
||||
# cycle_place_pos[cycle] = sorted(cycle_place_pos[cycle], key=lambda pt: pt.x)
|
||||
|
||||
pick_pos = Point(slotf1_pos[0] + (min_slot + max_slot) / 2 * slot_interval, slotf1_pos[1])
|
||||
_, seq = dynamic_programming_cycle_route(cycle_place_pos[cycle], pick_pos)
|
||||
head_position = [Point(0, 0) for _ in range(max_head_index)]
|
||||
for point in cycle_place_pos[cycle]:
|
||||
head_position[point.h] = point
|
||||
|
||||
for idx in range(len(seq) - 1):
|
||||
h1, h2 = seq[idx], seq[idx + 1]
|
||||
D_PL += max(abs(head_position[h1].x - head_position[h2].x),
|
||||
abs(head_position[h1].y - head_position[h2].y)) * opt_res.cycle_assign[cycle]
|
||||
|
||||
|
||||
# opt_res.placement_assign, opt_res.head_sequence = scan_based_placement_route_generation(component_data,
|
||||
# pcb_data,
|
||||
# opt_res.component_assign,
|
||||
# opt_res.cycle_assign,
|
||||
# opt_res.feeder_slot_assign,
|
||||
# hinter=False)
|
||||
# info = placement_info_evaluation(component_data, pcb_data, opt_res)
|
||||
# print(f'{info.place_distance + info.pickup_distance: .3f}\t{D_PL + D_PU: .3f}')
|
||||
opt_res_list[sol_counter] = opt_res
|
||||
|
||||
solution_number = 0
|
||||
mdl.Params.SolutionNumber = 0
|
||||
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(
|
||||
@@ -496,9 +498,10 @@ def gurobi_optimizer(pcb_data, component_data, feeder_data, reduction=True, part
|
||||
|
||||
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
|
||||
print('component assignment: ', opt_res_list[solution_number].component_assign)
|
||||
print('feeder assignment: ', opt_res_list[solution_number].feeder_slot_assign)
|
||||
print('cycle assignment: ', opt_res_list[solution_number].cycle_assign)
|
||||
|
||||
return opt_res_list[solution_number].component_assign, opt_res_list[solution_number].feeder_slot_assign, \
|
||||
opt_res_list[solution_number].cycle_assign
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -161,8 +161,27 @@ def load_data(filename: str, load_feeder=False, auto_register=True):
|
||||
feeder_data = pd.DataFrame(columns=feeder_columns)
|
||||
else:
|
||||
feeder_data = pd.DataFrame(columns=feeder_columns)
|
||||
|
||||
for idx, data in feeder_data.iterrows():
|
||||
feeder_data.at[idx, 'slot'] = int(data['slot'][1:])
|
||||
|
||||
feeder_data.sort_values(by='slot', ascending=True, inplace=True, ignore_index=True)
|
||||
|
||||
|
||||
feeder_data_check = defaultdict(str)
|
||||
for _, data in feeder_data.iterrows():
|
||||
feeder_data_check[data.slot] = data.part
|
||||
|
||||
for machine_index in range(machine_num):
|
||||
for idx, data in pcb_data[machine_index].iterrows():
|
||||
slot, part = data.fdr.split(' ')
|
||||
slot = int(slot[1:])
|
||||
if feeder_data_check[slot] == '':
|
||||
feeder_data_check[slot] = part
|
||||
if feeder_data_check[slot] != part:
|
||||
warning_info = f'conflict feeder registration PCB: {data.fdr}, BASE: F{slot} {feeder_data_check[slot]}'
|
||||
# warnings.warn(warning_info, UserWarning)
|
||||
|
||||
return pcb_data, component_data, feeder_data
|
||||
|
||||
|
||||
|
16
estimator.py
16
estimator.py
@@ -6,7 +6,7 @@ def exact_assembly_time(pcb_data, component_data):
|
||||
feeder_data = pd.DataFrame(columns=['slot', 'part'])
|
||||
component_result, cycle_result, feeder_slot_result = feeder_priority_assignment(component_data, pcb_data,
|
||||
feeder_data, hinter=False)
|
||||
placement_result, head_sequence_result = greedy_placement_route_generation(component_data, pcb_data,
|
||||
placement_result, head_sequence_result = place_allocate_sequence_route_generation(component_data, pcb_data,
|
||||
component_result, cycle_result,
|
||||
feeder_slot_result, hinter=False)
|
||||
opt_res = OptResult(component_result, cycle_result, feeder_slot_result, placement_result, head_sequence_result)
|
||||
@@ -19,7 +19,7 @@ def error_info(pred_val, real_val, type='train'):
|
||||
absolute_error = np.array([])
|
||||
for idx, (t1, t2) in enumerate(np.nditer([pred_val, real_val])):
|
||||
absolute_error = np.append(absolute_error, abs(t1 - t2) / (t2 + 1e-10) * 100)
|
||||
if absolute_error[-1] > 15:
|
||||
if absolute_error[-1] > 10:
|
||||
print(f'\033[0;31;31midx: {idx + 1: d}, net: {t1: .3f}, real: {t2: .3f}, '
|
||||
f'gap: {absolute_error[-1]: .3f}\033[0m')
|
||||
|
||||
@@ -561,6 +561,8 @@ class MetricEstimator(Estimator):
|
||||
def training(self, params):
|
||||
x_fit, y_fit = data_mgr.metric('opt/' + params.train_file)
|
||||
self.lr.fit(x_fit, y_fit)
|
||||
y_predict = self.lr.predict(x_fit)
|
||||
error_info(y_fit, y_predict, 'train')
|
||||
print(self.lr.coef_)
|
||||
|
||||
def testing(self, params):
|
||||
@@ -578,7 +580,7 @@ if __name__ == '__main__':
|
||||
|
||||
parser = argparse.ArgumentParser(description='network training implementation')
|
||||
# parser.add_argument('--train', default=True, type=bool, help='determine whether training the network')
|
||||
parser.add_argument('--save', default=True, type=bool,
|
||||
parser.add_argument('--save', default=False, type=bool,
|
||||
help='determine whether saving the parameters of network, linear regression model, etc.')
|
||||
parser.add_argument('--overwrite', default=False, type=bool,
|
||||
help='determine whether overwriting the training and testing data')
|
||||
@@ -588,7 +590,7 @@ if __name__ == '__main__':
|
||||
parser.add_argument('--batch_size', default=2000, type=int, help='size of training batch')
|
||||
parser.add_argument('--lr', default=1e-5, type=float, help='learning rate for the network')
|
||||
parser.add_argument('--model', default='neural-network', help='method for assembly time estimation')
|
||||
parser.add_argument('--machine_optimizer', default='feeder-scan', type=str, help='optimizer for single machine')
|
||||
parser.add_argument('--machine_optimizer', default='feeder-priority', type=str, help='optimizer for single machine')
|
||||
params = parser.parse_args()
|
||||
|
||||
data_mgr = DataMgr()
|
||||
@@ -606,13 +608,13 @@ if __name__ == '__main__':
|
||||
# data_mgr.remover() # remove the last saved data
|
||||
# data_mgr.saver('data/' + file_name, pcb_data) # save new data
|
||||
|
||||
info = base_optimizer(1, pcb_data, component_data, pd.DataFrame(columns=['slot', 'part', 'arg']),
|
||||
params, hinter=True)
|
||||
info = base_optimizer(1, pcb_data, component_data, pd.DataFrame(columns=['slot', 'part']), params,
|
||||
hinter=True)
|
||||
|
||||
data_mgr.recorder(f, info, pcb_data, component_data)
|
||||
f.close()
|
||||
|
||||
estimator = MetricEstimator()
|
||||
estimator = NeuralEstimator()
|
||||
|
||||
estimator.training(params)
|
||||
estimator.testing(params)
|
||||
|
28
generator.py
28
generator.py
@@ -73,9 +73,9 @@ class DataMgr:
|
||||
lineinfo += '\t' + '{:.3f}'.format(pcb_data['x'].max() - pcb_data['x'].min()) + '\t' + '{:.3f}'.format(
|
||||
pcb_data['y'].max() - pcb_data['y'].min())
|
||||
|
||||
# part_position = defaultdict(list)
|
||||
# for _, data in pcb_data.iterrows():
|
||||
# part_position[data['part']].append([data['x'], data['y']])
|
||||
part_position = defaultdict(list)
|
||||
for _, data in pcb_data.iterrows():
|
||||
part_position[data['part']].append([data['x'], data['y']])
|
||||
|
||||
point_counter, component_counter = 0, 0
|
||||
nozzle_type = set()
|
||||
@@ -94,8 +94,8 @@ class DataMgr:
|
||||
if data.points == 0:
|
||||
continue
|
||||
lineinfo += '\t' + data.part + '\t' + data.nz + '\t' + str(data.points)
|
||||
# lineinfo += '\t' + '{:.3f}'.format(np.ptp([pos[0] for pos in part_position[data.part]]))
|
||||
# lineinfo += '\t' + '{:.3f}'.format(np.ptp([pos[1] for pos in part_position[data.part]]))
|
||||
lineinfo += '\t' + '{:.3f}'.format(np.ptp([pos[0] for pos in part_position[data.part]]))
|
||||
lineinfo += '\t' + '{:.3f}'.format(np.ptp([pos[1] for pos in part_position[data.part]]))
|
||||
|
||||
lineinfo += '\n'
|
||||
file_handle.write(lineinfo)
|
||||
@@ -173,7 +173,7 @@ class DataMgr:
|
||||
return data
|
||||
|
||||
def heuristic_objective(self, cp_points, cp_nozzle):
|
||||
if len(cp_points.keys()) == 0:
|
||||
if len(cp_points.keys()) or sum(cp_points.values()) == 0:
|
||||
return 0, 0, 0, 0
|
||||
nozzle_heads, nozzle_points = defaultdict(int), defaultdict(int)
|
||||
for idx, points in cp_points.items():
|
||||
@@ -417,18 +417,18 @@ class DataMgr:
|
||||
# assembly time data
|
||||
time_data.append(float(items[0]))
|
||||
|
||||
cp_data.append([cp_points, cp_nozzle, board_width, board_height])
|
||||
cp_data.append([cp_points, cp_nozzle, board_width, board_height, cycle, nozzle_change_data, pickup_data])
|
||||
|
||||
line = file.readline()
|
||||
|
||||
if data_filter:
|
||||
cph_data = [point_data[idx] / time_data[idx] * 3600 for idx in range(len(time_data))]
|
||||
# cph_data = [point_data[idx] / time_data[idx] * 3600 for idx in range(len(time_data))]
|
||||
|
||||
w_quart = 0.6
|
||||
Q1, Q3 = np.percentile(np.array(cph_data), 25), np.percentile(np.array(cph_data), 75)
|
||||
w_quart = 0.3
|
||||
Q1, Q3 = np.percentile(np.array(time_data), 25), np.percentile(np.array(time_data), 75)
|
||||
|
||||
indices = [i for i in range(len(cph_data)) if
|
||||
Q1 - w_quart * (Q3 - Q1) <= cph_data[i] <= Q3 + w_quart * (Q3 - Q1)]
|
||||
indices = [i for i in range(len(time_data)) if
|
||||
Q1 - w_quart * (Q3 - Q1) <= time_data[i] <= Q3 + w_quart * (Q3 - Q1)]
|
||||
|
||||
filter_cp_data, filter_time_data = [], []
|
||||
for idx in indices:
|
||||
@@ -465,8 +465,8 @@ class DataMgr:
|
||||
|
||||
def neural_encode(self, input_data):
|
||||
train_data = []
|
||||
for cp_points, cp_nozzle, board_width, board_height in input_data:
|
||||
train_data.append(self.encode(cp_points, cp_nozzle, board_width, board_height))
|
||||
for cp_points, cp_nozzle, board_width, board_height, cy, nz, pu in input_data:
|
||||
train_data.append(self.encode(cp_points, cp_nozzle, board_width, board_height, cy, nz, pu))
|
||||
return train_data
|
||||
|
||||
def get_feature(self):
|
||||
|
@@ -278,7 +278,7 @@ def cal_individual_val(heuristic_map, cp_index, cp_points, cp_nozzle, cp_feeders
|
||||
def line_optimizer_hyperheuristic(component_data, pcb_data, machine_number):
|
||||
heuristic_map = {
|
||||
'p': LeastPoints,
|
||||
'n': LeastNzChange,
|
||||
'n': LeastNzTypes,
|
||||
'c': LeastCpTypes,
|
||||
'r': LeastCpNzRatio,
|
||||
'k': LeastCycle,
|
||||
@@ -411,12 +411,13 @@ def line_optimizer_hyperheuristic(component_data, pcb_data, machine_number):
|
||||
best_component_list = component_list.copy()
|
||||
|
||||
machine_cp_points = convert_assignment_result(heuristic_map, cp_index, cp_points, cp_nozzle, cp_feeders,
|
||||
best_component_list, best_heuristic_list, machine_number)
|
||||
best_component_list, best_heuristic_list, machine_number, is_opt=True)
|
||||
|
||||
assignment_result = [[0 for _ in range(len(component_data))] for _ in range(machine_number)]
|
||||
for machine_idx in range(machine_number):
|
||||
for idx in machine_cp_points[machine_idx]:
|
||||
assignment_result[machine_idx][cp_index[idx]] += cp_points[idx]
|
||||
|
||||
return assignment_result
|
||||
|
||||
|
||||
|
@@ -7,7 +7,7 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True):
|
||||
mdl = Model('pcb assembly line optimizer')
|
||||
mdl.setParam('Seed', 0)
|
||||
mdl.setParam('OutputFlag', hinter) # set whether output the debug information
|
||||
mdl.setParam('TimeLimit', 600 * 3)
|
||||
mdl.setParam('TimeLimit', 1000)
|
||||
|
||||
nozzle_type, component_type = [], []
|
||||
for _, data in component_data.iterrows():
|
||||
@@ -22,9 +22,10 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True):
|
||||
|
||||
H = max_head_index
|
||||
I = len(component_data)
|
||||
S = min(len(component_data) * ratio, 60)
|
||||
K = math.ceil(len(pcb_data) * 1.0 / H / M) + 1
|
||||
# K = 3
|
||||
S = sum([data.fdn * ratio for _, data in component_data.iterrows()])
|
||||
K = math.ceil(len(pcb_data) * 1.0 / M) + 1
|
||||
|
||||
# K = len(pcb_data)
|
||||
CompOfNozzle = [[0 for _ in range(J)] for _ in range(I)] # Compatibility
|
||||
|
||||
component_point = [0 for _ in range(I)]
|
||||
@@ -35,15 +36,17 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True):
|
||||
|
||||
# objective related
|
||||
g = mdl.addVars(list_range(K), list_range(M), vtype=GRB.BINARY)
|
||||
d = mdl.addVars(list_range(K), list_range(H), list_range(M), lb=0, vtype=GRB.CONTINUOUS)
|
||||
d = mdl.addVars(list_range(K), list_range(H), list_range(M), vtype=GRB.INTEGER)
|
||||
u = mdl.addVars(list_range(I), list_range(K), list_range(H), list_range(M), vtype=GRB.BINARY)
|
||||
v = mdl.addVars(list_range(S), list_range(K), list_range(H), list_range(M), vtype=GRB.BINARY)
|
||||
d_plus = mdl.addVars(list_range(J), list_range(K), list_range(H), list_range(M), lb=0, vtype=GRB.CONTINUOUS)
|
||||
d_minus = mdl.addVars(list_range(J), list_range(K), list_range(H), list_range(M), lb=0, vtype=GRB.CONTINUOUS)
|
||||
w = mdl.addVars(list_range(K), list_range(M), vtype=GRB.CONTINUOUS)
|
||||
w = mdl.addVars(list_range(J), list_range(K), list_range(H), list_range(M), vtype=GRB.BINARY)
|
||||
d_plus = mdl.addVars(list_range(J), list_range(K), list_range(H), list_range(M), vtype=GRB.CONTINUOUS)
|
||||
d_minus = mdl.addVars(list_range(J), list_range(K), list_range(H), list_range(M), vtype=GRB.CONTINUOUS)
|
||||
z = mdl.addVars(list_range(K), list_range(M), vtype=GRB.INTEGER)
|
||||
|
||||
e = mdl.addVars(list_range(-(H - 1) * ratio, S), list_range(K), list_range(M), vtype=GRB.BINARY)
|
||||
f = mdl.addVars(list_range(S), list_range(I), list_range(M), vtype=GRB.BINARY, name='')
|
||||
t = mdl.addVars(list_range(M), lb=0, ub=N, vtype=GRB.CONTINUOUS)
|
||||
obj = mdl.addVar(lb=0, ub=N, vtype=GRB.CONTINUOUS)
|
||||
|
||||
mdl.addConstrs(g[k, m] >= g[k + 1, m] for k in range(K - 1) for m in range(M))
|
||||
@@ -52,24 +55,26 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True):
|
||||
quicksum(u[i, k, h, m] for i in range(I)) <= g[k, m] for k in range(K) for h in range(H) for m in range(M))
|
||||
|
||||
# nozzle no more than 1 for head h and cycle k
|
||||
mdl.addConstrs(quicksum(CompOfNozzle[i][j] * u[i, k, h, m] for i in range(I) for j in range(J)) <= 1 for k
|
||||
in range(K) for h in range(H) for m in range(M))
|
||||
mdl.addConstrs(quicksum(w[j, k, h, m] for j in range(J)) <= 1 for k in range(K) for h in range(H) for m in range(M))
|
||||
|
||||
# work completion
|
||||
mdl.addConstrs(
|
||||
quicksum(u[i, k, h, m] for k in range(K) for h in range(H) for m in range(M)) == component_point[i] for i in
|
||||
range(I))
|
||||
|
||||
# nozzle change
|
||||
mdl.addConstrs(quicksum(CompOfNozzle[i][j] * u[i, k, h, m] for i in range(I)) - quicksum(
|
||||
CompOfNozzle[i][j] * u[i, k + 1, h, m] for i in range(I)) == d_plus[j, k, h, m] - d_minus[j, k, h, m] for k in
|
||||
mdl.addConstrs(
|
||||
u[i, k, h, m] <= quicksum(CompOfNozzle[i][j] * w[j, k, h, m] for j in range(J)) for i in range(I) for k in
|
||||
range(K) for h in range(H) for m in range(M))
|
||||
|
||||
mdl.addConstrs(w[j, k, h, m] - w[j, k + 1, h, m] == d_plus[j, k, h, m] - d_minus[j, k, h, m] for k in
|
||||
range(K - 1) for j in range(J) for h in range(H) for m in range(M))
|
||||
|
||||
mdl.addConstrs(quicksum(CompOfNozzle[i][j] * u[i, K - 1, h, m] for i in range(I)) - quicksum(
|
||||
CompOfNozzle[i][j] * u[i, 0, h, m] for i in range(I)) == d_plus[j, K - 1, h, m] - d_minus[j, K - 1, h, m] for j
|
||||
in range(J) for h in range(H) for m in range(M))
|
||||
mdl.addConstrs(w[j, 0, h, m] - w[j, K - 1, h, m] == d_plus[j, K - 1, h, m] - d_minus[j, K - 1, h, m]
|
||||
for j in range(J) for h in range(H) for m in range(M))
|
||||
|
||||
mdl.addConstrs(2 * d[k, h, m] == quicksum(d_plus[j, k, h, m] for j in range(J)) + quicksum(
|
||||
d_minus[j, k, h, m] for j in range(J)) - 1 for k in range(K) for h in range(H) for m in range(M))
|
||||
mdl.addConstrs(d[k, h, m] == quicksum(d_plus[j, k, h, m] for j in range(J)) + quicksum(
|
||||
d_minus[j, k, h, m] for j in range(J)) for k in range(K) for h in range(H) for m in range(M))
|
||||
|
||||
# simultaneous pick
|
||||
for s in range(-(H - 1) * ratio, S):
|
||||
@@ -99,25 +104,29 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True):
|
||||
for s in range(S) for m in range(M))
|
||||
|
||||
# pickup movement
|
||||
mdl.addConstrs(w[k, m] >= s1 * e[s1, k, m] - s2 * e[s2, k, m] + N * (e[s1, k, m] + e[s2, k, m] - 2) for s1 in
|
||||
mdl.addConstrs(z[k, m] >= s1 * e[s1, k, m] - s2 * e[s2, k, m] + N * (e[s1, k, m] + e[s2, k, m] - 2) for s1 in
|
||||
range(-(H - 1) * ratio, S) for s2 in range(-(H - 1) * ratio, S) for k in range(K) for m in range(M))
|
||||
|
||||
# objective
|
||||
mdl.addConstrs(obj >= Fit_cy * quicksum(g[k, m] for k in range(K)) + Fit_nz * 2 * quicksum(
|
||||
mdl.addConstrs(t[m] == Fit_cy * quicksum(g[k, m] for k in range(K)) + Fit_nz * quicksum(
|
||||
d[k, h, m] for h in range(H) for k in range(K)) + Fit_pu * quicksum(
|
||||
e[s, k, m] for s in range(-(H - 1) * ratio, S) for k in range(K)) + Fit_pl * quicksum(
|
||||
u[i, k, h, m] for i in range(I) for k in range(K) for h in range(H)) + Fit_mv * head_interval * quicksum(
|
||||
w[k, m] for k in range(K)) for m in range(M))
|
||||
u[i, k, h, m] for i in range(I) for k in range(K) for h in range(H)) + Fit_mv * quicksum(
|
||||
z[k, m] for k in range(K)) for m in range(M))
|
||||
for m in range(M - 1):
|
||||
mdl.addConstr(t[m] >= t[m + 1])
|
||||
|
||||
mdl.addConstrs(obj >= t[m] for m in range(M))
|
||||
|
||||
mdl.setObjective(obj, GRB.MINIMIZE)
|
||||
|
||||
mdl.optimize()
|
||||
|
||||
for m in range(M):
|
||||
print(f'machine {m} : cycle : {sum(g[k, m].x for k in range(K))}, '
|
||||
f'nozzle change : {sum(d[k, h, m].x for h in range(H) for k in range(K))}, '
|
||||
f'pick up : {sum(e[s, k, m].x for s in range(-(H - 1) * ratio, S) for k in range(K))}, '
|
||||
f'placement : {sum(u[i, k, h, m].x for i in range(I) for k in range(K) for h in range(H))}, '
|
||||
f'pick movement : {sum(w[k, m].x for k in range(K))}')
|
||||
f'pick movement : {sum(z[k, m].x for k in range(K))}')
|
||||
|
||||
pcb_part_indices = defaultdict(list)
|
||||
for idx, data in pcb_data.iterrows():
|
||||
@@ -162,19 +171,22 @@ def line_optimizer_model(component_data, pcb_data, machine_num, hinter=True):
|
||||
|
||||
average_pos = round(
|
||||
(sum(head_place_pos) / len(head_place_pos) + stopper_pos[0] - slotf1_pos[0] + 1) / slot_interval)
|
||||
print(f'average_pos: {average_pos}')
|
||||
|
||||
for k in range(len(feeder_slot_result)):
|
||||
for h in range(H):
|
||||
if feeder_slot_result[k][h] == -1:
|
||||
continue
|
||||
feeder_slot_result[k][h] = feeder_slot_result[k][h] * 2 + average_pos
|
||||
|
||||
placement_result, head_sequence = greedy_placement_route_generation(partial_component_data, partial_pcb_data,
|
||||
placement_result, head_sequence = place_allocate_sequence_route_generation(partial_component_data,
|
||||
partial_pcb_data,
|
||||
component_result, cycle_result,
|
||||
feeder_slot_result, hinter=False)
|
||||
print('----- Placement machine ' + str(m + 1) + ' ----- ')
|
||||
|
||||
opt_res = OptResult(component_result, cycle_result, feeder_slot_result, placement_result, head_sequence)
|
||||
info = placement_info_evaluation(partial_component_data, partial_pcb_data, opt_res, hinter=False)
|
||||
info = placement_info_evaluation(partial_component_data, partial_pcb_data, opt_res, hinter=hinter)
|
||||
if hinter:
|
||||
print('----- Placement machine ' + str(m + 1) + ' ----- ')
|
||||
optimization_assign_result(partial_component_data, partial_pcb_data, opt_res, nozzle_hinter=True,
|
||||
component_hinter=True, feeder_hinter=True)
|
||||
info.print()
|
||||
|
@@ -291,6 +291,328 @@ def evolutionary_component_assignment(pcb_data, component_data, machine_number,
|
||||
return min(pop_val), population[np.argmin(pop_val)]
|
||||
|
||||
|
||||
class SpiderMonkeyOpt:
|
||||
def __init__(self, pop_size, pcb_data, component_data, machine_number, estimator):
|
||||
self.PcbData = pcb_data
|
||||
self.ComponentData = component_data
|
||||
self.Estimator = estimator
|
||||
|
||||
self.PopSize = pop_size
|
||||
self.LocalLimit = pop_size
|
||||
self.GlobalLimit = pop_size
|
||||
|
||||
self.MachineNum = machine_number
|
||||
self.GroupSize = 0
|
||||
# self.Dim = sum(data.fdn for _, data in component_data.iterrows()) + machine_number
|
||||
|
||||
self.CpPoints = defaultdict(int)
|
||||
self.CpIndex = defaultdict(int)
|
||||
self.CpNozzle = defaultdict(str)
|
||||
|
||||
self.Dim = 0
|
||||
for cp_idx, data in component_data.iterrows():
|
||||
# cp_feeders[cp_idx] = data.fdn
|
||||
|
||||
division_data = copy.deepcopy(data)
|
||||
feeder_limit, total_points = division_data.fdn, division_data.points
|
||||
surplus_points = total_points % feeder_limit
|
||||
for _ in range(feeder_limit):
|
||||
division_data.fdn, division_data.points = 1, math.floor(total_points / feeder_limit)
|
||||
if surplus_points:
|
||||
division_data.points += 1
|
||||
surplus_points -= 1
|
||||
|
||||
self.CpPoints[self.Dim], self.CpNozzle[self.Dim] = division_data.points, division_data.nz
|
||||
self.CpIndex[self.Dim] = cp_idx
|
||||
self.Dim += 1
|
||||
|
||||
self.Dim += machine_number
|
||||
|
||||
component_list = list(range(len(self.CpPoints)))
|
||||
self.GenPosition = []
|
||||
self.GenPopVal = []
|
||||
for _ in range(self.PopSize):
|
||||
random.shuffle(component_list)
|
||||
self.GenPosition.append(component_list.copy())
|
||||
idx, prev = 0, 0
|
||||
div = random.sample(range(len(component_list)), self.MachineNum - 1)
|
||||
div.append(len(component_list))
|
||||
div.sort(reverse=False)
|
||||
|
||||
for _ in range(1, self.MachineNum + 1):
|
||||
self.GenPosition[-1].append(div[idx] - prev)
|
||||
prev = div[idx]
|
||||
idx += 1
|
||||
|
||||
self.GenPopVal.append(self.CalIndividualVal(self.GenPosition[-1]))
|
||||
|
||||
self.GroupPoint = [[0 for _ in range(2)] for _ in range(self.PopSize)]
|
||||
self.GroupPart = 1
|
||||
|
||||
self.GenProb = None
|
||||
self.GlobalMin = self.GenPopVal[0]
|
||||
self.GlobalLeaderPosition = self.GenPosition[0].copy()
|
||||
self.GlobalLimitCount = 0
|
||||
|
||||
self.LocalMin = np.ones(self.PopSize) * 1e10
|
||||
self.LocalLeaderPosition = [[0 for _ in range(self.Dim)] for _ in range(self.PopSize)]
|
||||
self.LocalLimitCount = [0 for _ in range(self.PopSize)]
|
||||
|
||||
for k in range(self.GroupSize):
|
||||
self.LocalMin[k] = self.GenPopVal[int(self.GroupPoint[k, 0])]
|
||||
self.LocalLeaderPosition[k,:] = self.GenPosition[int(self.GroupPoint[k, 0]),:]
|
||||
|
||||
self.CrossoverRatio = 0.1
|
||||
|
||||
|
||||
def GlobalLearning(self):
|
||||
GlobalTrial = self.GlobalMin
|
||||
for i in range(self.PopSize):
|
||||
if self.GenPopVal[i] < self.GlobalMin:
|
||||
self.GlobalMin = self.GenPopVal[i]
|
||||
self.GlobalLeaderPosition = self.GenPosition[i].copy()
|
||||
|
||||
if math.fabs(GlobalTrial - self.GlobalMin) < 1e-5:
|
||||
self.GlobalLimitCount = self.GlobalLimitCount + 1
|
||||
else:
|
||||
self.GlobalLimitCount = 0
|
||||
|
||||
def LocalLearning(self):
|
||||
OldMin = np.zeros(self.PopSize)
|
||||
for k in range(self.GroupSize):
|
||||
OldMin[k] = self.LocalMin[k]
|
||||
|
||||
for k in range(self.GroupSize):
|
||||
i = int(self.GroupPoint[k][0])
|
||||
while i <= int(self.GroupPoint[k][1]):
|
||||
if self.GenPopVal[i] < self.LocalMin[k]:
|
||||
self.LocalMin[k] = self.GenPopVal[i]
|
||||
self.LocalLeaderPosition[k] = self.GenPosition[i].copy()
|
||||
i = i + 1
|
||||
|
||||
for k in range(self.GroupSize):
|
||||
if math.fabs(OldMin[k] - self.LocalMin[k]) < 1e-5:
|
||||
self.LocalLimitCount[k] = self.LocalLimitCount[k] + 1
|
||||
else:
|
||||
self.LocalLimitCount[k] = 0
|
||||
|
||||
def CalculateProbabilities(self):
|
||||
self.GenProb = [0 for _ in range(self.PopSize)]
|
||||
MaxVal = self.GenPopVal[0]
|
||||
i = 1
|
||||
while i < self.PopSize:
|
||||
if self.GenPopVal[i] > MaxVal:
|
||||
MaxVal = self.GenPopVal[i]
|
||||
i += 1
|
||||
for i in range(self.PopSize):
|
||||
self.GenProb[i] = (0.9 * (self.GenPopVal[i] / MaxVal)) + 0.1
|
||||
|
||||
def LocalLeaderPhase(self, k):
|
||||
lo = int(self.GroupPoint[k][0])
|
||||
hi = int(self.GroupPoint[k][1])
|
||||
i = lo
|
||||
while i <= hi:
|
||||
|
||||
NewGene1, NewGene2 = self.CrossoverOperation(self.GenPosition[i], self.LocalLeaderPosition[k])
|
||||
NewGeneVal1, NewGeneVal2 = self.CalIndividualVal(NewGene1), self.CalIndividualVal(NewGene2)
|
||||
|
||||
if NewGeneVal1 < self.GenPopVal[i]:
|
||||
self.GenPosition[i] = NewGene1
|
||||
self.GenPopVal[i] = NewGeneVal1
|
||||
|
||||
if NewGeneVal2 < self.GenPopVal[i]:
|
||||
self.GenPosition[i] = NewGene2
|
||||
self.GenPopVal[i] = NewGeneVal2
|
||||
i += 1
|
||||
|
||||
def GlobalLeaderPhase(self, k):
|
||||
lo = int(self.GroupPoint[k][0])
|
||||
hi = int(self.GroupPoint[k][1])
|
||||
i = lo
|
||||
l = lo
|
||||
while l < hi:
|
||||
if random.random() < self.GenProb[i]:
|
||||
l += 1
|
||||
NewGene1, NewGene2 = self.CrossoverOperation(self.GenPosition[i], self.GlobalLeaderPosition)
|
||||
NewGeneVal1, NewGeneVal2 = self.CalIndividualVal(NewGene1), self.CalIndividualVal(NewGene2)
|
||||
|
||||
if NewGeneVal1 < self.GenPopVal[i]:
|
||||
self.GenPosition[i] = NewGene1
|
||||
self.GenPopVal[i] = NewGeneVal1
|
||||
|
||||
if NewGeneVal2 < self.GenPopVal[i]:
|
||||
self.GenPosition[i] = NewGene2
|
||||
self.GenPopVal[i] = NewGeneVal2
|
||||
|
||||
i += 1
|
||||
if i == hi:
|
||||
i = lo
|
||||
|
||||
def LocalLeaderDecision(self):
|
||||
for k in range(self.GroupSize):
|
||||
if self.LocalLimitCount[k] > self.LocalLimit:
|
||||
i = self.GroupPoint[k][0]
|
||||
while i <= int(self.GroupPoint[k][1]):
|
||||
if random.random() >= self.CrossoverRatio:
|
||||
NewGenPosition = list(range(self.Dim - self.MachineNum))
|
||||
random.shuffle(NewGenPosition)
|
||||
|
||||
idx, prev = 0, 0
|
||||
div = random.sample(range(len(NewGenPosition)), self.MachineNum - 1)
|
||||
div.append(len(NewGenPosition))
|
||||
div.sort(reverse=False)
|
||||
|
||||
for _ in range(1, self.MachineNum + 1):
|
||||
NewGenPosition.append(div[idx] - prev)
|
||||
prev = div[idx]
|
||||
idx += 1
|
||||
|
||||
NewGenVal = self.CalIndividualVal(NewGenPosition)
|
||||
|
||||
if NewGenVal < self.GenPopVal[i]:
|
||||
self.GenPosition[i] = NewGenPosition.copy()
|
||||
self.GenPopVal[i] = NewGenVal
|
||||
else:
|
||||
NewGene1, NewGene2 = self.CrossoverOperation(self.GenPosition[i], self.GlobalLeaderPosition)
|
||||
NewGeneVal1, NewGeneVal2 = self.CalIndividualVal(NewGene1), self.CalIndividualVal(NewGene2)
|
||||
|
||||
if NewGeneVal1 < self.GenPopVal[i]:
|
||||
self.GenPosition[i] = NewGene1.copy()
|
||||
self.GenPopVal[i] = NewGeneVal1
|
||||
|
||||
if NewGeneVal2 < self.GenPopVal[i]:
|
||||
self.GenPosition[i] = NewGene2.copy()
|
||||
self.GenPopVal[i] = NewGeneVal2
|
||||
|
||||
i += 1
|
||||
|
||||
self.LocalLimitCount[k] = 0
|
||||
|
||||
def GlobalLeaderDecision(self):
|
||||
if self.GlobalLimitCount> self.GlobalLimit:
|
||||
self.GroupPart += 1
|
||||
self.GlobalLimitCount = 0
|
||||
self.CreateGroup()
|
||||
self.LocalLearning()
|
||||
|
||||
def CreateGroup(self):
|
||||
g = 0
|
||||
lo = 0
|
||||
|
||||
while lo < self.PopSize:
|
||||
hi = lo + int(self.PopSize / self.GroupPart)
|
||||
self.GroupPoint[g][0] = lo
|
||||
self.GroupPoint[g][1] = hi
|
||||
if self.PopSize - hi < int(self.PopSize / self.GroupPart):
|
||||
self.GroupPoint[g][1] = (self.PopSize - 1)
|
||||
g = g + 1
|
||||
lo = hi + 1
|
||||
self.GroupSize = g
|
||||
|
||||
def CrossoverOperation(self, gene1, gene2):
|
||||
len_ = len(gene1)
|
||||
sub1, sub2 = partially_mapped_crossover(gene1[0: len_ - self.MachineNum], gene2[0: len_ - self.MachineNum])
|
||||
|
||||
pos1, pos2 = random.randint(0, self.MachineNum - 1), random.randint(0, self.MachineNum - 1)
|
||||
machine_assign1, machine_assign2 = gene1[len_ - self.MachineNum:], gene2[len_ - self.MachineNum:]
|
||||
machine_assign1[pos1], machine_assign2[pos2] = machine_assign2[pos2], machine_assign1[pos1]
|
||||
while sum(machine_assign1) != len_ - self.MachineNum:
|
||||
machine_idx = random.randint(0, self.MachineNum - 1)
|
||||
if machine_assign1[machine_idx] == 0:
|
||||
continue
|
||||
if sum(machine_assign1) > len_ - self.MachineNum:
|
||||
machine_assign1[machine_idx] -= 1
|
||||
else:
|
||||
machine_assign1[machine_idx] += 1
|
||||
|
||||
while sum(machine_assign2) != len_ - self.MachineNum:
|
||||
machine_idx = random.randint(0, self.MachineNum - 1)
|
||||
if machine_assign2[machine_idx] == 0:
|
||||
continue
|
||||
if sum(machine_assign2) > len_ - self.MachineNum:
|
||||
machine_assign2[machine_idx] -= 1
|
||||
else:
|
||||
machine_assign2[machine_idx] += 1
|
||||
|
||||
sub1.extend(machine_assign1)
|
||||
sub2.extend(machine_assign2)
|
||||
return sub1, sub2
|
||||
|
||||
|
||||
def CalIndividualVal(self, gene):
|
||||
ComponentNum = len(self.ComponentData)
|
||||
assignment_result = [[0 for _ in range(ComponentNum)] for _ in range(self.MachineNum)]
|
||||
|
||||
idx, machine_index = 0, 0
|
||||
for num in range(self.Dim - self.MachineNum, self.Dim):
|
||||
for _ in range(gene[num]):
|
||||
assignment_result[machine_index][self.CpIndex[gene[idx]]] += self.CpPoints[gene[idx]]
|
||||
idx += 1
|
||||
machine_index += 1
|
||||
|
||||
val = 0
|
||||
cp_items = converter(self.PcbData, self.ComponentData, assignment_result)
|
||||
for machine_index in range(self.MachineNum):
|
||||
cp_points, cp_nozzle, board_width, board_height = cp_items[machine_index]
|
||||
val = max(val, self.Estimator.predict(cp_points, cp_nozzle, board_width, board_height))
|
||||
return val
|
||||
|
||||
def spider_monkey_component_assignment(pcb_data, component_data, machine_number, estimator):
|
||||
population_size, iteration_number = 20, 50
|
||||
|
||||
smo = SpiderMonkeyOpt(population_size, pcb_data, component_data, machine_number, estimator)
|
||||
|
||||
# ========================== Calling: GlobalLearning() ======================== #
|
||||
smo.GlobalLearning()
|
||||
|
||||
# ========================== Calling: create_group() ========================== #
|
||||
smo.CreateGroup()
|
||||
|
||||
# ========================= Calling: LocalLearning() ========================== #
|
||||
smo.LocalLearning()
|
||||
|
||||
# ================================= Looping ================================== #
|
||||
with tqdm(total=iteration_number) as pbar:
|
||||
pbar.set_description('spider monkey algorithm process for PCB assembly line balance')
|
||||
for _ in range(iteration_number):
|
||||
for k in range(smo.GroupSize):
|
||||
# ==================== Calling: LocalLeaderPhase() =================== #
|
||||
smo.LocalLeaderPhase(k)
|
||||
|
||||
# =================== Calling: CalculateProbabilities() ================== #
|
||||
smo.CalculateProbabilities()
|
||||
|
||||
for k in range(smo.GroupSize):
|
||||
# ==================== Calling: GlobalLeaderPhase() ================== #
|
||||
smo.GlobalLeaderPhase(k)
|
||||
|
||||
# ======================= Calling: GlobalLearning() ====================== #
|
||||
smo.GlobalLearning()
|
||||
|
||||
# ======================= Calling: LocalLearning() ======================= #
|
||||
smo.LocalLearning()
|
||||
|
||||
# ================== Calling: LocalLeaderDecision() ====================== #
|
||||
smo.LocalLeaderDecision()
|
||||
|
||||
# ===================== Calling: GlobalLeaderDecision() ================== #
|
||||
smo.GlobalLeaderDecision()
|
||||
|
||||
pbar.update(1)
|
||||
|
||||
assignment_result = [[0 for _ in range(len(component_data))] for _ in range(machine_number)]
|
||||
|
||||
idx, machine_index = 0, 0
|
||||
for num in range(smo.Dim - machine_number, smo.Dim):
|
||||
for _ in range(smo.GlobalLeaderPosition[num]):
|
||||
assignment_result[machine_index][smo.CpIndex[smo.GlobalLeaderPosition[idx]]] += \
|
||||
smo.CpPoints[smo.GlobalLeaderPosition[idx]]
|
||||
idx += 1
|
||||
machine_index += 1
|
||||
|
||||
return smo.GlobalMin, assignment_result
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def line_optimizer_reconfiguration(component_data, pcb_data, machine_number):
|
||||
# === assignment of heads to modules is omitted ===
|
||||
@@ -303,9 +625,8 @@ def line_optimizer_reconfiguration(component_data, pcb_data, machine_number):
|
||||
print('random component allocation algorithm process for PCB assembly line balance')
|
||||
val, assignment = random_component_assignment(pcb_data, component_data, machine_number, estimator)
|
||||
elif i == 1:
|
||||
# brute force
|
||||
# which is proved to be useless, since it only ran in reasonable time for the smaller test instances
|
||||
continue
|
||||
# spider monkey
|
||||
val, assignment = spider_monkey_component_assignment(pcb_data, component_data, machine_number, estimator)
|
||||
elif i == 2:
|
||||
# local search
|
||||
print('local search component allocation algorithm process for PCB assembly line balance')
|
||||
@@ -314,7 +635,8 @@ def line_optimizer_reconfiguration(component_data, pcb_data, machine_number):
|
||||
# evolutionary
|
||||
val, assignment = evolutionary_component_assignment(pcb_data, component_data, machine_number, estimator)
|
||||
else:
|
||||
# greedy: unclear description
|
||||
# brute force
|
||||
# which is proved to be useless, since it only ran in reasonable time for the smaller test instances
|
||||
continue
|
||||
|
||||
if optimal_val is None or val < optimal_val:
|
||||
@@ -324,3 +646,5 @@ def line_optimizer_reconfiguration(component_data, pcb_data, machine_number):
|
||||
raise Exception('no feasible solution! ')
|
||||
|
||||
return optimal_assignment
|
||||
|
||||
|
||||
|
39
optimizer.py
39
optimizer.py
@@ -1,7 +1,3 @@
|
||||
import time
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from dataloader import *
|
||||
from lineopt_genetic import line_optimizer_genetic
|
||||
from lineopt_heuristic import line_optimizer_heuristic
|
||||
@@ -12,9 +8,9 @@ from lineopt_model import line_optimizer_model
|
||||
from base_optimizer.optimizer_interface import *
|
||||
|
||||
|
||||
def optimizer(pcb_data, component_data, feeder_data, params):
|
||||
def optimizer(pcb_data, component_data, feeder_data, params, hinter=True):
|
||||
if params.machine_number == 1:
|
||||
assembly_info = [base_optimizer(1, pcb_data, component_data, feeder_data, params, hinter=True)]
|
||||
assembly_info = [base_optimizer(1, pcb_data, component_data, feeder_data, params, hinter=hinter)]
|
||||
return assembly_info
|
||||
|
||||
if params.line_optimizer == 'hyper-heuristic' or params.line_optimizer == 'heuristic' or params.line_optimizer \
|
||||
@@ -33,7 +29,7 @@ def optimizer(pcb_data, component_data, feeder_data, params):
|
||||
for machine_index in range(params.machine_number):
|
||||
assembly_info.append(base_optimizer(machine_index + 1, partial_pcb_data[machine_index],
|
||||
partial_component_data[machine_index],
|
||||
pd.DataFrame(columns=['slot', 'part']), params, hinter=True))
|
||||
pd.DataFrame(columns=['slot', 'part']), params, hinter=hinter))
|
||||
elif params.line_optimizer == 'mip-model':
|
||||
assembly_info = line_optimizer_model(component_data, pcb_data, params.machine_number)
|
||||
else:
|
||||
@@ -49,16 +45,14 @@ def main():
|
||||
parser.add_argument('--mode', default=1, type=int, help='mode: 0 -directly load pcb data without optimization '
|
||||
'for data analysis, 1 -optimize pcb data, 2 -batch test')
|
||||
parser.add_argument('--filename', default='PCB.txt', type=str, help='load pcb data')
|
||||
# parser.add_argument('--filename', default='chapter3-2/PCB2-8 Arg1.txt', type=str, help='load pcb data')
|
||||
|
||||
parser.add_argument('--comp_register', default=1, type=int, help='register the component according the pcb data')
|
||||
parser.add_argument('--machine_number', default=1, type=int, help='the number of machine in the assembly line')
|
||||
# parser.add_argument('--machine_optimizer', default='mip-model', type=str, help='optimizer for single machine')
|
||||
parser.add_argument('--machine_number', default=3, type=int, help='the number of machine in the assembly line')
|
||||
parser.add_argument('--machine_optimizer', default='feeder-priority', type=str, help='optimizer for single machine')
|
||||
parser.add_argument('--line_optimizer', default='hyper-heuristic', type=str, help='optimizer for PCB assembly line')
|
||||
# parser.add_argument('--line_optimizer', default='model', type=str, help='optimizer for PCB assembly line')
|
||||
parser.add_argument('--feeder_limit', default=1, type=int, help='the upper feeder limit for each type of component')
|
||||
parser.add_argument('--save', default=0, type=int, help='save the optimization result')
|
||||
parser.add_argument('--save_suffix', default='(10)', type=str, help='load pcb data')
|
||||
parser.add_argument('--save_suffix', default='(1)', type=str, help='load pcb data')
|
||||
params = parser.parse_args()
|
||||
|
||||
# 结果输出显示所有行和列
|
||||
@@ -69,6 +63,7 @@ def main():
|
||||
assembly_info = []
|
||||
for machine_index in range(len(partial_pcb_data)):
|
||||
opt_res = convert_pcbdata_to_result(partial_pcb_data[machine_index], partial_component_data[machine_index])
|
||||
|
||||
info = placement_info_evaluation(partial_component_data[machine_index], partial_pcb_data[machine_index],
|
||||
opt_res)
|
||||
assembly_info.append(info)
|
||||
@@ -76,6 +71,7 @@ def main():
|
||||
nozzle_hinter=True, component_hinter=True, feeder_hinter=True)
|
||||
|
||||
info.print()
|
||||
|
||||
if params.save:
|
||||
output_optimize_result(f'result/{params.filename[:-4]}-T-Solution-M0{machine_index + 1}',
|
||||
partial_component_data[machine_index], partial_pcb_data[machine_index], opt_res)
|
||||
@@ -89,7 +85,6 @@ def main():
|
||||
f'standard deviation: {np.std([info.total_time for info in assembly_info]): .3f}')
|
||||
|
||||
elif params.mode == 1:
|
||||
# sys.stdout = open(f'record/{params.filename[:-4]}-{params.line_optimizer}.txt', 'w')
|
||||
|
||||
# 加载PCB数据
|
||||
partial_pcb_data, partial_component_data, feeder_data = load_data(params.filename, load_feeder=True)
|
||||
@@ -108,7 +103,10 @@ def main():
|
||||
|
||||
print(f'finial assembly time: {max(info.total_time for info in assembly_info): .3f} s, '
|
||||
f'standard deviation: {np.std([info.total_time for info in assembly_info]): .3f}')
|
||||
|
||||
elif params.mode == 2:
|
||||
|
||||
# sys.stdout = open(f'record/dissertation-experiment.txt', 'w')
|
||||
machine_optimizer = ['two-phase', 'hybrid-genetic', 'cell-division', 'feeder-priority', 'aggregation']
|
||||
running_round = 10
|
||||
opt_columns = ['Cycle', 'Pick', 'Nozzle-Change', 'Running-Time']
|
||||
@@ -120,16 +118,18 @@ def main():
|
||||
for _, file in enumerate(os.listdir('data/')):
|
||||
if file[-3:] != 'txt':
|
||||
continue
|
||||
|
||||
partial_pcb_data, partial_component_data, feeder_data = load_data(file)
|
||||
pcb_data, component_data = merge_data(partial_pcb_data, partial_component_data)
|
||||
|
||||
for opt in machine_optimizer:
|
||||
for round_idx in range(running_round):
|
||||
print(f'--- file : {file}, round : {round_idx}, optimizer : {opt} --- ')
|
||||
print(f'--- file : {file}, round : {round_idx + 1}, optimizer : {opt} --- ')
|
||||
|
||||
params = parser.parse_args(['--machine_optimizer', opt, '--machine_number', str(1)])
|
||||
params = parser.parse_args(['--machine_optimizer', opt, '--machine_number', str(1), '--filename', file])
|
||||
|
||||
start_time = time.time()
|
||||
assembly_info = optimizer(pcb_data, component_data, feeder_data, params)
|
||||
assembly_info = optimizer(pcb_data, component_data, feeder_data, params, hinter=False)
|
||||
|
||||
opt_result[opt].loc[file + str(round_idx + 1), 'Cycle'] = assembly_info[0].cycle_counter
|
||||
opt_result[opt].loc[file + str(round_idx + 1), 'Pick'] = assembly_info[0].pickup_counter
|
||||
@@ -140,8 +140,7 @@ def main():
|
||||
for opt, result in opt_result.items():
|
||||
result.to_excel(writer, sheet_name=opt, float_format='%.3f', na_rep='')
|
||||
else:
|
||||
# line_optimizer = ['T-Solution', 'hyper-heuristic', 'genetic', 'reconfiguration']
|
||||
line_optimizer = ['genetic']
|
||||
line_optimizer = ['T-Solution', 'hyper-heuristic', 'genetic', 'reconfiguration']
|
||||
file_dirs = ['L01', 'L02', 'L03']
|
||||
|
||||
running_round = 10
|
||||
@@ -169,6 +168,7 @@ def main():
|
||||
warning_info = f'file: {file_dir}/{file}: an unexpected error occurs for data loader'
|
||||
warnings.warn(warning_info, SyntaxWarning)
|
||||
continue
|
||||
|
||||
machine_number = len(partial_pcb_data)
|
||||
if not os.path.exists(f'record/{file_dir}'):
|
||||
os.makedirs(f'record/{file_dir}')
|
||||
@@ -207,11 +207,12 @@ def main():
|
||||
['--filename', file_dir + '/' + file, '--machine_number', str(machine_number),
|
||||
'--line_optimizer', line_opt, '--save_suffix', f'({round_idx + 1})'])
|
||||
start_time = time.time()
|
||||
assembly_info = optimizer(merge_pcb_data, merge_component_data, params)
|
||||
assembly_info = optimizer(merge_pcb_data, merge_component_data, None, params)
|
||||
|
||||
line_opt_result[file_dir].loc[file, line_opt + str(round_idx + 1)] = max(
|
||||
info.total_time for info in assembly_info)
|
||||
line_opt_runtime[file_dir].loc[file, line_opt + str(round_idx + 1)] = time.time() - start_time
|
||||
|
||||
for machine_idx, info in enumerate(assembly_info):
|
||||
print(
|
||||
f'assembly time for machine {machine_idx + 1: d}: {info.total_time: .3f} s, '
|
||||
|
Reference in New Issue
Block a user