增加了HSMO整线优化方法,读取数据增加了供料器部分

This commit is contained in:
2025-08-10 16:58:42 +08:00
parent 045f2f394d
commit 4fd5560650
17 changed files with 1765 additions and 352 deletions

View File

@@ -291,6 +291,328 @@ def evolutionary_component_assignment(pcb_data, component_data, machine_number,
return min(pop_val), population[np.argmin(pop_val)]
class SpiderMonkeyOpt:
def __init__(self, pop_size, pcb_data, component_data, machine_number, estimator):
self.PcbData = pcb_data
self.ComponentData = component_data
self.Estimator = estimator
self.PopSize = pop_size
self.LocalLimit = pop_size
self.GlobalLimit = pop_size
self.MachineNum = machine_number
self.GroupSize = 0
# self.Dim = sum(data.fdn for _, data in component_data.iterrows()) + machine_number
self.CpPoints = defaultdict(int)
self.CpIndex = defaultdict(int)
self.CpNozzle = defaultdict(str)
self.Dim = 0
for cp_idx, data in component_data.iterrows():
# cp_feeders[cp_idx] = data.fdn
division_data = copy.deepcopy(data)
feeder_limit, total_points = division_data.fdn, division_data.points
surplus_points = total_points % feeder_limit
for _ in range(feeder_limit):
division_data.fdn, division_data.points = 1, math.floor(total_points / feeder_limit)
if surplus_points:
division_data.points += 1
surplus_points -= 1
self.CpPoints[self.Dim], self.CpNozzle[self.Dim] = division_data.points, division_data.nz
self.CpIndex[self.Dim] = cp_idx
self.Dim += 1
self.Dim += machine_number
component_list = list(range(len(self.CpPoints)))
self.GenPosition = []
self.GenPopVal = []
for _ in range(self.PopSize):
random.shuffle(component_list)
self.GenPosition.append(component_list.copy())
idx, prev = 0, 0
div = random.sample(range(len(component_list)), self.MachineNum - 1)
div.append(len(component_list))
div.sort(reverse=False)
for _ in range(1, self.MachineNum + 1):
self.GenPosition[-1].append(div[idx] - prev)
prev = div[idx]
idx += 1
self.GenPopVal.append(self.CalIndividualVal(self.GenPosition[-1]))
self.GroupPoint = [[0 for _ in range(2)] for _ in range(self.PopSize)]
self.GroupPart = 1
self.GenProb = None
self.GlobalMin = self.GenPopVal[0]
self.GlobalLeaderPosition = self.GenPosition[0].copy()
self.GlobalLimitCount = 0
self.LocalMin = np.ones(self.PopSize) * 1e10
self.LocalLeaderPosition = [[0 for _ in range(self.Dim)] for _ in range(self.PopSize)]
self.LocalLimitCount = [0 for _ in range(self.PopSize)]
for k in range(self.GroupSize):
self.LocalMin[k] = self.GenPopVal[int(self.GroupPoint[k, 0])]
self.LocalLeaderPosition[k,:] = self.GenPosition[int(self.GroupPoint[k, 0]),:]
self.CrossoverRatio = 0.1
def GlobalLearning(self):
GlobalTrial = self.GlobalMin
for i in range(self.PopSize):
if self.GenPopVal[i] < self.GlobalMin:
self.GlobalMin = self.GenPopVal[i]
self.GlobalLeaderPosition = self.GenPosition[i].copy()
if math.fabs(GlobalTrial - self.GlobalMin) < 1e-5:
self.GlobalLimitCount = self.GlobalLimitCount + 1
else:
self.GlobalLimitCount = 0
def LocalLearning(self):
OldMin = np.zeros(self.PopSize)
for k in range(self.GroupSize):
OldMin[k] = self.LocalMin[k]
for k in range(self.GroupSize):
i = int(self.GroupPoint[k][0])
while i <= int(self.GroupPoint[k][1]):
if self.GenPopVal[i] < self.LocalMin[k]:
self.LocalMin[k] = self.GenPopVal[i]
self.LocalLeaderPosition[k] = self.GenPosition[i].copy()
i = i + 1
for k in range(self.GroupSize):
if math.fabs(OldMin[k] - self.LocalMin[k]) < 1e-5:
self.LocalLimitCount[k] = self.LocalLimitCount[k] + 1
else:
self.LocalLimitCount[k] = 0
def CalculateProbabilities(self):
self.GenProb = [0 for _ in range(self.PopSize)]
MaxVal = self.GenPopVal[0]
i = 1
while i < self.PopSize:
if self.GenPopVal[i] > MaxVal:
MaxVal = self.GenPopVal[i]
i += 1
for i in range(self.PopSize):
self.GenProb[i] = (0.9 * (self.GenPopVal[i] / MaxVal)) + 0.1
def LocalLeaderPhase(self, k):
lo = int(self.GroupPoint[k][0])
hi = int(self.GroupPoint[k][1])
i = lo
while i <= hi:
NewGene1, NewGene2 = self.CrossoverOperation(self.GenPosition[i], self.LocalLeaderPosition[k])
NewGeneVal1, NewGeneVal2 = self.CalIndividualVal(NewGene1), self.CalIndividualVal(NewGene2)
if NewGeneVal1 < self.GenPopVal[i]:
self.GenPosition[i] = NewGene1
self.GenPopVal[i] = NewGeneVal1
if NewGeneVal2 < self.GenPopVal[i]:
self.GenPosition[i] = NewGene2
self.GenPopVal[i] = NewGeneVal2
i += 1
def GlobalLeaderPhase(self, k):
lo = int(self.GroupPoint[k][0])
hi = int(self.GroupPoint[k][1])
i = lo
l = lo
while l < hi:
if random.random() < self.GenProb[i]:
l += 1
NewGene1, NewGene2 = self.CrossoverOperation(self.GenPosition[i], self.GlobalLeaderPosition)
NewGeneVal1, NewGeneVal2 = self.CalIndividualVal(NewGene1), self.CalIndividualVal(NewGene2)
if NewGeneVal1 < self.GenPopVal[i]:
self.GenPosition[i] = NewGene1
self.GenPopVal[i] = NewGeneVal1
if NewGeneVal2 < self.GenPopVal[i]:
self.GenPosition[i] = NewGene2
self.GenPopVal[i] = NewGeneVal2
i += 1
if i == hi:
i = lo
def LocalLeaderDecision(self):
for k in range(self.GroupSize):
if self.LocalLimitCount[k] > self.LocalLimit:
i = self.GroupPoint[k][0]
while i <= int(self.GroupPoint[k][1]):
if random.random() >= self.CrossoverRatio:
NewGenPosition = list(range(self.Dim - self.MachineNum))
random.shuffle(NewGenPosition)
idx, prev = 0, 0
div = random.sample(range(len(NewGenPosition)), self.MachineNum - 1)
div.append(len(NewGenPosition))
div.sort(reverse=False)
for _ in range(1, self.MachineNum + 1):
NewGenPosition.append(div[idx] - prev)
prev = div[idx]
idx += 1
NewGenVal = self.CalIndividualVal(NewGenPosition)
if NewGenVal < self.GenPopVal[i]:
self.GenPosition[i] = NewGenPosition.copy()
self.GenPopVal[i] = NewGenVal
else:
NewGene1, NewGene2 = self.CrossoverOperation(self.GenPosition[i], self.GlobalLeaderPosition)
NewGeneVal1, NewGeneVal2 = self.CalIndividualVal(NewGene1), self.CalIndividualVal(NewGene2)
if NewGeneVal1 < self.GenPopVal[i]:
self.GenPosition[i] = NewGene1.copy()
self.GenPopVal[i] = NewGeneVal1
if NewGeneVal2 < self.GenPopVal[i]:
self.GenPosition[i] = NewGene2.copy()
self.GenPopVal[i] = NewGeneVal2
i += 1
self.LocalLimitCount[k] = 0
def GlobalLeaderDecision(self):
if self.GlobalLimitCount> self.GlobalLimit:
self.GroupPart += 1
self.GlobalLimitCount = 0
self.CreateGroup()
self.LocalLearning()
def CreateGroup(self):
g = 0
lo = 0
while lo < self.PopSize:
hi = lo + int(self.PopSize / self.GroupPart)
self.GroupPoint[g][0] = lo
self.GroupPoint[g][1] = hi
if self.PopSize - hi < int(self.PopSize / self.GroupPart):
self.GroupPoint[g][1] = (self.PopSize - 1)
g = g + 1
lo = hi + 1
self.GroupSize = g
def CrossoverOperation(self, gene1, gene2):
len_ = len(gene1)
sub1, sub2 = partially_mapped_crossover(gene1[0: len_ - self.MachineNum], gene2[0: len_ - self.MachineNum])
pos1, pos2 = random.randint(0, self.MachineNum - 1), random.randint(0, self.MachineNum - 1)
machine_assign1, machine_assign2 = gene1[len_ - self.MachineNum:], gene2[len_ - self.MachineNum:]
machine_assign1[pos1], machine_assign2[pos2] = machine_assign2[pos2], machine_assign1[pos1]
while sum(machine_assign1) != len_ - self.MachineNum:
machine_idx = random.randint(0, self.MachineNum - 1)
if machine_assign1[machine_idx] == 0:
continue
if sum(machine_assign1) > len_ - self.MachineNum:
machine_assign1[machine_idx] -= 1
else:
machine_assign1[machine_idx] += 1
while sum(machine_assign2) != len_ - self.MachineNum:
machine_idx = random.randint(0, self.MachineNum - 1)
if machine_assign2[machine_idx] == 0:
continue
if sum(machine_assign2) > len_ - self.MachineNum:
machine_assign2[machine_idx] -= 1
else:
machine_assign2[machine_idx] += 1
sub1.extend(machine_assign1)
sub2.extend(machine_assign2)
return sub1, sub2
def CalIndividualVal(self, gene):
ComponentNum = len(self.ComponentData)
assignment_result = [[0 for _ in range(ComponentNum)] for _ in range(self.MachineNum)]
idx, machine_index = 0, 0
for num in range(self.Dim - self.MachineNum, self.Dim):
for _ in range(gene[num]):
assignment_result[machine_index][self.CpIndex[gene[idx]]] += self.CpPoints[gene[idx]]
idx += 1
machine_index += 1
val = 0
cp_items = converter(self.PcbData, self.ComponentData, assignment_result)
for machine_index in range(self.MachineNum):
cp_points, cp_nozzle, board_width, board_height = cp_items[machine_index]
val = max(val, self.Estimator.predict(cp_points, cp_nozzle, board_width, board_height))
return val
def spider_monkey_component_assignment(pcb_data, component_data, machine_number, estimator):
population_size, iteration_number = 20, 50
smo = SpiderMonkeyOpt(population_size, pcb_data, component_data, machine_number, estimator)
# ========================== Calling: GlobalLearning() ======================== #
smo.GlobalLearning()
# ========================== Calling: create_group() ========================== #
smo.CreateGroup()
# ========================= Calling: LocalLearning() ========================== #
smo.LocalLearning()
# ================================= Looping ================================== #
with tqdm(total=iteration_number) as pbar:
pbar.set_description('spider monkey algorithm process for PCB assembly line balance')
for _ in range(iteration_number):
for k in range(smo.GroupSize):
# ==================== Calling: LocalLeaderPhase() =================== #
smo.LocalLeaderPhase(k)
# =================== Calling: CalculateProbabilities() ================== #
smo.CalculateProbabilities()
for k in range(smo.GroupSize):
# ==================== Calling: GlobalLeaderPhase() ================== #
smo.GlobalLeaderPhase(k)
# ======================= Calling: GlobalLearning() ====================== #
smo.GlobalLearning()
# ======================= Calling: LocalLearning() ======================= #
smo.LocalLearning()
# ================== Calling: LocalLeaderDecision() ====================== #
smo.LocalLeaderDecision()
# ===================== Calling: GlobalLeaderDecision() ================== #
smo.GlobalLeaderDecision()
pbar.update(1)
assignment_result = [[0 for _ in range(len(component_data))] for _ in range(machine_number)]
idx, machine_index = 0, 0
for num in range(smo.Dim - machine_number, smo.Dim):
for _ in range(smo.GlobalLeaderPosition[num]):
assignment_result[machine_index][smo.CpIndex[smo.GlobalLeaderPosition[idx]]] += \
smo.CpPoints[smo.GlobalLeaderPosition[idx]]
idx += 1
machine_index += 1
return smo.GlobalMin, assignment_result
@timer_wrapper
def line_optimizer_reconfiguration(component_data, pcb_data, machine_number):
# === assignment of heads to modules is omitted ===
@@ -303,9 +625,8 @@ def line_optimizer_reconfiguration(component_data, pcb_data, machine_number):
print('random component allocation algorithm process for PCB assembly line balance')
val, assignment = random_component_assignment(pcb_data, component_data, machine_number, estimator)
elif i == 1:
# brute force
# which is proved to be useless, since it only ran in reasonable time for the smaller test instances
continue
# spider monkey
val, assignment = spider_monkey_component_assignment(pcb_data, component_data, machine_number, estimator)
elif i == 2:
# local search
print('local search component allocation algorithm process for PCB assembly line balance')
@@ -314,7 +635,8 @@ def line_optimizer_reconfiguration(component_data, pcb_data, machine_number):
# evolutionary
val, assignment = evolutionary_component_assignment(pcb_data, component_data, machine_number, estimator)
else:
# greedy: unclear description
# brute force
# which is proved to be useless, since it only ran in reasonable time for the smaller test instances
continue
if optimal_val is None or val < optimal_val:
@@ -324,3 +646,5 @@ def line_optimizer_reconfiguration(component_data, pcb_data, machine_number):
raise Exception('no feasible solution! ')
return optimal_assignment