diff --git a/src/EasyGA.py b/src/EasyGA.py index e45a009..d202507 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -27,7 +27,7 @@ class GA: self.chromosome_length = 10 self.population_size = 10 self.chromosome_impl = None - self.gene_impl = [random.randint,1,10] + self.gene_impl = None self.population = None self.target_fitness_type = 'maximum' self.update_fitness = True @@ -68,10 +68,9 @@ class GA: def evolve_generation(self, number_of_generations = 1, consider_termination = True): """Evolves the ga the specified number of generations.""" - # Evolve the specified number of generations - # and if consider_termination flag is set then - # also check if termination conditions reached - while(number_of_generations > 0 and (not consider_termination or self.termination_impl(self))): + while(number_of_generations > 0 # Evolve the specified number of generations + and (not consider_termination # and if consider_termination flag is set + or self.termination_impl(self))): # then also check if termination conditions reached # If its the first generation then initialize the population if self.current_generation == 0: @@ -81,13 +80,13 @@ class GA: # Otherwise evolve the population else: - self.population.reset_mating_pool() self.set_all_fitness() self.population.sort_by_best_fitness(self) self.parent_selection_impl(self) - next_population = self.crossover_population_impl(self) - self.survivor_selection_impl(self, next_population) + self.crossover_population_impl(self) + self.survivor_selection_impl(self) self.mutation_population_impl(self) + self.population.update() number_of_generations -= 1 self.current_generation += 1 @@ -134,4 +133,6 @@ class GA: etc. """ - return sorted(chromosome_set, key = lambda chromosome: chromosome.get_fitness(), reverse = True) + return sorted(chromosome_set, # list to be sorted + key = lambda chromosome: chromosome.get_fitness(), # by fitness + reverse = True) # from highest to lowest fitness diff --git a/src/crossover/crossover_methods.py b/src/crossover/crossover_methods.py index 4ad0121..de63fea 100644 --- a/src/crossover/crossover_methods.py +++ b/src/crossover/crossover_methods.py @@ -12,7 +12,15 @@ class Crossover_Methods: """ mating_pool = ga.population.get_mating_pool() - return ga.make_population([ga.crossover_individual_impl(ga, mating_pool[index], mating_pool[index-1]) for index in range(len(mating_pool))]) + + for index in range(len(mating_pool)): # for each parent in the mating pool + ga.population.add_child( # add a child + ga.crossover_individual_impl( # by crossing + ga, # + mating_pool[index], # the parent and + mating_pool[index-1] # the previous parent + ) + ) def random_selection(ga): @@ -21,7 +29,15 @@ class Crossover_Methods: """ mating_pool = ga.population.get_mating_pool() - return ga.make_population([ga.crossover_individual_impl(ga, parent, random.choice(mating_pool)) for parent in mating_pool]) + + for parent in mating_pool: # for each parent in the mating pool + ga.population.add_child( # add a child + ga.crossover_individual_impl( # by crossing + ga, # + parent, # the parent and + random.choice(mating_pool) # a random parent + ) + ) class Individual: @@ -41,18 +57,27 @@ class Crossover_Methods: def uniform(ga, parent_one, parent_two): """Cross two parents by swapping all genes randomly""" - return ga.make_chromosome([ - random.choice([parent_one.get_gene(i), parent_two.get_gene(i)]) - for i in range(parent_one.size())]) + + return ga.make_chromosome([ # Make a new chromosome + random.choice([ # by selecting random genes from + parent_one.get_gene(i), # each parent + parent_two.get_gene(i) # + ]) # + for i in range(parent_one.size())]) # for each gene class Arithmetic: """Crossover methods for numerical genes""" def int_random(ga, parent_one, parent_two): """Cross two parents by taking a random integer value between each of the genes""" - return ga.make_chromosome([ - ga.make_gene(random.randint(*sorted([parent_one.get_gene(i).get_value(), parent_two.get_gene(i).get_value()]))) - for i in range(parent_one.size())]) + + return ga.make_chromosome([ # Make a new chromosome + ga.make_gene( # filled with new genes + random.randint(*sorted([ # by choosing random integers between + parent_one.get_gene(i).get_value(), # the parents' genes + parent_two.get_gene(i).get_value() # + ]))) # + for i in range(parent_one.size())]) # for each gene def int_weighted(ga, parent_one, parent_two): @@ -60,16 +85,26 @@ class Crossover_Methods: # the percentage of genes taken from the first gene weight = 0.25 - return ga.make_chromosome([ - ga.make_gene(int(weight*parent_one.get_gene(i).get_value()+(1-weight)*parent_two.get_gene(i).get_value())) - for i in range(parent_one.size())]) + + return ga.make_chromosome([ # Make a new chromosome + ga.make_gene(int( # filled with new integer genes + weight*parent_one.get_gene(i).get_value()+ # with weight% from parent one and + (1-weight)*parent_two.get_gene(i).get_value() # (100-weight)% from parent two + )) # + for i in range(parent_one.size())]) # for each gene def float_random(ga, parent_one, parent_two): """Cross two parents by taking a random numeric value between each of the genes""" - return ga.make_chromosome([ - ga.make_gene(random.uniform(parent_one.get_gene(i).get_value(), parent_two.get_gene(i).get_value())) - for i in range(parent_one.size())]) + + return ga.make_chromosome([ # Make a new chromosome + ga.make_gene( # filled with new genes + random.uniform( # by taking a random float between + parent_one.get_gene(i).get_value(), # the parents' genes + parent_two.get_gene(i).get_value() # + ) # + ) # + for i in range(parent_one.size())]) # for each gene def float_weighted(ga, parent_one, parent_two): @@ -77,6 +112,10 @@ class Crossover_Methods: # the percentage of genes taken from the first gene weight = 0.25 - return ga.make_chromosome([ - ga.make_gene(weight*parent_one.get_gene(i).get_value()+(1-weight)*parent_two.get_gene(i).get_value()) - for i in range(parent_one.size())]) + + return ga.make_chromosome([ # Make a new chromosome + ga.make_gene( # filled with new float genes + weight*parent_one.get_gene(i).get_value()+ # with weight% from parent one and + (1-weight)*parent_two.get_gene(i).get_value() # (100-weight)% from parent two + ) # + for i in range(parent_one.size())]) # for each gene diff --git a/src/initialization/initialization_methods.py b/src/initialization/initialization_methods.py index b41a2d9..590fee5 100644 --- a/src/initialization/initialization_methods.py +++ b/src/initialization/initialization_methods.py @@ -12,16 +12,15 @@ class Initialization_Methods: if ga.chromosome_impl != None: return ga.make_population([ ga.make_chromosome([ - ga.make_gene(ga.chromosome_impl(j)) - for j in range(ga.chromosome_length)]) + ga.make_gene(value) + for value in ga.chromosome_impl()]) for i in range(ga.population_size)]) # Using the gene_impl to set every gene to be the same elif ga.gene_impl != None: - function = ga.gene_impl[0] return ga.make_population([ ga.make_chromosome([ - ga.make_gene(function(*ga.gene_impl[1:])) + ga.make_gene(ga.gene_impl()) for j in range(ga.chromosome_length)]) for i in range(ga.population_size)]) diff --git a/src/mutation/mutation_methods.py b/src/mutation/mutation_methods.py index 8d49190..7b3f258 100644 --- a/src/mutation/mutation_methods.py +++ b/src/mutation/mutation_methods.py @@ -25,14 +25,13 @@ class Mutation_Methods: # Using the chromosome_impl to set every index inside of the chromosome if ga.chromosome_impl != None: return ga.make_chromosome([ - ga.make_gene(ga.chromosome_impl(j)) - for j in range(chromosome.size())]) + ga.make_gene(value) + for value in ga.chromosome_impl()]) # Using the gene_impl elif ga.gene_impl != None: - function = ga.gene_impl[0] return ga.make_chromosome([ - ga.make_gene(function(*ga.gene_impl[1:])) + ga.make_gene(ga.gene_impl()) for j in range(chromosome.size())]) # Exit because no gene creation method specified @@ -48,13 +47,12 @@ class Mutation_Methods: # Using the chromosome_impl if ga.chromosome_impl != None: index = random.randint(0, chromosome.size()-1) - chromosome.set_gene(ga.make_gene(ga.chromosome_impl(index)), index) + chromosome.set_gene(ga.make_gene(ga.chromosome_impl()[index]), index) # Using the gene_impl elif ga.gene_impl != None: - function = ga.gene_impl[0] index = random.randint(0, chromosome.size()-1) - chromosome.set_gene(ga.make_gene(function(*ga.gene_impl[1:])), index) + chromosome.set_gene(ga.make_gene(ga.gene_impl()), index) # Exit because no gene creation method specified else: diff --git a/src/parent_selection/parent_selection_methods.py b/src/parent_selection/parent_selection_methods.py index 371e231..bd17623 100644 --- a/src/parent_selection/parent_selection_methods.py +++ b/src/parent_selection/parent_selection_methods.py @@ -110,8 +110,10 @@ class Parent_Selection: print("Selection probability must be between 0 and 1 to select parents.") return + max_fitness = ga.population.get_chromosome(0).get_fitness() + # Error if the highest fitness is not positive - if ga.population.get_chromosome(0).get_fitness() <= 0: + if max_fitness <= 0: print("Error using stochastic roulette selection, best fitness must be positive.") print("Consider using tournament selection.") return @@ -123,5 +125,5 @@ class Parent_Selection: index = random.randint(0, ga.population.size()-1) # Probability of becoming a parent is fitness/max_fitness - if random.uniform(ga.selection_probability, 1) < ga.population.get_chromosome(index).get_fitness() / ga.population.get_chromosome(0).get_fitness(): + if random.uniform(ga.selection_probability, 1) < ga.population.get_chromosome(index).get_fitness()/max_fitness: ga.population.set_parent(index) diff --git a/src/run_testing.py b/src/run_testing.py index e595e00..d619f1b 100644 --- a/src/run_testing.py +++ b/src/run_testing.py @@ -5,7 +5,7 @@ import EasyGA ga = EasyGA.GA() ga.population_size = 25 ga.generation_goal = 100 -ga.gene_impl = [random.randint,0,10] +ga.gene_impl = lambda: random.randint(1, 10) ga.selection_probability = 0.5 ga.fitness_function_impl = EasyGA.Fitness_Examples.near_5 ga.parent_selection_impl = EasyGA.Parent_Selection.Roulette.stochastic_selection diff --git a/src/structure/population.py b/src/structure/population.py index dcc9f50..cd7e5be 100644 --- a/src/structure/population.py +++ b/src/structure/population.py @@ -10,6 +10,43 @@ class Population: self.fitness = None self.mating_pool = [] + self.next_population = [] + + + def update(self): + self.set_chromosome_list(self.next_population) + self.reset_mating_pool() + self.reset_next_population() + + + def remove_chromosome(self, index): + """Removes a chromosome from the indicated index from the population""" + del self.chromosome_list[index] + + + def remove_parent(self, index): + """Removes a parent from the indicated index from the mating pool""" + del self.mating_pool[index] + + + def remove_child(self, index): + """Removes a child from the indicated index from the next population""" + del self.next_population[index] + + + def reset_mating_pool(self): + """Clears the mating pool""" + self.mating_pool = [] + + + def reset_next_population(self): + """Clears the next population""" + self.next_population = [] + + + def append_children(self, chromosome_list): + """Appends a list of chromosomes to the next population""" + self.next_population += chromosome_list def sort_by_best_fitness(self, ga): @@ -22,6 +59,11 @@ class Population: return len(self.chromosome_list) + def total_children(self): + """Returns the size of the next population""" + return len(self.next_population) + + def get_closet_fitness(self,value): """Get the chomosome that has the closets fitness to the value defined""" pass @@ -39,19 +81,9 @@ class Population: self.mating_pool.append(chromosome) - def remove_chromosome(self, index): - """Removes a chromosome from the indicated index from the population""" - del self.chromosome_list[index] - - - def remove_parent(self, index): - """Removes a parent from the indicated index from the mating pool""" - del self.mating_pool[index] - - - def reset_mating_pool(self): - """Clears the mating pool""" - self.mating_pool = [] + def add_child(self, chromosome): + """Adds a chromosome to the next population""" + self.next_population.append(chromosome) def get_chromosome(self, index): @@ -64,6 +96,11 @@ class Population: return self.mating_pool[index] + def get_child(self, index): + """Returns the child at the given index in the next population""" + return self.next_population[index] + + def get_chromosome_list(self): """Returns all chromosomes in the population""" return self.chromosome_list @@ -74,16 +111,16 @@ class Population: return self.mating_pool + def get_next_population(self): + """Returns chromosomes in the next population""" + return self.next_population + + def get_fitness(self): """Returns the population's fitness""" return self.fitness - def set_parent(self, index): - """Sets the indexed chromosome from the population as a parent""" - self.add_parent(self.get_chromosome(index)) - - def set_chromosome_list(self, chromosome_list): """Sets the chromosome list""" self.chromosome_list = chromosome_list @@ -99,6 +136,11 @@ class Population: self.chromosome_list[index] = chromosome + def set_parent(self, index): + """Sets the indexed chromosome from the population as a parent""" + self.add_parent(self.get_chromosome(index)) + + def set_fitness(self, fitness): """Sets the fitness value of the population""" self.fitness = fitness diff --git a/src/survivor_selection/survivor_selection_methods.py b/src/survivor_selection/survivor_selection_methods.py index 284486a..958c0c4 100644 --- a/src/survivor_selection/survivor_selection_methods.py +++ b/src/survivor_selection/survivor_selection_methods.py @@ -3,25 +3,42 @@ import random class Survivor_Selection: """Survivor selection determines which individuals should be brought to the next generation""" - def fill_in_best(ga, next_population): + def fill_in_best(ga): """Fills in the next population with the best chromosomes from the last population""" - ga.population.set_chromosome_list(ga.population.get_chromosome_list()[:ga.population.size()-next_population.size()] + next_population.get_chromosome_list()) + # add in chromosomes starting from + # the first chromosome in the population + # until the next population is full + ga.population.append_children( + ga.population.get_chromosome_list()[:ga.population.size()-len(ga.population.next_population)] + ) - def fill_in_random(ga, next_population): + def fill_in_random(ga): """Fills in the next population with random chromosomes from the last population""" - ga.population.set_chromosome_list([ - random.choice(ga.population.get_chromosome_list()) - for n in range(ga.population.size()-next_population.size())] - + next_population.get_chromosome_list()) + ga.population.append_children([ # add in chromosomes + random.choice( # randomly + ga.population.get_chromosome_list() # from the population + ) # until the next population is full + for n in range(ga.population.size()-ga.population.total_children())]) - def fill_in_parents_then_random(ga, next_population): + def fill_in_parents_then_random(ga): """Fills in the next population with all parents followed by random chromosomes from the last population""" - ga.population.set_chromosome_list([ - random.choice(ga.population.get_chromosome_list()) - for n in range(ga.population.size()-len(ga.population.get_mating_pool())-next_population.size())] - + ga.population.get_mating_pool() + next_population.get_chromosome_list()) + ga.population.append_children([ # add in chromosomes + random.choice( # randomly + ga.population.get_chromosome_list() # from the population + ) # until the next population is full + for n in range(ga.population.size()-ga.population.total_children())]) + + + ga.population.append_children( # add in chromosomes + ga.population.get_mating_pool() # from the mating pool + ) # + ga.population.append_children([ # add in chromosomes + random.choice( # randomly + ga.population.get_chromosome_list() # from the population + ) # until the next population is full + for n in range(ga.population.size()-ga.population.total_children())])