Delete files for renaming

This commit is contained in:
SimpleArt
2020-12-30 14:35:08 -05:00
parent 21c12c2bcd
commit cbc2fafb07
24 changed files with 0 additions and 955 deletions

View File

@ -1 +0,0 @@
# Mutation functions

View File

@ -1,2 +0,0 @@
# FROM (. means local) file_name IMPORT function_name
from .crossover_methods import Crossover_Methods

View File

@ -1,258 +0,0 @@
from EasyGA import function_info
import random
# Round to an integer near x with higher probability
# the closer it is to that integer.
randround = lambda x: int(x + random.random())
@function_info
def _check_weight(individual_method):
"""Checks if the weight is between 0 and 1 before running.
Exception may occur when using ga.adapt, which will catch
the error and try again with valid weight.
"""
def new_method(ga, parent_1, parent_2, *, weight = individual_method.__kwdefaults__.get('weight', None)):
if weight is None:
individual_method(ga, parent_1, parent_2)
elif 0 < weight < 1:
individual_method(ga, parent_1, parent_2, weight = weight)
else:
raise ValueError(f"Weight must be between 0 and 1 when using {individual_method.__name__}.")
return new_method
@function_info
def _gene_by_gene(individual_method):
"""Perform crossover by making a single new chromosome by combining each gene by gene."""
def new_method(ga, parent_1, parent_2, *, weight = individual_method.__kwdefaults__.get('weight', 'None')):
ga.population.add_child(
individual_method(ga, value_1, value_2)
if weight == 'None' else
individual_method(ga, value_1, value_2, weight = weight)
for value_1, value_2
in zip(parent_1.gene_value_iter, parent_2.gene_value_iter)
)
return new_method
class Crossover_Methods:
# Allowing access to decorators when importing class
_check_weight = _check_weight
_gene_by_gene = _gene_by_gene
class Population:
"""Methods for selecting chromosomes to crossover."""
def sequential_selection(ga, mating_pool):
"""Select sequential pairs from the mating pool.
Every parent is paired with the previous parent.
The first parent is paired with the last parent.
"""
for index in range(len(mating_pool)): # for each parent in the mating pool
ga.crossover_individual_impl( # apply crossover to
mating_pool[index], # the parent and
mating_pool[index-1], # the previous parent
)
def random_selection(ga, mating_pool):
"""Select random pairs from the mating pool.
Every parent is paired with a random parent.
"""
for parent in mating_pool: # for each parent in the mating pool
ga.crossover_individual_impl( # apply crossover to
parent, # the parent and
random.choice(mating_pool), # a random parent
)
class Individual:
"""Methods for crossing parents."""
@_check_weight
def single_point(ga, parent_1, parent_2, *, weight = 0.5):
"""Cross two parents by swapping genes at one random point."""
minimum_parent_length = min(len(parent_1), len(parent_2))
# Weighted random integer from 0 to minimum parent length - 1
swap_index = int(ga.weighted_random(weight) * minimum_parent_length)
ga.population.add_child(parent_1[:swap_index] + parent_2[swap_index:])
ga.population.add_child(parent_2[:swap_index] + parent_1[swap_index:])
@_check_weight
def multi_point(ga, parent_1, parent_2, *, weight = 0.5):
"""Cross two parents by swapping genes at multiple points."""
pass
@_check_weight
@_gene_by_gene
def uniform(ga, value_1, value_2, *, weight = 0.5):
"""Cross two parents by swapping all genes randomly."""
return random.choices(gene_pair, cum_weights = [weight, 1])[0]
class Arithmetic:
"""Crossover methods for numerical genes."""
@_gene_by_gene
def average(ga, value_1, value_2, *, weight = 0.5):
"""Cross two parents by taking the average of the genes."""
average_value = weight*value_1 + (1-weight)*value_2
if type(value_1) == type(value_2) == int:
average_value = randround(value)
return average_value
@_gene_by_gene
def extrapolate(ga, value_1, value_2, *, weight = 0.5):
"""Cross two parents by extrapolating towards the first parent.
May result in gene values outside the expected domain.
"""
extrapolated_value = weight*value_1 + (1-weight)*value_2
if type(value_1) == type(value_2) == int:
extrapolated_value = randround(value)
return extrapolated_value
@_check_weight
@_gene_by_gene
def random(ga, value_1, value_2, *, weight = 0.5):
"""Cross two parents by taking a random integer or float value between each of the genes."""
value = value_1 + ga.weighted_random(weight) * (value_2-value_1)
if type(value_1) == type(value_2) == int:
value = randround(value)
return value
class Permutation:
"""Crossover methods for permutation based chromosomes."""
@_check_weight
def ox1(ga, parent_1, parent_2, *, weight = 0.5):
"""Cross two parents by slicing out a random part of one parent
and then filling in the rest of the genes from the second parent."""
# Too small to cross
if len(parent_1) < 2:
return parent_1.gene_list
# Unequal parent lengths
if len(parent_1) != len(parent_2):
raise ValueError("Parents do not have the same lengths.")
# Swap with weighted probability so that most of the genes
# are taken directly from parent 1.
if random.choices([0, 1], cum_weights = [weight, 1]) == 1:
parent_1, parent_2 = parent_2, parent_1
# Extract genes from parent 1 between two random indexes
index_2 = random.randrange(1, len(parent_1))
index_1 = random.randrange(index_2)
# Create copies of the gene lists
gene_list_1 = [None]*index_1 + parent_1[index_1:index_2] + [None]*(len(parent_1)-index_2)
gene_list_2 = list(parent_2)
input_index = 0
# For each gene from the second parent
for _ in range(len(gene_list_2)):
# Remove it if it is already used
if gene_list_2[-1] in gene_list_1:
gene_list_2.pop(-1)
# Add it if it has not been used
else:
if input_index == index_1:
input_index = index_2
gene_list_1[input_index] = gene_list_2.pop(-1)
input_index += 1
ga.population.add_child(gene_list_1)
@_check_weight
def partially_mapped(ga, parent_1, parent_2, *, weight = 0.5):
"""Cross two parents by slicing out a random part of one parent
and then filling in the rest of the genes from the second parent,
preserving the ordering of genes wherever possible.
NOTE: Needs to be fixed, since genes are not hashable..."""
# Too small to cross
if len(parent_1) < 2:
return parent_1.gene_list
# Unequal parent lengths
if len(parent_1) != len(parent_2):
raise ValueError("Parents do not have the same lengths.")
# Swap with weighted probability so that most of the genes
# are taken directly from parent 1.
if random.choices([0, 1], cum_weights = [weight, 1]) == 1:
parent_1, parent_2 = parent_2, parent_1
# Extract genes from parent 1 between two random indexes
index_2 = random.randrange(1, len(parent_1))
index_1 = random.randrange(index_2)
# Create copies of the gene lists
gene_list_1 = [None]*index_1 + parent_1[index_1:index_2] + [None]*(len(parent_1)-index_2)
gene_list_2 = list(parent_2)
# Create hash for gene list 2
hash = {gene:index for index, gene in enumerate(gene_list_2)}
# For each gene in the copied segment from parent 2
for i in range(index_1, index_2):
# If it is not already copied,
# find where it got displaced to
j = i
while gene_list_1[(j := hash[gene_list_1[j]])] is not None:
pass
gene_list_1[j] = gene_list_2[i]
# Fill in whatever is leftover (copied from ox1).
# For each gene from the second parent
for _ in range(len(gene_list_2)):
# Remove it if it is already used
if gene_list_2[-1] in gene_list_1:
gene_list_2.pop(-1)
# Add it if it has not been used
else:
if input_index == index_1:
input_index = index_2
gene_list_1[input_index] = gene_list_2.pop(-1)
input_index += 1
ga.population.add_child(gene_list_1)

