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
def evolve(self):
def evolve(self, number_of_generations = 1, consider_termination = True):
"""Runs the ga until the termination point has been satisfied."""
while self.active():
self.evolve_generation()
self.evolve_generation(number_of_generations, consider_termination)
def active(self):
"""Returns if the ga should terminate based on the termination implimented."""
return self.termination_impl(self)
@ -96,6 +98,7 @@ class GA(Attributes):
the initialization implimentation
that is currently set.
"""
self.population = self.initialization_impl(self)
@ -122,10 +125,10 @@ class GA(Attributes):
"""
return sorted(
chromosome_list, # list to be sorted
key = lambda chromosome: chromosome.fitness, # by fitness
reverse = (self.target_fitness_type == 'max') # ordered by fitness type
)
chromosome_list, # list to be sorted
key = lambda chromosome: chromosome.fitness, # by fitness
reverse = (self.target_fitness_type == 'max') # ordered by fitness type
)
def get_chromosome_fitness(self, index):
@ -133,9 +136,10 @@ class GA(Attributes):
at the specified index after conversion based
on the target fitness type.
"""
return self.convert_fitness(
self.population[index].fitness
)
self.population[index].fitness
)
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."""
target_fitness_type_dict = {
'min' : 'min',
'minimize' : 'min',
'minimise' : 'min',
'minimization' : 'min',
'minimisation' : 'min',
'max' : 'max',
'maximize' : 'max',
'maximise' : 'max',
'maximization' : 'max',
'maximisation' : 'max'
}
'min' : 'min',
'minimize' : 'min',
'minimise' : 'min',
'minimization' : 'min',
'minimisation' : 'min',
'max' : 'max',
'maximize' : 'max',
'maximise' : 'max',
'maximization' : 'max',
'maximisation' : 'max'
}
def __init__(self,
chromosome_length = 10,

View File

@ -6,10 +6,10 @@ class Matplotlib_Graph:
# Common graphing functions
type_of_graph_dict = {
'line' : plt.plot,
'scatter' : plt.scatter,
'bar' : plt.bar
}
'line' : plt.plot,
'scatter' : plt.scatter,
'bar' : plt.bar
}
def __init__(self, database):
self.database = database
@ -31,9 +31,9 @@ class Matplotlib_Graph:
# Query for Y data
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 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)
plt.xlabel('Generation')
@ -53,9 +53,9 @@ class Matplotlib_Graph:
# Query for Y data
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 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)
plt.xlabel('Generation')
@ -75,9 +75,9 @@ class Matplotlib_Graph:
# Query for Y data
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 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)
plt.xlabel('Generation')

View File

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

View File

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

View File

