From 5821e709a34bc4ca9ac4ebced55d4690904d0259 Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Fri, 25 Sep 2020 01:15:53 -0400 Subject: [PATCH 01/16] New Initialization Method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a test implementation of a potential new initialization method. A testing file - new_initialization_method_testing.py - is included to allow for quick testing. In summary here is are the major points: 1) Two new attributes of GA were created - gene_input and gene_input_type. gene_input holds the user's custom range(s)/domain(s) after it gets passed to the initialize() function. gene_input_type holds an array with the same length as the chromosomes that holds the input type of the user's gene_input on a gene-by-gene basis. It does this in the same exact way that index-dependent gene ranges/domains are handled. By making the gene_input_type array the same size as the chromosome, the elements can be paired very easily. The acceptable values for this are either "range" or "domain". With a range, any value between the two can be generated; with domain, only the two elements included can be selected from randomly. 2) As mentioned in change 1, the user now has to pass their range(s)/domain(s) to the initialize() function. 3) The package is capable of implicitly determining if a certain input from the user is a range or domain. Strings can only ever be a domain – if given an element that only includes integers, the program assumes range. 4) If the user wishes to use numbers only as a domain, they can specify this by directly interacting with the ga.gene_input_type (or through a setter function). 5) the initialize() function in the GA object determines the implicit range/domain assignments if the user doesn’t do so themselves. 6) The random_initialization() function is effectively the same, except there is now an if/else to determine if the user is using the built-in gene creation function or not. If they are, then pass the gene_input, gene_input_type, and current gene index as arguments to the gene function. If they are using their own function, random_initialization() functions exactly the same way as it does in the current master branch. 7) Based on all the settings mentioned above, the random_gene() function will create a value before passing it back to random_initialization(). --- src/EasyGA.py | 33 ++++++++++++++++- .../gene_creation/gene_random.py | 13 +++++++ .../gene_function/gene_random.py | 20 ++++++++-- src/initialization/random_initialization.py | 37 +++++++++++++------ src/new_initialization_method_testing.py | 18 +++++++++ 5 files changed, 105 insertions(+), 16 deletions(-) create mode 100644 src/initialization/gene_creation/gene_random.py create mode 100644 src/new_initialization_method_testing.py diff --git a/src/EasyGA.py b/src/EasyGA.py index ef9dff8..75b7986 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -19,6 +19,8 @@ class GA: self.mutation_rate = 0.03 # Defualt EastGA implimentation structure self.gene_function_impl = random_gene + self.gene_input = [] + self.gene_input_type = [] #What if user gives two numbers (i.e. [1,100]) but wants to pick between the two (domain)? # Set the GA Configuration self.initialization_impl = random_initialization #self.mutation_impl = PerGeneMutation(Mutation_rate) @@ -28,12 +30,39 @@ class GA: #self.evaluation_impl = TestEvaluation() - def initialize(self): + def initialize(self, gene_input): + self.gene_input = gene_input + + #assuming domain if string (strings can never be range) + if self.gene_input_type == []: + for x in range(len(self.gene_input)): + if (isinstance(self.gene_input[x], list)): + for y in range(len(self.gene_input[x])): + if isinstance(gene_input[x][y], str): + self.gene_input_type.append("domain") + break + elif y == (len(self.gene_input[x]) -1): + self.gene_input_type.append("range") + else: + if isinstance(gene_input[x], str): + self.gene_input_type.append("domain") + else: + if isinstance(gene_input[x], int): + self.gene_input[x] = [self.gene_input[x], self.gene_input[x]] + self.gene_input_type.append("range") + + #If length doesn't correspond to chromosome, update here + while len(self.gene_input_type) != self.chromosome_length: + self.gene_input_type.append(self.gene_input_type[len(self.gene_input_type)-1]) + # Create the first population self.population = self.initialization_impl( self.population_size, self.chromosome_length, - self.gene_function_impl) + self.gene_function_impl, + self.gene_input, + self.gene_input_type) + def evolve(): # If you just want to evolve through all generations diff --git a/src/initialization/gene_creation/gene_random.py b/src/initialization/gene_creation/gene_random.py new file mode 100644 index 0000000..dbcc874 --- /dev/null +++ b/src/initialization/gene_creation/gene_random.py @@ -0,0 +1,13 @@ +# 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(): + return random.randint(1,100) diff --git a/src/initialization/gene_function/gene_random.py b/src/initialization/gene_function/gene_random.py index dbcc874..d3e16dd 100644 --- a/src/initialization/gene_function/gene_random.py +++ b/src/initialization/gene_function/gene_random.py @@ -6,8 +6,22 @@ def check_values(low,high): 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 > low, "High value can not be smaller then low value" assert high != 0, "High value can not be zero" -def random_gene(): - return random.randint(1,100) +def random_gene(gene_input, gene_input_type, gene_index): + + created_gene = None + #Determining if single range/domain or index-dependent + if isinstance(gene_input[0], list): + 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]) + else: + if gene_input_type[gene_index] == "range": + created_gene = random.randint(gene_input[0], gene_input[1]) + elif gene_input_type[gene_index] == "domain": + created_gene = random.choice(gene_input) + + return created_gene diff --git a/src/initialization/random_initialization.py b/src/initialization/random_initialization.py index cdef3ef..72cfbfa 100644 --- a/src/initialization/random_initialization.py +++ b/src/initialization/random_initialization.py @@ -2,15 +2,30 @@ 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): - # 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 +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-1): + 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/new_initialization_method_testing.py b/src/new_initialization_method_testing.py new file mode 100644 index 0000000..8687dd0 --- /dev/null +++ b/src/new_initialization_method_testing.py @@ -0,0 +1,18 @@ +import EasyGA +import random + +# Create the Genetic algorithm +ga = EasyGA.GA() +test_range_two = [["left", "right"],[22,88],5,[22,"up"]] +ga.initialize(test_range_two) +ga.population.print_all() + + + + +#test_range_one = [1,100] +#test_domain_one = ["left", "right", "up", "down"] +#test_range_two = [[1,100],[0,1],[33,35],[5,6]] +#test_domain_two = [["left", "right"], ["up", "down"], ["left", "down"], ["down", "right"]] + +#for index-specific bounds, do list of lists i.e. test_range = [[1, 100], [1, 25], [5, 25]] \ No newline at end of file From 129925bbdd8f77103535fb6fa45f4e7a647959ba Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Fri, 25 Sep 2020 11:14:09 -0400 Subject: [PATCH 02/16] Cleaned up backend & user interaction with EasyGA In the initial commit, string inputs would implicitly be seen as domain, and all integer inputs would be seen as range. If the user wanted to assign any integer inputs as domain, they would have to call the entire gene_input_type, even if only to change a single element to domain. It has now been updated to where the user can specifically call the element they want to update. The testing file new_initialization_method_testing.py reflects this. --- src/EasyGA.py | 19 ++++++++++--------- src/new_initialization_method_testing.py | 5 +++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/EasyGA.py b/src/EasyGA.py index 75b7986..9f367b8 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -21,6 +21,9 @@ class GA: self.gene_function_impl = random_gene self.gene_input = [] self.gene_input_type = [] #What if user gives two numbers (i.e. [1,100]) but wants to pick between the two (domain)? + while len(self.gene_input_type) != self.chromosome_length: + self.gene_input_type.append(None) + # Set the GA Configuration self.initialization_impl = random_initialization #self.mutation_impl = PerGeneMutation(Mutation_rate) @@ -34,26 +37,24 @@ class GA: self.gene_input = gene_input #assuming domain if string (strings can never be range) - if self.gene_input_type == []: - for x in range(len(self.gene_input)): + for x in range(len(self.gene_input)): + if self.gene_input_type[x] == None: if (isinstance(self.gene_input[x], list)): for y in range(len(self.gene_input[x])): if isinstance(gene_input[x][y], str): - self.gene_input_type.append("domain") + self.gene_input_type[x] = "domain" break elif y == (len(self.gene_input[x]) -1): - self.gene_input_type.append("range") + self.gene_input_type[x] = "range" else: if isinstance(gene_input[x], str): - self.gene_input_type.append("domain") + self.gene_input_type[x] = "domain" else: if isinstance(gene_input[x], int): self.gene_input[x] = [self.gene_input[x], self.gene_input[x]] - self.gene_input_type.append("range") + self.gene_input_type[x] = "range" - #If length doesn't correspond to chromosome, update here - while len(self.gene_input_type) != self.chromosome_length: - self.gene_input_type.append(self.gene_input_type[len(self.gene_input_type)-1]) + # Create the first population self.population = self.initialization_impl( diff --git a/src/new_initialization_method_testing.py b/src/new_initialization_method_testing.py index 8687dd0..2cbe4d2 100644 --- a/src/new_initialization_method_testing.py +++ b/src/new_initialization_method_testing.py @@ -3,9 +3,10 @@ import random # Create the Genetic algorithm ga = EasyGA.GA() -test_range_two = [["left", "right"],[22,88],5,[22,"up"]] +test_range_two = [["left", "right"],[22,35],5,[22,"up"]] +ga.gene_input_type[1] = "domain" ga.initialize(test_range_two) -ga.population.print_all() +ga.population.print_all() From ed1b2bbe03a96acbec342ff24f8a8d45d5ae3d0c Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Fri, 25 Sep 2020 11:24:47 -0400 Subject: [PATCH 03/16] Updated gene input checks Updated the check of incoming data to ensure validity - if the user enters a single digit, say "5", it will automatically be converted to a list like [5,5]. This already worked before with range, but it now works with domain as well. --- src/EasyGA.py | 5 +++-- src/new_initialization_method_testing.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/EasyGA.py b/src/EasyGA.py index 9f367b8..f17d6ba 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -38,6 +38,9 @@ class GA: #assuming domain if string (strings can never be range) for x in range(len(self.gene_input)): + if isinstance(gene_input[x], int): + self.gene_input[x] = [self.gene_input[x], self.gene_input[x]] + if self.gene_input_type[x] == None: if (isinstance(self.gene_input[x], list)): for y in range(len(self.gene_input[x])): @@ -50,8 +53,6 @@ class GA: if isinstance(gene_input[x], str): self.gene_input_type[x] = "domain" else: - if isinstance(gene_input[x], int): - self.gene_input[x] = [self.gene_input[x], self.gene_input[x]] self.gene_input_type[x] = "range" diff --git a/src/new_initialization_method_testing.py b/src/new_initialization_method_testing.py index 2cbe4d2..8f4e3b7 100644 --- a/src/new_initialization_method_testing.py +++ b/src/new_initialization_method_testing.py @@ -4,7 +4,7 @@ import random # Create the Genetic algorithm ga = EasyGA.GA() test_range_two = [["left", "right"],[22,35],5,[22,"up"]] -ga.gene_input_type[1] = "domain" +ga.gene_input_type[2] = "domain" ga.initialize(test_range_two) ga.population.print_all() From 922d046b72f897d8615ef1fe3c7a39bb576a113c Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Fri, 25 Sep 2020 16:10:28 -0400 Subject: [PATCH 04/16] Code optimizations, float-range implementation Random gene initialization now supports float ranges (assumed by default if gene input includes float). Backend was also optimized and cleaned up greatly. --- src/EasyGA.py | 20 ++++++++----------- .../gene_function/gene_random.py | 17 ++++++---------- src/new_initialization_method_testing.py | 14 ++++++++++--- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/EasyGA.py b/src/EasyGA.py index f17d6ba..92baaac 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -38,21 +38,17 @@ class GA: #assuming domain if string (strings can never be range) for x in range(len(self.gene_input)): - if isinstance(gene_input[x], int): + if isinstance(gene_input[x], list) == False: self.gene_input[x] = [self.gene_input[x], self.gene_input[x]] - if self.gene_input_type[x] == None: - if (isinstance(self.gene_input[x], list)): - for y in range(len(self.gene_input[x])): - if isinstance(gene_input[x][y], str): - self.gene_input_type[x] = "domain" - break - elif y == (len(self.gene_input[x]) -1): - self.gene_input_type[x] = "range" - else: - if isinstance(gene_input[x], str): + if self.gene_input_type[x] == None: #If it hasn't been hard-set by the user + for y in range(len(self.gene_input[x])): + if isinstance(gene_input[x][y], str): self.gene_input_type[x] = "domain" - else: + break + elif isinstance(gene_input[x][y], float): + self.gene_input_type[x] = "float-range" + elif y == (len(self.gene_input[x]) -1 and self.gene_input_type[x] != "float-range"): self.gene_input_type[x] = "range" diff --git a/src/initialization/gene_function/gene_random.py b/src/initialization/gene_function/gene_random.py index d3e16dd..af2cdd1 100644 --- a/src/initialization/gene_function/gene_random.py +++ b/src/initialization/gene_function/gene_random.py @@ -10,18 +10,13 @@ def check_values(low,high): assert high != 0, "High value can not be zero" def random_gene(gene_input, gene_input_type, gene_index): - created_gene = None #Determining if single range/domain or index-dependent - if isinstance(gene_input[0], list): - 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]) - else: - if gene_input_type[gene_index] == "range": - created_gene = random.randint(gene_input[0], gene_input[1]) - elif gene_input_type[gene_index] == "domain": - created_gene = random.choice(gene_input) + 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/new_initialization_method_testing.py b/src/new_initialization_method_testing.py index 8f4e3b7..07b1a09 100644 --- a/src/new_initialization_method_testing.py +++ b/src/new_initialization_method_testing.py @@ -1,11 +1,19 @@ import EasyGA import random +#1. GA should take in range for gene input +#2. GA should take in index-dependent range for gene input +#3. GA should take in domain input +#4. GA should take in index-dependent domain for gene input +#5. GA should accept mix of range and domain for gene input + + # Create the Genetic algorithm ga = EasyGA.GA() -test_range_two = [["left", "right"],[22,35],5,[22,"up"]] -ga.gene_input_type[2] = "domain" -ga.initialize(test_range_two) +test_gene_input = [["left", "right"],[1,100],[5.0,10],[22,"up"]] +#ga.gene_input_type[1] = "domain" +#ga.gene_input_type[1] = "float-range" +ga.initialize(test_gene_input) ga.population.print_all() From 6aec9770b681fcc27a490d33146cf6272737fbc1 Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Fri, 25 Sep 2020 18:02:45 -0400 Subject: [PATCH 05/16] Further optimizations, error-checking, user-input conversions 1) The initialization now accepts "general" inputs that should apply to each gene. For example, rather than a gene input of [1,100] being interpreted to mean gene 1 hsould be 1 and gene 2 should be 100, it will apply a range of [1,100] to each gene. 2) The initialization now accepts "general" gene_input_types. For example, if the user had a set of index-dependent number values, they could just say ga.gene_input_type = "domain" and the package will propagate that across all genes in the chromosome. The user still has the option of defining the entire array or just defining a specific element if they so choose. For later commits, the general gene_input_type will have to be checked for validity; for example, a string can never be a range. 3) Fixed an issue in the ordering of the initialization function call. 4) Added comments surrounding the signfiicant changes to the initialization. 5) Added example tests to the testing file. --- src/EasyGA.py | 61 +++++++++++++++---- .../gene_function/gene_random.py | 2 +- src/initialization/random_initialization.py | 2 +- src/new_initialization_method_testing.py | 25 +++++--- 4 files changed, 68 insertions(+), 22 deletions(-) diff --git a/src/EasyGA.py b/src/EasyGA.py index 92baaac..183487e 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -20,10 +20,11 @@ class GA: # Defualt EastGA implimentation structure self.gene_function_impl = random_gene self.gene_input = [] - self.gene_input_type = [] #What if user gives two numbers (i.e. [1,100]) but wants to pick between the two (domain)? + self.gene_input_type = [] + while len(self.gene_input_type) != self.chromosome_length: self.gene_input_type.append(None) - + # Set the GA Configuration self.initialization_impl = random_initialization #self.mutation_impl = PerGeneMutation(Mutation_rate) @@ -36,27 +37,63 @@ class GA: def initialize(self, gene_input): self.gene_input = gene_input - #assuming domain if string (strings can never be range) - for x in range(len(self.gene_input)): - if isinstance(gene_input[x], list) == False: - self.gene_input[x] = [self.gene_input[x], self.gene_input[x]] + #It's possible user may just enter "domain", "float-range", etc. for gene_input_type rather than referring to a specific gene + #In that case, create an array with the same length as the chromosome where each element is just the user's input + if isinstance(self.gene_input_type, str): + gene_input_type_temp = self.gene_input_type + self.gene_input_type = [] + while len(self.gene_input_type) != self.chromosome_length: + self.gene_input_type.append(gene_input_type_temp) - if self.gene_input_type[x] == None: #If it hasn't been hard-set by the user + + #There are two types of gene_input we should expect from the user - "general" or index-dependent + #For example, if a user just enters [1,100], it should be assumed that this is a range/domain that should apply to all genes in the chromosome... + #...rather than assuming that it means gene 1 should be 1 and gene 2 should be 100. + #The check for this is done by checking if any of the values in the user's gene input are lists. + #If lists are included, then values like the ones given above will be converted (i.e. [1, 100, ["up", "left"]] becomes [[1,1], [100,100], ["up", "left"]]) and apply to specific genes + #Else if no lists are included, the gene input will apply to each gene (i.e. for chromosomes with length 3, [1,100] becomes [[1,100],[1,100],[1,100]]) + general_gene_input = True + for n in range(len(self.gene_input)): + if isinstance(self.gene_input[n], list): + general_gene_input = False + break + + #Converting user's input into standardized format - list of lists where each sublist is the range/domain for a specific gene + if general_gene_input == False: + for x in range(len(self.gene_input)): + if isinstance(self.gene_input[x], list) == False: + self.gene_input[x] = [self.gene_input[x], self.gene_input[x]] + else: + gene_input_temp = self.gene_input + self.gene_input = [] + for y in range(self.chromosome_length): + self.gene_input.append(gene_input_temp) + + #Setting up the gene_input_type defaults in the standard format + #values including strings are always domain + #values including floats but no strings is a float-range + #values included neither strings or floats is a normal range + for x in range(len(self.gene_input_type)): + try: + if (self.gene_input[x]): + pass + except IndexError: + self.gene_input.append(None) + + if self.gene_input_type[x] == None and self.gene_input[x] != None: #If it hasn't been hard-set by the user for y in range(len(self.gene_input[x])): - if isinstance(gene_input[x][y], str): + if isinstance(self.gene_input[x][y], str): self.gene_input_type[x] = "domain" break - elif isinstance(gene_input[x][y], float): + elif isinstance(self.gene_input[x][y], float): self.gene_input_type[x] = "float-range" elif y == (len(self.gene_input[x]) -1 and self.gene_input_type[x] != "float-range"): self.gene_input_type[x] = "range" - - # Create the first population self.population = self.initialization_impl( - self.population_size, self.chromosome_length, + self.population_size, self.gene_function_impl, self.gene_input, self.gene_input_type) diff --git a/src/initialization/gene_function/gene_random.py b/src/initialization/gene_function/gene_random.py index af2cdd1..82609d5 100644 --- a/src/initialization/gene_function/gene_random.py +++ b/src/initialization/gene_function/gene_random.py @@ -11,7 +11,7 @@ def check_values(low,high): def random_gene(gene_input, gene_input_type, gene_index): created_gene = None - #Determining if single range/domain or index-dependent + 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": diff --git a/src/initialization/random_initialization.py b/src/initialization/random_initialization.py index 72cfbfa..7de1459 100644 --- a/src/initialization/random_initialization.py +++ b/src/initialization/random_initialization.py @@ -13,7 +13,7 @@ def random_initialization(chromosome_length,population_size,gene_function,gene_i for i in range(population_size): chromosome = create_chromosome() #Fill the Chromosome with genes - for j in range(chromosome_length-1): + 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 diff --git a/src/new_initialization_method_testing.py b/src/new_initialization_method_testing.py index 07b1a09..f4d84db 100644 --- a/src/new_initialization_method_testing.py +++ b/src/new_initialization_method_testing.py @@ -10,18 +10,27 @@ import random # Create the Genetic algorithm ga = EasyGA.GA() + test_gene_input = [["left", "right"],[1,100],[5.0,10],[22,"up"]] -#ga.gene_input_type[1] = "domain" -#ga.gene_input_type[1] = "float-range" +ga.gene_input_type[1] = "float-range" +ga.gene_input_type[2] = "domain" + ga.initialize(test_gene_input) ga.population.print_all() +#Example tests +#Note, the following examples assume a chromosome length of 4. +#if the test_gene_input is longer than the chromosomes, it will get truncated at the length of the chromosome +#for example, for chromosomes with length 2, [["left", "right"],[1,100],[5.0,10],[22,"up"]] becomes [["left", "right"],[1,100]] +#if the test_gene_input is shorter than the chromosomes, the remaining elements will be populated with None +#test_gene_input = [1,100] +#test_gene_input = [["left", "right"],[1,100],[5.0,10],[22,"up"]] +#test_gene_input = ["left", "right", "up", "down"] +#test_gene_input = [[1,100],[0,1],[33,35],[5,6]] +#test_gene_input = [["left", "right"], ["up", "down"], ["left", "down"], ["down", "right"]] -#test_range_one = [1,100] -#test_domain_one = ["left", "right", "up", "down"] -#test_range_two = [[1,100],[0,1],[33,35],[5,6]] -#test_domain_two = [["left", "right"], ["up", "down"], ["left", "down"], ["down", "right"]] - -#for index-specific bounds, do list of lists i.e. test_range = [[1, 100], [1, 25], [5, 25]] \ No newline at end of file +#ga.gene_input_type = "float-range" +#ga.gene_input_type[1] = "domain" +#ga.gene_input_type[1] = "float-range" \ No newline at end of file From 7e587d48d043f331fd8e1aa8b932031fc0c25a4a Mon Sep 17 00:00:00 2001 From: Ryley <39408998+RyleyGG@users.noreply.github.com> Date: Sun, 4 Oct 2020 08:00:33 -0400 Subject: [PATCH 06/16] Test Implementation for selection/crossover/mutation The current test implementation includes random mutation, single point crossover, and tournament selection. The implementation, in short, is a nested approach. The selection method is the only thing actually called by the GA. Both crossover and mutation occur within the selection method. As long as these three systems all follow a standard input/output system, any implementation we build, as well as any user implementations, will work perfectly. The selection function must take GA as a parameter and output a new population. Crossover takes in GA and outputs a population. Mutation takes a chromosome set and outputs a new chromosome set. Many of the changes in this commit are regarding this test implementation. I have also changed many of the file names from "x_examples" to "x_types" and updated the class names to follow capitalziation standards. I did this because I feel personally like the built-in mutation, crossover, and selection implementations are less "examples" and more just already built implementations to make the code required from the user smaller. --- src/EasyGA.py | 85 +++++++++++++++ src/crossover/__init__.py | 2 + src/crossover/crossover_types.py | 39 +++++++ src/fitness_function/__init__.py | 2 + src/fitness_function/examples.py | 17 +++ src/initialization/__init__.py | 5 + .../chromosome_structure/chromosome.py | 5 + src/initialization/gene_structure/gene.py | 10 ++ src/initialization/initialization_types.py | 32 ++++++ .../population_structure/population.py | 7 +- src/mutation/__init__.py | 2 + src/mutation/mutation_types.py | 21 ++++ src/run_testing.py | 15 +++ src/selection/__init__.py | 2 + src/selection/selection_types.py | 103 ++++++++++++++++++ src/termination_point/__init__.py | 2 + src/termination_point/termination_types.py | 16 +++ 17 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 src/crossover/__init__.py create mode 100644 src/crossover/crossover_types.py create mode 100644 src/fitness_function/__init__.py create mode 100644 src/fitness_function/examples.py create mode 100644 src/initialization/__init__.py create mode 100644 src/initialization/initialization_types.py create mode 100644 src/mutation/__init__.py create mode 100644 src/mutation/mutation_types.py create mode 100644 src/selection/__init__.py create mode 100644 src/selection/selection_types.py create mode 100644 src/termination_point/__init__.py create mode 100644 src/termination_point/termination_types.py diff --git a/src/EasyGA.py b/src/EasyGA.py index 183487e..2dcb3a5 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -1,3 +1,4 @@ +<<<<<<< Updated upstream # Import all the data prebuilt modules from initialization.population_structure.population import population as create_population from initialization.chromosome_structure.chromosome import chromosome as create_chromosome @@ -107,6 +108,90 @@ class GA: # If you want to evolve through a number of generations # and be able to pause and output data based on that generation run. pass +======= +import random +# Import all the data structure prebuilt modules +from initialization import Population as create_population +from initialization import Chromosome as create_chromosome +from initialization import Gene as create_gene +# Import example classes +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 + +class GA: + def __init__(self): + """Initialize the GA.""" + # Initilization variables + self.chromosome_length = 10 + self.population_size = 100 + self.chromosome_impl = None + self.gene_impl = None + self.population = None + # Termination varibles + self.current_generation = 0 + self.current_fitness = 0 + self.generation_goal = 35 + self.fitness_goal = 3 + # Mutation variables + self.mutation_rate = 0.075 + + # Rerun already computed fitness + self.update_fitness = True + + # Defualt EastGA implimentation structure + self.initialization_impl = Initialization_Types().random_initialization + self.fitness_function_impl = Fitness_Examples().is_it_5 + self.mutation_impl = Mutation_Types().random_mutation + self.selection_impl = Selection_Types().tournament_selection + self.crossover_impl = Crossover_Types().single_point_crossover + self.termination_impl = Termination_Types().generation_based + + 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. + Otherwise only fitness values set to None (i.e. uninitialized + fitness values) are updated.""" + # Get each chromosome in the population + + for chromosome in chromosome_set: + if(chromosome.fitness == None or self.update_fitness == True): + # Set the chromosomes fitness using the fitness function + chromosome.set_fitness(self.fitness_function_impl(chromosome)) + + 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 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.chromosomes) + + next_population = self.selection_impl(self) + self.population = next_population + self.set_all_fitness(self.population.chromosomes) + + number_of_generations -= 1 + self.current_generation += 1 +>>>>>>> Stashed changes + + def active(self): + """Returns if the ga should terminate base on the termination implimented""" + return self.termination_impl(self) def make_gene(self,value): return create_gene(value) diff --git a/src/crossover/__init__.py b/src/crossover/__init__.py new file mode 100644 index 0000000..ecb3eae --- /dev/null +++ b/src/crossover/__init__.py @@ -0,0 +1,2 @@ +# FROM (. means local) file_name IMPORT function_name +from .crossover_types import Crossover_Types diff --git a/src/crossover/crossover_types.py b/src/crossover/crossover_types.py new file mode 100644 index 0000000..8463009 --- /dev/null +++ b/src/crossover/crossover_types.py @@ -0,0 +1,39 @@ +import random +from initialization.chromosome_structure.chromosome import Chromosome +from initialization.population_structure.population import Population + +class Crossover_Types: + """ Crossover explination goes here. + + Points - Defined as sections between the chromosomes genetic makeup + """ + def __init__(self): + pass + + def single_point_crossover(self, ga): + """Single point crossover is when a "point" is selected and the genetic + make up of the two parent chromosomes are "Crossed" or better known as swapped""" + + crossover_pool = [] + for i in range(ga.population_size): + if ga.population.get_all_chromosomes()[i].selected: + crossover_pool.append(ga.population.get_all_chromosomes()[i]) + + new_population = Population() + for i in range(len(crossover_pool)): + if i + 1 < len(crossover_pool): + new_gene_set = [] + parent_one = crossover_pool[i].get_genes() + parent_two = crossover_pool[i+1].get_genes() + halfway_point = int(ga.chromosome_length/2) + new_gene_set.extend(parent_one[0:halfway_point]) + new_gene_set.extend(parent_two[halfway_point:]) + new_chromosome = Chromosome(new_gene_set) + new_population.add_chromosome(new_chromosome) + + return new_population + + def multi_point_crossover(self, ga,number_of_points = 2): + """Multi point crossover is when a specific number (More then one) of + "points" are created to merge the genetic makup of the chromosomes.""" + pass diff --git a/src/fitness_function/__init__.py b/src/fitness_function/__init__.py new file mode 100644 index 0000000..ad85c09 --- /dev/null +++ b/src/fitness_function/__init__.py @@ -0,0 +1,2 @@ +# FROM (. means local) file_name IMPORT class name +from .examples import Fitness_Examples diff --git a/src/fitness_function/examples.py b/src/fitness_function/examples.py new file mode 100644 index 0000000..a63cba2 --- /dev/null +++ b/src/fitness_function/examples.py @@ -0,0 +1,17 @@ +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: + # Check if its value = 5 + if(gene.value == 5): + # If its value is 5 then add one to + # the overal fitness of the chromosome. + fitness += 1 + + return fitness diff --git a/src/initialization/__init__.py b/src/initialization/__init__.py new file mode 100644 index 0000000..be55db2 --- /dev/null +++ b/src/initialization/__init__.py @@ -0,0 +1,5 @@ +# FROM (. means local) file_name IMPORT function_name +from .initialization_types import Initialization_Types +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 fecc278..5c481da 100644 --- a/src/initialization/chromosome_structure/chromosome.py +++ b/src/initialization/chromosome_structure/chromosome.py @@ -1,6 +1,11 @@ +<<<<<<< Updated upstream class chromosome: # fitness = Empty; genes = [gene, gene, gene, etc.] +======= +class Chromosome: + +>>>>>>> Stashed changes def __init__(self, genes = None): if genes is None: self.genes = [] diff --git a/src/initialization/gene_structure/gene.py b/src/initialization/gene_structure/gene.py index ea3b547..456340b 100644 --- a/src/initialization/gene_structure/gene.py +++ b/src/initialization/gene_structure/gene.py @@ -3,7 +3,12 @@ def check_gene(value): assert value != "" , "Gene can not be empty" return value +<<<<<<< Updated upstream class gene: +======= +class Gene: + +>>>>>>> Stashed changes def __init__(self, value): self.fitness = None self.value = check_gene(value) @@ -17,7 +22,12 @@ class gene: def set_fitness(self, fitness): self.fitness = fitness +<<<<<<< Updated upstream def set_value(self): +======= + def set_value(self, value): + """Set value of the gene""" +>>>>>>> Stashed changes self.value = value def __repr__(self): diff --git a/src/initialization/initialization_types.py b/src/initialization/initialization_types.py new file mode 100644 index 0000000..7ebf82b --- /dev/null +++ b/src/initialization/initialization_types.py @@ -0,0 +1,32 @@ +# 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 + +class Initialization_Types: + """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.""" + # Create the population object + population = create_population() + + # Fill the population with chromosomes + for i in range(ga.population_size): + chromosome = create_chromosome() + #Fill the Chromosome with genes + for j in range(ga.chromosome_length): + # Using the chromosome_impl to set every index inside of the chromosome + if ga.chromosome_impl != None: + # Each chromosome location is specified with its own function + chromosome.add_gene(create_gene(ga.chromosome_impl(j))) + # Will break if chromosome_length != len(lists) in domain + elif ga.gene_impl != None: + function = ga.gene_impl[0] + chromosome.add_gene(create_gene(function(*ga.gene_impl[1:]))) + else: + #Exit because either were not specified + print("You did not specify any initialization constraints.") + population.add_chromosome(chromosome) + return population diff --git a/src/initialization/population_structure/population.py b/src/initialization/population_structure/population.py index 2aaf699..63b2951 100644 --- a/src/initialization/population_structure/population.py +++ b/src/initialization/population_structure/population.py @@ -1,4 +1,4 @@ -class population: +class Population: # fitness = Empty; population = [chromosome, chromosome, etc.] def __init__(self, chromosomes = None): @@ -21,7 +21,12 @@ class population: del self.chromosomes[index] def get_all_chromosomes(self): +<<<<<<< Updated upstream return chromosomes +======= + """returns all chromosomes in the population""" + return self.chromosomes +>>>>>>> Stashed changes def get_fitness(self): return self.fitness diff --git a/src/mutation/__init__.py b/src/mutation/__init__.py new file mode 100644 index 0000000..68ad730 --- /dev/null +++ b/src/mutation/__init__.py @@ -0,0 +1,2 @@ +# FROM (. means local) file_name IMPORT function_name +from .mutation_types import Mutation_Types diff --git a/src/mutation/mutation_types.py b/src/mutation/mutation_types.py new file mode 100644 index 0000000..418abd3 --- /dev/null +++ b/src/mutation/mutation_types.py @@ -0,0 +1,21 @@ +import random + +class Mutation_Types: + + def __init__(self): + pass + + def random_mutation(self, ga, chromosome_set = None): + + if chromosome_set == None: + chromosome_set = ga.population + + chromosome_mutate_num = int(len(chromosome_set)*ga.mutation_rate) + temp_population = ga.initialization_impl(ga) + + while chromosome_mutate_num > 0: + chromosome_set[random.randint(0,ga.population_size-1)] = temp_population.get_all_chromosomes()[chromosome_mutate_num] + chromosome_mutate_num -= 1 + + return chromosome_set + diff --git a/src/run_testing.py b/src/run_testing.py index 7259af4..f7dca81 100644 --- a/src/run_testing.py +++ b/src/run_testing.py @@ -1,8 +1,14 @@ import EasyGA +<<<<<<< Updated upstream +======= +import random + +>>>>>>> Stashed changes # Create the Genetic algorithm ga = EasyGA.GA() +<<<<<<< Updated upstream #Creating a gene with no fitness gene1 = ga.make_gene("Im a gene") gene2 = ga.make_gene("Im also a gene") @@ -18,3 +24,12 @@ print(gene1) print(chromosome) print(populaiton) populaiton.print_all() +======= +ga.gene_impl = [random.randrange,1,10] + +# Run Everyhting +ga.evolve() + +# Print the current population +ga.population.print_all() +>>>>>>> Stashed changes diff --git a/src/selection/__init__.py b/src/selection/__init__.py new file mode 100644 index 0000000..f341e49 --- /dev/null +++ b/src/selection/__init__.py @@ -0,0 +1,2 @@ +# FROM (. means local) file_name IMPORT function_name +from .selection_types import Selection_Types diff --git a/src/selection/selection_types.py b/src/selection/selection_types.py new file mode 100644 index 0000000..4f45568 --- /dev/null +++ b/src/selection/selection_types.py @@ -0,0 +1,103 @@ +import random +from initialization.chromosome_structure.chromosome import Chromosome as create_chromosome +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: + """Selection is the process by which chromosomes are selected for crossover and eventually, influence the next generation of chromosomes.""" + def __init__(self): + pass + + def tournament_selection(self, ga): + """This example currently uses a 'with replacement' approach (chromosomes are placed back into the pool after participating)""" + tournament_size = int(len(ga.population.get_all_chromosomes())/10) #currently hard-coded for purposes of the example. + + #selection_probability is the likelihood that a chromosome will be selected. + #best chromosome in a tournament is given a selection probablity of selection_probability + #2nd best is given probability of selection_probability*(1-selection_probability) + selection_probability = 0.95 + total_selected = 0 #Total Chromosomes selected + + while (total_selected <= ga.population_size*2): + #create & gather tournament group + tournament_group = [] + for i in range(tournament_size): + tournament_group.append(random.choice(ga.population.get_all_chromosomes())) + + #Sort the tournament contenders based on their fitness + #currently hard-coded to only consider higher fitness = better; can be changed once this impl is agreed on + #also currently uses bubble sort because its easy + tournament_group_temp = tournament_group + not_sorted_check = 0 + while (not_sorted_check != len(tournament_group_temp)): + not_sorted_check = 0 + for i in range(len(tournament_group_temp)): + if ((i + 1 < len(tournament_group_temp)) and (tournament_group_temp[i + 1].fitness > tournament_group_temp[i].fitness)): + temp = tournament_group[i] + tournament_group_temp[i] = tournament_group[i + 1] + tournament_group_temp[i + 1] = temp + else: + not_sorted_check += 1 + + tournament_group = tournament_group_temp + + #After sorting by fitness, randomly select a chromosome based on selection_probability + for i in range(tournament_size): + random_num = random.uniform(0,1) + + #ugly implementation but its functional + if i == 0: + if random_num <= selection_probability: + tournament_group[i].selected = True + total_selected += 1 + break + else: + if random_num <= selection_probability*((1-selection_probability)**(i-1)): + tournament_group[i].selected = True + total_selected += 1 + break + + + new_population = ga.crossover_impl(ga) + + #If the crossover doesn't create enough chromosomes (ugly right now pls no judgerino, can be changed) + #Just does single-point crossover at random indices + while len(new_population.chromosomes) < ga.population_size: + crossover_pool = [] + for i in range(ga.population_size): + if ga.population.get_all_chromosomes()[i].selected: + crossover_pool.append(ga.population.get_all_chromosomes()[i]) + + split_point = random.randint(0,ga.chromosome_length) + chromosome_list = [] + for i in range(len(crossover_pool)): + if i + 1 < len(crossover_pool): + new_gene_set = [] + parent_one = crossover_pool[i].get_genes() + parent_two = crossover_pool[i+1].get_genes() + new_gene_set.extend(parent_one[0:split_point]) + new_gene_set.extend(parent_two[split_point:]) + new_chromosome = Chromosome(new_gene_set) + chromosome_list.append(new_chromosome) + + for i in range(len(chromosome_list)): + new_population.add_chromosome(chromosome_list[i]) + if len(new_population.chromosomes) >= ga.population_size: + break + + new_chromosome_set = ga.mutation_impl(ga, new_population.get_all_chromosomes()) + new_population.set_all_chromosomes(new_chromosome_set) + + return new_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 + that it will be selected. Using the example of a casino roulette wheel. + Where the chromosomes are the numbers to be selected and the board size for + those numbers are directly proportional to the chromosome's current fitness. Where + the ball falls is a randomly generated number between 0 and 1""" + pass diff --git a/src/termination_point/__init__.py b/src/termination_point/__init__.py new file mode 100644 index 0000000..b044846 --- /dev/null +++ b/src/termination_point/__init__.py @@ -0,0 +1,2 @@ +# FROM (. means local) file_name IMPORT class name +from .termination_types import Termination_Types diff --git a/src/termination_point/termination_types.py b/src/termination_point/termination_types.py new file mode 100644 index 0000000..a6ebf10 --- /dev/null +++ b/src/termination_point/termination_types.py @@ -0,0 +1,16 @@ +class Termination_Types: + """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 + if(ga.current_fitness > ga.fitness_goal): + continue_status = False + return continue_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 From 4b375659bbe3b2573e8f6daf5decf552e7318f40 Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Sun, 4 Oct 2020 14:35:19 -0400 Subject: [PATCH 07/16] Removed weird github thing with old implementation Why --- src/EasyGA.py | 112 --------------------------------------------- src/run_testing.py | 21 --------- 2 files changed, 133 deletions(-) diff --git a/src/EasyGA.py b/src/EasyGA.py index 2dcb3a5..bb66415 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -1,114 +1,3 @@ -<<<<<<< Updated upstream -# Import all the data prebuilt modules -from initialization.population_structure.population import population as create_population -from initialization.chromosome_structure.chromosome import chromosome as create_chromosome -from initialization.gene_structure.gene import gene as create_gene - -# Import functions for defaults -from initialization.gene_function.gene_random import random_gene -# Import functionality defaults -from initialization.random_initialization import random_initialization - - -class GA: - def __init__(self): - # Default variables - self.population = None - self.generations = 3 - self.chromosome_length = 4 - self.population_size = 5 - self.mutation_rate = 0.03 - # Defualt EastGA implimentation structure - self.gene_function_impl = random_gene - self.gene_input = [] - self.gene_input_type = [] - - while len(self.gene_input_type) != self.chromosome_length: - self.gene_input_type.append(None) - - # Set the GA Configuration - self.initialization_impl = random_initialization - #self.mutation_impl = PerGeneMutation(Mutation_rate) - #self.selection_impl = TournamentSelection() - #self.crossover_impl = FastSinglePointCrossover() - #self.termination_impl = GenerationTermination(Total_generations) - #self.evaluation_impl = TestEvaluation() - - - def initialize(self, gene_input): - self.gene_input = gene_input - - #It's possible user may just enter "domain", "float-range", etc. for gene_input_type rather than referring to a specific gene - #In that case, create an array with the same length as the chromosome where each element is just the user's input - if isinstance(self.gene_input_type, str): - gene_input_type_temp = self.gene_input_type - self.gene_input_type = [] - while len(self.gene_input_type) != self.chromosome_length: - self.gene_input_type.append(gene_input_type_temp) - - - #There are two types of gene_input we should expect from the user - "general" or index-dependent - #For example, if a user just enters [1,100], it should be assumed that this is a range/domain that should apply to all genes in the chromosome... - #...rather than assuming that it means gene 1 should be 1 and gene 2 should be 100. - #The check for this is done by checking if any of the values in the user's gene input are lists. - #If lists are included, then values like the ones given above will be converted (i.e. [1, 100, ["up", "left"]] becomes [[1,1], [100,100], ["up", "left"]]) and apply to specific genes - #Else if no lists are included, the gene input will apply to each gene (i.e. for chromosomes with length 3, [1,100] becomes [[1,100],[1,100],[1,100]]) - general_gene_input = True - for n in range(len(self.gene_input)): - if isinstance(self.gene_input[n], list): - general_gene_input = False - break - - #Converting user's input into standardized format - list of lists where each sublist is the range/domain for a specific gene - if general_gene_input == False: - for x in range(len(self.gene_input)): - if isinstance(self.gene_input[x], list) == False: - self.gene_input[x] = [self.gene_input[x], self.gene_input[x]] - else: - gene_input_temp = self.gene_input - self.gene_input = [] - for y in range(self.chromosome_length): - self.gene_input.append(gene_input_temp) - - #Setting up the gene_input_type defaults in the standard format - #values including strings are always domain - #values including floats but no strings is a float-range - #values included neither strings or floats is a normal range - for x in range(len(self.gene_input_type)): - try: - if (self.gene_input[x]): - pass - except IndexError: - self.gene_input.append(None) - - if self.gene_input_type[x] == None and self.gene_input[x] != None: #If it hasn't been hard-set by the user - for y in range(len(self.gene_input[x])): - if isinstance(self.gene_input[x][y], str): - self.gene_input_type[x] = "domain" - break - elif isinstance(self.gene_input[x][y], float): - self.gene_input_type[x] = "float-range" - elif y == (len(self.gene_input[x]) -1 and self.gene_input_type[x] != "float-range"): - self.gene_input_type[x] = "range" - - # Create the first population - self.population = self.initialization_impl( - self.chromosome_length, - self.population_size, - self.gene_function_impl, - self.gene_input, - self.gene_input_type) - - - def evolve(): - # If you just want to evolve through all generations - pass - - def evolve_generation(self, number_of_generations): - # If you want to evolve through a number of generations - # and be able to pause and output data based on that generation run. - pass -======= import random # Import all the data structure prebuilt modules from initialization import Population as create_population @@ -187,7 +76,6 @@ class GA: number_of_generations -= 1 self.current_generation += 1 ->>>>>>> Stashed changes def active(self): """Returns if the ga should terminate base on the termination implimented""" diff --git a/src/run_testing.py b/src/run_testing.py index f7dca81..1576b3a 100644 --- a/src/run_testing.py +++ b/src/run_testing.py @@ -1,30 +1,10 @@ import EasyGA -<<<<<<< Updated upstream -======= import random ->>>>>>> Stashed changes # Create the Genetic algorithm ga = EasyGA.GA() -<<<<<<< Updated upstream -#Creating a gene with no fitness -gene1 = ga.make_gene("Im a gene") -gene2 = ga.make_gene("Im also a gene") -#Creating a Chromosome with no genes -chromosome = ga.make_chromosome() -chromosome.add_gene(gene1) -chromosome.add_gene(gene2) -# Creating a populaiton -populaiton = ga.make_population() -populaiton.add_chromosome(chromosome) - -print(gene1) -print(chromosome) -print(populaiton) -populaiton.print_all() -======= ga.gene_impl = [random.randrange,1,10] # Run Everyhting @@ -32,4 +12,3 @@ ga.evolve() # Print the current population ga.population.print_all() ->>>>>>> Stashed changes From 89df506469fe468bb64c8a5d73724c82a970dd39 Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Sun, 4 Oct 2020 14:38:41 -0400 Subject: [PATCH 08/16] Fixed more github upstream stuff why v2 --- src/initialization/chromosome_structure/chromosome.py | 7 +------ src/initialization/gene_structure/gene.py | 8 -------- src/initialization/population_structure/population.py | 4 ---- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/initialization/chromosome_structure/chromosome.py b/src/initialization/chromosome_structure/chromosome.py index 5c481da..420e107 100644 --- a/src/initialization/chromosome_structure/chromosome.py +++ b/src/initialization/chromosome_structure/chromosome.py @@ -1,17 +1,12 @@ -<<<<<<< Updated upstream -class chromosome: - - # fitness = Empty; genes = [gene, gene, gene, etc.] -======= class Chromosome: ->>>>>>> Stashed changes def __init__(self, genes = None): if genes is None: self.genes = [] else: self.genes = genes self.fitness = None + self.selected = False def add_gene(self, gene, index = -1): if index == -1: diff --git a/src/initialization/gene_structure/gene.py b/src/initialization/gene_structure/gene.py index 456340b..bd13b54 100644 --- a/src/initialization/gene_structure/gene.py +++ b/src/initialization/gene_structure/gene.py @@ -3,12 +3,8 @@ def check_gene(value): assert value != "" , "Gene can not be empty" return value -<<<<<<< Updated upstream -class gene: -======= class Gene: ->>>>>>> Stashed changes def __init__(self, value): self.fitness = None self.value = check_gene(value) @@ -22,12 +18,8 @@ class Gene: def set_fitness(self, fitness): self.fitness = fitness -<<<<<<< Updated upstream - def set_value(self): -======= def set_value(self, value): """Set value of the gene""" ->>>>>>> Stashed changes self.value = value def __repr__(self): diff --git a/src/initialization/population_structure/population.py b/src/initialization/population_structure/population.py index 63b2951..44b9435 100644 --- a/src/initialization/population_structure/population.py +++ b/src/initialization/population_structure/population.py @@ -21,12 +21,8 @@ class Population: del self.chromosomes[index] def get_all_chromosomes(self): -<<<<<<< Updated upstream - return chromosomes -======= """returns all chromosomes in the population""" return self.chromosomes ->>>>>>> Stashed changes def get_fitness(self): return self.fitness From c18a5310349d31ba06b5cbb90187d9e45bf59e22 Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Sun, 4 Oct 2020 15:54:38 -0400 Subject: [PATCH 09/16] Updated selection implementation, added with/without replacement variation --- src/EasyGA.py | 6 +- src/crossover/crossover_types.py | 7 +- src/run_testing.py | 4 +- src/selection/selection_types.py | 134 +++++++++++++++++++++---------- 4 files changed, 101 insertions(+), 50 deletions(-) diff --git a/src/EasyGA.py b/src/EasyGA.py index bb66415..554c413 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -23,10 +23,10 @@ class GA: # Termination varibles self.current_generation = 0 self.current_fitness = 0 - self.generation_goal = 35 + self.generation_goal = 50 self.fitness_goal = 3 # Mutation variables - self.mutation_rate = 0.075 + self.mutation_rate = 0.05 # Rerun already computed fitness self.update_fitness = True @@ -35,7 +35,7 @@ class GA: self.initialization_impl = Initialization_Types().random_initialization self.fitness_function_impl = Fitness_Examples().is_it_5 self.mutation_impl = Mutation_Types().random_mutation - self.selection_impl = Selection_Types().tournament_selection + self.selection_impl = Selection_Types().Tournament().with_replacement self.crossover_impl = Crossover_Types().single_point_crossover self.termination_impl = Termination_Types().generation_based diff --git a/src/crossover/crossover_types.py b/src/crossover/crossover_types.py index 8463009..b8ee22b 100644 --- a/src/crossover/crossover_types.py +++ b/src/crossover/crossover_types.py @@ -25,9 +25,10 @@ class Crossover_Types: new_gene_set = [] parent_one = crossover_pool[i].get_genes() parent_two = crossover_pool[i+1].get_genes() - halfway_point = int(ga.chromosome_length/2) - new_gene_set.extend(parent_one[0:halfway_point]) - new_gene_set.extend(parent_two[halfway_point:]) + #halfway_point = int(ga.chromosome_length/2) + split_point = random.randint(0,ga.chromosome_length) + new_gene_set.extend(parent_one[0:split_point]) + new_gene_set.extend(parent_two[split_point:]) new_chromosome = Chromosome(new_gene_set) new_population.add_chromosome(new_chromosome) diff --git a/src/run_testing.py b/src/run_testing.py index 1576b3a..1568ac3 100644 --- a/src/run_testing.py +++ b/src/run_testing.py @@ -5,9 +5,9 @@ import random # Create the Genetic algorithm ga = EasyGA.GA() -ga.gene_impl = [random.randrange,1,10] +ga.gene_impl = [random.randrange,1,25] -# Run Everyhting +# Run Everything ga.evolve() # Print the current population diff --git a/src/selection/selection_types.py b/src/selection/selection_types.py index 4f45568..a0bde70 100644 --- a/src/selection/selection_types.py +++ b/src/selection/selection_types.py @@ -9,22 +9,69 @@ class Selection_Types: def __init__(self): pass - def tournament_selection(self, ga): - """This example currently uses a 'with replacement' approach (chromosomes are placed back into the pool after participating)""" - tournament_size = int(len(ga.population.get_all_chromosomes())/10) #currently hard-coded for purposes of the example. + class Tournament: + def with_replacement(self, ga): + tournament_size = int(len(ga.population.get_all_chromosomes())/10) #currently hard-coded for purposes of the example. + if tournament_size < 3: + tournament_size = int(len(ga.population.get_all_chromosomes())/3) - #selection_probability is the likelihood that a chromosome will be selected. - #best chromosome in a tournament is given a selection probablity of selection_probability - #2nd best is given probability of selection_probability*(1-selection_probability) - selection_probability = 0.95 - total_selected = 0 #Total Chromosomes selected + #selection_probability is the likelihood that a chromosome will be selected. + #best chromosome in a tournament is given a selection probablity of selection_probability + #2nd best is given probability of selection_probability*(1-selection_probability) + #3rd best is given probability of selection_probability*(1-selection_probability)**2 + selection_probability = 0.95 + total_selected = 0 #Total Chromosomes selected - while (total_selected <= ga.population_size*2): - #create & gather tournament group - tournament_group = [] - for i in range(tournament_size): - tournament_group.append(random.choice(ga.population.get_all_chromosomes())) + while (total_selected <= ga.population_size*2): + #create & gather tournament group + tournament_group = [] + for i in range(tournament_size): + tournament_group.append(random.choice(ga.population.get_all_chromosomes())) + total_selected = self.selection(tournament_group, tournament_size, total_selected, selection_probability)[0] + + new_population = self.create_new_population(ga) + return new_population + + def without_replacement(self, ga): + tournament_size = int(len(ga.population.get_all_chromosomes())/10) #currently hard-coded for purposes of the example. + if tournament_size < 3: + tournament_size = int(len(ga.population.get_all_chromosomes())/3) + + #selection_probability is the likelihood that a chromosome will be selected. + #best chromosome in a tournament is given a selection probablity of selection_probability + #2nd best is given probability of selection_probability*(1-selection_probability) + #3rd best is given probability of selection_probability*(1-selection_probability)**2 + selection_probability = 0.95 + total_selected = 0 #Total Chromosomes selected + available_chromosome_indices = [] + for i in range(len(ga.population.get_all_chromosomes())): + available_chromosome_indices.append(i) + + continue_selecting = True + + while (continue_selecting): + #create & gather tournament group + tournament_group = [] + + for i in range(tournament_size): + selected_chromosome_index = random.choice(available_chromosome_indices) + tournament_group.append(ga.population.get_all_chromosomes()[selected_chromosome_index]) + + winning_chromosome_index = self.selection(tournament_group, tournament_size, total_selected, selection_probability)[1] + for i in range(len(available_chromosome_indices)): + if tournament_group[winning_chromosome_index].selected: + del available_chromosome_indices[i] + break + #print(winning_chromosome_index) + #print(available_chromosome_indices) + if len(available_chromosome_indices) < 1: + continue_selecting = False + + new_population = self.create_new_population(ga) + return new_population + + def selection(self, tournament_group, tournament_size, total_selected, selection_probability): #Sort the tournament contenders based on their fitness #currently hard-coded to only consider higher fitness = better; can be changed once this impl is agreed on #also currently uses bubble sort because its easy @@ -43,6 +90,7 @@ class Selection_Types: tournament_group = tournament_group_temp #After sorting by fitness, randomly select a chromosome based on selection_probability + selected_chromosome_tournament_index = 0 for i in range(tournament_size): random_num = random.uniform(0,1) @@ -51,47 +99,49 @@ class Selection_Types: if random_num <= selection_probability: tournament_group[i].selected = True total_selected += 1 + selected_chromosome_tournament_index = i break else: if random_num <= selection_probability*((1-selection_probability)**(i-1)): tournament_group[i].selected = True total_selected += 1 + selected_chromosome_tournament_index = i break + return total_selected,selected_chromosome_tournament_index - new_population = ga.crossover_impl(ga) + def create_new_population(self, ga): + new_population = ga.crossover_impl(ga) - #If the crossover doesn't create enough chromosomes (ugly right now pls no judgerino, can be changed) - #Just does single-point crossover at random indices - while len(new_population.chromosomes) < ga.population_size: - crossover_pool = [] - for i in range(ga.population_size): - if ga.population.get_all_chromosomes()[i].selected: - crossover_pool.append(ga.population.get_all_chromosomes()[i]) + #If the crossover doesn't create enough chromosomes (ugly right now pls no judgerino, can be changed) + #Just does single-point crossover at random indices + while len(new_population.chromosomes) < ga.population_size: + crossover_pool = [] + for i in range(ga.population_size): + if ga.population.get_all_chromosomes()[i].selected: + crossover_pool.append(ga.population.get_all_chromosomes()[i]) - split_point = random.randint(0,ga.chromosome_length) - chromosome_list = [] - for i in range(len(crossover_pool)): - if i + 1 < len(crossover_pool): - new_gene_set = [] - parent_one = crossover_pool[i].get_genes() - parent_two = crossover_pool[i+1].get_genes() - new_gene_set.extend(parent_one[0:split_point]) - new_gene_set.extend(parent_two[split_point:]) - new_chromosome = Chromosome(new_gene_set) - chromosome_list.append(new_chromosome) - - for i in range(len(chromosome_list)): - new_population.add_chromosome(chromosome_list[i]) - if len(new_population.chromosomes) >= ga.population_size: - break - - new_chromosome_set = ga.mutation_impl(ga, new_population.get_all_chromosomes()) - new_population.set_all_chromosomes(new_chromosome_set) - - return new_population + split_point = random.randint(0,ga.chromosome_length) + chromosome_list = [] + for i in range(len(crossover_pool)): + if i + 1 < len(crossover_pool): + new_gene_set = [] + parent_one = crossover_pool[i].get_genes() + parent_two = crossover_pool[i+1].get_genes() + new_gene_set.extend(parent_one[0:split_point]) + new_gene_set.extend(parent_two[split_point:]) + new_chromosome = Chromosome(new_gene_set) + chromosome_list.append(new_chromosome) + for i in range(len(chromosome_list)): + new_population.add_chromosome(chromosome_list[i]) + if len(new_population.chromosomes) >= ga.population_size: + break + + new_chromosome_set = ga.mutation_impl(ga, new_population.get_all_chromosomes()) + new_population.set_all_chromosomes(new_chromosome_set) + return new_population def roulette_selection(self, ga): """Roulette selection works based off of how strong the fitness is of the From e05aa7f62b2c2478c63bee9ae517b348f38960c8 Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Sun, 4 Oct 2020 17:59:59 -0400 Subject: [PATCH 10/16] Changed implementation framework Instead of a nested approach, selection/crossover/mutation are all called separately and directly by the GA. selection_impl was also separated into parent_selection_impl and survivor_selection_impl, as both are needed separately. --- src/EasyGA.py | 17 +- .../population_structure/population.py | 6 +- src/mutation/mutation_types.py | 23 ++- src/run_testing.py | 5 +- src/selection/selection_types.py | 171 +++++++----------- 5 files changed, 102 insertions(+), 120 deletions(-) diff --git a/src/EasyGA.py b/src/EasyGA.py index 554c413..9a08f05 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -16,17 +16,17 @@ class GA: """Initialize the GA.""" # Initilization variables self.chromosome_length = 10 - self.population_size = 100 + self.population_size = 150 self.chromosome_impl = None self.gene_impl = None self.population = None # Termination varibles self.current_generation = 0 self.current_fitness = 0 - self.generation_goal = 50 + self.generation_goal = 100 self.fitness_goal = 3 # Mutation variables - self.mutation_rate = 0.05 + self.mutation_rate = 0.10 # Rerun already computed fitness self.update_fitness = True @@ -34,8 +34,9 @@ class GA: # Defualt EastGA implimentation structure self.initialization_impl = Initialization_Types().random_initialization self.fitness_function_impl = Fitness_Examples().is_it_5 - self.mutation_impl = Mutation_Types().random_mutation - self.selection_impl = Selection_Types().Tournament().with_replacement + 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 @@ -70,7 +71,11 @@ class GA: self.initialize_population() self.set_all_fitness(self.population.chromosomes) - next_population = self.selection_impl(self) + 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.chromosomes) diff --git a/src/initialization/population_structure/population.py b/src/initialization/population_structure/population.py index 44b9435..ae7670a 100644 --- a/src/initialization/population_structure/population.py +++ b/src/initialization/population_structure/population.py @@ -30,8 +30,10 @@ class Population: def set_all_chromosomes(self, chromosomes): self.chromosomes = chromosomes - def set_chromosome(self, chromosomes, index): - self.chromosome[index] = chromosome + def set_chromosome(self, chromosome, index = -1): + if index == -1: + index = len(self.chromosomes)-1 + self.chromosomes[index] = chromosome def set_fitness(self, fitness): self.fitness = fitness diff --git a/src/mutation/mutation_types.py b/src/mutation/mutation_types.py index 418abd3..12b4b6e 100644 --- a/src/mutation/mutation_types.py +++ b/src/mutation/mutation_types.py @@ -8,7 +8,7 @@ class Mutation_Types: def random_mutation(self, ga, chromosome_set = None): if chromosome_set == None: - chromosome_set = ga.population + chromosome_set = ga.population.get_all_chromosomes() chromosome_mutate_num = int(len(chromosome_set)*ga.mutation_rate) temp_population = ga.initialization_impl(ga) @@ -18,4 +18,25 @@ class Mutation_Types: chromosome_mutate_num -= 1 return chromosome_set + + def per_gene_mutation(self, ga, chromosome_set = None, gene_mutate_count = 1): + + gene_mutate_count_static = int(gene_mutate_count) + + if chromosome_set == None: + chromosome_set = ga.population.get_all_chromosomes() + + for i in range(len(chromosome_set)): + random_num = random.uniform(0,1) + + if (random_num <= ga.mutation_rate): + while gene_mutate_count > 0: + dummy_population = ga.initialization_impl(ga) #Really inefficient, but works for now + random_index = random.randint(0, ga.chromosome_length-1) + chromosome_set[i].get_genes()[random_index] = dummy_population.get_all_chromosomes()[random.randint(0,ga.population_size-1)].get_genes()[random_index] + gene_mutate_count -= 1 + gene_mutate_count = int(gene_mutate_count_static) + + return chromosome_set + diff --git a/src/run_testing.py b/src/run_testing.py index 1568ac3..cdc0a0f 100644 --- a/src/run_testing.py +++ b/src/run_testing.py @@ -5,7 +5,10 @@ import random # Create the Genetic algorithm ga = EasyGA.GA() -ga.gene_impl = [random.randrange,1,25] +#def random_parent_selection(population): + #while () + +ga.gene_impl = [random.randrange,1,100] # Run Everything ga.evolve() diff --git a/src/selection/selection_types.py b/src/selection/selection_types.py index a0bde70..b8aca55 100644 --- a/src/selection/selection_types.py +++ b/src/selection/selection_types.py @@ -9,113 +9,67 @@ class Selection_Types: def __init__(self): pass - class Tournament: - def with_replacement(self, ga): - tournament_size = int(len(ga.population.get_all_chromosomes())/10) #currently hard-coded for purposes of the example. - if tournament_size < 3: - tournament_size = int(len(ga.population.get_all_chromosomes())/3) + class Parent_Selection: + class Tournament: + def with_replacement(self, ga): + tournament_size = int(len(ga.population.get_all_chromosomes())/10) #currently hard-coded for purposes of the example. + if tournament_size < 3: + tournament_size = int(len(ga.population.get_all_chromosomes())/3) + parent_ratio = 0.25 - #selection_probability is the likelihood that a chromosome will be selected. - #best chromosome in a tournament is given a selection probablity of selection_probability - #2nd best is given probability of selection_probability*(1-selection_probability) - #3rd best is given probability of selection_probability*(1-selection_probability)**2 - selection_probability = 0.95 - total_selected = 0 #Total Chromosomes selected + #selection_probability is the likelihood that a chromosome will be selected. + #best chromosome in a tournament is given a selection probablity of selection_probability + #2nd best is given probability of selection_probability*(1-selection_probability) + #3rd best is given probability of selection_probability*(1-selection_probability)**2 + selection_probability = 0.95 + total_selected = 0 #Total Chromosomes selected - while (total_selected <= ga.population_size*2): - #create & gather tournament group - tournament_group = [] + while (total_selected < parent_ratio*ga.population_size): + #create & gather tournament group + tournament_group = [] - for i in range(tournament_size): - tournament_group.append(random.choice(ga.population.get_all_chromosomes())) - total_selected = self.selection(tournament_group, tournament_size, total_selected, selection_probability)[0] + for i in range(tournament_size): + tournament_group.append(random.choice(ga.population.get_all_chromosomes())) + + #Sort the tournament contenders based on their fitness + #currently hard-coded to only consider higher fitness = better; can be changed once this impl is agreed on + #also currently uses bubble sort because its easy + tournament_group_temp = tournament_group + not_sorted_check = 0 + while (not_sorted_check != len(tournament_group_temp)): + not_sorted_check = 0 + for i in range(len(tournament_group_temp)): + if ((i + 1 < len(tournament_group_temp)) and (tournament_group_temp[i + 1].fitness > tournament_group_temp[i].fitness)): + temp = tournament_group[i] + tournament_group_temp[i] = tournament_group[i + 1] + tournament_group_temp[i + 1] = temp + else: + not_sorted_check += 1 - new_population = self.create_new_population(ga) - return new_population + tournament_group = tournament_group_temp - def without_replacement(self, ga): - tournament_size = int(len(ga.population.get_all_chromosomes())/10) #currently hard-coded for purposes of the example. - if tournament_size < 3: - tournament_size = int(len(ga.population.get_all_chromosomes())/3) + #After sorting by fitness, randomly select a chromosome based on selection_probability + selected_chromosome_tournament_index = 0 + for i in range(tournament_size): + random_num = random.uniform(0,1) - #selection_probability is the likelihood that a chromosome will be selected. - #best chromosome in a tournament is given a selection probablity of selection_probability - #2nd best is given probability of selection_probability*(1-selection_probability) - #3rd best is given probability of selection_probability*(1-selection_probability)**2 - selection_probability = 0.95 - total_selected = 0 #Total Chromosomes selected - available_chromosome_indices = [] - for i in range(len(ga.population.get_all_chromosomes())): - available_chromosome_indices.append(i) + #ugly implementation but its functional + if i == 0: + if random_num <= selection_probability: + tournament_group[i].selected = True + total_selected += 1 + selected_chromosome_tournament_index = i + break + else: + if random_num <= selection_probability*((1-selection_probability)**(i-1)): + tournament_group[i].selected = True + total_selected += 1 + selected_chromosome_tournament_index = i + break - continue_selecting = True - - while (continue_selecting): - #create & gather tournament group - tournament_group = [] - - for i in range(tournament_size): - selected_chromosome_index = random.choice(available_chromosome_indices) - tournament_group.append(ga.population.get_all_chromosomes()[selected_chromosome_index]) - - winning_chromosome_index = self.selection(tournament_group, tournament_size, total_selected, selection_probability)[1] - for i in range(len(available_chromosome_indices)): - if tournament_group[winning_chromosome_index].selected: - del available_chromosome_indices[i] - break - #print(winning_chromosome_index) - #print(available_chromosome_indices) - if len(available_chromosome_indices) < 1: - continue_selecting = False - - new_population = self.create_new_population(ga) - return new_population - - def selection(self, tournament_group, tournament_size, total_selected, selection_probability): - #Sort the tournament contenders based on their fitness - #currently hard-coded to only consider higher fitness = better; can be changed once this impl is agreed on - #also currently uses bubble sort because its easy - tournament_group_temp = tournament_group - not_sorted_check = 0 - while (not_sorted_check != len(tournament_group_temp)): - not_sorted_check = 0 - for i in range(len(tournament_group_temp)): - if ((i + 1 < len(tournament_group_temp)) and (tournament_group_temp[i + 1].fitness > tournament_group_temp[i].fitness)): - temp = tournament_group[i] - tournament_group_temp[i] = tournament_group[i + 1] - tournament_group_temp[i + 1] = temp - else: - not_sorted_check += 1 - - tournament_group = tournament_group_temp - - #After sorting by fitness, randomly select a chromosome based on selection_probability - selected_chromosome_tournament_index = 0 - for i in range(tournament_size): - random_num = random.uniform(0,1) - - #ugly implementation but its functional - if i == 0: - if random_num <= selection_probability: - tournament_group[i].selected = True - total_selected += 1 - selected_chromosome_tournament_index = i - break - else: - if random_num <= selection_probability*((1-selection_probability)**(i-1)): - tournament_group[i].selected = True - total_selected += 1 - selected_chromosome_tournament_index = i - break - - return total_selected,selected_chromosome_tournament_index - - def create_new_population(self, ga): - new_population = ga.crossover_impl(ga) - - #If the crossover doesn't create enough chromosomes (ugly right now pls no judgerino, can be changed) - #Just does single-point crossover at random indices - while len(new_population.chromosomes) < ga.population_size: + class Survivor_Selection: + def repeated_crossover(self, ga, next_population): + while len(next_population.chromosomes) < ga.population_size: crossover_pool = [] for i in range(ga.population_size): if ga.population.get_all_chromosomes()[i].selected: @@ -130,19 +84,16 @@ class Selection_Types: parent_two = crossover_pool[i+1].get_genes() new_gene_set.extend(parent_one[0:split_point]) new_gene_set.extend(parent_two[split_point:]) - new_chromosome = Chromosome(new_gene_set) + new_chromosome = create_chromosome(new_gene_set) chromosome_list.append(new_chromosome) - + + for i in range(len(chromosome_list)): - new_population.add_chromosome(chromosome_list[i]) - if len(new_population.chromosomes) >= ga.population_size: + next_population.add_chromosome(chromosome_list[i]) + if len(next_population.chromosomes) >= ga.population_size: break - - new_chromosome_set = ga.mutation_impl(ga, new_population.get_all_chromosomes()) - new_population.set_all_chromosomes(new_chromosome_set) - - return new_population - + 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 From 665062fdf1f166a6cf475e473e24bf68b615a0b0 Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Mon, 5 Oct 2020 20:46:25 -0400 Subject: [PATCH 11/16] 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 From 3bfa962194fd0ba023cb046cd4f38005cfe5f588 Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Mon, 5 Oct 2020 20:59:21 -0400 Subject: [PATCH 12/16] Updated fitness based termination It now works, although it only supports a minimum approach --- src/termination_point/termination_methods.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/termination_point/termination_methods.py b/src/termination_point/termination_methods.py index 8b8f9fe..493f02d 100644 --- a/src/termination_point/termination_methods.py +++ b/src/termination_point/termination_methods.py @@ -3,9 +3,14 @@ class Termination_Methods: def fitness_based(self, ga): """Fitness based approach to terminate when the goal fitness has been reached""" + status = True - if(ga.current_fitness > ga.fitness_goal): - status = False + if ga.population == None: + return status + for i in range(len(ga.population.get_all_chromosomes())): + if(ga.population.get_all_chromosomes()[i].fitness >= ga.fitness_goal): + status = False + break return status def generation_based(self, ga): From e7ac0e23f4d65303a3422a544fed8bd482b03c14 Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Tue, 6 Oct 2020 17:55:17 -0400 Subject: [PATCH 13/16] Optimizations/updates 1. Deleted duplicate functions in EasyGA 2. Added new index-dependent fitness example 3. GA now auto-sorts by best fitness immediately after the fitness is calculated across the board 4. Removed 'selected' status flag from the Chromosome flag 5. Added mating_pool attribute to the population 6. Changed other code to be in line with 4 and 5 7. Optimized tournament selection method --- src/EasyGA.py | 53 +++++------ src/crossover/crossover_methods.py | 5 +- src/fitness_function/fitness_examples.py | 12 +++ .../chromosome_structure/chromosome.py | 1 - .../population_structure/population.py | 1 + src/run_testing.py | 2 +- src/selection/selection_methods.py | 91 +++++-------------- 7 files changed, 61 insertions(+), 104 deletions(-) diff --git a/src/EasyGA.py b/src/EasyGA.py index 4f27075..159c986 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -18,17 +18,20 @@ class GA: """Initialize the GA.""" # Initilization variables self.chromosome_length = 10 - self.population_size = 100 + self.population_size = 150 self.chromosome_impl = None self.gene_impl = None self.population = None + self.target_fitness_type = 'maximum' + + self.parent_ratio = 0.1 # Termination varibles self.current_generation = 0 self.generation_goal = 50 self.current_fitness = 0 - self.generation_goal = 100 - self.fitness_goal = 3 + self.generation_goal = 250 + self.fitness_goal = 9 # Mutation variables self.mutation_rate = 0.10 @@ -37,7 +40,7 @@ class GA: # Defualt EastGA implimentation structure self.initialization_impl = Initialization_Methods().random_initialization - self.fitness_function_impl = Fitness_Examples().is_it_5 + self.fitness_function_impl = Fitness_Examples().index_dependent_values # 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 @@ -54,6 +57,7 @@ class GA: if self.current_generation == 0: self.initialize_population() self.set_all_fitness(self.population.chromosome_list) + self.population.set_all_chromosomes(self.sort_by_best_fitness()) self.parent_selection_impl(self) next_population = self.crossover_impl(self) @@ -62,6 +66,7 @@ class GA: self.population = next_population self.set_all_fitness(self.population.chromosome_list) + self.population.set_all_chromosomes(self.sort_by_best_fitness()) number_of_generations -= 1 self.current_generation += 1 @@ -94,34 +99,26 @@ class GA: # Set the chromosomes fitness using the fitness function chromosome.set_fitness(self.fitness_function_impl(chromosome)) - 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 sort_by_best_fitness(self, chromosome_set = None): - 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())) + if chromosome_set == None: + chromosome_set = self.population.get_all_chromosomes() - self.population = next_population - self.set_all_fitness(self.population.chromosome_list) + chromosome_set_temp = chromosome_set + not_sorted_check = 0 + while (not_sorted_check != len(chromosome_set_temp)): + not_sorted_check = 0 + for i in range(len(chromosome_set_temp)): + if ((i + 1 < len(chromosome_set_temp)) and (chromosome_set_temp[i + 1].fitness > chromosome_set_temp[i].fitness)): + temp = chromosome_set[i] + chromosome_set_temp[i] = chromosome_set[i + 1] + chromosome_set_temp[i + 1] = temp + else: + not_sorted_check += 1 - number_of_generations -= 1 - self.current_generation += 1 + chromosome_set = chromosome_set_temp - def active(self): - """Returns if the ga should terminate base on the termination implimented""" - return self.termination_impl(self) + return chromosome_set def make_gene(self,value): return create_gene(value) diff --git a/src/crossover/crossover_methods.py b/src/crossover/crossover_methods.py index 4e2b1d1..48e55bf 100644 --- a/src/crossover/crossover_methods.py +++ b/src/crossover/crossover_methods.py @@ -14,10 +14,7 @@ class Crossover_Methods: """Single point crossover is when a "point" is selected and the genetic make up of the two parent chromosomes are "Crossed" or better known as swapped""" - crossover_pool = [] - for i in range(ga.population_size): - if ga.population.get_all_chromosomes()[i].selected: - crossover_pool.append(ga.population.get_all_chromosomes()[i]) + crossover_pool = ga.population.mating_pool new_population = Population() for i in range(len(crossover_pool)): diff --git a/src/fitness_function/fitness_examples.py b/src/fitness_function/fitness_examples.py index 8e29245..1354e9e 100644 --- a/src/fitness_function/fitness_examples.py +++ b/src/fitness_function/fitness_examples.py @@ -14,3 +14,15 @@ class Fitness_Examples: fitness += 1 return fitness + + def index_dependent_values(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 i in range(len(chromosome.gene_list)): + if (chromosome.gene_list[i].value == i+1): + fitness += 1 + + return fitness diff --git a/src/initialization/chromosome_structure/chromosome.py b/src/initialization/chromosome_structure/chromosome.py index 3a1978c..f9e16e6 100644 --- a/src/initialization/chromosome_structure/chromosome.py +++ b/src/initialization/chromosome_structure/chromosome.py @@ -6,7 +6,6 @@ class Chromosome: else: self.gene_list = genes self.fitness = None - self.selected = False def add_gene(self, gene, index = -1): if index == -1: diff --git a/src/initialization/population_structure/population.py b/src/initialization/population_structure/population.py index 0f414a9..94bdcb7 100644 --- a/src/initialization/population_structure/population.py +++ b/src/initialization/population_structure/population.py @@ -7,6 +7,7 @@ class Population: else: self.chromosome_list = chromosomes self.fitness = None + self.mating_pool = [] def get_closet_fitness(self,value): # Get the chomosome that has the closets fitness to the value defined diff --git a/src/run_testing.py b/src/run_testing.py index 390986b..2381627 100644 --- a/src/run_testing.py +++ b/src/run_testing.py @@ -13,4 +13,4 @@ ga.gene_impl = [random.randrange,1,100] ga.evolve() # Print the current population -ga.population.print_all() +ga.population.print_all() \ No newline at end of file diff --git a/src/selection/selection_methods.py b/src/selection/selection_methods.py index 201d468..953b117 100644 --- a/src/selection/selection_methods.py +++ b/src/selection/selection_methods.py @@ -12,68 +12,33 @@ class Selection_Methods: class Parent_Selection: class Tournament: def with_replacement(self, ga): - tournament_size = int(len(ga.population.get_all_chromosomes())/10) #currently hard-coded for purposes of the example. + tournament_size = int(len(ga.population.get_all_chromosomes())*ga.parent_ratio/10) if tournament_size < 3: - tournament_size = int(len(ga.population.get_all_chromosomes())/3) - parent_ratio = 0.25 - - #selection_probability is the likelihood that a chromosome will be selected. - #best chromosome in a tournament is given a selection probablity of selection_probability - #2nd best is given probability of selection_probability*(1-selection_probability) - #3rd best is given probability of selection_probability*(1-selection_probability)**2 + tournament_size = int(len(ga.population.get_all_chromosomes())*ga.parent_ratio/3) + + # Probability used for determining if a chromosome should enter the mating pool. selection_probability = 0.95 - total_selected = 0 #Total Chromosomes selected - - while (total_selected < parent_ratio*ga.population_size): - #create & gather tournament group - tournament_group = [] - - for i in range(tournament_size): - tournament_group.append(random.choice(ga.population.get_all_chromosomes())) + + # Repeat tournaments until the mating pool is large enough. + while (len(ga.population.mating_pool) < len(ga.population.get_all_chromosomes())*ga.parent_ratio): - #Sort the tournament contenders based on their fitness - #currently hard-coded to only consider higher fitness = better; can be changed once this impl is agreed on - #also currently uses bubble sort because its easy - tournament_group_temp = tournament_group - not_sorted_check = 0 - while (not_sorted_check != len(tournament_group_temp)): - not_sorted_check = 0 - for i in range(len(tournament_group_temp)): - if ((i + 1 < len(tournament_group_temp)) and (tournament_group_temp[i + 1].fitness > tournament_group_temp[i].fitness)): - temp = tournament_group[i] - tournament_group_temp[i] = tournament_group[i + 1] - tournament_group_temp[i + 1] = temp - else: - not_sorted_check += 1 - - tournament_group = tournament_group_temp - - #After sorting by fitness, randomly select a chromosome based on selection_probability - selected_chromosome_tournament_index = 0 - for i in range(tournament_size): - random_num = random.uniform(0,1) - - #ugly implementation but its functional - if i == 0: - if random_num <= selection_probability: - tournament_group[i].selected = True - total_selected += 1 - selected_chromosome_tournament_index = i - break - else: - if random_num <= selection_probability*((1-selection_probability)**(i-1)): - tournament_group[i].selected = True - total_selected += 1 - selected_chromosome_tournament_index = i - break + # Generate a random tournament group and sort by fitness. + tournament_group = ga.sort_by_best_fitness([random.choice(ga.population.get_all_chromosomes()) for n in range(tournament_size)]) + + # For each chromosome, add it to the mating pool based on its rank in the tournament. + for index in range(tournament_size): + # Probability required is selection_probability * (1-selection_probability) ^ (tournament_size-index+1) + # e.g. top ranked fitness has probability: selection_probability + # second ranked fitness has probability: selection_probability * (1-selection_probability) + # third ranked fitness has probability: selection_probability * (1-selection_probability)^2 + # etc. + if random.uniform(0, 1) < selection_probability * pow(1-selection_probability, index+1): + ga.population.mating_pool.append(tournament_group[index]) class Survivor_Selection: 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: - crossover_pool.append(ga.population.get_all_chromosomes()[i]) + crossover_pool = ga.population.mating_pool split_point = random.randint(0,ga.chromosome_length) chromosome_list = [] @@ -95,23 +60,9 @@ class Selection_Methods: 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]) + next_population.add_chromosome(ga.population.get_all_chromosomes()[iterator]) iterator += 1 return next_population From 49c98ba27c587555a289437a3052ad43761d9bc3 Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Tue, 6 Oct 2020 18:12:20 -0400 Subject: [PATCH 14/16] First generation now only does initialization stuff First generation now only does initialization stuff --- src/EasyGA.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/EasyGA.py b/src/EasyGA.py index 159c986..04e87b8 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -58,15 +58,15 @@ class GA: self.initialize_population() self.set_all_fitness(self.population.chromosome_list) self.population.set_all_chromosomes(self.sort_by_best_fitness()) - - 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())) + else: + 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) - self.population.set_all_chromosomes(self.sort_by_best_fitness()) + self.population = next_population + self.set_all_fitness(self.population.chromosome_list) + self.population.set_all_chromosomes(self.sort_by_best_fitness()) number_of_generations -= 1 self.current_generation += 1 From 2f78c9f464275380c64d0b165301d453703733ce Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Tue, 6 Oct 2020 18:46:34 -0400 Subject: [PATCH 15/16] Roulette Selection added --- src/EasyGA.py | 15 ++++++------ src/selection/selection_methods.py | 37 ++++++++++++++++++++++-------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/EasyGA.py b/src/EasyGA.py index 04e87b8..d94fca4 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -23,28 +23,29 @@ class GA: self.gene_impl = None self.population = None self.target_fitness_type = 'maximum' + self.update_fitness = True + # Selection variables self.parent_ratio = 0.1 - # Termination varibles + + # Termination variables self.current_generation = 0 self.generation_goal = 50 self.current_fitness = 0 self.generation_goal = 250 self.fitness_goal = 9 + # Mutation variables - self.mutation_rate = 0.10 + self.mutation_rate = 0.10 - # Rerun already computed fitness - self.update_fitness = True - - # Defualt EastGA implimentation structure + # Default EasyGA implimentation structure self.initialization_impl = Initialization_Methods().random_initialization self.fitness_function_impl = Fitness_Examples().index_dependent_values # 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.parent_selection_impl = Selection_Methods().Parent_Selection().Roulette().roulette_selection self.crossover_impl = Crossover_Methods().single_point_crossover self.mutation_impl = Mutation_Methods().per_gene_mutation # The type of termination to impliment diff --git a/src/selection/selection_methods.py b/src/selection/selection_methods.py index 953b117..75c7928 100644 --- a/src/selection/selection_methods.py +++ b/src/selection/selection_methods.py @@ -35,6 +35,34 @@ class Selection_Methods: if random.uniform(0, 1) < selection_probability * pow(1-selection_probability, index+1): ga.population.mating_pool.append(tournament_group[index]) + class Roulette: + 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 + that it will be selected. Using the example of a casino roulette wheel. + Where the chromosomes are the numbers to be selected and the board size for + those numbers are directly proportional to the chromosome's current fitness. Where + the ball falls is a randomly generated number between 0 and 1""" + total_fitness = sum(ga.population.chromosome_list[i].get_fitness() for i in range(len(ga.population.chromosome_list))) + rel_fitnesses = [] + + for chromosome in ga.population.chromosome_list: + if (total_fitness != 0): + rel_fitnesses.append(float(chromosome.fitness)/total_fitness) + + probability = [sum(rel_fitnesses[:i+1]) for i in range(len(rel_fitnesses))] + + while (len(ga.population.mating_pool) < len(ga.population.get_all_chromosomes())*ga.parent_ratio): + rand_number = random.random() + + # Loop through the list of probabilities + for i in range(len(probability)): + # If the probability is greater than the random_number, then select that chromosome + if (probability[i] >= rand_number): + ga.population.mating_pool.append(ga.population.chromosome_list[i]) + # print (f'Selected chromosome : {i}') + break + class Survivor_Selection: 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: @@ -65,12 +93,3 @@ class Selection_Methods: next_population.add_chromosome(ga.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 - that it will be selected. Using the example of a casino roulette wheel. - Where the chromosomes are the numbers to be selected and the board size for - those numbers are directly proportional to the chromosome's current fitness. Where - the ball falls is a randomly generated number between 0 and 1""" - pass From af88e4c348943f004d090f04096199fb1820dca3 Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Tue, 6 Oct 2020 20:58:20 -0400 Subject: [PATCH 16/16] Changed some hard-coded stuf to GA attribute Changed some hard-coded stuf to GA attribute --- src/EasyGA.py | 2 ++ src/selection/selection_methods.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/EasyGA.py b/src/EasyGA.py index d94fca4..575d5b3 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -27,6 +27,7 @@ class GA: # Selection variables self.parent_ratio = 0.1 + self.selection_probablity = 0.95 # Termination variables self.current_generation = 0 @@ -50,6 +51,7 @@ class GA: 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.""" diff --git a/src/selection/selection_methods.py b/src/selection/selection_methods.py index 75c7928..c8476a6 100644 --- a/src/selection/selection_methods.py +++ b/src/selection/selection_methods.py @@ -17,7 +17,7 @@ class Selection_Methods: tournament_size = int(len(ga.population.get_all_chromosomes())*ga.parent_ratio/3) # Probability used for determining if a chromosome should enter the mating pool. - selection_probability = 0.95 + selection_probability = ga.selection_probability # Repeat tournaments until the mating pool is large enough. while (len(ga.population.mating_pool) < len(ga.population.get_all_chromosomes())*ga.parent_ratio):