Cleaned up spacing

This commit is contained in:
SimpleArt
2020-11-26 21:30:49 -05:00
parent 33f74c4019
commit 1ba86c0661
10 changed files with 152 additions and 111 deletions

View File

@ -80,14 +80,16 @@ class GA(Attributes):
self.current_generation += 1 self.current_generation += 1
def evolve(self): def evolve(self, number_of_generations = 1, consider_termination = True):
"""Runs the ga until the termination point has been satisfied.""" """Runs the ga until the termination point has been satisfied."""
while self.active(): while self.active():
self.evolve_generation() self.evolve_generation(number_of_generations, consider_termination)
def active(self): def active(self):
"""Returns if the ga should terminate based on the termination implimented.""" """Returns if the ga should terminate based on the termination implimented."""
return self.termination_impl(self) return self.termination_impl(self)
@ -96,6 +98,7 @@ class GA(Attributes):
the initialization implimentation the initialization implimentation
that is currently set. that is currently set.
""" """
self.population = self.initialization_impl(self) self.population = self.initialization_impl(self)
@ -122,10 +125,10 @@ class GA(Attributes):
""" """
return sorted( return sorted(
chromosome_list, # list to be sorted chromosome_list, # list to be sorted
key = lambda chromosome: chromosome.fitness, # by fitness key = lambda chromosome: chromosome.fitness, # by fitness
reverse = (self.target_fitness_type == 'max') # ordered by fitness type reverse = (self.target_fitness_type == 'max') # ordered by fitness type
) )
def get_chromosome_fitness(self, index): def get_chromosome_fitness(self, index):
@ -133,9 +136,10 @@ class GA(Attributes):
at the specified index after conversion based at the specified index after conversion based
on the target fitness type. on the target fitness type.
""" """
return self.convert_fitness( return self.convert_fitness(
self.population[index].fitness self.population[index].fitness
) )
def convert_fitness(self, fitness_value): def convert_fitness(self, fitness_value):

View File

