diff --git a/EasyGA/EasyGA.py b/EasyGA/EasyGA.py index 26682fd..65a2bd2 100644 --- a/EasyGA/EasyGA.py +++ b/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]}") diff --git a/EasyGA/__init__.py b/EasyGA/__init__.py index e69de29..c3e2964 100644 --- a/EasyGA/__init__.py +++ b/EasyGA/__init__.py @@ -0,0 +1,2 @@ +import EasyGA +from .EasyGA import GA diff --git a/EasyGA/attributes.py b/EasyGA/attributes.py index 6d521c0..098e7e9 100644 --- a/EasyGA/attributes.py +++ b/EasyGA/attributes.py @@ -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) diff --git a/EasyGA/crossover/Crossover.py b/EasyGA/crossover/Crossover.py index 14eacf9..061ae46 100644 --- a/EasyGA/crossover/Crossover.py +++ b/EasyGA/crossover/Crossover.py @@ -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) - diff --git a/EasyGA/crossover/test_crossover_methods.py b/EasyGA/crossover/test_crossover_methods.py deleted file mode 100644 index e69de29..0000000 diff --git a/EasyGA/database/__init__.py b/EasyGA/database/__init__.py index f779a82..e69de29 100644 --- a/EasyGA/database/__init__.py +++ b/EasyGA/database/__init__.py @@ -1,2 +0,0 @@ -from .sql_database import SQLDatabase -from .matplotlib_graph import MatplotlibGraph diff --git a/EasyGA/database/matplotlib_graph.py b/EasyGA/database/matplotlib_graph.py index 64ad695..a092a0b 100644 --- a/EasyGA/database/matplotlib_graph.py +++ b/EasyGA/database/matplotlib_graph.py @@ -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.""" diff --git a/EasyGA/database/sql_database.py b/EasyGA/database/sql_database.py index 721f028..cc0344d 100644 --- a/EasyGA/database/sql_database.py +++ b/EasyGA/database/sql_database.py @@ -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 diff --git a/EasyGA/examples/test_Fitness.py b/EasyGA/examples/test_Fitness.py deleted file mode 100644 index 8b13789..0000000 --- a/EasyGA/examples/test_Fitness.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/EasyGA/mutation/Mutation.py b/EasyGA/mutation/Mutation.py index cbdf43a..dde0f40 100644 --- a/EasyGA/mutation/Mutation.py +++ b/EasyGA/mutation/Mutation.py @@ -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: diff --git a/EasyGA/mutation/test_mutation_methods.py b/EasyGA/mutation/test_mutation_methods.py deleted file mode 100644 index e69de29..0000000 diff --git a/EasyGA/parent/Parent.py b/EasyGA/parent/Parent.py index 0c01335..d2d63a3 100644 --- a/EasyGA/parent/Parent.py +++ b/EasyGA/parent/Parent.py @@ -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. """ diff --git a/EasyGA/parent/test_parent_selection_methods.py b/EasyGA/parent/test_parent_selection_methods.py deleted file mode 100644 index e69de29..0000000 diff --git a/EasyGA/run.py b/EasyGA/run.py deleted file mode 100644 index 7d29633..0000000 --- a/EasyGA/run.py +++ /dev/null @@ -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() diff --git a/EasyGA/structure/chromosome.py b/EasyGA/structure/chromosome.py index 7806821..4292e65 100644 --- a/EasyGA/structure/chromosome.py +++ b/EasyGA/structure/chromosome.py @@ -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 diff --git a/EasyGA/structure/population.py b/EasyGA/structure/population.py index abb2822..0c60981 100644 --- a/EasyGA/structure/population.py +++ b/EasyGA/structure/population.py @@ -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 diff --git a/EasyGA/survivor/Survivor.py b/EasyGA/survivor/Survivor.py index afd4265..cbaa7db 100644 --- a/EasyGA/survivor/Survivor.py +++ b/EasyGA/survivor/Survivor.py @@ -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): diff --git a/EasyGA/survivor/test_survivor_methods.py b/EasyGA/survivor/test_survivor_methods.py deleted file mode 100644 index e69de29..0000000 diff --git a/EasyGA/termination/Termination.py b/EasyGA/termination/Termination.py index 8e72962..9c31a7e 100644 --- a/EasyGA/termination/Termination.py +++ b/EasyGA/termination/Termination.py @@ -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 diff --git a/EasyGA/termination/test_termination_methods.py b/EasyGA/termination/test_termination_methods.py deleted file mode 100644 index e69de29..0000000 diff --git a/EasyGA/test_EasyGA.py b/EasyGA/test_EasyGA.py index 315d5cd..111e87c 100644 --- a/EasyGA/test_EasyGA.py +++ b/EasyGA/test_EasyGA.py @@ -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) diff --git a/setup.py b/setup.py index 4ca6a07..69536d4 100644 --- a/setup.py +++ b/setup.py @@ -22,8 +22,8 @@ setuptools.setup( ], install_requires=[ # "matplotlib ~= 3.3.2", - # "pyserial ~= 3.4", - "pytest>=3.7", + "pyserial ~= 3.4", + # "pytest>=3.7", "tabulate >=0.8.7" ], )