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
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]}")

View File

@ -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)