class ACO(object): def __init__(self, bins, items, population, evaporation, limit=10000): self.bins = bins self.items = items self.ants = [] for i in range(population): self.ants.append(Ant()) self.graph = Graph(len(bins), len(items)) self.evaporation = evaporation self.limit = limit self.numOfEvaluations = 0 self.ran = False self.runtime = 0 self.bestRun = None self.avgFitness = [] def run(self): """Runs a full ACO run.""" self.ran = False self.bestFits = [] self.avgFitness = [] startTime = time() while self.numOfEvaluations < self.limit: self.explore() for ant in self.ants: if self.bestRun and ant.fitness < self.bestRun.fitness: self.bestRun = ant.copy() elif not self.bestRun: self.bestRun = ant.copy() self.ran = True self.runtime = time() - startTime def explore(self): """Create a route for all ants and evaporate the graph.""" self.ants = [*map(self.createPath, self.ants)] best = None for ant in self.ants: ant.distributePheromones(self.graph) fitnesses = [ant.fitness for ant in self.ants] self.bestFits.append(min(fitnesses) / sum(self.items)) self.avgFitness.append(sum(fitnesses) / len(fitnesses)) self.graph.evaporate(self.evaporation) def createPath(self, ant): """Reset the bins and create a route for the given ant. :param ant: ant object :returns: ant with new path """ for b in self.bins: b.empty() currentBin = 0 ant.route = [] for item in enumerate(self.items): currentBin, item = self.nextBin(currentBin, item) ant.route.append((currentBin, item)) ant.fitness = self.getCurrentFitness() ant.bins = self.bins.copy() self.numOfEvaluations += 1 return ant def nextBin(self, currentBin, item): """Get the index of the next bin to place the item in. :param currentBin: index of the current bin :param item: item weight :returns: next bin index """ column = self.graph.graph[currentBin][item[0]].tolist() total = sum(column) threshold = total * random() current = 0.0 for index, weight in enumerate(column): if current + weight >= threshold: self.bins[index].addItem(item[1]) return index, item[0] current += weight def getCurrentFitness(self): """Calculate the fitness of the current bin configuration. :returns: current fitness """ maxWeight = self.bins[0].totalWeight minWeight = self.bins[0].totalWeight for b in self.bins: if b.totalWeight > maxWeight: maxWeight = b.totalWeight if b.totalWeight < minWeight: minWeight = b.totalWeight return maxWeight - minWeight