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))
def sort_by_best_fitness(self, chromosome_set):
"""Sorts the array by fitness based on fitness type.
def sort_by_best_fitness(self, chromosome_list):
"""Sorts the chromosome list by fitness based on fitness type.
1st element has best fitness.
2nd element has second best fitness.
etc.
"""
return sorted(
chromosome_set, # list to be sorted
chromosome_list, # list to be sorted
key = lambda chromosome: chromosome.get_fitness(), # by fitness
reverse = (self.target_fitness_type == 'max') # ordered by fitness type
)
@ -130,7 +130,7 @@ class GA(Attributes):
on the target fitness type.
"""
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.
"""
# No conversion needed
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:
return -fitness_value
# Otherwise flip values
else:
return max_fitness - fitness_value + min_fitness
@ -161,11 +167,11 @@ class GA(Attributes):
def print_best(self):
"""Prints the best chromosome and its fitness"""
print(f"Best Chromosome \t: {self.population.get_chromosome(0)}")
print(f"Best Fitness \t: {self.population.get_chromosome(0).get_fitness()}")
print(f"Best Chromosome \t: {self.population[0]}")
print(f"Best Fitness \t: {self.population[0].get_fitness()}")
def print_worst(self):
"""Prints the worst chromosome and its fitness"""
print(f"Worst Chromosome \t: {self.population.get_chromosome(-1)}")
print(f"Worst Fitness \t: {self.population.get_chromosome(-1).get_fitness()}")
print(f"Worst Chromosome \t: {self.population[-1]}")
print(f"Worst Fitness \t: {self.population[-1].get_fitness()}")

View File

@ -43,14 +43,14 @@ class Crossover_Methods:
class Individual:
"""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."""
swap_index = random.randint(0, parent_one.size()-1)
return ga.make_chromosome(parent_one.get_gene_list()[:swap_index] + parent_two.get_gene_list()[swap_index:])
swap_index = random.randint(0, len(parent_1)-1)
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."""
pass
@ -58,9 +58,9 @@ class Crossover_Methods:
def uniform(ga, parent_1, parent_2):
"""Cross two parents by swapping all genes randomly."""
return ga.make_chromosome([ # Make a new chromosome
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
return ga.make_chromosome([ # Make a new chromosome
random.choice([gene_1, gene_2]) # by randomly selecting genes
for gene_1, gene_2 in zip(parent_1, parent_2)]) # from each parent
class Arithmetic:
@ -69,9 +69,12 @@ class Crossover_Methods:
def int_random(ga, parent_1, parent_2):
"""Cross two parents by taking a random integer value between each of the genes."""
return ga.make_chromosome([ # Make a new chromosome
ga.make_gene(random.randint(*sorted([data_1, data_2]))) # by randomly selecting integer genes between
for data_1, data_2 in zip(iter(parent_1), iter(parent_2))]) # each parents' genes
value_list_1 = parent_1.gene_value_list
value_list_2 = parent_2.gene_value_list
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):
@ -80,19 +83,25 @@ class Crossover_Methods:
# the percentage of genes taken from the first gene
weight = 0.25
return ga.make_chromosome([ # Make a new chromosome
ga.make_gene(int( # filled with new integer genes
weight*data_1+(1-weight)*data_2 # with weight% from gene 1 and
)) # (100-weight)% from gene 2
for data_1, data_2 in zip(iter(parent_1), iter(parent_2))]) # from each parents' genes
value_list_1 = parent_1.gene_value_list
value_list_2 = parent_2.gene_value_list
return ga.make_chromosome([ # Make a new chromosome
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):
"""Cross two parents by taking a random numeric value between each of the genes."""
return ga.make_chromosome([ # Make a new chromosome
ga.make_gene(random.uniform([data_1, data_2])) # by randomly selecting integer genes between
for data_1, data_2 in zip(iter(parent_1), iter(parent_2))]) # from each parents' genes
value_list_1 = parent_1.gene_value_list
value_list_2 = parent_2.gene_value_list
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):
@ -101,8 +110,11 @@ class Crossover_Methods:
# the percentage of genes taken from the first gene
weight = 0.25
return ga.make_chromosome([ # Make a new chromosome
ga.make_gene( # filled with new float genes
weight*data_1+(1-weight)*data_2 # with weight% from gene 1 and
) # (100-weight)% from gene 2
for data_1, data_2 in zip(iter(parent_1), iter(parent_2))]) # from each parents' genes
value_list_1 = parent_1.gene_value_list
value_list_2 = parent_2.gene_value_list
return ga.make_chromosome([ # Make a new chromosome
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"""
# Loop until enough mutations occur
for n in range(ceil(ga.population.size()*ga.chromosome_mutation_rate)):
index = random.randint(0, ga.population.size()-1)
ga.population.set_chromosome(ga.mutation_individual_impl(ga, ga.population.get_chromosome(index)), index)
for n in range(ceil(len(ga.population)*ga.chromosome_mutation_rate)):
index = random.randint(0, len(ga.population)-1)
ga.population[index] = ga.mutation_individual_impl(ga, ga.population[index])
def random_selection_then_cross(ga):
"""Selects random chromosomes and self-crosses with parent"""
# Loop until enough mutations occur
for n in range(ceil(ga.population.size()*ga.chromosome_mutation_rate)):
index = random.randint(0, ga.population.size()-1)
chromosome = ga.population.get_chromosome(index)
for n in range(ceil(len(ga.population)*ga.chromosome_mutation_rate)):
index = random.randint(0, len(ga.population)-1)
chromosome = ga.population[index]
# Cross the chromosome with its mutation
ga.population.set_chromosome(
ga.crossover_individual_impl(ga, chromosome, ga.mutation_individual_impl(ga, chromosome)),
index
)
ga.population[index] = ga.crossover_individual_impl(ga, chromosome, ga.mutation_individual_impl(ga, chromosome))
class Individual:
@ -39,16 +36,16 @@ class Mutation_Methods:
chromosome = ga.make_chromosome(old_chromosome.get_gene_list())
# Loops until enough mutations occur
for n in range(ceil(chromosome.size()*ga.gene_mutation_rate)):
index = random.randint(0, chromosome.size()-1)
for n in range(ceil(len(chromosome)*ga.gene_mutation_rate)):
index = random.randint(0, len(chromosome)-1)
# Using the chromosome_impl
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
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
else:
@ -67,14 +64,14 @@ class Mutation_Methods:
chromosome = ga.make_chromosome(old_chromosome.get_gene_list())
# Loops until enough mutations occur
for n in range(ceil(chromosome.size()*ga.gene_mutation_rate)):
index_one = random.randint(0, chromosome.size()-1)
index_two = random.randint(0, chromosome.size()-1)
for n in range(ceil(len(chromosome)*ga.gene_mutation_rate)):
index_one = random.randint(0, len(chromosome)-1)
index_two = random.randint(0, len(chromosome)-1)
gene_one = chromosome.get_gene(index_one)
gene_two = chromosome.get_gene(index_two)
chromosome.set_gene(gene_one, index_two)
chromosome.set_gene(gene_two, index_one)
chromosome[index_two] = gene_one
chromosome[index_one] = gene_two
return chromosome

View File

@ -21,15 +21,15 @@ class Parent_Selection:
# Choose the tournament size.
# 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:
tournament_size = 5
# 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.
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 index in range(tournament_size):
@ -43,7 +43,7 @@ class Parent_Selection:
ga.population.set_parent(tournament_group[index])
# 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
@ -73,19 +73,19 @@ class Parent_Selection:
return
# 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
probability = [ga.selection_probability]
# 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 = probability[1:]
# 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
rand_number = random.random()
@ -121,10 +121,10 @@ class Parent_Selection:
return
# 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
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
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
def size(self):
"""Returns the number of genes in the chromosome"""
return len(self.gene_list)
def add_gene(self, gene, index = None):
"""Add a gene to the chromosome at the specified index, defaulted to end of the chromosome"""
if index is None:
@ -54,10 +49,30 @@ class Chromosome:
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):
"""Returns an iterable of the gene values"""
for gene in self.gene_list:
yeild gene.value
"""Returns an iterable of the gene list"""
return iter(self.gene_list)
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):

View File

@ -55,24 +55,26 @@ class Population:
self.set_chromosome_list(ga.sort_by_best_fitness(self.chromosome_list))
def size(self):
"""Returns the size of the population"""
return len(self.chromosome_list)
@property
def total_children(self):
"""Returns the size of the 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):
"""Get the chomosome that has the closets fitness to the value defined"""
pass
def add_chromosome(self, chromosome, index = None):
"""Adds a chromosome to the population at the input index, defaulted
to the end of the chromosome set"""
"""Adds a chromosome to the population at the input index,
defaulted to the end of the chromosome set"""
if index is None:
index = self.size()
@ -150,15 +152,52 @@ class Population:
def __iter__(self):
"""Returns an iterable of chromosome iterables"""
for chromosome in self.chromosome_list:
yield iter(chromosome)
"""Returns an iterable of chromosomes"""
return iter(self.chromosome_list)
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):
"""Returns a backend string representation of the entire population"""
return ''.join(
f'Chromosome - {index} {self.get_chromosome(index)} ' +
f'/ Fitness = {self.get_chromosome(index).get_fitness()}\n'
for index in range(self.size())
f'Chromosome - {self.index_of(chromosome)} {chromosome} ' +
f'/ Fitness = {chromosome.get_fitness()}\n'
for chromosome in self
)

View File

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