Attempted to reimport from egg
This commit is contained in:
103
EasyGA/EasyGA.py
103
EasyGA/EasyGA.py
@ -2,38 +2,38 @@ from __future__ import annotations
|
|||||||
from typing import Optional, MutableSequence, Iterable
|
from typing import Optional, MutableSequence, Iterable
|
||||||
|
|
||||||
# Import all decorators
|
# Import all decorators
|
||||||
import decorators
|
import EasyGA.decorators
|
||||||
|
|
||||||
# Import all the data structure prebuilt modules
|
# Import all the data structure prebuilt modules
|
||||||
from structure import Population as make_population
|
from EasyGA.structure import Population as make_population
|
||||||
from structure import Chromosome as make_chromosome
|
from EasyGA.structure import Chromosome as make_chromosome
|
||||||
from structure import Gene as make_gene
|
from EasyGA.structure import Gene as make_gene
|
||||||
from structure import Population
|
from EasyGA.structure import Population
|
||||||
from structure import Chromosome
|
from EasyGA.structure import Chromosome
|
||||||
from structure import Gene
|
from EasyGA.structure import Gene
|
||||||
|
|
||||||
# Misc. Methods
|
# Misc. Methods
|
||||||
from examples import Fitness
|
from EasyGA.examples import Fitness
|
||||||
from termination import Termination
|
from EasyGA.termination import Termination
|
||||||
|
|
||||||
# Parent/Survivor Selection Methods
|
# Parent/Survivor Selection Methods
|
||||||
from parent import Parent
|
from EasyGA.parent import Parent
|
||||||
from survivor import Survivor
|
from EasyGA.survivor import Survivor
|
||||||
|
|
||||||
# Genetic Operator Methods
|
# Genetic Operator Methods
|
||||||
from crossover import Crossover
|
from EasyGA.crossover import Crossover
|
||||||
from mutation import Mutation
|
from EasyGA.mutation import Mutation
|
||||||
|
|
||||||
# Default Attributes for the GA
|
# Default Attributes for the GA
|
||||||
from attributes import Attributes
|
from EasyGA.attributes import Attributes
|
||||||
|
|
||||||
# Database class
|
# Database class
|
||||||
# from database import SQLDatabase
|
from EasyGA.database import sql_database
|
||||||
# from sqlite3 import Error
|
from sqlite3 import Error
|
||||||
|
|
||||||
# Graphing package
|
# Graphing package
|
||||||
# from database import MatplotlibGraph
|
from EasyGA.database import matplotlib_graph
|
||||||
# import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
class GA(Attributes):
|
class GA(Attributes):
|
||||||
@ -46,6 +46,7 @@ class GA(Attributes):
|
|||||||
https://github.com/danielwilczak101/EasyGA/wiki
|
https://github.com/danielwilczak101/EasyGA/wiki
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def evolve(self: GA, number_of_generations: float = float('inf'), consider_termination: bool = True) -> None:
|
def evolve(self: GA, number_of_generations: float = float('inf'), consider_termination: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Evolves the ga until the ga is no longer active.
|
Evolves the ga until the ga is no longer active.
|
||||||
@ -62,12 +63,9 @@ class GA(Attributes):
|
|||||||
if self.population is None:
|
if self.population is None:
|
||||||
self.initialize_population()
|
self.initialize_population()
|
||||||
|
|
||||||
# Evolve the specified number of generations.
|
cond1 = lambda: number_of_generations > 0 # Evolve the specified number of generations.
|
||||||
def cond1(): return number_of_generations > 0
|
cond2 = lambda: not consider_termination # If consider_termination flag is set:
|
||||||
# If consider_termination flag is set:
|
cond3 = lambda: cond2() or self.active() # check termination conditions.
|
||||||
def cond2(): return not consider_termination
|
|
||||||
# check termination conditions.
|
|
||||||
def cond3(): return cond2() or self.active()
|
|
||||||
|
|
||||||
while cond1() and cond3():
|
while cond1() and cond3():
|
||||||
|
|
||||||
@ -76,11 +74,10 @@ class GA(Attributes):
|
|||||||
|
|
||||||
# Create the database here to allow the user to change the
|
# Create the database here to allow the user to change the
|
||||||
# database name and structure before running the function.
|
# database name and structure before running the function.
|
||||||
# self.database.create_all_tables(self)
|
self.database.create_all_tables(self)
|
||||||
|
|
||||||
# Add the current configuration to the config table
|
# Add the current configuration to the config table
|
||||||
# self.database.insert_config(self)
|
self.database.insert_config(self)
|
||||||
pass
|
|
||||||
|
|
||||||
# Otherwise evolve the population.
|
# Otherwise evolve the population.
|
||||||
else:
|
else:
|
||||||
@ -96,8 +93,7 @@ class GA(Attributes):
|
|||||||
self.sort_by_best_fitness()
|
self.sort_by_best_fitness()
|
||||||
|
|
||||||
# Save the population to the database
|
# Save the population to the database
|
||||||
if self.save_data:
|
self.save_population()
|
||||||
self.save_population()
|
|
||||||
|
|
||||||
# Adapt the ga if the generation times the adapt rate
|
# Adapt the ga if the generation times the adapt rate
|
||||||
# passes through an integer value.
|
# passes through an integer value.
|
||||||
@ -105,9 +101,10 @@ class GA(Attributes):
|
|||||||
if int(adapt_counter) < int(adapt_counter + self.adapt_rate):
|
if int(adapt_counter) < int(adapt_counter + self.adapt_rate):
|
||||||
self.adapt()
|
self.adapt()
|
||||||
|
|
||||||
number_of_generations -= 1
|
number_of_generations -= 1
|
||||||
self.current_generation += 1
|
self.current_generation += 1
|
||||||
|
|
||||||
|
|
||||||
def update_population(self: GA) -> None:
|
def update_population(self: GA) -> None:
|
||||||
"""
|
"""
|
||||||
Updates the population to the new population
|
Updates the population to the new population
|
||||||
@ -115,6 +112,7 @@ class GA(Attributes):
|
|||||||
"""
|
"""
|
||||||
self.population.update()
|
self.population.update()
|
||||||
|
|
||||||
|
|
||||||
def reset_run(self: GA) -> None:
|
def reset_run(self: GA) -> None:
|
||||||
"""
|
"""
|
||||||
Resets a run by re-initializing the
|
Resets a run by re-initializing the
|
||||||
@ -124,6 +122,7 @@ class GA(Attributes):
|
|||||||
self.current_generation = 0
|
self.current_generation = 0
|
||||||
self.run += 1
|
self.run += 1
|
||||||
|
|
||||||
|
|
||||||
def adapt(self: GA) -> None:
|
def adapt(self: GA) -> None:
|
||||||
"""Adapts the ga to hopefully get better results."""
|
"""Adapts the ga to hopefully get better results."""
|
||||||
|
|
||||||
@ -134,6 +133,7 @@ class GA(Attributes):
|
|||||||
self.set_all_fitness()
|
self.set_all_fitness()
|
||||||
self.sort_by_best_fitness()
|
self.sort_by_best_fitness()
|
||||||
|
|
||||||
|
|
||||||
def adapt_probabilities(self: GA) -> None:
|
def adapt_probabilities(self: GA) -> None:
|
||||||
"""
|
"""
|
||||||
Modifies the parent ratio and mutation rates based on the adapt
|
Modifies the parent ratio and mutation rates based on the adapt
|
||||||
@ -153,7 +153,7 @@ class GA(Attributes):
|
|||||||
|
|
||||||
# Difference between best and i-th chromosomes
|
# Difference between best and i-th chromosomes
|
||||||
best_chromosome = self.population[0]
|
best_chromosome = self.population[0]
|
||||||
def tol(i): return self.dist(best_chromosome, self.population[i])
|
tol = lambda i: self.dist(best_chromosome, self.population[i])
|
||||||
|
|
||||||
# Too few converged: cross more and mutate less
|
# Too few converged: cross more and mutate less
|
||||||
if tol(amount_converged//2) > tol(amount_converged//4)*2:
|
if tol(amount_converged//2) > tol(amount_converged//4)*2:
|
||||||
@ -168,14 +168,13 @@ class GA(Attributes):
|
|||||||
self.max_gene_mutation_rate)
|
self.max_gene_mutation_rate)
|
||||||
|
|
||||||
# Weighted average of x and y
|
# Weighted average of x and y
|
||||||
def average(x, y): return weight * x + (1-weight) * y
|
average = lambda x, y: weight * x + (1-weight) * y
|
||||||
|
|
||||||
# Adjust rates towards the bounds
|
# Adjust rates towards the bounds
|
||||||
self.selection_probability = average(
|
self.selection_probability = average(bounds[0], self.selection_probability)
|
||||||
bounds[0], self.selection_probability)
|
self.chromosome_mutation_rate = average(bounds[1], self.chromosome_mutation_rate)
|
||||||
self.chromosome_mutation_rate = average(
|
self.gene_mutation_rate = average(bounds[2], self.gene_mutation_rate)
|
||||||
bounds[1], self.chromosome_mutation_rate)
|
|
||||||
self.gene_mutation_rate = average(bounds[2], self.gene_mutation_rate)
|
|
||||||
|
|
||||||
def adapt_population(self: GA) -> None:
|
def adapt_population(self: GA) -> None:
|
||||||
"""
|
"""
|
||||||
@ -202,7 +201,7 @@ class GA(Attributes):
|
|||||||
self.crossover_individual_impl(
|
self.crossover_individual_impl(
|
||||||
self.population[n],
|
self.population[n],
|
||||||
parent,
|
parent,
|
||||||
weight=-3/4,
|
weight = -3/4,
|
||||||
)
|
)
|
||||||
|
|
||||||
# If negative weights can't be used or division by 0, use positive weight
|
# If negative weights can't be used or division by 0, use positive weight
|
||||||
@ -210,7 +209,7 @@ class GA(Attributes):
|
|||||||
self.crossover_individual_impl(
|
self.crossover_individual_impl(
|
||||||
self.population[n],
|
self.population[n],
|
||||||
parent,
|
parent,
|
||||||
weight=+1/4,
|
weight = +1/4,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Stop if we've filled up an entire population
|
# Stop if we've filled up an entire population
|
||||||
@ -218,19 +217,20 @@ class GA(Attributes):
|
|||||||
break
|
break
|
||||||
|
|
||||||
# Replace worst chromosomes with new chromosomes, except for the previous best chromosome
|
# Replace worst chromosomes with new chromosomes, except for the previous best chromosome
|
||||||
min_len = min(len(self.population)-1,
|
min_len = min(len(self.population)-1, len(self.population.next_population))
|
||||||
len(self.population.next_population))
|
|
||||||
if min_len > 0:
|
if min_len > 0:
|
||||||
self.population[-min_len:] = self.population.next_population[:min_len]
|
self.population[-min_len:] = self.population.next_population[:min_len]
|
||||||
self.population.next_population = []
|
self.population.next_population = []
|
||||||
self.population.mating_pool = []
|
self.population.mating_pool = []
|
||||||
|
|
||||||
|
|
||||||
def initialize_population(self: GA) -> None:
|
def initialize_population(self: GA) -> None:
|
||||||
"""
|
"""
|
||||||
Sets self.population using the chromosome implementation and population size.
|
Sets self.population using the chromosome implementation and population size.
|
||||||
"""
|
"""
|
||||||
self.population = self.make_population(self.population_impl())
|
self.population = self.make_population(self.population_impl())
|
||||||
|
|
||||||
|
|
||||||
def set_all_fitness(self: GA) -> None:
|
def set_all_fitness(self: GA) -> None:
|
||||||
"""
|
"""
|
||||||
Sets the fitness of each chromosome in the population.
|
Sets the fitness of each chromosome in the population.
|
||||||
@ -251,14 +251,15 @@ class GA(Attributes):
|
|||||||
if chromosome.fitness is None or self.update_fitness:
|
if chromosome.fitness is None or self.update_fitness:
|
||||||
chromosome.fitness = self.fitness_function_impl(chromosome)
|
chromosome.fitness = self.fitness_function_impl(chromosome)
|
||||||
|
|
||||||
|
|
||||||
def sort_by_best_fitness(
|
def sort_by_best_fitness(
|
||||||
self: GA,
|
self: GA,
|
||||||
chromosome_list: Optional[
|
chromosome_list: Optional[
|
||||||
Union[MutableSequence[Chromosome],
|
Union[MutableSequence[Chromosome],
|
||||||
Iterable[Chromosome]]
|
Iterable[Chromosome]]
|
||||||
] = None,
|
] = None,
|
||||||
in_place: bool = True,
|
in_place: bool = True,
|
||||||
) -> MutableSequence[Chromosome]:
|
) -> MutableSequence[Chromosome]:
|
||||||
"""
|
"""
|
||||||
Sorts the chromosome list 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.
|
||||||
@ -313,6 +314,7 @@ class GA(Attributes):
|
|||||||
else:
|
else:
|
||||||
return sorted(chromosome_list, key=key, reverse=reverse)
|
return sorted(chromosome_list, key=key, reverse=reverse)
|
||||||
|
|
||||||
|
|
||||||
def get_chromosome_fitness(self: GA, index: int) -> float:
|
def get_chromosome_fitness(self: GA, index: int) -> float:
|
||||||
"""
|
"""
|
||||||
Computes the converted fitness of a chromosome at an index.
|
Computes the converted fitness of a chromosome at an index.
|
||||||
@ -336,6 +338,7 @@ class GA(Attributes):
|
|||||||
"""
|
"""
|
||||||
return self.convert_fitness(self.population[index].fitness)
|
return self.convert_fitness(self.population[index].fitness)
|
||||||
|
|
||||||
|
|
||||||
def convert_fitness(self: GA, fitness: float) -> float:
|
def convert_fitness(self: GA, fitness: float) -> float:
|
||||||
"""
|
"""
|
||||||
Calculates a modified version of the fitness for various
|
Calculates a modified version of the fitness for various
|
||||||
@ -372,19 +375,23 @@ class GA(Attributes):
|
|||||||
|
|
||||||
return max_fitness - fitness + min_fitness
|
return max_fitness - fitness + min_fitness
|
||||||
|
|
||||||
|
|
||||||
def print_generation(self: GA) -> None:
|
def print_generation(self: GA) -> None:
|
||||||
"""Prints the current generation."""
|
"""Prints the current generation."""
|
||||||
print(f"Current Generation \t: {self.current_generation}")
|
print(f"Current Generation \t: {self.current_generation}")
|
||||||
|
|
||||||
|
|
||||||
def print_population(self: GA) -> None:
|
def print_population(self: GA) -> None:
|
||||||
"""Prints the entire population."""
|
"""Prints the entire population."""
|
||||||
print(self.population)
|
print(self.population)
|
||||||
|
|
||||||
|
|
||||||
def print_best_chromosome(self: GA) -> None:
|
def print_best_chromosome(self: GA) -> None:
|
||||||
"""Prints the best chromosome and its fitness."""
|
"""Prints the best chromosome and its fitness."""
|
||||||
print(f"Best Chromosome \t: {self.population[0]}")
|
print(f"Best Chromosome \t: {self.population[0]}")
|
||||||
print(f"Best Fitness \t: {self.population[0].fitness}")
|
print(f"Best Fitness \t: {self.population[0].fitness}")
|
||||||
|
|
||||||
|
|
||||||
def print_worst_chromosome(self: GA) -> None:
|
def print_worst_chromosome(self: GA) -> None:
|
||||||
"""Prints the worst chromosome and its fitness."""
|
"""Prints the worst chromosome and its fitness."""
|
||||||
print(f"Worst Chromosome \t: {self.population[-1]}")
|
print(f"Worst Chromosome \t: {self.population[-1]}")
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
import EasyGA
|
||||||
|
from .EasyGA import GA
|
||||||
|
|||||||
@ -1,29 +1,124 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from inspect import getmro, signature
|
from inspect import signature
|
||||||
from typing import Any, Callable, Dict, Iterable, Iterator, Optional
|
from typing import Callable, Optional, Iterable, Any, Dict
|
||||||
from math import sqrt, ceil
|
from math import sqrt, ceil
|
||||||
from dataclasses import dataclass, field, _MISSING_TYPE
|
from dataclasses import dataclass, field
|
||||||
from types import MethodType
|
from types import MethodType
|
||||||
import random
|
import random
|
||||||
|
|
||||||
# import sqlite3
|
import sqlite3
|
||||||
# import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
from structure import Population
|
from EasyGA.structure import Population
|
||||||
from structure import Chromosome
|
from EasyGA.structure import Chromosome
|
||||||
from structure import Gene
|
from EasyGA.structure import Gene
|
||||||
|
|
||||||
from examples import Fitness
|
from EasyGA.examples import Fitness
|
||||||
from termination import Termination
|
from EasyGA.termination import Termination
|
||||||
from parent import Parent
|
from EasyGA.parent import Parent
|
||||||
from survivor import Survivor
|
from EasyGA.survivor import Survivor
|
||||||
from crossover import Crossover
|
from EasyGA.crossover import Crossover
|
||||||
from mutation import Mutation
|
from EasyGA.mutation import Mutation
|
||||||
# from database import SQLDatabase, MatplotlibGraph, SQLDatabase as Database, MatplotlibGraph as Graph
|
from EasyGA.database import sql_database, matplotlib_graph
|
||||||
|
|
||||||
#========================================#
|
|
||||||
# Default methods not defined elsewhere. #
|
@dataclass
|
||||||
#========================================#
|
class Attributes:
|
||||||
|
"""
|
||||||
|
Attributes class which stores all attributes in a dataclass.
|
||||||
|
Contains default attributes for each attribute.
|
||||||
|
"""
|
||||||
|
|
||||||
|
properties: Dict[str, Any] = field(default_factory=dict, init=False, repr=False, compare=False)
|
||||||
|
|
||||||
|
run: int = 0
|
||||||
|
|
||||||
|
chromosome_length: int = 10
|
||||||
|
population_size: int = 10
|
||||||
|
population: Optional[Population] = None
|
||||||
|
|
||||||
|
target_fitness_type: str = 'max'
|
||||||
|
update_fitness: bool = False
|
||||||
|
|
||||||
|
parent_ratio: float = 0.1
|
||||||
|
selection_probability: float = 0.5
|
||||||
|
tournament_size_ratio: float = 0.1
|
||||||
|
|
||||||
|
current_generation: int = 0
|
||||||
|
generation_goal: int = 100
|
||||||
|
fitness_goal: Optional[float] = None
|
||||||
|
tolerance_goal: Optional[float] = None
|
||||||
|
percent_converged: float = 0.5
|
||||||
|
|
||||||
|
chromosome_mutation_rate: float = 0.15
|
||||||
|
gene_mutation_rate: float = 0.05
|
||||||
|
|
||||||
|
adapt_rate: float = 0.05
|
||||||
|
adapt_probability_rate: float = 0.05
|
||||||
|
adapt_population_flag: bool = True
|
||||||
|
|
||||||
|
max_selection_probability: float = 0.75
|
||||||
|
min_selection_probability: float = 0.25
|
||||||
|
max_chromosome_mutation_rate: float = None
|
||||||
|
min_chromosome_mutation_rate: float = None
|
||||||
|
max_gene_mutation_rate: float = 0.15
|
||||||
|
min_gene_mutation_rate: float = 0.01
|
||||||
|
|
||||||
|
fitness_function_impl: Callable[[Attributes, Chromosome], float] = Fitness.is_it_5
|
||||||
|
make_population: Callable[[Iterable[Iterable[Any]]], Population] = Population
|
||||||
|
make_chromosome: Callable[[Iterable[Any]], Chromosome] = Chromosome
|
||||||
|
make_gene: Callable[[Any], Gene] = Gene
|
||||||
|
|
||||||
|
gene_impl: Callable[[Attributes], Any] = field(default_factory=lambda: rand_1_to_10)
|
||||||
|
chromosome_impl: Optional[[Attributes], Iterable[Any]] = field(default_factory=lambda: use_genes)
|
||||||
|
population_impl: Optional[[Attributes], Iterable[Iterable[Any]]] = field(default_factory=lambda: use_chromosomes)
|
||||||
|
|
||||||
|
weighted_random: Callable[[Attributes, float], float] = field(default_factory=lambda: simple_linear)
|
||||||
|
dist: Callable[[Attributes, Chromosome, Chromosome], float] = field(default_factory=lambda: dist_fitness)
|
||||||
|
|
||||||
|
parent_selection_impl: Callable[[Attributes], None] = Parent.Rank.tournament
|
||||||
|
crossover_individual_impl: Callable[[Attributes], None] = Crossover.Individual.single_point
|
||||||
|
crossover_population_impl: Callable[[Attributes], None] = Crossover.Population.sequential
|
||||||
|
survivor_selection_impl: Callable[[Attributes], None] = Survivor.fill_in_best
|
||||||
|
mutation_individual_impl: Callable[[Attributes], None] = Mutation.Individual.individual_genes
|
||||||
|
mutation_population_impl: Callable[[Attributes], None] = Mutation.Population.random_avoid_best
|
||||||
|
termination_impl: Callable[[Attributes], None] = Termination.fitness_generation_tolerance
|
||||||
|
|
||||||
|
database: Database = field(default_factory=sql_database.SQL_Database)
|
||||||
|
database_name: str = 'database.db'
|
||||||
|
sql_create_data_structure: str = """
|
||||||
|
CREATE TABLE IF NOT EXISTS data (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
config_id INTEGER DEFAULT NULL,
|
||||||
|
generation INTEGER NOT NULL,
|
||||||
|
fitness REAL,
|
||||||
|
chromosome TEXT
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
|
||||||
|
graph: Callable[[Database], Graph] = matplotlib_graph.Matplotlib_Graph
|
||||||
|
|
||||||
|
|
||||||
|
#============================#
|
||||||
|
# Built-in database methods: #
|
||||||
|
#============================#
|
||||||
|
|
||||||
|
|
||||||
|
def save_population(self: Attributes) -> None:
|
||||||
|
"""Saves the current population to the database."""
|
||||||
|
self.database.insert_current_population(self)
|
||||||
|
|
||||||
|
|
||||||
|
def save_chromosome(self: Attributes, chromosome: Chromosome) -> None:
|
||||||
|
"""
|
||||||
|
Saves a chromosome to the database.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
chromosome : Chromosome
|
||||||
|
The chromosome to be saved.
|
||||||
|
"""
|
||||||
|
self.database.insert_current_chromosome(self.current_generation, chromosome)
|
||||||
|
|
||||||
|
|
||||||
def rand_1_to_10(self: Attributes) -> int:
|
def rand_1_to_10(self: Attributes) -> int:
|
||||||
@ -38,7 +133,7 @@ def rand_1_to_10(self: Attributes) -> int:
|
|||||||
return random.randint(1, 10)
|
return random.randint(1, 10)
|
||||||
|
|
||||||
|
|
||||||
def use_genes(self: Attributes) -> Iterator[Any]:
|
def use_genes(self: Attributes) -> Iterable[Any]:
|
||||||
"""
|
"""
|
||||||
Default chromosome_impl, generates a chromosome using the gene_impl and chromosome length.
|
Default chromosome_impl, generates a chromosome using the gene_impl and chromosome length.
|
||||||
|
|
||||||
@ -51,14 +146,14 @@ def use_genes(self: Attributes) -> Iterator[Any]:
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
chromosome : Iterator[Any]
|
chromosome : Iterable[Any]
|
||||||
Generates the genes for a chromosome.
|
Generates the genes for a chromosome.
|
||||||
"""
|
"""
|
||||||
for _ in range(self.chromosome_length):
|
for _ in range(self.chromosome_length):
|
||||||
yield self.gene_impl()
|
yield self.gene_impl()
|
||||||
|
|
||||||
|
|
||||||
def use_chromosomes(self: Attributes) -> Iterator[Iterable[Any]]:
|
def use_chromosomes(self: Attributes) -> Iterable[Any]:
|
||||||
"""
|
"""
|
||||||
Default population_impl, generates a population using the chromosome_impl and population size.
|
Default population_impl, generates a population using the chromosome_impl and population size.
|
||||||
|
|
||||||
@ -71,7 +166,7 @@ def use_chromosomes(self: Attributes) -> Iterator[Iterable[Any]]:
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
population : Iterator[Iterable[Any]]
|
population : Iterable[Iterable[Any]]
|
||||||
Generates the chromosomes for a population.
|
Generates the chromosomes for a population.
|
||||||
"""
|
"""
|
||||||
for _ in range(self.population_size):
|
for _ in range(self.population_size):
|
||||||
@ -117,339 +212,231 @@ def simple_linear(self: Attributes, weight: float) -> float:
|
|||||||
return 1 - (1-rand) * weight / (1-weight)
|
return 1 - (1-rand) * weight / (1-weight)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
#==================================================#
|
||||||
class AttributesData:
|
# Properties for attributes behaving like methods. #
|
||||||
|
#==================================================#
|
||||||
|
|
||||||
|
|
||||||
|
def get_method(name: str) -> Callable[[Attributes], Callable[..., Any]]:
|
||||||
"""
|
"""
|
||||||
Attributes class which stores all attributes in a dataclass.
|
Creates a getter method for getting a method from the Attributes class.
|
||||||
This includes type-hints/annotations and default values, except for methods.
|
|
||||||
|
|
||||||
Additionally gains dataclass features, including an __init__ and __repr__ to avoid boilerplate code.
|
Parameters
|
||||||
|
----------
|
||||||
|
name : str
|
||||||
|
The name of the method from Attributes.
|
||||||
|
|
||||||
Developer Notes:
|
Returns
|
||||||
|
-------
|
||||||
See the Attributes class for default methods.
|
getter(ga)(...) -> Any
|
||||||
|
The getter property, taking in an object and returning the method.
|
||||||
Override this class to set default attributes. See help(Attributes) for more information.
|
|
||||||
|
|
||||||
If you must override the __post_init__, don't forget to use super().__post_init__().
|
|
||||||
"""
|
"""
|
||||||
|
def getter(self: Attributes) -> Callable[..., Any]:
|
||||||
run: int = 0
|
return self.properties[name]
|
||||||
|
return getter
|
||||||
chromosome_length: int = 10
|
|
||||||
population_size: int = 10
|
|
||||||
population: Optional[Population] = None
|
|
||||||
|
|
||||||
target_fitness_type: str = 'max'
|
|
||||||
update_fitness: bool = False
|
|
||||||
|
|
||||||
parent_ratio: float = 0.1
|
|
||||||
selection_probability: float = 0.5
|
|
||||||
tournament_size_ratio: float = 0.1
|
|
||||||
|
|
||||||
current_generation: int = 0
|
|
||||||
generation_goal: int = 100
|
|
||||||
fitness_goal: Optional[float] = None
|
|
||||||
tolerance_goal: Optional[float] = None
|
|
||||||
percent_converged: float = 0.5
|
|
||||||
|
|
||||||
chromosome_mutation_rate: float = 0.15
|
|
||||||
gene_mutation_rate: float = 0.05
|
|
||||||
|
|
||||||
adapt_rate: float = 0.05
|
|
||||||
adapt_probability_rate: float = 0.05
|
|
||||||
adapt_population_flag: bool = True
|
|
||||||
|
|
||||||
max_selection_probability: float = 0.75
|
|
||||||
min_selection_probability: float = 0.25
|
|
||||||
max_chromosome_mutation_rate: float = None
|
|
||||||
min_chromosome_mutation_rate: float = None
|
|
||||||
max_gene_mutation_rate: float = 0.15
|
|
||||||
min_gene_mutation_rate: float = 0.01
|
|
||||||
|
|
||||||
#=================================#
|
|
||||||
# Default methods are implemented #
|
|
||||||
# in the Attributes descriptors: #
|
|
||||||
#=================================#
|
|
||||||
|
|
||||||
fitness_function_impl: Callable[["Attributes", Chromosome], float] = None
|
|
||||||
make_gene: Callable[[Any], Gene] = None
|
|
||||||
make_chromosome: Callable[[Iterable[Any]], Chromosome] = None
|
|
||||||
make_population: Callable[[Iterable[Iterable[Any]]], Population] = None
|
|
||||||
|
|
||||||
gene_impl: Callable[[], Any] = None
|
|
||||||
chromosome_impl: Callable[[], Iterable[Any]] = None
|
|
||||||
population_impl: Callable[[], Iterable[Iterable[Any]]] = None
|
|
||||||
|
|
||||||
weighted_random: Callable[[float], float] = None
|
|
||||||
dist: Callable[["Attributes", Chromosome, Chromosome], None] = None
|
|
||||||
|
|
||||||
parent_selection_impl: Callable[["Attributes"], None] = None
|
|
||||||
crossover_individual_impl: Callable[["Attributes"], None] = None
|
|
||||||
crossover_population_impl: Callable[[
|
|
||||||
"Attributes", Chromosome, Chromosome], None] = None
|
|
||||||
survivor_selection_impl: Callable[["Attributes"], None] = None
|
|
||||||
mutation_individual_impl: Callable[["Attributes", Chromosome], None] = None
|
|
||||||
mutation_population_impl: Callable[["Attributes"], None] = None
|
|
||||||
termination_impl: Callable[["Attributes"], bool] = None
|
|
||||||
|
|
||||||
# database: Database = field(default_factory=SQLDatabase)
|
|
||||||
#database_name: str = "database.db"
|
|
||||||
#save_data: bool = True
|
|
||||||
# sql_create_data_structure: str = """
|
|
||||||
# CREATE TABLE IF NOT EXISTS data (
|
|
||||||
# id INTEGER PRIMARY KEY,
|
|
||||||
# config_id INTEGER DEFAULT NULL,
|
|
||||||
# generation INTEGER NOT NULL,
|
|
||||||
# fitness REAL,
|
|
||||||
# chromosome TEXT
|
|
||||||
# );
|
|
||||||
# """
|
|
||||||
|
|
||||||
# graph: Callable[[Database], Graph] = MatplotlibGraph
|
|
||||||
|
|
||||||
def __post_init__(self: AttributesData) -> None:
|
|
||||||
"""
|
|
||||||
Undo any instance attributes that are None when they should be methods from the class.
|
|
||||||
|
|
||||||
Attributes here refers to the __dataclass_fields__.
|
|
||||||
|
|
||||||
Methods here refers to AsMethod descriptors on any of the super classes of self's class.
|
|
||||||
"""
|
|
||||||
def is_method(cls: type, name: str) -> bool:
|
|
||||||
"""
|
|
||||||
The class has the attribute `name` as a method if:
|
|
||||||
- it has the attribute,
|
|
||||||
- and it's the AsMethod descriptor.
|
|
||||||
"""
|
|
||||||
return hasattr(cls, name) and isinstance(getattr(cls, name), AsMethod)
|
|
||||||
# Check each dataclass attribute.
|
|
||||||
for name in self.__dataclass_fields__:
|
|
||||||
# If the instance attribute is None
|
|
||||||
# and any of the super classes has that as a method,
|
|
||||||
# then delete the None instance attribute.
|
|
||||||
if (
|
|
||||||
getattr(self, name) is None
|
|
||||||
and any(is_method(cls, name) for cls in getmro(type(self)))
|
|
||||||
):
|
|
||||||
delattr(self, name)
|
|
||||||
|
|
||||||
|
|
||||||
class AsMethod:
|
def set_method(name: str) -> Callable[[Attributes, Optional[Callable[..., Any]]], None]:
|
||||||
"""
|
"""
|
||||||
A descriptor for converting function attributes into bound methods.
|
Creates a setter method for setting a method from the Attributes class.
|
||||||
|
|
||||||
To support both inheritance and dataclasses, if the method is None,
|
Parameters
|
||||||
then nothing is set.
|
----------
|
||||||
|
name : str
|
||||||
|
The name of the method from Attributes.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
setter(ga, method)
|
||||||
|
The setter property, taking in an object and returning nothing.
|
||||||
"""
|
"""
|
||||||
|
def setter(self: Attributes, method: Optional[Callable[..., Any]]) -> None:
|
||||||
def __init__(self: AsMethod, name: str, default: Callable) -> None:
|
|
||||||
if not callable(default):
|
|
||||||
raise TypeError(f"'default' must be a method i.e. callable.")
|
|
||||||
self.name = name
|
|
||||||
self.default = default
|
|
||||||
|
|
||||||
def __get__(self: AsMethod, obj: "Attributes", cls: type) -> Callable:
|
|
||||||
# Already has the attribute on the object.
|
|
||||||
if self.name in vars(obj):
|
|
||||||
return vars(obj)[self.name]
|
|
||||||
# Otherwise use the default as a method.
|
|
||||||
if next(iter(signature(self.default).parameters), None) in ("self", "ga"):
|
|
||||||
return MethodType(self.default, obj)
|
|
||||||
# Otherwise use the default as a function.
|
|
||||||
return self.default
|
|
||||||
|
|
||||||
def __set__(self: AsMethod, obj: "Attributes", method: Optional[Callable]) -> None:
|
|
||||||
if method is None:
|
if method is None:
|
||||||
return
|
pass
|
||||||
elif not callable(method):
|
elif not callable(method):
|
||||||
raise TypeError(f"'{self.name}' must be a method i.e. callable.")
|
raise TypeError(f"{name} must be a method i.e. callable.")
|
||||||
elif next(iter(signature(method).parameters), None) in ("self", "ga"):
|
elif next(iter(signature(method).parameters), None) in ("self", "ga"):
|
||||||
method = MethodType(method, obj)
|
method = MethodType(method, self)
|
||||||
vars(obj)[self.name] = method
|
self.properties[name] = method
|
||||||
|
return setter
|
||||||
def __delete__(self: AsMethod, obj: "Attributes") -> None:
|
|
||||||
del vars(obj)[self.name]
|
|
||||||
|
|
||||||
|
|
||||||
class Attributes(AttributesData):
|
for name in (
|
||||||
|
"fitness_function_impl",
|
||||||
|
"parent_selection_impl",
|
||||||
|
"crossover_individual_impl",
|
||||||
|
"crossover_population_impl",
|
||||||
|
"survivor_selection_impl",
|
||||||
|
"mutation_individual_impl",
|
||||||
|
"mutation_population_impl",
|
||||||
|
"termination_impl",
|
||||||
|
"dist",
|
||||||
|
"weighted_random",
|
||||||
|
"gene_impl",
|
||||||
|
"chromosome_impl",
|
||||||
|
"population_impl",
|
||||||
|
):
|
||||||
|
setattr(Attributes, name, property(get_method(name), set_method(name)))
|
||||||
|
|
||||||
|
|
||||||
|
#============================#
|
||||||
|
# Static checking properties #
|
||||||
|
#============================#
|
||||||
|
|
||||||
|
|
||||||
|
static_checks = {
|
||||||
|
"run": {
|
||||||
|
"check": lambda value: isinstance(value, int) and value >= 0,
|
||||||
|
"error": "ga.run counter must be an integer greater than or equal to 0.",
|
||||||
|
},
|
||||||
|
"current_generation": {
|
||||||
|
"check": lambda value: isinstance(value, int) and value >= 0,
|
||||||
|
"error": "ga.current_generation must be an integer greater than or equal to 0",
|
||||||
|
},
|
||||||
|
"chromosome_length": {
|
||||||
|
"check": lambda value: isinstance(value, int) and value > 0,
|
||||||
|
"error": "ga.chromosome_length must be an integer greater than and not equal to 0.",
|
||||||
|
},
|
||||||
|
"population_size": {
|
||||||
|
"check": lambda value: isinstance(value, int) and value > 0,
|
||||||
|
"error": "ga.population_size must be an integer greater than and not equal to 0.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_attr(name: str) -> Callable[[Attributes], Any]:
|
||||||
"""
|
"""
|
||||||
The Attributes class inherits default attributes from AttributesData
|
Creates a getter method for getting an attribute from the Attributes class.
|
||||||
and implements methods, descriptors, and properties.
|
|
||||||
|
|
||||||
The built-in methods provide interfacing to the database.
|
Parameters
|
||||||
>>> ga.save_population() # references ga.database.insert_current_population(ga)
|
----------
|
||||||
The descriptors are used to convert function attributes into methods.
|
name : str
|
||||||
>>> ga.gene_impl = lambda self: ... # self is turned into an implicit argument.
|
The name of the attribute.
|
||||||
The properties are used to validate certain inputs.
|
|
||||||
|
|
||||||
Developer Notes:
|
Returns
|
||||||
|
-------
|
||||||
If inherited, the descriptors may be overridden with a method implementation,
|
getter(ga) -> Any
|
||||||
but this removes the descriptor.
|
A getter method which returns an attribute.
|
||||||
|
|
||||||
To override default attributes, we recommend creating a dataclass inheriting AttributesData.
|
|
||||||
Then inherit the Attributes and AttributesDataSubclass, in that order.
|
|
||||||
>>> from dataclasses import dataclass
|
|
||||||
>>> @dataclass
|
|
||||||
>>> class MyDefaults(AttributesData):
|
|
||||||
... run: int = 10
|
|
||||||
...
|
|
||||||
>>> class MyAttributes(Attributes, MyDefaults):
|
|
||||||
... pass
|
|
||||||
...
|
|
||||||
"""
|
"""
|
||||||
|
def getter(self: Attributes) -> Any:
|
||||||
|
return self.properties[name]
|
||||||
|
return getter
|
||||||
|
|
||||||
#============================#
|
|
||||||
# Built-in database methods: #
|
|
||||||
#============================#
|
|
||||||
|
|
||||||
def save_population(self: Attributes) -> None:
|
def set_attr(name: str, check: Callable[[Any], bool], error: str) -> Callable[[Attributes, Any], None]:
|
||||||
"""Saves the current population to the database."""
|
"""
|
||||||
self.database.insert_current_population(self)
|
Creates a setter method for setting an attribute from the Attributes class.
|
||||||
|
|
||||||
def save_chromosome(self: Attributes, chromosome: Chromosome) -> None:
|
Parameters
|
||||||
"""
|
----------
|
||||||
Saves a chromosome to the database.
|
name : str
|
||||||
|
The name of the attribute.
|
||||||
|
check(Any) -> bool
|
||||||
|
The condition needed to be passed for the attribute to be added.
|
||||||
|
error: str
|
||||||
|
An error message if check(...) turns False.
|
||||||
|
|
||||||
Parameters
|
Returns
|
||||||
----------
|
-------
|
||||||
chromosome : Chromosome
|
setter(ga, Any) -> None
|
||||||
The chromosome to be saved.
|
Raises ValueError(error)
|
||||||
"""
|
A setter method which saves to an attribute.
|
||||||
self.database.insert_current_chromosome(
|
"""
|
||||||
self.current_generation, chromosome)
|
def setter(self: Attributes, value: Any) -> Any:
|
||||||
|
if check(value):
|
||||||
#===========================#
|
self.properties[name] = value
|
||||||
# Descriptors which convert #
|
|
||||||
# functions into methods: #
|
|
||||||
#===========================#
|
|
||||||
|
|
||||||
fitness_function_impl = AsMethod("fitness_function_impl", Fitness.is_it_5)
|
|
||||||
make_gene = AsMethod("make_gene", Gene)
|
|
||||||
make_chromosome = AsMethod("make_chromosome", Chromosome)
|
|
||||||
make_population = AsMethod("make_population", Population)
|
|
||||||
gene_impl = AsMethod("gene_impl", rand_1_to_10)
|
|
||||||
chromosome_impl = AsMethod("chromosome_impl", use_genes)
|
|
||||||
population_impl = AsMethod("population_impl", use_chromosomes)
|
|
||||||
dist = AsMethod("dist", dist_fitness)
|
|
||||||
weighted_random = AsMethod("weighted_random", simple_linear)
|
|
||||||
parent_selection_impl = AsMethod(
|
|
||||||
"parent_selection_impl", Parent.Rank.tournament)
|
|
||||||
crossover_individual_impl = AsMethod(
|
|
||||||
"crossover_individual_impl", Crossover.Individual.single_point)
|
|
||||||
crossover_population_impl = AsMethod(
|
|
||||||
"crossover_population_impl", Crossover.Population.sequential)
|
|
||||||
survivor_selection_impl = AsMethod(
|
|
||||||
"survivor_selection_impl", Survivor.fill_in_best)
|
|
||||||
mutation_individual_impl = AsMethod(
|
|
||||||
"mutation_individual_impl", Mutation.Individual.individual_genes)
|
|
||||||
mutation_population_impl = AsMethod(
|
|
||||||
"mutation_population_impl", Mutation.Population.random_avoid_best)
|
|
||||||
termination_impl = AsMethod(
|
|
||||||
"termination_impl", Termination.fitness_generation_tolerance)
|
|
||||||
|
|
||||||
#=============#
|
|
||||||
# Properties: #
|
|
||||||
#=============#
|
|
||||||
|
|
||||||
@property
|
|
||||||
def run(self: AttributesProperties) -> int:
|
|
||||||
return vars(self)["run"]
|
|
||||||
|
|
||||||
@run.setter
|
|
||||||
def run(self: AttributesProperties, value: int) -> None:
|
|
||||||
if not isinstance(value, int) or value < 0:
|
|
||||||
raise ValueError(
|
|
||||||
"ga.run counter must be an integer greater than or equal to 0.")
|
|
||||||
vars(self)["run"] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_generation(self: AttributesProperties) -> int:
|
|
||||||
return vars(self)["current_generation"]
|
|
||||||
|
|
||||||
@current_generation.setter
|
|
||||||
def current_generation(self: AttributesProperties, value: int) -> None:
|
|
||||||
if not isinstance(value, int) or value < 0:
|
|
||||||
raise ValueError(
|
|
||||||
"ga.current_generation must be an integer greater than or equal to 0")
|
|
||||||
vars(self)["current_generation"] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def chromosome_length(self: AttributesProperties) -> int:
|
|
||||||
return vars(self)["chromosome_length"]
|
|
||||||
|
|
||||||
@chromosome_length.setter
|
|
||||||
def chromosome_length(self: AttributesProperties, value: int) -> None:
|
|
||||||
if not isinstance(value, int) or value <= 0:
|
|
||||||
raise ValueError(
|
|
||||||
"ga.chromosome_length must be an integer greater than and not equal to 0.")
|
|
||||||
vars(self)["chromosome_length"] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def population_size(self: AttributesProperties) -> int:
|
|
||||||
return vars(self)["population_size"]
|
|
||||||
|
|
||||||
@population_size.setter
|
|
||||||
def population_size(self: AttributesProperties, value: int) -> None:
|
|
||||||
if not isinstance(value, int) or value <= 0:
|
|
||||||
raise ValueError(
|
|
||||||
"ga.population_size must be an integer greater than and not equal to 0.")
|
|
||||||
vars(self)["population_size"] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_chromosome_mutation_rate(self: AttributesProperties) -> float:
|
|
||||||
# Default value.
|
|
||||||
if vars(self).get("max_chromosome_mutation_rate", None) is None:
|
|
||||||
return min(self.chromosome_mutation_rate * 2, (self.chromosome_mutation_rate + 1) / 2)
|
|
||||||
# Set value.
|
|
||||||
return vars(self)["max_chromosome_mutation_rate"]
|
|
||||||
|
|
||||||
@max_chromosome_mutation_rate.setter
|
|
||||||
def max_chromosome_mutation_rate(self: AttributesProperties, value: Optional[float]) -> None:
|
|
||||||
# Use default or a valid float.
|
|
||||||
if value is None or (isinstance(value, (float, int)) and 0 <= value <= 1):
|
|
||||||
vars(self)["max_chromosome_mutation_rate"] = value
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(error)
|
||||||
"Max chromosome mutation rate must be between 0 and 1")
|
return setter
|
||||||
|
|
||||||
@property
|
|
||||||
def min_chromosome_mutation_rate(self: AttributesProperties) -> float:
|
|
||||||
# Default value.
|
|
||||||
if vars(self).get("min_chromosome_mutation_rate", None) is None:
|
|
||||||
return max(self.chromosome_mutation_rate / 2, self.chromosome_mutation_rate * 2 - 1)
|
|
||||||
# Set value.
|
|
||||||
return vars(self)["min_chromosome_mutation_rate"]
|
|
||||||
|
|
||||||
@min_chromosome_mutation_rate.setter
|
for name in static_checks:
|
||||||
def min_chromosome_mutation_rate(self: AttributesProperties, value: Optional[float]) -> None:
|
setattr(
|
||||||
# Use default or a valid float.
|
Attributes,
|
||||||
if value is None or (isinstance(value, (float, int)) and 0 <= value <= 1):
|
name,
|
||||||
vars(self)["min_chromosome_mutation_rate"] = value
|
property(
|
||||||
else:
|
get_attr(name),
|
||||||
raise ValueError(
|
set_attr(name, static_checks[name]["check"], static_checks[name]["error"]),
|
||||||
"Min chromosome mutation rate must be between 0 and 1")
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# @property
|
|
||||||
# def database_name(self: AttributesProperties) -> str:
|
|
||||||
# return vars(self)["database_name"]
|
|
||||||
|
|
||||||
# @database_name.setter
|
#==================#
|
||||||
# def database_name(self: AttributesProperties, name: str) -> None:
|
# Other properties #
|
||||||
# # Update the database's name.
|
#==================#
|
||||||
# self.database._database_name = name
|
|
||||||
# # Set the attribute for itself.
|
|
||||||
# vars(self)["database_name"] = name
|
|
||||||
|
|
||||||
# @property
|
|
||||||
# def graph(self: AttributesProperties) -> Graph:
|
|
||||||
# return vars(self)["graph"]
|
|
||||||
|
|
||||||
# @graph.setter
|
def get_max_chromosome_mutation_rate(self: Attributes) -> float:
|
||||||
# def graph(self: AttributesProperties, graph: Callable[[Database], Graph]) -> None:
|
return self._max_chromosome_mutation_rate
|
||||||
# vars(self)["graph"] = graph(self.database)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def active(self: AttributesProperties) -> Callable[[], bool]:
|
def set_max_chromosome_mutation_rate(self: Attributes, value: Optional[float]) -> None:
|
||||||
return self.termination_impl
|
|
||||||
|
# Default value
|
||||||
|
if value is None:
|
||||||
|
self._max_chromosome_mutation_rate = min(
|
||||||
|
self.chromosome_mutation_rate * 2,
|
||||||
|
(self.chromosome_mutation_rate + 1) / 2,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Otherwise check value
|
||||||
|
elif isinstance(value, (float, int)) and 0 <= value <= 1:
|
||||||
|
self._max_chromosome_mutation_rate = value
|
||||||
|
|
||||||
|
# Raise error
|
||||||
|
else:
|
||||||
|
raise ValueError("Max chromosome mutation rate must be between 0 and 1")
|
||||||
|
|
||||||
|
|
||||||
|
def get_min_chromosome_mutation_rate(self: Attributes) -> float:
|
||||||
|
return self._min_chromosome_mutation_rate
|
||||||
|
|
||||||
|
|
||||||
|
def set_min_chromosome_mutation_rate(self: Attributes, value: Optional[float]) -> None:
|
||||||
|
|
||||||
|
# Default value
|
||||||
|
if value is None:
|
||||||
|
self._min_chromosome_mutation_rate = max(
|
||||||
|
self.chromosome_mutation_rate / 2,
|
||||||
|
self.chromosome_mutation_rate * 2 - 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Otherwise check value
|
||||||
|
elif isinstance(value, (float, int)) and 0 <= value <= 1:
|
||||||
|
self._min_chromosome_mutation_rate = value
|
||||||
|
|
||||||
|
# Raise error
|
||||||
|
else:
|
||||||
|
raise ValueError("Min chromosome mutation rate must be between 0 and 1")
|
||||||
|
|
||||||
|
|
||||||
|
def get_database_name(self: Attributes) -> str:
|
||||||
|
return self._database_name
|
||||||
|
|
||||||
|
|
||||||
|
def set_database_name(self: Attributes, name: str) -> None:
|
||||||
|
|
||||||
|
# Update the database class' name
|
||||||
|
self.database._database_name = name
|
||||||
|
|
||||||
|
# Set the attribute for itself
|
||||||
|
self._database_name = name
|
||||||
|
|
||||||
|
|
||||||
|
def get_graph(self: Attributes) -> Graph:
|
||||||
|
return self._graph
|
||||||
|
|
||||||
|
|
||||||
|
def set_graph(self: Attributes, graph: Callable[[Database], Graph]) -> None:
|
||||||
|
self._graph = graph(self.database)
|
||||||
|
|
||||||
|
|
||||||
|
def get_active(self: Attributes) -> Callable[[Attributes], None]:
|
||||||
|
return self.termination_impl
|
||||||
|
|
||||||
|
|
||||||
|
Attributes.max_chromosome_mutation_rate = property(get_max_chromosome_mutation_rate, set_max_chromosome_mutation_rate)
|
||||||
|
Attributes.min_chromosome_mutation_rate = property(get_min_chromosome_mutation_rate, set_min_chromosome_mutation_rate)
|
||||||
|
Attributes.database_name = property(get_database_name, set_database_name)
|
||||||
|
Attributes.graph = property(get_graph, set_graph)
|
||||||
|
Attributes.active = property(get_active)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
# Import all crossover decorators
|
# Import all crossover decorators
|
||||||
from decorators import _check_weight, _gene_by_gene
|
from EasyGA.decorators import _check_weight, _gene_by_gene
|
||||||
|
|
||||||
# Round to an integer near x with higher probability
|
# Round to an integer near x with higher probability
|
||||||
# the closer it is to that integer.
|
# the closer it is to that integer.
|
||||||
@ -160,4 +160,3 @@ class Individual:
|
|||||||
input_index += 1
|
input_index += 1
|
||||||
|
|
||||||
ga.population.add_child(gene_list_1)
|
ga.population.add_child(gene_list_1)
|
||||||
|
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
from .sql_database import SQLDatabase
|
|
||||||
from .matplotlib_graph import MatplotlibGraph
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import matplotlib.pyplot as plt
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
class MatplotlibGraph:
|
class Matplotlib_Graph:
|
||||||
"""Prebuilt graphing functions to make visual
|
"""Prebuilt graphing functions to make visual
|
||||||
represention of fitness data."""
|
represention of fitness data."""
|
||||||
|
|
||||||
|
|||||||
@ -1,24 +1,21 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
from sqlite3 import Error
|
|
||||||
|
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
|
|
||||||
|
class SQL_Database:
|
||||||
class SQLDatabase:
|
|
||||||
"""Main database class that controls all the functionality for input /
|
"""Main database class that controls all the functionality for input /
|
||||||
out of the database using SQLite3."""
|
out of the database using SQLite3."""
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.conn = None
|
self.conn = None
|
||||||
self.config_id = None
|
self.config_id = None
|
||||||
self._database_name = 'database.db'
|
self._database_name = 'database.db'
|
||||||
self.config_structure = """
|
self.config_structure = f"""
|
||||||
CREATE TABLE IF NOT EXISTS config (
|
CREATE TABLE IF NOT EXISTS config (
|
||||||
config_id INTEGER,
|
config_id INTEGER,
|
||||||
attribute_name TEXT,
|
attribute_name TEXT,
|
||||||
attribute_value TEXT
|
attribute_value TEXT)"""
|
||||||
);
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
#=====================================#
|
#=====================================#
|
||||||
@ -26,56 +23,102 @@ class SQLDatabase:
|
|||||||
#=====================================#
|
#=====================================#
|
||||||
|
|
||||||
def create_all_tables(self, ga):
|
def create_all_tables(self, ga):
|
||||||
"""Create the database if it doenst exist and then the data and config tables."""
|
"""Create the database if it doenst exist and then the data and config
|
||||||
# Create the database connection.
|
tables."""
|
||||||
|
|
||||||
|
# Create the database connection
|
||||||
self.create_connection()
|
self.create_connection()
|
||||||
# No connection.
|
|
||||||
if self.conn is None:
|
if self.conn is not None:
|
||||||
|
# Create data table
|
||||||
|
self.create_table(ga.sql_create_data_structure)
|
||||||
|
# Creare config table
|
||||||
|
self.create_table(self.config_structure)
|
||||||
|
# Set the config id
|
||||||
|
self.config_id = self.get_current_config()
|
||||||
|
|
||||||
|
else:
|
||||||
raise Exception("Error! Cannot create the database connection.")
|
raise Exception("Error! Cannot create the database connection.")
|
||||||
# Create data table.
|
|
||||||
self.create_table(ga.sql_create_data_structure)
|
|
||||||
# Creare config table.
|
|
||||||
self.create_table(self.config_structure)
|
|
||||||
# Set the config id.
|
|
||||||
self.config_id = self.get_current_config()
|
|
||||||
|
|
||||||
|
|
||||||
def insert_config(self, ga):
|
|
||||||
"""
|
|
||||||
Insert the configuration attributes into the config.
|
|
||||||
|
|
||||||
Notes:
|
def insert_config(self,ga):
|
||||||
|
"""Insert the configuration attributes into the config."""
|
||||||
"Attributes" here refers to ga.__dataclass_fields__.keys(),
|
|
||||||
which allows the attributes to be customized.
|
|
||||||
|
|
||||||
Only attributes that are bool, float, int, or str will be used.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Get the current config and add one for the new config key
|
# Get the current config and add one for the new config key
|
||||||
self.config_id = self.get_current_config()
|
self.config_id = self.get_current_config()
|
||||||
|
|
||||||
# Setting the config_id index if there is no file
|
# Setting the config_id index if there is no file
|
||||||
if self.config_id is None:
|
if self.config_id == None:
|
||||||
self.config_id = 0
|
self.config_id = 0
|
||||||
else:
|
else:
|
||||||
self.config_id = self.config_id + 1
|
self.config_id = self.config_id + 1
|
||||||
|
|
||||||
# Getting all attribute fields from the attributes class
|
# Getting all the attributes from the attributes class
|
||||||
db_config = [
|
db_config_dict = (
|
||||||
(self.config_id, attr_name, attr_value)
|
(attr_name, getattr(ga, attr_name))
|
||||||
for attr_name
|
for attr_name
|
||||||
in ga.__dataclass_fields__
|
in ga.__annotations__
|
||||||
if isinstance((attr_value := getattr(ga, attr_name)), (bool, float, int, str))
|
if attr_name != "population"
|
||||||
]
|
)
|
||||||
|
|
||||||
|
# Types supported in the database
|
||||||
|
sql_type_list = [int, float, str]
|
||||||
|
|
||||||
|
# Loop through all attributes
|
||||||
|
for name, value in db_config_dict:
|
||||||
|
|
||||||
|
# not a function
|
||||||
|
if not callable(value):
|
||||||
|
|
||||||
|
# Convert to the right type
|
||||||
|
value = str(value)
|
||||||
|
|
||||||
|
if "'" not in value and '"' not in value:
|
||||||
|
|
||||||
|
# Insert into database
|
||||||
|
self.conn.execute(f"""
|
||||||
|
INSERT INTO config(config_id, attribute_name, attribute_value)
|
||||||
|
VALUES ('{self.config_id}', '{name}','{value}');""")
|
||||||
|
|
||||||
|
|
||||||
query = """
|
|
||||||
INSERT INTO config(config_id, attribute_name, attribute_value)
|
|
||||||
VALUES (?, ?, ?);
|
|
||||||
"""
|
|
||||||
self.conn.executemany(query, db_config)
|
|
||||||
self.config_id = self.get_current_config()
|
self.config_id = self.get_current_config()
|
||||||
|
|
||||||
|
|
||||||
|
#=====================================#
|
||||||
|
# Decorators: #
|
||||||
|
#=====================================#
|
||||||
|
|
||||||
|
def default_config_id(method):
|
||||||
|
"""Decorator used to set the default config_id inside other functions."""
|
||||||
|
|
||||||
|
def new_method(self, config_id = None):
|
||||||
|
|
||||||
|
input_id = self.config_id if config_id is None else config_id
|
||||||
|
|
||||||
|
return method(self, input_id)
|
||||||
|
|
||||||
|
return new_method
|
||||||
|
|
||||||
|
|
||||||
|
def format_query_data(method):
|
||||||
|
"""Decorator used to format query data"""
|
||||||
|
|
||||||
|
def new_method(self, config_id):
|
||||||
|
query = method(self, config_id)
|
||||||
|
|
||||||
|
# Unpack elements if they are lists with only 1 element
|
||||||
|
if type(query[0]) in (list, tuple) and len(query[0]) == 1:
|
||||||
|
query = [i[0] for i in query]
|
||||||
|
|
||||||
|
# Unpack list if it is a list with only 1 element
|
||||||
|
if type(query) in (list, tuple) and len(query) == 1:
|
||||||
|
query = query[0]
|
||||||
|
|
||||||
|
return query
|
||||||
|
|
||||||
|
return new_method
|
||||||
|
|
||||||
#=====================================#
|
#=====================================#
|
||||||
# Request information Queries: #
|
# Request information Queries: #
|
||||||
#=====================================#
|
#=====================================#
|
||||||
@ -88,91 +131,79 @@ class SQLDatabase:
|
|||||||
def past_runs(self):
|
def past_runs(self):
|
||||||
"""Show a summerization of the past runs that the user has done."""
|
"""Show a summerization of the past runs that the user has done."""
|
||||||
|
|
||||||
query_data = self.query_all("""
|
query_data = self.query_all(f"""
|
||||||
SELECT config_id, attribute_name, attribute_value
|
SELECT config_id,attribute_name,attribute_value
|
||||||
FROM config;
|
FROM config;""")
|
||||||
""")
|
|
||||||
|
|
||||||
table = tabulate(
|
print(
|
||||||
query_data,
|
tabulate(
|
||||||
headers = [
|
query_data,
|
||||||
'config_id',
|
headers = [
|
||||||
'attribute_name',
|
'config_id',
|
||||||
'attribute_value',
|
'attribute_name',
|
||||||
]
|
'attribute_value'
|
||||||
|
]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
print(table)
|
|
||||||
return table
|
|
||||||
|
|
||||||
|
|
||||||
|
@default_config_id
|
||||||
def get_generation_total_fitness(self, config_id):
|
def get_generation_total_fitness(self, config_id):
|
||||||
"""Get each generations total fitness sum from the database """
|
"""Get each generations total fitness sum from the database """
|
||||||
|
|
||||||
config_id = self.config_id if config_id is None else config_id
|
|
||||||
|
|
||||||
return self.query_all(f"""
|
return self.query_all(f"""
|
||||||
SELECT SUM(fitness)
|
SELECT SUM(fitness)
|
||||||
FROM data
|
FROM data
|
||||||
WHERE config_id={config_id}
|
WHERE config_id={config_id}
|
||||||
GROUP BY generation;
|
GROUP BY generation;""")
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
|
@default_config_id
|
||||||
def get_total_generations(self, config_id):
|
def get_total_generations(self, config_id):
|
||||||
"""Get the total generations from the database"""
|
"""Get the total generations from the database"""
|
||||||
|
|
||||||
config_id = self.config_id if config_id is None else config_id
|
|
||||||
|
|
||||||
return self.query_one_item(f"""
|
return self.query_one_item(f"""
|
||||||
SELECT COUNT(DISTINCT generation)
|
SELECT COUNT(DISTINCT generation)
|
||||||
FROM data
|
FROM data
|
||||||
WHERE config_id={config_id};
|
WHERE config_id={config_id};""")
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
|
@default_config_id
|
||||||
def get_highest_chromosome(self, config_id):
|
def get_highest_chromosome(self, config_id):
|
||||||
"""Get the highest fitness of each generation"""
|
"""Get the highest fitness of each generation"""
|
||||||
|
|
||||||
config_id = self.config_id if config_id is None else config_id
|
|
||||||
|
|
||||||
return self.query_all(f"""
|
return self.query_all(f"""
|
||||||
SELECT max(fitness)
|
SELECT max(fitness)
|
||||||
FROM data
|
FROM data
|
||||||
WHERE config_id={config_id}
|
WHERE config_id={config_id}
|
||||||
GROUP by generation;
|
GROUP by generation;""")
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
|
@default_config_id
|
||||||
def get_lowest_chromosome(self, config_id):
|
def get_lowest_chromosome(self, config_id):
|
||||||
"""Get the lowest fitness of each generation"""
|
"""Get the lowest fitness of each generation"""
|
||||||
|
|
||||||
config_id = self.config_id if config_id is None else config_id
|
|
||||||
|
|
||||||
return self.query_all(f"""
|
return self.query_all(f"""
|
||||||
SELECT min(fitness)
|
SELECT min(fitness)
|
||||||
FROM data
|
FROM data
|
||||||
WHERE config_id={config_id}
|
WHERE config_id={config_id}
|
||||||
GROUP by generation;
|
GROUP by generation;""")
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_config_id(self):
|
def get_all_config_id(self):
|
||||||
"""Get an array of all the DISTINCT config_id in the database"""
|
"""Get an array of all the DISTINCT config_id in the database"""
|
||||||
|
|
||||||
return self.query_all(f"""
|
return self.query_all(f"""
|
||||||
SELECT DISTINCT config_id
|
SELECT DISTINCT config_id
|
||||||
FROM config;
|
FROM config;""")
|
||||||
""")
|
|
||||||
|
|
||||||
def get_each_generation_number(self, config_id):
|
def get_each_generation_number(self,config_id):
|
||||||
"""Get an array of all the generation numbers"""
|
"""Get an array of all the generation numbers"""
|
||||||
|
|
||||||
return self.query_all(f"""
|
return self.query_all(f"""
|
||||||
SELECT DISTINCT generation
|
SELECT DISTINCT generation
|
||||||
FROM data
|
FROM data
|
||||||
WHERE config_id={config_id};
|
WHERE config_id={config_id};""")
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -189,14 +220,12 @@ class SQLDatabase:
|
|||||||
self.config_id,
|
self.config_id,
|
||||||
generation,
|
generation,
|
||||||
chromosome.fitness,
|
chromosome.fitness,
|
||||||
repr(chromosome),
|
repr(chromosome)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create sql query structure
|
# Create sql query structure
|
||||||
sql = """
|
sql = """INSERT INTO data(config_id, generation, fitness, chromosome)
|
||||||
INSERT INTO data(config_id, generation, fitness, chromosome)
|
VALUES(?,?,?,?)"""
|
||||||
VALUES(?, ?, ?, ?)
|
|
||||||
"""
|
|
||||||
|
|
||||||
cur = self.conn.cursor()
|
cur = self.conn.cursor()
|
||||||
cur.execute(sql, db_chromosome)
|
cur.execute(sql, db_chromosome)
|
||||||
@ -220,10 +249,8 @@ class SQLDatabase:
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Create sql query structure
|
# Create sql query structure
|
||||||
sql = """
|
sql = """INSERT INTO data(config_id, generation, fitness, chromosome)
|
||||||
INSERT INTO data(config_id, generation, fitness, chromosome)
|
VALUES(?,?,?,?)"""
|
||||||
VALUES(?,?,?,?)
|
|
||||||
"""
|
|
||||||
|
|
||||||
cur = self.conn.cursor()
|
cur = self.conn.cursor()
|
||||||
cur.executemany(sql, db_chromosome_list)
|
cur.executemany(sql, db_chromosome_list)
|
||||||
@ -236,7 +263,8 @@ class SQLDatabase:
|
|||||||
#=====================================#
|
#=====================================#
|
||||||
|
|
||||||
def create_connection(self):
|
def create_connection(self):
|
||||||
"""Create a database connection to the SQLite database specified by db_file."""
|
"""Create a database connection to the SQLite database
|
||||||
|
specified by db_file."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.conn = sqlite3.connect(self.database_name)
|
self.conn = sqlite3.connect(self.database_name)
|
||||||
@ -244,7 +272,6 @@ class SQLDatabase:
|
|||||||
self.conn = None
|
self.conn = None
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
|
|
||||||
def create_table(self, create_table_sql):
|
def create_table(self, create_table_sql):
|
||||||
"""Create a table from the create_table_sql statement."""
|
"""Create a table from the create_table_sql statement."""
|
||||||
|
|
||||||
@ -255,31 +282,22 @@ class SQLDatabase:
|
|||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
|
|
||||||
def format_query_data(self, data):
|
@format_query_data
|
||||||
"""Used to format query data."""
|
|
||||||
# Unpack elements if they are lists with only 1 element
|
|
||||||
if isinstance(data[0], (list, tuple)) and len(data[0]) == 1:
|
|
||||||
data = [i[0] for i in data]
|
|
||||||
# Unpack list if it is a list with only 1 element
|
|
||||||
if isinstance(data, (list, tuple)) and len(data) == 1:
|
|
||||||
data = data[0]
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def query_all(self, query):
|
def query_all(self, query):
|
||||||
"""Query for muliple rows of data"""
|
"""Query for muliple rows of data"""
|
||||||
|
|
||||||
cur = self.conn.cursor()
|
cur = self.conn.cursor()
|
||||||
cur.execute(query)
|
cur.execute(query)
|
||||||
return self.format_query_data(cur.fetchall())
|
return cur.fetchall()
|
||||||
|
|
||||||
|
|
||||||
|
@format_query_data
|
||||||
def query_one_item(self, query):
|
def query_one_item(self, query):
|
||||||
"""Query for single data point"""
|
"""Query for single data point"""
|
||||||
|
|
||||||
cur = self.conn.cursor()
|
cur = self.conn.cursor()
|
||||||
cur.execute(query)
|
cur.execute(query)
|
||||||
return self.format_query_data(cur.fetchone())
|
return cur.fetchone()
|
||||||
|
|
||||||
|
|
||||||
def remove_database(self):
|
def remove_database(self):
|
||||||
@ -287,6 +305,16 @@ class SQLDatabase:
|
|||||||
os.remove(self._database_name)
|
os.remove(self._database_name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_var_names(self, ga):
|
||||||
|
"""Returns a list of the names of attributes of the ga."""
|
||||||
|
|
||||||
|
# Loop through all attributes
|
||||||
|
for var in ga.__dict__.keys():
|
||||||
|
|
||||||
|
# Remove leading underscore
|
||||||
|
yield (var[1:] if (var[0] == '_') else var)
|
||||||
|
|
||||||
|
|
||||||
#=====================================#
|
#=====================================#
|
||||||
# Setters and Getters: #
|
# Setters and Getters: #
|
||||||
#=====================================#
|
#=====================================#
|
||||||
@ -299,7 +327,7 @@ class SQLDatabase:
|
|||||||
|
|
||||||
@database_name.setter
|
@database_name.setter
|
||||||
def database_name(self, value_input):
|
def database_name(self, value_input):
|
||||||
raise AttributeError("Invalid usage, please use ga.database_name instead.")
|
raise Exception("Invalid usage, please use ga.database_name instead.")
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -318,7 +346,7 @@ class SQLDatabase:
|
|||||||
|
|
||||||
# If the connection doesnt exist then print error
|
# If the connection doesnt exist then print error
|
||||||
except:
|
except:
|
||||||
raise Exception("You are required to run a ga before you can connect to the database. Run ga.evolve() or ga.active()")
|
raise Exception("""You are required to run a ga before you can connect to the database. Run ga.evolve() or ga.active()""")
|
||||||
|
|
||||||
|
|
||||||
@conn.setter
|
@conn.setter
|
||||||
@ -345,7 +373,7 @@ class SQLDatabase:
|
|||||||
|
|
||||||
# If the config_id doesnt exist then print error
|
# If the config_id doesnt exist then print error
|
||||||
except:
|
except:
|
||||||
raise Exception("You are required to run a ga before you can connect to the database. Run ga.evolve() or ga.active()")
|
raise Exception("""You are required to run a ga before you can connect to the database. Run ga.evolve() or ga.active()""")
|
||||||
|
|
||||||
|
|
||||||
@config_id.setter
|
@config_id.setter
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
|
|
||||||
@ -2,7 +2,7 @@ import random
|
|||||||
from math import ceil
|
from math import ceil
|
||||||
|
|
||||||
# Import all mutation decorators
|
# Import all mutation decorators
|
||||||
from decorators import _check_chromosome_mutation_rate, _check_gene_mutation_rate, _reset_fitness, _loop_random_mutations
|
from EasyGA.decorators import _check_chromosome_mutation_rate, _check_gene_mutation_rate, _reset_fitness, _loop_random_mutations
|
||||||
|
|
||||||
|
|
||||||
class Population:
|
class Population:
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
# Import all parent decorators
|
# Import all parent decorators
|
||||||
from decorators import _check_selection_probability, _check_positive_fitness, _ensure_sorted, _compute_parent_amount
|
from EasyGA.decorators import _check_selection_probability, _check_positive_fitness, _ensure_sorted, _compute_parent_amount
|
||||||
|
|
||||||
|
|
||||||
class Rank:
|
class Rank:
|
||||||
@ -14,8 +14,8 @@ class Rank:
|
|||||||
@_compute_parent_amount
|
@_compute_parent_amount
|
||||||
def tournament(ga, parent_amount):
|
def tournament(ga, parent_amount):
|
||||||
"""
|
"""
|
||||||
Will make tournaments of size tournament_size and choose the winner (best fitness)
|
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
|
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.
|
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.
|
May require many loops if the selection probability is very low.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
import EasyGA
|
|
||||||
import random
|
|
||||||
|
|
||||||
# Create the Genetic algorithm
|
|
||||||
ga = EasyGA.GA()
|
|
||||||
|
|
||||||
ga.save_data = False
|
|
||||||
|
|
||||||
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.gene_list:
|
|
||||||
# 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
|
|
||||||
|
|
||||||
ga.fitness_function_impl = is_it_5
|
|
||||||
|
|
||||||
# Create random genes from 0 to 10
|
|
||||||
ga.gene_impl = lambda: random.randint(0, 10)
|
|
||||||
|
|
||||||
ga.evolve()
|
|
||||||
|
|
||||||
# Print your default genetic algorithm
|
|
||||||
ga.print_generation()
|
|
||||||
ga.print_population()
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
from structure import Gene as make_gene
|
from EasyGA.structure import Gene as make_gene
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
def to_gene(gene):
|
def to_gene(gene):
|
||||||
@ -107,17 +107,6 @@ class Chromosome():
|
|||||||
return (to_gene(gene) in self.gene_list)
|
return (to_gene(gene) in self.gene_list)
|
||||||
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
"""
|
|
||||||
Returns hash(self).
|
|
||||||
Allows the user to use
|
|
||||||
{chromosome}
|
|
||||||
{chromosome: x}
|
|
||||||
or any other thing requiring hashes with chromosomes.
|
|
||||||
"""
|
|
||||||
return hash(tuple(self))
|
|
||||||
|
|
||||||
|
|
||||||
def __eq__(self, chromosome):
|
def __eq__(self, chromosome):
|
||||||
"""Returns self == chromosome, True if all genes match."""
|
"""Returns self == chromosome, True if all genes match."""
|
||||||
return self.gene_list == chromosome.gene_list
|
return self.gene_list == chromosome.gene_list
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from structure import Chromosome as make_chromosome
|
from EasyGA.structure import Chromosome as make_chromosome
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
def to_chromosome(chromosome):
|
def to_chromosome(chromosome):
|
||||||
@ -159,17 +159,6 @@ class Population:
|
|||||||
return (to_chromosome(chromosome) in self.chromosome_list)
|
return (to_chromosome(chromosome) in self.chromosome_list)
|
||||||
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
"""
|
|
||||||
Returns hash(self).
|
|
||||||
Allows the user to use
|
|
||||||
{population}
|
|
||||||
{population: x}
|
|
||||||
or any other thing requiring hashes with populations.
|
|
||||||
"""
|
|
||||||
return hash(tuple(self))
|
|
||||||
|
|
||||||
|
|
||||||
def __eq__(self, population):
|
def __eq__(self, population):
|
||||||
"""Returns self == population, True if all chromosomes match."""
|
"""Returns self == population, True if all chromosomes match."""
|
||||||
return self.chromosome_list == population.chromosome_list
|
return self.chromosome_list == population.chromosome_list
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
# Import all survivor decorators
|
# Import all survivor decorators
|
||||||
from decorators import *
|
from EasyGA.decorators import *
|
||||||
|
|
||||||
|
|
||||||
def fill_in_best(ga):
|
def fill_in_best(ga):
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
# Import all termination decorators
|
# Import all termination decorators
|
||||||
from decorators import _add_by_fitness_goal, _add_by_generation_goal, _add_by_tolerance_goal
|
from EasyGA.decorators import _add_by_fitness_goal, _add_by_generation_goal, _add_by_tolerance_goal
|
||||||
|
|
||||||
@_add_by_fitness_goal
|
@_add_by_fitness_goal
|
||||||
@_add_by_generation_goal
|
@_add_by_generation_goal
|
||||||
|
|||||||
@ -1,8 +1,13 @@
|
|||||||
import random
|
import random
|
||||||
from EasyGA import GA, Parent, Crossover, Mutation, Survivor, Termination
|
from EasyGA import GA
|
||||||
|
from parent import Parent
|
||||||
|
from crossover import Crossover
|
||||||
|
from mutation import Mutation
|
||||||
|
from survivor import Survivor
|
||||||
|
from termination import Termination
|
||||||
|
|
||||||
# USE THIS COMMAND WHEN TESTING -
|
# USE THIS COMMAND WHEN TESTING -
|
||||||
# python3 -m pytest
|
# python3 -m pytest
|
||||||
|
|
||||||
# Tests can be broken down into three parts.
|
# Tests can be broken down into three parts.
|
||||||
# - Testing correct size
|
# - Testing correct size
|
||||||
@ -10,10 +15,11 @@ from EasyGA import GA, Parent, Crossover, Mutation, Survivor, Termination
|
|||||||
# - Testing correct value
|
# - Testing correct value
|
||||||
# - Testing integration with other functions
|
# - Testing integration with other functions
|
||||||
|
|
||||||
|
|
||||||
def test_population_size():
|
def test_population_size():
|
||||||
"""Test the population size is create correctly"""
|
"""Test the population size is create correctly"""
|
||||||
|
|
||||||
for i in range(4,100):
|
for i in range(4, 100):
|
||||||
# Create the ga to test
|
# Create the ga to test
|
||||||
ga = GA()
|
ga = GA()
|
||||||
|
|
||||||
@ -26,11 +32,12 @@ def test_population_size():
|
|||||||
# If they are not equal throw an error
|
# If they are not equal throw an error
|
||||||
assert int(len(ga.population)) == ga.population_size
|
assert int(len(ga.population)) == ga.population_size
|
||||||
|
|
||||||
|
|
||||||
def test_chromosome_length():
|
def test_chromosome_length():
|
||||||
""" Test to see if the actual chromosome length is the same as defined."""
|
""" Test to see if the actual chromosome length is the same as defined."""
|
||||||
|
|
||||||
# Test from 0 to 100 chromosome length
|
# Test from 0 to 100 chromosome length
|
||||||
for i in range(1,100):
|
for i in range(1, 100):
|
||||||
# Create the ga to test
|
# Create the ga to test
|
||||||
ga = GA()
|
ga = GA()
|
||||||
|
|
||||||
@ -43,14 +50,17 @@ def test_chromosome_length():
|
|||||||
# If they are not equal throw an error
|
# If they are not equal throw an error
|
||||||
assert len(ga.population.chromosome_list[0]) == ga.chromosome_length
|
assert len(ga.population.chromosome_list[0]) == ga.chromosome_length
|
||||||
|
|
||||||
|
|
||||||
def test_gene_value():
|
def test_gene_value():
|
||||||
""" """
|
""" """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_initilization():
|
def test_initilization():
|
||||||
""" """
|
""" """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_default():
|
def test_default():
|
||||||
# Create the Genetic algorithm
|
# Create the Genetic algorithm
|
||||||
ga = GA()
|
ga = GA()
|
||||||
@ -70,7 +80,7 @@ def test_attributes_gene_impl():
|
|||||||
# Set necessary attributes
|
# Set necessary attributes
|
||||||
ga.population_size = 3
|
ga.population_size = 3
|
||||||
ga.chromosome_length = 5
|
ga.chromosome_length = 5
|
||||||
ga.generation_goal = 1
|
ga.generation_goal = 1
|
||||||
# Set gene_impl
|
# Set gene_impl
|
||||||
ga.gene_impl = lambda: random.randint(1, 10)
|
ga.gene_impl = lambda: random.randint(1, 10)
|
||||||
|
|
||||||
@ -89,14 +99,15 @@ def test_attributes_chromosome_impl_lambdas():
|
|||||||
ga.gene_impl = None
|
ga.gene_impl = None
|
||||||
# Set chromosome_impl
|
# Set chromosome_impl
|
||||||
ga.chromosome_impl = lambda: [
|
ga.chromosome_impl = lambda: [
|
||||||
random.randrange(1,100),
|
random.randrange(1, 100),
|
||||||
random.uniform(10,5),
|
random.uniform(10, 5),
|
||||||
random.choice(["up","down"])
|
random.choice(["up", "down"])
|
||||||
]
|
]
|
||||||
|
|
||||||
# Evolve the genetic algorithm
|
# Evolve the genetic algorithm
|
||||||
ga.evolve()
|
ga.evolve()
|
||||||
|
|
||||||
|
|
||||||
def test_attributes_chromosome_impl_functions():
|
def test_attributes_chromosome_impl_functions():
|
||||||
# Create the Genetic algorithm
|
# Create the Genetic algorithm
|
||||||
ga = GA()
|
ga = GA()
|
||||||
@ -108,10 +119,10 @@ def test_attributes_chromosome_impl_functions():
|
|||||||
# Create chromosome_impl user function
|
# Create chromosome_impl user function
|
||||||
def user_chromosome_function():
|
def user_chromosome_function():
|
||||||
chromosome_data = [
|
chromosome_data = [
|
||||||
random.randrange(1,100),
|
random.randrange(1, 100),
|
||||||
random.uniform(10,5),
|
random.uniform(10, 5),
|
||||||
random.choice(["up","down"])
|
random.choice(["up", "down"])
|
||||||
]
|
]
|
||||||
return chromosome_data
|
return chromosome_data
|
||||||
|
|
||||||
# Set the chromosome_impl
|
# Set the chromosome_impl
|
||||||
@ -120,6 +131,7 @@ def test_attributes_chromosome_impl_functions():
|
|||||||
# Evolve the genetic algorithm
|
# Evolve the genetic algorithm
|
||||||
ga.evolve()
|
ga.evolve()
|
||||||
|
|
||||||
|
|
||||||
def test_while_ga_active():
|
def test_while_ga_active():
|
||||||
# Create the Genetic algorithm
|
# Create the Genetic algorithm
|
||||||
ga = GA()
|
ga = GA()
|
||||||
@ -142,7 +154,9 @@ def test_parent_selection_impl():
|
|||||||
# Evolve the genetic algorithm
|
# Evolve the genetic algorithm
|
||||||
ga.evolve()
|
ga.evolve()
|
||||||
|
|
||||||
assert (ga.parent_selection_impl == Parent.Fitness.roulette) and (ga != None)
|
assert (ga.parent_selection_impl ==
|
||||||
|
Parent.Fitness.roulette) and (ga != None)
|
||||||
|
|
||||||
|
|
||||||
def test_crossover_population_impl():
|
def test_crossover_population_impl():
|
||||||
# Create the Genetic algorithm
|
# Create the Genetic algorithm
|
||||||
@ -154,7 +168,9 @@ def test_crossover_population_impl():
|
|||||||
# Evolve the genetic algorithm
|
# Evolve the genetic algorithm
|
||||||
ga.evolve()
|
ga.evolve()
|
||||||
|
|
||||||
assert (ga.crossover_population_impl == Crossover.Population.sequential_selection) and (ga != None)
|
assert (ga.crossover_population_impl ==
|
||||||
|
Crossover.Population.sequential_selection) and (ga != None)
|
||||||
|
|
||||||
|
|
||||||
def test_crossover_individual_impl():
|
def test_crossover_individual_impl():
|
||||||
# Create the Genetic algorithm
|
# Create the Genetic algorithm
|
||||||
@ -166,7 +182,9 @@ def test_crossover_individual_impl():
|
|||||||
# Evolve the genetic algorithm
|
# Evolve the genetic algorithm
|
||||||
ga.evolve()
|
ga.evolve()
|
||||||
|
|
||||||
assert (ga.crossover_individual_impl == Crossover.Individual.single_point) and (ga != None)
|
assert (ga.crossover_individual_impl ==
|
||||||
|
Crossover.Individual.single_point) and (ga != None)
|
||||||
|
|
||||||
|
|
||||||
def test_mutation_population_impl():
|
def test_mutation_population_impl():
|
||||||
# Create the Genetic algorithm
|
# Create the Genetic algorithm
|
||||||
@ -178,7 +196,9 @@ def test_mutation_population_impl():
|
|||||||
# Evolve the genetic algorithm
|
# Evolve the genetic algorithm
|
||||||
ga.evolve()
|
ga.evolve()
|
||||||
|
|
||||||
assert (ga.mutation_population_impl == Mutation.Population.random_selection) and (ga != None)
|
assert (ga.mutation_population_impl ==
|
||||||
|
Mutation.Population.random_selection) and (ga != None)
|
||||||
|
|
||||||
|
|
||||||
def test_mutation_individual_impl():
|
def test_mutation_individual_impl():
|
||||||
# Create the Genetic algorithm
|
# Create the Genetic algorithm
|
||||||
@ -190,7 +210,9 @@ def test_mutation_individual_impl():
|
|||||||
# Evolve the genetic algorithm
|
# Evolve the genetic algorithm
|
||||||
ga.evolve()
|
ga.evolve()
|
||||||
|
|
||||||
assert (ga.mutation_individual_impl == Mutation.Individual.single_gene) and (ga != None)
|
assert (ga.mutation_individual_impl ==
|
||||||
|
Mutation.Individual.single_gene) and (ga != None)
|
||||||
|
|
||||||
|
|
||||||
def test_survivor_selection_impl():
|
def test_survivor_selection_impl():
|
||||||
# Create the Genetic algorithm
|
# Create the Genetic algorithm
|
||||||
@ -202,7 +224,9 @@ def test_survivor_selection_impl():
|
|||||||
# Evolve the genetic algorithm
|
# Evolve the genetic algorithm
|
||||||
ga.evolve()
|
ga.evolve()
|
||||||
|
|
||||||
assert (ga.survivor_selection_impl == Survivor.fill_in_random) and (ga != None)
|
assert (ga.survivor_selection_impl ==
|
||||||
|
Survivor.fill_in_random) and (ga != None)
|
||||||
|
|
||||||
|
|
||||||
def test_termination_impl():
|
def test_termination_impl():
|
||||||
# Create the Genetic algorithm
|
# Create the Genetic algorithm
|
||||||
@ -214,4 +238,5 @@ def test_termination_impl():
|
|||||||
# Evolve the genetic algorithm
|
# Evolve the genetic algorithm
|
||||||
ga.evolve()
|
ga.evolve()
|
||||||
|
|
||||||
assert (ga.termination_impl == Termination.fitness_and_generation_based) and (ga != None)
|
assert (ga.termination_impl ==
|
||||||
|
Termination.fitness_and_generation_based) and (ga != None)
|
||||||
|
|||||||
Reference in New Issue
Block a user