def from_config(world_config): world = World() if 'tools' in world_config: for t in world_config['tools']: tool = Tool.from_config(t) world.tools_repo.register_tool(tool) if 'populations' in world_config: for p in world_config['populations']: population = Population.from_config(p) world.populations.append(population) if len(world.populations) == 0: for i in range(0, world_config['populations_count']): pop_id = Population.generate_id() size = random.randint(world_config['min_pop_size'], world_config['max_pop_size']) world.populations.append(Population(pop_id, size)) if len(world.tools_repo) == 0: min_adopting = 1 max_adopting = round(math.sqrt(len(world.populations))) for i in range(0, world_config['tools_count']): tool = Tool.generate_instance() world.tools_repo.register_tool(tool) adopting_pops = random.randint(min_adopting, max_adopting) adopters = random.sample(world.populations, adopting_pops) [pop.adopt_tool(tool.id) for pop in adopters] return world
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 __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 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 test_population_keeps_track_of_exploration_over_life_cycle(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() assert hasattr( self.pop, "explorationShortHistory" ), "population must keep track of its exploration history!"
def test_population_keeps_track_of_resources_over_life_cycle(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() assert hasattr( self.pop, "ecologyShortHistory" ), "population must keep track of its ecological history!"
def test_ecological_time_correctly_assessed(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() self.pop.lifeCycle() ngen = 5 assert self.pop.ecoTime == self.pop.routineSteps, "one life cycle should be {0} units of ecological time, not {1}".format( self.pop.routineSteps, self.pop.ecoTime) self.pop = Pop(par="test/test/parameters.txt") self.pop.create() for i in range(ngen): self.pop.lifeCycle() assert self.pop.ecoTime == self.pop.routineSteps * ngen, "{2} life cycles should be {0} units of ecological time, not {1}".format( self.pop.routineSteps * 10, self.pop.ecoTime, ngen)
def test_population_keeps_track_of_ecological_time(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() assert hasattr( self.pop, "ecoTime"), "population must keep track of its ecological time!"
def test_mutants_are_drawn_from_binomial(self, pseudorandom): pseudorandom(0) self.nIndividuals = 1000 self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create(n=self.nIndividuals) self.mutationRate = 0.2 self.mutantCount = 0 for ind in self.fakepop.individuals: ind.mutate(mutRate=self.mutationRate, mutStep=0.05) if ind.mutant: self.mutantCount += 1 stat1, pval1 = scistats.ttest_1samp( [1] * self.mutantCount + [0] * (self.nIndividuals - self.mutantCount), self.mutationRate) assert pval1 > 0.05, "T-test mean failed. Observed: {0}, Expected: {1}".format( self.mutantCount / self.nIndividuals, self.mutationRate) self.test = scistats.binom_test(self.mutantCount, self.nIndividuals, self.mutationRate, alternative="two-sided") assert self.test > 0.05, "Success rate = {0} when mutation rate = {1}".format( self.mutantCount / self.nIndividuals, self.mutationRate) gc.collect()
def test_population_creates_grid(self): self.pop = Pop("test/test/parameters.txt") self.pop.create(n=20) assert hasattr(self.pop, "grid") assert type(self.pop.grid) is Grid assert self.pop.grid.resources.shape == (self.pop.gridSize, self.pop.gridSize)
def test_resources_info_augmented_at_each_routine(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() for i in range(self.pop.routineSteps): self.pop.routine() assert self.pop.ecologyShortHistory.shape == ((self.pop.gridSize**2) * self.pop.routineSteps, 4)
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 test_exploration_info_augmented_at_each_routine(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.predation = 0 self.pop.create() for i in range(self.pop.routineSteps): self.pop.routine() assert self.pop.explorationShortHistory.shape == ( self.pop.nIndiv * self.pop.routineSteps, 4)
def test_resources_crash_when_too_large(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.initRes = 20 self.pop.growth = 100 self.pop.create() self.pop.lifeCycle() for cell in np.nditer(self.pop.grid.resources): assert cell <= self.pop.initRes, "resources too large, should have crashed"
def test_update_replaces_old_gen_with_new_gen(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() self.pop.routine() self.pop.reproduce() oldgen = self.pop.individuals self.pop.update() assert len(self.pop.individuals) == self.pop.nIndiv assert self.pop.individuals != oldgen
def test_population_can_gather_vs_survive(self): assert hasattr(Pop(), "gatherAndSurvive") assert callable(getattr(Pop(), "gatherAndSurvive")) self.pop = Pop("test/test/parameters.txt") self.pop.create() try: self.pop.gatherAndSurvive() except ValueError as e: assert False, "missing info: {0}".format(e)
def test_mutation_does_not_affect_phenotype_type(self): self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create(n=1) self.indiv = self.fakepop.individuals[0] self.phen = self.indiv.vigilance self.indiv.mutate(mutRate=1, mutStep=0.05) assert type(self.indiv.vigilance) is float gc.collect()
def test_fertility_returns_positive_float(self): self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create() for ind in range(len(self.fakepop.individuals)): indiv = self.fakepop.individuals[ind] setattr(indiv, "storage", 1 + 9 * ind) indiv.reproduce(fecundity=2) assert type(indiv.fertility) is float assert indiv.fertility >= 0
def test_population_routine_changes_resources_grid(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() resG = self.pop.grid.resources self.pop.routine() compareGrids = self.pop.grid.resources != resG assert compareGrids.all()
def test_lam_error_when_resources_too_big(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.initRes = 20000000000000000000 self.pop.create() try: self.pop.lifeCycle() except ValueError as e: assert str( e ) == 'lam value too large', "This program should fail at poisson random draw, not '{0}'".format( e)
def test_population_routine_changes_share_grid(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() shareG = self.pop.grid.share self.pop.routine() compareGrids = self.pop.grid.share == shareG assert compareGrids.all() == False
def test_update_returns_population_vigilance_info(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() self.pop.routine() self.pop.reproduce() self.pop.update() assert hasattr(self.pop, "vigilance") assert self.pop.vigilance is not None assert type(self.pop.vigilance) is float assert 0 <= self.pop.vigilance <= 1
def test_mutants_are_defined(self): self.pop = Pop("test/test/parameters.txt") self.pop.create(n=1) self.indiv = self.pop.individuals[0] self.indiv.mutate(mutRate=0.5, mutStep=0.5) assert hasattr( self.indiv, "mutant" ), "We don't know if our individual is a mutant because it doesn't have this attribute" assert type(self.indiv.mutant) is bool gc.collect()
def test_mutants_get_deviation_from_phenotype(self): self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create(n=1) self.indiv = self.fakepop.individuals[0] self.indiv.mutate(mutRate=1, mutStep=0.05) assert hasattr( self.indiv, "mutationDeviation" ), "Individual is a mutant: it needs to be set a deviation from phenotype" assert -1 < self.indiv.mutationDeviation < 1 gc.collect()
def test_population_exploration_gives_share_info(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() self.pop.explore() m = self.pop.gridSize assert hasattr(self.pop, "ncell") assert type(self.pop.ncell) is np.ndarray assert self.pop.ncell.shape == (m, m) assert hasattr(self.pop, "vcell") assert type(self.pop.vcell) is np.ndarray assert self.pop.vcell.shape == (m, m)
def test_deviation_function_returns_float(self): self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create(n=1) self.indiv = self.fakepop.individuals[0] self.v = self.indiv.vigilance for mutationBool in [True, False]: self.indiv.mutant = mutationBool self.indiv.deviate(mutStep=0.05) assert type(self.indiv.mutationDeviation) is float gc.collect()
def test_grid_gif_is_created(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() self.pop.launch(dev='on') self.filesListRootOut = os.listdir("./output") assert "grid_out.gif" in self.filesListRootOut, "no output grid gif created" os.remove("output/vigilance_out.txt") os.remove("output/vigilance_out.gif") os.remove('output/resources_out.txt') os.remove('output/exploration_out.txt') os.remove('output/grid_out.gif')
def test_population_has_plotting_option(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() try: self.pop.launch(dev='on') except TypeError as e: assert False, "allow for 'on' device to show plots" os.remove("output/vigilance_out.txt") os.remove("output/vigilance_out.gif") os.remove('output/resources_out.txt') os.remove('output/exploration_out.txt') os.remove('output/grid_out.gif')
def test_fertility_increases_with_storage(self): self.fakepop = Pop("test/test/parameters.txt") self.fakepop.create() fertility = 0 for ind in range(len(self.fakepop.individuals)): indiv = self.fakepop.individuals[ind] setattr(indiv, "storage", 1 + 9 * ind) indiv.reproduce(fecundity=2) assert indiv.fertility > fertility fertility = indiv.fertility
def test_gathering_gives_individuals_storage(self): self.pop = Pop(par="test/test/parameters.txt") self.pop.create() self.pop.grid.share = np.full([self.pop.gridSize, self.pop.gridSize], 0.8) for i in self.pop.individuals: i.vigilance = 0 self.pop.gatherAndSurvive() for i in self.pop.individuals: assert i.storage > 0
def test_only_live_individuals_gather_and_survive(self): self.pop = Pop("test/test/parameters.txt") self.pop.create() self.pop.explore() for ind in self.pop.individuals: ind.alive = False self.pop.gatherAndSurvive() for ind in self.pop.individuals: assert ind.alive == False assert ind.storage == 0
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 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
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'