View File

@ -1 +0,0 @@
# Fitness function

View File

@ -1,2 +0,0 @@
# FROM (. means local) file_name IMPORT class name
from .fitness_examples import Fitness_Examples

View File

@ -1,36 +0,0 @@
class Fitness_Examples:
"""Fitness function examples used"""
def is_it_5(chromosome):
"""A very simple case test function - If the chromosomes gene value is a 5 add one
to the chromosomes overall fitness value."""
# Overall fitness value
fitness = 0
# For each gene in the chromosome
for gene in chromosome:
# Check if its value = 5
if(gene.value == 5):
# If its value is 5 then add one to
# the overal fitness of the chromosome.
fitness += 1
return fitness
def near_5(chromosome):
"""Test's the GA's ability to handle floats.
Computes how close each gene is to 5.
"""
return sum([1-pow(1-gene.value/5, 2) for gene in chromosome])
def index_dependent_values(chromosome):
"""Test of the GA's ability to improve fitness when the value is index-dependent.
If a gene is equal to its index in the chromosome + 1, fitness is incremented.
"""
fitness = 0
for i in range(len(chromosome)):
if (chromosome[i].value == i+1):
fitness += 1
return fitness

View File

@ -1 +0,0 @@

View File

@ -1 +0,0 @@
# Mutation functions

