class TestPopulation(unittest.TestCase): def setUp(self): turns = ['F', 'L', 'L', 'F', 'R', 'F', 'R'] values = ['P', 'P', 'H', 'P', 'H', 'H', 'P'] self.turnList1 = TurnList(turns, values) turns2 = ['R', 'L', 'L', 'F', 'L', 'F', 'R'] values2 = ['H', 'P', 'H', 'P', 'H', 'H', 'P'] self.turnList2 = TurnList(turns2, values2) turns3 = ['F', 'L', 'L', 'F', 'R', 'F', 'R'] values3 = ['P', 'P', 'H', 'P', 'H', 'H', 'P'] self.turnList3 = TurnList(turns3, values3) bigValues1 = list("PPHPPHHPPHHPPPPPHHHHHHHHHHPPPPPPHHPPHHPPHPPHHHHH") bigTurns1 = list("FRFRLFLFRFLFFRLFLRRLLRRFRFLFRLLRFRLFLFRLFFFFRLLF") self.bigTL1 = TurnList(bigTurns1, bigValues1) bigValues2 = list("PPHPPHHPPHHPPPPPHHHHHHHHHHPPPPPPHHPPHHPPHPPHHHHH") bigTurns2 = list("FRFRLFLFRFLFFRLFLRRLLRRFRFLFRLLRFRLFLFRLFFFFRLLF") self.bigTL2 = TurnList(bigTurns2, bigValues2) self.pop1 = Population([self.turnList1], [self.turnList1.calculateFitness()]) self.pop2 = Population([self.turnList2], [self.turnList2.calculateFitness()]) self.pop3 = Population([self.turnList3], [self.turnList3.calculateFitness()]) self.bigPop1 = Population([self.bigTL1], [self.bigTL1.calculateFitness()]) self.bigPop2 = Population([self.bigTL2], [self.bigTL2.calculateFitness()]) self.emptyPopulation = Population() def testAppendGene(self): self.pop1.appendGene(self.turnList2) self.assertEquals(self.pop1.getGene(1), self.turnList2) self.pop2.appendGene(self.turnList1) self.assertEquals(self.pop2.getGene(1), self.turnList1) self.pop2.appendGene(self.turnList1) def testAppendPopulation(self): self.pop1.appendPopulation(self.pop2) self.assertEquals(self.pop1.getGene(1), self.turnList2) self.assertTrue(self.pop1.size() == 2) self.pop2.appendPopulation(self.pop1) self.assertEquals(self.pop2.getGene(1), self.turnList1) self.assertEquals(self.pop2.getGene(2), self.turnList2) self.assertTrue(self.pop2.size() == 3) # Since these are pass-by-reference, updates to one member of the population # can result in updates to different members of the population. self.pop2.appendPopulation(self.pop2) self.pop2.getGene(2).setTurn(0, 'L') def testGetGene(self): self.assertEquals(self.pop1.getGene(0), self.turnList1) self.assertEquals(self.pop2.getGene(0), self.turnList2) def testGetFitness(self): self.assertEquals(self.pop1.getFitness(0), self.turnList1.calculateFitness()) self.assertEquals(self.pop2.getFitness(0), self.turnList2.calculateFitness()) def testSize(self): self.assertEquals(self.pop1.size(), 1) self.assertEquals(self.pop2.size(), 1) def testMutatatePopulationWithIncreaseAndCooling(self): print "Ensure advanced mutation algorithm is increase fitness faster than simple mutations." print "{:10s} {:10s}".format("Normal", "Advanced") for gen in range(100): self.bigPop1.mutatePopulation(1) self.bigPop2.mutatePopulationWithIncreaseAndCooling(1, gen) print "{:10d} {:10d}".format(self.bigPop1.getFitness(0), self.bigPop2.getFitness(0)) self.assertTrue( self.bigPop1.getFitness(0) <= self.bigPop2.getFitness(0))
class GeneticAlgorithm: """ An iterator that implements the genetic algorithm on a population of TurnList genes. """ def __init__(self, sequenceOfValues, populationSize=100, generations=200, elitePercent=.10, crossoverPercent=.80, mutationPercent=.25): self.sequenceOfValues = sequenceOfValues # Values self.geneLength = len(sequenceOfValues) # Length of a gene self.populationSize = populationSize # Size of the Population self.generations = generations # Number of generations to run. self.elitePercent = elitePercent # Percent of genes to preserve as elite. self.crossoverPercent = crossoverPercent # Percent of genes to crossover. self.mutationPercent = mutationPercent # Percent of gene turns to mutate. self.currentPopulation = Population( genes=[], fitness=[]) # Stores the current Population self.newPopulation = Population( genes=[], fitness=[] ) # Temporary Population used to construct next new population. self.elites = Population( genes=[], fitness=[] ) # The elites set aside while constructing the new population. self.nonElites = Population( genes=[], fitness=[] ) # The non-elites identified from the previous generation. # Crossed and Uncrossed have references to genes # from the previous generations - but they will be in their # mutated form. Use these Population sets for access to # crossedover indexes and locations. self.crossed = Population( genes=[], fitness=[] ) # The genes which have been crossed over for the new generation. self.uncrossed = Population( genes=[], fitness=[] ) # The genes which where not crossed for the new generation. # Mutations is the final form of the current generation, minus the # elites. Use for the information about mutation locations. self.mutations = Population( genes=[], fitness=[] ) # The crossed and uncrossed genes combined for mutation. self.history = [] def __iter__(self): return self def next(self): """ Performs the genetic algorithm on the previous population to construct a new population. @return: int the current generation. """ generation = len(self.history) # If this is the first iteration of the genetic algorithm, # a new, random population needs to be created. if len(self.history) == 0: self.initializeOriginalPopulation() self.history.append(self.currentPopulation.getGene(0)) return generation # For >1 generations, perform the following steps: elif len(self.history) < self.generations: # Prepares an empty new population. self.newPopulation = Population(genes=[], fitness=[]) # 1) Identify the elites. self.pullElites() # 2) Perform crossover on the non-elites. self.createCrossover() # 3) Perform mutations on the non-elites. self.createMutations() # 4) Construct a new population from the elites and non-elites. self.createNewPopulation() # 5) Assign the new population to the current population. self.currentPopulation = self.newPopulation # Create a history of the highest scoring member of the # current population. self.history.append(self.currentPopulation.getGene(0)) return generation else: raise StopIteration def initializeOriginalPopulation(self): """ Creates an original population from the values provided to this GeneticAlgorithm. """ for __ in range(self.populationSize): valid = False while not valid: # 1) Produce a sequence of turns using the MonteCarlo algorithm. sequenceOfTurns = montecarlo.getDefaultMCSequence( self.geneLength) # 2) Create a TurnList from the sequence of turns and # their corresponding values. turnList = TurnList(sequenceOfTurns, self.sequenceOfValues) # 3) Determine if the sequence of turns is valid. if not turnList.toCoordinates().hasConflict(): self.currentPopulation.appendGene(turnList) valid = True def pullElites(self): """ Construct a set of elite members of the population and a set of non-elite members of the population. """ numElites = int(self.populationSize * self.elitePercent) self.elites, self.nonElites = self.currentPopulation.splitElite( numElites) def createCrossover(self): """ Apply Crossover to the non-elite members of the current population. NOTE: New instances of the genes are created. """ numCrossovers = int(self.crossoverPercent * self.populationSize / 2) #self.crossed, self.uncrossed = self.nonElites.createCrossoverPopulation(numCrossovers) self.crossed, self.uncrossed = self.nonElites.createCrossoverPopulationWithIncreaseAndCooling( numCrossovers, len(self.history)) def createMutations(self): """ Mutate members of the current population which have been set aside as non-elite. NOTE: crossed-over genes have their new instances mutated, however the non-crossed-over non-elites will have their original genes (those in the current population) effected. """ self.mutations = Population(genes=[], fitness=[]) self.mutations.appendPopulation(self.crossed) self.mutations.appendPopulation(self.uncrossed) numMutations = (self.mutationPercent * self.mutations.size() * len(self.mutations.getGene(0))) #self.mutations.mutatePopulation(numMutations) self.mutations.mutatePopulationWithIncreaseAndCooling( numMutations, len(self.history)) def createNewPopulation(self): """ Combines the elite genes of the current generation with those non-elite genes which have been crossed over and mutated. Stores them in a new population. """ self.newPopulation = Population(genes=[], fitness=[]) self.newPopulation.appendPopulation(self.elites) self.newPopulation.appendPopulation(self.mutations) def displayStateChanges(self): """ A visually useful manner of inspecting the current generation of this Genetic Algorithm. * The first row references the indexes of the parents from which the current gene has been crossed-over. * The second row provides indexes for the genes of the current generation. An "!" indicates the gene was produced from a crossover operation that did not complete successfully. A "x" indicates that the gene was the product of a crossover operation. * The following rows indicate the turn state of the gene at a particular cell. A turn surrounded by "<" and ">" indicates a cross-over location from the previous generation. An "!" indicates a mutation created this cell. * The final row indicates the fitness of the gene. A "*" indicates an elite gene that was carried over directly from the previous generation. """ I = (len(str(self.populationSize - 1)) * 2) + 1 if I < 5: I = 5 crossedGenes = self.crossed.crossedOverIndexes.keys() numElites = int(self.populationSize * self.elitePercent) results = "\n" # Identify each gene's cross-over parents (if they had any). for geneIndex in range(len(self.currentPopulation.genes)): if self.currentPopulation.genes[geneIndex] in crossedGenes: sourceIndexes = self.crossed.crossedOverIndexes[ self.currentPopulation.genes[geneIndex]] results += str( str(sourceIndexes[0] + numElites) + "x" + str(sourceIndexes[1] + numElites)).center(I) + "|" else: results += str(" " * I) + "|" results += '\n' # Identify the gene's current index, whether it was the product of a crossover # operation, and whether that crossover operation was "bad". for geneIndex in range(len(self.currentPopulation.genes)): badCross = False cross = False if self.currentPopulation.genes[ geneIndex] in self.nonElites.cheapCrosses: badCross = True if self.currentPopulation.genes[geneIndex] in crossedGenes: cross = True placed = "" if badCross: placed += "!" placed += str(geneIndex) if cross: placed += "x" results += placed.center(I) + "|" results += '\n' # A divider. for geneIndex in range(len(self.currentPopulation.genes)): results += str("-" * I) + "|" results += "\n" # Displays rows for the cell values of each gene, indicating # crossover points and mutations. for cellIndex in range(len(self.currentPopulation.genes[0])): for geneIndex in range(len(self.currentPopulation.genes)): currentGene = self.currentPopulation.genes[geneIndex] mutation = False crossed = False if (self.mutations.mutationLocations.has_key(currentGene) and cellIndex in self.mutations.mutationLocations[currentGene]): mutation = True if (currentGene in crossedGenes and self.crossed.crossedOverLocations[currentGene] == cellIndex): crossed = True placed = "" if crossed: placed += "<" placed += str( self.currentPopulation.genes[geneIndex].get(cellIndex)[0]) if mutation: placed += "!" if crossed: placed += ">" results += placed.center(I) + "|" results += '\n' # Another divider. for geneIndex in range(len(self.currentPopulation.genes)): results += str("-" * I) + "|" results += "\n" # Display the fitness value for each gene, indicating whether the gene was elite # and carried directly from the previous generation. for geneIndex in range(len(self.currentPopulation.genes)): if self.currentPopulation.genes[geneIndex] in self.elites.genes: results += str( str(self.currentPopulation.fitness[geneIndex]) + "*").center(I) + "|" else: results += str( self.currentPopulation.fitness[geneIndex]).center(I) + "|" return results + '\n'
class GeneticAlgorithm: """ An iterator that implements the genetic algorithm on a population of TurnList genes. """ def __init__(self, sequenceOfValues, populationSize=100, generations=200, elitePercent=.10, crossoverPercent=.80, mutationPercent=.25): self.sequenceOfValues = sequenceOfValues # Values self.geneLength = len(sequenceOfValues) # Length of a gene self.populationSize = populationSize # Size of the Population self.generations = generations # Number of generations to run. self.elitePercent = elitePercent # Percent of genes to preserve as elite. self.crossoverPercent = crossoverPercent # Percent of genes to crossover. self.mutationPercent = mutationPercent # Percent of gene turns to mutate. self.currentPopulation = Population(genes=[], fitness=[]) # Stores the current Population self.newPopulation = Population(genes=[], fitness=[]) # Temporary Population used to construct next new population. self.elites = Population(genes=[], fitness=[]) # The elites set aside while constructing the new population. self.nonElites = Population(genes=[], fitness=[]) # The non-elites identified from the previous generation. # Crossed and Uncrossed have references to genes # from the previous generations - but they will be in their # mutated form. Use these Population sets for access to # crossedover indexes and locations. self.crossed = Population(genes=[], fitness=[]) # The genes which have been crossed over for the new generation. self.uncrossed = Population(genes=[], fitness=[]) # The genes which where not crossed for the new generation. # Mutations is the final form of the current generation, minus the # elites. Use for the information about mutation locations. self.mutations = Population(genes=[], fitness=[]) # The crossed and uncrossed genes combined for mutation. self.history = [] def __iter__(self): return self def next(self): """ Performs the genetic algorithm on the previous population to construct a new population. @return: int the current generation. """ generation = len(self.history) # If this is the first iteration of the genetic algorithm, # a new, random population needs to be created. if len(self.history) == 0: self.initializeOriginalPopulation() self.history.append(self.currentPopulation.getGene(0)) return generation # For >1 generations, perform the following steps: elif len(self.history) < self.generations: # Prepares an empty new population. self.newPopulation = Population(genes=[], fitness=[]) # 1) Identify the elites. self.pullElites() # 2) Perform crossover on the non-elites. self.createCrossover() # 3) Perform mutations on the non-elites. self.createMutations() # 4) Construct a new population from the elites and non-elites. self.createNewPopulation() # 5) Assign the new population to the current population. self.currentPopulation = self.newPopulation # Create a history of the highest scoring member of the # current population. self.history.append(self.currentPopulation.getGene(0)) return generation else: raise StopIteration def initializeOriginalPopulation(self): """ Creates an original population from the values provided to this GeneticAlgorithm. """ for __ in range(self.populationSize): valid = False while not valid: # 1) Produce a sequence of turns using the MonteCarlo algorithm. sequenceOfTurns = montecarlo.getDefaultMCSequence(self.geneLength) # 2) Create a TurnList from the sequence of turns and # their corresponding values. turnList = TurnList(sequenceOfTurns, self.sequenceOfValues) # 3) Determine if the sequence of turns is valid. if not turnList.toCoordinates().hasConflict(): self.currentPopulation.appendGene(turnList) valid = True def pullElites(self): """ Construct a set of elite members of the population and a set of non-elite members of the population. """ numElites = int(self.populationSize * self.elitePercent) self.elites, self.nonElites = self.currentPopulation.splitElite(numElites) def createCrossover(self): """ Apply Crossover to the non-elite members of the current population. NOTE: New instances of the genes are created. """ numCrossovers = int(self.crossoverPercent * self.populationSize / 2 ) #self.crossed, self.uncrossed = self.nonElites.createCrossoverPopulation(numCrossovers) self.crossed, self.uncrossed = self.nonElites.createCrossoverPopulationWithIncreaseAndCooling(numCrossovers, len(self.history)) def createMutations(self): """ Mutate members of the current population which have been set aside as non-elite. NOTE: crossed-over genes have their new instances mutated, however the non-crossed-over non-elites will have their original genes (those in the current population) effected. """ self.mutations = Population(genes=[], fitness=[]) self.mutations.appendPopulation(self.crossed) self.mutations.appendPopulation(self.uncrossed) numMutations = (self.mutationPercent * self.mutations.size() * len(self.mutations.getGene(0))) #self.mutations.mutatePopulation(numMutations) self.mutations.mutatePopulationWithIncreaseAndCooling(numMutations, len(self.history)) def createNewPopulation(self): """ Combines the elite genes of the current generation with those non-elite genes which have been crossed over and mutated. Stores them in a new population. """ self.newPopulation = Population(genes=[], fitness=[]) self.newPopulation.appendPopulation(self.elites) self.newPopulation.appendPopulation(self.mutations) def displayStateChanges(self): """ A visually useful manner of inspecting the current generation of this Genetic Algorithm. * The first row references the indexes of the parents from which the current gene has been crossed-over. * The second row provides indexes for the genes of the current generation. An "!" indicates the gene was produced from a crossover operation that did not complete successfully. A "x" indicates that the gene was the product of a crossover operation. * The following rows indicate the turn state of the gene at a particular cell. A turn surrounded by "<" and ">" indicates a cross-over location from the previous generation. An "!" indicates a mutation created this cell. * The final row indicates the fitness of the gene. A "*" indicates an elite gene that was carried over directly from the previous generation. """ I = (len(str(self.populationSize-1)) * 2) + 1 if I < 5: I = 5 crossedGenes = self.crossed.crossedOverIndexes.keys() numElites = int(self.populationSize * self.elitePercent) results = "\n" # Identify each gene's cross-over parents (if they had any). for geneIndex in range(len(self.currentPopulation.genes)): if self.currentPopulation.genes[geneIndex] in crossedGenes: sourceIndexes = self.crossed.crossedOverIndexes[self.currentPopulation.genes[geneIndex]] results += str( str(sourceIndexes[0] + numElites) + "x" + str(sourceIndexes[1] + numElites) ).center(I) + "|" else: results += str(" " * I) + "|" results += '\n' # Identify the gene's current index, whether it was the product of a crossover # operation, and whether that crossover operation was "bad". for geneIndex in range(len(self.currentPopulation.genes)): badCross = False cross = False if self.currentPopulation.genes[geneIndex] in self.nonElites.cheapCrosses: badCross = True if self.currentPopulation.genes[geneIndex] in crossedGenes: cross = True placed = "" if badCross: placed += "!" placed += str(geneIndex) if cross: placed += "x" results += placed.center(I) + "|" results += '\n' # A divider. for geneIndex in range(len(self.currentPopulation.genes)): results += str("-" * I) + "|" results += "\n" # Displays rows for the cell values of each gene, indicating # crossover points and mutations. for cellIndex in range(len(self.currentPopulation.genes[0])): for geneIndex in range(len(self.currentPopulation.genes)): currentGene = self.currentPopulation.genes[geneIndex] mutation = False crossed = False if (self.mutations.mutationLocations.has_key(currentGene) and cellIndex in self.mutations.mutationLocations[currentGene]): mutation = True if (currentGene in crossedGenes and self.crossed.crossedOverLocations[currentGene] == cellIndex): crossed = True placed = "" if crossed: placed += "<" placed += str(self.currentPopulation.genes[geneIndex].get(cellIndex)[0]) if mutation: placed += "!" if crossed: placed += ">" results += placed.center(I) + "|" results += '\n' # Another divider. for geneIndex in range(len(self.currentPopulation.genes)): results += str("-" * I) + "|" results += "\n" # Display the fitness value for each gene, indicating whether the gene was elite # and carried directly from the previous generation. for geneIndex in range(len(self.currentPopulation.genes)): if self.currentPopulation.genes[geneIndex] in self.elites.genes: results += str(str(self.currentPopulation.fitness[geneIndex]) + "*").center(I) + "|" else: results += str(self.currentPopulation.fitness[geneIndex]).center(I) + "|" return results + '\n'