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