View File

@ -1,2 +0,0 @@
# FROM (. means local) file_name IMPORT function_name
from .mutation_methods import Mutation_Methods

View File

@ -1,217 +0,0 @@
from EasyGA import function_info
import random
from math import ceil
@function_info
def _check_chromosome_mutation_rate(population_method):
"""Checks if the chromosome mutation rate is a float between 0 and 1 before running."""
def new_method(ga):
if not isinstance(ga.chromosome_mutation_rate, float):
raise TypeError("Chromosome mutation rate must be a float.")
elif 0 < ga.chromosome_mutation_rate < 1:
population_method(ga)
else:
raise ValueError("Chromosome mutation rate must be between 0 and 1.")
return new_method
@function_info
def _check_gene_mutation_rate(individual_method):
"""Checks if the gene mutation rate is a float between 0 and 1 before running."""
def new_method(ga, index):
if not isinstance(ga.gene_mutation_rate, float):
raise TypeError("Gene mutation rate must be a float.")
elif 0 < ga.gene_mutation_rate <= 1:
individual_method(ga, index)
else:
raise ValueError("Gene mutation rate must be between 0 and 1.")
return new_method
@function_info
def _reset_fitness(individual_method):
"""Resets the fitness value of the chromosome."""
def new_method(ga, chromosome):
chromosome.fitness = None
individual_method(ga, chromosome)
return new_method
@function_info
def _loop_random_mutations(individual_method):
"""Runs the individual method until enough
genes are mutated on the indexed chromosome.
"""
# Change input to include the gene index being mutated.
def new_method(ga, chromosome):
sample_space = range(len(chromosome))
sample_size = ceil(len(chromosome)*ga.gene_mutation_rate)
# Loop the individual method until enough genes are mutated.
for index in random.sample(sample_space, sample_size):
individual_method(ga, chromosome, index)
return new_method
class Mutation_Methods:
_check_chromosome_mutation_rate = _check_chromosome_mutation_rate
_check_gene_mutation_rate = _check_gene_mutation_rate
_reset_fitness = _reset_fitness
_loop_random_mutations = _loop_random_mutations
class Population:
"""Methods for selecting chromosomes to mutate"""
@_check_chromosome_mutation_rate
def random_selection(ga):
"""Selects random chromosomes."""
sample_space = range(len(ga.population))
sample_size = ceil(len(ga.population)*ga.chromosome_mutation_rate)
# Loop the individual method until enough genes are mutated.
for index in random.sample(sample_space, sample_size):
ga.mutation_individual_impl(ga.population[index])
@_check_chromosome_mutation_rate
def random_avoid_best(ga):
"""Selects random chromosomes while avoiding the best chromosomes. (Elitism)"""
sample_space = range(ceil(ga.percent_converged*len(ga.population)*3/16), len(ga.population))
sample_size = ceil(ga.chromosome_mutation_rate*len(ga.population))
for index in random.sample(sample_space, sample_size):
ga.mutation_individual_impl(ga.population[index])
@_check_chromosome_mutation_rate
def best_replace_worst(ga):
"""Selects the best chromosomes, copies them, and replaces the worst chromosomes."""
mutation_amount = ceil(ga.chromosome_mutation_rate*len(ga.population))
for i in range(mutation_amount):
ga.population[-i-1] = ga.make_chromosome(ga.population[i])
ga.mutation_individual_impl(ga.population[-i-1])
class Individual:
"""Methods for mutating a single chromosome."""
@_check_gene_mutation_rate
@_reset_fitness
@_loop_random_mutations
def individual_genes(ga, chromosome, index):
"""Mutates random genes by making completely new genes."""
# Using the chromosome_impl
if ga.chromosome_impl is not None:
chromosome[index] = ga.make_gene(ga.chromosome_impl()[index])
# Using the gene_impl
elif ga.gene_impl is not None:
chromosome[index] = ga.make_gene(ga.gene_impl())
# Exit because no gene creation method specified
else:
raise Exception("Did not specify any initialization constraints.")
class Arithmetic:
"""Methods for mutating a chromosome by numerically modifying the genes."""
@_check_gene_mutation_rate
@_reset_fitness
@_loop_random_mutations
def average(ga, chromosome, index):
"""Mutates random genes by making completely new genes
and then averaging them with the old genes. May cause
premature convergence. Weight is the reciprocal of the
number of generations run."""
weight = 1/max(1, ga.current_generation)
# Using the chromosome_impl
if ga.chromosome_impl is not None:
new_value = ga.chromosome_impl()[index]
# Using the gene_impl
elif ga.gene_impl is not None:
new_value = ga.gene_impl()
# Exit because no gene creation method specified
else:
raise Exception("Did not specify any initialization constraints.")
chromosome[index] = ga.make_gene((1-weight)*chromosome[index].value + weight*new_value)
@_check_gene_mutation_rate
@_reset_fitness
@_loop_random_mutations
def reflect_genes(ga, chromosome, index):
"""Reflects genes against the best chromosome.
Requires large genetic variety to work well but
when it does it may be very fast."""
difference = ga.population[0][index].value - chromosome[index].value
value = ga.population[0][index].value + 2*difference
chromosome[index] = ga.make_gene(value)
class Permutation:
"""Methods for mutating a chromosome
by changing the order of the genes."""
@_check_gene_mutation_rate
@_reset_fitness
@_loop_random_mutations
def swap_genes(ga, chromosome, index):
"""Swaps two random genes in the chromosome."""
# Indexes of genes to swap
index_one = index
index_two = random.randrange(len(chromosome))
# Swap genes
chromosome[index_one], chromosome[index_two] = chromosome[index_two], chromosome[index_one]
@_check_gene_mutation_rate
@_reset_fitness
def swap_segments(ga, chromosome):
"""Splits the chromosome into 3 segments and shuffle them."""
# Chromosome too short to mutate
if len(chromosome) < 3:
return
# Indexes to split the chromosome
index_two = random.randrange(2, len(chromosome))
index_one = random.randrange(1, index_two)
# Extract segments and shuffle them
segments = [chromosome[:index_one], chromosome[index_one:index_two], chromosome[index_two:]]
random.shuffle(segments)
# Put segments back together
chromosome.gene_list = segments[0] + segments[1] + segments[2]

