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
|
||||
|
||||
# Import all decorators
|
||||
import decorators
|
||||
import EasyGA.decorators
|
||||
|
||||
# Import all the data structure prebuilt modules
|
||||
from structure import Population as make_population
|
||||
from structure import Chromosome as make_chromosome
|
||||
from structure import Gene as make_gene
|
||||
from structure import Population
|
||||
from structure import Chromosome
|
||||
from structure import Gene
|
||||
from EasyGA.structure import Population as make_population
|
||||
from EasyGA.structure import Chromosome as make_chromosome
|
||||
from EasyGA.structure import Gene as make_gene
|
||||
from EasyGA.structure import Population
|
||||
from EasyGA.structure import Chromosome
|
||||
from EasyGA.structure import Gene
|
||||
|
||||
# Misc. Methods
|
||||
from examples import Fitness
|
||||
from termination import Termination
|
||||
from EasyGA.examples import Fitness
|
||||
from EasyGA.termination import Termination
|
||||
|
||||
# Parent/Survivor Selection Methods
|
||||
from parent import Parent
|
||||
from survivor import Survivor
|
||||
from EasyGA.parent import Parent
|
||||
from EasyGA.survivor import Survivor
|
||||
|
||||
# Genetic Operator Methods
|
||||
from crossover import Crossover
|
||||
from mutation import Mutation
|
||||
from EasyGA.crossover import Crossover
|
||||
from EasyGA.mutation import Mutation
|
||||
|
||||
# Default Attributes for the GA
|
||||
from attributes import Attributes
|
||||
from EasyGA.attributes import Attributes
|
||||
|
||||
# Database class
|
||||
# from database import SQLDatabase
|
||||
# from sqlite3 import Error
|
||||
from EasyGA.database import sql_database
|
||||
from sqlite3 import Error
|
||||
|
||||
# Graphing package
|
||||
# from database import MatplotlibGraph
|
||||
# import matplotlib.pyplot as plt
|
||||
from EasyGA.database import matplotlib_graph
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
class GA(Attributes):
|
||||
@ -46,6 +46,7 @@ class GA(Attributes):
|
||||
https://github.com/danielwilczak101/EasyGA/wiki
|
||||
"""
|
||||
|
||||
|
||||
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.
|
||||
@ -62,12 +63,9 @@ class GA(Attributes):
|
||||
if self.population is None:
|
||||
self.initialize_population()
|
||||
|
||||
# Evolve the specified number of generations.
|
||||
def cond1(): return number_of_generations > 0
|
||||
# If consider_termination flag is set:
|
||||
def cond2(): return not consider_termination
|
||||
# check termination conditions.
|
||||
def cond3(): return cond2() or self.active()
|
||||
cond1 = lambda: number_of_generations > 0 # Evolve the specified number of generations.
|
||||
cond2 = lambda: not consider_termination # If consider_termination flag is set:
|
||||
cond3 = lambda: cond2() or self.active() # check termination conditions.
|
||||
|
||||
while cond1() and cond3():
|
||||
|
||||
@ -76,11 +74,10 @@ class GA(Attributes):
|
||||
|
||||
# Create the database here to allow the user to change the
|
||||
# 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
|
||||
# self.database.insert_config(self)
|
||||
pass
|
||||
self.database.insert_config(self)
|
||||
|
||||
# Otherwise evolve the population.
|
||||
else:
|
||||
@ -96,8 +93,7 @@ class GA(Attributes):
|
||||
self.sort_by_best_fitness()
|
||||
|
||||
# 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
|
||||
# passes through an integer value.
|
||||
@ -105,9 +101,10 @@ class GA(Attributes):
|
||||
if int(adapt_counter) < int(adapt_counter + self.adapt_rate):
|
||||
self.adapt()
|
||||
|
||||
number_of_generations -= 1
|
||||
number_of_generations -= 1
|
||||
self.current_generation += 1
|
||||
|
||||
|
||||
def update_population(self: GA) -> None:
|
||||
"""
|
||||
Updates the population to the new population
|
||||
@ -115,6 +112,7 @@ class GA(Attributes):
|
||||
"""
|
||||
self.population.update()
|
||||
|
||||
|
||||
def reset_run(self: GA) -> None:
|
||||
"""
|
||||
Resets a run by re-initializing the
|
||||
@ -124,6 +122,7 @@ class GA(Attributes):
|
||||
self.current_generation = 0
|
||||
self.run += 1
|
||||
|
||||
|
||||
def adapt(self: GA) -> None:
|
||||
"""Adapts the ga to hopefully get better results."""
|
||||
|
||||
@ -134,6 +133,7 @@ class GA(Attributes):
|
||||
self.set_all_fitness()
|
||||
self.sort_by_best_fitness()
|
||||
|
||||
|
||||
def adapt_probabilities(self: GA) -> None:
|
||||
"""
|
||||
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
|
||||
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
|
||||
if tol(amount_converged//2) > tol(amount_converged//4)*2:
|
||||
@ -168,14 +168,13 @@ class GA(Attributes):
|
||||
self.max_gene_mutation_rate)
|
||||
|
||||
# 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
|
||||
self.selection_probability = average(
|
||||
bounds[0], self.selection_probability)
|
||||
self.chromosome_mutation_rate = average(
|
||||
bounds[1], self.chromosome_mutation_rate)
|
||||
self.gene_mutation_rate = average(bounds[2], self.gene_mutation_rate)
|
||||
self.selection_probability = average(bounds[0], self.selection_probability)
|
||||
self.chromosome_mutation_rate = average(bounds[1], self.chromosome_mutation_rate)
|
||||
self.gene_mutation_rate = average(bounds[2], self.gene_mutation_rate)
|
||||
|
||||
|
||||
def adapt_population(self: GA) -> None:
|
||||
"""
|
||||
@ -202,7 +201,7 @@ class GA(Attributes):
|
||||
self.crossover_individual_impl(
|
||||
self.population[n],
|
||||
parent,
|
||||
weight=-3/4,
|
||||
weight = -3/4,
|
||||
)
|
||||
|
||||
# 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.population[n],
|
||||
parent,
|
||||
weight=+1/4,
|
||||
weight = +1/4,
|
||||
)
|
||||
|
||||
# Stop if we've filled up an entire population
|
||||
@ -218,19 +217,20 @@ class GA(Attributes):
|
||||
break
|
||||
|
||||
# Replace worst chromosomes with new chromosomes, except for the previous best chromosome
|
||||
min_len = min(len(self.population)-1,
|
||||
len(self.population.next_population))
|
||||
min_len = min(len(self.population)-1, len(self.population.next_population))
|
||||
if min_len > 0:
|
||||
self.population[-min_len:] = self.population.next_population[:min_len]
|
||||
self.population.next_population = []
|
||||
self.population.mating_pool = []
|
||||
|
||||
|
||||
def initialize_population(self: GA) -> None:
|
||||
"""
|
||||
Sets self.population using the chromosome implementation and population size.
|
||||
"""
|
||||
self.population = self.make_population(self.population_impl())
|
||||
|
||||
|
||||
def set_all_fitness(self: GA) -> None:
|
||||
"""
|
||||
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:
|
||||
chromosome.fitness = self.fitness_function_impl(chromosome)
|
||||
|
||||
|
||||
def sort_by_best_fitness(
|
||||
self: GA,
|
||||
chromosome_list: Optional[
|
||||
Union[MutableSequence[Chromosome],
|
||||
Iterable[Chromosome]]
|
||||
] = None,
|
||||
in_place: bool = True,
|
||||
) -> MutableSequence[Chromosome]:
|
||||
self: GA,
|
||||
chromosome_list: Optional[
|
||||
Union[MutableSequence[Chromosome],
|
||||
Iterable[Chromosome]]
|
||||
] = None,
|
||||
in_place: bool = True,
|
||||
) -> MutableSequence[Chromosome]:
|
||||
"""
|
||||
Sorts the chromosome list by fitness based on fitness type.
|
||||
1st element has best fitness.
|
||||
@ -313,6 +314,7 @@ class GA(Attributes):
|
||||
else:
|
||||
return sorted(chromosome_list, key=key, reverse=reverse)
|
||||
|
||||
|
||||
def get_chromosome_fitness(self: GA, index: int) -> float:
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
def convert_fitness(self: GA, fitness: float) -> float:
|
||||
"""
|
||||
Calculates a modified version of the fitness for various
|
||||
@ -372,19 +375,23 @@ class GA(Attributes):
|
||||
|
||||
return max_fitness - fitness + min_fitness
|
||||
|
||||
|
||||
def print_generation(self: GA) -> None:
|
||||
"""Prints the current generation."""
|
||||
print(f"Current Generation \t: {self.current_generation}")
|
||||
|
||||
|
||||
def print_population(self: GA) -> None:
|
||||
"""Prints the entire population."""
|
||||
print(self.population)
|
||||
|
||||
|
||||
def print_best_chromosome(self: GA) -> None:
|
||||
"""Prints the best chromosome and its fitness."""
|
||||
print(f"Best Chromosome \t: {self.population[0]}")
|
||||
print(f"Best Fitness \t: {self.population[0].fitness}")
|
||||
|
||||
|
||||
def print_worst_chromosome(self: GA) -> None:
|
||||
"""Prints the worst chromosome and its fitness."""
|
||||
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 inspect import getmro, signature
|
||||
from typing import Any, Callable, Dict, Iterable, Iterator, Optional
|
||||
from inspect import signature
|
||||
from typing import Callable, Optional, Iterable, Any, Dict
|
||||
from math import sqrt, ceil
|
||||
from dataclasses import dataclass, field, _MISSING_TYPE
|
||||
from dataclasses import dataclass, field
|
||||
from types import MethodType
|
||||
import random
|
||||
|
||||
# import sqlite3
|
||||
# import matplotlib.pyplot as plt
|
||||
import sqlite3
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from structure import Population
|
||||
from structure import Chromosome
|
||||
from structure import Gene
|
||||
from EasyGA.structure import Population
|
||||
from EasyGA.structure import Chromosome
|
||||
from EasyGA.structure import Gene
|
||||
|
||||
from examples import Fitness
|
||||
from termination import Termination
|
||||
from parent import Parent
|
||||
from survivor import Survivor
|
||||
from crossover import Crossover
|
||||
from mutation import Mutation
|
||||
# from database import SQLDatabase, MatplotlibGraph, SQLDatabase as Database, MatplotlibGraph as Graph
|
||||
from EasyGA.examples import Fitness
|
||||
from EasyGA.termination import Termination
|
||||
from EasyGA.parent import Parent
|
||||
from EasyGA.survivor import Survivor
|
||||
from EasyGA.crossover import Crossover
|
||||
from EasyGA.mutation import Mutation
|
||||
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:
|
||||
@ -38,7 +133,7 @@ def rand_1_to_10(self: Attributes) -> int:
|
||||
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.
|
||||
|
||||
@ -51,14 +146,14 @@ def use_genes(self: Attributes) -> Iterator[Any]:
|
||||
|
||||
Returns
|
||||
-------
|
||||
chromosome : Iterator[Any]
|
||||
chromosome : Iterable[Any]
|
||||
Generates the genes for a chromosome.
|
||||
"""
|
||||
for _ in range(self.chromosome_length):
|
||||
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.
|
||||
|
||||
@ -71,7 +166,7 @@ def use_chromosomes(self: Attributes) -> Iterator[Iterable[Any]]:
|
||||
|
||||
Returns
|
||||
-------
|
||||
population : Iterator[Iterable[Any]]
|
||||
population : Iterable[Iterable[Any]]
|
||||
Generates the chromosomes for a population.
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
@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.
|
||||
This includes type-hints/annotations and default values, except for methods.
|
||||
Creates a getter method for getting a method from the Attributes class.
|
||||
|
||||
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:
|
||||
|
||||
See the Attributes class for default methods.
|
||||
|
||||
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__().
|
||||
Returns
|
||||
-------
|
||||
getter(ga)(...) -> Any
|
||||
The getter property, taking in an object and returning the method.
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
#=================================#
|
||||
# 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)
|
||||
def getter(self: Attributes) -> Callable[..., Any]:
|
||||
return self.properties[name]
|
||||
return getter
|
||||
|
||||
|
||||
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,
|
||||
then nothing is set.
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
The name of the method from Attributes.
|
||||
|
||||
Returns
|
||||
-------
|
||||
setter(ga, method)
|
||||
The setter property, taking in an object and returning nothing.
|
||||
"""
|
||||
|
||||
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:
|
||||
def setter(self: Attributes, method: Optional[Callable[..., Any]]) -> None:
|
||||
if method is None:
|
||||
return
|
||||
pass
|
||||
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"):
|
||||
method = MethodType(method, obj)
|
||||
vars(obj)[self.name] = method
|
||||
|
||||
def __delete__(self: AsMethod, obj: "Attributes") -> None:
|
||||
del vars(obj)[self.name]
|
||||
method = MethodType(method, self)
|
||||
self.properties[name] = method
|
||||
return setter
|
||||
|
||||
|
||||
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
|
||||
and implements methods, descriptors, and properties.
|
||||
Creates a getter method for getting an attribute from the Attributes class.
|
||||
|
||||
The built-in methods provide interfacing to the database.
|
||||
>>> ga.save_population() # references ga.database.insert_current_population(ga)
|
||||
The descriptors are used to convert function attributes into methods.
|
||||
>>> ga.gene_impl = lambda self: ... # self is turned into an implicit argument.
|
||||
The properties are used to validate certain inputs.
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
The name of the attribute.
|
||||
|
||||
Developer Notes:
|
||||
|
||||
If inherited, the descriptors may be overridden with a method implementation,
|
||||
but this removes the descriptor.
|
||||
|
||||
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
|
||||
...
|
||||
Returns
|
||||
-------
|
||||
getter(ga) -> Any
|
||||
A getter method which returns an attribute.
|
||||
"""
|
||||
def getter(self: Attributes) -> Any:
|
||||
return self.properties[name]
|
||||
return getter
|
||||
|
||||
#============================#
|
||||
# Built-in database methods: #
|
||||
#============================#
|
||||
|
||||
def save_population(self: Attributes) -> None:
|
||||
"""Saves the current population to the database."""
|
||||
self.database.insert_current_population(self)
|
||||
def set_attr(name: str, check: Callable[[Any], bool], error: str) -> Callable[[Attributes, Any], None]:
|
||||
"""
|
||||
Creates a setter method for setting an attribute from the Attributes class.
|
||||
|
||||
def save_chromosome(self: Attributes, chromosome: Chromosome) -> None:
|
||||
"""
|
||||
Saves a chromosome to the database.
|
||||
Parameters
|
||||
----------
|
||||
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
|
||||
----------
|
||||
chromosome : Chromosome
|
||||
The chromosome to be saved.
|
||||
"""
|
||||
self.database.insert_current_chromosome(
|
||||
self.current_generation, chromosome)
|
||||
|
||||
#===========================#
|
||||
# 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
|
||||
Returns
|
||||
-------
|
||||
setter(ga, Any) -> None
|
||||
Raises ValueError(error)
|
||||
A setter method which saves to an attribute.
|
||||
"""
|
||||
def setter(self: Attributes, value: Any) -> Any:
|
||||
if check(value):
|
||||
self.properties[name] = value
|
||||
else:
|
||||
raise ValueError(
|
||||
"Max chromosome mutation rate must be between 0 and 1")
|
||||
raise ValueError(error)
|
||||
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
|
||||
def min_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)["min_chromosome_mutation_rate"] = value
|
||||
else:
|
||||
raise ValueError(
|
||||
"Min chromosome mutation rate must be between 0 and 1")
|
||||
for name in static_checks:
|
||||
setattr(
|
||||
Attributes,
|
||||
name,
|
||||
property(
|
||||
get_attr(name),
|
||||
set_attr(name, static_checks[name]["check"], static_checks[name]["error"]),
|
||||
)
|
||||
)
|
||||
|
||||
# @property
|
||||
# def database_name(self: AttributesProperties) -> str:
|
||||
# return vars(self)["database_name"]
|
||||
|
||||
# @database_name.setter
|
||||
# def database_name(self: AttributesProperties, name: str) -> None:
|
||||
# # Update the database's name.
|
||||
# self.database._database_name = name
|
||||
# # Set the attribute for itself.
|
||||
# vars(self)["database_name"] = name
|
||||
#==================#
|
||||
# Other properties #
|
||||
#==================#
|
||||
|
||||
# @property
|
||||
# def graph(self: AttributesProperties) -> Graph:
|
||||
# return vars(self)["graph"]
|
||||
|
||||
# @graph.setter
|
||||
# def graph(self: AttributesProperties, graph: Callable[[Database], Graph]) -> None:
|
||||
# vars(self)["graph"] = graph(self.database)
|
||||
def get_max_chromosome_mutation_rate(self: Attributes) -> float:
|
||||
return self._max_chromosome_mutation_rate
|
||||
|
||||
@property
|
||||
def active(self: AttributesProperties) -> Callable[[], bool]:
|
||||
return self.termination_impl
|
||||
|
||||
def set_max_chromosome_mutation_rate(self: Attributes, value: Optional[float]) -> None:
|
||||
|
||||
# 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 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
|
||||
# the closer it is to that integer.
|
||||
@ -160,4 +160,3 @@ class Individual:
|
||||
input_index += 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
|
||||
|
||||
|
||||
class MatplotlibGraph:
|
||||
class Matplotlib_Graph:
|
||||
"""Prebuilt graphing functions to make visual
|
||||
represention of fitness data."""
|
||||
|
||||
|
||||
@ -1,24 +1,21 @@
|
||||
import sqlite3
|
||||
from sqlite3 import Error
|
||||
|
||||
from tabulate import tabulate
|
||||
|
||||
|
||||
class SQLDatabase:
|
||||
class SQL_Database:
|
||||
"""Main database class that controls all the functionality for input /
|
||||
out of the database using SQLite3."""
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.conn = None
|
||||
self.config_id = None
|
||||
self._database_name = 'database.db'
|
||||
self.config_structure = """
|
||||
CREATE TABLE IF NOT EXISTS config (
|
||||
config_id INTEGER,
|
||||
attribute_name TEXT,
|
||||
attribute_value TEXT
|
||||
);
|
||||
"""
|
||||
self.config_structure = f"""
|
||||
CREATE TABLE IF NOT EXISTS config (
|
||||
config_id INTEGER,
|
||||
attribute_name TEXT,
|
||||
attribute_value TEXT)"""
|
||||
|
||||
|
||||
#=====================================#
|
||||
@ -26,56 +23,102 @@ class SQLDatabase:
|
||||
#=====================================#
|
||||
|
||||
def create_all_tables(self, ga):
|
||||
"""Create the database if it doenst exist and then the data and config tables."""
|
||||
# Create the database connection.
|
||||
"""Create the database if it doenst exist and then the data and config
|
||||
tables."""
|
||||
|
||||
# Create the database 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.")
|
||||
# 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:
|
||||
|
||||
"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.
|
||||
"""
|
||||
def insert_config(self,ga):
|
||||
"""Insert the configuration attributes into the config."""
|
||||
|
||||
# Get the current config and add one for the new config key
|
||||
self.config_id = self.get_current_config()
|
||||
|
||||
# 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
|
||||
else:
|
||||
self.config_id = self.config_id + 1
|
||||
|
||||
# Getting all attribute fields from the attributes class
|
||||
db_config = [
|
||||
(self.config_id, attr_name, attr_value)
|
||||
# Getting all the attributes from the attributes class
|
||||
db_config_dict = (
|
||||
(attr_name, getattr(ga, attr_name))
|
||||
for attr_name
|
||||
in ga.__dataclass_fields__
|
||||
if isinstance((attr_value := getattr(ga, attr_name)), (bool, float, int, str))
|
||||
]
|
||||
in ga.__annotations__
|
||||
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()
|
||||
|
||||
|
||||
#=====================================#
|
||||
# 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: #
|
||||
#=====================================#
|
||||
@ -88,91 +131,79 @@ class SQLDatabase:
|
||||
def past_runs(self):
|
||||
"""Show a summerization of the past runs that the user has done."""
|
||||
|
||||
query_data = self.query_all("""
|
||||
SELECT config_id, attribute_name, attribute_value
|
||||
FROM config;
|
||||
""")
|
||||
query_data = self.query_all(f"""
|
||||
SELECT config_id,attribute_name,attribute_value
|
||||
FROM config;""")
|
||||
|
||||
table = tabulate(
|
||||
query_data,
|
||||
headers = [
|
||||
'config_id',
|
||||
'attribute_name',
|
||||
'attribute_value',
|
||||
]
|
||||
print(
|
||||
tabulate(
|
||||
query_data,
|
||||
headers = [
|
||||
'config_id',
|
||||
'attribute_name',
|
||||
'attribute_value'
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
print(table)
|
||||
return table
|
||||
|
||||
|
||||
@default_config_id
|
||||
def get_generation_total_fitness(self, config_id):
|
||||
"""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"""
|
||||
SELECT SUM(fitness)
|
||||
FROM data
|
||||
WHERE config_id={config_id}
|
||||
GROUP BY generation;
|
||||
""")
|
||||
SELECT SUM(fitness)
|
||||
FROM data
|
||||
WHERE config_id={config_id}
|
||||
GROUP BY generation;""")
|
||||
|
||||
|
||||
@default_config_id
|
||||
def get_total_generations(self, config_id):
|
||||
"""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"""
|
||||
SELECT COUNT(DISTINCT generation)
|
||||
FROM data
|
||||
WHERE config_id={config_id};
|
||||
""")
|
||||
SELECT COUNT(DISTINCT generation)
|
||||
FROM data
|
||||
WHERE config_id={config_id};""")
|
||||
|
||||
|
||||
@default_config_id
|
||||
def get_highest_chromosome(self, config_id):
|
||||
"""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"""
|
||||
SELECT max(fitness)
|
||||
FROM data
|
||||
WHERE config_id={config_id}
|
||||
GROUP by generation;
|
||||
""")
|
||||
SELECT max(fitness)
|
||||
FROM data
|
||||
WHERE config_id={config_id}
|
||||
GROUP by generation;""")
|
||||
|
||||
|
||||
@default_config_id
|
||||
def get_lowest_chromosome(self, config_id):
|
||||
"""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"""
|
||||
SELECT min(fitness)
|
||||
FROM data
|
||||
WHERE config_id={config_id}
|
||||
GROUP by generation;
|
||||
""")
|
||||
SELECT min(fitness)
|
||||
FROM data
|
||||
WHERE config_id={config_id}
|
||||
GROUP by generation;""")
|
||||
|
||||
|
||||
def get_all_config_id(self):
|
||||
"""Get an array of all the DISTINCT config_id in the database"""
|
||||
|
||||
return self.query_all(f"""
|
||||
SELECT DISTINCT config_id
|
||||
FROM config;
|
||||
""")
|
||||
SELECT DISTINCT config_id
|
||||
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"""
|
||||
|
||||
return self.query_all(f"""
|
||||
SELECT DISTINCT generation
|
||||
FROM data
|
||||
WHERE config_id={config_id};
|
||||
""")
|
||||
SELECT DISTINCT generation
|
||||
FROM data
|
||||
WHERE config_id={config_id};""")
|
||||
|
||||
|
||||
|
||||
@ -189,14 +220,12 @@ class SQLDatabase:
|
||||
self.config_id,
|
||||
generation,
|
||||
chromosome.fitness,
|
||||
repr(chromosome),
|
||||
repr(chromosome)
|
||||
)
|
||||
|
||||
# Create sql query structure
|
||||
sql = """
|
||||
INSERT INTO data(config_id, generation, fitness, chromosome)
|
||||
VALUES(?, ?, ?, ?)
|
||||
"""
|
||||
sql = """INSERT INTO data(config_id, generation, fitness, chromosome)
|
||||
VALUES(?,?,?,?)"""
|
||||
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(sql, db_chromosome)
|
||||
@ -220,10 +249,8 @@ class SQLDatabase:
|
||||
]
|
||||
|
||||
# Create sql query structure
|
||||
sql = """
|
||||
INSERT INTO data(config_id, generation, fitness, chromosome)
|
||||
VALUES(?,?,?,?)
|
||||
"""
|
||||
sql = """INSERT INTO data(config_id, generation, fitness, chromosome)
|
||||
VALUES(?,?,?,?)"""
|
||||
|
||||
cur = self.conn.cursor()
|
||||
cur.executemany(sql, db_chromosome_list)
|
||||
@ -236,7 +263,8 @@ class SQLDatabase:
|
||||
#=====================================#
|
||||
|
||||
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:
|
||||
self.conn = sqlite3.connect(self.database_name)
|
||||
@ -244,7 +272,6 @@ class SQLDatabase:
|
||||
self.conn = None
|
||||
print(e)
|
||||
|
||||
|
||||
def create_table(self, create_table_sql):
|
||||
"""Create a table from the create_table_sql statement."""
|
||||
|
||||
@ -255,31 +282,22 @@ class SQLDatabase:
|
||||
print(e)
|
||||
|
||||
|
||||
def format_query_data(self, 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
|
||||
|
||||
|
||||
@format_query_data
|
||||
def query_all(self, query):
|
||||
"""Query for muliple rows of data"""
|
||||
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(query)
|
||||
return self.format_query_data(cur.fetchall())
|
||||
return cur.fetchall()
|
||||
|
||||
|
||||
@format_query_data
|
||||
def query_one_item(self, query):
|
||||
"""Query for single data point"""
|
||||
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(query)
|
||||
return self.format_query_data(cur.fetchone())
|
||||
return cur.fetchone()
|
||||
|
||||
|
||||
def remove_database(self):
|
||||
@ -287,6 +305,16 @@ class SQLDatabase:
|
||||
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: #
|
||||
#=====================================#
|
||||
@ -299,7 +327,7 @@ class SQLDatabase:
|
||||
|
||||
@database_name.setter
|
||||
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
|
||||
@ -318,7 +346,7 @@ class SQLDatabase:
|
||||
|
||||
# If the connection doesnt exist then print error
|
||||
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
|
||||
@ -345,7 +373,7 @@ class SQLDatabase:
|
||||
|
||||
# If the config_id doesnt exist then print error
|
||||
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
|
||||
|
||||
@ -1 +0,0 @@
|
||||
|
||||
@ -2,7 +2,7 @@ import random
|
||||
from math import ceil
|
||||
|
||||
# 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:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import random
|
||||
|
||||
# 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:
|
||||
@ -14,8 +14,8 @@ class Rank:
|
||||
@_compute_parent_amount
|
||||
def tournament(ga, parent_amount):
|
||||
"""
|
||||
Will make tournaments of size tournament_size and choose the winner (best fitness)
|
||||
from the tournament and use it as a parent for the next generation. The total number
|
||||
Will make tournaments of size tournament_size and choose the winner (best fitness)
|
||||
from the tournament and use it as a parent for the next generation. The total number
|
||||
of parents selected is determined by parent_ratio, an attribute to the GA object.
|
||||
May require many loops if the selection probability is very low.
|
||||
"""
|
||||
|
||||
@ -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
|
||||
|
||||
def to_gene(gene):
|
||||
@ -107,17 +107,6 @@ class Chromosome():
|
||||
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):
|
||||
"""Returns self == chromosome, True if all genes match."""
|
||||
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
|
||||
|
||||
def to_chromosome(chromosome):
|
||||
@ -159,17 +159,6 @@ class Population:
|
||||
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):
|
||||
"""Returns self == population, True if all chromosomes match."""
|
||||
return self.chromosome_list == population.chromosome_list
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import random
|
||||
|
||||
# Import all survivor decorators
|
||||
from decorators import *
|
||||
# Import all survivor decorators
|
||||
from EasyGA.decorators import *
|
||||
|
||||
|
||||
def fill_in_best(ga):
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Import all termination decorators
|
||||
from decorators import _add_by_fitness_goal, _add_by_generation_goal, _add_by_tolerance_goal
|
||||
# Import all termination decorators
|
||||
from EasyGA.decorators import _add_by_fitness_goal, _add_by_generation_goal, _add_by_tolerance_goal
|
||||
|
||||
@_add_by_fitness_goal
|
||||
@_add_by_generation_goal
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
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 -
|
||||
# python3 -m pytest
|
||||
# python3 -m pytest
|
||||
|
||||
# Tests can be broken down into three parts.
|
||||
# - Testing correct size
|
||||
@ -10,10 +15,11 @@ from EasyGA import GA, Parent, Crossover, Mutation, Survivor, Termination
|
||||
# - Testing correct value
|
||||
# - Testing integration with other functions
|
||||
|
||||
|
||||
def test_population_size():
|
||||
"""Test the population size is create correctly"""
|
||||
|
||||
for i in range(4,100):
|
||||
for i in range(4, 100):
|
||||
# Create the ga to test
|
||||
ga = GA()
|
||||
|
||||
@ -26,11 +32,12 @@ def test_population_size():
|
||||
# If they are not equal throw an error
|
||||
assert int(len(ga.population)) == ga.population_size
|
||||
|
||||
|
||||
def test_chromosome_length():
|
||||
""" Test to see if the actual chromosome length is the same as defined."""
|
||||
|
||||
# Test from 0 to 100 chromosome length
|
||||
for i in range(1,100):
|
||||
for i in range(1, 100):
|
||||
# Create the ga to test
|
||||
ga = GA()
|
||||
|
||||
@ -43,14 +50,17 @@ def test_chromosome_length():
|
||||
# If they are not equal throw an error
|
||||
assert len(ga.population.chromosome_list[0]) == ga.chromosome_length
|
||||
|
||||
|
||||
def test_gene_value():
|
||||
""" """
|
||||
pass
|
||||
|
||||
|
||||
def test_initilization():
|
||||
""" """
|
||||
pass
|
||||
|
||||
|
||||
def test_default():
|
||||
# Create the Genetic algorithm
|
||||
ga = GA()
|
||||
@ -70,7 +80,7 @@ def test_attributes_gene_impl():
|
||||
# Set necessary attributes
|
||||
ga.population_size = 3
|
||||
ga.chromosome_length = 5
|
||||
ga.generation_goal = 1
|
||||
ga.generation_goal = 1
|
||||
# Set gene_impl
|
||||
ga.gene_impl = lambda: random.randint(1, 10)
|
||||
|
||||
@ -89,14 +99,15 @@ def test_attributes_chromosome_impl_lambdas():
|
||||
ga.gene_impl = None
|
||||
# Set chromosome_impl
|
||||
ga.chromosome_impl = lambda: [
|
||||
random.randrange(1,100),
|
||||
random.uniform(10,5),
|
||||
random.choice(["up","down"])
|
||||
]
|
||||
random.randrange(1, 100),
|
||||
random.uniform(10, 5),
|
||||
random.choice(["up", "down"])
|
||||
]
|
||||
|
||||
# Evolve the genetic algorithm
|
||||
ga.evolve()
|
||||
|
||||
|
||||
def test_attributes_chromosome_impl_functions():
|
||||
# Create the Genetic algorithm
|
||||
ga = GA()
|
||||
@ -108,10 +119,10 @@ def test_attributes_chromosome_impl_functions():
|
||||
# Create chromosome_impl user function
|
||||
def user_chromosome_function():
|
||||
chromosome_data = [
|
||||
random.randrange(1,100),
|
||||
random.uniform(10,5),
|
||||
random.choice(["up","down"])
|
||||
]
|
||||
random.randrange(1, 100),
|
||||
random.uniform(10, 5),
|
||||
random.choice(["up", "down"])
|
||||
]
|
||||
return chromosome_data
|
||||
|
||||
# Set the chromosome_impl
|
||||
@ -120,6 +131,7 @@ def test_attributes_chromosome_impl_functions():
|
||||
# Evolve the genetic algorithm
|
||||
ga.evolve()
|
||||
|
||||
|
||||
def test_while_ga_active():
|
||||
# Create the Genetic algorithm
|
||||
ga = GA()
|
||||
@ -142,7 +154,9 @@ def test_parent_selection_impl():
|
||||
# Evolve the genetic algorithm
|
||||
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():
|
||||
# Create the Genetic algorithm
|
||||
@ -154,7 +168,9 @@ def test_crossover_population_impl():
|
||||
# Evolve the genetic algorithm
|
||||
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():
|
||||
# Create the Genetic algorithm
|
||||
@ -166,7 +182,9 @@ def test_crossover_individual_impl():
|
||||
# Evolve the genetic algorithm
|
||||
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():
|
||||
# Create the Genetic algorithm
|
||||
@ -178,7 +196,9 @@ def test_mutation_population_impl():
|
||||
# Evolve the genetic algorithm
|
||||
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():
|
||||
# Create the Genetic algorithm
|
||||
@ -190,7 +210,9 @@ def test_mutation_individual_impl():
|
||||
# Evolve the genetic algorithm
|
||||
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():
|
||||
# Create the Genetic algorithm
|
||||
@ -202,7 +224,9 @@ def test_survivor_selection_impl():
|
||||
# Evolve the genetic algorithm
|
||||
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():
|
||||
# Create the Genetic algorithm
|
||||
@ -214,4 +238,5 @@ def test_termination_impl():
|
||||
# Evolve the genetic algorithm
|
||||
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