From 0ee545429c1418011aa8cd06e03d48e4fc698be4 Mon Sep 17 00:00:00 2001 From: SimpleArt <71458112+SimpleArt@users.noreply.github.com> Date: Thu, 19 Nov 2020 11:46:47 -0500 Subject: [PATCH] Added iterable features --- src/EasyGA.py | 26 +++++--- src/crossover/crossover_methods.py | 58 ++++++++++------- src/mutation/mutation_methods.py | 35 +++++----- .../parent_selection_methods.py | 18 ++--- src/structure/chromosome.py | 31 ++++++--- src/structure/population.py | 65 +++++++++++++++---- .../survivor_selection_methods.py | 31 ++++----- 7 files changed, 167 insertions(+), 97 deletions(-) diff --git a/src/EasyGA.py b/src/EasyGA.py index 8e5cfc1..eb77bb9 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -110,15 +110,15 @@ class GA(Attributes): chromosome.set_fitness(self.fitness_function_impl(chromosome)) - def sort_by_best_fitness(self, chromosome_set): - """Sorts the array by fitness based on fitness type. + def sort_by_best_fitness(self, chromosome_list): + """Sorts the chromosome list by fitness based on fitness type. 1st element has best fitness. 2nd element has second best fitness. etc. """ return sorted( - chromosome_set, # list to be sorted + chromosome_list, # list to be sorted key = lambda chromosome: chromosome.get_fitness(), # by fitness reverse = (self.target_fitness_type == 'max') # ordered by fitness type ) @@ -130,7 +130,7 @@ class GA(Attributes): on the target fitness type. """ return self.convert_fitness( - self.population.get_chromosome(index).get_fitness() + self.population[index].get_fitness() ) @@ -140,11 +140,17 @@ class GA(Attributes): inverted using max - value + min. """ + # No conversion needed if self.target_fitness_type == 'max': return fitness_value - max_fitness = self.population.get_chromosome(-1).get_fitness() - min_fitness = self.population.get_chromosome(0).get_fitness() + + max_fitness = self.population[-1].get_fitness() + min_fitness = self.population[0].get_fitness() + + # Avoid catastrophic cancellation if min_fitness / max_fitness < 1e-5: return -fitness_value + + # Otherwise flip values else: return max_fitness - fitness_value + min_fitness @@ -161,11 +167,11 @@ class GA(Attributes): def print_best(self): """Prints the best chromosome and its fitness""" - print(f"Best Chromosome \t: {self.population.get_chromosome(0)}") - print(f"Best Fitness \t: {self.population.get_chromosome(0).get_fitness()}") + print(f"Best Chromosome \t: {self.population[0]}") + print(f"Best Fitness \t: {self.population[0].get_fitness()}") def print_worst(self): """Prints the worst chromosome and its fitness""" - print(f"Worst Chromosome \t: {self.population.get_chromosome(-1)}") - print(f"Worst Fitness \t: {self.population.get_chromosome(-1).get_fitness()}") + print(f"Worst Chromosome \t: {self.population[-1]}") + print(f"Worst Fitness \t: {self.population[-1].get_fitness()}") diff --git a/src/crossover/crossover_methods.py b/src/crossover/crossover_methods.py index fb02777..a4476ac 100644 --- a/src/crossover/crossover_methods.py +++ b/src/crossover/crossover_methods.py @@ -43,14 +43,14 @@ class Crossover_Methods: class Individual: """Methods for crossing parents.""" - def single_point(ga, parent_one, parent_two): + def single_point(ga, parent_1, parent_2): """Cross two parents by swapping genes at one random point.""" - swap_index = random.randint(0, parent_one.size()-1) - return ga.make_chromosome(parent_one.get_gene_list()[:swap_index] + parent_two.get_gene_list()[swap_index:]) + swap_index = random.randint(0, len(parent_1)-1) + return ga.make_chromosome(parent_1[:swap_index] + parent_2[swap_index:]) - def multi_point(ga, parent_one, parent_two): + def multi_point(ga, parent_1, parent_2): """Cross two parents by swapping genes at multiple points.""" pass @@ -58,9 +58,9 @@ class Crossover_Methods: def uniform(ga, parent_1, parent_2): """Cross two parents by swapping all genes randomly.""" - return ga.make_chromosome([ # Make a new chromosome - random.choice([gene_1, gene_2]) # by randomly selecting genes - for gene_1, gene_2 in zip(parent_1.gene_list, parent_2.gene_list)]) # from each parent + return ga.make_chromosome([ # Make a new chromosome + random.choice([gene_1, gene_2]) # by randomly selecting genes + for gene_1, gene_2 in zip(parent_1, parent_2)]) # from each parent class Arithmetic: @@ -69,9 +69,12 @@ class Crossover_Methods: def int_random(ga, parent_1, parent_2): """Cross two parents by taking a random integer value between each of the genes.""" - return ga.make_chromosome([ # Make a new chromosome - ga.make_gene(random.randint(*sorted([data_1, data_2]))) # by randomly selecting integer genes between - for data_1, data_2 in zip(iter(parent_1), iter(parent_2))]) # each parents' genes + value_list_1 = parent_1.gene_value_list + value_list_2 = parent_2.gene_value_list + + return ga.make_chromosome([ # Make a new chromosome + ga.make_gene(random.randint(*sorted([value_1, value_2]))) # by randomly selecting integer genes between + for value_1, value_2 in zip(value_list_1, value_list_2)]) # each parents' genes def int_weighted(ga, parent_1, parent_2): @@ -80,19 +83,25 @@ class Crossover_Methods: # the percentage of genes taken from the first gene weight = 0.25 - return ga.make_chromosome([ # Make a new chromosome - ga.make_gene(int( # filled with new integer genes - weight*data_1+(1-weight)*data_2 # with weight% from gene 1 and - )) # (100-weight)% from gene 2 - for data_1, data_2 in zip(iter(parent_1), iter(parent_2))]) # from each parents' genes + value_list_1 = parent_1.gene_value_list + value_list_2 = parent_2.gene_value_list + + return ga.make_chromosome([ # Make a new chromosome + ga.make_gene(int( # filled with new integer genes + weight*value_1+(1-weight)*value_2 # with weight% from gene 1 and + )) # (100-weight)% from gene 2 + for value_1, value_2 in zip(value_list_1, value_list_2)]) # from each parents' genes def float_random(ga, parent_one, parent_two): """Cross two parents by taking a random numeric value between each of the genes.""" - return ga.make_chromosome([ # Make a new chromosome - ga.make_gene(random.uniform([data_1, data_2])) # by randomly selecting integer genes between - for data_1, data_2 in zip(iter(parent_1), iter(parent_2))]) # from each parents' genes + value_list_1 = parent_1.gene_value_list + value_list_2 = parent_2.gene_value_list + + return ga.make_chromosome([ # Make a new chromosome + ga.make_gene(random.uniform([value_1, value_2])) # by randomly selecting integer genes between + for value_1, value_2 in zip(value_list_1, value_list_2)]) # from each parents' genes def float_weighted(ga, parent_one, parent_two): @@ -101,8 +110,11 @@ class Crossover_Methods: # the percentage of genes taken from the first gene weight = 0.25 - return ga.make_chromosome([ # Make a new chromosome - ga.make_gene( # filled with new float genes - weight*data_1+(1-weight)*data_2 # with weight% from gene 1 and - ) # (100-weight)% from gene 2 - for data_1, data_2 in zip(iter(parent_1), iter(parent_2))]) # from each parents' genes + value_list_1 = parent_1.gene_value_list + value_list_2 = parent_2.gene_value_list + + return ga.make_chromosome([ # Make a new chromosome + ga.make_gene( # filled with new float genes + weight*value_1+(1-weight)*value_2 # with weight% from gene 1 and + ) # (100-weight)% from gene 2 + for value_1, value_2 in zip(value_list_1, value_list_2)]) # from each parents' genes diff --git a/src/mutation/mutation_methods.py b/src/mutation/mutation_methods.py index 8ff352b..a6cc483 100644 --- a/src/mutation/mutation_methods.py +++ b/src/mutation/mutation_methods.py @@ -10,24 +10,21 @@ class Mutation_Methods: """Selects random chromosomes""" # Loop until enough mutations occur - for n in range(ceil(ga.population.size()*ga.chromosome_mutation_rate)): - index = random.randint(0, ga.population.size()-1) - ga.population.set_chromosome(ga.mutation_individual_impl(ga, ga.population.get_chromosome(index)), index) + for n in range(ceil(len(ga.population)*ga.chromosome_mutation_rate)): + index = random.randint(0, len(ga.population)-1) + ga.population[index] = ga.mutation_individual_impl(ga, ga.population[index]) def random_selection_then_cross(ga): """Selects random chromosomes and self-crosses with parent""" # Loop until enough mutations occur - for n in range(ceil(ga.population.size()*ga.chromosome_mutation_rate)): - index = random.randint(0, ga.population.size()-1) - chromosome = ga.population.get_chromosome(index) + for n in range(ceil(len(ga.population)*ga.chromosome_mutation_rate)): + index = random.randint(0, len(ga.population)-1) + chromosome = ga.population[index] # Cross the chromosome with its mutation - ga.population.set_chromosome( - ga.crossover_individual_impl(ga, chromosome, ga.mutation_individual_impl(ga, chromosome)), - index - ) + ga.population[index] = ga.crossover_individual_impl(ga, chromosome, ga.mutation_individual_impl(ga, chromosome)) class Individual: @@ -39,16 +36,16 @@ class Mutation_Methods: chromosome = ga.make_chromosome(old_chromosome.get_gene_list()) # Loops until enough mutations occur - for n in range(ceil(chromosome.size()*ga.gene_mutation_rate)): - index = random.randint(0, chromosome.size()-1) + for n in range(ceil(len(chromosome)*ga.gene_mutation_rate)): + index = random.randint(0, len(chromosome)-1) # Using the chromosome_impl if ga.chromosome_impl is not None: - chromosome.set_gene(ga.make_gene(ga.chromosome_impl()[index]), index) + chromosome[index] = ga.make_gene(ga.chromosome_impl()[index]) # Using the gene_impl elif ga.gene_impl is not None: - chromosome.set_gene(ga.make_gene(ga.gene_impl()), index) + chromosome[index] = ga.make_gene(ga.gene_impl()) # Exit because no gene creation method specified else: @@ -67,14 +64,14 @@ class Mutation_Methods: chromosome = ga.make_chromosome(old_chromosome.get_gene_list()) # Loops until enough mutations occur - for n in range(ceil(chromosome.size()*ga.gene_mutation_rate)): - index_one = random.randint(0, chromosome.size()-1) - index_two = random.randint(0, chromosome.size()-1) + for n in range(ceil(len(chromosome)*ga.gene_mutation_rate)): + index_one = random.randint(0, len(chromosome)-1) + index_two = random.randint(0, len(chromosome)-1) gene_one = chromosome.get_gene(index_one) gene_two = chromosome.get_gene(index_two) - chromosome.set_gene(gene_one, index_two) - chromosome.set_gene(gene_two, index_one) + chromosome[index_two] = gene_one + chromosome[index_one] = gene_two return chromosome diff --git a/src/parent_selection/parent_selection_methods.py b/src/parent_selection/parent_selection_methods.py index faf2e82..00604c7 100644 --- a/src/parent_selection/parent_selection_methods.py +++ b/src/parent_selection/parent_selection_methods.py @@ -21,15 +21,15 @@ class Parent_Selection: # Choose the tournament size. # Use no less than 5 chromosomes per tournament. - tournament_size = int(ga.population.size()*ga.tournament_size_ratio) + tournament_size = int(len(ga.population)*ga.tournament_size_ratio) if tournament_size < 5: tournament_size = 5 # Repeat tournaments until the mating pool is large enough. - while (len(ga.population.get_mating_pool()) < ga.population.size()*ga.parent_ratio): + while (len(ga.population.get_mating_pool()) < len(ga.population)*ga.parent_ratio): # Generate a random tournament group and sort by fitness. - tournament_group = sorted([random.randint(0, ga.population.size()-1) for n in range(tournament_size)]) + tournament_group = sorted([random.randint(0, len(ga.population)-1) 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): @@ -43,7 +43,7 @@ class Parent_Selection: ga.population.set_parent(tournament_group[index]) # Stop if parent ratio reached - if len(ga.population.get_mating_pool()) >= ga.population.size()*ga.parent_ratio: + if len(ga.population.get_mating_pool()) >= len(ga.population)*ga.parent_ratio: break @@ -73,19 +73,19 @@ class Parent_Selection: return # The sum of all the fitnessess in a population - fitness_sum = sum(ga.get_chromosome_fitness(index) for index in range(ga.population.size())) + fitness_sum = sum(ga.get_chromosome_fitness(index) for index in range(len(ga.population))) # A list of ranges that represent the probability of a chromosome getting chosen probability = [ga.selection_probability] # The chance of being selected increases incrementally - for index in range(ga.population.size()): + for index in range(len(ga.population)): probability.append(probability[-1]+ga.get_chromosome_fitness(index)/fitness_sum) probability = probability[1:] # Loops until it reaches a desired mating pool size - while (len(ga.population.get_mating_pool()) < ga.population.size()*ga.parent_ratio): + while (len(ga.population.get_mating_pool()) < len(ga.population)*ga.parent_ratio): # Spin the roulette rand_number = random.random() @@ -121,10 +121,10 @@ class Parent_Selection: return # Loops until it reaches a desired mating pool size - while (len(ga.population.get_mating_pool()) < ga.population.size()*ga.parent_ratio): + while (len(ga.population.get_mating_pool()) < len(ga.population)*ga.parent_ratio): # Selected chromosome - index = random.randint(0, ga.population.size()-1) + index = random.randint(0, len(ga.population)-1) # Probability of becoming a parent is fitness/max_fitness if random.uniform(ga.selection_probability, 1) < ga.get_chromosome_fitness(index)/max_fitness: diff --git a/src/structure/chromosome.py b/src/structure/chromosome.py index 26ce253..1b3eea5 100644 --- a/src/structure/chromosome.py +++ b/src/structure/chromosome.py @@ -10,11 +10,6 @@ class Chromosome: self.fitness = None - def size(self): - """Returns the number of genes in the chromosome""" - return len(self.gene_list) - - def add_gene(self, gene, index = None): """Add a gene to the chromosome at the specified index, defaulted to end of the chromosome""" if index is None: @@ -54,10 +49,30 @@ class Chromosome: self.fitness = fitness + @property + def gene_value_list(self): + """Returns a list of gene values""" + return [gene.value for gene in self] + + def __iter__(self): - """Returns an iterable of the gene values""" - for gene in self.gene_list: - yeild gene.value + """Returns an iterable of the gene list""" + return iter(self.gene_list) + + + def __getitem__(self, k): + """Returns the k-th gene""" + return self.get_gene(k) + + + def __setitem__(self, k, gene): + """Sets the k-th gene value""" + self.set_gene(gene, k) + + + def __len__(self): + """Returns the number of genes in the chromosome""" + return len(self.gene_list) def __repr__(self): diff --git a/src/structure/population.py b/src/structure/population.py index a65a7a4..b7ca342 100644 --- a/src/structure/population.py +++ b/src/structure/population.py @@ -55,24 +55,26 @@ class Population: self.set_chromosome_list(ga.sort_by_best_fitness(self.chromosome_list)) - def size(self): - """Returns the size of the population""" - return len(self.chromosome_list) - - + @property def total_children(self): """Returns the size of the next population""" return len(self.next_population) + @property + def total_parents(self): + """Returns the size of the mating pool""" + return len(self.mating_pool) + + def get_closet_fitness(self, value): """Get the chomosome that has the closets fitness to the value defined""" pass def add_chromosome(self, chromosome, index = None): - """Adds a chromosome to the population at the input index, defaulted - to the end of the chromosome set""" + """Adds a chromosome to the population at the input index, + defaulted to the end of the chromosome set""" if index is None: index = self.size() @@ -150,15 +152,52 @@ class Population: def __iter__(self): - """Returns an iterable of chromosome iterables""" - for chromosome in self.chromosome_list: - yield iter(chromosome) + """Returns an iterable of chromosomes""" + return iter(self.chromosome_list) + + + def __getitem__(self, k): + """Returns the k-th chromosome""" + return self.get_chromosome(k) + + + def __setitem__(self, k, chromosome): + """Sets the k-th chromosome""" + self.set_chromosome(chromosome, k) + + + def __len__(self): + """Returns the number of chromosomes in the current population""" + return len(self.chromosome_list) + + + def __contains__(self, searched_chromosome): + """Returns True if the current population contains the chromosome and False otherwise. + Ex. if chromosome in ga.population: ...""" + + for index in range(len(self)): + if self[index] == searched_chromosome: + return True + else: + return False + + + def index_of(self, searched_chromosome): + """Returns the index of the chromosome in the current population. + Returns -1 if no index found.""" + + for index in range(len(self)): + if self[index] == searched_chromosome: + return index + else: + return -1 def __repr__(self): """Returns a backend string representation of the entire population""" + return ''.join( - f'Chromosome - {index} {self.get_chromosome(index)} ' + - f'/ Fitness = {self.get_chromosome(index).get_fitness()}\n' - for index in range(self.size()) + f'Chromosome - {self.index_of(chromosome)} {chromosome} ' + + f'/ Fitness = {chromosome.get_fitness()}\n' + for chromosome in self ) diff --git a/src/survivor_selection/survivor_selection_methods.py b/src/survivor_selection/survivor_selection_methods.py index ce88362..78f5420 100644 --- a/src/survivor_selection/survivor_selection_methods.py +++ b/src/survivor_selection/survivor_selection_methods.py @@ -6,37 +6,38 @@ class Survivor_Selection: def fill_in_best(ga): """Fills in the next population with the best chromosomes from the last population""" - needed_amount = ga.population.size()-len(ga.population.next_population) + needed_amount = len(ga.population) - ga.population.total_children + ga.population.append_children( - ga.population.get_chromosome_list()[:needed_amount] + ga.population[:needed_amount] ) def fill_in_random(ga): """Fills in the next population with random chromosomes from the last population""" - needed_amount = ga.population.size()-ga.population.total_children() + needed_amount = len(ga.population) - ga.population.total_children - ga.population.append_children([ # add in chromosomes - random.choice( # randomly - ga.population.get_chromosome_list() # from the population - ) # - for n in range(needed_amount)]) # until the next population is full + ga.population.append_children([ # add in chromosomes + random.choice( # randomly + ga.population # from the population + ) # + for n in range(needed_amount)]) # until the next population is full def fill_in_parents_then_random(ga): """Fills in the next population with all parents followed by random chromosomes from the last population""" - needed_amount = ga.population.size()-ga.population.total_children() - parent_amount = max(len(ga.population.get_mating_pool()), needed_amount) + needed_amount = len(ga.population) - ga.population.total_children + parent_amount = max(len(ga.population.total_parents), needed_amount) random_amount = needed_amount - parent_amount ga.population.append_children( # add in chromosomes ga.population.get_mating_pool()[:parent_amount] # from the mating pool ) # - ga.population.append_children([ # add in chromosomes - random.choice( # randomly - ga.population.get_chromosome_list() # from the population - ) # - for n in range(random_amount)]) # until the next population is full + ga.population.append_children([ # add in chromosomes + random.choice( # randomly + ga.population # from the population + ) # + for n in range(random_amount)]) # until the next population is full