Added iterable features

This commit is contained in:
SimpleArt
2020-11-19 11:46:47 -05:00
parent 6e95ff5d9d
commit 0ee545429c
7 changed files with 167 additions and 97 deletions

View File

@ -110,15 +110,15 @@ class GA(Attributes):
chromosome.set_fitness(self.fitness_function_impl(chromosome)) chromosome.set_fitness(self.fitness_function_impl(chromosome))
def sort_by_best_fitness(self, chromosome_set): def sort_by_best_fitness(self, chromosome_list):
"""Sorts the array by fitness based on fitness type. """Sorts the chromosome list by fitness based on fitness type.
1st element has best fitness. 1st element has best fitness.
2nd element has second best fitness. 2nd element has second best fitness.
etc. etc.
""" """
return sorted( return sorted(
chromosome_set, # list to be sorted chromosome_list, # list to be sorted
key = lambda chromosome: chromosome.get_fitness(), # by fitness key = lambda chromosome: chromosome.get_fitness(), # by fitness
reverse = (self.target_fitness_type == 'max') # ordered by fitness type reverse = (self.target_fitness_type == 'max') # ordered by fitness type
) )
@ -130,7 +130,7 @@ class GA(Attributes):
on the target fitness type. on the target fitness type.
""" """
return self.convert_fitness( return self.convert_fitness(
self.population.get_chromosome(index).get_fitness() self.population[index].get_fitness()
) )
@ -140,11 +140,17 @@ class GA(Attributes):
inverted using max - value + min. inverted using max - value + min.
""" """
# No conversion needed
if self.target_fitness_type == 'max': return fitness_value if self.target_fitness_type == 'max': return fitness_value
max_fitness = self.population.get_chromosome(-1).get_fitness()
min_fitness = self.population.get_chromosome(0).get_fitness() max_fitness = self.population[-1].get_fitness()
min_fitness = self.population[0].get_fitness()
# Avoid catastrophic cancellation
if min_fitness / max_fitness < 1e-5: if min_fitness / max_fitness < 1e-5:
return -fitness_value return -fitness_value
# Otherwise flip values
else: else:
return max_fitness - fitness_value + min_fitness return max_fitness - fitness_value + min_fitness
@ -161,11 +167,11 @@ class GA(Attributes):
def print_best(self): def print_best(self):
"""Prints the best chromosome and its fitness""" """Prints the best chromosome and its fitness"""
print(f"Best Chromosome \t: {self.population.get_chromosome(0)}") print(f"Best Chromosome \t: {self.population[0]}")
print(f"Best Fitness \t: {self.population.get_chromosome(0).get_fitness()}") print(f"Best Fitness \t: {self.population[0].get_fitness()}")
def print_worst(self): def print_worst(self):
"""Prints the worst chromosome and its fitness""" """Prints the worst chromosome and its fitness"""
print(f"Worst Chromosome \t: {self.population.get_chromosome(-1)}") print(f"Worst Chromosome \t: {self.population[-1]}")
print(f"Worst Fitness \t: {self.population.get_chromosome(-1).get_fitness()}") print(f"Worst Fitness \t: {self.population[-1].get_fitness()}")

View File

