增加超启发式线体优化算法

This commit is contained in:
2024-05-17 22:52:49 +08:00
parent 6fa1f53f69
commit 7c9a900b95
13 changed files with 1731 additions and 1109 deletions

View File

@ -1,49 +1,18 @@
from base_optimizer.optimizer_common import *
from estimator import *
# 生产过程中不允许吸嘴更换/点的拾取贴装仅与供料器槽位/模组相关
def objective_value_calculate(component_assignment, component_nozzle, task_block_weight, machine_number):
machine_assembly_time = []
for machine_index in range(max_machine_index):
task_block_number, total_point_number = 0, sum(component_assignment[machine_index])
nozzle_points, nozzle_heads = defaultdict(int), defaultdict(int)
for part, points in enumerate(component_assignment[machine_index]):
nozzle = component_nozzle[part]
nozzle_points[nozzle] += points
nozzle_heads[nozzle] = 1
remaining_head = max_head_index - len(nozzle_heads)
nozzle_fraction = []
for nozzle, points in nozzle_points.items():
val = remaining_head * points / total_point_number
nozzle_heads[nozzle] += math.floor(val)
nozzle_fraction.append([nozzle, val - math.floor(val)])
remaining_head = max_head_index - sum(nozzle_heads.values())
sorted(nozzle_fraction, key=lambda x: x[1])
nozzle_fraction_index = 0
while remaining_head > 0:
nozzle_heads[nozzle_fraction[nozzle_fraction_index][0]] += 1
remaining_head -= 1
for nozzle, heads_number in nozzle_heads.items():
task_block_number = max(task_block_weight, math.ceil(nozzle_points[nozzle] / heads_number))
machine_assembly_time.append(
(t_pick + t_place) * sum(component_assignment[machine_index]) + task_block_number * task_block_weight)
return max(machine_assembly_time)
def random_component_assignment(component_points, component_nozzle, component_feeders, task_block_weight, machine_number):
component_points_cpy = copy.deepcopy(component_points)
component_number = len(component_points_cpy)
assignment_result = [[0 for _ in range(component_number)] for _ in range(machine_number)]
def random_component_assignment(pcb_data, component_data, machine_number, estimator=None):
# == the set of feasible component type for each nozzle type
nozzle_part_list = defaultdict(list)
for index, nozzle in enumerate(component_nozzle):
nozzle_part_list[nozzle].append(index)
component_points = []
for idx, data in component_data.iterrows():
component_points.append(data.points)
nozzle_part_list[data.nz].append(idx)
component_number = len(component_data)
assignment_result = [[0 for _ in range(component_number)] for _ in range(machine_number)]
# === ensure every nozzle types ===
selected_part = []
for part_list in nozzle_part_list.values():
@ -51,7 +20,7 @@ def random_component_assignment(component_points, component_nozzle, component_fe
machine_index = random.randint(0, machine_number - 1)
assignment_result[machine_index][part] += 1
component_points_cpy[part] -= 1
component_points[part] -= 1
selected_part.append(part)
# === assign one placement which has not been selected ===
@ -60,7 +29,7 @@ def random_component_assignment(component_points, component_nozzle, component_fe
continue
assignment_result[random.randint(0, machine_number - 1)][part] += 1
component_points_cpy[part] -= 1
component_points[part] -= 1
machine_assign = list(range(machine_number))
random.shuffle(machine_assign)
@ -73,62 +42,74 @@ def random_component_assignment(component_points, component_nozzle, component_fe
if assignment_result[idx][part] > 0 or idx == machine_index:
feeder_counter += 1
if component_points_cpy[part] == 0 or feeder_counter > component_feeders[part]:
if component_points[part] == 0 or feeder_counter > component_data.iloc[part]['feeder-limit']:
continue
# feeder limit restriction
points = random.randint(1, component_points_cpy[part])
points = random.randint(1, component_points[part])
assignment_result[machine_index][part] += points
component_points_cpy[part] -= points
if component_points_cpy[part] == 0:
component_points[part] -= points
if component_points[part] == 0:
finished_assign_counter += 1
assert sum(component_points_cpy) == 0
assert sum(component_points) == 0
val = 0
if estimator:
cp_items = estimator.convert(pcb_data, component_data, assignment_result)
for machine_index in range(machine_number):
cp_points, cp_nozzle, cp_width, cp_height, board_width, board_height = cp_items[machine_index]
# objective_value.append(
# estimator.neural_network(cp_points, cp_nozzle, cp_width, cp_height, board_width, board_height))
val = max(val, estimator.heuristic(cp_points, cp_nozzle))
return objective_value_calculate(assignment_result, component_nozzle, task_block_weight), assignment_result
return val, assignment_result
def greedy_component_assignment(component_points, component_nozzle, component_feeders, task_block_weight):
pass # 不清楚原文想说什么
def local_search_component_assignment(component_points, component_nozzle, component_feeders, task_block_weight,
machine_number):
def local_search_component_assignment(pcb_data, component_data, machine_number, estimator):
# maximum number of iterations : 5000
# maximum number of unsuccessful iterations: 50
component_number = len(component_points)
component_number = len(component_data)
iteration_counter, unsuccessful_iteration_counter = 5000, 50
optimal_val, optimal_assignment = random_component_assignment(component_points, component_nozzle, component_feeders,
task_block_weight, machine_number)
optimal_val, optimal_assignment = random_component_assignment(pcb_data, component_data, machine_number, estimator)
for _ in range(iteration_counter):
machine_index = random.randint(0, machine_number - 1)
if sum(optimal_assignment[machine_index]) == 0:
machine_idx = random.randint(0, machine_number - 1)
if sum(optimal_assignment[machine_idx]) == 0:
continue
part_set = []
for component_index in range(component_number):
if optimal_assignment[machine_index][component_index] != 0:
part_set.append(component_index)
component_index = random.sample(part_set, 1)[0]
r = random.randint(1, optimal_assignment[machine_index][component_index])
for part_idx in range(component_number):
if optimal_assignment[machine_idx][part_idx] != 0:
part_set.append(part_idx)
part_idx = random.sample(part_set, 1)[0]
r = random.randint(1, optimal_assignment[machine_idx][part_idx])
assignment = copy.deepcopy(optimal_assignment)
cyclic_counter = 0
swap_machine_index = None
while cyclic_counter <= 2 * machine_index:
swap_machine_idx = None
while cyclic_counter <= 2 * machine_idx:
cyclic_counter += 1
swap_machine_index = random.randint(0, machine_number - 1)
swap_machine_idx = random.randint(0, machine_number - 1)
feeder_available = 0
for machine in range(machine_number):
if optimal_assignment[machine][component_index] or machine == swap_machine_index:
if optimal_assignment[machine][part_idx] or machine == swap_machine_idx:
feeder_available += 1
if feeder_available <= component_feeders[component_index] and swap_machine_index != machine_index:
if feeder_available <= component_data.iloc[part_idx]['feeder-limit'] and swap_machine_idx != machine_idx:
break
assert swap_machine_index is not None
assignment[machine_index][component_index] -= r
assignment[swap_machine_index][component_index] += r
val = objective_value_calculate(assignment, component_nozzle, task_block_weight)
assert swap_machine_idx is not None
assignment[machine_idx][part_idx] -= r
assignment[swap_machine_idx][part_idx] += r
val = 0
cp_items = estimator.convert(pcb_data, component_data, assignment)
for machine_index in range(machine_number):
cp_points, cp_nozzle, _, _, _, _ = cp_items[machine_index]
val = max(val, estimator.heuristic(cp_points, cp_nozzle))
if val < optimal_val:
optimal_assignment, optimal_val = assignment, val
unsuccessful_iteration_counter = 50
@ -140,9 +121,9 @@ def local_search_component_assignment(component_points, component_nozzle, compon
return optimal_val, optimal_assignment
def reconfig_crossover_operation(component_points, component_feeders, parent1, parent2, machine_number):
def reconfig_crossover_operation(component_data, parent1, parent2, machine_number):
offspring1, offspring2 = copy.deepcopy(parent1), copy.deepcopy(parent2)
component_number = len(component_points)
component_number = len(component_data)
# === crossover ===
mask_bit = []
@ -161,57 +142,57 @@ def reconfig_crossover_operation(component_points, component_feeders, parent1, p
# === balancing ===
# equally to reach the correct number
for component_index in range(component_number):
for part_index in range(component_number):
for offspring in [offspring1, offspring2]:
additional_points = sum([offspring[mt][component_index] for mt in range(machine_number)]) - \
component_points[component_index]
additional_points = sum([offspring[mt][part_index] for mt in range(machine_number)]) - \
component_data.iloc[part_index]['points']
if additional_points > 0:
# if a component type has more placements, decrease the assigned values on every head equally keeping
# the proportion of the number of placement among the heads
points_list = []
for machine_index in range(machine_number):
points = math.floor(
additional_points * offspring[machine_index][component_index] / component_points[component_index])
additional_points * offspring[machine_index][part_index] / component_data[part_index]['points'])
points_list.append(points)
offspring[machine_index][component_index] -= points
offspring[machine_index][part_index] -= points
additional_points -= sum(points_list)
for machine_index in range(machine_number):
if additional_points == 0:
break
if offspring[machine_index][component_index] == 0:
if offspring[machine_index][part_index] == 0:
continue
offspring[machine_index][component_index] -= 1
offspring[machine_index][part_index] -= 1
additional_points += 1
elif additional_points < 0:
# otherwise, increase the assigned nonzero values equally
machine_set = []
for machine_index in range(machine_number):
if offspring[machine_index][component_index] == 0:
if offspring[machine_index][part_index] == 0:
continue
machine_set.append(machine_index)
points = -math.ceil(additional_points / len(machine_set))
for machine_index in machine_set:
offspring[machine_index][component_index] += points
offspring[machine_index][part_index] += points
additional_points += points
for machine_index in machine_set:
if additional_points == 0:
break
offspring[machine_index][component_index] += 1
offspring[machine_index][part_index] += 1
additional_points -= 1
# === 结果校验 ===
for offspring in [offspring1, offspring2]:
for part in range(component_number):
pt = sum(offspring[mt][part] for mt in range(machine_number))
assert pt == component_points[part]
assert pt == component_data.iloc[part]['points']
return offspring1, offspring2
def reconfig_mutation_operation(component_feeders, parent, machine_number):
def reconfig_mutation_operation(component_data, parent, machine_number):
offspring = copy.deepcopy(parent)
swap_direction = random.randint(0, 1)
@ -228,10 +209,10 @@ def reconfig_mutation_operation(component_feeders, parent, machine_number):
swap_points = random.randint(1, offspring[swap_machine1][swap_component_index])
feeder_counter = 0
for machine_index in range(max_machine_index):
for machine_index in range(machine_number):
if offspring[swap_machine1][swap_component_index] < swap_points or machine_index == swap_machine2:
feeder_counter += 1
if feeder_counter > component_feeders[swap_component_index]:
if feeder_counter > component_data.iloc[swap_component_index]['feeder-limit']:
return offspring
offspring[swap_machine1][swap_component_index] -= swap_points
@ -239,7 +220,7 @@ def reconfig_mutation_operation(component_feeders, parent, machine_number):
return offspring
def evolutionary_component_assignment(component_points, component_nozzle, component_feeders, task_block_weight, machine_number):
def evolutionary_component_assignment(pcb_data, component_data, machine_number, estimator):
# population size: 10
# probability of the mutation: 0.1
# probability of the crossover: 0.8
@ -250,9 +231,7 @@ def evolutionary_component_assignment(component_points, component_nozzle, compon
population = []
for _ in range(population_size):
population.append(
random_component_assignment(component_points, component_nozzle, component_feeders, task_block_weight,
machine_number)[1])
population.append(random_component_assignment(pcb_data, component_data, machine_number, None)[1])
with tqdm(total=generation_number) as pbar:
pbar.set_description('evolutionary algorithm process for PCB assembly line balance')
@ -262,7 +241,12 @@ def evolutionary_component_assignment(component_points, component_nozzle, compon
# calculate fitness value
pop_val = []
for individual in population:
pop_val.append(objective_value_calculate(individual, component_nozzle, task_block_weight, machine_number))
val = 0
cp_items = estimator.convert(pcb_data, component_data, individual)
for machine_index in range(machine_number):
cp_points, cp_nozzle, _, _, _, _ = cp_items[machine_index]
val = max(val, estimator.heuristic(cp_points, cp_nozzle))
pop_val.append(val)
select_index = get_top_k_value(pop_val, population_size - len(new_population), reverse=False)
population = [population[idx] for idx in select_index]
@ -270,7 +254,12 @@ def evolutionary_component_assignment(component_points, component_nozzle, compon
population += new_population
for individual in new_population:
pop_val.append(objective_value_calculate(individual, component_nozzle, task_block_weight, machine_number))
cp_items = estimator.convert(pcb_data, component_data, individual)
val = 0
for machine_index in range(machine_index):
cp_points, cp_nozzle, _, _, _, _ = cp_items[machine_index]
val = max(val, estimator.heuristic(cp_points, cp_nozzle))
pop_val.append(val)
# min-max convert
max_val = max(pop_val)
@ -288,15 +277,14 @@ def evolutionary_component_assignment(component_points, component_nozzle, compon
if index1 != index2:
break
offspring1, offspring2 = reconfig_crossover_operation(component_points, component_feeders,
population[index1], population[index2],
machine_number)
offspring1, offspring2 = reconfig_crossover_operation(component_data, population[index1],
population[index2], machine_number)
if np.random.random() < mutation_rate:
offspring1 = reconfig_mutation_operation(component_feeders, offspring1, machine_number)
offspring1 = reconfig_mutation_operation(component_data, offspring1, machine_number)
if np.random.random() < mutation_rate:
offspring2 = reconfig_mutation_operation(component_feeders, offspring2, machine_number)
offspring2 = reconfig_mutation_operation(component_data, offspring2, machine_number)
new_population.append(offspring1)
new_population.append(offspring2)
@ -306,47 +294,26 @@ def evolutionary_component_assignment(component_points, component_nozzle, compon
return min(pop_val), population[np.argmin(pop_val)]
def reconfiguration_optimizer(pcb_data, component_data, machine_number):
# === data preparation ===
component_number = len(component_data)
component_points = [0 for _ in range(component_number)]
component_nozzle = [0 for _ in range(component_number)]
component_feeders = [0 for _ in range(component_number)]
component_part = [0 for _ in range(component_number)]
for _, data in pcb_data.iterrows():
part_index = component_data[component_data['part'] == data['part']].index.tolist()[0]
nozzle = component_data.loc[part_index]['nz']
component_points[part_index] += 1
component_nozzle[part_index] = nozzle
component_part[part_index] = data['part']
component_feeders[part_index] = component_data.loc[part_index]['feeder-limit']
@timer_wrapper
def line_optimizer_reconfiguration(component_data, pcb_data, machine_number):
# === assignment of heads to modules is omitted ===
optimal_assignment, optimal_val = [], None
task_block_weight = 5 # element from list [0, 1, 2, 5, 10] task_block ~= cycle
estimator = Estimator(task_block_weight=5) # element from list [0, 1, 2, 5, 10] task_block ~= cycle
# === assignment of components to heads
for i in range(5):
if i == 0:
# random
val, assignment = random_component_assignment(component_points, component_nozzle, component_feeders,
task_block_weight, machine_number)
val, assignment = random_component_assignment(pcb_data, component_data, machine_number, estimator)
elif i == 1:
# brute force
# which is proved to be useless, since it only ran in reasonable time for the smaller test instances
continue
elif i == 2:
# local search
val, assignment = local_search_component_assignment(component_points, component_nozzle, component_feeders,
task_block_weight, machine_number)
val, assignment = local_search_component_assignment(pcb_data, component_data, machine_number, estimator)
elif i == 3:
# evolutionary
val, assignment = evolutionary_component_assignment(component_points, component_nozzle, component_feeders,
task_block_weight, machine_number)
val, assignment = evolutionary_component_assignment(pcb_data, component_data, machine_number, estimator)
else:
# greedy: unclear description
continue