From 6b031dc48662e8368aa4f2899ec2cabf23de48c5 Mon Sep 17 00:00:00 2001 From: hit-lu Date: Sat, 25 Feb 2023 21:09:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E7=AB=A0=E5=A4=8D=E7=8E=B0=EF=BC=9A?= =?UTF-8?q?=20guo=5Fintegrated=5F2012?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 8 + SMO/benchmarks.py | 34 +++ SMO/main.py | 79 ++++++ SMO/smo.py | 348 ++++++++++++++++++++++++++ SMO/solution.py | 33 +++ dataloader.py | 89 +++++++ optimizer.py | 316 ++++++++++++++++++++++++ optimizer_common.py | 78 ++++++ result_analysis.py | 579 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 1564 insertions(+) create mode 100644 .gitignore create mode 100644 SMO/benchmarks.py create mode 100644 SMO/main.py create mode 100644 SMO/smo.py create mode 100644 SMO/solution.py create mode 100644 dataloader.py create mode 100644 optimizer.py create mode 100644 optimizer_common.py create mode 100644 result_analysis.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55710f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.idea +result/ +data/ +*.txt +__pycache__ +Lib/ +Scripts/ +*.cfg \ No newline at end of file diff --git a/SMO/benchmarks.py b/SMO/benchmarks.py new file mode 100644 index 0000000..9ec6a7e --- /dev/null +++ b/SMO/benchmarks.py @@ -0,0 +1,34 @@ +# -*- 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 + + + diff --git a/SMO/main.py b/SMO/main.py new file mode 100644 index 0000000..9302aae --- /dev/null +++ b/SMO/main.py @@ -0,0 +1,79 @@ +# -*- 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)) diff --git a/SMO/smo.py b/SMO/smo.py new file mode 100644 index 0000000..7b8651b --- /dev/null +++ b/SMO/smo.py @@ -0,0 +1,348 @@ +# -*- 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 =================================== # + + diff --git a/SMO/solution.py b/SMO/solution.py new file mode 100644 index 0000000..34ed4b2 --- /dev/null +++ b/SMO/solution.py @@ -0,0 +1,33 @@ +# -*- 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 diff --git a/dataloader.py b/dataloader.py new file mode 100644 index 0000000..00befae --- /dev/null +++ b/dataloader.py @@ -0,0 +1,89 @@ +import random + +from optimizer_common import * + + +def load_data(filename: str, load_cp_data=True, load_feeder_data=True, component_register=False): + # 锁定随机数种子 + random.seed(0) + + # 读取PCB数据 + filename = 'data/' + filename + pcb_data = pd.DataFrame(pd.read_csv(filepath_or_buffer=filename, sep='\t', header=None)) + if len(pcb_data.columns) <= 17: + step_col = ["ref", "x", "y", "z", "r", "part", "desc", "fdr", "nz", "hd", "cs", "cy", "sk", "bl", "ar", + "pl", "lv"] + elif len(pcb_data.columns) <= 18: + step_col = ["ref", "x", "y", "z", "r", "part", "desc", "fdr", "nz", "hd", "cs", "cy", "sk", "bl", "ar", "fid", + "pl", "lv"] + else: + step_col = ["ref", "x", "y", "z", "r", "part", "desc", "fdr", "nz", "hd", "cs", "cy", "sk", "bl", "ar", "fid", + "", "pl", "lv"] + + pcb_data.columns = step_col + pcb_data = pcb_data.dropna(axis=1) + + # 坐标系处理 + # pcb_data = pcb_data.sort_values(by = ['x', 'y'], ascending = True) + # 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) + 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: + 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) + # 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] + part_feeder_assign[part].add(slot) + + if nozzle != 'A' and component_data.loc[part_index]['nz'] != nozzle: + warning_info = 'the nozzle type of component ' + part + ' is not consistent with the pcb data' + warnings.warn(warning_info, UserWarning) + + for idx, data in component_data.iterrows(): + 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)) + if load_feeder_data: + for data in pcb_data.iterrows(): + fdr = data[1]['fdr'] + slot, part = fdr.split(' ') + if slot[0] != 'F' and slot[0] != 'R': + continue + 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: + drop_index = random.sample(list(range(len(feeder_data))), len(feeder_data) // 2) + 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'] # 同上 + + return pcb_data, component_data, feeder_data diff --git a/optimizer.py b/optimizer.py new file mode 100644 index 0000000..283044b --- /dev/null +++ b/optimizer.py @@ -0,0 +1,316 @@ +import math +import random + +import matplotlib.pyplot as plt + +from optimizer_common import * +from dataloader 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) + break + return res + + +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) + 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 + 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 nozzle_heads.keys(): # TODO:有利于减少周期的方法 + 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 max(objective_val), machine_component_points + + +@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__': + # 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('--auto_register', default=1, type=int, help='register the component according the pcb data') + + params = parser.parse_args() + + # 结果输出显示所有行和列 + pd.set_option('display.max_columns', None) + pd.set_option('display.max_rows', None) + + # 加载PCB数据 + pcb_data, component_data, _ = load_data(params.filename, component_register=params.auto_register) # 加载PCB数据 + + optimizer(pcb_data, component_data) + + + + diff --git a/optimizer_common.py b/optimizer_common.py new file mode 100644 index 0000000..59be942 --- /dev/null +++ b/optimizer_common.py @@ -0,0 +1,78 @@ +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 + + diff --git a/result_analysis.py b/result_analysis.py new file mode 100644 index 0000000..2c6b71f --- /dev/null +++ b/result_analysis.py @@ -0,0 +1,579 @@ +from optimizer_common import * + +# 绘制各周期从供料器周期拾取的元件位置 +def pickup_cycle_schematic(feeder_slot_result, cycle_result): + plt.rcParams['font.sans-serif'] = ['KaiTi'] # 指定默认字体 + plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题 + # data + bar_width = .7 + feeder_part = np.zeros((int)(max_slot_index / 2), dtype = np.int) + for cycle in range(len(feeder_slot_result)): + label_str = '周期' + str(cycle + 1) + cur_feeder_part = np.zeros((int)(max_slot_index / 2), dtype = np.int) + for slot in feeder_slot_result[cycle]: + if slot > 0: + cur_feeder_part[slot] += cycle_result[cycle] + + plt.bar(np.arange(max_slot_index / 2), cur_feeder_part, bar_width, edgecolor='black', bottom=feeder_part, + label=label_str) + + for slot in feeder_slot_result[cycle]: + if slot > 0: + feeder_part[slot] += cycle_result[cycle] + + plt.legend() + plt.show() + + +def placement_route_schematic(pcb_data, component_result, cycle_result, feeder_slot_result, placement_result, + head_sequence, cycle=-1): + + plt.figure('cycle {}'.format(cycle + 1)) + pos_x, pos_y = [], [] + for i in range(len(pcb_data)): + pos_x.append(pcb_data.loc[i]['x'] + stopper_pos[0]) + pos_y.append(pcb_data.loc[i]['y'] + stopper_pos[1]) + # plt.text(pcb_data.loc[i]['x'], pcb_data.loc[i]['y'] + 0.1, '%d' % i, ha='center', va = 'bottom', size = 8) + + mount_pos = [] + for head in head_sequence[cycle]: + index = placement_result[cycle][head] + plt.text(pos_x[index], pos_y[index] + 0.1, 'HD%d' % (head + 1), ha='center', va = 'bottom', size = 10) + plt.plot([pos_x[index], pos_x[index] - head * head_interval], [pos_y[index], pos_y[index]], linestyle='-.', + color='black', linewidth=1) + mount_pos.append([pos_x[index] - head * head_interval, pos_y[index]]) + plt.plot(mount_pos[-1][0], mount_pos[-1][1], marker='^', color='red', markerfacecolor='white') + + # plt.text(mount_pos[-1][0], mount_pos[-1][1], '%d' % index, size=8) + + # 绘制贴装路径 + for i in range(len(mount_pos) - 1): + plt.plot([mount_pos[i][0], mount_pos[i + 1][0]], [mount_pos[i][1], mount_pos[i + 1][1]], color='blue', linewidth=1) + + draw_x, draw_y = [], [] + for c in range(cycle, len(placement_result)): + for h in range(max_head_index): + i = placement_result[c][h] + if i == -1: + continue + draw_x.append(pcb_data.loc[i]['x'] + stopper_pos[0]) + draw_y.append(pcb_data.loc[i]['y'] + stopper_pos[1]) + + # plt.text(draw_x[-1], draw_y[-1] - 5, '%d' % i, ha='center', va='bottom', size=10) + + plt.scatter(draw_x, draw_y, s=8) + + # 绘制供料器位置布局 + for slot in range(max_slot_index // 2): + plt.scatter(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1], marker = 'x', s = 12, color = 'green') + plt.text(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1] - 50, slot + 1, ha = 'center', va = 'bottom', size = 8) + + feeder_part, feeder_counter = {}, {} + placement_cycle = 0 + for cycle_, components in enumerate(component_result): + for head, component in enumerate(components): + if component == -1: + continue + placement = placement_result[placement_cycle][head] + slot = feeder_slot_result[cycle_][head] + feeder_part[slot] = pcb_data.loc[placement]['part'] + if slot not in feeder_counter.keys(): + feeder_counter[slot] = 0 + + feeder_counter[slot] += cycle_result[cycle_] + placement_cycle += cycle_result[cycle_] + + for slot, part in feeder_part.items(): + plt.text(slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1] + 15, part + ': ' + str(feeder_counter[slot]), ha = 'center', size = 7, rotation = 90) + + plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)], + [slotf1_pos[1] + 10, slotf1_pos[1] + 10], color = 'black') + plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)], + [slotf1_pos[1] - 40, slotf1_pos[1] - 40], color = 'black') + + for counter in range(max_slot_index // 2 + 1): + pos = slotf1_pos[0] + (counter - 0.5) * slot_interval + plt.plot([pos, pos], [slotf1_pos[1] + 10, slotf1_pos[1] - 40], color='black', linewidth = 1) + + # 绘制拾取路径 + pick_slot = [] + cycle_group = 0 + while sum(cycle_result[0: cycle_group + 1]) < cycle: + cycle_group += 1 + for head, slot in enumerate(feeder_slot_result[cycle_group]): + if slot == -1: + continue + pick_slot.append(slot - head * interval_ratio) + pick_slot = list(set(pick_slot)) + pick_slot = sorted(pick_slot) + + next_cycle_group = 0 + next_pick_slot = max_slot_index + while sum(cycle_result[0: next_cycle_group + 1]) < cycle + 1: + next_cycle_group += 1 + if next_cycle_group < len(feeder_slot_result): + for head, slot in enumerate(feeder_slot_result[cycle_group]): + if slot == -1: + continue + next_pick_slot = min(next_pick_slot, slot - head * interval_ratio) + + # 前往PCB贴装 + plt.plot([mount_pos[-1][0], slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)], [mount_pos[-1][1], slotf1_pos[1]], + color='blue', linewidth=1) + # 基座移动路径 + plt.plot([slotf1_pos[0] + slot_interval * (pick_slot[0] - 1), slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)], + [slotf1_pos[1], slotf1_pos[1]], color='blue', linewidth=1) + # 返回基座取料 + plt.plot([mount_pos[0][0], slotf1_pos[0] + slot_interval * (next_pick_slot - 1)], [mount_pos[0][1], slotf1_pos[1]], + color='blue', linewidth=1) + + plt.show() + + +def save_placement_route_figure(file_name, pcb_data, component_result, cycle_result, feeder_slot_result, placement_result, head_sequence): + path = 'result/' + file_name[:file_name.find('.')] + if not os.path.exists(path): + os.mkdir(path) + + pos_x, pos_y = [], [] + for i in range(len(pcb_data)): + pos_x.append(pcb_data.loc[i]['x'] + stopper_pos[0]) + pos_y.append(pcb_data.loc[i]['y'] + stopper_pos[1]) + # plt.text(pcb_data.loc[i]['x'], pcb_data.loc[i]['y'] + 0.1, '%d' % i, ha='center', va = 'bottom', size = 8) + + with tqdm(total=100) as pbar: + pbar.set_description('save figure') + for cycle in range(len(placement_result)): + plt.figure(cycle) + + mount_pos = [] + for head in head_sequence[cycle]: + index = placement_result[cycle][head] + plt.text(pos_x[index], pos_y[index] + 0.1, 'HD%d' % (head + 1), ha='center', va='bottom', size=10) + plt.plot([pos_x[index], pos_x[index] - head * head_interval], [pos_y[index], pos_y[index]], linestyle='-.', + color='black', linewidth=1) + mount_pos.append([pos_x[index] - head * head_interval, pos_y[index]]) + plt.plot(mount_pos[-1][0], mount_pos[-1][1], marker='^', color='red', markerfacecolor='white') + + # 绘制贴装路径 + for i in range(len(mount_pos) - 1): + plt.plot([mount_pos[i][0], mount_pos[i + 1][0]], [mount_pos[i][1], mount_pos[i + 1][1]], color='blue', + linewidth=1) + + draw_x, draw_y = [], [] + for c in range(cycle, len(placement_result)): + for h in range(max_head_index): + i = placement_result[c][h] + if i == -1: + continue + draw_x.append(pcb_data.loc[i]['x'] + stopper_pos[0]) + draw_y.append(pcb_data.loc[i]['y'] + stopper_pos[1]) + + # plt.text(draw_x[-1], draw_y[-1] - 5, '%d' % i, ha='center', va='bottom', size=10) + + plt.scatter(pos_x, pos_y, s=8) + # 绘制供料器位置布局 + for slot in range(max_slot_index // 2): + plt.scatter(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1], marker='x', s=12, color='green') + plt.text(slotf1_pos[0] + slot_interval * slot, slotf1_pos[1] - 50, slot + 1, ha='center', va='bottom', size=8) + + feeder_part, feeder_counter = {}, {} + placement_cycle = 0 + for cycle_, components in enumerate(component_result): + for head, component in enumerate(components): + if component == -1: + continue + placement = placement_result[placement_cycle][head] + slot = feeder_slot_result[cycle_][head] + feeder_part[slot] = pcb_data.loc[placement]['part'] + if slot not in feeder_counter.keys(): + feeder_counter[slot] = 0 + + feeder_counter[slot] += cycle_result[cycle_] + placement_cycle += cycle_result[cycle_] + + for slot, part in feeder_part.items(): + plt.text(slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1] + 15, + part + ': ' + str(feeder_counter[slot]), ha='center', size=7, rotation=90) + + plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)], + [slotf1_pos[1] + 10, slotf1_pos[1] + 10], color='black') + plt.plot([slotf1_pos[0] - slot_interval / 2, slotf1_pos[0] + slot_interval * (max_slot_index // 2 - 1 + 0.5)], + [slotf1_pos[1] - 40, slotf1_pos[1] - 40], color='black') + + for counter in range(max_slot_index // 2 + 1): + pos = slotf1_pos[0] + (counter - 0.5) * slot_interval + plt.plot([pos, pos], [slotf1_pos[1] + 10, slotf1_pos[1] - 40], color='black', linewidth=1) + + # 绘制拾取路径 + pick_slot = [] + cycle_group = 0 + while sum(cycle_result[0: cycle_group + 1]) < cycle: + cycle_group += 1 + for head, slot in enumerate(feeder_slot_result[cycle_group]): + if slot == -1: + continue + pick_slot.append(slot - head * interval_ratio) + pick_slot = list(set(pick_slot)) + pick_slot = sorted(pick_slot) + + plt.plot([mount_pos[0][0], slotf1_pos[0] + slot_interval * (pick_slot[0] - 1)], [mount_pos[0][1], slotf1_pos[1]], + color='blue', linewidth=1) + plt.plot([mount_pos[-1][0], slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)], [mount_pos[-1][1], slotf1_pos[1]], + color='blue', linewidth=1) + plt.plot([slotf1_pos[0] + slot_interval * (pick_slot[0] - 1), slotf1_pos[0] + slot_interval * (pick_slot[-1] - 1)], + [slotf1_pos[1], slotf1_pos[1]], color='blue', linewidth=1) + + plt.savefig(path + '/cycle_{}'.format(cycle + 1)) + + plt.close(cycle) + pbar.update(100 / len(placement_result)) + + +def output_optimize_result(file_name, method, component_data, pcb_data, feeder_data, component_result, cycle_result, + feeder_slot_result, placement_result, head_sequence): + assert len(component_result) == len(feeder_slot_result) + if feeder_data is None: + warning_info = 'file: ' + file_name + ' optimize result is not existed!' + warnings.warn(warning_info, UserWarning) + return + + output_data = pcb_data.copy(deep=True) + + # 默认ANC参数 + anc_list = defaultdict(list) + anc_list['CN065'] = list(range(14, 25, 2)) + anc_list['CN220'] = list(range(15, 26, 2)) + anc_list['CN140'] = list(range(26, 37, 2)) + anc_list['CN400'] = list(range(27, 38, 2)) + + # 更新供料器组参数 + for cycle_set in range(len(cycle_result)): + for head, component in enumerate(component_result[cycle_set]): + if component == -1: + continue + if feeder_data[feeder_data['slot'] == feeder_slot_result[cycle_set][head]].index.empty: + part = component_data.loc[component]['part'] + feeder_data.loc[len(feeder_data.index)] = [feeder_slot_result[cycle_set][head], part, 0] + feeder_data.sort_values('slot', inplace=True, ascending=True, ignore_index=True) + + placement_index = [] + assigned_nozzle, assigned_anc_hole = ['' for _ in range(max_head_index)], [-1 for _ in range(max_head_index)] + for cycle_set in range(len(cycle_result)): + floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)]) + for cycle in range(floor_cycle, ceil_cycle): + cycle_start = True + cycle_nozzle = ['' for _ in range(max_head_index)] + head_indexes = [-1 for _ in range(max_head_index)] + for head in head_sequence[cycle]: + index_ = placement_result[cycle][head] + if index_ == -1: + continue + head_indexes[head] = index_ + placement_index.append(index_) + + output_data.loc[index_, 'cs'] = 1 if cycle_start else 0 + output_data.loc[index_, 'cy'] = cycle + 1 + output_data.loc[index_, 'hd'] = head + 1 + + cycle_start = False + + # 供料器信息 + slot = feeder_slot_result[cycle_set][head] + fdr = 'F' + str(slot) if slot < max_slot_index // 2 else 'R' + str(slot - max_slot_index // 2) + feeder_index = feeder_data[feeder_data['slot'] == slot].index.tolist()[0] + + output_data.loc[index_, 'fdr'] = fdr + ' ' + feeder_data.loc[feeder_index, 'part'] + + # ANC信息 + cycle_nozzle[head] = component_data.loc[component_result[cycle_set][head], 'nz'] + + for head in range(max_head_index): + nozzle = cycle_nozzle[head] + if nozzle == '': + continue + if nozzle != assigned_nozzle[head]: + # 已分配有吸嘴,卸载原吸嘴 + if assigned_nozzle[head] != '': + anc_list[assigned_nozzle[head]].append(assigned_anc_hole[head]) + anc_list[assigned_nozzle[head]] = sorted(anc_list[assigned_nozzle[head]]) + + # 安装新的吸嘴 + assigned_nozzle[head] = nozzle + try: + assigned_anc_hole[head] = anc_list[nozzle][0] + except IndexError: + info = 'the number of nozzle for [' + nozzle + '] exceeds the quantity limit' + raise IndexError(info) + anc_list[nozzle].pop(0) + + output_data.loc[head_indexes[head], 'nz'] = '1-' + str(assigned_anc_hole[head]) + ' ' + nozzle + + output_data = output_data.reindex(placement_index) + output_data = output_data.reset_index(drop=True) + if 'desc' not in output_data.columns: + column_index = int(np.where(output_data.columns.values.reshape(-1) == 'part')[0][0]) + output_data.insert(loc=column_index + 1, column='desc', value='') + + if not os.path.exists('result/' + method): + os.makedirs('result/' + method) + + file_name = method + '/' + file_name.split('.')[0] + '.xlsx' + output_data.to_excel('result/' + file_name, sheet_name='tb1', float_format='%.3f', na_rep='') + + +def component_assign_evaluate(component_data, component_result, cycle_result, feeder_slot_result) -> float: + nozzle_change_counter = 0 + for head in range(max_head_index): + nozzle = '' + for cycle in range(len(component_result)): + component_index = component_result[cycle][head] + if component_index == -1: + continue + + if cycle != 0 and nozzle != component_data.loc[component_index, 'nz']: + nozzle_change_counter += 1 + nozzle = component_data.loc[component_index, 'nz'] + + gang_pick_counter = 0 + for cycle, feeder_slot in enumerate(feeder_slot_result): + pick_slot = defaultdict(int) + for head, slot in enumerate(feeder_slot): + if slot == -1: + continue + pick_slot[slot - head * interval_ratio] += 1 + for _ in pick_slot.values(): + gang_pick_counter += cycle_result[cycle] + + return sum(cycle_result) + e_nz_change * nozzle_change_counter + e_gang_pick * gang_pick_counter + + +def optimization_assign_result(component_data, pcb_data, component_result, cycle_result, feeder_slot_result, + nozzle_hinter=False, component_hinter=False, feeder_hinter=False): + if nozzle_hinter: + columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle'] + + nozzle_assign = pd.DataFrame(columns=columns) + for cycle, components in enumerate(component_result): + nozzle_assign.loc[cycle, 'cycle'] = cycle_result[cycle] + for head in range(max_head_index): + index = component_result[cycle][head] + if index == -1: + nozzle_assign.loc[cycle, 'H{}'.format(head + 1)] = '' + else: + nozzle = component_data.loc[index]['nz'] + nozzle_assign.loc[cycle, 'H{}'.format(head + 1)] = nozzle + + print(nozzle_assign) + print('') + + if component_hinter: + columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle'] + + component_assign = pd.DataFrame(columns=columns) + for cycle, components in enumerate(component_result): + component_assign.loc[cycle, 'cycle'] = cycle_result[cycle] + for head in range(max_head_index): + index = component_result[cycle][head] + if index == -1: + component_assign.loc[cycle, 'H{}'.format(head + 1)] = '' + else: + part = component_data.loc[index]['part'] + component_assign.loc[cycle, 'H{}'.format(head + 1)] = part + + print(component_assign) + print('') + + if feeder_hinter: + columns = ['H{}'.format(i + 1) for i in range(max_head_index)] + ['cycle'] + + feedr_assign = pd.DataFrame(columns=columns) + for cycle, components in enumerate(feeder_slot_result): + feedr_assign.loc[cycle, 'cycle'] = cycle_result[cycle] + for head in range(max_head_index): + slot = feeder_slot_result[cycle][head] + if slot == -1: + feedr_assign.loc[cycle, 'H{}'.format(head + 1)] = 'A' + else: + feedr_assign.loc[cycle, 'H{}'.format(head + 1)] = 'F{}'.format( + slot) if slot <= max_slot_index // 2 else 'R{}'.format(slot - max_head_index) + + print(feedr_assign) + print('') + + +def placement_time_estimate(component_data, pcb_data, component_result, cycle_result, feeder_slot_result, + placement_result, head_sequence, hinter=True) -> float: + # === 校验 === + total_points = 0 + for cycle, components in enumerate(component_result): + for head, component in enumerate(components): + if component == -1: + continue + total_points += cycle_result[cycle] + + if total_points != len(pcb_data): + warning_info = 'the number of placement points is not match with the PCB data. ' + warnings.warn(warning_info, UserWarning) + return 0. + + for placements in placement_result: + for placement in placements: + if placement == -1: + continue + total_points -= 1 + + if total_points != 0: + warnings.warn( + 'the optimization result of component assignment result and placement result are not consistent. ', + UserWarning) + return 0. + + feeder_arrangement = defaultdict(set) + for cycle, feeder_slots in enumerate(feeder_slot_result): + for head, slot in enumerate(feeder_slots): + if slot == -1: + continue + feeder_arrangement[component_result[cycle][head]].add(slot) + + for part, data in component_data.iterrows(): + if part in feeder_arrangement.keys() and data['feeder-limit'] < len(feeder_arrangement[part]): + info = 'the number of arranged feeder of [' + data['part'] + '] exceeds the quantity limit' + warnings.warn(info, UserWarning) + return 0. + + t_pick, t_place = .078, .051 # 贴装/拾取用时 + t_nozzle_put, t_nozzle_pick = 0.9, 0.75 # 装卸吸嘴用时 + t_fix_camera_check = 0.12 # 固定相机检测时间 + + total_moving_time = .0 # 总移动用时 + total_operation_time = .0 # 操作用时 + total_nozzle_change_counter = 0 # 总吸嘴更换次数 + total_pick_counter = 0 # 总拾取次数 + total_mount_distance, total_pick_distance = .0, .0 # 贴装距离、拾取距离 + total_distance = 0 # 总移动距离 + cur_pos, next_pos = anc_marker_pos, [0, 0] # 贴装头当前位置 + + # 初始化首个周期的吸嘴装配信息 + nozzle_assigned = ['Empty' for _ in range(max_head_index)] + for head in range(max_head_index): + for cycle in range(len(component_result)): + idx = component_result[cycle][head] + if idx == -1: + continue + else: + nozzle_assigned[head] = component_data.loc[idx]['nz'] + break + + for cycle_set, _ in enumerate(component_result): + floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)]) + for cycle in range(floor_cycle, ceil_cycle): + pick_slot, mount_pos, mount_angle = [], [], [] + nozzle_pick_counter, nozzle_put_counter = 0, 0 # 吸嘴更换次数统计(拾取/放置分别算一次) + for head in range(max_head_index): + if feeder_slot_result[cycle_set][head] != -1: + pick_slot.append(feeder_slot_result[cycle_set][head] - interval_ratio * head) + if component_result[cycle_set][head] == -1: + continue + nozzle = component_data.loc[component_result[cycle_set][head]]['nz'] + if nozzle != nozzle_assigned[head]: + if nozzle_assigned[head] != 'Empty': + nozzle_put_counter += 1 + nozzle_pick_counter += 1 + nozzle_assigned[head] = nozzle + + # ANC处进行吸嘴更换 + if nozzle_pick_counter + nozzle_put_counter > 0: + next_pos = anc_marker_pos + total_moving_time += max(axis_moving_time(cur_pos[0] - next_pos[0], 0), + axis_moving_time(cur_pos[1] - next_pos[1], 1)) + total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1])) + cur_pos = next_pos + + pick_slot = list(set(pick_slot)) + pick_slot = sorted(pick_slot, reverse=True) + + # 拾取路径(自右向左) + for slot in pick_slot: + if slot < max_slot_index // 2: + next_pos = [slotf1_pos[0] + slot_interval * (slot - 1), slotf1_pos[1]] + else: + next_pos = [slotr1_pos[0] - slot_interval * (max_slot_index - slot - 1), slotr1_pos[1]] + total_operation_time += t_pick + total_pick_counter += 1 + total_moving_time += max(axis_moving_time(cur_pos[0] - next_pos[0], 0), + axis_moving_time(cur_pos[1] - next_pos[1], 1)) + total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1])) + if slot != pick_slot[0]: + total_pick_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1])) + cur_pos = next_pos + + # 固定相机检测 + for head in range(max_head_index): + if component_result[cycle_set][head] == -1: + continue + camera = component_data.loc[component_result[cycle_set][head]]['camera'] + if camera == '固定相机': + next_pos = [fix_camera_pos[0] - head * head_interval, fix_camera_pos[1]] + total_moving_time += max(axis_moving_time(cur_pos[0] - next_pos[0], 0), + axis_moving_time(cur_pos[1] - next_pos[1], 1)) + total_distance += max(abs(cur_pos[0] - next_pos[0]), abs(cur_pos[1] - next_pos[1])) + total_operation_time += t_fix_camera_check + cur_pos = next_pos + + # 贴装路径 + for head in head_sequence[cycle]: + index = placement_result[cycle][head] + if index == -1: + continue + mount_pos.append([pcb_data.loc[index]['x'] - head * head_interval + stopper_pos[0], + pcb_data.loc[index]['y'] + stopper_pos[1]]) + mount_angle.append(pcb_data.loc[index]['r']) + + # 单独计算贴装路径 + for cntPoints in range(len(mount_pos) - 1): + total_mount_distance += max(abs(mount_pos[cntPoints][0] - mount_pos[cntPoints + 1][0]), + abs(mount_pos[cntPoints][1] - mount_pos[cntPoints + 1][1])) + + # 考虑R轴预旋转,补偿同轴角度转动带来的额外贴装用时 + total_operation_time += head_rotary_time(mount_angle[0]) # 补偿角度转动带来的额外贴装用时 + total_operation_time += t_nozzle_put * nozzle_put_counter + t_nozzle_pick * nozzle_pick_counter + for pos in mount_pos: + total_operation_time += t_place + total_moving_time += max(axis_moving_time(cur_pos[0] - pos[0], 0), + axis_moving_time(cur_pos[1] - pos[1], 1)) + total_distance += max(abs(cur_pos[0] - pos[0]), abs(cur_pos[1] - pos[1])) + cur_pos = pos + + total_nozzle_change_counter += nozzle_put_counter + nozzle_pick_counter + + total_time = total_moving_time + total_operation_time + minutes, seconds = int(total_time // 60), int(total_time) % 60 + millisecond = int((total_time - minutes * 60 - seconds) * 60) + + if hinter: + optimization_assign_result(component_data, pcb_data, component_result, cycle_result, feeder_slot_result, + nozzle_hinter=True, component_hinter=True, feeder_hinter=True) + + print('-Cycle counter: {}'.format(sum(cycle_result))) + print('-Nozzle change counter: {}'.format(total_nozzle_change_counter // 2)) + print('-Pick operation counter: {}'.format(total_pick_counter)) + + print('-Expected mounting tour length: {} mm'.format(total_mount_distance)) + print('-Expected picking tour length: {} mm'.format(total_pick_distance)) + print('-Expected total tour length: {} mm'.format(total_distance)) + + print('-Expected total moving time: {} s'.format(total_moving_time)) + print('-Expected total operation time: {} s'.format(total_operation_time)) + + if minutes > 0: + print('-Mounting time estimation: {:d} min {} s {:2d} ms ({:.3f}s)'.format(minutes, seconds, millisecond, + total_time)) + else: + print('-Mounting time estimation: {} s {:2d} ms ({:.3f}s)'.format(seconds, millisecond, total_time)) + + return total_time + + + +