@ -43,14 +43,14 @@ class Crossover_Methods:
class Individual: class Individual:
"""Methods for crossing parents.""" """Methods for crossing parents."""
def single_point(ga, parent_one, parent_two): def single_point(ga, parent_1, parent_2):
"""Cross two parents by swapping genes at one random point.""" """Cross two parents by swapping genes at one random point."""
swap_index = random.randint(0, parent_one.size()-1) swap_index = random.randint(0, len(parent_1)-1)
return ga.make_chromosome(parent_one.get_gene_list()[:swap_index] + parent_two.get_gene_list()[swap_index:]) return ga.make_chromosome(parent_1[:swap_index] + parent_2[swap_index:])
def multi_point(ga, parent_one, parent_two): def multi_point(ga, parent_1, parent_2):
"""Cross two parents by swapping genes at multiple points.""" """Cross two parents by swapping genes at multiple points."""
pass pass
@ -58,9 +58,9 @@ class Crossover_Methods:
def uniform(ga, parent_1, parent_2): def uniform(ga, parent_1, parent_2):
"""Cross two parents by swapping all genes randomly.""" """Cross two parents by swapping all genes randomly."""
return ga.make_chromosome([ # Make a new chromosome return ga.make_chromosome([ # Make a new chromosome
random.choice([gene_1, gene_2]) # by randomly selecting genes random.choice([gene_1, gene_2]) # by randomly selecting genes
for gene_1, gene_2 in zip(parent_1.gene_list, parent_2.gene_list)]) # from each parent for gene_1, gene_2 in zip(parent_1, parent_2)]) # from each parent
class Arithmetic: class Arithmetic:
@ -69,9 +69,12 @@ class Crossover_Methods:
def int_random(ga, parent_1, parent_2): def int_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 value between each of the genes."""
return ga.make_chromosome([ # Make a new chromosome value_list_1 = parent_1.gene_value_list
ga.make_gene(random.randint(*sorted([data_1, data_2]))) # by randomly selecting integer genes between value_list_2 = parent_2.gene_value_list
for data_1, data_2 in zip(iter(parent_1), iter(parent_2))]) # each parents' genes
return ga.make_chromosome([ # Make a new chromosome
ga.make_gene(random.randint(*sorted([value_1, value_2]))) # by randomly selecting integer genes between
for value_1, value_2 in zip(value_list_1, value_list_2)]) # each parents' genes
def int_weighted(ga, parent_1, parent_2): def int_weighted(ga, parent_1, parent_2):
@ -80,19 +83,25 @@ class Crossover_Methods:
# the percentage of genes taken from the first gene # the percentage of genes taken from the first gene
weight = 0.25 weight = 0.25
return ga.make_chromosome([ # Make a new chromosome value_list_1 = parent_1.gene_value_list
ga.make_gene(int( # filled with new integer genes value_list_2 = parent_2.gene_value_list
weight*data_1+(1-weight)*data_2 # with weight% from gene 1 and
)) # (100-weight)% from gene 2 return ga.make_chromosome([ # Make a new chromosome
for data_1, data_2 in zip(iter(parent_1), iter(parent_2))]) # from each parents' genes ga.make_gene(int( # filled with new integer genes
weight*value_1+(1-weight)*value_2 # with weight% from gene 1 and
)) # (100-weight)% from gene 2
for value_1, value_2 in zip(value_list_1, value_list_2)]) # from each parents' genes
def float_random(ga, parent_one, parent_two): def float_random(ga, parent_one, parent_two):
"""Cross two parents by taking a random numeric value between each of the genes.""" """Cross two parents by taking a random numeric value between each of the genes."""
return ga.make_chromosome([ # Make a new chromosome value_list_1 = parent_1.gene_value_list
ga.make_gene(random.uniform([data_1, data_2])) # by randomly selecting integer genes between value_list_2 = parent_2.gene_value_list
for data_1, data_2 in zip(iter(parent_1), iter(parent_2))]) # from each parents' genes
return ga.make_chromosome([ # Make a new chromosome
ga.make_gene(random.uniform([value_1, value_2])) # by randomly selecting integer genes between
for value_1, value_2 in zip(value_list_1, value_list_2)]) # from each parents' genes
def float_weighted(ga, parent_one, parent_two): def float_weighted(ga, parent_one, parent_two):
@ -101,8 +110,11 @@ class Crossover_Methods:
# the percentage of genes taken from the first gene # the percentage of genes taken from the first gene
weight = 0.25 weight = 0.25
return ga.make_chromosome([ # Make a new chromosome value_list_1 = parent_1.gene_value_list
ga.make_gene( # filled with new float genes value_list_2 = parent_2.gene_value_list
weight*data_1+(1-weight)*data_2 # with weight% from gene 1 and
) # (100-weight)% from gene 2 return ga.make_chromosome([ # Make a new chromosome
for data_1, data_2 in zip(iter(parent_1), iter(parent_2))]) # from each parents' genes ga.make_gene( # filled with new float genes
weight*value_1+(1-weight)*value_2 # with weight% from gene 1 and
) # (100-weight)% from gene 2
for value_1, value_2 in zip(value_list_1, value_list_2)]) # from each parents' genes

View File

@ -10,24 +10,21 @@ class Mutation_Methods:
"""Selects random chromosomes""" """Selects random chromosomes"""
# Loop until enough mutations occur # Loop until enough mutations occur
for n in range(ceil(ga.population.size()*ga.chromosome_mutation_rate)): for n in range(ceil(len(ga.population)*ga.chromosome_mutation_rate)):
index = random.randint(0, ga.population.size()-1) index = random.randint(0, len(ga.population)-1)
ga.population.set_chromosome(ga.mutation_individual_impl(ga, ga.population.get_chromosome(index)), index) ga.population[index] = ga.mutation_individual_impl(ga, ga.population[index])
def random_selection_then_cross(ga): def random_selection_then_cross(ga):
"""Selects random chromosomes and self-crosses with parent""" """Selects random chromosomes and self-crosses with parent"""
# Loop until enough mutations occur # Loop until enough mutations occur
for n in range(ceil(ga.population.size()*ga.chromosome_mutation_rate)): for n in range(ceil(len(ga.population)*ga.chromosome_mutation_rate)):
index = random.randint(0, ga.population.size()-1) index = random.randint(0, len(ga.population)-1)
chromosome = ga.population.get_chromosome(index) chromosome = ga.population[index]
# Cross the chromosome with its mutation # Cross the chromosome with its mutation
ga.population.set_chromosome( ga.population[index] = ga.crossover_individual_impl(ga, chromosome, ga.mutation_individual_impl(ga, chromosome))
ga.crossover_individual_impl(ga, chromosome, ga.mutation_individual_impl(ga, chromosome)),
index
)
class Individual: class Individual:
@ -39,16 +36,16 @@ class Mutation_Methods:
chromosome = ga.make_chromosome(old_chromosome.get_gene_list()) chromosome = ga.make_chromosome(old_chromosome.get_gene_list())
# Loops until enough mutations occur # Loops until enough mutations occur
for n in range(ceil(chromosome.size()*ga.gene_mutation_rate)): for n in range(ceil(len(chromosome)*ga.gene_mutation_rate)):
index = random.randint(0, chromosome.size()-1) index = random.randint(0, len(chromosome)-1)
# Using the chromosome_impl # Using the chromosome_impl
if ga.chromosome_impl is not None: if ga.chromosome_impl is not None:
chromosome.set_gene(ga.make_gene(ga.chromosome_impl()[index]), index) chromosome[index] = ga.make_gene(ga.chromosome_impl()[index])
# Using the gene_impl # Using the gene_impl
elif ga.gene_impl is not None: elif ga.gene_impl is not None:
chromosome.set_gene(ga.make_gene(ga.gene_impl()), index) chromosome[index] = ga.make_gene(ga.gene_impl())
# Exit because no gene creation method specified # Exit because no gene creation method specified
else: else:
@ -67,14 +64,14 @@ class Mutation_Methods:
chromosome = ga.make_chromosome(old_chromosome.get_gene_list()) chromosome = ga.make_chromosome(old_chromosome.get_gene_list())
# Loops until enough mutations occur # Loops until enough mutations occur
for n in range(ceil(chromosome.size()*ga.gene_mutation_rate)): for n in range(ceil(len(chromosome)*ga.gene_mutation_rate)):
index_one = random.randint(0, chromosome.size()-1) index_one = random.randint(0, len(chromosome)-1)
index_two = random.randint(0, chromosome.size()-1) index_two = random.randint(0, len(chromosome)-1)
gene_one = chromosome.get_gene(index_one) gene_one = chromosome.get_gene(index_one)
gene_two = chromosome.get_gene(index_two) gene_two = chromosome.get_gene(index_two)
chromosome.set_gene(gene_one, index_two) chromosome[index_two] = gene_one
chromosome.set_gene(gene_two, index_one) chromosome[index_one] = gene_two
return chromosome return chromosome

View File

@ -21,15 +21,15 @@ class Parent_Selection:
# Choose the tournament size. # Choose the tournament size.
# Use no less than 5 chromosomes per tournament. # Use no less than 5 chromosomes per tournament.
tournament_size = int(ga.population.size()*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 = 5
# Repeat tournaments until the mating pool is large enough. # Repeat tournaments until the mating pool is large enough.
while (len(ga.population.get_mating_pool()) < ga.population.size()*ga.parent_ratio): while (len(ga.population.get_mating_pool()) < len(ga.population)*ga.parent_ratio):
# Generate a random tournament group and sort by fitness. # Generate a random tournament group and sort by fitness.
tournament_group = sorted([random.randint(0, ga.population.size()-1) for n in range(tournament_size)]) tournament_group = sorted([random.randint(0, len(ga.population)-1) for n in range(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):
@ -43,7 +43,7 @@ class Parent_Selection:
ga.population.set_parent(tournament_group[index]) ga.population.set_parent(tournament_group[index])
# Stop if parent ratio reached # Stop if parent ratio reached
if len(ga.population.get_mating_pool()) >= ga.population.size()*ga.parent_ratio: if len(ga.population.get_mating_pool()) >= len(ga.population)*ga.parent_ratio:
break break
@ -73,19 +73,19 @@ class Parent_Selection:
return return
# 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(ga.population.size())) 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]
# The chance of being selected increases incrementally # The chance of being selected increases incrementally
for index in range(ga.population.size()): for index in range(len(ga.population)):
probability.append(probability[-1]+ga.get_chromosome_fitness(index)/fitness_sum) probability.append(probability[-1]+ga.get_chromosome_fitness(index)/fitness_sum)
probability = probability[1:] probability = probability[1:]
# Loops until it reaches a desired mating pool size # Loops until it reaches a desired mating pool size
while (len(ga.population.get_mating_pool()) < ga.population.size()*ga.parent_ratio): while (len(ga.population.get_mating_pool()) < len(ga.population)*ga.parent_ratio):
# Spin the roulette # Spin the roulette
rand_number = random.random() rand_number = random.random()
@ -121,10 +121,10 @@ class Parent_Selection:
return return
# Loops until it reaches a desired mating pool size # Loops until it reaches a desired mating pool size
while (len(ga.population.get_mating_pool()) < ga.population.size()*ga.parent_ratio): while (len(ga.population.get_mating_pool()) < len(ga.population)*ga.parent_ratio):
# Selected chromosome # Selected chromosome
index = random.randint(0, ga.population.size()-1) index = random.randint(0, len(ga.population)-1)
# Probability of becoming a parent is fitness/max_fitness # Probability of becoming a parent is fitness/max_fitness
if random.uniform(ga.selection_probability, 1) < ga.get_chromosome_fitness(index)/max_fitness: if random.uniform(ga.selection_probability, 1) < ga.get_chromosome_fitness(index)/max_fitness:

View File

@ -10,11 +10,6 @@ class Chromosome:
self.fitness = None self.fitness = None
def size(self):
"""Returns the number of genes in the chromosome"""
return len(self.gene_list)
def add_gene(self, gene, index = None): def add_gene(self, gene, index = None):
"""Add a gene to the chromosome at the specified index, defaulted to end of the chromosome""" """Add a gene to the chromosome at the specified index, defaulted to end of the chromosome"""
if index is None: if index is None:
@ -54,10 +49,30 @@ class Chromosome:
self.fitness = fitness self.fitness = fitness
@property
def gene_value_list(self):
"""Returns a list of gene values"""
return [gene.value for gene in self]
def __iter__(self): def __iter__(self):
"""Returns an iterable of the gene values""" """Returns an iterable of the gene list"""
for gene in self.gene_list: return iter(self.gene_list)
yeild gene.value
def __getitem__(self, k):
"""Returns the k-th gene"""
return self.get_gene(k)
def __setitem__(self, k, gene):
"""Sets the k-th gene value"""
self.set_gene(gene, k)
def __len__(self):
"""Returns the number of genes in the chromosome"""
return len(self.gene_list)
def __repr__(self): def __repr__(self):

View File

@ -55,24 +55,26 @@ class Population:
self.set_chromosome_list(ga.sort_by_best_fitness(self.chromosome_list)) self.set_chromosome_list(ga.sort_by_best_fitness(self.chromosome_list))
def size(self): @property
"""Returns the size of the population"""
return len(self.chromosome_list)
def total_children(self): def total_children(self):
"""Returns the size of the next population""" """Returns the size of the next population"""
return len(self.next_population) return len(self.next_population)
@property
def total_parents(self):
"""Returns the size of the mating pool"""
return len(self.mating_pool)
def get_closet_fitness(self, value): 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 pass
def add_chromosome(self, chromosome, index = None): def add_chromosome(self, chromosome, index = None):
"""Adds a chromosome to the population at the input index, defaulted """Adds a chromosome to the population at the input index,
to the end of the chromosome set""" defaulted to the end of the chromosome set"""
if index is None: if index is None:
index = self.size() index = self.size()
@ -150,15 +152,52 @@ class Population:
def __iter__(self): def __iter__(self):
"""Returns an iterable of chromosome iterables""" """Returns an iterable of chromosomes"""
for chromosome in self.chromosome_list: return iter(self.chromosome_list)
yield iter(chromosome)
def __getitem__(self, k):
"""Returns the k-th chromosome"""
return self.get_chromosome(k)
def __setitem__(self, k, chromosome):
"""Sets the k-th chromosome"""
self.set_chromosome(chromosome, k)
def __len__(self):
"""Returns the number of chromosomes in the current population"""
return len(self.chromosome_list)
def __contains__(self, searched_chromosome):
"""Returns True if the current population contains the chromosome and False otherwise.
Ex. if chromosome in ga.population: ..."""
for index in range(len(self)):
if self[index] == searched_chromosome:
return True
else:
return False
def index_of(self, searched_chromosome):
"""Returns the index of the chromosome in the current population.
Returns -1 if no index found."""
for index in range(len(self)):
if self[index] == searched_chromosome:
return index
else:
return -1
def __repr__(self): def __repr__(self):
"""Returns a backend string representation of the entire population""" """Returns a backend string representation of the entire population"""
return ''.join( return ''.join(
f'Chromosome - {index} {self.get_chromosome(index)} ' + f'Chromosome - {self.index_of(chromosome)} {chromosome} ' +
f'/ Fitness = {self.get_chromosome(index).get_fitness()}\n' f'/ Fitness = {chromosome.get_fitness()}\n'
for index in range(self.size()) for chromosome in self
) )

View File

@ -6,37 +6,38 @@ class Survivor_Selection:
def fill_in_best(ga): def fill_in_best(ga):
"""Fills in the next population with the best chromosomes from the last population""" """Fills in the next population with the best chromosomes from the last population"""
needed_amount = ga.population.size()-len(ga.population.next_population) needed_amount = len(ga.population) - ga.population.total_children
ga.population.append_children( ga.population.append_children(
ga.population.get_chromosome_list()[:needed_amount] ga.population[:needed_amount]
) )
def fill_in_random(ga): def fill_in_random(ga):
"""Fills in the next population with random chromosomes from the last population""" """Fills in the next population with random chromosomes from the last population"""
needed_amount = ga.population.size()-ga.population.total_children() needed_amount = len(ga.population) - ga.population.total_children
ga.population.append_children([ # add in chromosomes ga.population.append_children([ # add in chromosomes
random.choice( # randomly random.choice( # randomly
ga.population.get_chromosome_list() # from the population ga.population # from the population
) # ) #
for n in range(needed_amount)]) # until the next population is full for n in range(needed_amount)]) # until the next population is full
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"""
needed_amount = ga.population.size()-ga.population.total_children() needed_amount = len(ga.population) - ga.population.total_children
parent_amount = max(len(ga.population.get_mating_pool()), needed_amount) parent_amount = max(len(ga.population.total_parents), needed_amount)
random_amount = needed_amount - parent_amount random_amount = needed_amount - parent_amount
ga.population.append_children( # add in chromosomes ga.population.append_children( # add in chromosomes
ga.population.get_mating_pool()[:parent_amount] # from the mating pool ga.population.get_mating_pool()[:parent_amount] # from the mating pool
) # ) #
ga.population.append_children([ # add in chromosomes ga.population.append_children([ # add in chromosomes
random.choice( # randomly random.choice( # randomly
ga.population.get_chromosome_list() # from the population ga.population # from the population
) # ) #
for n in range(random_amount)]) # until the next population is full for n in range(random_amount)]) # until the next population is full