View File

@ -1 +0,0 @@
# Selection functions

View File

@ -1,2 +0,0 @@
# FROM (. means local) file_name IMPORT function_name
from .parent_selection_methods import Parent_Selection

View File

@ -1,266 +0,0 @@
from EasyGA import function_info
import random
@function_info
def _check_selection_probability(selection_method):
"""Raises an exception if the selection_probability
is not between 0 and 1 inclusively. Otherwise runs
the selection method.
"""
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.")
new_method.__name__ = selection_method.__name__
return new_method
@function_info
def _check_positive_fitness(selection_method):
"""Raises an exception if the population contains a
chromosome with negative fitness. Otherwise runs
the selection method.
"""
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 can't have negative values or be all 0. Consider using rank selection or stochastic selection instead.")
new_method.__name__ = selection_method.__name__
return new_method
@function_info
def _ensure_sorted(selection_method):
"""Sorts the population by fitness
and then runs the selection method.
"""
def new_method(ga):
ga.population.sort_by_best_fitness(ga)
selection_method(ga)
new_method.__name__ = selection_method.__name__
return new_method
@function_info
def _compute_parent_amount(selection_method):
"""Computes the amount of parents
needed to be selected, and passes it
as another argument for the method.
"""
def new_method(ga):
parent_amount = max(2, round(len(ga.population)*ga.parent_ratio))
selection_method(ga, parent_amount)
new_method.__name__ = selection_method.__name__
return new_method
class Parent_Selection:
# Allowing access to decorators when importing class
_check_selection_probability = _check_selection_probability
_check_positive_fitness = _check_positive_fitness
_ensure_sorted = _ensure_sorted
_compute_parent_amount = _compute_parent_amount
class Rank:
"""Methods for selecting parents based on their rankings in the population
i.e. the n-th best chromosome has a fixed probability of being selected,
regardless of their chances"""
@_check_selection_probability
@_ensure_sorted
@_compute_parent_amount
def tournament(ga, parent_amount):
"""
Will make tournaments of size tournament_size and choose the winner (best fitness)
from the tournament and use it as a parent for the next generation. The total number
of parents selected is determined by parent_ratio, an attribute to the GA object.
May require many loops if the selection probability is very low.
"""
# Choose the tournament size.
# Use no less than 5 chromosomes per tournament.
tournament_size = int(len(ga.population)*ga.tournament_size_ratio)
if tournament_size < 5:
tournament_size = min(5, len(ga.population))
# Repeat tournaments until the mating pool is large enough.
while len(ga.population.mating_pool) < parent_amount:
# Generate a random tournament group and sort by fitness.
tournament_group = sorted(random.sample(
range(len(ga.population)),
tournament_size
))
# For each chromosome, add it to the mating pool based on its rank in the tournament.
for index in range(tournament_size):
# Probability required is selection_probability * (1-selection_probability) ^ index
# Each chromosome is (1-selection_probability) times
# more likely to become a parent than the next ranked.
if random.random() < ga.selection_probability * (1-ga.selection_probability) ** index:
break
# Use random in tournament if noone wins
else:
index = random.randrange(tournament_size)
ga.population.set_parent(tournament_group[index])
@_check_selection_probability
@_ensure_sorted
@_compute_parent_amount
def stochastic_geometric(ga, parent_amount):
"""
Selects parents with probabilities given by a geometric progression. This
method is similar to tournament selection, but doesn't create several
tournaments. Instead, it assigns probabilities to each rank and selects
the entire mating pool using random.choices. Since it essentially uses the
entire population as a tournament repeatedly, it is less likely to select
worse parents than tournament selection.
"""
# Set the weights of each parent based on their rank.
# Each chromosome is (1-selection_probability) times
# more likely to become a parent than the next ranked.
weights = [
(1-ga.selection_probability) ** i
for i
in range(len(ga.population))
]
# Set the mating pool.
ga.population.mating_pool = random.choices(ga.population, weights, k = parent_amount)
@_check_selection_probability
@_ensure_sorted
@_compute_parent_amount
def stochastic_arithmetic(ga, parent_amount):
"""
Selects parents with probabilities given by an arithmetic progression. This
method is similar to stochastic-geometric selection, but is more likely to
select worse parents with its simpler selection scheme.
"""
# Set the weights of each parent based on their rank.
# The worst chromosome has a weight of 1,
# the next worst chromosome has a weight of 2,
# etc.
# with an inflation of (1-selection probability) * average weight
average_weight = (len(ga.population)+1) // 2
inflation = (1-ga.selection_probability) * average_weight
weights = [
i + inflation
for i
in range(len(ga.population), 0, -1)
]
# Set the mating pool.
ga.population.mating_pool = random.choices(ga.population, weights, k = parent_amount)
class Fitness:
@_check_selection_probability
@_ensure_sorted
@_check_positive_fitness
@_compute_parent_amount
def roulette(ga, parent_amount):
"""Roulette selection works based off of how strong the fitness is of the
chromosomes in the population. The stronger the fitness the higher the probability
that it will be selected. Using the example of a casino roulette wheel.
Where the chromosomes are the numbers to be selected and the board size for
those numbers are directly proportional to the chromosome's current fitness. Where
the ball falls is a randomly generated number between 0 and 1.
"""
# The sum of all the fitnessess in a 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]
# The chance of being selected increases incrementally
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.mating_pool) < parent_amount:
# Spin the roulette
rand_number = random.random()
# Find where the roulette landed.
for index in range(len(probability)):
if (probability[index] >= rand_number):
ga.population.set_parent(index)
break
@_check_selection_probability
@_ensure_sorted
@_compute_parent_amount
def stochastic(ga, parent_amount):
"""
Selects parents using the same probability approach as roulette selection,
but doesn't spin a roulette for every selection. Uses random.choices with
weighted values to select parents and may produce duplicate parents.
"""
# All fitnesses are the same, select randomly.
if ga.get_chromosome_fitness(-1) == ga.get_chromosome_fitness(0):
offset = 1-ga.get_chromosome_fitness(-1)
# Some chromosomes have negative fitness, shift them all into positives.
elif ga.get_chromosome_fitness(-1) < 0:
offset = -ga.get_chromosome_fitness(-1)
# No change needed.
else:
offset = 0
# Set the weights of each parent based on their fitness + offset.
weights = [
ga.get_chromosome_fitness(index) + offset
for index
in range(len(ga.population))
]
inflation = sum(weights) * (1 - ga.selection_probability)
# Rescale and adjust using selection_probability so that
# if selection_probability is high, a low inflation is used,
# making selection mostly based on fitness.
# if selection_probability is low, a high offset is used,
# so everyone has a more equal chance.
weights = [
weight + inflation
for weight
in weights
]
# Set the mating pool.
ga.population.mating_pool = random.choices(ga.population, weights, k = parent_amount)

