Another attempt at this maddess

This commit is contained in:
2023-01-02 20:39:44 +01:00
parent b9d8dbe0a2
commit 12280bf4e0
2 changed files with 109 additions and 95 deletions

View File

@ -7,7 +7,7 @@ import EasyGA.decorators
# Import all the data structure prebuilt modules # Import all the data structure prebuilt modules
from EasyGA.structure import Population as make_population from EasyGA.structure import Population as make_population
from EasyGA.structure import Chromosome as make_chromosome 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 Population
from EasyGA.structure import Chromosome from EasyGA.structure import Chromosome
from EasyGA.structure import Gene from EasyGA.structure import Gene
@ -17,23 +17,23 @@ from EasyGA.examples import Fitness
from EasyGA.termination import Termination from EasyGA.termination import Termination
# Parent/Survivor Selection Methods # Parent/Survivor Selection Methods
from EasyGA.parent import Parent from EasyGA.parent import Parent
from EasyGA.survivor import Survivor from EasyGA.survivor import Survivor
# Genetic Operator Methods # Genetic Operator Methods
from EasyGA.crossover import Crossover from EasyGA.crossover import Crossover
from EasyGA.mutation import Mutation from EasyGA.mutation import Mutation
# Default Attributes for the GA # Default Attributes for the GA
from EasyGA.attributes import Attributes from EasyGA.attributes import Attributes
# Database class # Database class
from EasyGA.database import sql_database # from EasyGA.database import sql_database
from sqlite3 import Error # from sqlite3 import Error
# Graphing package # Graphing package
from EasyGA.database import matplotlib_graph # from EasyGA.database import matplotlib_graph
import matplotlib.pyplot as plt # import matplotlib.pyplot as plt
class GA(Attributes): class GA(Attributes):
@ -46,7 +46,6 @@ class GA(Attributes):
https://github.com/danielwilczak101/EasyGA/wiki https://github.com/danielwilczak101/EasyGA/wiki
""" """
def evolve(self: GA, number_of_generations: float = float('inf'), consider_termination: bool = True) -> None: def evolve(self: GA, number_of_generations: float = float('inf'), consider_termination: bool = True) -> None:
""" """
Evolves the ga until the ga is no longer active. Evolves the ga until the ga is no longer active.
@ -63,9 +62,12 @@ class GA(Attributes):
if self.population is None: if self.population is None:
self.initialize_population() self.initialize_population()
cond1 = lambda: number_of_generations > 0 # Evolve the specified number of generations. # Evolve the specified number of generations.
cond2 = lambda: not consider_termination # If consider_termination flag is set: def cond1(): return number_of_generations > 0
cond3 = lambda: cond2() or self.active() # check termination conditions. # 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(): while cond1() and cond3():
@ -74,12 +76,13 @@ class GA(Attributes):
# Create the database here to allow the user to change the # Create the database here to allow the user to change the
# database name and structure before running the function. # database name and structure before running the function.
self.database.create_all_tables(self) # self.database.create_all_tables(self)
# Add the current configuration to the config table # Add the current configuration to the config table
self.database.insert_config(self) # self.database.insert_config(self)
pass
# Otherwise evolve the population. # Otherwise evolve the population.
else: else:
self.parent_selection_impl() self.parent_selection_impl()
self.crossover_population_impl() self.crossover_population_impl()
@ -93,7 +96,7 @@ class GA(Attributes):
self.sort_by_best_fitness() self.sort_by_best_fitness()
# Save the population to the database # Save the population to the database
self.save_population() # self.save_population()
# Adapt the ga if the generation times the adapt rate # Adapt the ga if the generation times the adapt rate
# passes through an integer value. # passes through an integer value.
@ -101,10 +104,9 @@ class GA(Attributes):
if int(adapt_counter) < int(adapt_counter + self.adapt_rate): if int(adapt_counter) < int(adapt_counter + self.adapt_rate):
self.adapt() self.adapt()
number_of_generations -= 1 number_of_generations -= 1
self.current_generation += 1 self.current_generation += 1
def update_population(self: GA) -> None: def update_population(self: GA) -> None:
""" """
Updates the population to the new population Updates the population to the new population
@ -112,7 +114,6 @@ class GA(Attributes):
""" """
self.population.update() self.population.update()
def reset_run(self: GA) -> None: def reset_run(self: GA) -> None:
""" """
Resets a run by re-initializing the Resets a run by re-initializing the
@ -122,7 +123,6 @@ class GA(Attributes):
self.current_generation = 0 self.current_generation = 0
self.run += 1 self.run += 1
def adapt(self: GA) -> None: def adapt(self: GA) -> None:
"""Adapts the ga to hopefully get better results.""" """Adapts the ga to hopefully get better results."""
@ -133,7 +133,6 @@ class GA(Attributes):
self.set_all_fitness() self.set_all_fitness()
self.sort_by_best_fitness() self.sort_by_best_fitness()
def adapt_probabilities(self: GA) -> None: def adapt_probabilities(self: GA) -> None:
""" """
Modifies the parent ratio and mutation rates based on the adapt Modifies the parent ratio and mutation rates based on the adapt
@ -153,7 +152,7 @@ class GA(Attributes):
# Difference between best and i-th chromosomes # Difference between best and i-th chromosomes
best_chromosome = self.population[0] best_chromosome = self.population[0]
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 # Too few converged: cross more and mutate less
if tol(amount_converged//2) > tol(amount_converged//4)*2: if tol(amount_converged//2) > tol(amount_converged//4)*2:
@ -168,13 +167,14 @@ class GA(Attributes):
self.max_gene_mutation_rate) self.max_gene_mutation_rate)
# Weighted average of x and y # Weighted average of x and y
average = lambda x, y: weight * x + (1-weight) * y def average(x, y): return weight * x + (1-weight) * y
# Adjust rates towards the bounds # Adjust rates towards the bounds
self.selection_probability = average(bounds[0], self.selection_probability) self.selection_probability = average(
self.chromosome_mutation_rate = average(bounds[1], self.chromosome_mutation_rate) bounds[0], self.selection_probability)
self.gene_mutation_rate = average(bounds[2], self.gene_mutation_rate) 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: def adapt_population(self: GA) -> None:
""" """
@ -201,7 +201,7 @@ class GA(Attributes):
self.crossover_individual_impl( self.crossover_individual_impl(
self.population[n], self.population[n],
parent, parent,
weight = -3/4, weight=-3/4,
) )
# If negative weights can't be used or division by 0, use positive weight # If negative weights can't be used or division by 0, use positive weight
@ -209,7 +209,7 @@ class GA(Attributes):
self.crossover_individual_impl( self.crossover_individual_impl(
self.population[n], self.population[n],
parent, parent,
weight = +1/4, weight=+1/4,
) )
# Stop if we've filled up an entire population # Stop if we've filled up an entire population
@ -217,20 +217,19 @@ class GA(Attributes):
break break
# Replace worst chromosomes with new chromosomes, except for the previous best chromosome # Replace worst chromosomes with new chromosomes, except for the previous best chromosome
min_len = min(len(self.population)-1, len(self.population.next_population)) min_len = min(len(self.population)-1,
len(self.population.next_population))
if min_len > 0: if min_len > 0:
self.population[-min_len:] = self.population.next_population[:min_len] self.population[-min_len:] = self.population.next_population[:min_len]
self.population.next_population = [] self.population.next_population = []
self.population.mating_pool = [] self.population.mating_pool = []
def initialize_population(self: GA) -> None: def initialize_population(self: GA) -> None:
""" """
Sets self.population using the chromosome implementation and population size. Sets self.population using the chromosome implementation and population size.
""" """
self.population = self.make_population(self.population_impl()) self.population = self.make_population(self.population_impl())
def set_all_fitness(self: GA) -> None: def set_all_fitness(self: GA) -> None:
""" """
Sets the fitness of each chromosome in the population. Sets the fitness of each chromosome in the population.
@ -251,15 +250,14 @@ class GA(Attributes):
if chromosome.fitness is None or self.update_fitness: if chromosome.fitness is None or self.update_fitness:
chromosome.fitness = self.fitness_function_impl(chromosome) chromosome.fitness = self.fitness_function_impl(chromosome)
def sort_by_best_fitness( def sort_by_best_fitness(
self: GA, self: GA,
chromosome_list: Optional[ chromosome_list: Optional[
Union[MutableSequence[Chromosome], Union[MutableSequence[Chromosome],
Iterable[Chromosome]] Iterable[Chromosome]]
] = None, ] = None,
in_place: bool = True, in_place: bool = True,
) -> MutableSequence[Chromosome]: ) -> MutableSequence[Chromosome]:
""" """
Sorts the chromosome list by fitness based on fitness type. Sorts the chromosome list by fitness based on fitness type.
1st element has best fitness. 1st element has best fitness.
@ -314,7 +312,6 @@ class GA(Attributes):
else: else:
return sorted(chromosome_list, key=key, reverse=reverse) return sorted(chromosome_list, key=key, reverse=reverse)
def get_chromosome_fitness(self: GA, index: int) -> float: def get_chromosome_fitness(self: GA, index: int) -> float:
""" """
Computes the converted fitness of a chromosome at an index. Computes the converted fitness of a chromosome at an index.
@ -338,7 +335,6 @@ class GA(Attributes):
""" """
return self.convert_fitness(self.population[index].fitness) return self.convert_fitness(self.population[index].fitness)
def convert_fitness(self: GA, fitness: float) -> float: def convert_fitness(self: GA, fitness: float) -> float:
""" """
Calculates a modified version of the fitness for various Calculates a modified version of the fitness for various
@ -375,23 +371,19 @@ class GA(Attributes):
return max_fitness - fitness + min_fitness return max_fitness - fitness + min_fitness
def print_generation(self: GA) -> None: def print_generation(self: GA) -> None:
"""Prints the current generation.""" """Prints the current generation."""
print(f"Current Generation \t: {self.current_generation}") print(f"Current Generation \t: {self.current_generation}")
def print_population(self: GA) -> None: def print_population(self: GA) -> None:
"""Prints the entire population.""" """Prints the entire population."""
print(self.population) print(self.population)
def print_best_chromosome(self: GA) -> None: def print_best_chromosome(self: GA) -> None:
"""Prints the best chromosome and its fitness.""" """Prints the best chromosome and its fitness."""
print(f"Best Chromosome \t: {self.population[0]}") print(f"Best Chromosome \t: {self.population[0]}")
print(f"Best Fitness \t: {self.population[0].fitness}") print(f"Best Fitness \t: {self.population[0].fitness}")
def print_worst_chromosome(self: GA) -> None: def print_worst_chromosome(self: GA) -> None:
"""Prints the worst chromosome and its fitness.""" """Prints the worst chromosome and its fitness."""
print(f"Worst Chromosome \t: {self.population[-1]}") print(f"Worst Chromosome \t: {self.population[-1]}")

