From 12280bf4e0770d95a48ae6c0ff58179e6e284374 Mon Sep 17 00:00:00 2001 From: Thomas Paulin Date: Mon, 2 Jan 2023 20:39:44 +0100 Subject: [PATCH] Another attempt at this maddess --- EasyGA/EasyGA.py | 82 +++++++++++++---------------- EasyGA/attributes.py | 122 +++++++++++++++++++++++++------------------ 2 files changed, 109 insertions(+), 95 deletions(-) diff --git a/EasyGA/EasyGA.py b/EasyGA/EasyGA.py index 65a2bd2..5f78d8e 100644 --- a/EasyGA/EasyGA.py +++ b/EasyGA/EasyGA.py @@ -7,7 +7,7 @@ import EasyGA.decorators # Import all the data structure prebuilt modules 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 Gene as make_gene from EasyGA.structure import Population from EasyGA.structure import Chromosome from EasyGA.structure import Gene @@ -17,23 +17,23 @@ from EasyGA.examples import Fitness from EasyGA.termination import Termination # Parent/Survivor Selection Methods -from EasyGA.parent import Parent +from EasyGA.parent import Parent from EasyGA.survivor import Survivor # Genetic Operator Methods from EasyGA.crossover import Crossover -from EasyGA.mutation import Mutation +from EasyGA.mutation import Mutation # Default Attributes for the GA from EasyGA.attributes import Attributes # Database class -from EasyGA.database import sql_database -from sqlite3 import Error +# from EasyGA.database import sql_database +# from sqlite3 import Error # Graphing package -from EasyGA.database import matplotlib_graph -import matplotlib.pyplot as plt +# from EasyGA.database import matplotlib_graph +# import matplotlib.pyplot as plt class GA(Attributes): @@ -46,7 +46,6 @@ 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. @@ -63,9 +62,12 @@ class GA(Attributes): if self.population is None: self.initialize_population() - 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. + # 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() while cond1() and cond3(): @@ -74,12 +76,13 @@ 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) + # self.database.insert_config(self) + pass - # Otherwise evolve the population. + # Otherwise evolve the population. else: self.parent_selection_impl() self.crossover_population_impl() @@ -93,7 +96,7 @@ class GA(Attributes): self.sort_by_best_fitness() # Save the population to the database - self.save_population() + # self.save_population() # Adapt the ga if the generation times the adapt rate # passes through an integer value. @@ -101,10 +104,9 @@ 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 @@ -112,7 +114,6 @@ class GA(Attributes): """ self.population.update() - def reset_run(self: GA) -> None: """ Resets a run by re-initializing the @@ -122,7 +123,6 @@ class GA(Attributes): self.current_generation = 0 self.run += 1 - def adapt(self: GA) -> None: """Adapts the ga to hopefully get better results.""" @@ -133,7 +133,6 @@ 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 +152,7 @@ class GA(Attributes): # Difference between best and i-th chromosomes best_chromosome = self.population[0] - tol = lambda i: self.dist(best_chromosome, self.population[i]) + def tol(i): return 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,13 +167,14 @@ class GA(Attributes): self.max_gene_mutation_rate) # Weighted average of x and y - average = lambda x, y: weight * x + (1-weight) * y + def average(x, y): return 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: """ @@ -201,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 @@ -209,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 @@ -217,20 +217,19 @@ 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,15 +250,14 @@ 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. @@ -314,7 +312,6 @@ 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. @@ -338,7 +335,6 @@ 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 @@ -375,23 +371,19 @@ 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/attributes.py b/EasyGA/attributes.py index 098e7e9..9df4785 100644 --- a/EasyGA/attributes.py +++ b/EasyGA/attributes.py @@ -6,8 +6,8 @@ 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 EasyGA.structure import Population from EasyGA.structure import Chromosome @@ -19,7 +19,7 @@ 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 +# from EasyGA.database import sql_database, matplotlib_graph @dataclass @@ -29,7 +29,8 @@ class Attributes: Contains default attributes for each attribute. """ - properties: Dict[str, Any] = field(default_factory=dict, init=False, repr=False, compare=False) + properties: Dict[str, Any] = field( + default_factory=dict, init=False, repr=False, compare=False) run: int = 0 @@ -64,61 +65,73 @@ class Attributes: 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 + 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) + 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) + 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 + 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 + #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_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.# - - 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) + # Parameters + # ---------- + # chromosome : Chromosome + # The chromosome to be saved. + # """ + # self.database.insert_current_chromosome( + # was self.current_generation, chromosome) def rand_1_to_10(self: Attributes) -> int: @@ -231,6 +244,7 @@ def get_method(name: str) -> Callable[[Attributes], Callable[..., Any]]: getter(ga)(...) -> Any The getter property, taking in an object and returning the method. """ + def getter(self: Attributes) -> Callable[..., Any]: return self.properties[name] return getter @@ -250,6 +264,7 @@ def set_method(name: str) -> Callable[[Attributes, Optional[Callable[..., Any]]] setter(ga, method) The setter property, taking in an object and returning nothing. """ + def setter(self: Attributes, method: Optional[Callable[..., Any]]) -> None: if method is None: pass @@ -318,6 +333,7 @@ def get_attr(name: str) -> Callable[[Attributes], Any]: getter(ga) -> Any A getter method which returns an attribute. """ + def getter(self: Attributes) -> Any: return self.properties[name] return getter @@ -342,6 +358,7 @@ def set_attr(name: str, check: Callable[[Any], bool], error: str) -> Callable[[A 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 @@ -356,7 +373,8 @@ for name in static_checks: name, property( get_attr(name), - set_attr(name, static_checks[name]["check"], static_checks[name]["error"]), + set_attr(name, static_checks[name] + ["check"], static_checks[name]["error"]), ) ) @@ -385,7 +403,8 @@ def set_max_chromosome_mutation_rate(self: Attributes, value: Optional[float]) - # Raise error else: - raise ValueError("Max chromosome mutation rate must be between 0 and 1") + raise ValueError( + "Max chromosome mutation rate must be between 0 and 1") def get_min_chromosome_mutation_rate(self: Attributes) -> float: @@ -407,7 +426,8 @@ def set_min_chromosome_mutation_rate(self: Attributes, value: Optional[float]) - # Raise error else: - raise ValueError("Min chromosome mutation rate must be between 0 and 1") + raise ValueError( + "Min chromosome mutation rate must be between 0 and 1") def get_database_name(self: Attributes) -> str: @@ -435,8 +455,10 @@ 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.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)