增加单机优化方法
This commit is contained in:
@ -1,34 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Python code of Spider-Monkey Optimization (SMO)
|
||||
Coded by: Mukesh Saraswat (emailid: saraswatmukesh@gmail.com), Himanshu Mittal (emailid: himanshu.mittal224@gmail.com) and Raju Pal (emailid: raju3131.pal@gmail.com)
|
||||
The code template used is similar to code given at link: https://github.com/himanshuRepo/CKGSA-in-Python
|
||||
and C++ version of the SMO at link: http://smo.scrs.in/
|
||||
|
||||
Reference: Jagdish Chand Bansal, Harish Sharma, Shimpi Singh Jadon, and Maurice Clerc. "Spider monkey optimization algorithm for numerical optimization." Memetic computing 6, no. 1, 31-47, 2014.
|
||||
@link: http://smo.scrs.in/
|
||||
|
||||
-- Benchmark.py: Defining the benchmark function along its range lower bound, upper bound and dimensions
|
||||
|
||||
Code compatible:
|
||||
-- Python: 2.* or 3.*
|
||||
"""
|
||||
|
||||
import numpy
|
||||
import math
|
||||
|
||||
|
||||
# define the function blocks
|
||||
def F1(x):
|
||||
s = numpy.sum(x ** 2);
|
||||
return s
|
||||
|
||||
|
||||
# define the function parameters
|
||||
def getFunctionDetails():
|
||||
# [name, lb, ub, dim, acc_err, obj_val]
|
||||
param = ["F1", -100, 100, 30, 1.0e-5, 0]
|
||||
return param
|
||||
|
||||
|
||||
|
79
SMO/main.py
79
SMO/main.py
@ -1,79 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Python code of Spider-Monkey Optimization (SMO)
|
||||
Coded by: Mukesh Saraswat (emailid: saraswatmukesh@gmail.com), Himanshu Mittal (emailid: himanshu.mittal224@gmail.com) and Raju Pal (emailid: raju3131.pal@gmail.com)
|
||||
The code template used is similar to code given at link: https://github.com/himanshuRepo/CKGSA-in-Python
|
||||
and C++ version of the SMO at link: http://smo.scrs.in/
|
||||
|
||||
Reference: Jagdish Chand Bansal, Harish Sharma, Shimpi Singh Jadon, and Maurice Clerc. "Spider monkey optimization algorithm for numerical optimization." Memetic computing 6, no. 1, 31-47, 2014.
|
||||
@link: http://smo.scrs.in/
|
||||
|
||||
-- Main.py: Calling the Spider-Monkey Optimization (SMO) Algorithm
|
||||
for minimizing of an objective Function
|
||||
|
||||
Code compatible:
|
||||
-- Python: 2.* or 3.*
|
||||
"""
|
||||
import smo
|
||||
import benchmarks
|
||||
import csv
|
||||
import numpy
|
||||
import time
|
||||
import math
|
||||
|
||||
|
||||
def selector(func_details, popSize, Iter, succ_rate, mean_feval):
|
||||
function_name = func_details[0]
|
||||
lb = func_details[1]
|
||||
ub = func_details[2]
|
||||
dim = func_details[3]
|
||||
acc_err = func_details[4]
|
||||
obj_val = func_details[5]
|
||||
|
||||
|
||||
x, succ_rate, mean_feval = smo.main(getattr(benchmarks, function_name), lb, ub, dim, popSize, Iter, acc_err,
|
||||
obj_val, succ_rate, mean_feval)
|
||||
return x, succ_rate, mean_feval
|
||||
|
||||
|
||||
# Select number of repetitions for each experiment.
|
||||
# To obtain meaningful statistical results, usually 30 independent runs are executed for each algorithm.
|
||||
NumOfRuns = 2
|
||||
|
||||
# Select general parameters for all optimizers (population size, number of iterations)
|
||||
PopulationSize = 10
|
||||
Iterations = 500
|
||||
|
||||
mean_error = 0
|
||||
total_feval = 0
|
||||
mean1 = 0
|
||||
var = 0
|
||||
sd = 0
|
||||
mean_feval = 0
|
||||
succ_rate = 0
|
||||
GlobalMins = numpy.zeros(NumOfRuns)
|
||||
|
||||
for k in range(0, NumOfRuns):
|
||||
|
||||
func_details = benchmarks.getFunctionDetails()
|
||||
print("Run: {}".format(k + 1))
|
||||
x, succ_rate, mean_feval = selector(func_details, PopulationSize, Iterations, succ_rate, mean_feval)
|
||||
mean_error = mean_error + x.error;
|
||||
mean1 = mean1 + x.convergence[-1]
|
||||
total_feval = total_feval + x.feval
|
||||
GlobalMins[k] = x.convergence[-1]
|
||||
|
||||
|
||||
mean1 = mean1 / NumOfRuns;
|
||||
mean_error = mean_error / NumOfRuns
|
||||
if (succ_rate > 0):
|
||||
mean_feval = mean_feval / succ_rate
|
||||
total_feval = total_feval / NumOfRuns
|
||||
for k in range(NumOfRuns):
|
||||
var = var + math.pow((GlobalMins[k] - mean1), 2)
|
||||
var = var / NumOfRuns
|
||||
sd = math.sqrt(var)
|
||||
|
||||
print(
|
||||
"Values after executing SMO: \n Mean Error:{} \n Mean Function eval:{} \n Total Function eval:{} \n Variance:{} \n STD:{}".format(
|
||||
mean_error, mean_feval, total_feval, var, sd))
|
348
SMO/smo.py
348
SMO/smo.py
@ -1,348 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Python code of Spider-Monkey Optimization (SMO)
|
||||
Coded by: Mukesh Saraswat (emailid: saraswatmukesh@gmail.com), Himanshu Mittal (emailid: himanshu.mittal224@gmail.com) and Raju Pal (emailid: raju3131.pal@gmail.com)
|
||||
The code template used is similar to code given at link: https://github.com/himanshuRepo/CKGSA-in-Python
|
||||
and C++ version of the SMO at link: http://smo.scrs.in/
|
||||
|
||||
Reference: Jagdish Chand Bansal, Harish Sharma, Shimpi Singh Jadon, and Maurice Clerc. "Spider monkey optimization algorithm for numerical optimization." Memetic computing 6, no. 1, 31-47, 2014.
|
||||
@link: http://smo.scrs.in/
|
||||
|
||||
-- smo.py: Performing the Spider-Monkey Optimization (SMO) Algorithm
|
||||
|
||||
Code compatible:
|
||||
-- Python: 2.* or 3.*
|
||||
"""
|
||||
|
||||
from __future__ import division
|
||||
import time
|
||||
import random
|
||||
import numpy
|
||||
import math
|
||||
from solution import solution
|
||||
|
||||
|
||||
class SMO():
|
||||
def __init__(self, objf1, lb1, ub1, dim1, PopSize1, acc_err1, iters1):
|
||||
self.PopSize = PopSize1
|
||||
self.dim = dim1
|
||||
self.acc_err = acc_err1
|
||||
self.lb = lb1
|
||||
self.ub = ub1
|
||||
self.objf = objf1
|
||||
self.pos = numpy.zeros((PopSize1, dim1))
|
||||
self.fun_val = numpy.zeros(PopSize1)
|
||||
self.fitness = numpy.zeros(PopSize1)
|
||||
self.gpoint = numpy.zeros((PopSize1, 2))
|
||||
self.prob = numpy.zeros(PopSize1)
|
||||
self.LocalLimit = dim1 * PopSize1;
|
||||
self.GlobalLimit = PopSize1;
|
||||
self.fit = numpy.zeros(PopSize1)
|
||||
self.MinCost = numpy.zeros(iters1)
|
||||
self.Bestpos = numpy.zeros(dim1)
|
||||
self.group = 0
|
||||
self.func_eval = 0
|
||||
self.part = 1
|
||||
self.max_part = 5
|
||||
self.cr = 0.1
|
||||
|
||||
# ====== Function: CalculateFitness() ========= #
|
||||
def CalculateFitness(self, fun1):
|
||||
if fun1 >= 0:
|
||||
result = (1 / (fun1 + 1))
|
||||
else:
|
||||
result = (1 + math.fabs(fun1))
|
||||
return result
|
||||
|
||||
# ================ X X X ===================== #
|
||||
|
||||
# ==================================== Function: Initialization() ============================================ #
|
||||
def initialize(self):
|
||||
global GlobalMin, GlobalLeaderPosition, GlobalLimitCount, LocalMin, LocalLimitCount, LocalLeaderPosition
|
||||
S_max = int(self.PopSize / 2)
|
||||
LocalMin = numpy.zeros(S_max)
|
||||
LocalLeaderPosition = numpy.zeros((S_max, self.dim))
|
||||
LocalLimitCount = numpy.zeros(S_max)
|
||||
for i in range(self.PopSize):
|
||||
for j in range(self.dim):
|
||||
if type(self.ub) == int:
|
||||
self.pos[i, j] = random.random() * (self.ub - self.lb) + self.lb
|
||||
else:
|
||||
self.pos[i, j] = random.random() * (self.ub[j] - self.lb[j]) + self.lb[j]
|
||||
# Calculate objective function for each particle
|
||||
for i in range(self.PopSize):
|
||||
# Performing the bound checking
|
||||
self.pos[i, :] = numpy.clip(self.pos[i, :], self.lb, self.ub)
|
||||
self.fun_val[i] = self.objf(self.pos[i, :])
|
||||
self.func_eval += 1
|
||||
self.fitness[i] = self.CalculateFitness(self.fun_val[i])
|
||||
|
||||
# Initialize Global Leader Learning
|
||||
GlobalMin = self.fun_val[0]
|
||||
GlobalLeaderPosition = self.pos[0, :]
|
||||
GlobalLimitCount = 0
|
||||
|
||||
# Initialize Local Leader Learning
|
||||
for k in range(self.group):
|
||||
LocalMin[k] = self.fun_val[int(self.gpoint[k, 0])]
|
||||
LocalLimitCount[k] = 0
|
||||
LocalLeaderPosition[k, :] = self.pos[int(self.gpoint[k, 0]), :]
|
||||
|
||||
# ============================================ X X X ======================================================= #
|
||||
|
||||
# =========== Function: CalculateProbabilities() ============ #
|
||||
def CalculateProbabilities(self):
|
||||
maxfit = self.fitness[0];
|
||||
i = 1
|
||||
while (i < self.PopSize):
|
||||
if (self.fitness[i] > maxfit):
|
||||
maxfit = self.fitness[i];
|
||||
i += 1
|
||||
for i in range(self.PopSize):
|
||||
self.prob[i] = (0.9 * (self.fitness[i] / maxfit)) + 0.1;
|
||||
|
||||
# ========================== X X X ======================== #
|
||||
|
||||
# ================= Function: create_group() ================ #
|
||||
def create_group(self):
|
||||
g = 0
|
||||
lo = 0
|
||||
while (lo < self.PopSize):
|
||||
hi = lo + int(self.PopSize / self.part)
|
||||
self.gpoint[g, 0] = lo
|
||||
self.gpoint[g, 1] = hi
|
||||
if ((self.PopSize - hi) < (int(self.PopSize / self.part))):
|
||||
self.gpoint[g, 1] = (self.PopSize - 1)
|
||||
g = g + 1
|
||||
lo = hi + 1
|
||||
self.group = g
|
||||
|
||||
# ========================== X X X ======================== #
|
||||
|
||||
# ================= Function: LocalLearning() ================ #
|
||||
def LocalLearning(self):
|
||||
global LocalMin, LocalLimitCount, LocalLeaderPosition
|
||||
S_max = int(self.PopSize / 2)
|
||||
OldMin = numpy.zeros(S_max)
|
||||
for k in range(self.group):
|
||||
OldMin[k] = LocalMin[k]
|
||||
|
||||
for k in range(self.group):
|
||||
i = int(self.gpoint[k, 0])
|
||||
while (i <= int(self.gpoint[k, 1])):
|
||||
if (self.fun_val[i] < LocalMin[k]):
|
||||
LocalMin[k] = self.fun_val[i]
|
||||
LocalLeaderPosition[k, :] = self.pos[i, :]
|
||||
i = i + 1
|
||||
|
||||
for k in range(self.group):
|
||||
if (math.fabs(OldMin[k] - LocalMin[k]) < self.acc_err):
|
||||
LocalLimitCount[k] = LocalLimitCount[k] + 1
|
||||
else:
|
||||
LocalLimitCount[k] = 0
|
||||
|
||||
# ========================== X X X ======================== #
|
||||
|
||||
# ================= Function: GlobalLearning() ================ #
|
||||
def GlobalLearning(self):
|
||||
global GlobalMin, GlobalLeaderPosition, GlobalLimitCount
|
||||
G_trial = GlobalMin
|
||||
for i in range(self.PopSize):
|
||||
if (self.fun_val[i] < GlobalMin):
|
||||
GlobalMin = self.fun_val[i]
|
||||
GlobalLeaderPosition = self.pos[i, :]
|
||||
|
||||
if (math.fabs(G_trial - GlobalMin) < self.acc_err):
|
||||
GlobalLimitCount = GlobalLimitCount + 1
|
||||
else:
|
||||
GlobalLimitCount = 0
|
||||
|
||||
# ========================== X X X ======================== #
|
||||
|
||||
# ================= Function: LocalLeaderPhase() ================ #
|
||||
def LocalLeaderPhase(self, k):
|
||||
global LocalLeaderPosition
|
||||
new_position = numpy.zeros((1, self.dim))
|
||||
lo = int(self.gpoint[k, 0])
|
||||
hi = int(self.gpoint[k, 1])
|
||||
i = lo
|
||||
while (i <= hi):
|
||||
while True:
|
||||
PopRand = int((random.random() * (hi - lo) + lo))
|
||||
if (PopRand != i):
|
||||
break
|
||||
for j in range(self.dim):
|
||||
if (random.random() >= self.cr):
|
||||
new_position[0, j] = self.pos[i, j] + (LocalLeaderPosition[k, j] - self.pos[i, j]) * (
|
||||
random.random()) + (self.pos[PopRand, j] - self.pos[i, j]) * (random.random() - 0.5) * 2
|
||||
else:
|
||||
new_position[0, j] = self.pos[i, j]
|
||||
new_position = numpy.clip(new_position, self.lb, self.ub)
|
||||
|
||||
ObjValSol = self.objf(new_position)
|
||||
self.func_eval += 1
|
||||
FitnessSol = self.CalculateFitness(ObjValSol)
|
||||
if (FitnessSol > self.fitness[i]):
|
||||
self.pos[i, :] = new_position
|
||||
self.fun_val[i] = ObjValSol
|
||||
self.fitness[i] = FitnessSol
|
||||
i += 1
|
||||
|
||||
# ========================== X X X ======================== #
|
||||
|
||||
# ================= Function: GlobalLeaderPhase() ================ #
|
||||
def GlobalLeaderPhase(self, k):
|
||||
global GlobalLeaderPosition
|
||||
new_position = numpy.zeros((1, self.dim))
|
||||
lo = int(self.gpoint[k, 0])
|
||||
hi = int(self.gpoint[k, 1])
|
||||
i = lo;
|
||||
l = lo;
|
||||
while (l < hi):
|
||||
if (random.random() < self.prob[i]):
|
||||
l += 1
|
||||
while True:
|
||||
PopRand = int(random.random() * (hi - lo) + lo)
|
||||
if (PopRand != i):
|
||||
break
|
||||
param2change = int(random.random() * self.dim)
|
||||
new_position = self.pos[i, :]
|
||||
new_position[param2change] = self.pos[i, param2change] + (
|
||||
GlobalLeaderPosition[param2change] - self.pos[i, param2change]) * (random.random()) + (
|
||||
self.pos[PopRand, param2change] - self.pos[
|
||||
i, param2change]) * (random.random() - 0.5) * 2
|
||||
new_position = numpy.clip(new_position, self.lb, self.ub)
|
||||
ObjValSol = self.objf(new_position)
|
||||
self.func_eval += 1
|
||||
FitnessSol = self.CalculateFitness(ObjValSol)
|
||||
if (FitnessSol > self.fitness[i]):
|
||||
self.pos[i, :] = new_position
|
||||
self.fun_val[i] = ObjValSol
|
||||
self.fitness[i] = FitnessSol
|
||||
i += 1;
|
||||
if (i == hi):
|
||||
i = lo;
|
||||
|
||||
# ========================== X X X ======================== #
|
||||
|
||||
# ================= Function: GlobalLeaderDecision() ================ #
|
||||
def GlobalLeaderDecision(self):
|
||||
global GlobalLimitCount
|
||||
if (GlobalLimitCount > self.GlobalLimit):
|
||||
GlobalLimitCount = 0
|
||||
if (self.part < self.max_part):
|
||||
self.part = self.part + 1
|
||||
self.create_group()
|
||||
self.LocalLearning()
|
||||
else:
|
||||
self.part = 1
|
||||
self.create_group()
|
||||
self.LocalLearning()
|
||||
|
||||
# ========================== X X X ======================== #
|
||||
|
||||
# ================= Function: LocalLeaderDecision() ================ #
|
||||
def LocalLeaderDecision(self):
|
||||
global GlobalLeaderPosition, LocalLimitCount, LocalLeaderPosition
|
||||
for k in range(self.group):
|
||||
if (LocalLimitCount[k] > self.LocalLimit):
|
||||
i = self.gpoint[k, 0]
|
||||
while (i <= int(self.gpoint[k, 1])):
|
||||
for j in range(self.dim):
|
||||
if (random.random() >= self.cr):
|
||||
if type(self.ub) == int:
|
||||
self.pos[i, j] = random.random() * (self.ub - self.lb) + self.lb
|
||||
else:
|
||||
self.pos[i, j] = random.random() * (self.ub[j] - self.lb[j]) + self.lb[j]
|
||||
else:
|
||||
self.pos[i, j] = self.pos[i, j] + (
|
||||
GlobalLeaderPosition[j] - self.pos[i, j]) * random.random() + (
|
||||
self.pos[i, j] - LocalLeaderPosition[k, j]) * random.random()
|
||||
self.pos[i, :] = numpy.clip(self.pos[i, :], self.lb, self.ub)
|
||||
self.fun_val[i] = self.objf(self.pos[i, :])
|
||||
self.func_eval += 1
|
||||
self.fitness[i] = self.CalculateFitness(self.fun_val[i])
|
||||
i += 1
|
||||
LocalLimitCount[k] = 0
|
||||
# ========================== X X X ======================== #
|
||||
|
||||
|
||||
# ==================================== Main() ===================================== #
|
||||
def main(objf1, lb1, ub1, dim1, PopSize1, iters, acc_err1, obj_val, succ_rate, mean_feval):
|
||||
smo = SMO(objf1, lb1, ub1, dim1, PopSize1, acc_err1, iters)
|
||||
s = solution()
|
||||
print("SMO is optimizing \"" + smo.objf.__name__ + "\"")
|
||||
timerStart = time.time()
|
||||
s.startTime = time.strftime("%Y-%m-%d-%H-%M-%S")
|
||||
|
||||
# =========================== Calling: initialize() =========================== #
|
||||
smo.initialize()
|
||||
|
||||
# ========================== Calling: GlobalLearning() ======================== #
|
||||
smo.GlobalLearning()
|
||||
|
||||
# ========================= Calling: LocalLearning() ========================== #
|
||||
smo.LocalLearning()
|
||||
|
||||
# ========================== Calling: create_group() ========================== #
|
||||
smo.create_group()
|
||||
|
||||
# ================================= Looping ================================== #
|
||||
for l in range(iters):
|
||||
for k in range(smo.group):
|
||||
# ==================== Calling: LocalLeaderPhase() =================== #
|
||||
smo.LocalLeaderPhase(k)
|
||||
|
||||
# =================== Calling: CalculateProbabilities() ================== #
|
||||
smo.CalculateProbabilities()
|
||||
|
||||
for k in range(smo.group):
|
||||
# ==================== Calling: GlobalLeaderPhase() ================== #
|
||||
smo.GlobalLeaderPhase(k)
|
||||
|
||||
# ======================= Calling: GlobalLearning() ====================== #
|
||||
smo.GlobalLearning()
|
||||
|
||||
# ======================= Calling: LocalLearning() ======================= #
|
||||
smo.LocalLearning()
|
||||
|
||||
# ================== Calling: LocalLeaderDecision() ====================== #
|
||||
smo.LocalLeaderDecision()
|
||||
|
||||
# ===================== Calling: GlobalLeaderDecision() ================== #
|
||||
smo.GlobalLeaderDecision()
|
||||
|
||||
# ======================= Updating: 'cr' parameter ======================= #
|
||||
smo.cr = smo.cr + (0.4 / iters)
|
||||
|
||||
# ====================== Saving the best individual ====================== #
|
||||
smo.MinCost[l] = GlobalMin
|
||||
gBestScore = GlobalMin
|
||||
|
||||
# ================ Displaying the fitness of each iteration ============== #
|
||||
if (l % 1 == 0):
|
||||
print(['At iteration ' + str(l + 1) + ' the best fitness is ' + str(gBestScore)]);
|
||||
|
||||
# ====================== Checking: acc_error ============================ #
|
||||
if (math.fabs(GlobalMin - obj_val) <= smo.acc_err):
|
||||
succ_rate += 1
|
||||
mean_feval = mean_feval + smo.func_eval
|
||||
break
|
||||
# ========================= XXX Ending of Loop XXX ========================== #
|
||||
|
||||
# =========================== XX Result saving XX =========================== #
|
||||
error1 = math.fabs(GlobalMin - obj_val)
|
||||
timerEnd = time.time()
|
||||
s.endTime = time.strftime("%Y-%m-%d-%H-%M-%S")
|
||||
s.executionTime = timerEnd - timerStart
|
||||
s.convergence = smo.MinCost
|
||||
s.optimizer = "SMO"
|
||||
s.error = error1
|
||||
s.feval = smo.func_eval
|
||||
s.objfname = smo.objf.__name__
|
||||
|
||||
return s, succ_rate, mean_feval
|
||||
|
||||
# ================================ X X X =================================== #
|
||||
|
||||
|
@ -1,33 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Python code of Spider-Monkey Optimization (SMO)
|
||||
Coded by: Mukesh Saraswat (emailid: saraswatmukesh@gmail.com), Himanshu Mittal (emailid: himanshu.mittal224@gmail.com) and Raju Pal (emailid: raju3131.pal@gmail.com)
|
||||
The code template used is similar to code given at link: https://github.com/himanshuRepo/CKGSA-in-Python
|
||||
and C++ version of the SMO at link: http://smo.scrs.in/
|
||||
|
||||
Reference: Jagdish Chand Bansal, Harish Sharma, Shimpi Singh Jadon, and Maurice Clerc. "Spider monkey optimization algorithm for numerical optimization." Memetic computing 6, no. 1, 31-47, 2014.
|
||||
@link: http://smo.scrs.in/
|
||||
|
||||
-- solution.py: Defining the solution variable for saving the output variables
|
||||
|
||||
Code compatible:
|
||||
-- Python: 2.* or 3.*
|
||||
"""
|
||||
|
||||
class solution:
|
||||
def __init__(self):
|
||||
self.best = 0
|
||||
self.bestIndividual=[]
|
||||
self.convergence = []
|
||||
self.optimizer=""
|
||||
self.objfname=""
|
||||
self.startTime=0
|
||||
self.endTime=0
|
||||
self.executionTime=0
|
||||
self.lb=0
|
||||
self.ub=0
|
||||
self.dim=0
|
||||
self.popnum=0
|
||||
self.error =0
|
||||
self.feval=0
|
||||
self.maxiers=0
|
220
base_optimizer/optimizer_aggregation.py
Normal file
220
base_optimizer/optimizer_aggregation.py
Normal file
@ -0,0 +1,220 @@
|
||||
from base_optimizer.optimizer_common import *
|
||||
|
||||
from ortools.sat.python import cp_model
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def optimizer_aggregation(component_data, pcb_data):
|
||||
# === phase 0: data preparation ===
|
||||
M = 1000 # a sufficient large number
|
||||
a, b = 1, 6 # coefficient
|
||||
K, I, J, L = max_head_index, 0, 0, 0 # the maximum number of heads, component types, nozzle types and batch level
|
||||
|
||||
component_list, nozzle_list = defaultdict(int), defaultdict(int)
|
||||
cpidx_2_part, nzidx_2_nozzle = {}, {}
|
||||
for _, data in pcb_data.iterrows():
|
||||
part = data['part']
|
||||
if part not in cpidx_2_part.values():
|
||||
cpidx_2_part[len(cpidx_2_part)] = part
|
||||
|
||||
component_list[part] += 1
|
||||
|
||||
idx = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
nozzle = component_data.loc[idx]['nz']
|
||||
if nozzle not in nzidx_2_nozzle.values():
|
||||
nzidx_2_nozzle[len(nzidx_2_nozzle)] = nozzle
|
||||
nozzle_list[nozzle] += 1
|
||||
|
||||
I, J = len(component_list.keys()), len(nozzle_list.keys())
|
||||
L = I + 1
|
||||
HC = [[M for _ in range(J)] for _ in range(I)] # the handing class when component i is handled by nozzle type j
|
||||
# represent the nozzle-component compatibility
|
||||
for i in range(I):
|
||||
for _, item in enumerate(cpidx_2_part.items()):
|
||||
index, part = item
|
||||
cp_idx = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
nozzle = component_data.loc[cp_idx]['nz']
|
||||
|
||||
for j in range(J):
|
||||
if nzidx_2_nozzle[j] == nozzle:
|
||||
HC[index][j] = 0
|
||||
|
||||
# === phase 1: mathematical model solver ===
|
||||
model = cp_model.CpModel()
|
||||
solver = cp_model.CpSolver()
|
||||
|
||||
# === Decision Variables ===
|
||||
# the number of components of type i that are placed by nozzle type j on placement head k
|
||||
X = {}
|
||||
for i in range(I):
|
||||
for j in range(J):
|
||||
for k in range(K):
|
||||
X[i, j, k] = model.NewIntVar(0, component_list[cpidx_2_part[i]], 'X_{}_{}_{}'.format(i, j, k))
|
||||
|
||||
# the total number of nozzle changes on placement head k
|
||||
N = {}
|
||||
for k in range(K):
|
||||
N[k] = model.NewIntVar(0, J, 'N_{}'.format(k))
|
||||
|
||||
# the largest workload of all placement heads
|
||||
WL = model.NewIntVar(0, len(pcb_data), 'WL')
|
||||
|
||||
# whether batch Xijk is placed on level l
|
||||
Z = {}
|
||||
for i in range(I):
|
||||
for j in range(J):
|
||||
for l in range(L):
|
||||
for k in range(K):
|
||||
Z[i, j, l, k] = model.NewBoolVar('Z_{}_{}_{}_{}'.format(i, j, l, k))
|
||||
|
||||
# Dlk := 2 if a change of nozzles in the level l + 1 on placement head k
|
||||
# Dlk := 1 if there are no batches placed on levels higher than l
|
||||
D = {}
|
||||
for l in range(L):
|
||||
for k in range(K):
|
||||
D[l, k] = model.NewIntVar(0, 2, 'D_{}_{}'.format(l, k))
|
||||
|
||||
D_abs = {}
|
||||
for l in range(L):
|
||||
for j in range(J):
|
||||
for k in range(K):
|
||||
D_abs[l, j, k] = model.NewIntVar(0, M, 'D_abs_{}_{}_{}'.format(l, j, k))
|
||||
|
||||
# == Objective function ===
|
||||
model.Minimize(a * WL + b * sum(N[k] for k in range(K)))
|
||||
|
||||
# === Constraint ===
|
||||
for i in range(I):
|
||||
model.Add(sum(X[i, j, k] for j in range(J) for k in range(K)) == component_list[cpidx_2_part[i]])
|
||||
|
||||
for k in range(K):
|
||||
model.Add(sum(X[i, j, k] for i in range(I) for j in range(J)) <= WL)
|
||||
|
||||
for i in range(I):
|
||||
for j in range(J):
|
||||
for k in range(K):
|
||||
model.Add(X[i, j, k] <= M * sum(Z[i, j, l, k] for l in range(L)))
|
||||
|
||||
for i in range(I):
|
||||
for j in range(J):
|
||||
for k in range(K):
|
||||
model.Add(sum(Z[i, j, l, k] for l in range(L)) <= 1)
|
||||
|
||||
for i in range(I):
|
||||
for j in range(J):
|
||||
for k in range(K):
|
||||
model.Add(sum(Z[i, j, l, k] for l in range(L)) <= X[i, j, k])
|
||||
|
||||
for k in range(K):
|
||||
for l in range(L - 1):
|
||||
model.Add(sum(Z[i, j, l, k] for j in range(J) for i in range(I)) >= sum(
|
||||
Z[i, j, l + 1, k] for j in range(J) for i in range(I)))
|
||||
|
||||
for l in range(I):
|
||||
for k in range(K):
|
||||
model.Add(sum(Z[i, j, l, k] for i in range(I) for j in range(J)) <= 1)
|
||||
|
||||
for l in range(L - 1):
|
||||
for j in range(J):
|
||||
for k in range(K):
|
||||
model.AddAbsEquality(D_abs[l, j, k],
|
||||
sum(Z[i, j, l, k] for i in range(I)) - sum(Z[i, j, l + 1, k] for i in range(I)))
|
||||
|
||||
for k in range(K):
|
||||
for l in range(L):
|
||||
model.Add(D[l, k] == sum(D_abs[l, j, k] for j in range(J)))
|
||||
|
||||
for k in range(K):
|
||||
model.Add(N[k] == sum(D[l, k] for l in range(L)) - 1)
|
||||
|
||||
for l in range(L):
|
||||
for k in range(K):
|
||||
model.Add(0 >= sum(HC[i][j] * Z[i, j, l, k] for i in range(I) for j in range(J)))
|
||||
|
||||
# === Main Process ===
|
||||
component_result, cycle_result = [], []
|
||||
feeder_slot_result, placement_result, head_sequence = [], [], []
|
||||
solver.parameters.max_time_in_seconds = 20.0
|
||||
|
||||
status = solver.Solve(model)
|
||||
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
|
||||
print('total cost = {}'.format(solver.ObjectiveValue()))
|
||||
|
||||
# convert cp model solution to standard output
|
||||
model_cycle_result, model_component_result = [], []
|
||||
for l in range(L):
|
||||
model_component_result.append([None for _ in range(K)])
|
||||
model_cycle_result.append([0 for _ in range(K)])
|
||||
for k in range(K):
|
||||
for i in range(I):
|
||||
for j in range(J):
|
||||
if solver.BooleanValue(Z[i, j, l, k]) != 0:
|
||||
model_component_result[-1][k] = cpidx_2_part[i]
|
||||
model_cycle_result[-1][k] = solver.Value(X[i, j, k])
|
||||
|
||||
# remove redundant term
|
||||
if sum(model_cycle_result[-1]) == 0:
|
||||
model_component_result.pop()
|
||||
model_cycle_result.pop()
|
||||
|
||||
head_component_index = [0 for _ in range(max_head_index)]
|
||||
while True:
|
||||
head_cycle = []
|
||||
for head, index in enumerate(head_component_index):
|
||||
head_cycle.append(model_cycle_result[index][head])
|
||||
|
||||
if len([cycle for cycle in head_cycle if cycle > 0]) == 0:
|
||||
break
|
||||
|
||||
component_result.append([None for _ in range(max_head_index)])
|
||||
min_cycle = min([cycle for cycle in head_cycle if cycle > 0])
|
||||
for head, index in enumerate(head_component_index):
|
||||
if model_cycle_result[index][head] != 0:
|
||||
component_result[-1][head] = model_component_result[index][head]
|
||||
else:
|
||||
continue
|
||||
|
||||
model_cycle_result[index][head] -= min_cycle
|
||||
if model_cycle_result[index][head] == 0 and index + 1 < len(model_cycle_result):
|
||||
head_component_index[head] += 1
|
||||
|
||||
cycle_result.append(min_cycle)
|
||||
|
||||
part_2_index = {}
|
||||
for index, data in component_data.iterrows():
|
||||
part_2_index[data['part']] = index
|
||||
|
||||
for cycle in range(len(component_result)):
|
||||
for head in range(max_head_index):
|
||||
part = component_result[cycle][head]
|
||||
component_result[cycle][head] = -1 if part is None else part_2_index[part]
|
||||
|
||||
feeder_slot_result = feeder_assignment(component_data, pcb_data, component_result, cycle_result)
|
||||
|
||||
# === phase 2: heuristic method ===
|
||||
mount_point_pos = defaultdict(list)
|
||||
for pcb_idx, data in pcb_data.iterrows():
|
||||
part = data['part']
|
||||
part_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
mount_point_pos[part_index].append([data['x'], data['y'], pcb_idx])
|
||||
|
||||
for index_ in mount_point_pos.keys():
|
||||
mount_point_pos[index_].sort(key=lambda x: (x[1], x[0]))
|
||||
|
||||
for cycle_idx, _ in enumerate(cycle_result):
|
||||
for _ in range(cycle_result[cycle_idx]):
|
||||
placement_result.append([-1 for _ in range(max_head_index)])
|
||||
for head in range(max_head_index):
|
||||
if component_result[cycle_idx][head] == -1:
|
||||
continue
|
||||
index_ = component_result[cycle_idx][head]
|
||||
|
||||
placement_result[-1][head] = mount_point_pos[index_][-1][2]
|
||||
mount_point_pos[index_].pop()
|
||||
head_sequence.append(dynamic_programming_cycle_path(pcb_data, placement_result[-1], feeder_slot_result[cycle_idx]))
|
||||
|
||||
else:
|
||||
warnings.warn('No solution found!', UserWarning)
|
||||
|
||||
return component_result, cycle_result, feeder_slot_result, placement_result, head_sequence
|
204
base_optimizer/optimizer_celldivision.py
Normal file
204
base_optimizer/optimizer_celldivision.py
Normal file
@ -0,0 +1,204 @@
|
||||
from base_optimizer.optimizer_common import *
|
||||
from result_analysis import *
|
||||
|
||||
|
||||
def convert_cell_2_result(pcb_data, component_data, component_cell, population):
|
||||
assert component_cell['points'].sum() == len(pcb_data)
|
||||
head_assignment = [[] for _ in range(max_head_index)]
|
||||
|
||||
wl = [0 for _ in range(max_head_index)] # workload
|
||||
|
||||
e1, e2, e3 = 1, 0.5, 1. / 6
|
||||
|
||||
component_result, cycle_result, feeder_slot_result = [], [], []
|
||||
for index in population:
|
||||
if component_cell.loc[index]['points'] == 0:
|
||||
continue
|
||||
# 元胞对应的元件类型和贴装点数
|
||||
component_type, component_points = component_cell.loc[index, 'index'], component_cell.loc[index, 'points']
|
||||
|
||||
nozzle_change, maxwl = [0 for _ in range(max_head_index)], [0 for _ in range(max_head_index)]
|
||||
for head in range(max_head_index):
|
||||
if head_assignment[head]:
|
||||
assigned_part = head_assignment[head][-1][0]
|
||||
if component_data.loc[assigned_part]['nz'] != component_data.loc[component_type]['nz']:
|
||||
nozzle_change[head] = 1
|
||||
wl1 = wl.copy()
|
||||
wl1[head] += component_points
|
||||
maxwl[head] = max(wl1) + e1 * nozzle_change[head]
|
||||
|
||||
awl, wl2 = min(maxwl), wl.copy()
|
||||
for idx, val in enumerate(maxwl):
|
||||
if val > awl:
|
||||
wl2[idx] += e3
|
||||
head_ = wl2.index(min(wl2))
|
||||
wl[head_] += component_points
|
||||
head_assignment[head_].append([component_type, component_points])
|
||||
|
||||
head_assignment_counter = [0 for _ in range(max_head_index)]
|
||||
while True:
|
||||
assigned_part, assigned_cycle = [-1 for _ in range(max_head_index)], [0 for _ in range(max_head_index)]
|
||||
for head in range(max_head_index):
|
||||
counter = head_assignment_counter[head]
|
||||
|
||||
if head_assignment[head] and head_assignment[head][counter][1] > 0:
|
||||
assigned_part[head] = head_assignment[head][counter][0]
|
||||
assigned_cycle[head] = head_assignment[head][counter][1]
|
||||
|
||||
nonzero_cycle = [cycle for cycle in assigned_cycle if cycle > 0]
|
||||
if not nonzero_cycle:
|
||||
break
|
||||
|
||||
cycle = min(nonzero_cycle)
|
||||
cycle_result.append(cycle)
|
||||
component_result.append(assigned_part)
|
||||
|
||||
for head in range(max_head_index):
|
||||
counter = head_assignment_counter[head]
|
||||
|
||||
if head_assignment[head] and head_assignment[head][counter][1] > 0:
|
||||
head_assignment[head][counter][1] -= cycle_result[-1]
|
||||
if head_assignment[head][counter][1] == 0 and counter < len(head_assignment[head]) - 1:
|
||||
head_assignment_counter[head] += 1
|
||||
|
||||
feeder_slot_result = feeder_assignment(component_data, pcb_data, component_result, cycle_result)
|
||||
return component_result, cycle_result, feeder_slot_result
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def optimizer_celldivision(pcb_data, component_data, hinter=True):
|
||||
# Crossover method: Two-point crossover
|
||||
# Mutation method: Swap
|
||||
# Parent selection method: Roulette wheel
|
||||
# Termination condition: 20 successive non-improvement iterations
|
||||
population_size = 40 # 种群规模
|
||||
crossover_rate, mutation_rate = .6, .02
|
||||
golden_section = 0.618
|
||||
|
||||
# 获取元件元胞
|
||||
point_num = len(pcb_data)
|
||||
component_cell = pd.DataFrame({'index': np.arange(len(component_data)), 'points': np.zeros(len(component_data), dtype=int)})
|
||||
for point_cnt in range(point_num):
|
||||
part = pcb_data.loc[point_cnt, 'fdr'].split(' ', 1)[1]
|
||||
index = np.where(component_data['part'].values == part)
|
||||
component_cell.loc[index[0], 'points'] += 1
|
||||
component_cell = component_cell[~component_cell['points'].isin([0])]
|
||||
|
||||
# component_cell.sort_values(by = "points" , inplace = True, ascending = False)
|
||||
best_population, best_component_cell = [], []
|
||||
min_pop_val = float('inf') # 最优种群价值
|
||||
Div, Imp = 0, 0
|
||||
while True:
|
||||
# randomly generate permutations
|
||||
generation_ = np.array(component_cell.index)
|
||||
pop_generation = []
|
||||
for _ in range(population_size):
|
||||
np.random.shuffle(generation_)
|
||||
pop_generation.append(generation_.tolist())
|
||||
|
||||
pop_val = []
|
||||
for pop in range(population_size):
|
||||
component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data,
|
||||
component_cell,
|
||||
pop_generation[pop])
|
||||
pop_val.append(
|
||||
component_assign_evaluate(component_data, component_result, cycle_result, feeder_slot_result))
|
||||
|
||||
# 初始化随机生成种群
|
||||
Upit = int(1.5 * np.sqrt(len(component_cell)))
|
||||
|
||||
while Div < Upit:
|
||||
if hinter:
|
||||
print('----- current div : ' + str(Div) + ' , total div : ' + str(Upit) + ' -----')
|
||||
|
||||
# 选择
|
||||
new_pop_generation, new_pop_val = [], []
|
||||
top_k_index = get_top_k_value(pop_val, int(population_size * 0.3))
|
||||
for index in top_k_index:
|
||||
new_pop_generation.append(pop_generation[index])
|
||||
new_pop_val.append(pop_val[index])
|
||||
index = [i for i in range(population_size)]
|
||||
|
||||
select_index = random.choices(index, weights=pop_val, k=population_size - int(population_size * 0.3))
|
||||
for index in select_index:
|
||||
new_pop_generation.append(pop_generation[index])
|
||||
new_pop_val.append(pop_val[index])
|
||||
pop_generation, pop_val = new_pop_generation, new_pop_val
|
||||
|
||||
# 交叉
|
||||
for pop in range(population_size):
|
||||
if pop % 2 == 0 and np.random.random() < crossover_rate:
|
||||
index1, index2 = roulette_wheel_selection(pop_val), -1
|
||||
while True:
|
||||
index2 = roulette_wheel_selection(pop_val)
|
||||
if index1 != index2:
|
||||
break
|
||||
# 两点交叉算子
|
||||
pop_generation[index1], pop_generation[index2] = partially_mapped_crossover(pop_generation[index1],
|
||||
pop_generation[index2])
|
||||
|
||||
if np.random.random() < mutation_rate:
|
||||
index_ = roulette_wheel_selection(pop_val)
|
||||
swap_mutation(pop_generation[index_])
|
||||
|
||||
# 将元件元胞分配到各个吸杆上,计算价值函数
|
||||
for pop in range(population_size):
|
||||
component_result, cycle_result, feeder_slot_result = convert_cell_2_result(pcb_data, component_data,
|
||||
component_cell,
|
||||
pop_generation[pop])
|
||||
pop_val[pop] = component_assign_evaluate(component_data, component_result, cycle_result,
|
||||
feeder_slot_result)
|
||||
assert(pop_val[pop] > 0)
|
||||
|
||||
if min(pop_val) < min_pop_val:
|
||||
min_pop_val = min(pop_val)
|
||||
best_population = copy.deepcopy(pop_generation[np.argmin(pop_val)])
|
||||
best_component_cell = copy.deepcopy(component_cell)
|
||||
Div, Imp = 0, 1
|
||||
else:
|
||||
Div += 1
|
||||
|
||||
if Imp == 1:
|
||||
Div, Imp = 0, 0
|
||||
# Section: cell division operation
|
||||
if hinter:
|
||||
print(' ------------- cell division operation ------------- ')
|
||||
division_component_cell = pd.DataFrame()
|
||||
for idx, rows in component_cell.iterrows():
|
||||
if component_cell.loc[idx, 'points'] <= 1:
|
||||
division_component_cell = pd.concat([division_component_cell, pd.DataFrame([rows])],
|
||||
ignore_index=True)
|
||||
else:
|
||||
division_component_cell = pd.concat([division_component_cell, pd.DataFrame([rows] * 2)],
|
||||
ignore_index=True)
|
||||
|
||||
rows_counter = len(division_component_cell)
|
||||
division_points = int(max(np.ceil(division_component_cell.loc[rows_counter - 2,
|
||||
'points'] * golden_section), 1))
|
||||
# 避免出现空元胞的情形
|
||||
if division_points == 0 or division_points == division_component_cell.loc[
|
||||
rows_counter - 2, 'points']:
|
||||
division_component_cell.loc[rows_counter - 2, 'points'] = 1
|
||||
else:
|
||||
division_component_cell.loc[rows_counter - 2, 'points'] = division_points
|
||||
|
||||
division_component_cell.loc[rows_counter - 1, 'points'] -= division_component_cell.loc[
|
||||
rows_counter - 2, 'points']
|
||||
|
||||
if division_component_cell.loc[rows_counter - 2, 'points'] == 0 or division_component_cell.loc[
|
||||
rows_counter - 1, 'points'] == 0:
|
||||
raise ValueError
|
||||
|
||||
component_cell = division_component_cell
|
||||
|
||||
# 完成分裂后重新生成染色体组
|
||||
generation_ = np.array(range(len(component_cell)))
|
||||
pop_generation = []
|
||||
for _ in range(population_size):
|
||||
np.random.shuffle(generation_)
|
||||
pop_generation.append(generation_.tolist())
|
||||
else:
|
||||
break
|
||||
|
||||
assert(len(best_component_cell) == len(best_population))
|
||||
return convert_cell_2_result(pcb_data, component_data, best_component_cell, best_population)
|
954
base_optimizer/optimizer_common.py
Normal file
954
base_optimizer/optimizer_common.py
Normal file
@ -0,0 +1,954 @@
|
||||
import copy
|
||||
import time
|
||||
import math
|
||||
import random
|
||||
import argparse
|
||||
import os
|
||||
import warnings
|
||||
import copy
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from functools import wraps
|
||||
from collections import defaultdict
|
||||
from tqdm import tqdm
|
||||
|
||||
# 整线参数
|
||||
max_machine_index = 3
|
||||
|
||||
# 时间参数
|
||||
T_pp, T_tr, T_nc = 2, 5, 25
|
||||
|
||||
# 机器参数
|
||||
max_head_index, max_slot_index = 6, 120
|
||||
interval_ratio = 2
|
||||
slot_interval = 15
|
||||
head_interval = slot_interval * interval_ratio
|
||||
head_nozzle = ['' for _ in range(max_head_index)] # 头上已经分配吸嘴
|
||||
|
||||
# 位置信息
|
||||
slotf1_pos, slotr1_pos = [-31.267, 44.], [807., 810.545] # F1(前基座最左侧)、R1(后基座最右侧)位置
|
||||
fix_camera_pos = [269.531, 694.823] # 固定相机位置
|
||||
anc_marker_pos = [336.457, 626.230] # ANC基准点位置
|
||||
stopper_pos = [635.150, 124.738] # 止档块位置
|
||||
|
||||
# 算法权重参数
|
||||
e_nz_change, e_gang_pick = 4, 0.6
|
||||
|
||||
# 电机参数
|
||||
head_rotary_velocity = 8e-5 # 贴装头R轴旋转时间
|
||||
x_max_velocity, y_max_velocity = 1.4, 1.2
|
||||
x_max_acceleration, y_max_acceleration = x_max_velocity / 0.079, y_max_velocity / 0.079
|
||||
|
||||
# 不同种类供料器宽度
|
||||
feeder_width = {'SM8': (7.25, 7.25), 'SM12': (7.00, 20.00), 'SM16': (7.00, 22.00),
|
||||
'SM24': (7.00, 29.00), 'SM32': (7.00, 44.00)}
|
||||
|
||||
# 可用吸嘴数量限制
|
||||
nozzle_limit = {'CN065': 6, 'CN040': 6, 'CN220': 6, 'CN400': 6, 'CN140': 6}
|
||||
|
||||
|
||||
def axis_moving_time(distance, axis=0):
|
||||
distance = abs(distance) * 1e-3
|
||||
Lamax = x_max_velocity ** 2 / x_max_acceleration if axis == 0 else y_max_velocity ** 2 / y_max_acceleration
|
||||
Tmax = x_max_velocity / x_max_acceleration if axis == 0 else y_max_velocity / y_max_acceleration
|
||||
if axis == 0:
|
||||
return 2 * math.sqrt(distance / x_max_acceleration) if distance < Lamax else 2 * Tmax + (
|
||||
distance - Lamax) / x_max_velocity
|
||||
else:
|
||||
return 2 * math.sqrt(distance / y_max_acceleration) if distance < Lamax else 2 * Tmax + (
|
||||
distance - Lamax) / y_max_velocity
|
||||
|
||||
|
||||
def head_rotary_time(angle):
|
||||
while -180 > angle > 180:
|
||||
if angle > 180:
|
||||
angle -= 360
|
||||
else:
|
||||
angle += 360
|
||||
return abs(angle) * head_rotary_velocity
|
||||
|
||||
|
||||
def find_commonpart(head_group, feeder_group):
|
||||
feeder_group_len = len(feeder_group)
|
||||
|
||||
max_length, max_common_part = -1, []
|
||||
for offset in range(-max_head_index + 1, feeder_group_len - 1):
|
||||
# offset: head_group相对于feeder_group的偏移量
|
||||
length, common_part = 0, []
|
||||
for hd_index in range(max_head_index):
|
||||
fd_index = hd_index + offset
|
||||
if fd_index < 0 or fd_index >= feeder_group_len:
|
||||
common_part.append(-1)
|
||||
continue
|
||||
|
||||
if head_group[hd_index] == feeder_group[fd_index] and head_group[hd_index] != -1:
|
||||
length += 1
|
||||
common_part.append(head_group[hd_index])
|
||||
else:
|
||||
common_part.append(-1)
|
||||
if length > max_length:
|
||||
max_length = length
|
||||
max_common_part = common_part
|
||||
|
||||
return max_common_part
|
||||
|
||||
|
||||
def timer_wrapper(func):
|
||||
@wraps(func)
|
||||
def measure_time(*args, **kwargs):
|
||||
start_time = time.time()
|
||||
result = func(*args, **kwargs)
|
||||
|
||||
print("function {} running time : {} s".format(func.__name__, time.time() - start_time))
|
||||
return result
|
||||
|
||||
return measure_time
|
||||
|
||||
|
||||
def feeder_assignment(component_data, pcb_data, component_result, cycle_result):
|
||||
# Section: 供料器分配结果
|
||||
feeder_slot_result, feeder_group_result = [], []
|
||||
feeder_limit = defaultdict(int)
|
||||
for component in range(len(component_data)):
|
||||
feeder_limit[component] = component_data.loc[component]['feeder-limit']
|
||||
|
||||
for component_cycle in component_result:
|
||||
new_feeder_group = []
|
||||
for component in component_cycle:
|
||||
if component == -1 or feeder_limit[component] == 0 or new_feeder_group.count(component) >= feeder_limit[component]:
|
||||
new_feeder_group.append(-1)
|
||||
else:
|
||||
new_feeder_group.append(component)
|
||||
|
||||
if len(new_feeder_group) == 0:
|
||||
continue
|
||||
|
||||
while sum(i >= 0 for i in new_feeder_group) != 0:
|
||||
max_common_part, index = [], -1
|
||||
max_common_length = -1
|
||||
for feeder_index in range(len(feeder_group_result)):
|
||||
common_part = find_commonpart(new_feeder_group, feeder_group_result[feeder_index])
|
||||
if sum(i > 0 for i in common_part) > max_common_length:
|
||||
max_common_length = sum(i > 0 for i in common_part)
|
||||
max_common_part, index = common_part, feeder_index
|
||||
|
||||
new_feeder_length = 0
|
||||
for feeder in new_feeder_group:
|
||||
if feeder != -1 and feeder_limit[feeder] > 0:
|
||||
new_feeder_length += 1
|
||||
|
||||
if new_feeder_length > max_common_length:
|
||||
# 新分配供料器
|
||||
feeder_group_result.append([])
|
||||
for feeder_index in range(len(new_feeder_group)):
|
||||
feeder = new_feeder_group[feeder_index]
|
||||
if feeder != -1 and feeder_limit[feeder] > 0:
|
||||
feeder_group_result[-1].append(feeder)
|
||||
new_feeder_group[feeder_index] = -1
|
||||
feeder_limit[feeder] -= 1
|
||||
else:
|
||||
feeder_group_result[-1].append(-1)
|
||||
else:
|
||||
# 使用旧供料器
|
||||
for feeder_index, feeder_part in enumerate(max_common_part):
|
||||
if feeder_part != -1:
|
||||
new_feeder_group[feeder_index] = -1
|
||||
|
||||
# 去除多余的元素
|
||||
for feeder_group in feeder_group_result:
|
||||
while len(feeder_group) > 0 and feeder_group[0] == -1:
|
||||
feeder_group.pop(0)
|
||||
|
||||
while len(feeder_group) > 0 and feeder_group[-1] == -1:
|
||||
feeder_group.pop(-1)
|
||||
|
||||
# 确定供料器组的安装位置
|
||||
point_num = len(pcb_data)
|
||||
component_pos = [[] for _ in range(len(component_data))]
|
||||
for point_cnt in range(point_num):
|
||||
part = pcb_data.loc[point_cnt, 'part']
|
||||
index = np.where(component_data['part'].values == part)[0]
|
||||
component_pos[index[0]].append(pcb_data.loc[point_cnt, 'x'] + stopper_pos[0])
|
||||
|
||||
# 元件使用的头
|
||||
CT_Head = defaultdict(list)
|
||||
for component_cycle in component_result:
|
||||
for head, component in enumerate(component_cycle):
|
||||
if component == -1:
|
||||
continue
|
||||
if component not in CT_Head:
|
||||
CT_Head[component] = [head, head]
|
||||
CT_Head[component][0] = min(CT_Head[component][0], head)
|
||||
CT_Head[component][1] = max(CT_Head[component][1], head)
|
||||
|
||||
# 供料器组分配的优先顺序
|
||||
feeder_assign_sequence = []
|
||||
for i in range(len(feeder_group_result)):
|
||||
for j in range(len(feeder_group_result)):
|
||||
if j in feeder_assign_sequence:
|
||||
continue
|
||||
|
||||
if len(feeder_assign_sequence) == i:
|
||||
feeder_assign_sequence.append(j)
|
||||
else:
|
||||
seq = feeder_assign_sequence[-1]
|
||||
if cycle_result[seq] * len([k for k in feeder_group_result[seq] if k >= 0]) < cycle_result[j] * len(
|
||||
[k for k in feeder_group_result[seq] if k >= 0]):
|
||||
feeder_assign_sequence.pop(-1)
|
||||
feeder_assign_sequence.append(j)
|
||||
|
||||
# TODO: 暂未考虑机械限位
|
||||
feeder_group_slot = [-1] * len(feeder_group_result)
|
||||
feeder_lane_state = [0] * max_slot_index # 0表示空,1表示已占有
|
||||
for index in feeder_assign_sequence:
|
||||
feeder_group = feeder_group_result[index]
|
||||
best_slot = []
|
||||
for cp_index, component in enumerate(feeder_group):
|
||||
if component == -1:
|
||||
continue
|
||||
best_slot.append(round((sum(component_pos[component]) / len(component_pos[component]) - slotf1_pos[
|
||||
0]) / slot_interval) + 1 - cp_index * interval_ratio)
|
||||
best_slot = round(sum(best_slot) / len(best_slot))
|
||||
|
||||
search_dir, step = 0, 0 # dir: 1-向右, 0-向左
|
||||
left_out_range, right_out_range = False, False
|
||||
while True:
|
||||
assign_slot = best_slot + step if search_dir else best_slot - step
|
||||
# 出现越界,反向搜索
|
||||
if assign_slot + (len(feeder_group) - 1) * interval_ratio >= max_slot_index / 2:
|
||||
right_out_range = True
|
||||
search_dir = 0
|
||||
step += 1
|
||||
elif assign_slot < 0:
|
||||
left_out_range = True
|
||||
search_dir = 1
|
||||
step += 1
|
||||
else:
|
||||
if left_out_range or right_out_range:
|
||||
step += 1 # 单向搜索
|
||||
else:
|
||||
search_dir = 1 - search_dir # 双向搜索
|
||||
if search_dir == 0:
|
||||
step += 1
|
||||
|
||||
assign_available = True
|
||||
|
||||
# === 分配对应槽位 ===
|
||||
for slot in range(assign_slot, assign_slot + interval_ratio * len(feeder_group), interval_ratio):
|
||||
feeder_index = int((slot - assign_slot) / interval_ratio)
|
||||
pick_part = feeder_group[feeder_index]
|
||||
if feeder_lane_state[slot] == 1 and pick_part != -1:
|
||||
assign_available = False
|
||||
break
|
||||
|
||||
if pick_part != -1 and (slot - CT_Head[pick_part][0] * interval_ratio <= 0 or
|
||||
slot + (max_head_index - CT_Head[pick_part][1] - 1) * interval_ratio > max_slot_index // 2):
|
||||
assign_available = False
|
||||
break
|
||||
|
||||
if assign_available:
|
||||
for idx, part in enumerate(feeder_group):
|
||||
if part != -1:
|
||||
feeder_lane_state[assign_slot + idx * interval_ratio] = 1
|
||||
feeder_group_slot[index] = assign_slot
|
||||
break
|
||||
|
||||
if feeder_group_slot[index] == -1:
|
||||
raise Exception('feeder assign error!')
|
||||
|
||||
# 按照最大匹配原则,确定各元件周期拾取槽位
|
||||
for component_cycle in component_result:
|
||||
feeder_slot_result.append([-1] * max_head_index)
|
||||
head_index = [head for head, component in enumerate(component_cycle) if component >= 0]
|
||||
while head_index:
|
||||
max_overlap_counter = 0
|
||||
overlap_feeder_group_index, overlap_feeder_group_offset = -1, -1
|
||||
for feeder_group_idx, feeder_group in enumerate(feeder_group_result):
|
||||
# offset 头1 相对于 供料器组第一个元件的偏移量
|
||||
for offset in range(-max_head_index + 1, max_head_index + len(feeder_group)):
|
||||
overlap_counter = 0
|
||||
for head in head_index:
|
||||
if 0 <= head + offset < len(feeder_group) and component_cycle[head] == \
|
||||
feeder_group[head + offset]:
|
||||
overlap_counter += 1
|
||||
|
||||
if overlap_counter > max_overlap_counter:
|
||||
max_overlap_counter = overlap_counter
|
||||
overlap_feeder_group_index, overlap_feeder_group_offset = feeder_group_idx, offset
|
||||
|
||||
feeder_group = feeder_group_result[overlap_feeder_group_index]
|
||||
head_index_cpy = copy.deepcopy(head_index)
|
||||
|
||||
for idx, head in enumerate(head_index_cpy):
|
||||
if 0 <= head + overlap_feeder_group_offset < len(feeder_group) and component_cycle[head] == \
|
||||
feeder_group[head + overlap_feeder_group_offset]:
|
||||
feeder_slot_result[-1][head] = feeder_group_slot[overlap_feeder_group_index] + interval_ratio * (
|
||||
head + overlap_feeder_group_offset)
|
||||
head_index.remove(head)
|
||||
|
||||
return feeder_slot_result
|
||||
|
||||
|
||||
def dynamic_programming_cycle_path(pcb_data, cycle_placement, assigned_feeder):
|
||||
head_sequence = []
|
||||
num_pos = sum([placement != -1 for placement in cycle_placement]) + 1
|
||||
|
||||
pos, head_set = [], []
|
||||
feeder_set = set()
|
||||
for head, feeder in enumerate(assigned_feeder):
|
||||
if feeder == -1:
|
||||
continue
|
||||
|
||||
head_set.append(head)
|
||||
placement = cycle_placement[head]
|
||||
if feeder != -1 and placement == -1:
|
||||
print(assigned_feeder)
|
||||
print(cycle_placement)
|
||||
|
||||
pos.append([pcb_data.loc[placement]['x'] - head * head_interval + stopper_pos[0],
|
||||
pcb_data.loc[placement]['y'] + stopper_pos[1]])
|
||||
|
||||
feeder_set.add(feeder - head * interval_ratio)
|
||||
|
||||
pos.insert(0, [slotf1_pos[0] + ((min(list(feeder_set)) + max(list(feeder_set))) / 2 - 1) * slot_interval,
|
||||
slotf1_pos[1]])
|
||||
|
||||
def get_distance(pos_1, pos_2):
|
||||
return math.sqrt((pos_1[0] - pos_2[0]) ** 2 + (pos_1[1] - pos_2[1]) ** 2)
|
||||
|
||||
# 各节点之间的距离
|
||||
dist = [[get_distance(pos_1, pos_2) for pos_2 in pos] for pos_1 in pos]
|
||||
|
||||
min_dist = [[np.inf for _ in range(num_pos)] for s in range(1 << num_pos)]
|
||||
min_path = [[[] for _ in range(num_pos)] for s in range(1 << num_pos)]
|
||||
|
||||
# 状压dp搜索
|
||||
for s in range(1, 1 << num_pos, 2):
|
||||
# 考虑节点集合s必须包括节点0
|
||||
if not (s & 1):
|
||||
continue
|
||||
for j in range(1, num_pos):
|
||||
# 终点j需在当前考虑节点集合s内
|
||||
if not (s & (1 << j)):
|
||||
continue
|
||||
if s == int((1 << j) | 1):
|
||||
# 若考虑节点集合s仅含节点0和节点j,dp边界,赋予初值
|
||||
# print('j:', j)
|
||||
min_path[s][j] = [j]
|
||||
min_dist[s][j] = dist[0][j]
|
||||
|
||||
# 枚举下一个节点i,更新
|
||||
for i in range(1, num_pos):
|
||||
# 下一个节点i需在考虑节点集合s外
|
||||
if s & (1 << i):
|
||||
continue
|
||||
if min_dist[s][j] + dist[j][i] < min_dist[s | (1 << i)][i]:
|
||||
min_path[s | (1 << i)][i] = min_path[s][j] + [i]
|
||||
min_dist[s | (1 << i)][i] = min_dist[s][j] + dist[j][i]
|
||||
|
||||
ans_dist = float('inf')
|
||||
ans_path = []
|
||||
# 求最终最短哈密顿回路
|
||||
for i in range(1, num_pos):
|
||||
if min_dist[(1 << num_pos) - 1][i] + dist[i][0] < ans_dist:
|
||||
# 更新,回路化
|
||||
ans_path = min_path[s][i]
|
||||
ans_dist = min_dist[(1 << num_pos) - 1][i] + dist[i][0]
|
||||
|
||||
for parent in ans_path:
|
||||
head_sequence.append(head_set[parent - 1])
|
||||
|
||||
start_head, end_head = head_sequence[0], head_sequence[-1]
|
||||
if pcb_data.loc[cycle_placement[start_head]]['x'] - start_head * head_interval > \
|
||||
pcb_data.loc[cycle_placement[end_head]]['x'] - end_head * head_interval:
|
||||
head_sequence = list(reversed(head_sequence))
|
||||
return head_sequence
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def greedy_placement_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result):
|
||||
placement_result, head_sequence_result = [], []
|
||||
mount_point_index = [[] for _ in range(len(component_data))]
|
||||
mount_point_pos = [[] for _ in range(len(component_data))]
|
||||
|
||||
for i in range(len(pcb_data)):
|
||||
part = pcb_data.loc[i]['part']
|
||||
component_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
# 记录贴装点序号索引和对应的位置坐标
|
||||
mount_point_index[component_index].append(i)
|
||||
mount_point_pos[component_index].append([pcb_data.loc[i]['x'], pcb_data.loc[i]['y']])
|
||||
|
||||
search_dir = 1 # 0:自左向右搜索 1:自右向左搜索
|
||||
for cycle_set in range(len(component_result)):
|
||||
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
|
||||
for cycle in range(floor_cycle, ceil_cycle):
|
||||
# search_dir = 1 - search_dir
|
||||
assigned_placement = [-1] * max_head_index
|
||||
max_pos = [max(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in
|
||||
range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0]
|
||||
min_pos = [min(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in
|
||||
range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0]
|
||||
point2head_range = min(math.floor((max_pos - min_pos) / head_interval) + 1, max_head_index)
|
||||
|
||||
# 最近邻确定
|
||||
way_point = None
|
||||
head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index)
|
||||
for head_counter, head in enumerate(head_range):
|
||||
if component_result[cycle_set][head] == -1:
|
||||
continue
|
||||
|
||||
component_index = component_result[cycle_set][head]
|
||||
if way_point is None or head_counter % point2head_range == 0:
|
||||
index = 0
|
||||
if way_point is None:
|
||||
if search_dir:
|
||||
index = np.argmax(mount_point_pos[component_index], axis=0)[0]
|
||||
else:
|
||||
index = np.argmin(mount_point_pos[component_index], axis=0)[0]
|
||||
else:
|
||||
for next_head in head_range:
|
||||
component_index = component_result[cycle_set][next_head]
|
||||
if assigned_placement[next_head] == -1 and component_index != -1:
|
||||
num_points = len(mount_point_pos[component_index])
|
||||
index = np.argmin(
|
||||
[abs(mount_point_pos[component_index][i][0] - way_point[0]) * .1 + abs(
|
||||
mount_point_pos[component_index][i][1] - way_point[1]) for i in
|
||||
range(num_points)])
|
||||
head = next_head
|
||||
break
|
||||
# index = np.argmax(mount_point_pos[component_index], axis=0)[0]
|
||||
assigned_placement[head] = mount_point_index[component_index][index]
|
||||
|
||||
# 记录路标点
|
||||
way_point = mount_point_pos[component_index][index]
|
||||
way_point[0] += (max_head_index - head - 1) * head_interval if search_dir else -head * head_interval
|
||||
|
||||
mount_point_index[component_index].pop(index)
|
||||
mount_point_pos[component_index].pop(index)
|
||||
else:
|
||||
head_index, point_index = -1, -1
|
||||
min_cheby_distance, min_euler_distance = float('inf'), float('inf')
|
||||
for next_head in range(max_head_index):
|
||||
if assigned_placement[next_head] != -1 or component_result[cycle_set][next_head] == -1:
|
||||
continue
|
||||
next_comp_index = component_result[cycle_set][next_head]
|
||||
for counter in range(len(mount_point_pos[next_comp_index])):
|
||||
if search_dir:
|
||||
delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0]
|
||||
+ (max_head_index - next_head - 1) * head_interval)
|
||||
else:
|
||||
delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0]
|
||||
- next_head * head_interval)
|
||||
|
||||
delta_y = abs(mount_point_pos[next_comp_index][counter][1] - way_point[1])
|
||||
|
||||
euler_distance = pow(axis_moving_time(delta_x, 0), 2) + pow(axis_moving_time(delta_y, 1), 2)
|
||||
cheby_distance = max(axis_moving_time(delta_x, 0),
|
||||
axis_moving_time(delta_y, 1)) + 5e-2 * euler_distance
|
||||
if cheby_distance < min_cheby_distance or (abs(cheby_distance - min_cheby_distance) < 1e-9
|
||||
and euler_distance < min_euler_distance):
|
||||
# if euler_distance < min_euler_distance:
|
||||
min_cheby_distance, min_euler_distance = cheby_distance, euler_distance
|
||||
head_index, point_index = next_head, counter
|
||||
|
||||
component_index = component_result[cycle_set][head_index]
|
||||
assert (0 <= head_index < max_head_index)
|
||||
|
||||
assigned_placement[head_index] = mount_point_index[component_index][point_index]
|
||||
way_point = mount_point_pos[component_index][point_index]
|
||||
way_point[0] += (max_head_index - head_index - 1) * head_interval if search_dir \
|
||||
else -head_index * head_interval
|
||||
|
||||
mount_point_index[component_index].pop(point_index)
|
||||
mount_point_pos[component_index].pop(point_index)
|
||||
|
||||
placement_result.append(assigned_placement) # 各个头上贴装的元件类型
|
||||
head_sequence_result.append(
|
||||
dynamic_programming_cycle_path(pcb_data, assigned_placement, feeder_slot_result[cycle_set]))
|
||||
|
||||
return placement_result, head_sequence_result
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def beam_search_for_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result):
|
||||
beam_width = 4 # 集束宽度
|
||||
base_points = [float('inf'), float('inf')]
|
||||
|
||||
mount_point_index = [[] for _ in range(len(component_data))]
|
||||
mount_point_pos = [[] for _ in range(len(component_data))]
|
||||
|
||||
for i in range(len(pcb_data)):
|
||||
part = pcb_data.loc[i]['part']
|
||||
component_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
|
||||
# 记录贴装点序号索引和对应的位置坐标
|
||||
mount_point_index[component_index].append(i)
|
||||
mount_point_pos[component_index].append([pcb_data.loc[i]['x'], pcb_data.loc[i]['y']])
|
||||
|
||||
# 记录最左下角坐标
|
||||
if mount_point_pos[component_index][-1][0] < base_points[0]:
|
||||
base_points[0] = mount_point_pos[component_index][-1][0]
|
||||
if mount_point_pos[component_index][-1][1] < base_points[1]:
|
||||
base_points[1] = mount_point_pos[component_index][-1][1]
|
||||
|
||||
beam_placement_sequence, beam_head_sequence = [], []
|
||||
beam_mount_point_index, beam_mount_point_pos = [], []
|
||||
|
||||
for beam_counter in range(beam_width):
|
||||
beam_mount_point_index.append(copy.deepcopy(mount_point_index))
|
||||
beam_mount_point_pos.append(copy.deepcopy(mount_point_pos))
|
||||
|
||||
beam_placement_sequence.append([])
|
||||
beam_head_sequence.append([])
|
||||
|
||||
beam_distance = [0 for _ in range(beam_width)] # 记录当前集束搜索点的点数
|
||||
def argpartition(list, kth):
|
||||
if kth < len(list):
|
||||
return np.argpartition(list, kth)
|
||||
else:
|
||||
index, indexes = 0, []
|
||||
while len(indexes) < kth:
|
||||
indexes.append(index)
|
||||
index += 1
|
||||
if index >= len(list):
|
||||
index = 0
|
||||
return np.array(indexes)
|
||||
|
||||
with tqdm(total=100) as pbar:
|
||||
search_dir = 0
|
||||
pbar.set_description('route schedule')
|
||||
for cycle_set in range(len(component_result)):
|
||||
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
|
||||
for cycle in range(floor_cycle, ceil_cycle):
|
||||
search_dir = 1 - search_dir
|
||||
beam_way_point = None
|
||||
for beam_counter in range(beam_width):
|
||||
beam_placement_sequence[beam_counter].append([-1 for _ in range(max_head_index)])
|
||||
|
||||
head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index)
|
||||
for head in head_range:
|
||||
component_index = component_result[cycle_set][head]
|
||||
if component_index == -1:
|
||||
continue
|
||||
|
||||
if beam_way_point is None:
|
||||
# 首个贴装点的选取,距离基准点最近的beam_width个点
|
||||
beam_way_point = [[0, 0]] * beam_width
|
||||
|
||||
for beam_counter in range(beam_width):
|
||||
if search_dir:
|
||||
index = np.argmax(beam_mount_point_pos[beam_counter][component_index], axis=0)[0]
|
||||
else:
|
||||
index = np.argmin(beam_mount_point_pos[beam_counter][component_index], axis=0)[0]
|
||||
|
||||
beam_placement_sequence[beam_counter][-1][head] = beam_mount_point_index[beam_counter][component_index][index]
|
||||
|
||||
beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][index]
|
||||
beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \
|
||||
search_dir else -head * head_interval
|
||||
|
||||
beam_mount_point_index[beam_counter][component_index].pop(index)
|
||||
beam_mount_point_pos[beam_counter][component_index].pop(index)
|
||||
else:
|
||||
# 后续贴装点
|
||||
search_beam_distance = []
|
||||
search_beam_index = [0] * (beam_width ** 2)
|
||||
for beam_counter in range(beam_width ** 2):
|
||||
search_beam_distance.append(beam_distance[beam_counter // beam_width])
|
||||
|
||||
for beam_counter in range(beam_width):
|
||||
# 对于集束beam_counter + 1最近的beam_width个点
|
||||
num_points = len(beam_mount_point_pos[beam_counter][component_index])
|
||||
|
||||
dist = []
|
||||
for i in range(num_points):
|
||||
if search_dir:
|
||||
delta_x = axis_moving_time(
|
||||
beam_mount_point_pos[beam_counter][component_index][i][0] -
|
||||
beam_way_point[beam_counter][0] + (max_head_index - head - 1) * head_interval,
|
||||
0)
|
||||
else:
|
||||
delta_x = axis_moving_time(
|
||||
beam_mount_point_pos[beam_counter][component_index][i][0] -
|
||||
beam_way_point[beam_counter][0] - head * head_interval, 0)
|
||||
|
||||
delta_y = axis_moving_time(beam_mount_point_pos[beam_counter][component_index][i][1] -
|
||||
beam_way_point[beam_counter][1], 1)
|
||||
|
||||
dist.append(max(delta_x, delta_y))
|
||||
|
||||
indexes = argpartition(dist, kth=beam_width)[:beam_width]
|
||||
|
||||
# 记录中间信息
|
||||
for i, index in enumerate(indexes):
|
||||
search_beam_distance[i + beam_counter * beam_width] += dist[index]
|
||||
search_beam_index[i + beam_counter * beam_width] = index
|
||||
|
||||
indexes = np.argsort(search_beam_distance)
|
||||
|
||||
beam_mount_point_pos_cpy = copy.deepcopy(beam_mount_point_pos)
|
||||
beam_mount_point_index_cpy = copy.deepcopy(beam_mount_point_index)
|
||||
|
||||
beam_placement_sequence_cpy = copy.deepcopy(beam_placement_sequence)
|
||||
beam_head_sequence_cpy = copy.deepcopy(beam_head_sequence)
|
||||
beam_counter = 0
|
||||
assigned_placement = []
|
||||
|
||||
for i, index in enumerate(indexes):
|
||||
# 拷贝原始集束数据
|
||||
beam_mount_point_pos[beam_counter] = copy.deepcopy(beam_mount_point_pos_cpy[index // beam_width])
|
||||
beam_mount_point_index[beam_counter] = copy.deepcopy(beam_mount_point_index_cpy[index // beam_width])
|
||||
beam_placement_sequence[beam_counter] = copy.deepcopy(beam_placement_sequence_cpy[index // beam_width])
|
||||
beam_head_sequence[beam_counter] = copy.deepcopy(beam_head_sequence_cpy[index // beam_width])
|
||||
|
||||
# 更新各集束最新扫描的的贴装点
|
||||
component_index = component_result[cycle_set][head]
|
||||
|
||||
beam_placement_sequence[beam_counter][-1][head] = \
|
||||
beam_mount_point_index[beam_counter][component_index][search_beam_index[index]]
|
||||
|
||||
if beam_placement_sequence[beam_counter][
|
||||
-1] in assigned_placement and beam_width - beam_counter < len(indexes) - i:
|
||||
continue
|
||||
|
||||
assigned_placement.append(beam_placement_sequence[beam_counter][-1])
|
||||
|
||||
# 更新参考基准点
|
||||
beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][search_beam_index[index]]
|
||||
beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \
|
||||
search_dir else -head * head_interval
|
||||
|
||||
# 更新各集束贴装路径长度,移除各集束已分配的贴装点
|
||||
beam_distance[beam_counter] = search_beam_distance[index]
|
||||
|
||||
beam_mount_point_pos[beam_counter][component_index].pop(search_beam_index[index])
|
||||
beam_mount_point_index[beam_counter][component_index].pop(search_beam_index[index])
|
||||
|
||||
beam_counter += 1
|
||||
|
||||
if beam_counter >= beam_width:
|
||||
break
|
||||
assert(beam_counter >= beam_width)
|
||||
|
||||
# 更新头贴装顺序
|
||||
for beam_counter in range(beam_width):
|
||||
beam_head_sequence[beam_counter].append(
|
||||
dynamic_programming_cycle_path(pcb_data, beam_placement_sequence[beam_counter][-1],
|
||||
feeder_slot_result[cycle_set]))
|
||||
|
||||
pbar.update(1 / sum(cycle_result) * 100)
|
||||
|
||||
index = np.argmin(beam_distance)
|
||||
return beam_placement_sequence[index], beam_head_sequence[index]
|
||||
|
||||
|
||||
def optimal_nozzle_assignment(component_data, pcb_data):
|
||||
# === Nozzle Assignment ===
|
||||
# number of points for nozzle & number of heads for nozzle
|
||||
nozzle_points, nozzle_assigned_counter = defaultdict(int), defaultdict(int)
|
||||
if len(pcb_data) == 0:
|
||||
return nozzle_assigned_counter
|
||||
for _, step in pcb_data.iterrows():
|
||||
part = step['part']
|
||||
idx = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
nozzle = component_data.loc[idx]['nz']
|
||||
|
||||
nozzle_assigned_counter[nozzle] = 0
|
||||
nozzle_points[nozzle] += 1
|
||||
|
||||
assert len(nozzle_points.keys()) <= max_head_index
|
||||
total_points, available_head = len(pcb_data), max_head_index
|
||||
# S1: set of nozzle types which are sufficient to assign one nozzle to the heads
|
||||
# S2: temporary nozzle set
|
||||
# S3: set of nozzle types which already have the maximum reasonable nozzle amounts.
|
||||
S1, S2, S3 = [], [], []
|
||||
|
||||
for nozzle in nozzle_points.keys(): # Phase 1
|
||||
if nozzle_points[nozzle] * max_head_index < total_points:
|
||||
nozzle_assigned_counter[nozzle] = 1
|
||||
available_head -= 1
|
||||
total_points -= nozzle_points[nozzle]
|
||||
|
||||
S1.append(nozzle)
|
||||
else:
|
||||
S2.append(nozzle)
|
||||
|
||||
available_head_ = available_head # Phase 2
|
||||
for nozzle in S2:
|
||||
nozzle_assigned_counter[nozzle] = math.floor(available_head * nozzle_points[nozzle] / total_points)
|
||||
available_head_ = available_head_ - nozzle_assigned_counter[nozzle]
|
||||
|
||||
S2.sort(key=lambda x: nozzle_points[x] / (nozzle_assigned_counter[x] + 1e-10), reverse=True)
|
||||
while available_head_ > 0:
|
||||
nozzle = S2[0]
|
||||
nozzle_assigned_counter[nozzle] += 1
|
||||
|
||||
S2.remove(nozzle)
|
||||
S3.append(nozzle)
|
||||
available_head_ -= 1
|
||||
|
||||
phase_iteration = len(S2) - 1
|
||||
while phase_iteration > 0: # Phase 3
|
||||
nozzle_i_val, nozzle_j_val = 0, 0
|
||||
nozzle_i, nozzle_j = None, None
|
||||
for nozzle in S2:
|
||||
if nozzle_i is None or nozzle_points[nozzle] / nozzle_assigned_counter[nozzle] > nozzle_i_val:
|
||||
nozzle_i_val = nozzle_points[nozzle] / nozzle_assigned_counter[nozzle]
|
||||
nozzle_i = nozzle
|
||||
|
||||
if nozzle_assigned_counter[nozzle] > 1:
|
||||
if nozzle_j is None or nozzle_points[nozzle] / (nozzle_assigned_counter[nozzle] - 1) < nozzle_j_val:
|
||||
nozzle_j_val = nozzle_points[nozzle] / (nozzle_assigned_counter[nozzle] - 1)
|
||||
nozzle_j = nozzle
|
||||
|
||||
if nozzle_i and nozzle_j and nozzle_points[nozzle_j] / (nozzle_assigned_counter[nozzle_j] - 1) < \
|
||||
nozzle_points[nozzle_i] / nozzle_assigned_counter[nozzle_i]:
|
||||
nozzle_assigned_counter[nozzle_j] -= 1
|
||||
nozzle_assigned_counter[nozzle_i] += 1
|
||||
S2.remove(nozzle_i)
|
||||
S3.append(nozzle_i)
|
||||
else:
|
||||
break
|
||||
|
||||
return nozzle_assigned_counter
|
||||
|
||||
|
||||
# === 遗传算法公用函数 ===
|
||||
def sigma_scaling(pop_val, c: float):
|
||||
# function: f' = max(f - (avg(f) - c · sigma(f), 0)
|
||||
avg_val = sum(pop_val) / len(pop_val)
|
||||
sigma_val = math.sqrt(sum(abs(v - avg_val) for v in pop_val) / len(pop_val))
|
||||
|
||||
for idx, val in enumerate(pop_val):
|
||||
pop_val[idx] = max(val - (avg_val - c * sigma_val), 0)
|
||||
return pop_val
|
||||
|
||||
|
||||
def directed_edge_recombination_crossover(c, individual1, individual2):
|
||||
assert len(individual1) == len(individual2)
|
||||
left_edge_list, right_edge_list = defaultdict(list), defaultdict(list)
|
||||
|
||||
for index in range(len(individual1) - 1):
|
||||
elem1, elem2 = individual1[index], individual1[index + 1]
|
||||
right_edge_list[elem1].append(elem2)
|
||||
left_edge_list[elem2].append(elem1)
|
||||
|
||||
for index in range(len(individual2) - 1):
|
||||
elem1, elem2 = individual2[index], individual2[index + 1]
|
||||
right_edge_list[elem1].append(elem2)
|
||||
left_edge_list[elem2].append(elem1)
|
||||
|
||||
offspring = []
|
||||
while len(offspring) != len(individual1):
|
||||
while True:
|
||||
center_element = np.random.choice(individual1)
|
||||
if center_element not in offspring: # 避免重复选取
|
||||
break
|
||||
direction, candidate = 1, [center_element]
|
||||
parent = center_element
|
||||
for edge_list in left_edge_list.values():
|
||||
while parent in edge_list:
|
||||
edge_list.remove(parent)
|
||||
|
||||
for edge_list in right_edge_list.values():
|
||||
while parent in edge_list:
|
||||
edge_list.remove(parent)
|
||||
|
||||
while True:
|
||||
max_len, max_len_neighbor = -1, 0
|
||||
if direction == 1:
|
||||
if len(right_edge_list[parent]) == 0:
|
||||
direction, parent = -1, center_element
|
||||
continue
|
||||
for neighbor in right_edge_list[parent]:
|
||||
if max_len < len(right_edge_list[neighbor]):
|
||||
max_len_neighbor = neighbor
|
||||
max_len = len(right_edge_list[neighbor])
|
||||
candidate.append(max_len_neighbor)
|
||||
parent = max_len_neighbor
|
||||
elif direction == -1:
|
||||
if len(left_edge_list[parent]) == 0:
|
||||
direction, parent = 0, center_element
|
||||
continue
|
||||
for neighbor in left_edge_list[parent]:
|
||||
if max_len < len(left_edge_list[neighbor]):
|
||||
max_len_neighbor = neighbor
|
||||
max_len = len(left_edge_list[neighbor])
|
||||
candidate.insert(0, max_len_neighbor)
|
||||
parent = max_len_neighbor
|
||||
else:
|
||||
break
|
||||
|
||||
# 移除重复元素
|
||||
for edge_list in left_edge_list.values():
|
||||
while max_len_neighbor in edge_list:
|
||||
edge_list.remove(max_len_neighbor)
|
||||
|
||||
for edge_list in right_edge_list.values():
|
||||
while max_len_neighbor in edge_list:
|
||||
edge_list.remove(max_len_neighbor)
|
||||
|
||||
offspring += candidate
|
||||
|
||||
return offspring
|
||||
|
||||
|
||||
def partially_mapped_crossover(parent1, parent2):
|
||||
range_ = np.random.randint(0, len(parent1), 2) # 前闭后开
|
||||
range_ = sorted(range_)
|
||||
|
||||
parent1_cpy, parent2_cpy = [-1 for _ in range(len(parent1))], [-1 for _ in range(len(parent2))]
|
||||
|
||||
parent1_cpy[range_[0]: range_[1] + 1] = copy.deepcopy(parent2[range_[0]: range_[1] + 1])
|
||||
parent2_cpy[range_[0]: range_[1] + 1] = copy.deepcopy(parent1[range_[0]: range_[1] + 1])
|
||||
|
||||
for index in range(len(parent1)):
|
||||
if range_[0] <= index <= range_[1]:
|
||||
continue
|
||||
|
||||
cur_ptr, cur_elem = 0, parent1[index]
|
||||
while True:
|
||||
parent1_cpy[index] = cur_elem
|
||||
if parent1_cpy.count(cur_elem) == 1:
|
||||
break
|
||||
parent1_cpy[index] = -1
|
||||
|
||||
if cur_ptr == 0:
|
||||
cur_ptr, cur_elem = 1, parent2[index]
|
||||
else:
|
||||
index_ = parent1_cpy.index(cur_elem)
|
||||
cur_elem = parent2[index_]
|
||||
|
||||
for index in range(len(parent2)):
|
||||
if range_[0] <= index <= range_[1]:
|
||||
continue
|
||||
|
||||
cur_ptr, cur_elem = 0, parent2[index]
|
||||
while True:
|
||||
parent2_cpy[index] = cur_elem
|
||||
if parent2_cpy.count(cur_elem) == 1:
|
||||
break
|
||||
parent2_cpy[index] = -1
|
||||
|
||||
if cur_ptr == 0:
|
||||
cur_ptr, cur_elem = 1, parent1[index]
|
||||
else:
|
||||
index_ = parent2_cpy.index(cur_elem)
|
||||
cur_elem = parent1[index_]
|
||||
|
||||
return parent1_cpy, parent2_cpy
|
||||
|
||||
|
||||
def cycle_crossover(parent1, parent2):
|
||||
offspring1, offspring2 = [-1 for _ in range(len(parent1))], [-1 for _ in range(len(parent2))]
|
||||
|
||||
idx = 0
|
||||
while True:
|
||||
if offspring1[idx] != -1:
|
||||
break
|
||||
offspring1[idx] = parent1[idx]
|
||||
idx = parent1.index(parent2[idx])
|
||||
|
||||
for idx, gene in enumerate(offspring1):
|
||||
if gene == -1:
|
||||
offspring1[idx] = parent2[idx]
|
||||
|
||||
idx = 0
|
||||
while True:
|
||||
if offspring2[idx] != -1:
|
||||
break
|
||||
offspring2[idx] = parent2[idx]
|
||||
idx = parent2.index(parent1[idx])
|
||||
|
||||
for idx, gene in enumerate(offspring2):
|
||||
if gene == -1:
|
||||
offspring2[idx] = parent1[idx]
|
||||
|
||||
return offspring1, offspring2
|
||||
|
||||
|
||||
def swap_mutation(parent):
|
||||
range_ = np.random.randint(0, len(parent), 2)
|
||||
parent[range_[0]], parent[range_[1]] = parent[range_[1]], parent[range_[0]]
|
||||
return parent
|
||||
|
||||
|
||||
def constraint_swap_mutation(component_points, individual):
|
||||
offspring = individual.copy()
|
||||
|
||||
idx, component_index = 0, random.randint(0, len(component_points) - 1)
|
||||
for points in component_points.values():
|
||||
if component_index == 0:
|
||||
while True:
|
||||
index1, index2 = random.sample(range(points + max_machine_index - 2), 2)
|
||||
if offspring[idx + index1] != offspring[idx + index2]:
|
||||
break
|
||||
|
||||
clip = offspring[idx: idx + points + max_machine_index - 1].copy()
|
||||
avl_machine = 0
|
||||
for idx_, gene in enumerate(clip):
|
||||
if gene == 0 and (idx_ == 0 or clip[idx_ - 1] != 0):
|
||||
avl_machine += 1
|
||||
|
||||
clip[index1], clip[index2] = clip[index2], clip[index1]
|
||||
for idx_, gene in enumerate(clip):
|
||||
if gene == 0 and (idx_ == 0 or clip[idx_ - 1] != 0):
|
||||
avl_machine -= 1
|
||||
|
||||
if avl_machine != 0:
|
||||
return offspring
|
||||
|
||||
offspring[idx + index1], offspring[idx + index2] = offspring[idx + index2], offspring[idx + index1]
|
||||
break
|
||||
|
||||
component_index -= 1
|
||||
idx += (points + max_machine_index - 1)
|
||||
|
||||
return offspring
|
||||
|
||||
|
||||
def random_selective(data, possibility): # 依概率选择随机数
|
||||
assert len(data) == len(possibility) and len(data) > 0
|
||||
|
||||
sum_val = sum(possibility)
|
||||
possibility = [p / sum_val for p in possibility]
|
||||
|
||||
random_val = random.random()
|
||||
for idx, val in enumerate(possibility):
|
||||
random_val -= val
|
||||
if random_val <= 0:
|
||||
break
|
||||
return data[idx]
|
||||
|
||||
|
||||
def insert_mutation(parent):
|
||||
pos, val = np.random.randint(0, len(parent), 1), parent[-1]
|
||||
parent[pos: len(parent) - 1] = parent[pos + 1:]
|
||||
parent[pos] = val
|
||||
return parent
|
||||
|
||||
|
||||
def roulette_wheel_selection(pop_eval):
|
||||
# Roulette wheel
|
||||
random_val = np.random.random() * sum(pop_eval)
|
||||
for idx, val in enumerate(pop_eval):
|
||||
random_val -= val
|
||||
if random_val <= 0:
|
||||
return idx
|
||||
return len(pop_eval) - 1
|
||||
|
||||
|
||||
def get_top_k_value(pop_val, k: int, reverse=True):
|
||||
res = []
|
||||
pop_val_cpy = copy.deepcopy(pop_val)
|
||||
pop_val_cpy.sort(reverse=reverse)
|
||||
|
||||
for i in range(min(len(pop_val_cpy), k)):
|
||||
for j in range(len(pop_val)):
|
||||
if abs(pop_val_cpy[i] - pop_val[j]) < 1e-9 and j not in res:
|
||||
res.append(j)
|
||||
break
|
||||
return res
|
720
base_optimizer/optimizer_feederpriority.py
Normal file
720
base_optimizer/optimizer_feederpriority.py
Normal file
@ -0,0 +1,720 @@
|
||||
from base_optimizer.optimizer_common import *
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def feeder_allocate(component_data, pcb_data, feeder_data, nozzle_pattern, figure=False):
|
||||
|
||||
feeder_points, feeder_division_points = defaultdict(int), defaultdict(int) # 供料器贴装点数
|
||||
mount_center_pos = defaultdict(int)
|
||||
|
||||
feeder_limit, feeder_arrange = defaultdict(int), defaultdict(int)
|
||||
part_nozzle = defaultdict(str)
|
||||
|
||||
feeder_base = [-2] * max_slot_index # 已安装在供料器基座上的元件(-2: 未分配,-1: 占用状态)
|
||||
feeder_base_points = [0] * max_slot_index # 供料器基座结余贴装点数量
|
||||
|
||||
for data in pcb_data.iterrows():
|
||||
pos, part = data[1]['x'] + stopper_pos[0], data[1]['part']
|
||||
|
||||
part_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
if part not in component_data:
|
||||
feeder_limit[part_index] = component_data.loc[part_index]['feeder-limit']
|
||||
feeder_arrange[part_index] = 0
|
||||
|
||||
feeder_points[part_index] += 1
|
||||
mount_center_pos[part_index] += ((pos - mount_center_pos[part_index]) / feeder_points[part_index])
|
||||
part_nozzle[part_index] = component_data.loc[part_index]['nz']
|
||||
|
||||
for part_index, points in feeder_points.items():
|
||||
feeder_division_points[part_index] = max(points // feeder_limit[part_index], 1)
|
||||
|
||||
nozzle_component, nozzle_component_points = defaultdict(list), defaultdict(list)
|
||||
for part, nozzle in part_nozzle.items():
|
||||
for _ in range(feeder_limit[part]):
|
||||
nozzle_component[nozzle].append(part)
|
||||
nozzle_component_points[nozzle].append(feeder_points[part])
|
||||
|
||||
if feeder_data is not None:
|
||||
for _, feeder in feeder_data.iterrows():
|
||||
slot, part = feeder['slot'], feeder['part']
|
||||
part_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
|
||||
# 供料器基座分配位置和对应贴装点数
|
||||
feeder_base[slot], feeder_base_points[slot] = part_index, feeder_division_points[part_index]
|
||||
|
||||
feeder_type = component_data.loc[part_index]['fdr']
|
||||
extra_width = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval
|
||||
while extra_width > 0:
|
||||
slot += 1
|
||||
feeder_base[slot] = -1
|
||||
extra_width -= slot_interval
|
||||
|
||||
feeder_limit[part_index] -= 1
|
||||
feeder_arrange[part_index] += 1
|
||||
if feeder_limit[part_index] < 0:
|
||||
info = 'the number of arranged feeder for [' + part + '] exceeds the quantity limit'
|
||||
raise ValueError(info)
|
||||
|
||||
for nozzle, components in nozzle_component.items():
|
||||
if part_index in components:
|
||||
index_ = components.index(part_index)
|
||||
|
||||
nozzle_component[nozzle].pop(index_)
|
||||
nozzle_component_points[nozzle].pop(index_)
|
||||
break
|
||||
|
||||
nozzle_assigned_counter = optimal_nozzle_assignment(component_data, pcb_data)
|
||||
head_assign_indexes = list(range(max_head_index))
|
||||
nozzle_pattern, optimal_nozzle_pattern, optimal_nozzle_points = [], None, 0
|
||||
# nozzle_pattern = ['CN220', 'CN065','CN065','CN065','CN065','CN220']
|
||||
|
||||
# 先排序
|
||||
nozzle_pattern_list = []
|
||||
for nozzle, counter in nozzle_assigned_counter.items():
|
||||
nozzle_pattern_list.append([nozzle, sum(nozzle_component_points[nozzle]) // counter])
|
||||
nozzle_pattern_list.sort(key=lambda x: x[1], reverse=True)
|
||||
|
||||
# 后确定吸嘴分配模式
|
||||
head_index = [3, 2, 4, 1, 5, 0]
|
||||
nozzle_pattern = [0] * max_head_index
|
||||
for nozzle, _ in nozzle_pattern_list:
|
||||
counter = nozzle_assigned_counter[nozzle]
|
||||
while counter:
|
||||
nozzle_pattern[head_index[0]] = nozzle
|
||||
counter -= 1
|
||||
head_index.pop(0)
|
||||
|
||||
while True:
|
||||
best_assign, best_assign_points = [], []
|
||||
best_assign_slot, best_assign_value = -1, -np.Inf
|
||||
best_nozzle_component, best_nozzle_component_points = None, None
|
||||
for slot in range(1, max_slot_index // 2 - (max_head_index - 1) * interval_ratio + 1):
|
||||
nozzle_assigned_counter_cpy = copy.deepcopy(nozzle_assigned_counter)
|
||||
|
||||
feeder_assign, feeder_assign_points = [], []
|
||||
tmp_feeder_limit, tmp_feeder_points = feeder_limit.copy(), feeder_points.copy()
|
||||
tmp_nozzle_component, tmp_nozzle_component_points = copy.deepcopy(nozzle_component), copy.deepcopy(
|
||||
nozzle_component_points)
|
||||
|
||||
# 记录扫描到的已安装的供料器元件类型
|
||||
for head in range(max_head_index):
|
||||
feeder_assign.append(feeder_base[slot + head * interval_ratio])
|
||||
|
||||
if scan_part := feeder_assign[-1] >= 0:
|
||||
nozzle = part_nozzle[scan_part]
|
||||
feeder_assign_points.append(feeder_base_points[slot + head * interval_ratio])
|
||||
if feeder_assign_points[-1] <= 0:
|
||||
feeder_assign[-1], feeder_assign_points[-1] = -1, 0
|
||||
elif nozzle in nozzle_assigned_counter_cpy.keys():
|
||||
nozzle_assigned_counter_cpy[nozzle] -= 1
|
||||
if nozzle_assigned_counter_cpy[nozzle] == 0:
|
||||
nozzle_assigned_counter_cpy.pop(nozzle)
|
||||
else:
|
||||
feeder_assign_points.append(0)
|
||||
|
||||
if -2 not in feeder_assign: # 无可用槽位
|
||||
if sum(feeder_assign_points) > optimal_nozzle_points:
|
||||
optimal_nozzle_points = sum(feeder_assign_points)
|
||||
optimal_nozzle_pattern = [''] * max_head_index
|
||||
for head in range(max_head_index):
|
||||
optimal_nozzle_pattern[head] = part_nozzle[feeder_assign[head]]
|
||||
continue
|
||||
|
||||
assign_part_stack, assign_part_stack_points = [], []
|
||||
for idx in head_assign_indexes:
|
||||
if feeder_assign[idx] != -2:
|
||||
continue
|
||||
|
||||
if len(nozzle_pattern) == 0: # 吸嘴匹配模式为空,优先分配元件,根据分配元件倒推吸嘴匹配模式
|
||||
nozzle_assign = ''
|
||||
max_points, max_nozzle_points = 0, 0
|
||||
for nozzle in nozzle_assigned_counter_cpy.keys():
|
||||
if len(tmp_nozzle_component[nozzle]) == 0:
|
||||
continue
|
||||
part = max(tmp_nozzle_component[nozzle],
|
||||
key=lambda x: tmp_feeder_points[x] / tmp_feeder_limit[x] if
|
||||
tmp_feeder_points[x] != 0 else 0)
|
||||
index_ = tmp_nozzle_component[nozzle].index(part)
|
||||
if max_points < tmp_nozzle_component_points[nozzle][index_]:
|
||||
max_points, nozzle_assign = tmp_nozzle_component_points[nozzle][index_], nozzle
|
||||
else:
|
||||
# 吸嘴匹配模式非空,按对应吸嘴类型进行元件分配
|
||||
nozzle_assign = nozzle_pattern[idx]
|
||||
|
||||
if len(tmp_nozzle_component[nozzle_assign]) == 0:
|
||||
# 当前头对应吸嘴类型无可用元件,将计划分配的元件压入堆栈
|
||||
part = max(tmp_feeder_points.keys(),
|
||||
key=lambda x: tmp_feeder_points[x] / tmp_feeder_limit[x] if tmp_feeder_limit[
|
||||
x] != 0 else 0)
|
||||
for nozzle, component_list in tmp_nozzle_component.items():
|
||||
if part in component_list:
|
||||
nozzle_assign = nozzle
|
||||
|
||||
assign_part_stack.append(part)
|
||||
assign_part_stack_points.append(feeder_division_points[part])
|
||||
break
|
||||
else:
|
||||
# 当前头对应吸嘴类型有可用元件,直接分配对应类型的元件
|
||||
index_ = tmp_nozzle_component[nozzle_assign].index(max(tmp_nozzle_component[nozzle_assign],
|
||||
key=lambda x: tmp_feeder_points[x] /
|
||||
tmp_feeder_limit[x] if
|
||||
tmp_feeder_limit[x] != 0 else 0))
|
||||
|
||||
part = tmp_nozzle_component[nozzle_assign][index_]
|
||||
|
||||
feeder_type = component_data.loc[part]['fdr']
|
||||
extra_width, extra_slot = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval, 1
|
||||
slot_overlap = False
|
||||
while extra_width > 0:
|
||||
slot_ = slot + idx * interval_ratio + extra_slot
|
||||
if feeder_base[slot_] != -2 or slot_ > max_slot_index // 2:
|
||||
slot_overlap = True
|
||||
break
|
||||
extra_width -= slot_interval
|
||||
extra_slot += 1
|
||||
|
||||
# 可用供料器数目充足且不存在和已有供料器的占位冲突
|
||||
if tmp_feeder_limit[part] > 0 and not slot_overlap:
|
||||
feeder_assign[idx], feeder_assign_points[idx] = part, feeder_division_points[part]
|
||||
extra_width, extra_head = feeder_width[feeder_type][0] + feeder_width[feeder_type][
|
||||
1] - head_interval, 1
|
||||
while extra_width > 0 and idx + extra_head < max_head_index:
|
||||
feeder_assign[idx + extra_head] = -1
|
||||
extra_head += 1
|
||||
extra_width -= head_interval
|
||||
else:
|
||||
part = -1 # 存在位置冲突的元件,不占用可用供料器数
|
||||
|
||||
# 更新吸嘴匹配模式的吸嘴数
|
||||
if nozzle_assign in nozzle_assigned_counter_cpy.keys():
|
||||
nozzle_assigned_counter_cpy[nozzle_assign] -= 1
|
||||
if nozzle_assigned_counter_cpy[nozzle_assign] == 0:
|
||||
nozzle_assigned_counter_cpy.pop(nozzle_assign)
|
||||
|
||||
if part >= 0 and tmp_feeder_limit[part] == 0:
|
||||
continue
|
||||
|
||||
if part in tmp_nozzle_component[nozzle_assign]:
|
||||
part_index = tmp_nozzle_component[nozzle_assign].index(part)
|
||||
|
||||
tmp_nozzle_component[nozzle_assign].pop(part_index)
|
||||
tmp_nozzle_component_points[nozzle_assign].pop(part_index)
|
||||
|
||||
tmp_feeder_limit[part] -= 1
|
||||
tmp_feeder_points[part] -= feeder_division_points[part]
|
||||
|
||||
# 元件堆栈出栈,首先分配吸嘴类型一致的头
|
||||
if nozzle_pattern:
|
||||
for head, feeder in enumerate(feeder_assign):
|
||||
if feeder != -2:
|
||||
continue
|
||||
for idx, part in enumerate(assign_part_stack):
|
||||
|
||||
feeder_type = component_data.loc[part]['fdr']
|
||||
extra_width, extra_slot = feeder_width[feeder_type][0] + feeder_width[feeder_type][
|
||||
1] - slot_interval, 1
|
||||
|
||||
slot_overlap = False
|
||||
while extra_width > 0:
|
||||
slot_ = slot + head * interval_ratio + extra_slot
|
||||
if feeder_base[slot_] != -2 or slot_ > max_slot_index // 2:
|
||||
slot_overlap = True
|
||||
break
|
||||
extra_width -= slot_interval
|
||||
extra_slot += 1
|
||||
|
||||
if component_data.loc[part]['nz'] == nozzle_pattern[head] and not slot_overlap:
|
||||
feeder_assign[head], feeder_assign_points[head] = assign_part_stack[idx], \
|
||||
assign_part_stack_points[idx]
|
||||
|
||||
assign_part_stack.pop(idx)
|
||||
assign_part_stack_points.pop(idx)
|
||||
break
|
||||
|
||||
# 元件堆栈,然后分配元件堆栈中未分配的其它元件
|
||||
for head in head_assign_indexes:
|
||||
if feeder_assign[head] != -2 or len(assign_part_stack) == 0:
|
||||
continue
|
||||
part, points = assign_part_stack[0], assign_part_stack_points[0]
|
||||
|
||||
feeder_type = component_data.loc[part]['fdr']
|
||||
extra_width, extra_slot = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval, 1
|
||||
|
||||
slot_overlap = False
|
||||
while extra_width > 0:
|
||||
slot_ = slot + head * interval_ratio + extra_slot
|
||||
if feeder_base[slot_] != -2 or slot_ > max_slot_index // 2:
|
||||
slot_overlap = True
|
||||
break
|
||||
extra_width -= slot_interval
|
||||
extra_slot += 1
|
||||
|
||||
if not slot_overlap:
|
||||
feeder_assign[head], feeder_assign_points[head] = part, points
|
||||
extra_width, extra_head = feeder_width[feeder_type][0] + feeder_width[feeder_type][
|
||||
1] - head_interval, 1
|
||||
while extra_width > 0 and head + extra_head < max_head_index:
|
||||
feeder_assign[head + extra_head] = -1
|
||||
extra_head += 1
|
||||
extra_width -= head_interval
|
||||
else:
|
||||
# 返还由于机械限位无法分配的,压入元件堆栈中的元素
|
||||
nozzle = component_data.loc[part]['nz']
|
||||
tmp_nozzle_component[nozzle].insert(0, part)
|
||||
tmp_nozzle_component_points[nozzle].insert(0, points)
|
||||
|
||||
assign_part_stack.pop(0)
|
||||
assign_part_stack_points.pop(0)
|
||||
|
||||
# 仍然存在由于机械限位,无法进行分配的在堆栈中的元件
|
||||
while assign_part_stack:
|
||||
part, points = assign_part_stack[0], assign_part_stack_points[0]
|
||||
nozzle = component_data.loc[part]['nz']
|
||||
|
||||
tmp_nozzle_component[nozzle].insert(0, part)
|
||||
tmp_nozzle_component_points[nozzle].insert(0, points)
|
||||
|
||||
assign_part_stack.pop(0)
|
||||
assign_part_stack_points.pop(0)
|
||||
|
||||
nozzle_change_counter, average_slot = 0, []
|
||||
for head, feeder_ in enumerate(feeder_assign):
|
||||
if feeder_ < 0:
|
||||
continue
|
||||
average_slot.append(
|
||||
(mount_center_pos[feeder_] - slotf1_pos[0]) / slot_interval + 1 - head * interval_ratio)
|
||||
if nozzle_pattern and component_data.loc[feeder_]['nz'] != nozzle_pattern[head]:
|
||||
nozzle_change_counter += 1
|
||||
|
||||
if len(average_slot) == 0:
|
||||
continue
|
||||
|
||||
average_slot = sum(average_slot) / len(average_slot)
|
||||
assign_value = 0
|
||||
feeder_assign_points_cpy = feeder_assign_points.copy()
|
||||
while True:
|
||||
points_filter = list(filter(lambda x: x > 0, feeder_assign_points_cpy))
|
||||
if not points_filter:
|
||||
break
|
||||
assign_value += e_gang_pick * min(points_filter) * (len(points_filter) - 1)
|
||||
for head, _ in enumerate(feeder_assign_points_cpy):
|
||||
if feeder_assign_points_cpy[head] == 0:
|
||||
continue
|
||||
feeder_assign_points_cpy[head] -= min(points_filter)
|
||||
|
||||
assign_value -= 1e2 * e_nz_change * nozzle_change_counter + 1e-5 * abs(slot - average_slot)
|
||||
|
||||
if assign_value >= best_assign_value and sum(feeder_assign_points) != 0:
|
||||
|
||||
best_assign_value = assign_value
|
||||
best_assign = feeder_assign.copy()
|
||||
best_assign_points = feeder_assign_points.copy()
|
||||
best_assign_slot = slot
|
||||
best_nozzle_component, best_nozzle_component_points = tmp_nozzle_component, tmp_nozzle_component_points
|
||||
|
||||
if not best_assign_points:
|
||||
break
|
||||
|
||||
if len(nozzle_pattern) == 0:
|
||||
nozzle_pattern = [''] * max_head_index
|
||||
for idx, part in enumerate(best_assign):
|
||||
if part < 0:
|
||||
continue
|
||||
|
||||
# 新安装的供料器
|
||||
if feeder_base[best_assign_slot + idx * interval_ratio] != part:
|
||||
# 除去分配给最大化同时拾取周期的项,保留结余项
|
||||
feeder_base_points[best_assign_slot + idx * interval_ratio] += (
|
||||
feeder_division_points[part] - min(filter(lambda x: x > 0, best_assign_points)))
|
||||
|
||||
feeder_points[part] -= feeder_division_points[part]
|
||||
feeder_limit[part] -= 1
|
||||
feeder_arrange[part] += 1
|
||||
|
||||
if feeder_limit[part] == 0:
|
||||
feeder_division_points[part] = 0
|
||||
for nozzle, components in nozzle_component.items():
|
||||
if part in components:
|
||||
index_ = components.index(part)
|
||||
|
||||
nozzle_component[nozzle].pop(index_)
|
||||
nozzle_component_points[nozzle].pop(index_)
|
||||
break
|
||||
feeder_division_points[part] = 0
|
||||
else:
|
||||
# 已有的供料器
|
||||
feeder_base_points[best_assign_slot + idx * interval_ratio] -= min(
|
||||
filter(lambda x: x > 0, best_assign_points))
|
||||
|
||||
# 更新供料器基座信息
|
||||
feeder_base[best_assign_slot + idx * interval_ratio] = part
|
||||
|
||||
feeder_type, extra_slot = component_data.loc[part]['fdr'], 0
|
||||
extra_width = feeder_width[feeder_type][0] + feeder_width[feeder_type][1] - slot_interval
|
||||
while extra_width > 0:
|
||||
extra_slot += 1
|
||||
if feeder_base[best_assign_slot + idx * interval_ratio + extra_slot] == -2:
|
||||
feeder_base[best_assign_slot + idx * interval_ratio + extra_slot] = -1 # 标记槽位已占用
|
||||
extra_width -= slot_interval
|
||||
|
||||
# 更新吸嘴信息
|
||||
nozzle_pattern[idx] = component_data.loc[part]['nz']
|
||||
|
||||
# 更新头分配的先后顺序
|
||||
head_assign_indexes = np.array(best_assign_points).argsort().tolist()
|
||||
|
||||
nozzle_component, nozzle_component_points = copy.deepcopy(best_nozzle_component), copy.deepcopy(
|
||||
best_nozzle_component_points)
|
||||
|
||||
if sum(best_assign_points) > optimal_nozzle_points:
|
||||
optimal_nozzle_points = sum(best_assign_points)
|
||||
optimal_nozzle_pattern = nozzle_pattern.copy()
|
||||
|
||||
assert not list(filter(lambda x: x < 0, feeder_limit.values())) # 分配供料器数目在限制范围内
|
||||
|
||||
# 若所有供料器均安装在基座上,重新对基座进行扫描,确定最优吸嘴模式(有序)
|
||||
if not optimal_nozzle_points:
|
||||
feeder_base, feeder_base_points = [-2] * max_slot_index, [0] * max_slot_index
|
||||
for _, feeder in feeder_data.iterrows():
|
||||
slot, part = feeder['slot'], feeder['part']
|
||||
part_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
|
||||
# 供料器基座分配位置和对应贴装点数
|
||||
feeder_base[slot], feeder_base_points[slot] = part_index, feeder_division_points[part_index]
|
||||
|
||||
# 前基座 TODO: 后基座
|
||||
for slot in range(max_slot_index // 2 - (max_head_index - 1) * interval_ratio):
|
||||
sum_scan_points = 0
|
||||
for head in range(max_head_index):
|
||||
sum_scan_points += feeder_base_points[slot + head * interval_ratio]
|
||||
|
||||
if sum_scan_points > optimal_nozzle_points:
|
||||
optimal_nozzle_pattern = ['' for _ in range(max_head_index)]
|
||||
for head in range(max_head_index):
|
||||
if part := feeder_base[slot + head * interval_ratio] == -2:
|
||||
continue
|
||||
optimal_nozzle_pattern[head] = part_nozzle[part]
|
||||
|
||||
# 更新供料器占位信息
|
||||
for _, data in feeder_data.iterrows():
|
||||
feeder_base[data['slot']] = -1
|
||||
|
||||
for slot, feeder in enumerate(feeder_base):
|
||||
if feeder < 0:
|
||||
continue
|
||||
part = component_data.loc[feeder]['part']
|
||||
|
||||
feeder_data.loc[len(feeder_data.index)] = [slot, part, 0]
|
||||
|
||||
if figure:
|
||||
# 绘制供料器位置布局
|
||||
for slot in range(max_slot_index // 2):
|
||||
plt.scatter(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1], marker='x', s=12, color='black', alpha=0.5)
|
||||
plt.text(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1] - 45, slot + 1, ha='center', va='bottom',
|
||||
size=8)
|
||||
|
||||
feeder_assign_range = []
|
||||
for feeder in feeder_data.iterrows():
|
||||
slot, part = feeder[1]['slot'], feeder[1]['part']
|
||||
part_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
feeder_type = component_data.loc[part_index]['fdr']
|
||||
width = feeder_width[feeder_type][0] + feeder_width[feeder_type][1]
|
||||
start = slotf1_pos[0] + slot_interval * (slot - 1) - slot_interval / 2
|
||||
end = slotf1_pos[0] + slot_interval * (slot - 1) - slot_interval / 2 + width
|
||||
|
||||
rec_x = [start, end, end, start]
|
||||
rec_y = [slotf1_pos[1] - 40, slotf1_pos[1] - 40, slotf1_pos[1] + 10, slotf1_pos[1] + 10]
|
||||
|
||||
c = 'red' if feeder[1]['arg'] == 0 else 'black' # 黑色表示已分配,红色表示新分配
|
||||
plt.text(slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1] + 12,
|
||||
part + ': ' + str(feeder_points[part_index]), ha='center', size=7, rotation=90, color=c)
|
||||
|
||||
plt.fill(rec_x, rec_y, facecolor='yellow', alpha=0.4)
|
||||
|
||||
feeder_assign_range.append([start, end])
|
||||
|
||||
# 记录重叠区间
|
||||
feeder_assign_range.sort(key=lambda x: x[0])
|
||||
for i in range(1, len(feeder_assign_range)):
|
||||
if feeder_assign_range[i][0] < feeder_assign_range[i - 1][1]:
|
||||
start, end = feeder_assign_range[i][0], feeder_assign_range[i - 1][1]
|
||||
|
||||
rec_x = [start, end, end, start]
|
||||
rec_y = [slotf1_pos[1] - 40, slotf1_pos[1] - 40, slotf1_pos[1] + 10, slotf1_pos[1] + 10]
|
||||
plt.fill(rec_x, rec_y, facecolor='red')
|
||||
|
||||
plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
|
||||
[slotf1_pos[1] + 10, slotf1_pos[1] + 10], color='black')
|
||||
plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
|
||||
[slotf1_pos[1] - 40, slotf1_pos[1] - 40], color='black')
|
||||
|
||||
for counter in range(max_slot_index // 2 + 1):
|
||||
pos = slotf1_pos[0] + (counter - 0.5) * slot_interval
|
||||
plt.plot([pos, pos], [slotf1_pos[1] + 10, slotf1_pos[1] - 40], color='black', linewidth=1)
|
||||
|
||||
plt.ylim(-10, 100)
|
||||
plt.show()
|
||||
|
||||
return optimal_nozzle_pattern
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def feeder_base_scan(component_data, pcb_data, feeder_data, nozzle_pattern):
|
||||
feeder_assign_check = set()
|
||||
for feeder in feeder_data.iterrows():
|
||||
feeder_assign_check.add(feeder[1]['part'])
|
||||
|
||||
component_points = [0] * len(component_data)
|
||||
for step in pcb_data.iterrows():
|
||||
part = step[1]['part']
|
||||
part_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
|
||||
component_points[part_index] += 1
|
||||
nozzle_type = component_data.loc[part_index]['nz']
|
||||
if nozzle_type not in nozzle_limit.keys() or nozzle_limit[nozzle_type] <= 0:
|
||||
info = 'there is no available nozzle [' + nozzle_type + '] for the assembly process'
|
||||
raise ValueError(info)
|
||||
|
||||
assert len(feeder_assign_check) == len(component_points) - component_points.count(0) # 所有供料器均已分配槽位
|
||||
feeder_part = [-1] * max_slot_index
|
||||
for feeder in feeder_data.iterrows():
|
||||
part, slot = feeder[1]['part'], feeder[1]['slot']
|
||||
part_index = component_data[component_data['part'] == part].index.tolist()
|
||||
if len(part_index) != 1:
|
||||
print('unregistered component: ', part, ' in slot', slot)
|
||||
continue
|
||||
part_index = part_index[0]
|
||||
feeder_part[slot] = part_index
|
||||
|
||||
component_result, cycle_result, feeder_slot_result = [], [], [] # 贴装点索引和拾取槽位优化结果
|
||||
|
||||
nozzle_mode = [nozzle_pattern] # 吸嘴匹配模式
|
||||
with tqdm(total=len(pcb_data)) as pbar:
|
||||
pbar.set_description('feeder scan process')
|
||||
pbar_prev = 0
|
||||
value_increment_base = 0
|
||||
while True:
|
||||
# === 周期内循环 ===
|
||||
assigned_part = [-1 for _ in range(max_head_index)] # 当前扫描到的头分配元件信息
|
||||
assigned_cycle = [0 for _ in range(max_head_index)] # 当前扫描到的元件最大分配次数
|
||||
assigned_slot = [-1 for _ in range(max_head_index)] # 当前扫描到的供料器分配信息
|
||||
|
||||
best_assigned_eval_func = -float('inf')
|
||||
nozzle_insert_cycle = 0
|
||||
for cycle_index, nozzle_cycle in enumerate(nozzle_mode):
|
||||
scan_eval_func_list = [] # 若干次扫描得到的最优解
|
||||
# nozzle_cycle 吸嘴模式下,已扫描到的最优结果
|
||||
cur_scan_part = [-1 for _ in range(max_head_index)]
|
||||
cur_scan_cycle = [0 for _ in range(max_head_index)]
|
||||
cur_scan_slot = [-1 for _ in range(max_head_index)]
|
||||
cur_nozzle_limit = copy.deepcopy(nozzle_limit)
|
||||
|
||||
while True:
|
||||
best_scan_part, best_scan_cycle = [-1 for _ in range(max_head_index)], [-1 for _ in
|
||||
range(max_head_index)]
|
||||
best_scan_slot = [-1 for _ in range(max_head_index)]
|
||||
best_scan_nozzle_limit = copy.deepcopy(cur_nozzle_limit)
|
||||
|
||||
scan_eval_func, search_break = -float('inf'), True
|
||||
|
||||
# 前供料器基座扫描
|
||||
for slot in range(1, max_slot_index // 2 - (max_head_index - 1) * interval_ratio + 1):
|
||||
scan_cycle, scan_part, scan_slot = cur_scan_cycle.copy(), cur_scan_part.copy(), cur_scan_slot.copy()
|
||||
scan_nozzle_limit = copy.deepcopy(cur_nozzle_limit)
|
||||
|
||||
# 预扫描确定各类型元件拾取数目(前瞻)
|
||||
preview_scan_part = defaultdict(int)
|
||||
for head in range(max_head_index):
|
||||
part = feeder_part[slot + head * interval_ratio]
|
||||
|
||||
# 贴装头和拾取槽位满足对应关系
|
||||
if scan_part[head] == -1 and part != -1 and component_points[part] > 0 and scan_part.count(
|
||||
part) < component_points[part]:
|
||||
preview_scan_part[part] += 1
|
||||
|
||||
component_counter = 0
|
||||
for head in range(max_head_index):
|
||||
part = feeder_part[slot + head * interval_ratio]
|
||||
# 1.匹配条件满足: 贴装头和拾取槽位满足对应关系
|
||||
if scan_part[head] == -1 and part != -1 and component_points[part] > 0 and scan_part.count(
|
||||
part) < component_points[part]:
|
||||
# 2.匹配条件满足:不超过可用吸嘴数的限制
|
||||
nozzle = component_data.loc[part]['nz']
|
||||
if scan_nozzle_limit[nozzle] <= 0:
|
||||
continue
|
||||
|
||||
# 3.增量条件满足: 引入新的元件类型不会使代价函数的值减少(前瞻)
|
||||
if scan_cycle.count(0) == max_head_index:
|
||||
gang_pick_change = component_points[part]
|
||||
else:
|
||||
prev_cycle = min(filter(lambda x: x > 0, scan_cycle))
|
||||
# 同时拾取数的提升
|
||||
gang_pick_change = min(prev_cycle, component_points[part] // preview_scan_part[part])
|
||||
|
||||
# 4.拾取移动距离条件满足: 邻近元件进行同时抓取,降低移动路径长度
|
||||
# reference_slot = -1
|
||||
# for head_, slot_ in enumerate(scan_slot):
|
||||
# if slot_ != -1:
|
||||
# reference_slot = slot_ - head_ * interval_ratio
|
||||
# if reference_slot != -1 and abs(reference_slot - slot) > (max_head_index - 1) * interval_ratio:
|
||||
# continue
|
||||
|
||||
# 5.同时拾取的增量 和 吸嘴更换次数比较
|
||||
prev_nozzle_change = 0
|
||||
if cycle_index + 1 < len(nozzle_mode):
|
||||
prev_nozzle_change = 2 * (nozzle_cycle[head] != nozzle_mode[cycle_index + 1][head])
|
||||
|
||||
# 避免首个周期吸杆占用率低的问题
|
||||
if nozzle_cycle[head] == '':
|
||||
nozzle_change = 0
|
||||
else:
|
||||
nozzle_change = 2 * (nozzle != nozzle_cycle[head])
|
||||
|
||||
if cycle_index + 1 < len(nozzle_mode):
|
||||
nozzle_change += 2 * (nozzle != nozzle_mode[cycle_index + 1][head])
|
||||
nozzle_change -= prev_nozzle_change
|
||||
|
||||
val = e_gang_pick * gang_pick_change - e_nz_change * nozzle_change
|
||||
if val < value_increment_base:
|
||||
continue
|
||||
|
||||
component_counter += 1
|
||||
|
||||
scan_part[head] = part
|
||||
scan_cycle[head] = component_points[part] // preview_scan_part[part]
|
||||
scan_slot[head] = slot + head * interval_ratio
|
||||
|
||||
scan_nozzle_limit[nozzle] -= 1
|
||||
|
||||
nozzle_counter = 0 # 吸嘴更换次数
|
||||
# 上一周期
|
||||
for head, nozzle in enumerate(nozzle_cycle):
|
||||
if scan_part[head] == -1:
|
||||
continue
|
||||
if component_data.loc[scan_part[head]]['nz'] != nozzle and nozzle != '':
|
||||
nozzle_counter += 2
|
||||
|
||||
# 下一周期(额外增加的吸嘴更换次数)
|
||||
if cycle_index + 1 < len(nozzle_mode):
|
||||
for head, nozzle in enumerate(nozzle_mode[cycle_index + 1]):
|
||||
if scan_part[head] == -1:
|
||||
continue
|
||||
prev_counter, new_counter = 0, 0
|
||||
if nozzle_cycle[head] != nozzle and nozzle_cycle[head] != '' and nozzle != '':
|
||||
prev_counter += 2
|
||||
if component_data.loc[scan_part[head]]['nz'] != nozzle and nozzle != '':
|
||||
new_counter += 2
|
||||
nozzle_counter += new_counter - prev_counter
|
||||
else:
|
||||
for head, nozzle in enumerate(nozzle_mode[0]):
|
||||
if scan_part[head] == -1:
|
||||
continue
|
||||
prev_counter, new_counter = 0, 0
|
||||
if nozzle_cycle[head] != nozzle and nozzle_cycle[head] != '' and nozzle != '':
|
||||
prev_counter += 2
|
||||
if component_data.loc[scan_part[head]]['nz'] != nozzle and nozzle != '':
|
||||
new_counter += 2
|
||||
nozzle_counter += new_counter - prev_counter
|
||||
|
||||
if component_counter == 0: # 当前情形下未扫描到任何元件
|
||||
continue
|
||||
|
||||
search_break = False
|
||||
|
||||
scan_part_head = defaultdict(list)
|
||||
for head, part in enumerate(scan_part):
|
||||
if part == -1:
|
||||
continue
|
||||
scan_part_head[part].append(head)
|
||||
|
||||
for part, heads in scan_part_head.items():
|
||||
part_cycle = component_points[part] // len(heads)
|
||||
for head in heads:
|
||||
scan_cycle[head] = part_cycle
|
||||
|
||||
# 计算扫描后的代价函数,记录扫描后的最优解
|
||||
# 短期收益
|
||||
cycle = min(filter(lambda x: x > 0, scan_cycle))
|
||||
gang_pick_counter, gang_pick_slot_set = 0, set()
|
||||
for head, pick_slot in enumerate(scan_slot):
|
||||
gang_pick_slot_set.add(pick_slot - head * interval_ratio)
|
||||
|
||||
eval_func_short_term = e_gang_pick * (max_head_index - scan_slot.count(-1) - len(
|
||||
gang_pick_slot_set)) * cycle - e_nz_change * nozzle_counter
|
||||
|
||||
# 长期收益
|
||||
gang_pick_slot_dict = defaultdict(list)
|
||||
for head, pick_slot in enumerate(scan_slot):
|
||||
if pick_slot == -1:
|
||||
continue
|
||||
gang_pick_slot_dict[pick_slot - head * interval_ratio].append(scan_cycle[head])
|
||||
|
||||
eval_func_long_term = 0
|
||||
for pick_cycle in gang_pick_slot_dict.values():
|
||||
while pick_cycle:
|
||||
min_cycle = min(pick_cycle)
|
||||
eval_func_long_term += e_gang_pick * (len(pick_cycle) - 1) * min(pick_cycle)
|
||||
pick_cycle = list(map(lambda c: c - min_cycle, pick_cycle))
|
||||
pick_cycle = list(filter(lambda c: c > 0, pick_cycle))
|
||||
eval_func_long_term -= e_nz_change * nozzle_counter
|
||||
|
||||
ratio = 0.5
|
||||
eval_func = (1 - ratio) * eval_func_short_term + ratio * eval_func_long_term
|
||||
if eval_func >= scan_eval_func:
|
||||
scan_eval_func = eval_func
|
||||
best_scan_part, best_scan_cycle = scan_part.copy(), scan_cycle.copy()
|
||||
best_scan_slot = scan_slot.copy()
|
||||
|
||||
best_scan_nozzle_limit = copy.deepcopy(scan_nozzle_limit)
|
||||
|
||||
if search_break:
|
||||
break
|
||||
|
||||
scan_eval_func_list.append(scan_eval_func)
|
||||
|
||||
cur_scan_part = best_scan_part.copy()
|
||||
cur_scan_slot = best_scan_slot.copy()
|
||||
cur_scan_cycle = best_scan_cycle.copy()
|
||||
|
||||
cur_nozzle_limit = copy.deepcopy(best_scan_nozzle_limit)
|
||||
|
||||
if len(scan_eval_func_list) != 0:
|
||||
if sum(scan_eval_func_list) >= best_assigned_eval_func:
|
||||
best_assigned_eval_func = sum(scan_eval_func_list)
|
||||
|
||||
assigned_part = cur_scan_part.copy()
|
||||
assigned_slot = cur_scan_slot.copy()
|
||||
assigned_cycle = cur_scan_cycle.copy()
|
||||
|
||||
nozzle_insert_cycle = cycle_index
|
||||
|
||||
# 从供料器基座中移除对应数量的贴装点
|
||||
nonzero_cycle = [cycle for cycle in assigned_cycle if cycle > 0]
|
||||
if not nonzero_cycle:
|
||||
value_increment_base -= max_head_index
|
||||
continue
|
||||
|
||||
for head, slot in enumerate(assigned_slot):
|
||||
if assigned_part[head] == -1:
|
||||
continue
|
||||
component_points[feeder_part[slot]] -= min(nonzero_cycle)
|
||||
|
||||
component_result.insert(nozzle_insert_cycle, assigned_part)
|
||||
cycle_result.insert(nozzle_insert_cycle, min(nonzero_cycle))
|
||||
feeder_slot_result.insert(nozzle_insert_cycle, assigned_slot)
|
||||
|
||||
# 更新吸嘴匹配模式
|
||||
cycle_nozzle = nozzle_mode[nozzle_insert_cycle].copy()
|
||||
for head, component in enumerate(assigned_part):
|
||||
if component == -1:
|
||||
continue
|
||||
cycle_nozzle[head] = component_data.loc[component]['nz']
|
||||
|
||||
nozzle_mode.insert(nozzle_insert_cycle + 1, cycle_nozzle)
|
||||
|
||||
pbar.update(len(pcb_data) - sum(component_points) - pbar_prev)
|
||||
pbar_prev = len(pcb_data) - sum(component_points)
|
||||
if sum(component_points) == 0:
|
||||
break
|
||||
|
||||
return component_result, cycle_result, feeder_slot_result
|
564
base_optimizer/optimizer_hybridgenetic.py
Normal file
564
base_optimizer/optimizer_hybridgenetic.py
Normal file
@ -0,0 +1,564 @@
|
||||
import copy
|
||||
import random
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from base_optimizer.optimizer_common import *
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def dynamic_programming_cycle_path(cycle_placement, cycle_points):
|
||||
head_sequence = []
|
||||
num_pos = sum([placement != -1 for placement in cycle_placement]) + 1
|
||||
|
||||
pos, head_set = [], []
|
||||
average_pos_x, counter = 0, 1
|
||||
for head, placement in enumerate(cycle_placement):
|
||||
if placement == -1:
|
||||
continue
|
||||
head_set.append(head)
|
||||
pos.append([cycle_points[head][0], cycle_points[head][1]])
|
||||
average_pos_x = average_pos_x + (pos[-1][0] - average_pos_x) / counter
|
||||
|
||||
counter += 1
|
||||
|
||||
pos.insert(0, [average_pos_x, slotf1_pos[1]])
|
||||
|
||||
def get_distance(pos_1, pos_2):
|
||||
return math.sqrt((pos_1[0] - pos_2[0]) ** 2 + (pos_1[1] - pos_2[1]) ** 2)
|
||||
|
||||
# 各节点之间的距离
|
||||
dist = [[get_distance(pos_1, pos_2) for pos_2 in pos] for pos_1 in pos]
|
||||
|
||||
min_dist = [[np.inf for i in range(num_pos)] for s in range(1 << num_pos)]
|
||||
min_path = [[[] for i in range(num_pos)] for s in range(1 << num_pos)]
|
||||
|
||||
# 状压dp搜索
|
||||
for s in range(1, 1 << num_pos, 2):
|
||||
# 考虑节点集合s必须包括节点0
|
||||
if not (s & 1):
|
||||
continue
|
||||
for j in range(1, num_pos):
|
||||
# 终点i需在当前考虑节点集合s内
|
||||
if not (s & (1 << j)):
|
||||
continue
|
||||
if s == int((1 << j) | 1):
|
||||
# 若考虑节点集合s仅含节点0和节点j,dp边界,赋予初值
|
||||
# print('j:', j)
|
||||
min_path[s][j] = [j]
|
||||
min_dist[s][j] = dist[0][j]
|
||||
|
||||
# 枚举下一个节点i,更新
|
||||
for i in range(1, num_pos):
|
||||
# 下一个节点i需在考虑节点集合s外
|
||||
if s & (1 << i):
|
||||
continue
|
||||
if min_dist[s][j] + dist[j][i] < min_dist[s | (1 << i)][i]:
|
||||
min_path[s | (1 << i)][i] = min_path[s][j] + [i]
|
||||
min_dist[s | (1 << i)][i] = min_dist[s][j] + dist[j][i]
|
||||
|
||||
ans_dist = np.inf
|
||||
ans_path = []
|
||||
# 求最终最短哈密顿回路
|
||||
for i in range(1, num_pos):
|
||||
if min_dist[(1 << num_pos) - 1][i] + dist[i][0] < ans_dist:
|
||||
# 更新,回路化
|
||||
ans_path = min_path[s][i]
|
||||
ans_dist = min_dist[(1 << num_pos) - 1][i] + dist[i][0]
|
||||
|
||||
for element in ans_path:
|
||||
head_sequence.append(head_set[element - 1])
|
||||
|
||||
return head_sequence
|
||||
|
||||
|
||||
def pickup_group_combination(component_nozzle, designated_nozzle, supply, supply_cycle, demand, demand_cycle):
|
||||
|
||||
combination, combination_cycle = demand.copy(), demand_cycle.copy()
|
||||
supply_cpy = supply.copy()
|
||||
|
||||
while True:
|
||||
supply_cpy_bits = max_head_index - supply_cpy.count(None)
|
||||
if supply_cpy_bits == 0:
|
||||
break
|
||||
max_match_offset, max_match_counter = 0, 0
|
||||
supply_cpy_index = [idx for idx, part in enumerate(supply_cpy) if part] # 加快搜索速度
|
||||
for offset in range(-supply_cpy_index[-1], max_head_index - supply_cpy_index[0]):
|
||||
match_counter = 0
|
||||
for idx, part in enumerate(supply_cpy):
|
||||
if 0 <= idx + offset < max_head_index:
|
||||
if part is None:
|
||||
continue
|
||||
if combination[idx + offset] is None and designated_nozzle[idx + offset] == designated_nozzle[idx]:
|
||||
match_counter += 1
|
||||
if match_counter > max_match_counter:
|
||||
max_match_counter = match_counter
|
||||
max_match_offset = offset
|
||||
if match_counter == supply_cpy_bits:
|
||||
break
|
||||
|
||||
for idx, part in enumerate(supply_cpy):
|
||||
if 0 <= idx + max_match_offset < max_head_index:
|
||||
if part is None:
|
||||
continue
|
||||
|
||||
if demand[idx + max_match_offset] is None:
|
||||
combination[idx + max_match_offset] = part
|
||||
combination_cycle[idx + max_match_offset] = supply_cycle[idx]
|
||||
supply_cpy[idx] = None
|
||||
|
||||
return combination, combination_cycle
|
||||
|
||||
|
||||
def cal_individual_val(component_nozzle, component_point_pos, designated_nozzle, pickup_group, pickup_group_cycle,
|
||||
pair_group, feeder_part_arrange, individual):
|
||||
|
||||
place_time, pick_time = 0.234, 0.4
|
||||
x_moving_speed, y_moving_speed = 300, 300 # mm/s
|
||||
|
||||
prev_pair_index = None
|
||||
sequenced_pickup_group, sequenced_pickup_cycle = [], []
|
||||
for gene in individual:
|
||||
pickup = pickup_group[gene]
|
||||
|
||||
pair_index = None
|
||||
for idx, pair in enumerate(pair_group):
|
||||
if gene in pair:
|
||||
pair_index = idx
|
||||
break
|
||||
|
||||
if pair_index is not None and pair_index == prev_pair_index:
|
||||
for idx, component in enumerate(pickup):
|
||||
sequenced_pickup_group[-1][idx] = component
|
||||
else:
|
||||
sequenced_pickup_group.append(pickup.copy())
|
||||
sequenced_pickup_cycle.append(pickup_group_cycle[gene])
|
||||
|
||||
V = [float('inf') for _ in range(len(sequenced_pickup_group) + 1)] # Node Value
|
||||
V[0] = 0
|
||||
V_SNode = [-1 for _ in range(len(sequenced_pickup_group) + 1)]
|
||||
|
||||
nozzle_assigned_heads = defaultdict(int)
|
||||
for nozzle in designated_nozzle:
|
||||
nozzle_assigned_heads[nozzle] += 1
|
||||
|
||||
pickup_result, pickup_cycle_result = [[] for _ in range(len(V))], [[] for _ in range(len(V))]
|
||||
component_point_index = defaultdict(int)
|
||||
for i in range(1, len(V)):
|
||||
cost, t0 = 0, 0
|
||||
load = defaultdict(int)
|
||||
Pd, Pd_cycle = [None for _ in range(max_head_index)], [0 for _ in range(max_head_index)] # demand pickup
|
||||
j = i
|
||||
while j < len(V):
|
||||
Ps, Ps_cycle = sequenced_pickup_group[j - 1], [sequenced_pickup_cycle[j - 1] for _ in
|
||||
range(max_head_index)] # supply pickup and its cycle
|
||||
for part in Ps:
|
||||
if part:
|
||||
load[component_nozzle[part]] += 1
|
||||
|
||||
is_combinable = True
|
||||
for nozzle, counter in load.items():
|
||||
if counter > nozzle_assigned_heads[nozzle]:
|
||||
is_combinable = False
|
||||
|
||||
if is_combinable:
|
||||
cost = cost - t0
|
||||
# combine sequenced pickup ρb and ps into ρu(union pickup)
|
||||
Pu, Pu_cycle = pickup_group_combination(component_nozzle, designated_nozzle, Ps, Ps_cycle, Pd, Pd_cycle)
|
||||
|
||||
# decide the placement cluster and sequencing of pickup ρu
|
||||
pickup_action_counter, place_action_counter = 0, max_head_index - Pu.count(None)
|
||||
right_most_slot, left_most_slot = 0, max_slot_index // 2 # most left and right pickup slot
|
||||
|
||||
# === TODO: 机械限位、后槽位分配未处理 ===
|
||||
for head in range(max_head_index):
|
||||
if not Pu[head]:
|
||||
continue
|
||||
assert Pu[head] in feeder_part_arrange.keys()
|
||||
for slot in feeder_part_arrange[Pu[head]]:
|
||||
left_most_slot = min(slot - head * interval_ratio, left_most_slot)
|
||||
right_most_slot = max(slot - head * interval_ratio, right_most_slot)
|
||||
|
||||
# calculate forward, backward, pick and place traveling time
|
||||
t_FW, t_BW, t_PL, t_PU = 0, 0, 0, 0
|
||||
cycle = 0
|
||||
while cycle < max(Pu_cycle):
|
||||
mount_points = []
|
||||
for head, part in enumerate(Pu):
|
||||
if part is None or cycle > Pu_cycle[head]:
|
||||
continue
|
||||
idx = component_point_index[part]
|
||||
mount_points.append([component_point_pos[part][idx][0] - head * head_interval + stopper_pos[0],
|
||||
component_point_pos[part][idx][0] + stopper_pos[1]])
|
||||
assert len(mount_points) > 0
|
||||
|
||||
# calculate cycle moving distance
|
||||
mount_points.sort(key=lambda x: x[0])
|
||||
t_FW += max(
|
||||
abs(slotf1_pos[0] + (left_most_slot - 1) * slot_interval - mount_points[0][0]) / x_moving_speed,
|
||||
abs(slotf1_pos[1] - mount_points[0][1]) / y_moving_speed)
|
||||
t_BW += max(
|
||||
abs(slotf1_pos[0] + (right_most_slot - 1) * slot_interval - mount_points[-1][0]) / x_moving_speed,
|
||||
abs(slotf1_pos[1] - mount_points[-1][1]) / y_moving_speed)
|
||||
# pick up moving time
|
||||
t_PU += (right_most_slot - left_most_slot) * slot_interval / x_moving_speed
|
||||
# place moving time
|
||||
for idx_points in range(len(mount_points) - 1):
|
||||
t_PL += max(abs(mount_points[idx_points][0] - mount_points[idx_points + 1][0]) / x_moving_speed,
|
||||
abs(mount_points[idx_points][1] - mount_points[idx_points + 1][1]) / y_moving_speed)
|
||||
cycle += 1
|
||||
|
||||
t0 = t_FW + (t_PL + place_action_counter * place_time) + t_BW
|
||||
cost += (t_PU + pickup_action_counter * pick_time) + t0
|
||||
|
||||
if V[i - 1] + cost < V[j]:
|
||||
pickup_result[j], pickup_cycle_result[j] = Pu, Pu_cycle
|
||||
V_SNode[j] = i - 1
|
||||
V[j] = V[i - 1] + cost
|
||||
|
||||
Pd, Pd_cycle = Pu, Pu_cycle
|
||||
j += 1
|
||||
else:
|
||||
break
|
||||
|
||||
node = len(V) - 1
|
||||
while True:
|
||||
prev_node = V_SNode[node]
|
||||
if prev_node == -1:
|
||||
break
|
||||
for k in range(prev_node + 1, node):
|
||||
pickup_result[k], pickup_cycle_result[k] = [], []
|
||||
node = prev_node
|
||||
return V[-1], pickup_result, pickup_cycle_result
|
||||
|
||||
|
||||
def convert_individual_2_result(component_data, component_point_pos, designated_nozzle, pickup_group, pickup_group_cycle,
|
||||
pair_group, feeder_lane, individual):
|
||||
component_result, cycle_result, feeder_slot_result = [], [], []
|
||||
placement_result, head_sequence_result = [], []
|
||||
|
||||
# === 记录不同元件对应的槽位 ===
|
||||
feeder_part_arrange = defaultdict(list)
|
||||
for slot in range(1, max_slot_index // 2 + 1):
|
||||
if feeder_lane[slot]:
|
||||
feeder_part_arrange[feeder_lane[slot]].append(slot)
|
||||
|
||||
# === 记录不同元件的注册吸嘴类型 ===
|
||||
component_nozzle = defaultdict(str)
|
||||
for pickup in pickup_group:
|
||||
for part in pickup:
|
||||
if part is None or part in component_nozzle.keys():
|
||||
continue
|
||||
component_nozzle[part] = component_data[component_data['part'] == part]['nz'].tolist()[0]
|
||||
|
||||
# initial result
|
||||
_, pickup_result, pickup_cycle_result = cal_individual_val(component_nozzle, component_point_pos, designated_nozzle,
|
||||
pickup_group, pickup_group_cycle,
|
||||
pair_group, feeder_part_arrange, individual)
|
||||
|
||||
for idx, pickup in enumerate(pickup_result):
|
||||
while pickup and max(pickup_cycle_result[idx]) != 0:
|
||||
cycle = min([cycle_ for cycle_ in pickup_cycle_result[idx] if cycle_ > 0])
|
||||
feeder_part_arrange_index = defaultdict(int)
|
||||
component_result.append([-1 for _ in range(max_head_index)])
|
||||
feeder_slot_result.append([-1 for _ in range(max_head_index)])
|
||||
cycle_result.append(cycle)
|
||||
for head, part in enumerate(pickup):
|
||||
if part is None or pickup_cycle_result[idx][head] == 0:
|
||||
continue
|
||||
|
||||
part_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
component_result[-1][head] = part_index
|
||||
feeder_slot_result[-1][head] = feeder_part_arrange[part][feeder_part_arrange_index[part]]
|
||||
feeder_part_arrange_index[part] += 1
|
||||
if feeder_part_arrange_index[part] >= len(feeder_part_arrange[part]):
|
||||
feeder_part_arrange_index[part] = 0
|
||||
|
||||
pickup_cycle_result[idx][head] -= cycle
|
||||
|
||||
component_point_index = defaultdict(int)
|
||||
for cycle_set in range(len(cycle_result)):
|
||||
for cycle in range(cycle_result[cycle_set]):
|
||||
placement_result.append([-1 for _ in range(max_head_index)])
|
||||
mount_point = [[0, 0] for _ in range(max_head_index)]
|
||||
for head in range(max_head_index):
|
||||
part_index = component_result[cycle_set][head]
|
||||
if part_index == -1:
|
||||
continue
|
||||
|
||||
part = component_data.iloc[part_index]['part']
|
||||
point_info = component_point_pos[part][component_point_index[part]]
|
||||
|
||||
placement_result[-1][head] = point_info[2]
|
||||
mount_point[head] = point_info[0:2]
|
||||
|
||||
component_point_index[part] += 1
|
||||
head_sequence_result.append(dynamic_programming_cycle_path(placement_result[-1], mount_point))
|
||||
|
||||
return component_result, cycle_result, feeder_slot_result, placement_result, head_sequence_result
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def optimizer_hybrid_genetic(pcb_data, component_data, hinter=True):
|
||||
random.seed(0)
|
||||
np.random.seed(0)
|
||||
nozzle_assigned_counter = optimal_nozzle_assignment(component_data, pcb_data)
|
||||
|
||||
# nozzle assignment result:
|
||||
designated_nozzle = [''] * max_head_index
|
||||
head_index = 0
|
||||
for nozzle, num in nozzle_assigned_counter.items():
|
||||
while num > 0:
|
||||
designated_nozzle[head_index] = nozzle
|
||||
head_index += 1
|
||||
num -= 1
|
||||
|
||||
# === component assignment ===
|
||||
component_points, nozzle_components = defaultdict(int), defaultdict(list) # 元件贴装点数,吸嘴-元件对应关系
|
||||
component_feeder_limit, component_divided_points = defaultdict(int), defaultdict(list)
|
||||
for step in pcb_data.iterrows():
|
||||
part = step[1]['part']
|
||||
idx = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
nozzle = component_data.loc[idx]['nz']
|
||||
|
||||
component_feeder_limit[part] = component_data.loc[idx]['feeder-limit']
|
||||
component_points[part] += 1
|
||||
if nozzle_components[nozzle].count(part) < component_feeder_limit[part]:
|
||||
nozzle_components[nozzle].append(part)
|
||||
|
||||
for part, feeder_limit in component_feeder_limit.items():
|
||||
for _ in range(feeder_limit):
|
||||
component_divided_points[part].append(component_points[part] // feeder_limit)
|
||||
|
||||
for part, divided_points in component_divided_points.items():
|
||||
index = 0
|
||||
while sum(divided_points) < component_points[part]:
|
||||
divided_points[index] += 1
|
||||
index += 1
|
||||
|
||||
CT_Group, CT_Points = [], [] # CT: Component Type
|
||||
while sum(len(nozzle_components[nozzle]) for nozzle in nozzle_components.keys()) != 0:
|
||||
|
||||
CT_Group.append([None for _ in range(max_head_index)])
|
||||
CT_Points.append([0 for _ in range(max_head_index)])
|
||||
|
||||
for head_index in range(max_head_index):
|
||||
nozzle = designated_nozzle[head_index] # 分配的吸嘴
|
||||
if len(nozzle_components[nozzle]) == 0: # 无可用元件
|
||||
continue
|
||||
|
||||
max_points, designated_part = 0, None
|
||||
for part in nozzle_components[nozzle]:
|
||||
if component_points[part] > max_points:
|
||||
max_points = component_points[part]
|
||||
designated_part = part
|
||||
|
||||
component_points[designated_part] -= component_divided_points[designated_part][-1]
|
||||
|
||||
CT_Group[-1][head_index] = designated_part
|
||||
CT_Points[-1][head_index] = component_divided_points[designated_part][-1]
|
||||
|
||||
component_divided_points[designated_part].pop()
|
||||
nozzle_components[nozzle].remove(designated_part)
|
||||
|
||||
# === assign CT group to feeder slot ===
|
||||
point_num = len(pcb_data)
|
||||
component_point_pos = defaultdict(list)
|
||||
for point_cnt in range(point_num):
|
||||
part = pcb_data.loc[point_cnt, 'part']
|
||||
component_point_pos[part].append(
|
||||
[pcb_data.loc[point_cnt, 'x'] + stopper_pos[0], pcb_data.loc[point_cnt, 'y'] + stopper_pos[1], point_cnt])
|
||||
|
||||
for pos_list in component_point_pos.values():
|
||||
pos_list.sort(key=lambda x: (x[0], x[1]))
|
||||
|
||||
CT_Group_slot = [-1] * len(CT_Group)
|
||||
feeder_lane = [None] * max_slot_index # 供料器基座上已分配的元件类型
|
||||
CT_Head = defaultdict(list)
|
||||
for pickup in CT_Group:
|
||||
for head, CT in enumerate(pickup):
|
||||
if CT is None:
|
||||
continue
|
||||
if CT not in CT_Head:
|
||||
CT_Head[CT] = [head, head]
|
||||
CT_Head[CT][0] = min(CT_Head[CT][0], head)
|
||||
CT_Head[CT][1] = max(CT_Head[CT][1], head)
|
||||
|
||||
for CTIdx, pickup in enumerate(CT_Group):
|
||||
best_slot = []
|
||||
for cp_index, component in enumerate(pickup):
|
||||
if component is None:
|
||||
continue
|
||||
best_slot.append(round((sum(pos[0] for pos in component_point_pos[component]) / len(
|
||||
component_point_pos[component]) - slotf1_pos[0]) / slot_interval) + 1 - cp_index * interval_ratio)
|
||||
best_slot = round(sum(best_slot) / len(best_slot))
|
||||
|
||||
search_dir, step = 0, 0 # dir: 1-向右, 0-向左
|
||||
prev_assign_available = True
|
||||
while True:
|
||||
assign_slot = best_slot + step if search_dir else best_slot - step
|
||||
if assign_slot + (len(pickup) - 1) * interval_ratio >= max_slot_index / 2 or assign_slot < 0:
|
||||
if not prev_assign_available:
|
||||
raise Exception('feeder assign error!')
|
||||
|
||||
# prev_assign_available = False
|
||||
search_dir = 1 - search_dir
|
||||
if search_dir == 1:
|
||||
step += 1
|
||||
continue
|
||||
|
||||
prev_assign_available = True
|
||||
assign_available = True
|
||||
|
||||
# 分配对应槽位
|
||||
for slot in range(assign_slot, assign_slot + interval_ratio * len(pickup), interval_ratio):
|
||||
pickup_index = int((slot - assign_slot) / interval_ratio)
|
||||
pick_part = pickup[pickup_index]
|
||||
|
||||
# 检查槽位占用情况
|
||||
if feeder_lane[slot] is not None and pick_part is not None:
|
||||
assign_available = False
|
||||
break
|
||||
|
||||
# 检查机械限位冲突
|
||||
if pick_part is not None and (slot - CT_Head[pick_part][0] * interval_ratio <= 0 or
|
||||
slot + (max_head_index - CT_Head[pick_part][1] - 1) * interval_ratio > max_slot_index // 2):
|
||||
assign_available = False
|
||||
break
|
||||
|
||||
if assign_available:
|
||||
for idx, component in enumerate(pickup):
|
||||
if component is not None:
|
||||
feeder_lane[assign_slot + idx * interval_ratio] = component
|
||||
CT_Group_slot[CTIdx] = assign_slot
|
||||
break
|
||||
|
||||
search_dir = 1 - search_dir
|
||||
if search_dir == 1:
|
||||
step += 1
|
||||
|
||||
# === Initial Pickup Group ===
|
||||
initial_pickup, initial_pickup_cycle = [], []
|
||||
for index, CT in enumerate(CT_Group):
|
||||
while True:
|
||||
if CT_Points[index].count(0) == max_head_index:
|
||||
break
|
||||
min_element = min([Points for Points in CT_Points[index] if Points > 0])
|
||||
|
||||
initial_pickup.append(copy.deepcopy(CT_Group[index]))
|
||||
initial_pickup_cycle.append(min_element)
|
||||
for head in range(max_head_index):
|
||||
if CT_Points[index][head] >= min_element:
|
||||
CT_Points[index][head] -= min_element
|
||||
if CT_Points[index][head] == 0:
|
||||
CT_Group[index][head] = None
|
||||
|
||||
# pickup partition rule
|
||||
partition_probability = 0.1
|
||||
pickup_group, pair_group = [], [] # pair_group: pickups from same initial group
|
||||
pickup_group_cycle = []
|
||||
for idx, Pickup in enumerate(initial_pickup):
|
||||
pickup_num = len([element for element in Pickup if element is not None])
|
||||
if 2 <= pickup_num <= max_head_index / 3 or (
|
||||
max_head_index / 3 <= pickup_num <= max_head_index / 2 and np.random.rand() < partition_probability):
|
||||
# partitioned into single component pickups
|
||||
# or partition the potentially inefficient initial pickups with a small probability
|
||||
pair_index = []
|
||||
for index, CT in enumerate(Pickup):
|
||||
if CT is not None:
|
||||
pair_index.append(len(pickup_group))
|
||||
pickup_group.append([None for _ in range(max_head_index)])
|
||||
pickup_group[-1][index] = CT
|
||||
pickup_group_cycle.append(initial_pickup_cycle[idx])
|
||||
pair_group.append(pair_index)
|
||||
else:
|
||||
pickup_group.append(Pickup)
|
||||
pickup_group_cycle.append(initial_pickup_cycle[idx])
|
||||
|
||||
# basic parameter
|
||||
# crossover rate & mutation rate: 80% & 10%
|
||||
# population size: 200
|
||||
# the number of generation: 500
|
||||
crossover_rate, mutation_rate = 0.8, 0.1
|
||||
population_size, n_generations = 200, 500
|
||||
|
||||
# initial solution
|
||||
population = []
|
||||
for _ in range(population_size):
|
||||
pop_permutation = list(range(len(pickup_group)))
|
||||
np.random.shuffle(pop_permutation)
|
||||
population.append(pop_permutation)
|
||||
|
||||
best_individual, best_pop_val = [], []
|
||||
|
||||
# === 记录不同元件对应的槽位 ===
|
||||
feeder_part_arrange = defaultdict(list)
|
||||
for slot in range(1, max_slot_index // 2 + 1):
|
||||
if feeder_lane[slot]:
|
||||
feeder_part_arrange[feeder_lane[slot]].append(slot)
|
||||
|
||||
# === 记录不同元件的注册吸嘴类型 ===
|
||||
component_nozzle = defaultdict(str)
|
||||
for pickup in pickup_group:
|
||||
for part in pickup:
|
||||
if part is None or part in component_nozzle.keys():
|
||||
continue
|
||||
component_nozzle[part] = component_data[component_data['part'] == part]['nz'].tolist()[0]
|
||||
|
||||
with tqdm(total=n_generations) as pbar:
|
||||
pbar.set_description('hybrid genetic process')
|
||||
|
||||
for _ in range(n_generations):
|
||||
# calculate fitness value
|
||||
pop_val = []
|
||||
for pop_idx, individual in enumerate(population):
|
||||
val, _, _ = cal_individual_val(component_nozzle, component_point_pos, designated_nozzle, pickup_group,
|
||||
pickup_group_cycle, pair_group, feeder_part_arrange, individual)
|
||||
pop_val.append(val)
|
||||
|
||||
idx = np.argmin(pop_val)
|
||||
if len(best_pop_val) == 0 or pop_val[idx] < best_pop_val[-1]:
|
||||
best_individual = copy.deepcopy(population[idx])
|
||||
best_pop_val.append(pop_val[idx])
|
||||
|
||||
# min-max convert
|
||||
max_val = 1.5 * max(pop_val)
|
||||
pop_val = list(map(lambda v: max_val - v, pop_val))
|
||||
|
||||
# crossover and mutation
|
||||
c = 0
|
||||
new_population = []
|
||||
for pop in range(population_size):
|
||||
if pop % 2 == 0 and np.random.random() < crossover_rate:
|
||||
index1, index2 = roulette_wheel_selection(pop_val), -1
|
||||
while True:
|
||||
index2 = roulette_wheel_selection(pop_val)
|
||||
if index1 != index2:
|
||||
break
|
||||
# 两点交叉算子
|
||||
offspring1 = directed_edge_recombination_crossover(c, population[index1], population[index2])
|
||||
c += 1
|
||||
offspring2 = directed_edge_recombination_crossover(c, population[index2], population[index1])
|
||||
c += 1
|
||||
|
||||
if np.random.random() < mutation_rate:
|
||||
swap_mutation(offspring1)
|
||||
|
||||
if np.random.random() < mutation_rate:
|
||||
swap_mutation(offspring2)
|
||||
|
||||
new_population.append(offspring1)
|
||||
new_population.append(offspring2)
|
||||
|
||||
# selection
|
||||
top_k_index = get_top_k_value(pop_val, population_size - len(new_population))
|
||||
for index in top_k_index:
|
||||
new_population.append(population[index])
|
||||
|
||||
population = new_population
|
||||
pbar.update(1)
|
||||
|
||||
return convert_individual_2_result(component_data, component_point_pos, designated_nozzle, pickup_group,
|
||||
pickup_group_cycle, pair_group, feeder_lane, best_individual)
|
166
base_optimizer/optimizer_scanbased.py
Normal file
166
base_optimizer/optimizer_scanbased.py
Normal file
@ -0,0 +1,166 @@
|
||||
import itertools
|
||||
from base_optimizer.optimizer_common import *
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def optimizer_scanbased(component_data, pcb_data, hinter):
|
||||
|
||||
population_size = 200 # 种群规模
|
||||
crossover_rate, mutation_rate = .4, .02
|
||||
n_generation = 5
|
||||
|
||||
component_points = [0] * len(component_data)
|
||||
for i in range(len(pcb_data)):
|
||||
part = pcb_data.loc[i]['part']
|
||||
part_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
|
||||
component_points[part_index] += 1
|
||||
nozzle_type = component_data.loc[part_index]['nz']
|
||||
if nozzle_type not in nozzle_limit.keys() or nozzle_limit[nozzle_type] <= 0:
|
||||
info = 'there is no available nozzle [' + nozzle_type + '] for the assembly process'
|
||||
raise ValueError(info)
|
||||
|
||||
# randomly generate permutations
|
||||
generation_ = np.array([i for i in range(max_slot_index // 2)]) # 仅考虑前基座
|
||||
pop_individual, pop_val = [], []
|
||||
for _ in range(population_size):
|
||||
np.random.shuffle(generation_)
|
||||
pop_individual.append(generation_.tolist())
|
||||
|
||||
_, cycle_result, feeder_slot_result = convert_individual_2_result(component_points, pop_individual[-1])
|
||||
|
||||
pop_val.append(feeder_arrange_evaluate(feeder_slot_result, cycle_result))
|
||||
|
||||
# todo: 过程写的有问题,暂时不想改
|
||||
with tqdm(total=n_generation) as pbar:
|
||||
pbar.set_description('hybrid genetic process')
|
||||
for _ in range(n_generation):
|
||||
# 交叉
|
||||
for pop in range(population_size):
|
||||
if pop % 2 == 0 and np.random.random() < crossover_rate:
|
||||
index1, index2 = roulette_wheel_selection(pop_val), -1
|
||||
while True:
|
||||
index2 = roulette_wheel_selection(pop_val)
|
||||
if index1 != index2:
|
||||
break
|
||||
|
||||
# 两点交叉算子
|
||||
offspring1, offspring2 = cycle_crossover(pop_individual[index1], pop_individual[index2])
|
||||
|
||||
_, cycle_result, feeder_slot_result = convert_individual_2_result(component_points, offspring1)
|
||||
pop_val.append(feeder_arrange_evaluate(feeder_slot_result, cycle_result))
|
||||
pop_individual.append(offspring1)
|
||||
|
||||
_, cycle_result, feeder_slot_result = convert_individual_2_result(component_points, offspring2)
|
||||
pop_val.append(feeder_arrange_evaluate(feeder_slot_result, cycle_result))
|
||||
pop_individual.append(offspring2)
|
||||
|
||||
sigma_scaling(pop_val, 1)
|
||||
|
||||
# 变异
|
||||
if np.random.random() < mutation_rate:
|
||||
index_ = roulette_wheel_selection(pop_val)
|
||||
offspring = swap_mutation(pop_individual[index_])
|
||||
_, cycle_result, feeder_slot_result = convert_individual_2_result(component_points, offspring)
|
||||
|
||||
pop_val.append(feeder_arrange_evaluate(feeder_slot_result, cycle_result))
|
||||
pop_individual.append(offspring)
|
||||
|
||||
sigma_scaling(pop_val, 1)
|
||||
|
||||
new_population, new_popval = [], []
|
||||
for index in get_top_k_value(pop_val, population_size):
|
||||
new_population.append(pop_individual[index])
|
||||
new_popval.append(pop_val[index])
|
||||
|
||||
pop_individual, pop_val = new_population, new_popval
|
||||
|
||||
# select the best individual
|
||||
pop = np.argmin(pop_val)
|
||||
component_result, cycle_result, feeder_slot_result = convert_individual_2_result(component_points, pop_individual[pop])
|
||||
|
||||
placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
|
||||
cycle_result, feeder_slot_result)
|
||||
|
||||
return component_result, cycle_result, feeder_slot_result, placement_result, head_sequence
|
||||
|
||||
|
||||
def convert_individual_2_result(component_points, pop):
|
||||
component_result, cycle_result, feeder_slot_result = [], [], []
|
||||
|
||||
feeder_part = [-1] * (max_slot_index // 2) # 已安装在供料器基座上的元件(0: 未分配)
|
||||
feeder_base_points = [0] * (max_slot_index // 2) # 供料器基座结余贴装点数量
|
||||
|
||||
# 将基因信息转换为供料器基座安装结果
|
||||
for idx, gene in enumerate(pop):
|
||||
if idx >= len(component_points):
|
||||
break
|
||||
feeder_part[gene], feeder_base_points[gene] = idx, component_points[idx]
|
||||
|
||||
# TODO: 暂时未考虑可用吸嘴数的限制
|
||||
# for _ in range(math.ceil(sum(component_points) / max_head_index)):
|
||||
while True:
|
||||
# === 周期内循环 ===
|
||||
assigned_part = [-1 for _ in range(max_head_index)] # 当前扫描到的头分配元件信息
|
||||
assigned_slot = [-1 for _ in range(max_head_index)] # 当前扫描到的供料器分配信息
|
||||
|
||||
prev_scan_slot = len(feeder_part) // 2 # 前一轮扫描的位置
|
||||
while True:
|
||||
best_scan_part, best_scan_slot = [-1 for _ in range(max_head_index)], [-1 for _ in range(max_head_index)]
|
||||
best_slot_index = -1
|
||||
for slot in range(max_slot_index // 2 - (max_head_index - 1) * interval_ratio):
|
||||
scan_part, scan_slot = assigned_part.copy(), assigned_slot.copy()
|
||||
|
||||
for head in range(max_head_index):
|
||||
part = feeder_part[slot + head * interval_ratio]
|
||||
|
||||
# 贴装头和拾取槽位满足对应关系
|
||||
if scan_part[head] == -1 and part != -1 and feeder_base_points[slot + head * interval_ratio] > 0:
|
||||
scan_part[head], scan_slot[head] = part, slot + head * interval_ratio + 1
|
||||
|
||||
if scan_part.count(-1) < best_scan_part.count(-1) or (scan_part.count(-1) == best_scan_part.count(-1)
|
||||
and abs(slot - prev_scan_slot) <
|
||||
abs(best_slot_index - prev_scan_slot)):
|
||||
best_slot_index = slot
|
||||
best_scan_part, best_scan_slot = scan_part.copy(), scan_slot.copy()
|
||||
|
||||
assigned_points = 0
|
||||
for idx, slot in enumerate(best_scan_slot):
|
||||
if slot != -1 and assigned_slot[idx] == -1:
|
||||
feeder_base_points[slot - 1] -= 1
|
||||
assigned_points += 1
|
||||
|
||||
assigned_part, assigned_slot = best_scan_part.copy(), best_scan_slot.copy()
|
||||
prev_scan_slot = best_slot_index
|
||||
|
||||
if assigned_part.count(-1) == 0 or assigned_points == 0:
|
||||
break
|
||||
|
||||
if len(cycle_result) == 0 or component_result[-1] != assigned_part:
|
||||
cycle_result.append(1)
|
||||
component_result.append(assigned_part)
|
||||
feeder_slot_result.append(assigned_slot)
|
||||
else:
|
||||
cycle_result[-1] += 1
|
||||
|
||||
if sum(feeder_base_points) == 0:
|
||||
break
|
||||
|
||||
return component_result, cycle_result, feeder_slot_result
|
||||
|
||||
|
||||
def feeder_arrange_evaluate(feeder_slot_result, cycle_result):
|
||||
assert len(feeder_slot_result) == len(cycle_result)
|
||||
arrange_val = 0
|
||||
for cycle, feeder_slot in enumerate(feeder_slot_result):
|
||||
pick_slot = set()
|
||||
for head, slot in enumerate(feeder_slot):
|
||||
pick_slot.add(slot - head * interval_ratio)
|
||||
|
||||
arrange_val += len(pick_slot) * t_pick * cycle_result[cycle]
|
||||
pick_slot = list(pick_slot)
|
||||
pick_slot.sort()
|
||||
arrange_val += axis_moving_time(pick_slot[0] - pick_slot[-1]) * cycle_result[cycle]
|
||||
|
||||
return arrange_val
|
||||
|
@ -1,11 +1,9 @@
|
||||
import random
|
||||
|
||||
from optimizer_common import *
|
||||
from base_optimizer.optimizer_common import *
|
||||
|
||||
|
||||
def load_data(filename: str, load_cp_data=True, load_feeder_data=True, component_register=False):
|
||||
# 锁定随机数种子
|
||||
random.seed(0)
|
||||
def load_data(filename: str, default_feeder_limit=1, load_cp_data=True, load_feeder_data=True, cp_auto_register=False):
|
||||
|
||||
# 读取PCB数据
|
||||
filename = 'data/' + filename
|
||||
@ -28,25 +26,28 @@ def load_data(filename: str, load_cp_data=True, load_feeder_data=True, component
|
||||
# pcb_data["x"] = pcb_data["x"].apply(lambda x: -x)
|
||||
|
||||
# 注册元件检查
|
||||
component_data = None
|
||||
if load_cp_data:
|
||||
part_feeder_assign = defaultdict(set)
|
||||
part_col = ["part", "desc", "fdr", "nz", 'camera', 'group', 'feeder-limit']
|
||||
try:
|
||||
component_data = pd.DataFrame(pd.read_csv(filepath_or_buffer='component.txt', sep='\t', header=None), columns=part_col)
|
||||
if load_cp_data:
|
||||
component_data = pd.DataFrame(pd.read_csv(filepath_or_buffer='component.txt', sep='\t', header=None),
|
||||
columns=part_col)
|
||||
else:
|
||||
component_data = pd.DataFrame(columns=part_col)
|
||||
except:
|
||||
component_data = pd.DataFrame(columns=part_col)
|
||||
|
||||
for _, data in pcb_data.iterrows():
|
||||
part, nozzle = data.part, data.nz.split(' ')[1]
|
||||
slot = data['fdr'].split(' ')[0]
|
||||
|
||||
if part not in component_data['part'].values:
|
||||
if not component_register:
|
||||
if not cp_auto_register:
|
||||
raise Exception("unregistered component: " + component_data['part'].values)
|
||||
else:
|
||||
component_data = pd.concat([component_data,
|
||||
pd.DataFrame([part, '', 'SM8', nozzle, '飞行相机1', 'CHIP-Rect', 0],
|
||||
index=part_col).T], ignore_index=True)
|
||||
component_data = pd.concat([component_data, pd.DataFrame(
|
||||
[part, '', 'SM8', nozzle, '飞行相机1', 'CHIP-Rect', default_feeder_limit], index=part_col).T],
|
||||
ignore_index=True)
|
||||
# warning_info = 'register component ' + part + ' with default feeder type'
|
||||
# warnings.warn(warning_info, UserWarning)
|
||||
part_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
@ -60,12 +61,8 @@ def load_data(filename: str, load_cp_data=True, load_feeder_data=True, component
|
||||
if data['fdr'][0:3] == 'SME': # 电动供料器和气动供料器参数一致
|
||||
component_data.at[idx, 'fdr'] = data['fdr'][0:2] + data['fdr'][3:]
|
||||
|
||||
for part, slots in part_feeder_assign.items():
|
||||
part_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
component_data.at[part_index, 'feeder-limit'] = max(len(slots), component_data.at[part_index, 'feeder-limit'])
|
||||
|
||||
# 读取供料器基座数据
|
||||
feeder_data = pd.DataFrame(columns=range(3))
|
||||
feeder_data = pd.DataFrame(columns=['slot', 'part', 'arg']) # arg表示是否为预分配,不表示分配数目
|
||||
if load_feeder_data:
|
||||
for data in pcb_data.iterrows():
|
||||
fdr = data[1]['fdr']
|
||||
@ -75,7 +72,6 @@ def load_data(filename: str, load_cp_data=True, load_feeder_data=True, component
|
||||
slot = int(slot[1:]) if slot[0] == 'F' else int(slot[1:]) + max_slot_index // 2
|
||||
feeder_data = pd.concat([feeder_data, pd.DataFrame([slot, part, 1]).T])
|
||||
|
||||
feeder_data.columns = ['slot', 'part', 'arg'] # arg表示是否为预分配,不表示分配数目
|
||||
feeder_data.drop_duplicates(subset='slot', inplace=True, ignore_index=True)
|
||||
# 随机移除部分已安装的供料器
|
||||
if load_feeder_data == 2:
|
||||
@ -83,7 +79,7 @@ def load_data(filename: str, load_cp_data=True, load_feeder_data=True, component
|
||||
feeder_data.drop(index=drop_index, inplace=True)
|
||||
|
||||
feeder_data.sort_values(by='slot', ascending=True, inplace=True, ignore_index=True)
|
||||
else:
|
||||
feeder_data.columns = ['slot', 'part', 'arg'] # 同上
|
||||
|
||||
# plt.scatter(pcb_data["x"], pcb_data["y"])
|
||||
# plt.show()
|
||||
return pcb_data, component_data, feeder_data
|
||||
|
418
optimizer.py
418
optimizer.py
@ -1,305 +1,166 @@
|
||||
import math
|
||||
import random
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
|
||||
from base_optimizer.optimizer_aggregation import *
|
||||
from base_optimizer.optimizer_scanbased import *
|
||||
from base_optimizer.optimizer_celldivision import *
|
||||
from base_optimizer.optimizer_hybridgenetic import *
|
||||
from base_optimizer.optimizer_feederpriority import *
|
||||
|
||||
from optimizer_common import *
|
||||
from dataloader import *
|
||||
|
||||
from optimizer_genetic import *
|
||||
from optimizer_heuristic import *
|
||||
|
||||
def get_top_k_value(pop_val, k: int):
|
||||
res = []
|
||||
pop_val_cpy = copy.deepcopy(pop_val)
|
||||
pop_val_cpy.sort(reverse=True)
|
||||
|
||||
for i in range(min(len(pop_val_cpy), k)):
|
||||
for j in range(len(pop_val)):
|
||||
if abs(pop_val_cpy[i] - pop_val[j]) < 1e-9 and j not in res:
|
||||
res.append(j)
|
||||
def optimizer(pcb_data, component_data, assembly_line_optimizer, single_machine_optimizer):
|
||||
assignment_result = assemblyline_optimizer_genetic(pcb_data, component_data)
|
||||
|
||||
# assignment_result = [[0, 0, 0, 0, 216, 0, 0], [0, 0, 0, 0, 216, 0, 0], [36, 24, 12, 12, 0, 36, 12]]
|
||||
placement_points, placement_time = [], []
|
||||
partial_pcb_data, partial_component_data = defaultdict(pd.DataFrame), defaultdict(pd.DataFrame)
|
||||
for machine_index in range(max_machine_index):
|
||||
partial_pcb_data[machine_index] = pd.DataFrame(columns=pcb_data.columns)
|
||||
partial_component_data[machine_index] = component_data.copy(deep=True)
|
||||
placement_points.append(sum(assignment_result[machine_index]))
|
||||
|
||||
# averagely assign available feeder
|
||||
for part_index, data in component_data.iterrows():
|
||||
feeder_limit = data['feeder-limit']
|
||||
feeder_points = [assignment_result[machine_index][part_index] for machine_index in range(max_machine_index)]
|
||||
|
||||
for machine_index in range(max_machine_index):
|
||||
if feeder_points[machine_index] == 0:
|
||||
continue
|
||||
|
||||
arg_feeder = max(math.floor(feeder_points[machine_index] / sum(feeder_points) * data['feeder-limit']), 1)
|
||||
|
||||
partial_component_data[machine_index].loc[part_index]['feeder-limit'] = arg_feeder
|
||||
feeder_limit -= arg_feeder
|
||||
|
||||
for machine_index in range(max_machine_index):
|
||||
if feeder_limit <= 0:
|
||||
break
|
||||
return res
|
||||
|
||||
if feeder_points[machine_index] == 0:
|
||||
continue
|
||||
partial_component_data[machine_index].loc[part_index]['feeder-limit'] += 1
|
||||
feeder_limit -= 1
|
||||
|
||||
def swap_mutation(component_points, individual):
|
||||
offspring = individual.copy()
|
||||
|
||||
idx, component_index = 0, random.randint(0, len(component_points) - 1)
|
||||
for points in component_points.values():
|
||||
if component_index == 0:
|
||||
index1 = random.randint(0, points + max_machine_index - 2)
|
||||
component_machine_index = [0 for _ in range(len(component_data))]
|
||||
pcb_data = pcb_data.sort_values(by="x", ascending=False)
|
||||
for _, data in pcb_data.iterrows():
|
||||
part = data['part']
|
||||
part_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
while True:
|
||||
index2 = random.randint(0, points + max_machine_index - 2)
|
||||
if index1 != index2 and offspring[idx + index1] != offspring[idx + index2]:
|
||||
break
|
||||
offspring[idx + index1], offspring[idx + index2] = offspring[idx + index2], offspring[idx + index1]
|
||||
break
|
||||
|
||||
component_index -= 1
|
||||
idx += (points + max_machine_index - 1)
|
||||
|
||||
return offspring
|
||||
|
||||
|
||||
def roulette_wheel_selection(pop_eval):
|
||||
# Roulette wheel
|
||||
random_val = np.random.random()
|
||||
for idx, val in enumerate(pop_eval):
|
||||
random_val -= val
|
||||
if random_val <= 0:
|
||||
return idx
|
||||
return len(pop_eval) - 1
|
||||
|
||||
|
||||
def random_selective(data, possibility): # 依概率选择随机数
|
||||
assert len(data) == len(possibility) and len(data) > 0
|
||||
|
||||
sum_val = sum(possibility)
|
||||
possibility = [p / sum_val for p in possibility]
|
||||
|
||||
random_val = random.random()
|
||||
for idx, val in enumerate(possibility):
|
||||
random_val -= val
|
||||
if random_val <= 0:
|
||||
break
|
||||
return data[idx]
|
||||
|
||||
|
||||
def selective_initialization(component_points, population_size):
|
||||
population = [] # population initialization
|
||||
|
||||
for _ in range(population_size):
|
||||
individual = []
|
||||
for points in component_points.values():
|
||||
if points == 0:
|
||||
continue
|
||||
avl_machine_num = random.randint(1, min(max_machine_index, points)) # 可用机器数
|
||||
|
||||
selective_possibility = []
|
||||
for p in range(1, avl_machine_num + 1):
|
||||
selective_possibility.append(pow(2, avl_machine_num - p + 1))
|
||||
|
||||
sel_machine_num = random_selective([p + 1 for p in range(avl_machine_num)], selective_possibility) # 选择的机器数
|
||||
sel_machine_set = random.sample([p for p in range(avl_machine_num)], sel_machine_num)
|
||||
|
||||
sel_machine_points = [1 for _ in range(sel_machine_num)]
|
||||
for p in range(sel_machine_num - 1):
|
||||
if points == sum(sel_machine_points):
|
||||
break
|
||||
assign_points = random.randint(1, points - sum(sel_machine_points))
|
||||
sel_machine_points[p] += assign_points
|
||||
|
||||
if sum(sel_machine_points) < points:
|
||||
sel_machine_points[-1] += (points - sum(sel_machine_points))
|
||||
|
||||
# code component allocation into chromosome
|
||||
for p in range(max_machine_index):
|
||||
if p in sel_machine_set:
|
||||
individual += [0 for _ in range(sel_machine_points[0])]
|
||||
sel_machine_points.pop(0)
|
||||
individual.append(1)
|
||||
individual.pop(-1)
|
||||
|
||||
population.append(individual)
|
||||
return population
|
||||
|
||||
|
||||
def selective_crossover(mother, father, non_decelerating=True):
|
||||
assert len(mother) == len(father)
|
||||
|
||||
offspring1, offspring2 = mother.copy(), father.copy()
|
||||
one_counter, feasible_cutline = 0, []
|
||||
for idx in range(len(mother) - 1):
|
||||
if mother[idx] == 1:
|
||||
one_counter += 1
|
||||
if father[idx] == 1:
|
||||
one_counter -= 1
|
||||
|
||||
# first constraint: the total number of “1”s (the number of partitions) in the chromosome is unchanged
|
||||
if one_counter != 0 or idx == 0 or idx == len(mother) - 2:
|
||||
continue
|
||||
|
||||
# the selected cutline should guarantee there are the same or a larger number unassigned machine
|
||||
# for each component type
|
||||
n_bro, n_new = 0, 0
|
||||
if mother[idx] and mother[idx + 1]:
|
||||
n_bro += 1
|
||||
if father[idx] and father[idx + 1]:
|
||||
n_bro += 1
|
||||
if mother[idx] and father[idx + 1]:
|
||||
n_new += 1
|
||||
if father[idx] and mother[idx + 1]:
|
||||
n_new += 1
|
||||
|
||||
# non_decelerating or accelerating crossover
|
||||
if (non_decelerating and n_bro <= n_new) or n_bro < n_new:
|
||||
feasible_cutline.append(idx)
|
||||
|
||||
if len(feasible_cutline) == 0:
|
||||
return offspring1, offspring2
|
||||
|
||||
cutline_idx = feasible_cutline[random.randint(0, len(feasible_cutline) - 1)]
|
||||
offspring1, offspring2 = mother[:cutline_idx + 1] + father[cutline_idx + 1:], father[:cutline_idx + 1] + mother[
|
||||
cutline_idx + 1:]
|
||||
return offspring1, offspring2
|
||||
|
||||
|
||||
def cal_individual_val(component_points, component_nozzle, individual):
|
||||
idx, objective_val = 0, [0]
|
||||
machine_component_points = [[] for _ in range(max_machine_index)]
|
||||
|
||||
# decode the component allocation
|
||||
for points in component_points.values():
|
||||
component_gene = individual[idx: idx + points + max_machine_index - 1]
|
||||
machine_idx, component_counter = 0, 0
|
||||
for gene in component_gene:
|
||||
if gene:
|
||||
machine_component_points[machine_idx].append(component_counter)
|
||||
machine_idx += 1
|
||||
component_counter = 0
|
||||
machine_index = component_machine_index[part_index]
|
||||
if assignment_result[machine_index][part_index] == 0:
|
||||
component_machine_index[part_index] += 1
|
||||
machine_index += 1
|
||||
else:
|
||||
component_counter += 1
|
||||
machine_component_points[-1].append(component_counter)
|
||||
idx += (points + max_machine_index - 1)
|
||||
break
|
||||
assignment_result[machine_index][part_index] -= 1
|
||||
partial_pcb_data[machine_index] = pd.concat([partial_pcb_data[machine_index], pd.DataFrame(data).T])
|
||||
|
||||
for machine_idx in range(max_machine_index):
|
||||
nozzle_points = defaultdict(int)
|
||||
for idx, nozzle in component_nozzle.items():
|
||||
if component_points[idx] == 0:
|
||||
for machine_index, data in partial_pcb_data.items():
|
||||
data = data.reset_index(drop=True)
|
||||
if len(data) == 0:
|
||||
continue
|
||||
nozzle_points[nozzle] += machine_component_points[machine_idx][idx]
|
||||
|
||||
machine_points = sum(machine_component_points[machine_idx]) # num of placement points
|
||||
if machine_points == 0:
|
||||
placement_time.append(base_optimizer(machine_index + 1, data, partial_component_data[machine_index],
|
||||
feeder_data=pd.DataFrame(columns=['slot', 'part', 'arg']),
|
||||
method=single_machine_optimizer, hinter=True))
|
||||
|
||||
average_time, standard_deviation_time = sum(placement_time) / max_machine_index, 0
|
||||
for machine_index in range(max_machine_index):
|
||||
print('assembly time for machine ' + str(machine_index + 1) + ': ' + str(
|
||||
placement_time[machine_index]) + ' s, ' + 'total placements: ' + str(placement_points[machine_index]))
|
||||
standard_deviation_time += pow(placement_time[machine_index] - average_time, 2)
|
||||
standard_deviation_time /= max_machine_index
|
||||
standard_deviation_time = math.sqrt(standard_deviation_time)
|
||||
|
||||
print('finial assembly time: ' + str(max(placement_time)) + 's, standard deviation: ' + str(standard_deviation_time))
|
||||
|
||||
|
||||
# todo: 不同类型元件的组装时间差异
|
||||
def base_optimizer(machine_index, pcb_data, component_data, feeder_data=None, method='', hinter=False):
|
||||
if method == 'cell_division': # 基于元胞分裂的遗传算法
|
||||
component_result, cycle_result, feeder_slot_result = optimizer_celldivision(pcb_data, component_data, False)
|
||||
placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
|
||||
cycle_result, feeder_slot_result)
|
||||
elif method == 'feeder_priority': # 基于基座扫描的供料器优先算法
|
||||
# 第1步:分配供料器位置
|
||||
nozzle_pattern = feeder_allocate(component_data, pcb_data, feeder_data, False)
|
||||
# 第2步:扫描供料器基座,确定元件拾取的先后顺序
|
||||
component_result, cycle_result, feeder_slot_result = feeder_base_scan(component_data, pcb_data, feeder_data,
|
||||
nozzle_pattern)
|
||||
|
||||
# 第3步:贴装路径规划
|
||||
placement_result, head_sequence = greedy_placement_route_generation(component_data, pcb_data, component_result,
|
||||
cycle_result, feeder_slot_result)
|
||||
# placement_result, head_sequence = beam_search_for_route_generation(component_data, pcb_data, component_result,
|
||||
# cycle_result, feeder_slot_result)
|
||||
|
||||
elif method == 'hybrid_genetic': # 基于拾取组的混合遗传算法
|
||||
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_hybrid_genetic(
|
||||
pcb_data, component_data, False)
|
||||
|
||||
elif method == 'aggregation': # 基于batch-level的整数规划 + 启发式算法
|
||||
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_aggregation(
|
||||
component_data, pcb_data)
|
||||
elif method == 'scan_based':
|
||||
component_result, cycle_result, feeder_slot_result, placement_result, head_sequence = optimizer_scanbased(
|
||||
component_data, pcb_data, False)
|
||||
else:
|
||||
raise 'method is not existed'
|
||||
|
||||
if hinter:
|
||||
optimization_assign_result(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
nozzle_hinter=False, component_hinter=False, feeder_hinter=False)
|
||||
|
||||
print('----- Placement machine ' + str(machine_index) + ' ----- ')
|
||||
print('-Cycle counter: {}'.format(sum(cycle_result)))
|
||||
|
||||
total_nozzle_change_counter, total_pick_counter = 0, 0
|
||||
assigned_nozzle = ['' if idx == -1 else component_data.loc[idx]['nz'] for idx in component_result[0]]
|
||||
|
||||
for cycle in range(len(cycle_result)):
|
||||
pick_slot = set()
|
||||
for head in range(max_head_index):
|
||||
if (idx := component_result[cycle][head]) == -1:
|
||||
continue
|
||||
ul = math.ceil(len(nozzle_points) * 1.0 / max_head_index) - 1 # num of nozzle set
|
||||
|
||||
# assignments of nozzles to heads
|
||||
wl = 0 # num of workload
|
||||
total_heads = (1 + ul) * max_head_index - len(nozzle_points)
|
||||
nozzle_heads = defaultdict(int)
|
||||
for nozzle in nozzle_points.keys():
|
||||
nozzle_heads[nozzle] = math.floor(nozzle_points[nozzle] * 1.0 / machine_points * total_heads)
|
||||
nozzle_heads[nozzle] += 1
|
||||
nozzle = component_data.loc[idx]['nz']
|
||||
if nozzle != assigned_nozzle[head]:
|
||||
if assigned_nozzle[head] != '':
|
||||
total_nozzle_change_counter += 1
|
||||
assigned_nozzle[head] = nozzle
|
||||
|
||||
total_heads = (1 + ul) * max_head_index
|
||||
for heads in nozzle_heads.values():
|
||||
total_heads -= heads
|
||||
pick_slot.add(feeder_slot_result[cycle][head] - head * interval_ratio)
|
||||
total_pick_counter += len(pick_slot) * cycle_result[cycle]
|
||||
|
||||
for nozzle in nozzle_heads.keys(): # TODO:有利于减少周期的方法
|
||||
if total_heads == 0:
|
||||
break
|
||||
nozzle_heads[nozzle] += 1
|
||||
total_heads -= 1
|
||||
print('-Nozzle change counter: {}'.format(total_nozzle_change_counter))
|
||||
print('-Pick operation counter: {}'.format(total_pick_counter))
|
||||
print('------------------------------ ')
|
||||
|
||||
# averagely assign placements to heads
|
||||
heads_placement = []
|
||||
for nozzle in nozzle_heads.keys():
|
||||
points = math.floor(nozzle_points[nozzle] / nozzle_heads[nozzle])
|
||||
|
||||
heads_placement += [[nozzle, points] for _ in range(nozzle_heads[nozzle])]
|
||||
nozzle_points[nozzle] -= (nozzle_heads[nozzle] * points)
|
||||
for idx in range(len(heads_placement) - 1, -1, -1):
|
||||
if nozzle_points[nozzle] <= 0:
|
||||
break
|
||||
nozzle_points[nozzle] -= 1
|
||||
heads_placement[idx][1] += 1
|
||||
heads_placement = sorted(heads_placement, key=lambda x: x[1], reverse=True)
|
||||
|
||||
# every max_head_index heads in the non-decreasing order are grouped together as nozzle set
|
||||
for idx in range(len(heads_placement) // max_head_index):
|
||||
wl += heads_placement[idx][1]
|
||||
objective_val.append(T_pp * machine_points + T_tr * wl + T_nc * ul)
|
||||
|
||||
return max(objective_val), machine_component_points
|
||||
# 估算贴装用时
|
||||
return placement_time_estimate(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
placement_result, head_sequence, False)
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def optimizer(pcb_data, component_data):
|
||||
# basic parameter
|
||||
# crossover rate & mutation rate: 80% & 10%
|
||||
# population size: 200
|
||||
# the number of generation: 500
|
||||
crossover_rate, mutation_rate = 0.8, 0.1
|
||||
population_size, n_generations = 200, 500
|
||||
|
||||
# the number of placement points and nozzle type of component
|
||||
component_points, component_nozzle = defaultdict(int), defaultdict(str)
|
||||
for data in pcb_data.iterrows():
|
||||
part_index = component_data[component_data['part'] == data[1]['part']].index.tolist()[0]
|
||||
nozzle = component_data.loc[part_index]['nz']
|
||||
|
||||
component_points[part_index] += 1
|
||||
component_nozzle[part_index] = nozzle
|
||||
|
||||
# population initialization
|
||||
best_popval = []
|
||||
population = selective_initialization(component_points, population_size)
|
||||
with tqdm(total=n_generations) as pbar:
|
||||
pbar.set_description('genetic process for PCB assembly')
|
||||
|
||||
new_population, new_pop_val = [], []
|
||||
for _ in range(n_generations):
|
||||
# calculate fitness value
|
||||
pop_val = []
|
||||
for individual in population:
|
||||
val, _ = cal_individual_val(component_points, component_nozzle, individual)
|
||||
pop_val.append(val)
|
||||
|
||||
best_popval.append(min(pop_val))
|
||||
# min-max convert
|
||||
max_val = max(pop_val)
|
||||
pop_val = list(map(lambda v: max_val - v, pop_val))
|
||||
|
||||
sum_pop_val = sum(pop_val)
|
||||
pop_val = [v / sum_pop_val for v in pop_val]
|
||||
|
||||
select_index = get_top_k_value(pop_val, population_size - len(new_pop_val))
|
||||
population = [population[idx] for idx in select_index]
|
||||
pop_val = [pop_val[idx] for idx in select_index]
|
||||
|
||||
population += new_population
|
||||
for individual in new_population:
|
||||
val, _ = cal_individual_val(component_points, component_nozzle, individual)
|
||||
pop_val.append(val)
|
||||
|
||||
# crossover and mutation
|
||||
new_population = []
|
||||
for pop in range(population_size):
|
||||
if pop % 2 == 0 and np.random.random() < crossover_rate:
|
||||
index1 = roulette_wheel_selection(pop_val)
|
||||
while True:
|
||||
index2 = roulette_wheel_selection(pop_val)
|
||||
if index1 != index2:
|
||||
break
|
||||
|
||||
offspring1, offspring2 = selective_crossover(population[index1], population[index2])
|
||||
if np.random.random() < mutation_rate:
|
||||
offspring1 = swap_mutation(component_points, offspring1)
|
||||
|
||||
if np.random.random() < mutation_rate:
|
||||
offspring1 = swap_mutation(component_points, offspring1)
|
||||
|
||||
new_population.append(offspring1)
|
||||
new_population.append(offspring2)
|
||||
|
||||
pbar.update(1)
|
||||
|
||||
best_individual = population[np.argmin(pop_val)]
|
||||
val, result = cal_individual_val(component_points, component_nozzle, best_individual)
|
||||
print(result)
|
||||
|
||||
plt.plot(best_popval)
|
||||
plt.show()
|
||||
# TODO: 计算实际的PCB整线组装时间
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
def main():
|
||||
# warnings.simplefilter('ignore')
|
||||
# 参数解析
|
||||
parser = argparse.ArgumentParser(description='assembly line optimizer implementation')
|
||||
parser.add_argument('--filename', default='PCB.txt', type=str, help='load pcb data')
|
||||
parser.add_argument('--filename', default='PCB1 - FL19-30W.txt', type=str, help='load pcb data')
|
||||
parser.add_argument('--auto_register', default=1, type=int, help='register the component according the pcb data')
|
||||
|
||||
parser.add_argument('--base_optimizer', default='feeder_priority', type=str,
|
||||
help='base optimizer for single machine')
|
||||
parser.add_argument('--assembly_optimizer', default='genetic', type=str, help='optimizer for PCB Assembly Line')
|
||||
parser.add_argument('--feeder_limit', default=2, type=int,
|
||||
help='the upper feeder limit for each type of component')
|
||||
params = parser.parse_args()
|
||||
|
||||
# 结果输出显示所有行和列
|
||||
@ -307,10 +168,13 @@ if __name__ == '__main__':
|
||||
pd.set_option('display.max_rows', None)
|
||||
|
||||
# 加载PCB数据
|
||||
pcb_data, component_data, _ = load_data(params.filename, component_register=params.auto_register) # 加载PCB数据
|
||||
pcb_data, component_data, _ = load_data(params.filename, default_feeder_limit=params.feeder_limit,
|
||||
cp_auto_register=params.auto_register) # 加载PCB数据
|
||||
|
||||
optimizer(pcb_data, component_data)
|
||||
optimizer(pcb_data, component_data, params.assembly_optimizer, params.base_optimizer)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
|
@ -1,78 +0,0 @@
|
||||
import copy
|
||||
import time
|
||||
import math
|
||||
import argparse
|
||||
|
||||
import warnings
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from functools import wraps
|
||||
from collections import defaultdict
|
||||
from typing import List, Counter
|
||||
from tqdm import tqdm
|
||||
|
||||
# 机器参数
|
||||
max_head_index, max_slot_index = 6, 120 # 暂时默认所有机器参数相同
|
||||
max_machine_index = 3
|
||||
interval_ratio = 2
|
||||
slot_interval = 15
|
||||
head_interval = slot_interval * interval_ratio
|
||||
head_nozzle = ['' for _ in range(max_head_index)] # 头上已经分配吸嘴
|
||||
|
||||
# 位置信息
|
||||
slotf1_pos, slotr1_pos = [-31.267, 44.], [807., 810.545] # F1(前基座最左侧)、R1(后基座最右侧)位置
|
||||
fix_camera_pos = [269.531, 694.823] # 固定相机位置
|
||||
anc_marker_pos = [336.457, 626.230] # ANC基准点位置
|
||||
stopper_pos = [635.150, 124.738] # 止档块位置
|
||||
|
||||
# 时间参数
|
||||
T_pp, T_tr, T_nc = 2, 5, 25
|
||||
|
||||
# 电机参数
|
||||
head_rotary_velocity = 8e-5 # 贴装头R轴旋转时间
|
||||
x_max_velocity, y_max_velocity = 1.4, 1.2
|
||||
x_max_acceleration, y_max_acceleration = x_max_velocity / 0.079, y_max_velocity / 0.079
|
||||
|
||||
# 不同种类供料器宽度
|
||||
feeder_width = {'SM8': (7.25, 7.25), 'SM12': (7.00, 20.00), 'SM16': (7.00, 22.00),
|
||||
'SM24': (7.00, 29.00), 'SM32': (7.00, 44.00)}
|
||||
|
||||
# 可用吸嘴数量限制
|
||||
nozzle_limit = {'CN065': 6, 'CN040': 6, 'CN220': 6, 'CN400': 6, 'CN140': 6}
|
||||
|
||||
|
||||
def axis_moving_time(distance, axis=0):
|
||||
distance = abs(distance) * 1e-3
|
||||
Lamax = x_max_velocity ** 2 / x_max_acceleration if axis == 0 else y_max_velocity ** 2 / y_max_acceleration
|
||||
Tmax = x_max_velocity / x_max_acceleration if axis == 0 else y_max_velocity / y_max_acceleration
|
||||
if axis == 0:
|
||||
return 2 * math.sqrt(distance / x_max_acceleration) if distance < Lamax else 2 * Tmax + (
|
||||
distance - Lamax) / x_max_velocity
|
||||
else:
|
||||
return 2 * math.sqrt(distance / y_max_acceleration) if distance < Lamax else 2 * Tmax + (
|
||||
distance - Lamax) / y_max_velocity
|
||||
|
||||
|
||||
def head_rotary_time(angle):
|
||||
while -180 > angle > 180:
|
||||
if angle > 180:
|
||||
angle -= 360
|
||||
else:
|
||||
angle += 360
|
||||
return abs(angle) * head_rotary_velocity
|
||||
|
||||
|
||||
def timer_wrapper(func):
|
||||
@wraps(func)
|
||||
def measure_time(*args, **kwargs):
|
||||
start_time = time.time()
|
||||
result = func(*args, **kwargs)
|
||||
|
||||
print("function {} running time : {} s".format(func.__name__, time.time() - start_time))
|
||||
return result
|
||||
|
||||
return measure_time
|
||||
|
||||
|
288
optimizer_genetic.py
Normal file
288
optimizer_genetic.py
Normal file
@ -0,0 +1,288 @@
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from base_optimizer.optimizer_common import *
|
||||
|
||||
|
||||
def selective_initialization(component_points, component_feeders, population_size):
|
||||
population = [] # population initialization
|
||||
|
||||
for _ in range(population_size):
|
||||
individual = []
|
||||
for part_index, points in component_points.items():
|
||||
if points == 0:
|
||||
continue
|
||||
# 可用机器数
|
||||
avl_machine_num = random.randint(1, min(max_machine_index, component_feeders[part_index], points))
|
||||
|
||||
selective_possibility = []
|
||||
for p in range(1, avl_machine_num + 1):
|
||||
selective_possibility.append(pow(2, avl_machine_num - p + 1))
|
||||
|
||||
sel_machine_num = random_selective([p + 1 for p in range(avl_machine_num)], selective_possibility) # 选择的机器数
|
||||
sel_machine_set = random.sample([p for p in range(max_machine_index)], sel_machine_num)
|
||||
|
||||
sel_machine_points = [1 for _ in range(sel_machine_num)]
|
||||
for p in range(sel_machine_num - 1):
|
||||
if points == sum(sel_machine_points):
|
||||
break
|
||||
assign_points = random.randint(1, points - sum(sel_machine_points))
|
||||
sel_machine_points[p] += assign_points
|
||||
|
||||
if sum(sel_machine_points) < points:
|
||||
sel_machine_points[-1] += (points - sum(sel_machine_points))
|
||||
|
||||
# code component allocation into chromosome
|
||||
for p in range(max_machine_index):
|
||||
if p in sel_machine_set:
|
||||
individual += [0 for _ in range(sel_machine_points[0])]
|
||||
sel_machine_points.pop(0)
|
||||
individual.append(1)
|
||||
individual.pop(-1)
|
||||
|
||||
population.append(individual)
|
||||
return population
|
||||
|
||||
|
||||
def selective_crossover(component_points, component_feeders, mother, father, non_decelerating=True):
|
||||
assert len(mother) == len(father)
|
||||
|
||||
offspring1, offspring2 = mother.copy(), father.copy()
|
||||
one_counter, feasible_cut_line = 0, []
|
||||
|
||||
idx = 0
|
||||
for part_index, points in component_points.items():
|
||||
one_counter = 0
|
||||
|
||||
idx_, mother_cut_line, father_cut_line = 0, [-1], [-1]
|
||||
for idx_, gene in enumerate(mother[idx: idx + points + max_machine_index - 1]):
|
||||
if gene:
|
||||
mother_cut_line.append(idx_)
|
||||
mother_cut_line.append(idx_ + 1)
|
||||
|
||||
for idx_, gene in enumerate(father[idx: idx + points + max_machine_index - 1]):
|
||||
if gene:
|
||||
father_cut_line.append(idx_)
|
||||
father_cut_line.append(idx_ + 1)
|
||||
|
||||
for offset in range(points + max_machine_index - 1):
|
||||
if mother[idx + offset] == 1:
|
||||
one_counter += 1
|
||||
if father[idx + offset] == 1:
|
||||
one_counter -= 1
|
||||
|
||||
# first constraint: the total number of '1's (the number of partitions) in the chromosome is unchanged
|
||||
if one_counter != 0 or offset == 0 or offset == points + max_machine_index - 2:
|
||||
continue
|
||||
|
||||
# the selected cut-line should guarantee there are the same or a larger number unassigned machine
|
||||
# for each component type
|
||||
n_bro, n_new = 0, 0
|
||||
if mother[idx + offset] and mother[idx + offset + 1]:
|
||||
n_bro += 1
|
||||
if father[idx + offset] and father[idx + offset + 1]:
|
||||
n_bro += 1
|
||||
if mother[idx + offset] and father[idx + offset + 1]:
|
||||
n_new += 1
|
||||
if father[idx + offset] and mother[idx + offset + 1]:
|
||||
n_new += 1
|
||||
|
||||
# second constraint: non_decelerating or accelerating crossover
|
||||
if n_new < n_bro or (n_new == n_bro and not non_decelerating):
|
||||
continue
|
||||
|
||||
# third constraint (customized constraint):
|
||||
# no more than the maximum number of available machine for each component type
|
||||
new_mother_cut_line, new_father_cut_line = [], []
|
||||
for idx_ in range(max_machine_index + 1):
|
||||
if mother_cut_line[idx_] <= offset:
|
||||
new_mother_cut_line.append(mother_cut_line[idx_])
|
||||
else:
|
||||
new_father_cut_line.append(mother_cut_line[idx_])
|
||||
|
||||
if father_cut_line[idx_] <= offset:
|
||||
new_father_cut_line.append(father_cut_line[idx_])
|
||||
else:
|
||||
new_mother_cut_line.append(father_cut_line[idx_])
|
||||
|
||||
sorted(new_mother_cut_line, reverse=False)
|
||||
sorted(new_father_cut_line, reverse=False)
|
||||
n_mother_machine, n_father_machine = 0, 0
|
||||
|
||||
for idx_ in range(max_machine_index):
|
||||
if new_mother_cut_line[idx_ + 1] - new_mother_cut_line[idx_]:
|
||||
n_mother_machine += 1
|
||||
|
||||
if new_father_cut_line[idx_ + 1] - new_father_cut_line[idx_]:
|
||||
n_father_machine += 1
|
||||
|
||||
if n_mother_machine > component_feeders[part_index] or n_father_machine > component_feeders[part_index]:
|
||||
continue
|
||||
|
||||
feasible_cut_line.append(idx + offset)
|
||||
|
||||
idx += (points + max_machine_index - 1)
|
||||
|
||||
if len(feasible_cut_line) == 0:
|
||||
return offspring1, offspring2
|
||||
|
||||
cut_line_idx = feasible_cut_line[random.randint(0, len(feasible_cut_line) - 1)]
|
||||
offspring1, offspring2 = mother[:cut_line_idx + 1] + father[cut_line_idx + 1:], father[:cut_line_idx + 1] + mother[
|
||||
cut_line_idx + 1:]
|
||||
return offspring1, offspring2
|
||||
|
||||
|
||||
def cal_individual_val(component_points, component_nozzle, individual):
|
||||
idx, objective_val = 0, []
|
||||
machine_component_points = [[] for _ in range(max_machine_index)]
|
||||
|
||||
# decode the component allocation
|
||||
for points in component_points.values():
|
||||
component_gene = individual[idx: idx + points + max_machine_index - 1]
|
||||
machine_idx, component_counter = 0, 0
|
||||
for gene in component_gene:
|
||||
if gene:
|
||||
machine_component_points[machine_idx].append(component_counter)
|
||||
machine_idx += 1
|
||||
component_counter = 0
|
||||
else:
|
||||
component_counter += 1
|
||||
machine_component_points[-1].append(component_counter)
|
||||
idx += (points + max_machine_index - 1)
|
||||
|
||||
for machine_idx in range(max_machine_index):
|
||||
nozzle_points = defaultdict(int)
|
||||
for idx, nozzle in component_nozzle.items():
|
||||
if component_points[idx] == 0:
|
||||
continue
|
||||
nozzle_points[nozzle] += machine_component_points[machine_idx][idx]
|
||||
|
||||
machine_points = sum(machine_component_points[machine_idx]) # num of placement points
|
||||
if machine_points == 0:
|
||||
continue
|
||||
ul = math.ceil(len(nozzle_points) * 1.0 / max_head_index) - 1 # num of nozzle set
|
||||
|
||||
# assignments of nozzles to heads
|
||||
wl = 0 # num of workload
|
||||
total_heads = (1 + ul) * max_head_index - len(nozzle_points)
|
||||
nozzle_heads = defaultdict(int)
|
||||
for nozzle in nozzle_points.keys():
|
||||
nozzle_heads[nozzle] = math.floor(nozzle_points[nozzle] * 1.0 / machine_points * total_heads)
|
||||
nozzle_heads[nozzle] += 1
|
||||
|
||||
total_heads = (1 + ul) * max_head_index
|
||||
for heads in nozzle_heads.values():
|
||||
total_heads -= heads
|
||||
|
||||
for nozzle in sorted(nozzle_heads, key=lambda x: nozzle_points[x] / nozzle_heads[x], reverse=True):
|
||||
if total_heads == 0:
|
||||
break
|
||||
nozzle_heads[nozzle] += 1
|
||||
total_heads -= 1
|
||||
|
||||
# averagely assign placements to heads
|
||||
heads_placement = []
|
||||
for nozzle in nozzle_heads.keys():
|
||||
points = math.floor(nozzle_points[nozzle] / nozzle_heads[nozzle])
|
||||
|
||||
heads_placement += [[nozzle, points] for _ in range(nozzle_heads[nozzle])]
|
||||
nozzle_points[nozzle] -= (nozzle_heads[nozzle] * points)
|
||||
for idx in range(len(heads_placement) - 1, -1, -1):
|
||||
if nozzle_points[nozzle] <= 0:
|
||||
break
|
||||
nozzle_points[nozzle] -= 1
|
||||
heads_placement[idx][1] += 1
|
||||
heads_placement = sorted(heads_placement, key=lambda x: x[1], reverse=True)
|
||||
|
||||
# every max_head_index heads in the non-decreasing order are grouped together as nozzle set
|
||||
for idx in range(len(heads_placement) // max_head_index):
|
||||
wl += heads_placement[idx][1]
|
||||
objective_val.append(T_pp * machine_points + T_tr * wl + T_nc * ul)
|
||||
|
||||
return objective_val, machine_component_points
|
||||
|
||||
|
||||
def assemblyline_optimizer_genetic(pcb_data, component_data):
|
||||
# basic parameter
|
||||
# crossover rate & mutation rate: 80% & 10%
|
||||
# population size: 200
|
||||
# the number of generation: 500
|
||||
crossover_rate, mutation_rate = 0.8, 0.1
|
||||
population_size, n_generations = 200, 500
|
||||
|
||||
# the number of placement points, the number of available feeders, and nozzle type of component respectively
|
||||
component_points, component_feeders, component_nozzle = defaultdict(int), defaultdict(int), defaultdict(str)
|
||||
for data in pcb_data.iterrows():
|
||||
part_index = component_data[component_data['part'] == data[1]['part']].index.tolist()[0]
|
||||
nozzle = component_data.loc[part_index]['nz']
|
||||
|
||||
component_points[part_index] += 1
|
||||
component_feeders[part_index] = component_data.loc[part_index]['feeder-limit']
|
||||
component_nozzle[part_index] = nozzle
|
||||
|
||||
# population initialization
|
||||
best_popval = []
|
||||
population = selective_initialization(component_points, component_feeders, population_size)
|
||||
with tqdm(total=n_generations) as pbar:
|
||||
pbar.set_description('genetic algorithm process for PCB assembly line balance')
|
||||
|
||||
new_population, new_pop_val = [], []
|
||||
for _ in range(n_generations):
|
||||
# calculate fitness value
|
||||
pop_val = []
|
||||
for individual in population:
|
||||
val, assigned_points = cal_individual_val(component_points, component_nozzle, individual)
|
||||
pop_val.append(max(val))
|
||||
|
||||
best_popval.append(min(pop_val))
|
||||
|
||||
select_index = get_top_k_value(pop_val, population_size - len(new_pop_val), reverse=False)
|
||||
population = [population[idx] for idx in select_index]
|
||||
pop_val = [pop_val[idx] for idx in select_index]
|
||||
|
||||
population += new_population
|
||||
for individual in new_population:
|
||||
val, _ = cal_individual_val(component_points, component_nozzle, individual)
|
||||
pop_val.append(max(val))
|
||||
|
||||
# min-max convert
|
||||
max_val = max(pop_val)
|
||||
pop_val = list(map(lambda v: max_val - v, pop_val))
|
||||
sum_pop_val = sum(pop_val) + 1e-10
|
||||
pop_val = [v / sum_pop_val + 1e-3 for v in pop_val]
|
||||
|
||||
# crossover and mutation
|
||||
new_population = []
|
||||
for pop in range(population_size):
|
||||
if pop % 2 == 0 and np.random.random() < crossover_rate:
|
||||
index1 = roulette_wheel_selection(pop_val)
|
||||
while True:
|
||||
index2 = roulette_wheel_selection(pop_val)
|
||||
if index1 != index2:
|
||||
break
|
||||
|
||||
offspring1, offspring2 = selective_crossover(component_points, component_feeders,
|
||||
population[index1], population[index2])
|
||||
|
||||
if np.random.random() < mutation_rate:
|
||||
offspring1 = constraint_swap_mutation(component_points, offspring1)
|
||||
|
||||
if np.random.random() < mutation_rate:
|
||||
offspring1 = constraint_swap_mutation(component_points, offspring1)
|
||||
|
||||
new_population.append(offspring1)
|
||||
new_population.append(offspring2)
|
||||
|
||||
pbar.update(1)
|
||||
|
||||
best_individual = population[np.argmax(pop_val)]
|
||||
_, assignment_result = cal_individual_val(component_points, component_nozzle, best_individual)
|
||||
|
||||
# available feeder check
|
||||
for part_index, data in component_data.iterrows():
|
||||
feeder_limit = data['feeder-limit']
|
||||
for machine_index in range(max_machine_index):
|
||||
if assignment_result[machine_index][part_index]:
|
||||
feeder_limit -= 1
|
||||
assert feeder_limit >= 0
|
||||
|
||||
return assignment_result
|
16
optimizer_heuristic.py
Normal file
16
optimizer_heuristic.py
Normal file
@ -0,0 +1,16 @@
|
||||
from base_optimizer.optimizer_common import *
|
||||
|
||||
|
||||
# TODO: 需要考虑贴装点分布位置的限制
|
||||
def assembly_time_estimator(pcb_data, component_data, assignment):
|
||||
return 0
|
||||
|
||||
|
||||
def assemblyline_optimizer_heuristic(pcb_data, component_data):
|
||||
assignment_result = []
|
||||
|
||||
|
||||
# for machine_index in range(max_machine_index):
|
||||
# assembly_time_estimator(pcb_data, component_data, assignment_result[machine_index])
|
||||
|
||||
return assignment_result
|
@ -1,4 +1,5 @@
|
||||
from optimizer_common import *
|
||||
from base_optimizer.optimizer_common import *
|
||||
|
||||
|
||||
# 绘制各周期从供料器周期拾取的元件位置
|
||||
def pickup_cycle_schematic(feeder_slot_result, cycle_result):
|
||||
@ -6,10 +7,10 @@ def pickup_cycle_schematic(feeder_slot_result, cycle_result):
|
||||
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
|
||||
# data
|
||||
bar_width = .7
|
||||
feeder_part = np.zeros((int)(max_slot_index / 2), dtype = np.int)
|
||||
feeder_part = np.zeros(int(max_slot_index / 2), dtype=np.int)
|
||||
for cycle in range(len(feeder_slot_result)):
|
||||
label_str = '周期' + str(cycle + 1)
|
||||
cur_feeder_part = np.zeros((int)(max_slot_index / 2), dtype = np.int)
|
||||
cur_feeder_part = np.zeros(int(max_slot_index / 2), dtype=np.int)
|
||||
for slot in feeder_slot_result[cycle]:
|
||||
if slot > 0:
|
||||
cur_feeder_part[slot] += cycle_result[cycle]
|
||||
@ -38,7 +39,7 @@ def placement_route_schematic(pcb_data, component_result, cycle_result, feeder_s
|
||||
mount_pos = []
|
||||
for head in head_sequence[cycle]:
|
||||
index = placement_result[cycle][head]
|
||||
plt.text(pos_x[index], pos_y[index] + 0.1, 'HD%d' % (head + 1), ha='center', va = 'bottom', size = 10)
|
||||
plt.text(pos_x[index], pos_y[index] + 0.1, 'HD%d' % (head + 1), ha='center', va='bottom', size=10)
|
||||
plt.plot([pos_x[index], pos_x[index] - head * head_interval], [pos_y[index], pos_y[index]], linestyle='-.',
|
||||
color='black', linewidth=1)
|
||||
mount_pos.append([pos_x[index] - head * head_interval, pos_y[index]])
|
||||
@ -48,7 +49,8 @@ def placement_route_schematic(pcb_data, component_result, cycle_result, feeder_s
|
||||
|
||||
# 绘制贴装路径
|
||||
for i in range(len(mount_pos) - 1):
|
||||
plt.plot([mount_pos[i][0], mount_pos[i + 1][0]], [mount_pos[i][1], mount_pos[i + 1][1]], color='blue', linewidth=1)
|
||||
plt.plot([mount_pos[i][0], mount_pos[i + 1][0]], [mount_pos[i][1], mount_pos[i + 1][1]], color='blue',
|
||||
linewidth=1)
|
||||
|
||||
draw_x, draw_y = [], []
|
||||
for c in range(cycle, len(placement_result)):
|
||||
@ -65,8 +67,8 @@ def placement_route_schematic(pcb_data, component_result, cycle_result, feeder_s
|
||||
|
||||
# 绘制供料器位置布局
|
||||
for slot in range(max_slot_index // 2):
|
||||
plt.scatter(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1], marker = 'x', s = 12, color = 'green')
|
||||
plt.text(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1] - 50, slot + 1, ha = 'center', va = 'bottom', size = 8)
|
||||
plt.scatter(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1], marker='x', s=12, color='green')
|
||||
plt.text(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1] - 50, slot + 1, ha='center', va='bottom', size=8)
|
||||
|
||||
feeder_part, feeder_counter = {}, {}
|
||||
placement_cycle = 0
|
||||
@ -84,7 +86,8 @@ def placement_route_schematic(pcb_data, component_result, cycle_result, feeder_s
|
||||
placement_cycle += cycle_result[cycle_]
|
||||
|
||||
for slot, part in feeder_part.items():
|
||||
plt.text(slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1] + 15, part + ': ' + str(feeder_counter[slot]), ha = 'center', size = 7, rotation = 90)
|
||||
plt.text(slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1] + 15,
|
||||
part + ': ' + str(feeder_counter[slot]), ha='center', size=7, rotation=90)
|
||||
|
||||
plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
|
||||
[slotf1_pos[1] + 10, slotf1_pos[1] + 10], color = 'black')
|
||||
@ -93,7 +96,7 @@ def placement_route_schematic(pcb_data, component_result, cycle_result, feeder_s
|
||||
|
||||
for counter in range(max_slot_index // 2 + 1):
|
||||
pos = slotf1_pos[0] + (counter - 0.5) * slot_interval
|
||||
plt.plot([pos, pos], [slotf1_pos[1] + 10, slotf1_pos[1] - 40], color='black', linewidth = 1)
|
||||
plt.plot([pos, pos], [slotf1_pos[1] + 10, slotf1_pos[1] - 40], color='black', linewidth=1)
|
||||
|
||||
# 绘制拾取路径
|
||||
pick_slot = []
|
||||
@ -130,7 +133,8 @@ def placement_route_schematic(pcb_data, component_result, cycle_result, feeder_s
|
||||
plt.show()
|
||||
|
||||
|
||||
def save_placement_route_figure(file_name, pcb_data, component_result, cycle_result, feeder_slot_result, placement_result, head_sequence):
|
||||
def save_placement_route_figure(file_name, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
placement_result, head_sequence):
|
||||
path = 'result/' + file_name[:file_name.find('.')]
|
||||
if not os.path.exists(path):
|
||||
os.mkdir(path)
|
||||
@ -150,8 +154,8 @@ def save_placement_route_figure(file_name, pcb_data, component_result, cycle_res
|
||||
for head in head_sequence[cycle]:
|
||||
index = placement_result[cycle][head]
|
||||
plt.text(pos_x[index], pos_y[index] + 0.1, 'HD%d' % (head + 1), ha='center', va='bottom', size=10)
|
||||
plt.plot([pos_x[index], pos_x[index] - head * head_interval], [pos_y[index], pos_y[index]], linestyle='-.',
|
||||
color='black', linewidth=1)
|
||||
plt.plot([pos_x[index], pos_x[index] - head * head_interval], [pos_y[index], pos_y[index]],
|
||||
linestyle='-.', color='black', linewidth=1)
|
||||
mount_pos.append([pos_x[index] - head * head_interval, pos_y[index]])
|
||||
plt.plot(mount_pos[-1][0], mount_pos[-1][1], marker='^', color='red', markerfacecolor='white')
|
||||
|
||||
@ -196,9 +200,11 @@ def save_placement_route_figure(file_name, pcb_data, component_result, cycle_res
|
||||
plt.text(slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1] + 15,
|
||||
part + ': ' + str(feeder_counter[slot]), ha='center', size=7, rotation=90)
|
||||
|
||||
plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
|
||||
plt.plot(
|
||||
[slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
|
||||
[slotf1_pos[1] + 10, slotf1_pos[1] + 10], color='black')
|
||||
plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
|
||||
plt.plot(
|
||||
[slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)],
|
||||
[slotf1_pos[1] - 40, slotf1_pos[1] - 40], color='black')
|
||||
|
||||
for counter in range(max_slot_index // 2 + 1):
|
||||
@ -217,12 +223,13 @@ def save_placement_route_figure(file_name, pcb_data, component_result, cycle_res
|
||||
pick_slot = list(set(pick_slot))
|
||||
pick_slot = sorted(pick_slot)
|
||||
|
||||
plt.plot([mount_pos[0][0], slotf1_pos[0] + slot_interval * (pick_slot[0] - 1)], [mount_pos[0][1], slotf1_pos[1]],
|
||||
plt.plot([mount_pos[0][0], slotf1_pos[0] + slot_interval * (pick_slot[0] - 1)],
|
||||
[mount_pos[0][1], slotf1_pos[1]], color='blue', linewidth=1)
|
||||
plt.plot([mount_pos[-1][0], slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)],
|
||||
[mount_pos[-1][1], slotf1_pos[1]], color='blue', linewidth=1)
|
||||
plt.plot([slotf1_pos[0] + slot_interval * (pick_slot[0] - 1),
|
||||
slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)], [slotf1_pos[1], slotf1_pos[1]],
|
||||
color='blue', linewidth=1)
|
||||
plt.plot([mount_pos[-1][0], slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)], [mount_pos[-1][1], slotf1_pos[1]],
|
||||
color='blue', linewidth=1)
|
||||
plt.plot([slotf1_pos[0] + slot_interval * (pick_slot[0] - 1), slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)],
|
||||
[slotf1_pos[1], slotf1_pos[1]], color='blue', linewidth=1)
|
||||
|
||||
plt.savefig(path + '/cycle_{}'.format(cycle + 1))
|
||||
|
||||
|
Reference in New Issue
Block a user