View File

@ -6,8 +6,8 @@ from dataclasses import dataclass, field
from types import MethodType from types import MethodType
import random import random
import sqlite3 # import sqlite3
import matplotlib.pyplot as plt # import matplotlib.pyplot as plt
from EasyGA.structure import Population from EasyGA.structure import Population
from EasyGA.structure import Chromosome from EasyGA.structure import Chromosome
@ -19,7 +19,7 @@ from EasyGA.parent import Parent
from EasyGA.survivor import Survivor from EasyGA.survivor import Survivor
from EasyGA.crossover import Crossover from EasyGA.crossover import Crossover
from EasyGA.mutation import Mutation from EasyGA.mutation import Mutation
from EasyGA.database import sql_database, matplotlib_graph # from EasyGA.database import sql_database, matplotlib_graph
@dataclass @dataclass
@ -29,7 +29,8 @@ class Attributes:
Contains default attributes for each attribute. 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 run: int = 0
@ -64,61 +65,73 @@ class Attributes:
max_gene_mutation_rate: float = 0.15 max_gene_mutation_rate: float = 0.15
min_gene_mutation_rate: float = 0.01 min_gene_mutation_rate: float = 0.01
fitness_function_impl: Callable[[Attributes, Chromosome], float] = Fitness.is_it_5 fitness_function_impl: Callable[[
make_population: Callable[[Iterable[Iterable[Any]]], Population] = Population Attributes, Chromosome], float] = Fitness.is_it_5
make_population: Callable[[
Iterable[Iterable[Any]]], Population] = Population
make_chromosome: Callable[[Iterable[Any]], Chromosome] = Chromosome make_chromosome: Callable[[Iterable[Any]], Chromosome] = Chromosome
make_gene: Callable[[Any], Gene] = Gene make_gene: Callable[[Any], Gene] = Gene
gene_impl: Callable[[Attributes], Any] = field(default_factory=lambda: rand_1_to_10) gene_impl: Callable[[Attributes], Any] = field(
chromosome_impl: Optional[[Attributes], Iterable[Any]] = field(default_factory=lambda: use_genes) default_factory=lambda: rand_1_to_10)
population_impl: Optional[[Attributes], Iterable[Iterable[Any]]] = field(default_factory=lambda: use_chromosomes) 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) weighted_random: Callable[[Attributes, float], float] = field(
dist: Callable[[Attributes, Chromosome, Chromosome], float] = field(default_factory=lambda: dist_fitness) 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 parent_selection_impl: Callable[[
crossover_individual_impl: Callable[[Attributes], None] = Crossover.Individual.single_point Attributes], None] = Parent.Rank.tournament
crossover_population_impl: Callable[[Attributes], None] = Crossover.Population.sequential crossover_individual_impl: Callable[[
survivor_selection_impl: Callable[[Attributes], None] = Survivor.fill_in_best Attributes], None] = Crossover.Individual.single_point
mutation_individual_impl: Callable[[Attributes], None] = Mutation.Individual.individual_genes crossover_population_impl: Callable[[
mutation_population_impl: Callable[[Attributes], None] = Mutation.Population.random_avoid_best Attributes], None] = Crossover.Population.sequential
termination_impl: Callable[[Attributes], None] = Termination.fitness_generation_tolerance 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: Database = field(default_factory=sql_database.SQL_Database)
database_name: str = 'database.db' #database_name: str = 'database.db'
sql_create_data_structure: str = """ # sql_create_data_structure: str = """
CREATE TABLE IF NOT EXISTS data ( # CREATE TABLE IF NOT EXISTS data (
id INTEGER PRIMARY KEY, # id INTEGER PRIMARY KEY,
config_id INTEGER DEFAULT NULL, # config_id INTEGER DEFAULT NULL,
generation INTEGER NOT NULL, # generation INTEGER NOT NULL,
fitness REAL, # fitness REAL,
chromosome TEXT # chromosome TEXT
); # );
""" # """
graph: Callable[[Database], Graph] = matplotlib_graph.Matplotlib_Graph
# graph: Callable[[Database], Graph] = matplotlib_graph.Matplotlib_Graph
#============================# #============================#
# Built-in database methods: # # 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: # def save_chromosome(self: Attributes, chromosome: Chromosome) -> None:
"""Saves the current population to the database.""" # """
self.database.insert_current_population(self) # Saves a chromosome to the database.#
# Parameters
def save_chromosome(self: Attributes, chromosome: Chromosome) -> None: # ----------
""" # chromosome : Chromosome
Saves a chromosome to the database. # The chromosome to be saved.
# """
Parameters # self.database.insert_current_chromosome(
---------- # was self.current_generation, chromosome)
chromosome : Chromosome
The chromosome to be saved.
"""
self.database.insert_current_chromosome(self.current_generation, chromosome)
def rand_1_to_10(self: Attributes) -> int: def rand_1_to_10(self: Attributes) -> int:
@ -231,6 +244,7 @@ def get_method(name: str) -> Callable[[Attributes], Callable[..., Any]]:
getter(ga)(...) -> Any getter(ga)(...) -> Any
The getter property, taking in an object and returning the method. The getter property, taking in an object and returning the method.
""" """
def getter(self: Attributes) -> Callable[..., Any]: def getter(self: Attributes) -> Callable[..., Any]:
return self.properties[name] return self.properties[name]
return getter return getter
@ -250,6 +264,7 @@ def set_method(name: str) -> Callable[[Attributes, Optional[Callable[..., Any]]]
setter(ga, method) setter(ga, method)
The setter property, taking in an object and returning nothing. The setter property, taking in an object and returning nothing.
""" """
def setter(self: Attributes, method: Optional[Callable[..., Any]]) -> None: def setter(self: Attributes, method: Optional[Callable[..., Any]]) -> None:
if method is None: if method is None:
pass pass
@ -318,6 +333,7 @@ def get_attr(name: str) -> Callable[[Attributes], Any]:
getter(ga) -> Any getter(ga) -> Any
A getter method which returns an attribute. A getter method which returns an attribute.
""" """
def getter(self: Attributes) -> Any: def getter(self: Attributes) -> Any:
return self.properties[name] return self.properties[name]
return getter return getter
@ -342,6 +358,7 @@ def set_attr(name: str, check: Callable[[Any], bool], error: str) -> Callable[[A
Raises ValueError(error) Raises ValueError(error)
A setter method which saves to an attribute. A setter method which saves to an attribute.
""" """
def setter(self: Attributes, value: Any) -> Any: def setter(self: Attributes, value: Any) -> Any:
if check(value): if check(value):
self.properties[name] = value self.properties[name] = value
@ -356,7 +373,8 @@ for name in static_checks:
name, name,
property( property(
get_attr(name), 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 # Raise error
else: 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: 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 # Raise error
else: 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: def get_database_name(self: Attributes) -> str:
@ -435,8 +455,10 @@ def get_active(self: Attributes) -> Callable[[Attributes], None]:
return self.termination_impl return self.termination_impl
Attributes.max_chromosome_mutation_rate = property(get_max_chromosome_mutation_rate, set_max_chromosome_mutation_rate) Attributes.max_chromosome_mutation_rate = property(
Attributes.min_chromosome_mutation_rate = property(get_min_chromosome_mutation_rate, set_min_chromosome_mutation_rate) 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.database_name = property(get_database_name, set_database_name)
Attributes.graph = property(get_graph, set_graph) Attributes.graph = property(get_graph, set_graph)
Attributes.active = property(get_active) Attributes.active = property(get_active)