@ -4,7 +4,7 @@ from math import ceil
def check_chromosome_mutation_rate(population_method):
"""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):
raise TypeError("Chromosome mutation rate must be a float.")
@ -15,13 +15,13 @@ def check_chromosome_mutation_rate(population_method):
else:
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):
"""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):
raise TypeError("Gene mutation rate must be a float.")
@ -32,19 +32,19 @@ def check_gene_mutation_rate(individual_method):
else:
raise ValueError("Gene mutation rate must be between 0 and 1.")
return new_individual_method
return new_method
def loop_selections(population_method):
"""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.
for _ in range(ceil(len(ga.population)*ga.chromosome_mutation_rate)):
population_method(ga)
return new_population_method
return new_method
def loop_mutations(individual_method):
@ -53,26 +53,22 @@ def loop_mutations(individual_method):
"""
# 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.
for _ in range(ceil(len(ga.population[index])*ga.gene_mutation_rate)):
individual_method(ga, ga.population[index])
return new_individual_method
return new_method
class Mutation_Methods:
# Private method decorators, see above.
def _check_chromosome_mutation_rate(population_method):
return check_chromosome_mutation_rate(population_method)
def _check_gene_mutation_rate(individual_method):
return check_gene_mutation_rate(individual_method)
def _loop_selections(population_method):
return loop_selections(population_method)
def _loop_mutations(individual_method):
return loop_mutations(individual_method)
_check_chromosome_mutation_rate = check_chromosome_mutation_rate
_check_gene_mutation_rate = check_gene_mutation_rate
_loop_selections = loop_selections
_loop_mutations = loop_mutations
class Population:
@ -92,7 +88,10 @@ class Mutation_Methods:
def random_avoid_best(ga):
"""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)

View File

@ -5,12 +5,14 @@ def check_selection_probability(selection_method):
is not between 0 and 1. Otherwise runs the selection
method.
"""
def helper(ga):
def new_method(ga):
if 0 < ga.selection_probability < 1:
selection_method(ga)
else:
raise Exception("Selection probability must be between 0 and 1 to select parents.")
return helper
return new_method
def check_positive_fitness(selection_method):
@ -18,33 +20,34 @@ def check_positive_fitness(selection_method):
chromosome with negative fitness. Otherwise runs
the selection method.
"""
def helper(ga):
def new_method(ga):
if ga.get_chromosome_fitness(0) > 0 and ga.get_chromosome_fitness(-1) >= 0:
selection_method(ga)
else:
raise Exception("Converted fitness values must be all positive. Consider using rank selection instead.")
return helper
return new_method
def ensure_sorted(selection_method):
"""Sorts the population by fitness
and then runs the selection method.
"""
def helper(ga):
def new_method(ga):
ga.population.sort_by_best_fitness(ga)
selection_method(ga)
return helper
return new_method
class Parent_Selection:
# Private method decorators, see above.
def _check_selection_probability(selection_method):
return check_selection_probability(selection_method)
def _check_positive_fitness(selection_method):
return check_positive_fitness(selection_method)
def _ensure_sorted(selection_method):
return ensure_sorted(selection_method)
_check_selection_probability = check_selection_probability
_check_positive_fitness = check_positive_fitness
_ensure_sorted = ensure_sorted
class Rank:
@ -62,13 +65,16 @@ class Parent_Selection:
# Use no less than 5 chromosomes per tournament.
tournament_size = int(len(ga.population)*ga.tournament_size_ratio)
if tournament_size < 5:
tournament_size = 5
tournament_size = min(5, len(ga.population))
# Repeat tournaments until the mating pool is large enough.
while True:
# 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 index in range(tournament_size):
@ -101,7 +107,11 @@ class Parent_Selection:
"""
# 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
probability = [ga.selection_probability]

View File

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

View File

@ -2,15 +2,16 @@ import random
def append_to_next_population(survivor_method):
"""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:
"""Survivor selection determines which individuals should be brought to the next generation"""
# Private method decorator, see above.
def _append_to_next_population(survivor_method):
return append_to_next_population(survivor_method)
_append_to_next_population = append_to_next_population
@append_to_next_population
@ -33,8 +34,11 @@ class Survivor_Selection:
def fill_in_parents_then_random(ga):
"""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)
parent_amount = min(len(ga.population.mating_pool), needed_amount)
parent_amount = min(needed_amount, len(mating_pool))
random_amount = needed_amount - parent_amount
# Only parents are used.
@ -43,5 +47,8 @@ class Survivor_Selection:
# Parents need to be removed from the random sample to avoid dupes.
else:
return ga.population.mating_pool +\
random.sample(set(ga.population)-set(ga.population.mating_pool), random_amount)
return mating_pool \
+ random.sample(
set(ga.population) - mating_pool,
random_amount
)

View File

@ -1,7 +1,7 @@
def add_by_fitness_goal(termination_impl):
"""Adds termination by fitness goal to the method."""
def helper(ga):
def new_method(ga):
# If fitness goal is set, check it.
if ga.fitness_goal is not None:
@ -16,13 +16,14 @@ def add_by_fitness_goal(termination_impl):
# Check other termination methods
return termination_impl(ga)
return helper
return new_method
def add_by_generation_goal(termination_impl):
"""Adds termination by generation goal to the method."""
def helper(ga):
def new_method(ga):
# If generation goal is set, check it.
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
return termination_impl(ga)
return helper
return new_method
def add_by_tolerance_goal(termination_impl):
"""Adds termination by tolerance goal to the method."""
def helper(ga):
def new_method(ga):
# If tolerance is set, check it.
if ga.tolerance_goal is not None:
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))
# 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
return termination_impl(ga)
return helper
return new_method
class Termination_Methods:
"""Example functions that can be used to terminate the the algorithms loop"""
# Private method decorators, see above.
def _add_by_fitness_goal(termination_impl):
return add_by_fitness_goal(termination_impl)
def _add_by_generation_goal(termination_impl):
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
_add_by_generation_goal = add_by_generation_goal
_add_by_tolerance_goal = add_by_tolerance_goal
@add_by_fitness_goal