diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..e09bcec Binary files /dev/null and b/.DS_Store differ diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1 @@ + diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index cc2fdc5..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,19 +0,0 @@ -graft docs -graft src -graft ci -graft tests - -include .bumpversion.cfg -include .coveragerc -include .cookiecutterrc -include .editorconfig - -include AUTHORS.rst -include CHANGELOG.rst -include CONTRIBUTING.rst -include LICENSE -include README.rst - -include tox.ini .travis.yml .appveyor.yml .readthedocs.yml .pre-commit-config.yaml - -global-exclude *.py[cod] __pycache__/* *.so *.dylib diff --git a/README.md b/README.md index d371357..4624399 100644 --- a/README.md +++ b/README.md @@ -25,45 +25,7 @@ ga.evolve() Put the out here ``` -## Customized: -```python -import random -import EasyGA -# Setup the default genetic algorithm -ga = EasyGA.GA() - -# User set sizes -ga.population_size = 10 -ga.chromosome_length = 10 -ga.generations = 10 - -# The user defined gene function -def user_gene_function(): - pass - -# The user defined Fitness function that gives the chromosome some kind of fitness -def user_fitness_function(chromosome): - pass - -# The user defined initialization function -def user_initialization_function(): - pass - -# User sets the gene function -ga.gene = user_gene_function -# Set the fitness functions -ga.fitness = user_fitness_function -# Changing the initialization function. -ga.initialization = user_initialization_function -# Run the customized genetic algorithm -ga.eveolve() -``` - -### Output: -```python -Put the out here -``` # How Testing works diff --git a/setup.py b/setup.py index 157dce4..2983dfa 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,21 @@ -from setuptools import setup +from setuptools import setup, find_packages with open("README.md", "r") as fh: long_description = fh.read() + setup( name='EasyGA', - version='0.0.8', + version='0.0.25', description='A ubiquitous or general purpuse GA', py_modules=["EasyGA"], - package_dir={'':'src'}, + packages=find_packages(where='EasyGA'), + package_dir={ + '': 'EasyGA', + }, python_requires='>=3.6', url="https://github.com/danielwilczak101/EasyGA", - author="Daniel Wilczak", + author="Daniel Wilczak, Jack RyanNguyen, Ryley Griffith, Jared Curtis, Matthew Chase Oxamendi", author_email="danielwilczak101@gmail.com", long_description = long_description, long_description_content_type = "text/markdown", diff --git a/src/EasyGA.py b/src/EasyGA.py index a415e42..1ae5d60 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -1,52 +1,111 @@ import random -# 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 functionality defaults -from initialization.random_initialization import random_initialization - +# 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_examples +from termination_point import termination_examples +from selection import selection_examples +from crossover import crossover_examples +from mutation import mutation_examples class GA: def __init__(self): - # Default variables + """Initialize the GA.""" + # Initilization variables + self.chromosome_length = 3 + self.population_size = 5 self.chromosome_impl = None self.gene_impl = None self.population = None - self.generations = 3 - self.chromosome_length = 3 - self.population_size = 5 + # Termination varibles + self.current_generation = 0 + self.current_fitness = 0 + self.generation_goal = 3 + self.fitness_goal = 3 + # Mutation variables self.mutation_rate = 0.03 + + # Rerun already computed fitness + self.update_fitness = False + # Defualt EastGA implimentation structure - self.initialization_impl = random_initialization + self.initialization_impl = initialization_examples.random_initialization + self.fitness_funciton_impl = fitness_examples.is_it_5 #self.mutation_impl = PerGeneMutation(Mutation_rate) #self.selection_impl = TournamentSelection() #self.crossover_impl = FastSinglePointCrossover() - #self.termination_impl = GenerationTermination(Total_generations) - #self.evaluation_impl = TestEvaluation() + self.termination_impl = termination_examples.generation_based - def initialize(self): + def initialize_population(self): + """Initialize the population using the initialization + implimentation that is currently set""" self.population = self.initialization_impl( self.population_size, self.chromosome_length, self.chromosome_impl, self.gene_impl) - def evolve(): - # If you just want to evolve through all generations - pass + def get_population_fitness(self,population): + """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 population: + # If the fitness is not set then get its fitness or if allways getting + # fitness is turn on then always get the fitness of the chromosome. + if(chromosome.fitness == None or self.update_fitness == True): + # Set the chromosomes fitness using the fitness function + chromosome.fitness = self.fitness_funciton_impl(chromosome) - 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 + + 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 evolve_generation(self, number_of_generations = 1): + """Evolves the ga the specified number of generations.""" + while(number_of_generations > 0): + # If its the first generation then initialize the population + if(self.current_generation == 0): + # Initialize the population + self.initialize_population() + # First get the fitness of the population + self.get_population_fitness(self.population.chromosomes) + # Selection - Triggers flags in the chromosome if its been selected + # self.selection_impl(self) + # Crossover - Takes the flagged chromosomes and crosses there genetic + # makup to make new offsprings. + # self.crossover_impl(self) + # Repopulate - Manipulates the population to some desired way + # self.repopulate_impl(self) + # Mutation - Manipulates the population very slightly + # self.mutation_impl(self) + + # Counter for the local number of generations in evolve_generation + number_of_generations -= 1 + # Add one to the current overall generation + self.current_generation += 1 def make_gene(self,value): + """Let's the user create a gene.""" return create_gene(value) def make_chromosome(self): + """Let's the user create a chromosome.""" return create_chromosome() def make_population(self): + """Let's the user create a population.""" return create_population() diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..6e90f58 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,3 @@ +import EasyGA +import run_testing +import test_EasyGA diff --git a/src/crossover/__init__.py b/src/crossover/__init__.py new file mode 100644 index 0000000..f9697e1 --- /dev/null +++ b/src/crossover/__init__.py @@ -0,0 +1,2 @@ +# FROM (. means local) file_name IMPORT function_name +from .examples import crossover_examples diff --git a/src/crossover/examples.py b/src/crossover/examples.py new file mode 100644 index 0000000..5b64d81 --- /dev/null +++ b/src/crossover/examples.py @@ -0,0 +1,15 @@ +class crossover_examples: + """ Crossover explination goes here. + + Points - Defined as sections between the chromosomes genetic makeup + """ + + def single_point_crossover(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""" + pass + + def multi_point_crossover(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/crossover/test_crossover.py b/src/crossover/test_examples.py similarity index 100% rename from src/crossover/test_crossover.py rename to src/crossover/test_examples.py diff --git a/src/fitness_function/__init__.py b/src/fitness_function/__init__.py new file mode 100644 index 0000000..f6294c2 --- /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..ab964e8 --- /dev/null +++ b/src/fitness_function/examples.py @@ -0,0 +1,16 @@ +class fitness_examples: + """Fitness function examples used""" + + def is_it_5(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/fitness_function/test_fitness_function.py b/src/fitness_function/test_examples.py similarity index 100% rename from src/fitness_function/test_fitness_function.py rename to src/fitness_function/test_examples.py diff --git a/src/initialization/__init__.py b/src/initialization/__init__.py new file mode 100644 index 0000000..c4790d1 --- /dev/null +++ b/src/initialization/__init__.py @@ -0,0 +1,5 @@ +# FROM (. means local) file_name IMPORT function_name +from .examples import initialization_examples +from .population_structure.population import population +from .chromosome_structure.chromosome import chromosome +from .gene_structure.gene import gene diff --git a/src/mutation/test_mutation.py b/src/initialization/chromosome_structure/__init__.py similarity index 100% rename from src/mutation/test_mutation.py rename to src/initialization/chromosome_structure/__init__.py diff --git a/src/initialization/chromosome_structure/chromosome.py b/src/initialization/chromosome_structure/chromosome.py index fecc278..aaec88f 100644 --- a/src/initialization/chromosome_structure/chromosome.py +++ b/src/initialization/chromosome_structure/chromosome.py @@ -1,37 +1,48 @@ class chromosome: - - # fitness = Empty; genes = [gene, gene, gene, etc.] + def __init__(self, genes = None): + """Initialize the chromosome based on input gene list, defaulted to an empty list""" if genes is None: self.genes = [] else: self.genes = genes + # The fitness of the overal chromosome self.fitness = None + # If the chromosome has been selected then the flag would switch to true + self.selected = False def add_gene(self, gene, index = -1): + """Add a gene to the chromosome at the specified index, defaulted to end of the chromosome""" if index == -1: index = len(self.genes) self.genes.insert(index, gene) def remove_gene(self, index): + """Remove a gene from the chromosome at the specified index""" del self.genes[index] def get_genes(self): + """Return all genes in the chromosome""" return self.genes def get_fitness(self): + """Return the fitness of the chromosome""" return self.fitness def set_gene(self, gene, index): + """Set a gene at a specific index""" self.genes[index] = gene def set_genes(self, genes): + """Set the entire gene set of the chromosome""" self.genes = genes def set_fitness(self, fitness): + """Set the fitness value of the chromosome""" self.fitness = fitness def __repr__(self): + """Format the repr() output for the chromosome""" output_str = '' for gene in self.genes: output_str += gene.__repr__() diff --git a/src/initialization/examples.py b/src/initialization/examples.py new file mode 100644 index 0000000..9257b44 --- /dev/null +++ b/src/initialization/examples.py @@ -0,0 +1,33 @@ +# 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_examples: + """Initialization examples that are used as defaults and examples""" + + def random_initialization(population_size, chromosome_length, chromosome_impl, gene_impl): + """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(population_size): + chromosome = create_chromosome() + #Fill the Chromosome with genes + for j in range(chromosome_length): + # Using the chromosome_impl to set every index inside of the chromosome + if chromosome_impl != None: + # Each chromosome location is specified with its own function + chromosome.add_gene(create_gene(chromosome_impl(j))) + # Will break if chromosome_length != len(lists) in domain + elif gene_impl != None: + # gene_impl = [range function,lowerbound,upperbound] + function = gene_impl[0] + chromosome.add_gene(create_gene(function(*gene_impl[1:]))) + else: + #Exit because either were not specified + print("Your domain or range were not specified") + population.add_chromosome(chromosome) + return population diff --git a/src/initialization/focused_initialization.py b/src/initialization/focused_initialization.py deleted file mode 100644 index 34a5e6f..0000000 --- a/src/initialization/focused_initialization.py +++ /dev/null @@ -1,16 +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 - -def focused_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 diff --git a/src/initialization/gene_structure/__init__.py b/src/initialization/gene_structure/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/initialization/gene_structure/gene.py b/src/initialization/gene_structure/gene.py index ea3b547..598b6dc 100644 --- a/src/initialization/gene_structure/gene.py +++ b/src/initialization/gene_structure/gene.py @@ -4,21 +4,28 @@ def check_gene(value): return value class gene: + def __init__(self, value): + """Initialize a gene with fitness of value None and the input value""" self.fitness = None self.value = check_gene(value) def get_fitness(self): + """Return fitness of the gene""" return self.fitness def get_value(self): + """Return value of the gene""" return self.value def set_fitness(self, fitness): + """Set fitness of the gene""" self.fitness = fitness def set_value(self): + """Set value of the gene""" self.value = value def __repr__(self): + """Format the repr() output value""" return f'[{self.value}]' diff --git a/src/initialization/population_structure/__init__.py b/src/initialization/population_structure/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/initialization/population_structure/population.py b/src/initialization/population_structure/population.py index f2167bc..18c7e38 100644 --- a/src/initialization/population_structure/population.py +++ b/src/initialization/population_structure/population.py @@ -1,7 +1,7 @@ class population: - # fitness = Empty; population = [chromosome, chromosome, etc.] def __init__(self, chromosomes = None): + """Intiialize the population with fitness of value None, and a set of chromosomes dependant on user-passed parameter""" if chromosomes is None: self.chromosomes = [] else: @@ -9,47 +9,48 @@ class population: self.fitness = None def get_closet_fitness(self,value): - # Get the chomosome that has the closets fitness to the value defined + """Get the chomosome that has the closets fitness to the value defined""" pass def add_chromosome(self, chromosome, index = -1): + """Adds a chromosome to the population at the input index, defaulted to the end of the chromosome set""" if index == -1: index = len(self.chromosomes) self.chromosomes.insert(index, chromosome) def remove_chromosome(self, index): + """removes a chromosome from the indicated index""" del self.chromosomes[index] def get_all_chromosomes(self): + """returns all chromosomes in the population""" return chromosomes def get_fitness(self): + """returns the population's fitness""" return self.fitness def set_all_chromosomes(self, chromosomes): + """sets the chromosome set of the population""" self.chromosomes = chromosomes - def set_chromosome(self, chromosomes, index): - self.chromosome[index] = chromosome + def set_chromosome(self, chromosome, index): + """sets a specific chromosome at a specific index""" + self.chromosomes[index] = chromosome def set_fitness(self, fitness): + """Sets the fitness value of the population""" self.fitness = fitness def __repr__(self): + """Sets the repr() output format""" return ''.join([chromosome.__repr__() for chromosome in self.chromosomes]) def print_all(self): - # Ex .Current population - # Chromosome 1 - [gene][gene][gene][.etc] / Chromosome fitness - # + """Prints information about the population in the following format:""" + """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) diff --git a/src/initialization/random_initialization.py b/src/initialization/random_initialization.py deleted file mode 100644 index 6484e43..0000000 --- a/src/initialization/random_initialization.py +++ /dev/null @@ -1,26 +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 - -def random_initialization(population_size, chromosome_length, chromosome_impl, gene_impl): - # 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): - if chromosome_impl != None: - # Each chromosome location is specified with its own function - chromosome.add_gene(create_gene(chromosome_impl(j))) - # Will break if chromosome_length != lists in domain - elif gene_impl != None: - # gene_impl = [range function,lowerbound,upperbound] - function = gene_impl[0] - chromosome.add_gene(create_gene(function(gene_impl[1],gene_impl[2]))) - else: - #Exit because either were not specified - print("Your domain or range were not specified") - population.add_chromosome(chromosome) - return population 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 new file mode 100644 index 0000000..c7e2fa1 --- /dev/null +++ b/src/mutation/__init__.py @@ -0,0 +1,2 @@ +# FROM (. means local) file_name IMPORT function_name +from .examples import mutation_examples diff --git a/src/mutation/examples.py b/src/mutation/examples.py new file mode 100644 index 0000000..e97b9ff --- /dev/null +++ b/src/mutation/examples.py @@ -0,0 +1,3 @@ +class mutation_examples: + """Selection examples will go here """ + 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 e05ac52..acc5f86 100644 --- a/src/run_testing.py +++ b/src/run_testing.py @@ -3,18 +3,15 @@ import random # Create the Genetic algorithm ga = EasyGA.GA() -def user_gene_domain(gene_index): - """Each gene index is assosiated to its index in the chromosome""" - chromosome = [ - random.randrange(1,100), - random.uniform(10,5), - random.choice(["up","down"]) - ] - return chromosome[gene_index] - +ga.chromosome_length = 3 +ga.max_generations = 5 # If the user wants to use a domain -ga.chromosome_impl = user_gene_domain +ga.gene_impl = [random.randrange,1,10] -ga.initialize() +ga.generation = 36 +# Run Everyhting +ga.evolve() + +# Print the current population ga.population.print_all() diff --git a/src/selection/__init__.py b/src/selection/__init__.py new file mode 100644 index 0000000..b5f82e4 --- /dev/null +++ b/src/selection/__init__.py @@ -0,0 +1,2 @@ +# FROM (. means local) file_name IMPORT function_name +from .examples import selection_examples diff --git a/src/selection/examples.py b/src/selection/examples.py new file mode 100644 index 0000000..2c149a7 --- /dev/null +++ b/src/selection/examples.py @@ -0,0 +1,15 @@ +class selection_examples: + """Selection defintion here""" + + def tournament_selection(): + """ """ + pass + + def roulette_selection(): + """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/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 new file mode 100644 index 0000000..55fe7e1 --- /dev/null +++ b/src/termination_point/__init__.py @@ -0,0 +1,2 @@ +# FROM (. means local) file_name IMPORT class name +from .examples import termination_examples diff --git a/src/termination_point/examples.py b/src/termination_point/examples.py new file mode 100644 index 0000000..823757a --- /dev/null +++ b/src/termination_point/examples.py @@ -0,0 +1,16 @@ +class termination_examples: + """Example functions that can be used to terminate the the algorithms loop""" + + def fitness_based(ga): + """Fitness based approach to terminate when the goal fitness has been reached""" + status = True + if(ga.current_fitness > ga.fitness_goal): + status = False + return status + + def generation_based(ga): + """Generation based approach to terminate when the goal generation has been reached""" + 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