diff --git a/src/EasyGA.py b/src/EasyGA.py index d3c620d..e52cd23 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -47,7 +47,11 @@ class GA(Attributes): while cond1() and cond3(): - # If its the first generation + # Create the initial population if necessary. + if self.population is None: + self.initialize_population() + + # If its the first generation, setup the database. if self.current_generation == 0: # Create the database here to allow the user to change the @@ -57,11 +61,7 @@ class GA(Attributes): # Add the current configuration to the config table self.database.insert_config(self) - # Create the initial population - if self.population is None: - self.initialize_population() - - # Otherwise evolve the population + # Otherwise evolve the population. else: self.parent_selection_impl(self) self.crossover_population_impl(self) @@ -107,7 +107,7 @@ class GA(Attributes): """ # Check each chromosome - for chromosome in self.population.get_chromosome_list(): + for chromosome in self.population: # Update fitness if needed or asked by the user if chromosome.fitness is None or self.update_fitness: diff --git a/src/crossover/crossover_methods.py b/src/crossover/crossover_methods.py index 3d9a57d..dcde3a6 100644 --- a/src/crossover/crossover_methods.py +++ b/src/crossover/crossover_methods.py @@ -1,6 +1,7 @@ import random def append_children_from_mating_pool(crossover_method): + """Appends the new chromosomes to the next population.""" return lambda ga:\ ga.population.append_children( [chromosome for chromosome in crossover_method(ga, ga.population.mating_pool)] @@ -8,17 +9,20 @@ def append_children_from_mating_pool(crossover_method): def genes_to_chromosome(crossover_method): + """Converts a collection of genes into a chromosome.""" return lambda ga, parent_1, parent_2:\ return ga.make_chromosome(crossover_method(ga, parent_1, parent_2)) def values_to_genes(crossover_method): + """Converts a collection of values into genes.""" return lambda ga, parent_1, parent_2:\ return (ga.make_gene(value) for value in crossover_method(ga, parent_1, parent_2)) class Crossover_Methods: + # Private method decorators, see above. def __append_children_from_mating_pool(crossover_method): return append_children_from_mating_pool(crossover_method) def __genes_to_chromosome(crossover_method): diff --git a/src/initialization/initialization_methods.py b/src/initialization/initialization_methods.py index 9fb4206..1dc9437 100644 --- a/src/initialization/initialization_methods.py +++ b/src/initialization/initialization_methods.py @@ -1,16 +1,20 @@ def chromosomes_to_population(initialize): + """Makes a population from chromosomes.""" return lambda ga: ga.make_population([initialize(ga) for _ in range(ga.population_size)]) def genes_to_chromosome(initialize): + """Converts a collection of genes to a chromosome.""" return lambda ga: ga.make_chromosome([genes for genes in initialize(ga)]) def value_to_gene(initialize): + """Converts a collection of values to genes.""" return lambda ga: (ga.make_gene(value) for value in initialize(ga)) class Initialization_Methods: """Initialization examples that are used as defaults and examples""" + # Private method decorators, see above. def __chromosomes_to_population(initialize): return chromosomes_to_population(initialize) def __genes_to_chromosome(initialize): @@ -23,10 +27,8 @@ class Initialization_Methods: @genes_to_chromosome @value_to_gene def random_initialization(ga): - """Takes the initialization inputs and - - return a new population - - filled with chromosomes - - filled with genes + """Takes the initialization inputs and returns a collection of values. + Method decorators convert them to a GA population object. """ # Using the chromosome_impl to set every index inside of the chromosome diff --git a/src/mutation/mutation_methods.py b/src/mutation/mutation_methods.py index b4b2453..72819a4 100644 --- a/src/mutation/mutation_methods.py +++ b/src/mutation/mutation_methods.py @@ -2,18 +2,18 @@ import random from math import ceil def loop_selections(selection_method): + """Runs the selection method until enough chromosomes are mutated.""" def helper(ga): - # Loop until enough mutations occur for n in range(ceil(len(ga.population)*ga.chromosome_mutation_rate)): selection_method(ga) return helper def loop_mutations(mutation_method): + """Runs the mutation method until enough genes are mutated.""" def helper(ga, old_chromosome): chromosome = ga.make_chromosome(list(old_chromosome)) - # Loops until enough mutations occur for n in range(ceil(len(chromosome)*ga.gene_mutation_rate)): mutation_method(ga, chromosome) @@ -23,6 +23,7 @@ def loop_mutations(mutation_method): class Mutation_Methods: + # Private method decorators, see above. def __loop_selections(selection_method): return loop_selections(selection_method) def __loop_mutations(mutation_method): @@ -32,10 +33,9 @@ class Mutation_Methods: class Population: """Methods for selecting chromosomes to mutate""" - @loop_selections def random_selection(ga): - """Selects random chromosomes""" + """Selects random chromosomes.""" index = random.randint(0, len(ga.population)-1) ga.population[index] = ga.mutation_individual_impl(ga, ga.population[index]) @@ -43,7 +43,7 @@ class Mutation_Methods: @loop_selections def random_selection_then_cross(ga): - """Selects random chromosomes and self-crosses with parent""" + """Selects random chromosomes and self-crosses with parent.""" index = random.randint(0, len(ga.population)-1) chromosome = ga.population[index] @@ -51,12 +51,11 @@ class Mutation_Methods: class Individual: - """Methods for mutating a single chromosome""" - + """Methods for mutating a single chromosome.""" @loop_mutations def individual_genes(ga, chromosome): - """Mutates a random gene in the chromosome and resets the fitness.""" + """Mutates a random gene in the chromosome.""" index = random.randint(0, len(chromosome)-1) # Using the chromosome_impl @@ -72,15 +71,15 @@ class Mutation_Methods: raise Exception("Did not specify any initialization constraints.") - class Permutation: - """Methods for mutating a chromosome - by changing the order of the genes.""" + class Permutation: + """Methods for mutating a chromosome + by changing the order of the genes.""" - @loop_mutations - def swap_genes(ga, chromosome): - """Mutates a random gene in the chromosome and resets the fitness.""" + @loop_mutations + def swap_genes(ga, chromosome): + """Swaps two random genes in the chromosome.""" - index_one = random.randint(0, len(chromosome)-1) - index_two = random.randint(0, len(chromosome)-1) + index_one = random.randint(0, len(chromosome)-1) + index_two = random.randint(0, len(chromosome)-1) - chromosome[index_one], chromosome[index_two] = chromosome[index_two], chromosome[index_one] + chromosome[index_one], chromosome[index_two] = chromosome[index_two], chromosome[index_one] diff --git a/src/parent_selection/parent_selection_methods.py b/src/parent_selection/parent_selection_methods.py index 495e886..21891b4 100644 --- a/src/parent_selection/parent_selection_methods.py +++ b/src/parent_selection/parent_selection_methods.py @@ -1,6 +1,10 @@ import random def check_selection_probability(selection_method): + """Raises an exception if the selection_probability + is not between 0 and 1. Otherwise runs the selection + method. + """ def helper(ga): if 0 < ga.selection_probability < 1: selection_method(ga) @@ -10,6 +14,10 @@ def check_selection_probability(selection_method): def check_positive_fitness(selection_method): + """Raises an exception if the population contains a + chromosome with negative fitness. Otherwise runs + the selection method. + """ def helper(ga): if ga.get_chromosome_fitness(0) > 0 and ga.get_chromosome_fitness(-1) >= 0: selection_method(ga) @@ -19,6 +27,9 @@ def check_positive_fitness(selection_method): def ensure_sorted(selection_method): + """Sorts the population by fitness + and then runs the selection method. + """ def helper(ga): ga.population.sort_by_best_fitness(ga) selection_method(ga) @@ -27,6 +38,7 @@ def ensure_sorted(selection_method): class Parent_Selection: + # Private method decorators, see above. def __check_selection_probability(selection_method): return check_selection_probability(selection_method) def __check_positive_fitness(selection_method): diff --git a/src/structure/chromosome.py b/src/structure/chromosome.py index 05624dd..9d82d59 100644 --- a/src/structure/chromosome.py +++ b/src/structure/chromosome.py @@ -56,43 +56,81 @@ class Chromosome: def __iter__(self): - """Returns an iterable of the gene list""" + """ + Allows the user to use + + iter(chromosome) + list(chromosome) == chromosome.gene_list + tuple(chromosome) + for gene in chromosome + + to loop through the chromosome. + """ return iter(self.gene_list) def __getitem__(self, index): - """Returns the indexed gene""" + """ + Allows the user to use + gene = chromosome[index] + to get the indexed gene. + """ return self.gene_list[index] def __setitem__(self, index, gene): - """Sets the indexed gene value""" + """ + Allows the user to use + chromosome[index] = gene + to set the indexed gene. + """ self.gene_list[index] = gene def __len__(self): - """Returns the number of genes in the chromosome""" + """ + Allows the user to use + size = len(chromosome) + to get the length of the chromosome. + """ return len(self.gene_list) def __contains__(self, searched_gene): - """Returns True if the chromosome contains the gene and False otherwise. - Ex. if chromosome in ga.population: ...""" - + """ + Allows the user to use + if gene in chromosome + to check if a gene is in the chromosome. + """ return (searched_gene in self.gene_list) def index_of(self, searched_gene): - """Returns the index of the gene in the current chromosome.""" - + """ + Allows the user to use + index = chromosome.index_of(gene) + to find the index of a gene in the chromosome. + Be sure to check if the chromosome contains the gene + first, or to catch an exception if the gene is not + in the chromosome. + """ return self.gene_list.index(searched_gene) def __repr__(self): - """Create a backend string of the chromosome. Ex '1, 2, 3'.""" + """ + Allows the user to use + repr(chromosome) + to get a backend representation of the chromosome. + """ return ', '.join(repr(gene) for gene in self) def __str__(self): - """Create a printable string of the chromosome. Ex '[1][2][3]'.""" + """ + Allows the user to use + str(chromosome) + print(chromosome) + to get a frontend representation of the chromosome. + """ return ''.join(str(gene) for gene in self) diff --git a/src/structure/gene.py b/src/structure/gene.py index ae0bcea..5a6600d 100644 --- a/src/structure/gene.py +++ b/src/structure/gene.py @@ -29,10 +29,19 @@ class Gene: def __repr__(self): - """Create a backend string of the chromosome. Ex '1'.""" + """ + Allows the user to use + repr(gene) + to get a backend representation of the gene. + """ return str(self.value) def __str__(self): - """Create a printable string of the chromosome. Ex '[1]'.""" + """ + Allows the user to use + str(gene) + print(gene) + to get a frontend representation of the gene. + """ return f'[{str(self.value)}]' diff --git a/src/structure/population.py b/src/structure/population.py index 5a52108..f5172e4 100644 --- a/src/structure/population.py +++ b/src/structure/population.py @@ -152,41 +152,75 @@ class Population: def __iter__(self): - """Returns an iterable of chromosomes""" + """ + Allows the user to use + + iter(population) + list(population) == population.chromosome_list + tuple(population) + for chromosome in population + + to loop through the population. + """ return iter(self.chromosome_list) def __getitem__(self, index): - """Returns the indexed chromosome""" + """ + Allows the user to use + chromosome = population[index] + to get the indexed chromosome. + """ return self.chromosome_list[index] def __setitem__(self, index, chromosome): - """Sets the indexed chromosome""" + """ + Allows the user to use + population[index] = chromosome + to set the indexed chromosome. + """ self.chromosome_list[index] = chromosome def __len__(self): - """Returns the number of chromosomes in the current population""" + """ + Allows the user to use + size = len(population) + to get the length of the population. + """ return len(self.chromosome_list) def __contains__(self, searched_chromosome): - """Returns True if the current population contains the chromosome and False otherwise. - Ex. if chromosome in ga.population: ...""" - + """ + Allows the user to use + if chromosome in population + to check if a chromosome is in the population. + """ return (searched_chromosome in self.chromosome_list) def index_of(self, searched_chromosome): - """Returns the index of the chromosome in the current population.""" - + """ + Allows the user to use + index = population.index_of(chromosome) + to find the index of a chromosome in the population. + Be sure to check if the population contains the chromosome + first, or to catch an exception if the chromosome is not + in the population. + """ return self.chromosome_list.index(searched_chromosome) def __repr__(self): - """Returns a backend string representation of the entire population""" - + """ + Allows the user to use + repr(population) + str(population) + print(population) + to get a backend representation of the population. + """ return ''.join( f'Chromosome - {index} {chromosome} ' + f'/ Fitness = {chromosome.fitness}\n' diff --git a/src/survivor_selection/survivor_selection_methods.py b/src/survivor_selection/survivor_selection_methods.py index fc5def3..d6b4d78 100644 --- a/src/survivor_selection/survivor_selection_methods.py +++ b/src/survivor_selection/survivor_selection_methods.py @@ -1,12 +1,14 @@ import random def append_to_next_population(survivor_method): + """Appends the selected chromosomes to the next population.""" return lambda ga: ga.population.append_children(survivor_method(ga)) class Survivor_Selection: """Survivor selection determines which individuals should be brought to the next generation""" + # Private method decorator, see above. def __append_to_next_population(survivor_method): return append_to_next_population(survivor_method) diff --git a/src/termination_point/termination_methods.py b/src/termination_point/termination_methods.py index 12e9a58..cf0ab0a 100644 --- a/src/termination_point/termination_methods.py +++ b/src/termination_point/termination_methods.py @@ -1,4 +1,6 @@ def add_by_fitness_goal(termination_impl): + """Adds termination by fitness goal to the method.""" + def helper(ga): # If fitness goal is set, check it. @@ -18,6 +20,8 @@ def add_by_fitness_goal(termination_impl): def add_by_generation_goal(termination_impl): + """Adds termination by generation goal to the method.""" + def helper(ga): # If generation goal is set, check it. @@ -30,6 +34,8 @@ def add_by_generation_goal(termination_impl): def add_by_tolerance_goal(termination_impl): + """Adds termination by tolerance goal to the method.""" + def helper(ga): # If tolerance is set, check it. @@ -50,6 +56,7 @@ def add_by_tolerance_goal(termination_impl): class Termination_Methods: """Example functions that can be used to terminate the the algorithms loop""" + # Private method decorators, see above. def __add_by_fitness_goal(termination_impl): return add_by_fitness_goal(termination_impl) def __add_by_generation_goal(termination_impl):