Added iterable features
This commit is contained in:
@ -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()}")
|
||||
|
||||
@ -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
|
||||
|
||||
@ -60,7 +60,7 @@ class Crossover_Methods:
|
||||
|
||||
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
|
||||
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."""
|
||||
|
||||
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([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
|
||||
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
|
||||
|
||||
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*data_1+(1-weight)*data_2 # with weight% from gene 1 and
|
||||
weight*value_1+(1-weight)*value_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
|
||||
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."""
|
||||
|
||||
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([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
|
||||
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
|
||||
|
||||
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*data_1+(1-weight)*data_2 # with weight% from gene 1 and
|
||||
weight*value_1+(1-weight)*value_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
|
||||
for value_1, value_2 in zip(value_list_1, value_list_2)]) # from each parents' genes
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -6,20 +6,21 @@ 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
|
||||
ga.population # from the population
|
||||
) #
|
||||
for n in range(needed_amount)]) # until the next population is full
|
||||
|
||||
@ -27,8 +28,8 @@ 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"""
|
||||
|
||||
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
|
||||
@ -37,6 +38,6 @@ class Survivor_Selection:
|
||||
|
||||
ga.population.append_children([ # add in chromosomes
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user