From 665062fdf1f166a6cf475e473e24bf68b615a0b0 Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Mon, 5 Oct 2020 20:46:25 -0400 Subject: [PATCH] Updated Implementation Framework Updated to cover changes made by Dan to Master regarding general design changes Also added remove_two_worst survivor selection method --- src/EasyGA.py | 69 ++++++++++++++----- src/crossover/__init__.py | 2 +- ...rossover_types.py => crossover_methods.py} | 2 +- src/crossover/test_examples.py | 0 src/fitness_function/__init__.py | 2 +- .../{examples.py => fitness_examples.py} | 3 +- src/fitness_function/test_examples.py | 12 ++++ src/initialization/__init__.py | 2 +- .../chromosome_structure/chromosome.py | 18 ++--- .../gene_function/gene_random.py | 22 ------ ...ion_types.py => initialization_methods.py} | 8 +-- .../population_structure/population.py | 32 ++++----- src/initialization/random_initialization.py | 31 --------- src/initialization/test_examples.py | 0 src/mutation/__init__.py | 2 +- ...{mutation_types.py => mutation_methods.py} | 2 +- src/mutation/test_examples.py | 0 src/run_testing.py | 3 +- src/selection/__init__.py | 2 +- ...election_types.py => selection_methods.py} | 29 ++++++-- src/selection/test_examples.py | 0 src/termination_point/__init__.py | 2 +- ...nation_types.py => termination_methods.py} | 16 ++--- src/termination_point/test_examples.py | 0 24 files changed, 133 insertions(+), 126 deletions(-) rename src/crossover/{crossover_types.py => crossover_methods.py} (98%) create mode 100644 src/crossover/test_examples.py rename src/fitness_function/{examples.py => fitness_examples.py} (93%) create mode 100644 src/fitness_function/test_examples.py delete mode 100644 src/initialization/gene_function/gene_random.py rename src/initialization/{initialization_types.py => initialization_methods.py} (93%) delete mode 100644 src/initialization/random_initialization.py create mode 100644 src/initialization/test_examples.py rename src/mutation/{mutation_types.py => mutation_methods.py} (98%) create mode 100644 src/mutation/test_examples.py rename src/selection/{selection_types.py => selection_methods.py} (79%) create mode 100644 src/selection/test_examples.py rename src/termination_point/{termination_types.py => termination_methods.py} (58%) create mode 100644 src/termination_point/test_examples.py diff --git a/src/EasyGA.py b/src/EasyGA.py index 9a08f05..4f27075 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -3,25 +3,29 @@ import random from initialization import Population as create_population from initialization import Chromosome as create_chromosome from initialization import Gene as create_gene -# Import example classes +# Structure Methods from fitness_function import Fitness_Examples -from initialization import Initialization_Types -from termination_point import Termination_Types -from selection import Selection_Types -from crossover import Crossover_Types -from mutation import Mutation_Types +from initialization import Initialization_Methods +from termination_point import Termination_Methods +# Population Methods +from selection import Selection_Methods +# Manipulation Methods +from mutation import Mutation_Methods +from crossover import Crossover_Methods class GA: def __init__(self): """Initialize the GA.""" # Initilization variables self.chromosome_length = 10 - self.population_size = 150 + self.population_size = 100 self.chromosome_impl = None self.gene_impl = None self.population = None # Termination varibles self.current_generation = 0 + self.generation_goal = 50 + self.current_fitness = 0 self.generation_goal = 100 self.fitness_goal = 3 @@ -32,19 +36,52 @@ class GA: self.update_fitness = True # Defualt EastGA implimentation structure - self.initialization_impl = Initialization_Types().random_initialization + self.initialization_impl = Initialization_Methods().random_initialization self.fitness_function_impl = Fitness_Examples().is_it_5 - self.mutation_impl = Mutation_Types().per_gene_mutation - self.parent_selection_impl = Selection_Types().Parent_Selection().Tournament().with_replacement - self.survivor_selection_impl = Selection_Types().Survivor_Selection().repeated_crossover - self.crossover_impl = Crossover_Types().single_point_crossover - self.termination_impl = Termination_Types().generation_based + # Selects which chromosomes should be automaticly moved to the next population + self.survivor_selection_impl = Selection_Methods().Survivor_Selection().remove_two_worst + # Methods for accomplishing parent-selection -> Crossover -> Mutation + self.parent_selection_impl = Selection_Methods().Parent_Selection().Tournament().with_replacement + self.crossover_impl = Crossover_Methods().single_point_crossover + self.mutation_impl = Mutation_Methods().per_gene_mutation + # The type of termination to impliment + self.termination_impl = Termination_Methods().generation_based + + def evolve_generation(self, number_of_generations = 1, consider_termination = True): + """Evolves the ga the specified number of generations.""" + while(number_of_generations > 0 and (consider_termination == False or self.termination_impl(self))): + # If its the first generation then initialize the population + if self.current_generation == 0: + self.initialize_population() + self.set_all_fitness(self.population.chromosome_list) + + self.parent_selection_impl(self) + next_population = self.crossover_impl(self) + next_population = self.survivor_selection_impl(self, next_population) + next_population.set_all_chromosomes(self.mutation_impl(self, next_population.get_all_chromosomes())) + + self.population = next_population + self.set_all_fitness(self.population.chromosome_list) + + number_of_generations -= 1 + self.current_generation += 1 + + def evolve(self): + """Runs the ga until the termination point has been satisfied.""" + # While the termination point hasnt been reached keep running + while(self.active()): + self.evolve_generation() + + def active(self): + """Returns if the ga should terminate base on the termination implimented""" + # Send termination_impl the whole ga class + return self.termination_impl(self) def initialize_population(self): """Initialize the population using the initialization implimentation that is currently set""" self.population = self.initialization_impl(self) - + def set_all_fitness(self,chromosome_set): """Will get and set the fitness of each chromosome in the population. If update_fitness is set then all fitness values are updated. @@ -69,7 +106,7 @@ class GA: # If its the first generation then initialize the population if self.current_generation == 0: self.initialize_population() - self.set_all_fitness(self.population.chromosomes) + self.set_all_fitness(self.population.chromosome_list) self.parent_selection_impl(self) next_population = self.crossover_impl(self) @@ -77,7 +114,7 @@ class GA: next_population.set_all_chromosomes(self.mutation_impl(self, next_population.get_all_chromosomes())) self.population = next_population - self.set_all_fitness(self.population.chromosomes) + self.set_all_fitness(self.population.chromosome_list) number_of_generations -= 1 self.current_generation += 1 diff --git a/src/crossover/__init__.py b/src/crossover/__init__.py index ecb3eae..417b9f4 100644 --- a/src/crossover/__init__.py +++ b/src/crossover/__init__.py @@ -1,2 +1,2 @@ # FROM (. means local) file_name IMPORT function_name -from .crossover_types import Crossover_Types +from .crossover_methods import Crossover_Methods diff --git a/src/crossover/crossover_types.py b/src/crossover/crossover_methods.py similarity index 98% rename from src/crossover/crossover_types.py rename to src/crossover/crossover_methods.py index b8ee22b..4e2b1d1 100644 --- a/src/crossover/crossover_types.py +++ b/src/crossover/crossover_methods.py @@ -2,7 +2,7 @@ import random from initialization.chromosome_structure.chromosome import Chromosome from initialization.population_structure.population import Population -class Crossover_Types: +class Crossover_Methods: """ Crossover explination goes here. Points - Defined as sections between the chromosomes genetic makeup diff --git a/src/crossover/test_examples.py b/src/crossover/test_examples.py new file mode 100644 index 0000000..e69de29 diff --git a/src/fitness_function/__init__.py b/src/fitness_function/__init__.py index ad85c09..c1736e7 100644 --- a/src/fitness_function/__init__.py +++ b/src/fitness_function/__init__.py @@ -1,2 +1,2 @@ # FROM (. means local) file_name IMPORT class name -from .examples import Fitness_Examples +from .fitness_examples import Fitness_Examples diff --git a/src/fitness_function/examples.py b/src/fitness_function/fitness_examples.py similarity index 93% rename from src/fitness_function/examples.py rename to src/fitness_function/fitness_examples.py index a63cba2..8e29245 100644 --- a/src/fitness_function/examples.py +++ b/src/fitness_function/fitness_examples.py @@ -1,13 +1,12 @@ class Fitness_Examples: """Fitness function examples used""" - def is_it_5(self, chromosome): """A very simple case test function - If the chromosomes gene value is a 5 add one to the chromosomes overall fitness value.""" # Overall fitness value fitness = 0 # For each gene in the chromosome - for gene in chromosome.genes: + for gene in chromosome.gene_list: # Check if its value = 5 if(gene.value == 5): # If its value is 5 then add one to diff --git a/src/fitness_function/test_examples.py b/src/fitness_function/test_examples.py new file mode 100644 index 0000000..1cfd756 --- /dev/null +++ b/src/fitness_function/test_examples.py @@ -0,0 +1,12 @@ +class test_fitness_funciton: + def get_fitness(self, chromosome): + # For every gene in chromosome + for i in range(len(chromosome.genes)): + # If the gene has a five then add one to the fitness + # Example -> Chromosome = [5],[2],[2],[5],[5] then fitness = 3 + if (chromosome.genes[i].get_value == 5): + # Add to the genes fitness + chromosome.genes[i].fitness += 1 + # Add to the chromosomes fitness + chromosome.fitness += 1 + return chromosome.fitness diff --git a/src/initialization/__init__.py b/src/initialization/__init__.py index be55db2..c293145 100644 --- a/src/initialization/__init__.py +++ b/src/initialization/__init__.py @@ -1,5 +1,5 @@ # FROM (. means local) file_name IMPORT function_name -from .initialization_types import Initialization_Types +from .initialization_methods import Initialization_Methods from .population_structure.population import Population from .chromosome_structure.chromosome import Chromosome from .gene_structure.gene import Gene diff --git a/src/initialization/chromosome_structure/chromosome.py b/src/initialization/chromosome_structure/chromosome.py index 420e107..3a1978c 100644 --- a/src/initialization/chromosome_structure/chromosome.py +++ b/src/initialization/chromosome_structure/chromosome.py @@ -2,37 +2,37 @@ class Chromosome: def __init__(self, genes = None): if genes is None: - self.genes = [] + self.gene_list = [] else: - self.genes = genes + self.gene_list = genes self.fitness = None self.selected = False def add_gene(self, gene, index = -1): if index == -1: - index = len(self.genes) - self.genes.insert(index, gene) + index = len(self.gene_list) + self.gene_list.insert(index, gene) def remove_gene(self, index): - del self.genes[index] + del self.gene_list[index] def get_genes(self): - return self.genes + return self.gene_list def get_fitness(self): return self.fitness def set_gene(self, gene, index): - self.genes[index] = gene + self.gene_list[index] = gene def set_genes(self, genes): - self.genes = genes + self.gene_list = genes def set_fitness(self, fitness): self.fitness = fitness def __repr__(self): output_str = '' - for gene in self.genes: + for gene in self.gene_list: output_str += gene.__repr__() return output_str diff --git a/src/initialization/gene_function/gene_random.py b/src/initialization/gene_function/gene_random.py deleted file mode 100644 index 82609d5..0000000 --- a/src/initialization/gene_function/gene_random.py +++ /dev/null @@ -1,22 +0,0 @@ -# Imported library -import random - -def check_values(low,high): - #Check to make sure its not less then zero - assert low > 0 , "The random gene low can not be less then zero" - # Check to make sure the high value is not - # lower than or equal to low and not 0. - assert high > low, "High value can not be smaller then low value" - assert high != 0, "High value can not be zero" - -def random_gene(gene_input, gene_input_type, gene_index): - created_gene = None - - if gene_input_type[gene_index] == "range": - created_gene = random.randint(gene_input[gene_index][0], gene_input[gene_index][1]) - elif gene_input_type[gene_index] == "domain": - created_gene = random.choice(gene_input[gene_index]) - elif gene_input_type[gene_index] == "float-range": - created_gene = random.uniform(gene_input[gene_index][0], gene_input[gene_index][1]) - - return created_gene diff --git a/src/initialization/initialization_types.py b/src/initialization/initialization_methods.py similarity index 93% rename from src/initialization/initialization_types.py rename to src/initialization/initialization_methods.py index 7ebf82b..5ede298 100644 --- a/src/initialization/initialization_types.py +++ b/src/initialization/initialization_methods.py @@ -3,12 +3,12 @@ from .population_structure.population import Population as create_population from .chromosome_structure.chromosome import Chromosome as create_chromosome from .gene_structure.gene import Gene as create_gene -class Initialization_Types: +class Initialization_Methods: """Initialization examples that are used as defaults and examples""" - + def random_initialization(self, ga): - """Takes the initialization inputs and choregraphs them to output the type of population - with the given parameters.""" + """Takes the initialization inputs and choregraphs them to output the type of population with the given parameters.""" + # Create the population object population = create_population() diff --git a/src/initialization/population_structure/population.py b/src/initialization/population_structure/population.py index ae7670a..0f414a9 100644 --- a/src/initialization/population_structure/population.py +++ b/src/initialization/population_structure/population.py @@ -3,9 +3,9 @@ class Population: # fitness = Empty; population = [chromosome, chromosome, etc.] def __init__(self, chromosomes = None): if chromosomes is None: - self.chromosomes = [] + self.chromosome_list = [] else: - self.chromosomes = chromosomes + self.chromosome_list = chromosomes self.fitness = None def get_closet_fitness(self,value): @@ -14,46 +14,38 @@ class Population: def add_chromosome(self, chromosome, index = -1): if index == -1: - index = len(self.chromosomes) - self.chromosomes.insert(index, chromosome) + index = len(self.chromosome_list) + self.chromosome_list.insert(index, chromosome) def remove_chromosome(self, index): - del self.chromosomes[index] + del self.chromosome_list[index] def get_all_chromosomes(self): """returns all chromosomes in the population""" - return self.chromosomes + return self.chromosome_list def get_fitness(self): return self.fitness def set_all_chromosomes(self, chromosomes): - self.chromosomes = chromosomes + self.chromosome_list = chromosomes def set_chromosome(self, chromosome, index = -1): if index == -1: index = len(self.chromosomes)-1 - self.chromosomes[index] = chromosome + self.chromosome_list[index] = chromosome def set_fitness(self, fitness): self.fitness = fitness def __repr__(self): for index in range(len(self.chromosomes)): - return f'{self.chromosomes[index]}' + return f'{self.chromosome_list[index]}' def print_all(self): # Ex .Current population # Chromosome 1 - [gene][gene][gene][.etc] / Chromosome fitness - # print("Current population:") - for index in range(len(self.chromosomes)): - print(f'Chromosome - {index} {self.chromosomes[index]}', end = "") - print(f' / Fitness = {self.chromosomes[index].fitness}') - - def generate_first_chromosomes(self, chromosome_count, chromosome_length, gene_lower_bound, gene_upper_bound): - #Creating the chromosomes with Genes of random size - for x in range(chromosome_count): - chromosome = Chromosome(chromosome_length) - for y in range(chromosome_length): - chromosome.gene_set[y] = Gene(random.randint(gene_lower_bound[y], gene_upper_bound[y])) - self.chromosome_set.append(chromosome) + for index in range(len(self.chromosome_list)): + print(f'Chromosome - {index} {self.chromosome_list[index]}', end = "") + print(f' / Fitness = {self.chromosome_list[index].fitness}') \ No newline at end of file diff --git a/src/initialization/random_initialization.py b/src/initialization/random_initialization.py deleted file mode 100644 index 7de1459..0000000 --- a/src/initialization/random_initialization.py +++ /dev/null @@ -1,31 +0,0 @@ -# Import the data structure -from .population_structure.population import population as create_population -from .chromosome_structure.chromosome import chromosome as create_chromosome -from .gene_structure.gene import gene as create_gene -from .gene_function.gene_random import random_gene as random_gene - -def random_initialization(chromosome_length,population_size,gene_function,gene_input,gene_input_type): - - if gene_function == random_gene: - # Create the population object - population = create_population() - # Fill the population with chromosomes - for i in range(population_size): - chromosome = create_chromosome() - #Fill the Chromosome with genes - for j in range(chromosome_length): - chromosome.add_gene(create_gene(gene_function(gene_input, gene_input_type, j))) - population.add_chromosome(chromosome) - return population - - else: #For user input gene-function, don't do anything with gene_input parameter - # Create the population object - population = create_population() - # Fill the population with chromosomes - for i in range(population_size): - chromosome = create_chromosome() - #Fill the Chromosome with genes - for j in range(chromosome_length): - chromosome.add_gene(create_gene(gene_function())) - population.add_chromosome(chromosome) - return population \ No newline at end of file diff --git a/src/initialization/test_examples.py b/src/initialization/test_examples.py new file mode 100644 index 0000000..e69de29 diff --git a/src/mutation/__init__.py b/src/mutation/__init__.py index 68ad730..c0fabea 100644 --- a/src/mutation/__init__.py +++ b/src/mutation/__init__.py @@ -1,2 +1,2 @@ # FROM (. means local) file_name IMPORT function_name -from .mutation_types import Mutation_Types +from .mutation_methods import Mutation_Methods diff --git a/src/mutation/mutation_types.py b/src/mutation/mutation_methods.py similarity index 98% rename from src/mutation/mutation_types.py rename to src/mutation/mutation_methods.py index 12b4b6e..99181ed 100644 --- a/src/mutation/mutation_types.py +++ b/src/mutation/mutation_methods.py @@ -1,6 +1,6 @@ import random -class Mutation_Types: +class Mutation_Methods: def __init__(self): pass diff --git a/src/mutation/test_examples.py b/src/mutation/test_examples.py new file mode 100644 index 0000000..e69de29 diff --git a/src/run_testing.py b/src/run_testing.py index cdc0a0f..390986b 100644 --- a/src/run_testing.py +++ b/src/run_testing.py @@ -5,8 +5,7 @@ import random # Create the Genetic algorithm ga = EasyGA.GA() -#def random_parent_selection(population): - #while () + ga.gene_impl = [random.randrange,1,100] diff --git a/src/selection/__init__.py b/src/selection/__init__.py index f341e49..9c90847 100644 --- a/src/selection/__init__.py +++ b/src/selection/__init__.py @@ -1,2 +1,2 @@ # FROM (. means local) file_name IMPORT function_name -from .selection_types import Selection_Types +from .selection_methods import Selection_Methods diff --git a/src/selection/selection_types.py b/src/selection/selection_methods.py similarity index 79% rename from src/selection/selection_types.py rename to src/selection/selection_methods.py index b8aca55..201d468 100644 --- a/src/selection/selection_types.py +++ b/src/selection/selection_methods.py @@ -4,7 +4,7 @@ from initialization.gene_structure.gene import Gene as create_gene from initialization.population_structure.population import Population from initialization.chromosome_structure.chromosome import Chromosome -class Selection_Types: +class Selection_Methods: """Selection is the process by which chromosomes are selected for crossover and eventually, influence the next generation of chromosomes.""" def __init__(self): pass @@ -68,8 +68,8 @@ class Selection_Types: break class Survivor_Selection: - def repeated_crossover(self, ga, next_population): - while len(next_population.chromosomes) < ga.population_size: + def repeated_crossover(self, ga, next_population): #Might be cheating? I don't know honestly - RG + while len(next_population.get_all_chromosomes()) < ga.population_size: crossover_pool = [] for i in range(ga.population_size): if ga.population.get_all_chromosomes()[i].selected: @@ -90,10 +90,31 @@ class Selection_Types: for i in range(len(chromosome_list)): next_population.add_chromosome(chromosome_list[i]) - if len(next_population.chromosomes) >= ga.population_size: + if len(next_population.get_all_chromosomes()) >= ga.population_size: break return next_population + def remove_two_worst(self, ga, next_population): + + #Bubble sorting by highest fitness + temp_population = ga.population + not_sorted_check = 0 + while (not_sorted_check != len(temp_population.get_all_chromosomes())): + not_sorted_check = 0 + for i in range(len(temp_population.get_all_chromosomes())): + if ((i + 1 < len(temp_population.get_all_chromosomes())) and (temp_population.get_all_chromosomes()[i + 1].fitness > temp_population.get_all_chromosomes()[i].fitness)): + temp = temp_population.get_all_chromosomes()[i] + temp_population.get_all_chromosomes()[i] = ga.population.get_all_chromosomes()[i + 1] + temp_population.get_all_chromosomes()[i + 1] = temp + else: + not_sorted_check += 1 + + iterator = 0 + while len(next_population.get_all_chromosomes()) < ga.population_size: + next_population.add_chromosome(temp_population.get_all_chromosomes()[iterator]) + iterator += 1 + return next_population + def roulette_selection(self, ga): """Roulette selection works based off of how strong the fitness is of the chromosomes in the population. The stronger the fitness the higher the probability diff --git a/src/selection/test_examples.py b/src/selection/test_examples.py new file mode 100644 index 0000000..e69de29 diff --git a/src/termination_point/__init__.py b/src/termination_point/__init__.py index b044846..9eb2097 100644 --- a/src/termination_point/__init__.py +++ b/src/termination_point/__init__.py @@ -1,2 +1,2 @@ # FROM (. means local) file_name IMPORT class name -from .termination_types import Termination_Types +from .termination_methods import Termination_Methods diff --git a/src/termination_point/termination_types.py b/src/termination_point/termination_methods.py similarity index 58% rename from src/termination_point/termination_types.py rename to src/termination_point/termination_methods.py index a6ebf10..8b8f9fe 100644 --- a/src/termination_point/termination_types.py +++ b/src/termination_point/termination_methods.py @@ -1,16 +1,16 @@ -class Termination_Types: +class Termination_Methods: """Example functions that can be used to terminate the the algorithms loop""" def fitness_based(self, ga): """Fitness based approach to terminate when the goal fitness has been reached""" - continue_status = True + status = True if(ga.current_fitness > ga.fitness_goal): - continue_status = False - return continue_status + status = False + return status def generation_based(self, ga): """Generation based approach to terminate when the goal generation has been reached""" - continue_status = True - if(ga.current_generation > ga.generation_goal-1): - continue_status = False - return continue_status + status = True + if(ga.current_generation > ga.generation_goal): + status = False + return status diff --git a/src/termination_point/test_examples.py b/src/termination_point/test_examples.py new file mode 100644 index 0000000..e69de29