Structure change to make it easier for users to clone and use the repository.
This commit is contained in:
0
structure/README.md
Normal file
0
structure/README.md
Normal file
4
structure/__init__.py
Normal file
4
structure/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
# FROM (. means local) file_name IMPORT function_name
|
||||
from .gene import Gene
|
||||
from .chromosome import Chromosome
|
||||
from .population import Population
|
||||
224
structure/chromosome.py
Normal file
224
structure/chromosome.py
Normal file
@ -0,0 +1,224 @@
|
||||
from structure import Gene as make_gene
|
||||
from itertools import chain
|
||||
|
||||
def to_gene(gene):
|
||||
"""Converts the input to a gene if it isn't already one."""
|
||||
|
||||
if isinstance(gene, make_gene):
|
||||
return gene
|
||||
else:
|
||||
return make_gene(gene)
|
||||
|
||||
|
||||
class Chromosome():
|
||||
|
||||
def __init__(self, gene_list):
|
||||
"""Initialize the chromosome with fitness value of None, and a
|
||||
set of genes dependent on user-passed parameter."""
|
||||
|
||||
self.gene_list = [make_gene(gene) for gene in gene_list]
|
||||
self.fitness = None
|
||||
|
||||
|
||||
@property
|
||||
def gene_value_list(self):
|
||||
"""Returns a list of gene values"""
|
||||
return [gene.value for gene in self]
|
||||
|
||||
|
||||
@property
|
||||
def gene_value_iter(self):
|
||||
"""Returns an iterable of gene values"""
|
||||
return (gene.value for gene in self)
|
||||
|
||||
|
||||
#==================================================#
|
||||
# Magic-Dunder Methods replicating list structure. #
|
||||
#==================================================#
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Allows the user to use
|
||||
|
||||
iter(chromosome)
|
||||
list(chromosome) == chromosome.gene_list
|
||||
tuple(chromosome)
|
||||
for gene in chromosome
|
||||
|
||||
to loop through the chromosome.
|
||||
|
||||
Note: using list(chromosome) creates a copy of
|
||||
the gene_list. Altering this will not
|
||||
alter the original gene_list.
|
||||
"""
|
||||
return iter(self.gene_list)
|
||||
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""
|
||||
Allows the user to use
|
||||
gene = chromosome[index]
|
||||
to get the indexed gene.
|
||||
"""
|
||||
return self.gene_list[index]
|
||||
|
||||
|
||||
def __setitem__(self, index, gene):
|
||||
"""
|
||||
Allows the user to use
|
||||
chromosome[index] = gene
|
||||
to set the indexed gene.
|
||||
"""
|
||||
|
||||
# Single gene
|
||||
if isinstance(index, int):
|
||||
self.gene_list[index] = to_gene(gene)
|
||||
|
||||
# Multiple genes
|
||||
else:
|
||||
self.gene_list[index] = [to_gene(item) for item in gene]
|
||||
|
||||
|
||||
def __delitem__(self, index):
|
||||
"""
|
||||
Allows the user to use
|
||||
del chromosome[index]
|
||||
to delete a gene at the specified index.
|
||||
"""
|
||||
del self.gene_list[index]
|
||||
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Allows the user to use
|
||||
size = len(chromosome)
|
||||
to get the length of the chromosome.
|
||||
"""
|
||||
return len(self.gene_list)
|
||||
|
||||
|
||||
def __contains__(self, gene):
|
||||
"""
|
||||
Allows the user to use
|
||||
if gene in chromosome
|
||||
to check if a gene is in the chromosome.
|
||||
"""
|
||||
return (to_gene(gene) in self.gene_list)
|
||||
|
||||
|
||||
def __eq__(self, chromosome):
|
||||
"""Returns self == chromosome, True if all genes match."""
|
||||
return self.gene_list == chromosome.gene_list
|
||||
|
||||
|
||||
def __add__(self, chromosome):
|
||||
"""Return self + chromosome, a chromosome made by concatenating the genes."""
|
||||
return Chromosome(chain(self, chromosome))
|
||||
|
||||
|
||||
def __iadd__(self, chromosome):
|
||||
"""Implement self += chromosome by concatenating the new genes."""
|
||||
self.gene_list += (to_gene(gene) for gene in chromosome)
|
||||
|
||||
|
||||
def append(self, gene):
|
||||
"""Append gene to the end of the chromosome."""
|
||||
self.gene_list.append(to_gene(gene))
|
||||
|
||||
|
||||
def clear(self):
|
||||
"""Remove all genes from chromosome."""
|
||||
self.gene_list = []
|
||||
|
||||
|
||||
def copy(self):
|
||||
"""Return a copy of the chromosome."""
|
||||
return Chromosome(self)
|
||||
|
||||
|
||||
def count(self, gene):
|
||||
"""Return number of occurrences of the gene in the chromosome."""
|
||||
return self.gene_list.count(to_gene(gene))
|
||||
|
||||
|
||||
def index(self, gene, guess = None):
|
||||
"""
|
||||
Allows the user to use
|
||||
index = chromosome.index(gene)
|
||||
index = chromosome.index(gene, guess)
|
||||
to find the index of a gene in the chromosome.
|
||||
|
||||
If no guess is given, it finds the index of the first match.
|
||||
If a guess is given, it finds index of the nearest match.
|
||||
"""
|
||||
|
||||
# Cast to gene object
|
||||
gene = to_gene(gene)
|
||||
|
||||
# Use built-in method
|
||||
if guess is None:
|
||||
return self.gene_list.index(gene)
|
||||
|
||||
# Use symmetric mod
|
||||
guess %= len(self)
|
||||
if guess >= len(self)//2:
|
||||
guess -= len(self)
|
||||
|
||||
# Search outwards for the gene
|
||||
for i in range(1+len(self)//2):
|
||||
|
||||
# Search to the left
|
||||
if gene == self[guess-i]:
|
||||
return (guess-i) % len(self)
|
||||
|
||||
# Search to the right
|
||||
elif gene == self[guess+i]:
|
||||
return (guess+i) % len(self)
|
||||
|
||||
# Gene not found
|
||||
raise ValueError("No such gene in the chromosome found")
|
||||
|
||||
|
||||
def insert(self, index, gene):
|
||||
"""Insert gene so that self[index] == gene."""
|
||||
self.gene_list.insert(index, to_gene(gene))
|
||||
|
||||
|
||||
def pop(self, index = -1):
|
||||
"""Remove and return gene at index (default last).
|
||||
|
||||
Raises IndexError if chromosome is empty or index is out of range.
|
||||
"""
|
||||
return self.gene_list.pop(index)
|
||||
|
||||
|
||||
def remove(self, gene):
|
||||
"""Remove first occurrence of gene.
|
||||
|
||||
Raises ValueError if the gene in not present.
|
||||
"""
|
||||
self.gene_list.remove(to_gene(gene))
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Allows the user to use
|
||||
chromosome_string = repr(chromosome)
|
||||
chromosome_data = eval(chromosome_string)
|
||||
chromosome = ga.make_chromosome(chromosome_data)
|
||||
to get a backend representation of the chromosome
|
||||
which can be evaluated directly as code to create
|
||||
the chromosome.
|
||||
"""
|
||||
return repr(self.gene_list)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Allows the user to use
|
||||
str(chromosome)
|
||||
print(chromosome)
|
||||
to get a frontend representation of the chromosome.
|
||||
"""
|
||||
return ''.join(str(gene) for gene in self)
|
||||
40
structure/gene.py
Normal file
40
structure/gene.py
Normal file
@ -0,0 +1,40 @@
|
||||
from copy import deepcopy
|
||||
|
||||
class Gene:
|
||||
|
||||
def __init__(self, value):
|
||||
"""Initialize a gene with the input value."""
|
||||
|
||||
# Copy another gene
|
||||
try:
|
||||
self.value = deepcopy(value.value)
|
||||
|
||||
# Otherwise copy the given value
|
||||
except:
|
||||
self.value = deepcopy(value)
|
||||
|
||||
|
||||
def __eq__(self, other_gene):
|
||||
"""Comparing two genes by their value."""
|
||||
return self.value == Gene(other_gene).value
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Allows the user to use
|
||||
gene_string = repr(gene)
|
||||
gene_data = eval(gene_string)
|
||||
gene = ga.make_gene(gene_data)
|
||||
to get a backend representation of the gene.
|
||||
"""
|
||||
return repr(self.value)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Allows the user to use
|
||||
str(gene)
|
||||
print(gene)
|
||||
to get a frontend representation of the gene.
|
||||
"""
|
||||
return f'[{str(self.value)}]'
|
||||
287
structure/population.py
Normal file
287
structure/population.py
Normal file
@ -0,0 +1,287 @@
|
||||
from structure import Chromosome as make_chromosome
|
||||
from itertools import chain
|
||||
|
||||
def to_chromosome(chromosome):
|
||||
"""Converts the input to a chromosome if it isn't already one."""
|
||||
|
||||
if isinstance(chromosome, make_chromosome):
|
||||
return chromosome
|
||||
else:
|
||||
return make_chromosome(chromosome)
|
||||
|
||||
|
||||
class Population:
|
||||
|
||||
def __init__(self, chromosome_list):
|
||||
"""Initialize the population with a collection
|
||||
of chromosomes dependant on user-passed parameter."""
|
||||
|
||||
self.chromosome_list = [make_chromosome(chromosome) for chromosome in chromosome_list]
|
||||
self.mating_pool = []
|
||||
self.next_population = []
|
||||
|
||||
|
||||
def update(self):
|
||||
"""Sets all the population variables to what they should be at
|
||||
the end of the generation """
|
||||
self.chromosome_list = self.next_population
|
||||
self.reset_mating_pool()
|
||||
self.reset_next_population()
|
||||
|
||||
|
||||
def reset_mating_pool(self):
|
||||
"""Clears the mating pool"""
|
||||
self.mating_pool = []
|
||||
|
||||
|
||||
def reset_next_population(self):
|
||||
"""Clears the next population"""
|
||||
self.next_population = []
|
||||
|
||||
|
||||
def remove_chromosome(self, index):
|
||||
"""Removes and returns a chromosome from the indicated index from the population"""
|
||||
return self.chromosome_list.pop(index)
|
||||
|
||||
|
||||
def remove_parent(self, index):
|
||||
"""Removes and returns a parent from the indicated index from the mating pool"""
|
||||
return self.mating_pool.pop(index)
|
||||
|
||||
|
||||
def remove_child(self, index):
|
||||
"""Removes and returns a child from the indicated index from the next population"""
|
||||
return self.next_population.pop(index)
|
||||
|
||||
|
||||
def append_children(self, chromosome_list):
|
||||
"""Appends a list of chromosomes to the next population."""
|
||||
|
||||
self.next_population += (
|
||||
to_chromosome(chromosome)
|
||||
for chromosome
|
||||
in chromosome_list
|
||||
)
|
||||
|
||||
|
||||
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"""
|
||||
|
||||
if index is None:
|
||||
index = len(self)
|
||||
self.chromosome_list.insert(index, to_chromosome(chromosome))
|
||||
|
||||
|
||||
def add_parent(self, chromosome):
|
||||
"""Adds a chromosome to the mating pool"""
|
||||
self.mating_pool.append(to_chromosome(chromosome))
|
||||
|
||||
|
||||
def add_child(self, chromosome):
|
||||
"""Adds a chromosome to the next population"""
|
||||
self.next_population.append(to_chromosome(chromosome))
|
||||
|
||||
|
||||
def set_parent(self, index):
|
||||
"""Sets the indexed chromosome from the population as a parent"""
|
||||
self.add_parent(self[index])
|
||||
|
||||
|
||||
#==================================================#
|
||||
# Magic-Dunder Methods replicating list structure. #
|
||||
#==================================================#
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Allows the user to use
|
||||
|
||||
iter(population)
|
||||
list(population) == population.chromosome_list
|
||||
tuple(population)
|
||||
for chromosome in population
|
||||
|
||||
to loop through the population.
|
||||
"""
|
||||
return iter(self.chromosome_list)
|
||||
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""
|
||||
Allows the user to use
|
||||
chromosome = population[index]
|
||||
to get the indexed chromosome.
|
||||
"""
|
||||
return self.chromosome_list[index]
|
||||
|
||||
|
||||
def __setitem__(self, index, chromosome):
|
||||
"""
|
||||
Allows the user to use
|
||||
population[index] = chromosome
|
||||
to set the indexed chromosome.
|
||||
"""
|
||||
|
||||
# Just one chromosome
|
||||
if isinstance(index, int):
|
||||
self.chromosome_list[index] = to_chromosome(chromosome)
|
||||
|
||||
# Multiple chromosomes
|
||||
else:
|
||||
self.chromosome_list[index] = [to_chromosome(item) for item in chromosome]
|
||||
|
||||
|
||||
def __delitem__(self, index):
|
||||
"""
|
||||
Allows the user to use
|
||||
del population[index]
|
||||
to delete a chromosome at the specified index.
|
||||
"""
|
||||
del self.chromosome_list[index]
|
||||
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Allows the user to use
|
||||
size = len(population)
|
||||
to get the length of the population.
|
||||
"""
|
||||
return len(self.chromosome_list)
|
||||
|
||||
|
||||
def __contains__(self, chromosome):
|
||||
"""
|
||||
Allows the user to use
|
||||
if chromosome in population
|
||||
to check if a chromosome is in the population.
|
||||
"""
|
||||
return (to_chromosome(chromosome) in self.chromosome_list)
|
||||
|
||||
|
||||
def __eq__(self, population):
|
||||
"""Returns self == population, True if all chromosomes match."""
|
||||
return self.chromosome_list == population.chromosome_list
|
||||
|
||||
|
||||
def __add__(self, population):
|
||||
"""Returns self + population, a population made by concatenating the chromosomes."""
|
||||
return Population(chain(self, population))
|
||||
|
||||
|
||||
def __iadd__(self, population):
|
||||
"""Implement self += population by concatenating the new chromosomes."""
|
||||
self.chromosome_list += (to_chromosome(chromosome) for chromosome in population)
|
||||
|
||||
|
||||
def append(self, chromosome):
|
||||
"""Append chromosome to the end of the population."""
|
||||
self.chromosome_list.append(to_chromosome(chromosome))
|
||||
|
||||
|
||||
def clear(self):
|
||||
"""Remove all chromosomes from the population."""
|
||||
self.chromosome_list = []
|
||||
|
||||
|
||||
def copy(self):
|
||||
"""Return a copy of the population."""
|
||||
return Population(self)
|
||||
|
||||
|
||||
def count(self, chromosome):
|
||||
"""Return number of occurrences of the chromosome in the population."""
|
||||
return self.chromosome_list.count(to_chromosome(chromosome))
|
||||
|
||||
|
||||
def index(self, chromosome, guess = None):
|
||||
"""
|
||||
Allows the user to use
|
||||
index = population.index(chromosome)
|
||||
index = population.index(chromosome, guess)
|
||||
to find the index of a chromosome in the population.
|
||||
|
||||
If no guess is given, it finds the index of the first match.
|
||||
If a guess is given, it finds index of the nearest match.
|
||||
"""
|
||||
|
||||
chromosome = to_chromosome(chromosome)
|
||||
|
||||
# Use built-in method
|
||||
if guess is None:
|
||||
return self.chromosome_list.index(chromosome)
|
||||
|
||||
# Use symmetric mod
|
||||
guess %= len(self)
|
||||
if guess >= len(self)//2:
|
||||
guess -= len(self)
|
||||
|
||||
# Search outwards for the chromosome
|
||||
for i in range(len(self)//2):
|
||||
|
||||
# Search to the left
|
||||
if chromosome == self[guess-i]:
|
||||
return (guess-i) % len(self)
|
||||
|
||||
# Search to the right
|
||||
elif chromosome == self[guess+i]:
|
||||
return (guess+i) % len(self)
|
||||
|
||||
# Chromosome not found
|
||||
raise IndexError("No such chromosome in the population found")
|
||||
|
||||
|
||||
def insert(self, index, chromosome):
|
||||
"""Insert chromosome so that self[index] == chromsome."""
|
||||
self.chromosome_list.insert(index, to_chromosome(chromosome))
|
||||
|
||||
|
||||
def pop(self, index = -1):
|
||||
"""Remove and return chromosome at index (default last).
|
||||
|
||||
Raises IndexError if population is empty or index is out of range.
|
||||
"""
|
||||
return self.chromosome_list.pop(index)
|
||||
|
||||
|
||||
def remove(self, chromosome):
|
||||
"""Remove first occurrence of chromosome.
|
||||
|
||||
Raises ValueError if the chromosome is not present.
|
||||
"""
|
||||
self.chromosome_list.remove(to_chromosome(chromosome))
|
||||
|
||||
|
||||
def sort(self, *, key = lambda chromosome: chromosome.fitness, reverse):
|
||||
"""Sorts the population."""
|
||||
self.chromosome_list.sort(
|
||||
key = key,
|
||||
reverse = reverse
|
||||
)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Allows the user to use
|
||||
population_string = repr(population)
|
||||
population_data = eval(population_string)
|
||||
population = ga.make_population(population_data)
|
||||
to get a backend representation of the population
|
||||
which can be evaluated directly as code to create
|
||||
the population.
|
||||
"""
|
||||
return repr(self.chromosome_list)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Allows the user to use
|
||||
str(population)
|
||||
print(population)
|
||||
to get a frontend representation of the population.
|
||||
"""
|
||||
return ''.join(
|
||||
f'Chromosome - {index} {chromosome} / Fitness = {chromosome.fitness}\n'
|
||||
for index, chromosome
|
||||
in enumerate(self)
|
||||
)
|
||||
0
structure/test_struction.py
Normal file
0
structure/test_struction.py
Normal file
Reference in New Issue
Block a user