@ -35,17 +35,17 @@ class Attributes:
attributes have been catigorized to explain sections in the ga process.""" attributes have been catigorized to explain sections in the ga process."""
target_fitness_type_dict = { target_fitness_type_dict = {
'min' : 'min', 'min' : 'min',
'minimize' : 'min', 'minimize' : 'min',
'minimise' : 'min', 'minimise' : 'min',
'minimization' : 'min', 'minimization' : 'min',
'minimisation' : 'min', 'minimisation' : 'min',
'max' : 'max', 'max' : 'max',
'maximize' : 'max', 'maximize' : 'max',
'maximise' : 'max', 'maximise' : 'max',
'maximization' : 'max', 'maximization' : 'max',
'maximisation' : 'max' 'maximisation' : 'max'
} }
def __init__(self, def __init__(self,
chromosome_length = 10, chromosome_length = 10,

View File

@ -6,10 +6,10 @@ class Matplotlib_Graph:
# Common graphing functions # Common graphing functions
type_of_graph_dict = { type_of_graph_dict = {
'line' : plt.plot, 'line' : plt.plot,
'scatter' : plt.scatter, 'scatter' : plt.scatter,
'bar' : plt.bar 'bar' : plt.bar
} }
def __init__(self, database): def __init__(self, database):
self.database = database self.database = database
@ -31,9 +31,9 @@ class Matplotlib_Graph:
# Query for Y data # Query for Y data
self.y = self.database.get_generation_total_fitness(config_id) self.y = self.database.get_generation_total_fitness(config_id)
# If using log then the values have to be positive numbers
if self.yscale == "log": if self.yscale == "log":
# If using log then the values have to be positive numbers self.y = [abs(ele) for ele in self.y]
self.y = [abs(ele) for ele in self.y]
self.type_of_graph(self.x, self.y) self.type_of_graph(self.x, self.y)
plt.xlabel('Generation') plt.xlabel('Generation')
@ -53,9 +53,9 @@ class Matplotlib_Graph:
# Query for Y data # Query for Y data
self.y = self.database.get_highest_chromosome(config_id) self.y = self.database.get_highest_chromosome(config_id)
# If using log then the values have to be positive numbers
if self.yscale == "log": if self.yscale == "log":
# If using log then the values have to be positive numbers self.y = [abs(ele) for ele in self.y]
self.y = [abs(ele) for ele in self.y]
self.type_of_graph(self.x, self.y) self.type_of_graph(self.x, self.y)
plt.xlabel('Generation') plt.xlabel('Generation')
@ -75,9 +75,9 @@ class Matplotlib_Graph:
# Query for Y data # Query for Y data
self.y = self.database.get_lowest_chromosome(config_id) self.y = self.database.get_lowest_chromosome(config_id)
# If using log then the values have to be positive numbers
if self.yscale == "log": if self.yscale == "log":
# If using log then the values have to be positive numbers self.y = [abs(ele) for ele in self.y]
self.y = [abs(ele) for ele in self.y]
self.type_of_graph(self.x, self.y) self.type_of_graph(self.x, self.y)
plt.xlabel('Generation') plt.xlabel('Generation')

View File

@ -11,14 +11,17 @@ class SQL_Database:
def default_config_id(method): def default_config_id(method):
"""Decorator used to set the default config_id""" """Decorator used to set the default config_id"""
def new_method(self, config_id = None): def new_method(self, config_id = None):
input_id = self.config_id if config_id is None else config_id input_id = self.config_id if config_id is None else config_id
return method(self, input_id) return method(self, input_id)
return new_method return new_method
def format_query_data(method): def format_query_data(method):
"""Decorator used to format query data""" """Decorator used to format query data"""
def new_method(self, config_id): def new_method(self, config_id):
query = method(self, config_id) query = method(self, config_id)
@ -31,6 +34,7 @@ class SQL_Database:
query = query[0] query = query[0]
return query return query
return new_method return new_method
@ -48,9 +52,9 @@ class SQL_Database:
def sql_type_of(self, obj): def sql_type_of(self, obj):
"""Returns the sql type for the object""" """Returns the sql type for the object"""
if type(obj) == int: if isinstance(obj, int):
return 'INT' return 'INT'
elif type(obj) == float: elif isinstance(obj, float):
return 'REAL' return 'REAL'
else: else:
return 'TEXT' return 'TEXT'
@ -84,7 +88,12 @@ class SQL_Database:
""" Insert one chromosome into the database""" """ Insert one chromosome into the database"""
# Structure the insert data # Structure the insert data
db_chromosome = (self.config_id, generation, chromosome.fitness, repr(chromosome)) db_chromosome = (
self.config_id,
generation,
chromosome.fitness,
repr(chromosome)
)
# Create sql query structure # Create sql query structure
sql = ''' INSERT INTO data(config_id, generation, fitness, chromosome) sql = ''' INSERT INTO data(config_id, generation, fitness, chromosome)
@ -101,13 +110,14 @@ class SQL_Database:
# Structure the insert data # Structure the insert data
db_chromosome_list = [ db_chromosome_list = [
( (
self.config_id, self.config_id,
ga.current_generation, ga.current_generation,
chromosome.fitness, chromosome.fitness,
repr(chromosome) repr(chromosome)
) )
for chromosome in ga.population for chromosome
in ga.population
] ]
# Create sql query structure # Create sql query structure
@ -158,9 +168,12 @@ class SQL_Database:
attribute variables and adds them as columns in the database table config""" attribute variables and adds them as columns in the database table config"""
# Structure the config table # Structure the config table
sql = "CREATE TABLE IF NOT EXISTS config (id INTEGER PRIMARY KEY," sql = "CREATE TABLE IF NOT EXISTS config (id INTEGER PRIMARY KEY," \
sql += ",".join(var + ' ' + self.sql_type_of(var) for var in self.get_var_names(ga)) + ",".join(
sql += "); " var + ' ' + self.sql_type_of(var)
for var
in self.get_var_names(ga)
) + "); "
return sql return sql
@ -179,11 +192,11 @@ class SQL_Database:
db_config_list[i] = str(db_config_list[i]) db_config_list[i] = str(db_config_list[i])
# Create sql query structure # Create sql query structure
sql = "INSERT INTO config (" sql = "INSERT INTO config (" \
sql += ",".join(self.get_var_names(ga)) + ",".join(self.get_var_names(ga)) \
sql += ") VALUES(" + ") VALUES(" \
sql += ( ",?"*len(db_config_list) )[1:] + ( ",?"*len(db_config_list) )[1:] \
sql += ") " + ") "
# For some reason it has to be in var = array(tuple()) form # For some reason it has to be in var = array(tuple()) form
db_config_list = [tuple(db_config_list)] db_config_list = [tuple(db_config_list)]
@ -202,7 +215,6 @@ class SQL_Database:
cur = self.conn.cursor() cur = self.conn.cursor()
cur.execute(query) cur.execute(query)
return cur.fetchall() return cur.fetchall()
@ -270,6 +282,7 @@ class SQL_Database:
@property @property
def conn(self): def conn(self):
"""Getter function for conn""" """Getter function for conn"""
# Return if the connection has already been set # Return if the connection has already been set
if self._conn is not None: if self._conn is not None:
return self._conn return self._conn
@ -279,6 +292,7 @@ class SQL_Database:
try: try:
# Check if you can connect to the database # Check if you can connect to the database
self._conn = self.create_connection() self._conn = self.create_connection()
except: except:
# if the connection doesnt exist then print error # if the connection doesnt exist then print error
raise Exception("""You are required to run a ga before you raise Exception("""You are required to run a ga before you
@ -296,6 +310,7 @@ class SQL_Database:
@property @property
def config_id(self): def config_id(self):
"""Getter function for config_id""" """Getter function for config_id"""
# Return if the config_id has already been set # Return if the config_id has already been set
if self._config_id is not None: if self._config_id is not None:
return self._config_id return self._config_id
@ -305,10 +320,11 @@ class SQL_Database:
try: try:
# Check if you can connect to the database # Check if you can connect to the database
self._config_id = self.get_most_recent_config_id() self._config_id = self.get_most_recent_config_id()
except: except:
# if the config_id doesnt exist then print error # if the config_id doesnt exist then print error
raise Exception("""You are required to run a ga before you raise Exception("""You are required to run a ga before you
can connect to the database. Run ga.evolve() or ga.active()""") can connect to the database. Run ga.evolve() or ga.active()""")
@config_id.setter @config_id.setter

View File

@ -2,7 +2,11 @@ def chromosomes_to_population(initialize):
"""Makes a population from chromosomes.""" """Makes a population from chromosomes."""
return lambda ga:\ return lambda ga:\
ga.make_population( ga.make_population(
[initialize(ga) for _ in range(ga.population_size)] [
initialize(ga)
for _
in range(ga.population_size)
]
) )
def genes_to_chromosome(initialize): def genes_to_chromosome(initialize):
@ -15,19 +19,20 @@ def genes_to_chromosome(initialize):
def values_to_genes(initialize): def values_to_genes(initialize):
"""Converts a collection of values to genes.""" """Converts a collection of values to genes."""
return lambda ga:\ return lambda ga:\
(ga.make_gene(value) for value in initialize(ga)) (
ga.make_gene(value)
for value
in initialize(ga)
)
class Initialization_Methods: class Initialization_Methods:
"""Initialization examples that are used as defaults and examples""" """Initialization examples that are used as defaults and examples"""
# Private method decorators, see above. # Private method decorators, see above.
def _chromosomes_to_population(initialize): _chromosomes_to_population = chromosomes_to_population
return chromosomes_to_population(initialize) _genes_to_chromosome = genes_to_chromosome
def _genes_to_chromosome(initialize): _value_to_gene = value_to_gene
return genes_to_chromosome(initialize)
def _value_to_gene(initialize):
return value_to_gene(initialize)
@chromosomes_to_population @chromosomes_to_population

View File

@ -4,7 +4,7 @@ from math import ceil
def check_chromosome_mutation_rate(population_method): def check_chromosome_mutation_rate(population_method):
"""Checks if the chromosome mutation rate is a float between 0 and 1 before running.""" """Checks if the chromosome mutation rate is a float between 0 and 1 before running."""
def new_population_method(ga): def new_method(ga):
if not isinstance(ga.chromosome_mutation_rate, float): if not isinstance(ga.chromosome_mutation_rate, float):
raise TypeError("Chromosome mutation rate must be a float.") raise TypeError("Chromosome mutation rate must be a float.")
@ -15,13 +15,13 @@ def check_chromosome_mutation_rate(population_method):
else: else:
raise ValueError("Chromosome mutation rate must be between 0 and 1.") raise ValueError("Chromosome mutation rate must be between 0 and 1.")
return new_population_method return new_method
def check_gene_mutation_rate(individual_method): def check_gene_mutation_rate(individual_method):
"""Checks if the gene mutation rate is a float between 0 and 1 before running.""" """Checks if the gene mutation rate is a float between 0 and 1 before running."""
def new_individual_method(ga, index): def new_method(ga, index):
if not isinstance(ga.gene_mutation_rate, float): if not isinstance(ga.gene_mutation_rate, float):
raise TypeError("Gene mutation rate must be a float.") raise TypeError("Gene mutation rate must be a float.")
@ -32,19 +32,19 @@ def check_gene_mutation_rate(individual_method):
else: else:
raise ValueError("Gene mutation rate must be between 0 and 1.") raise ValueError("Gene mutation rate must be between 0 and 1.")
return new_individual_method return new_method
def loop_selections(population_method): def loop_selections(population_method):
"""Runs the population method until enough chromosomes are mutated.""" """Runs the population method until enough chromosomes are mutated."""
def new_population_method(ga): def new_method(ga):
# Loop the population method until enough chromosomes are mutated. # Loop the population method until enough chromosomes are mutated.
for _ in range(ceil(len(ga.population)*ga.chromosome_mutation_rate)): for _ in range(ceil(len(ga.population)*ga.chromosome_mutation_rate)):
population_method(ga) population_method(ga)
return new_population_method return new_method
def loop_mutations(individual_method): def loop_mutations(individual_method):
@ -53,26 +53,22 @@ def loop_mutations(individual_method):
""" """
# Change input from index to chromosome. # Change input from index to chromosome.
def new_individual_method(ga, index): def new_method(ga, index):
# Loop the individual method until enough genes are mutated. # Loop the individual method until enough genes are mutated.
for _ in range(ceil(len(ga.population[index])*ga.gene_mutation_rate)): for _ in range(ceil(len(ga.population[index])*ga.gene_mutation_rate)):
individual_method(ga, ga.population[index]) individual_method(ga, ga.population[index])
return new_individual_method return new_method
class Mutation_Methods: class Mutation_Methods:
# Private method decorators, see above. # Private method decorators, see above.
def _check_chromosome_mutation_rate(population_method): _check_chromosome_mutation_rate = check_chromosome_mutation_rate
return check_chromosome_mutation_rate(population_method) _check_gene_mutation_rate = check_gene_mutation_rate
def _check_gene_mutation_rate(individual_method): _loop_selections = loop_selections
return check_gene_mutation_rate(individual_method) _loop_mutations = loop_mutations
def _loop_selections(population_method):
return loop_selections(population_method)
def _loop_mutations(individual_method):
return loop_mutations(individual_method)
class Population: class Population:
@ -92,7 +88,10 @@ class Mutation_Methods:
def random_avoid_best(ga): def random_avoid_best(ga):
"""Selects random chromosomes while avoiding the best chromosomes. (Elitism)""" """Selects random chromosomes while avoiding the best chromosomes. (Elitism)"""
index = random.randrange(int(len(ga.population)*ga.gene_mutation_rate/2), len(ga.population)) index = random.randrange(
int(len(ga.population)*ga.gene_mutation_rate/2),
len(ga.population)
)
ga.mutation_individual_impl(ga, index) ga.mutation_individual_impl(ga, index)

View File

@ -5,12 +5,14 @@ def check_selection_probability(selection_method):
is not between 0 and 1. Otherwise runs the selection is not between 0 and 1. Otherwise runs the selection
method. method.
""" """
def helper(ga):
def new_method(ga):
if 0 < ga.selection_probability < 1: if 0 < ga.selection_probability < 1:
selection_method(ga) selection_method(ga)
else: else:
raise Exception("Selection probability must be between 0 and 1 to select parents.") raise Exception("Selection probability must be between 0 and 1 to select parents.")
return helper
return new_method
def check_positive_fitness(selection_method): def check_positive_fitness(selection_method):
@ -18,33 +20,34 @@ def check_positive_fitness(selection_method):
chromosome with negative fitness. Otherwise runs chromosome with negative fitness. Otherwise runs
the selection method. the selection method.
""" """
def helper(ga):
def new_method(ga):
if ga.get_chromosome_fitness(0) > 0 and ga.get_chromosome_fitness(-1) >= 0: if ga.get_chromosome_fitness(0) > 0 and ga.get_chromosome_fitness(-1) >= 0:
selection_method(ga) selection_method(ga)
else: else:
raise Exception("Converted fitness values must be all positive. Consider using rank selection instead.") raise Exception("Converted fitness values must be all positive. Consider using rank selection instead.")
return helper
return new_method
def ensure_sorted(selection_method): def ensure_sorted(selection_method):
"""Sorts the population by fitness """Sorts the population by fitness
and then runs the selection method. and then runs the selection method.
""" """
def helper(ga):
def new_method(ga):
ga.population.sort_by_best_fitness(ga) ga.population.sort_by_best_fitness(ga)
selection_method(ga) selection_method(ga)
return helper
return new_method
class Parent_Selection: class Parent_Selection:
# Private method decorators, see above. # Private method decorators, see above.
def _check_selection_probability(selection_method): _check_selection_probability = check_selection_probability
return check_selection_probability(selection_method) _check_positive_fitness = check_positive_fitness
def _check_positive_fitness(selection_method): _ensure_sorted = ensure_sorted
return check_positive_fitness(selection_method)
def _ensure_sorted(selection_method):
return ensure_sorted(selection_method)
class Rank: class Rank:
@ -62,13 +65,16 @@ class Parent_Selection:
# Use no less than 5 chromosomes per tournament. # Use no less than 5 chromosomes per tournament.
tournament_size = int(len(ga.population)*ga.tournament_size_ratio) tournament_size = int(len(ga.population)*ga.tournament_size_ratio)
if tournament_size < 5: if tournament_size < 5:
tournament_size = 5 tournament_size = min(5, len(ga.population))
# Repeat tournaments until the mating pool is large enough. # Repeat tournaments until the mating pool is large enough.
while True: while True:
# Generate a random tournament group and sort by fitness. # Generate a random tournament group and sort by fitness.
tournament_group = sorted([random.randrange(len(ga.population)) for _ in range(tournament_size)]) tournament_group = sorted(random.sample(
range(len(ga.population)),
k = tournament_size
))
# For each chromosome, add it to the mating pool based on its rank in the tournament. # For each chromosome, add it to the mating pool based on its rank in the tournament.
for index in range(tournament_size): for index in range(tournament_size):
@ -101,7 +107,11 @@ class Parent_Selection:
""" """
# The sum of all the fitnessess in a population # The sum of all the fitnessess in a population
fitness_sum = sum(ga.get_chromosome_fitness(index) for index in range(len(ga.population))) 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 # A list of ranges that represent the probability of a chromosome getting chosen
probability = [ga.selection_probability] probability = [ga.selection_probability]

View File

@ -150,7 +150,7 @@ class Population:
to get a backend representation of the population. to get a backend representation of the population.
""" """
return ''.join( return ''.join(
f'Chromosome - {index} {chromosome} ' + f'Chromosome - {index} {chromosome} / Fitness = {chromosome.fitness}\n'
f'/ Fitness = {chromosome.fitness}\n' for index, chromosome
for index, chromosome in enumerate(self) in enumerate(self)
) )

View File

@ -2,15 +2,16 @@ import random
def append_to_next_population(survivor_method): def append_to_next_population(survivor_method):
"""Appends the selected chromosomes to the next population.""" """Appends the selected chromosomes to the next population."""
return lambda ga: ga.population.append_children(survivor_method(ga))
return lambda ga:\
ga.population.append_children(survivor_method(ga))
class Survivor_Selection: class Survivor_Selection:
"""Survivor selection determines which individuals should be brought to the next generation""" """Survivor selection determines which individuals should be brought to the next generation"""
# Private method decorator, see above. # Private method decorator, see above.
def _append_to_next_population(survivor_method): _append_to_next_population = append_to_next_population
return append_to_next_population(survivor_method)
@append_to_next_population @append_to_next_population
@ -33,8 +34,11 @@ class Survivor_Selection:
def fill_in_parents_then_random(ga): def fill_in_parents_then_random(ga):
"""Fills in the next population with all parents followed by random chromosomes from the last population""" """Fills in the next population with all parents followed by random chromosomes from the last population"""
# Remove dupes from the mating pool
mating_pool = set(ga.population.mating_pool)
needed_amount = len(ga.population) - len(ga.population.next_population) needed_amount = len(ga.population) - len(ga.population.next_population)
parent_amount = min(len(ga.population.mating_pool), needed_amount) parent_amount = min(needed_amount, len(mating_pool))
random_amount = needed_amount - parent_amount random_amount = needed_amount - parent_amount
# Only parents are used. # Only parents are used.
@ -43,5 +47,8 @@ class Survivor_Selection:
# Parents need to be removed from the random sample to avoid dupes. # Parents need to be removed from the random sample to avoid dupes.
else: else:
return ga.population.mating_pool +\ return mating_pool \
random.sample(set(ga.population)-set(ga.population.mating_pool), random_amount) + random.sample(
set(ga.population) - mating_pool,
random_amount
)

View File

@ -1,7 +1,7 @@
def add_by_fitness_goal(termination_impl): def add_by_fitness_goal(termination_impl):
"""Adds termination by fitness goal to the method.""" """Adds termination by fitness goal to the method."""
def helper(ga): def new_method(ga):
# If fitness goal is set, check it. # If fitness goal is set, check it.
if ga.fitness_goal is not None: if ga.fitness_goal is not None:
@ -16,13 +16,14 @@ def add_by_fitness_goal(termination_impl):
# Check other termination methods # Check other termination methods
return termination_impl(ga) return termination_impl(ga)
return helper
return new_method
def add_by_generation_goal(termination_impl): def add_by_generation_goal(termination_impl):
"""Adds termination by generation goal to the method.""" """Adds termination by generation goal to the method."""
def helper(ga): def new_method(ga):
# If generation goal is set, check it. # If generation goal is set, check it.
if ga.generation_goal is not None and ga.current_generation >= ga.generation_goal: if ga.generation_goal is not None and ga.current_generation >= ga.generation_goal:
@ -30,18 +31,19 @@ def add_by_generation_goal(termination_impl):
# Check other termination methods # Check other termination methods
return termination_impl(ga) return termination_impl(ga)
return helper
return new_method
def add_by_tolerance_goal(termination_impl): def add_by_tolerance_goal(termination_impl):
"""Adds termination by tolerance goal to the method.""" """Adds termination by tolerance goal to the method."""
def helper(ga): def new_method(ga):
# If tolerance is set, check it. # If tolerance is set, check it.
if ga.tolerance_goal is not None: if ga.tolerance_goal is not None:
best_fitness = ga.population[0].fitness best_fitness = ga.population[0].fitness
threshhold_fitness = ga.population[int(ga.percent_converged*len(ga.population))].fitness threshhold_fitness = ga.population[round(ga.percent_converged*len(ga.population))].fitness
tol = ga.tolerance_goal * (1 + abs(best_fitness)) tol = ga.tolerance_goal * (1 + abs(best_fitness))
# Terminate if the specified amount of the population has converged to the specified tolerance # Terminate if the specified amount of the population has converged to the specified tolerance
@ -50,19 +52,17 @@ def add_by_tolerance_goal(termination_impl):
# Check other termination methods # Check other termination methods
return termination_impl(ga) return termination_impl(ga)
return helper
return new_method
class Termination_Methods: class Termination_Methods:
"""Example functions that can be used to terminate the the algorithms loop""" """Example functions that can be used to terminate the the algorithms loop"""
# Private method decorators, see above. # Private method decorators, see above.
def _add_by_fitness_goal(termination_impl): _add_by_fitness_goal = add_by_fitness_goal
return add_by_fitness_goal(termination_impl) _add_by_generation_goal = add_by_generation_goal
def _add_by_generation_goal(termination_impl): _add_by_tolerance_goal = add_by_tolerance_goal
return add_by_generation_goal(termination_impl)
def _add_by_tolerance_goal(termination_impl):
return add_by_tolerance_goal(termination_impl)
@add_by_fitness_goal @add_by_fitness_goal