View File

@ -1 +0,0 @@
# Selection functions

View File

@ -1,2 +0,0 @@
# FROM (. means local) file_name IMPORT function_name
from .survivor_selection_methods import Survivor_Selection

View File

@ -1,60 +0,0 @@
from EasyGA import function_info
import random
@function_info
def _append_to_next_population(survivor_method):
"""Appends the selected chromosomes to the next population."""
def new_method(ga):
ga.population.append_children(survivor_method(ga))
new_method.__name__ = survivor_method.__name__
return new_method
class Survivor_Selection:
"""Survivor selection determines which individuals should be brought to the next generation"""
# Allowing access to decorators when importing class
_append_to_next_population = _append_to_next_population
@_append_to_next_population
def fill_in_best(ga):
"""Fills in the next population with the best chromosomes from the last population"""
needed_amount = len(ga.population) - len(ga.population.next_population)
return ga.population[:needed_amount]
@_append_to_next_population
def fill_in_random(ga):
"""Fills in the next population with random chromosomes from the last population"""
needed_amount = len(ga.population) - len(ga.population.next_population)
return random.sample(ga.population, needed_amount)
@_append_to_next_population
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(needed_amount, len(mating_pool))
random_amount = needed_amount - parent_amount
# Only parents are used.
if random_amount == 0:
return ga.population.mating_pool[:parent_amount]
# Parents need to be removed from the random sample to avoid dupes.
else:
return mating_pool \
+ random.sample(
set(ga.population) - mating_pool,
random_amount
)

