Cleaned up spacing and added exception checking

This commit is contained in:
SimpleArt
2020-11-26 21:32:13 -05:00
parent 32dc66f857
commit fa832c1059

View File

@ -1,40 +1,91 @@
import random import random
def append_children_to_next_population(crossover_method): def append_children_to_next_population(population_method):
"""Appends the new chromosomes to the next population.""" """Appends the new chromosomes to the next population.
Also modifies the input to include the mating pool.
"""
return lambda ga:\ return lambda ga:\
ga.population.append_children( ga.population.append_children(
list(crossover_method(ga, ga.population.mating_pool)) list(population_method(ga, ga.population.mating_pool))
) )
def genes_to_chromosome(crossover_method): def weak_check_exceptions(population_method):
"""Checks if the first two chromosomes can be crossed."""
def new_method(ga, mating_pool):
# check if any genes are an Exception from
# crossing just the first two parents.
for gene in ga.crossover_individual_impl(ga, mating_pool[0], mating_pool[1]):
if isinstance(gene.value, Exception):
raise gene.value
# continue if no Exceptions found.
else:
return population_method(ga, ga.population.mating_pool)
return new_method
def strong_check_exceptions(population_method):
"""Checks if every pair of selected chromosomes can be crossed.
Warning: Very slow, consider comparing the types of genes
allowed to the method used beforehand instead.
"""
def new_method(ga, mating_pool):
next_population = list(population_method(ga, ga.population.mating_pool))
# check if any genes are an Exception.
for chromosome in next_population:
for gene in chromosome:
if isinstance(gene.value, Exception):
raise gene.value
# continue if no Exceptions found.
else:
return next_population
return new_method
def genes_to_chromosome(individual_method):
"""Converts a collection of genes into a chromosome. """Converts a collection of genes into a chromosome.
Note: Will recreate the gene list if given gene list. Note: Will recreate the gene list if given gene list.
Methods used here do not construct gene lists Built-in methods do not construct gene lists
and use yield for efficiency. and use yield for efficiency.
""" """
return lambda ga, parent_1, parent_2:\ return lambda ga, parent_1, parent_2:\
ga.make_chromosome( ga.make_chromosome(
list(crossover_method(ga, parent_1, parent_2)) list(individual_method(ga, parent_1, parent_2))
) )
def values_to_genes(crossover_method): def values_to_genes(individual_method):
"""Converts a collection of values into genes.""" """Converts a collection of values into genes.
Returns a generator of genes to avoid storing a new list.
"""
return lambda ga, parent_1, parent_2:\ return lambda ga, parent_1, parent_2:\
(ga.make_gene(value) for value in crossover_method(ga, parent_1, parent_2)) (
ga.make_gene(value)
for value
in individual_method(ga, parent_1, parent_2)
)
class Crossover_Methods: class Crossover_Methods:
# Private method decorators, see above. # Private method decorators, see above.
def _append_children_to_next_population(crossover_method): _append_children_to_next_population = append_children_to_next_population
return append_children_to_next_population(crossover_method) _weak_check_exceptions = weak_check_exceptions
def _genes_to_chromosome(crossover_method): _strong_check_exceptions = strong_check_exceptions
return values_to_chromosome(crossover_method) _genes_to_chromosome = genes_to_chromosome
def _values_to_genes(crossover_method): _values_to_genes = values_to_genes
return values_to_genes(crossover_method)
class Population: class Population:
@ -42,6 +93,7 @@ class Crossover_Methods:
@append_children_to_next_population @append_children_to_next_population
@weak_check_exceptions
def sequential_selection(ga, mating_pool): def sequential_selection(ga, mating_pool):
"""Select sequential pairs from the mating pool. """Select sequential pairs from the mating pool.
Every parent is paired with the previous parent. Every parent is paired with the previous parent.
@ -50,24 +102,25 @@ class Crossover_Methods:
for index in range(len(mating_pool)): # for each parent in the mating pool for index in range(len(mating_pool)): # for each parent in the mating pool
yield ga.crossover_individual_impl( # apply crossover to yield ga.crossover_individual_impl( # apply crossover to
ga, # ga, #
mating_pool[index], # the parent and mating_pool[index], # the parent and
mating_pool[index-1] # the previous parent mating_pool[index-1] # the previous parent
) )
@append_children_to_next_population @append_children_to_next_population
@weak_check_exceptions
def random_selection(ga, mating_pool): def random_selection(ga, mating_pool):
"""Select random pairs from the mating pool. """Select random pairs from the mating pool.
Every parent is paired with a random parent. Every parent is paired with a random parent.
""" """
for parent in mating_pool: # for each parent in the mating pool for parent in mating_pool: # for each parent in the mating pool
yield ga.crossover_individual_impl( # apply crossover to yield ga.crossover_individual_impl( # apply crossover to
ga, # ga, #
parent, # the parent and parent, # the parent and
random.choice(mating_pool) # a random parent random.choice(mating_pool) # a random parent
) )
class Individual: class Individual:
@ -101,53 +154,35 @@ class Crossover_Methods:
@genes_to_chromosome @genes_to_chromosome
@values_to_genes @values_to_genes
def int_random(ga, parent_1, parent_2): def random(ga, parent_1, parent_2):
"""Cross two parents by taking a random integer value between each of the genes.""" """Cross two parents by taking a random integer or float value between each of the genes."""
value_list_1 = parent_1.gene_value_list value_iter_1 = parent_1.gene_value_iter
value_list_2 = parent_2.gene_value_list value_iter_2 = parent_2.gene_value_iter
for value_1, value_2 in zip(value_list_1, value_list_2): for value_1, value_2 in zip(value_iter_1, value_iter_2):
yield random.randint(*sorted([value_1, value_2])) if type(value_1) == type(value_2) == int:
yield random.randint(*sorted([value_1, value_2]))
else:
try:
yield random.uniform([value_1, value_2])
except:
yield ValueError("Unhandled gene type found. Use integer or float genes.")
@genes_to_chromosome @genes_to_chromosome
@values_to_genes @values_to_genes
def int_weighted(ga, parent_1, parent_2): def average(ga, parent_1, parent_2):
"""Cross two parents by taking a a weighted average of the genes.""" """Cross two parents by taking a a weighted average of the genes."""
# the percentage of genes taken from the first gene value_iter_1 = parent_1.gene_value_iter
weight = 0.25 value_iter_2 = parent_2.gene_value_iter
value_list_1 = parent_1.gene_value_list for value_1, value_2 in zip(value_iter_1, value_iter_2):
value_list_2 = parent_2.gene_value_list if type(value_1) == type(value_2) == int:
yield (value_1+value_2)//2
for value_1, value_2 in zip(value_list_1, value_list_2): else:
yield int(weight*value_1+(1-weight)*value_2) try:
yield (value_1+value_2)/2
except:
@genes_to_chromosome raise ValueError("Could not take the average of the gene values. Use integer or float genes.")
@values_to_genes
def float_random(ga, parent_one, parent_two):
"""Cross two parents by taking a random numeric value between each of the genes."""
value_list_1 = parent_1.gene_value_list
value_list_2 = parent_2.gene_value_list
for value_1, value_2 in zip(value_list_1, value_list_2):
yield random.uniform([value_1, value_2])
@genes_to_chromosome
@values_to_genes
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
value_list_1 = parent_1.gene_value_list
value_list_2 = parent_2.gene_value_list
for value_1, value_2 in zip(value_list_1, value_list_2):
yield weight*value_1+(1-weight)*value_2