diff --git a/src/EasyGA.py b/src/EasyGA.py index fb32f51..e45a009 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -6,22 +6,23 @@ from structure import Chromosome as create_chromosome from structure import Gene as create_gene # Structure Methods -from fitness_function import Fitness_Examples -from initialization import Initialization_Methods +from fitness_function import Fitness_Examples +from initialization import Initialization_Methods from termination_point import Termination_Methods # Parent/Survivor Selection Methods -from parent_selection import Parent_Selection +from parent_selection import Parent_Selection from survivor_selection import Survivor_Selection # Genetic Operator Methods -from mutation import Mutation_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 = 10 @@ -33,7 +34,7 @@ class GA: # Selection variables self.parent_ratio = 0.1 - self.selection_probability = 0.95 + self.selection_probability = 0.75 self.tournament_size_ratio = 0.1 # Termination variables @@ -54,7 +55,7 @@ class GA: # Methods for accomplishing Parent-Selection -> Crossover -> Survivor_Selection -> Mutation self.parent_selection_impl = Parent_Selection.Tournament.with_replacement - self.crossover_individual_impl = Crossover_Methods.Individual.single_point_crossover + self.crossover_individual_impl = Crossover_Methods.Individual.single_point self.crossover_population_impl = Crossover_Methods.Population.random_selection self.survivor_selection_impl = Survivor_Selection.fill_in_best self.mutation_individual_impl = Mutation_Methods.Individual.single_gene @@ -82,7 +83,7 @@ class GA: else: self.population.reset_mating_pool() self.set_all_fitness() - self.population.set_all_chromosomes(self.sort_by_best_fitness(self.population.get_all_chromosomes())) + self.population.sort_by_best_fitness(self) self.parent_selection_impl(self) next_population = self.crossover_population_impl(self) self.survivor_selection_impl(self, next_population) @@ -119,7 +120,7 @@ class GA: """ # Check each chromosome - for chromosome in self.population.get_all_chromosomes(): + for chromosome in self.population.get_chromosome_list(): # Update fitness if needed or asked by the user if(chromosome.get_fitness() is None or self.update_fitness): diff --git a/src/crossover/crossover_methods.py b/src/crossover/crossover_methods.py index ff4ce64..4ad0121 100644 --- a/src/crossover/crossover_methods.py +++ b/src/crossover/crossover_methods.py @@ -27,13 +27,56 @@ class Crossover_Methods: class Individual: """Methods for crossing parents""" - def single_point_crossover(ga, parent_one, parent_two): + def single_point(ga, parent_one, parent_two): """Cross two parents by swapping genes at one random point""" index = random.randint(0, parent_one.size()-1) return ga.make_chromosome(parent_one.get_gene_list()[:index] + parent_two.get_gene_list()[index:]) - def multi_point_crossover(ga, parent_one, parent_two): + def multi_point(ga, parent_one, parent_two): """Cross two parents by swapping genes at multiple points""" pass + + + def uniform(ga, parent_one, parent_two): + """Cross two parents by swapping all genes randomly""" + return ga.make_chromosome([ + random.choice([parent_one.get_gene(i), parent_two.get_gene(i)]) + for i in range(parent_one.size())]) + + class Arithmetic: + """Crossover methods for numerical genes""" + + def int_random(ga, parent_one, parent_two): + """Cross two parents by taking a random integer value between each of the genes""" + return ga.make_chromosome([ + ga.make_gene(random.randint(*sorted([parent_one.get_gene(i).get_value(), parent_two.get_gene(i).get_value()]))) + for i in range(parent_one.size())]) + + + def int_weighted(ga, parent_one, parent_two): + """Cross two parents by taking a a weighted average of the genes""" + + # the percentage of genes taken from the first gene + weight = 0.25 + return ga.make_chromosome([ + ga.make_gene(int(weight*parent_one.get_gene(i).get_value()+(1-weight)*parent_two.get_gene(i).get_value())) + for i in range(parent_one.size())]) + + + def float_random(ga, parent_one, parent_two): + """Cross two parents by taking a random numeric value between each of the genes""" + return ga.make_chromosome([ + ga.make_gene(random.uniform(parent_one.get_gene(i).get_value(), parent_two.get_gene(i).get_value())) + for i in range(parent_one.size())]) + + + def float_weighted(ga, parent_one, parent_two): + """Cross two parents by taking a a weighted average of the genes""" + + # the percentage of genes taken from the first gene + weight = 0.25 + return ga.make_chromosome([ + ga.make_gene(weight*parent_one.get_gene(i).get_value()+(1-weight)*parent_two.get_gene(i).get_value()) + for i in range(parent_one.size())]) diff --git a/src/fitness_function/fitness_examples.py b/src/fitness_function/fitness_examples.py index 698e0d7..ce556d7 100644 --- a/src/fitness_function/fitness_examples.py +++ b/src/fitness_function/fitness_examples.py @@ -17,6 +17,13 @@ class Fitness_Examples: return fitness + def near_5(chromosome): + """Test's the GA's ability to handle floats. + Computes how close each gene is to 5. + """ + return sum([1-pow(1-gene.get_value()/5, 2) for gene in chromosome.get_gene_list()]) + + def index_dependent_values(chromosome): """Test of the GA's ability to improve fitness when the value is index-dependent. If a gene is equal to its index in the chromosome + 1, fitness is incremented. diff --git a/src/run_testing.py b/src/run_testing.py index 947272e..e595e00 100644 --- a/src/run_testing.py +++ b/src/run_testing.py @@ -3,15 +3,19 @@ import EasyGA # Create the Genetic algorithm ga = EasyGA.GA() -ga.population_size = 100 -ga.generation_goal = 200 +ga.population_size = 25 +ga.generation_goal = 100 +ga.gene_impl = [random.randint,0,10] +ga.selection_probability = 0.5 +ga.fitness_function_impl = EasyGA.Fitness_Examples.near_5 ga.parent_selection_impl = EasyGA.Parent_Selection.Roulette.stochastic_selection ga.crossover_population_impl = EasyGA.Crossover_Methods.Population.sequential_selection -ga.survivor_selection_impl = EasyGA.Survivor_Selection.fill_in_parents_then_random +ga.crossover_individual_impl = EasyGA.Crossover_Methods.Individual.Arithmetic.int_random +ga.survivor_selection_impl = EasyGA.Survivor_Selection.fill_in_best ga.evolve() ga.set_all_fitness() -ga.population.set_all_chromosomes(ga.sort_by_best_fitness(ga.population.get_all_chromosomes())) +ga.population.sort_by_best_fitness(ga) print(f"Current Generation: {ga.current_generation}") ga.population.print_all() diff --git a/src/structure/chromosome.py b/src/structure/chromosome.py index af4fdd8..d25080f 100644 --- a/src/structure/chromosome.py +++ b/src/structure/chromosome.py @@ -7,8 +7,6 @@ class Chromosome: self.gene_list = gene_list self.fitness = None - # If the chromosome has been selected then the flag would switch to true - self.selected = False def size(self): @@ -16,10 +14,10 @@ class Chromosome: return len(self.gene_list) - def add_gene(self, gene, index = -1): + 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 == -1: - index = len(self.gene_list) + if index is None: + index = self.size() self.gene_list.insert(index, gene) @@ -30,7 +28,7 @@ class Chromosome: def get_gene(self, index): """Returns the gene at the given index""" - return gene_list[index] + return self.gene_list[index] def get_gene_list(self): diff --git a/src/structure/population.py b/src/structure/population.py index 84def4e..dcc9f50 100644 --- a/src/structure/population.py +++ b/src/structure/population.py @@ -14,7 +14,7 @@ class Population: def sort_by_best_fitness(self, ga): """Sorts the population by fitness""" - self.set_all_chromosomes(ga.sort_by_best_fitness(self.chromosome_list)) + self.set_chromosome_list(ga.sort_by_best_fitness(self.chromosome_list)) def size(self): @@ -27,10 +27,10 @@ class Population: pass - def add_chromosome(self, chromosome, index = -1): + 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""" - if index == -1: - index = len(self.chromosome_list) + if index is None: + index = self.size() self.chromosome_list.insert(index, chromosome) @@ -64,7 +64,7 @@ class Population: return self.mating_pool[index] - def get_all_chromosomes(self): + def get_chromosome_list(self): """Returns all chromosomes in the population""" return self.chromosome_list @@ -80,11 +80,11 @@ class Population: def set_parent(self, index): - """Sets the index chromosome from the population as a parent""" + """Sets the indexed chromosome from the population as a parent""" self.add_parent(self.get_chromosome(index)) - def set_all_chromosomes(self, chromosome_list): + def set_chromosome_list(self, chromosome_list): """Sets the chromosome list""" self.chromosome_list = chromosome_list @@ -120,5 +120,5 @@ class Population: print("Current population:") for index in range(self.size()): - print(f'Chromosome - {index} {self.chromosome_list[index]}', end = "") - print(f' / Fitness = {self.chromosome_list[index].get_fitness()}') + print(f'Chromosome - {index} {self.get_chromosome(index)}', end = "") + print(f' / Fitness = {self.get_chromosome(index).get_fitness()}') diff --git a/src/survivor_selection/survivor_selection_methods.py b/src/survivor_selection/survivor_selection_methods.py index 143cf83..284486a 100644 --- a/src/survivor_selection/survivor_selection_methods.py +++ b/src/survivor_selection/survivor_selection_methods.py @@ -6,22 +6,22 @@ class Survivor_Selection: def fill_in_best(ga, next_population): """Fills in the next population with the best chromosomes from the last population""" - ga.population.set_all_chromosomes(ga.population.get_all_chromosomes()[:ga.population.size()-next_population.size()] + next_population.get_all_chromosomes()) + ga.population.set_chromosome_list(ga.population.get_chromosome_list()[:ga.population.size()-next_population.size()] + next_population.get_chromosome_list()) def fill_in_random(ga, next_population): """Fills in the next population with random chromosomes from the last population""" - ga.population.set_all_chromosomes([ - random.choice(ga.population.get_all_chromosomes()) + ga.population.set_chromosome_list([ + random.choice(ga.population.get_chromosome_list()) for n in range(ga.population.size()-next_population.size())] - + next_population.get_all_chromosomes()) + + next_population.get_chromosome_list()) def fill_in_parents_then_random(ga, next_population): """Fills in the next population with all parents followed by random chromosomes from the last population""" - ga.population.set_all_chromosomes([ - random.choice(ga.population.get_all_chromosomes()) + ga.population.set_chromosome_list([ + random.choice(ga.population.get_chromosome_list()) for n in range(ga.population.size()-len(ga.population.get_mating_pool())-next_population.size())] - + ga.population.get_mating_pool() + next_population.get_all_chromosomes()) + + ga.population.get_mating_pool() + next_population.get_chromosome_list())