View File

@ -1 +0,0 @@
# Termination functions

View File

@ -1,2 +0,0 @@
# FROM (. means local) file_name IMPORT class name
from .termination_methods import Termination_Methods

View File

@ -1,99 +0,0 @@
from EasyGA import function_info
@function_info
def _add_by_fitness_goal(termination_impl):
"""Adds termination by fitness goal to the method."""
def new_method(ga):
# Try to check the fitness goal
try:
# If minimum fitness goal reached, stop ga.
if ga.target_fitness_type == 'min' and ga.population[0].fitness <= ga.fitness_goal:
return False
# If maximum fitness goal reached, stop ga.
elif ga.target_fitness_type == 'max' and ga.population[0].fitness >= ga.fitness_goal:
return False
# Fitness or fitness goals are None
except TypeError:
pass
# Population not initialized
except AttributeError:
pass
# Check other termination methods
return termination_impl(ga)
return new_method
@function_info
def _add_by_generation_goal(termination_impl):
"""Adds termination by generation goal to the method."""
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:
return False
# Check other termination methods
return termination_impl(ga)
return new_method
@function_info
def _add_by_tolerance_goal(termination_impl):
"""Adds termination by tolerance goal to the method."""
def new_method(ga):
# If tolerance is set, check it, if possible.
try:
best_fitness = ga.population[0].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
if abs(best_fitness - threshhold_fitness) < tol:
return False
# Fitness or tolerance goals are None
except TypeError:
pass
# Population not initialized
except AttributeError:
pass
# Check other termination methods
return termination_impl(ga)
return new_method
class Termination_Methods:
"""Example functions that can be used to terminate the the algorithms loop"""
# Allowing access to decorators when importing class
_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
@_add_by_generation_goal
@_add_by_tolerance_goal
def fitness_generation_tolerance(ga):
"""Terminate GA when any of the
- fitness,
- generation, or
- tolerance
goals are met."""
return True