Compare commits

..

12 Commits

26 changed files with 794 additions and 632 deletions

10
CITATION.cff Normal file
View File

@ -0,0 +1,10 @@
cff-version: 1.2.0
message: "If you use this software, please cite it as below."
authors:
- family-names: Wilczak
given-names: Daniel
- family-names: Nguyen
given-names: Jack
title: "EasyGA - Genetic Algorithms made Easy"
version: 2.0.4
date-released: 2021-13-05

View File

@ -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 sql_database
from sqlite3 import Error
# from EasyGA.database import sql_database
# from sqlite3 import Error
# Graphing package
from 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,10 +76,11 @@ 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.
else:
@ -93,8 +96,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,7 +107,6 @@ class GA(Attributes):
number_of_generations -= 1
self.current_generation += 1
def update_population(self: GA) -> None:
"""
Updates the population to the new population
@ -113,7 +114,6 @@ class GA(Attributes):
"""
self.population.update()
def reset_run(self: GA) -> None:
"""
Resets a run by re-initializing the
@ -123,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."""
@ -134,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
@ -154,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:
@ -169,14 +167,15 @@ 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.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:
"""
Performs weighted crossover between the best chromosome and
@ -218,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.
@ -252,7 +250,6 @@ 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[
@ -315,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.
@ -339,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
@ -376,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

@ -0,0 +1,2 @@
import EasyGA
from .EasyGA import GA

View File

@ -1,135 +1,37 @@
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 sql_database, matplotlib_graph
from database import sql_database as Database, matplotlib_graph as Graph
#========================================#
# Default methods not defined elsewhere. #
#========================================#
def rand_1_to_10(self: Attributes) -> int:
"""
Default gene_impl, returning a random integer from 1 to 10.
Returns
-------
rand : int
A random integer between 1 and 10, inclusive.
"""
return random.randint(1, 10)
def use_genes(self: Attributes) -> Iterator[Any]:
"""
Default chromosome_impl, generates a chromosome using the gene_impl and chromosome length.
Attributes
----------
gene_impl() -> Any
A gene implementation.
chromosome_length : int
The length of a chromosome.
Returns
-------
chromosome : Iterator[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]]:
"""
Default population_impl, generates a population using the chromosome_impl and population size.
Attributes
----------
chromosome_impl() -> Any
A chromosome implementation.
population_size : int
The size of the population.
Returns
-------
population : Iterator[Iterable[Any]]
Generates the chromosomes for a population.
"""
for _ in range(self.population_size):
yield self.chromosome_impl()
def dist_fitness(self: Attributes, chromosome_1: Chromosome, chromosome_2: Chromosome) -> float:
"""
Measures the distance between two chromosomes based on their fitnesses.
Parameters
----------
chromosome_1, chromosome_2 : Chromosome
Chromosomes being compared.
Returns
-------
dist : float
The distance between the two chromosomes.
"""
return sqrt(abs(chromosome_1.fitness - chromosome_2.fitness))
def simple_linear(self: Attributes, weight: float) -> float:
"""
Returns a random value between 0 and 1, with increased probability
closer towards the side with weight.
Parameters
----------
weight : float
A float between 0 and 1 which determines the output distribution.
Returns
-------
rand : float
A random value between 0 and 1.
"""
rand = random.random()
if rand < weight:
return rand * (1-weight) / weight
else:
return 1 - (1-rand) * weight / (1-weight)
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
@dataclass
class AttributesData:
class Attributes:
"""
Attributes class which stores all attributes in a dataclass.
This includes type-hints/annotations and default values, except for methods.
Additionally gains dataclass features, including an __init__ and __repr__ to avoid boilerplate code.
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__().
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
@ -163,274 +65,400 @@ class AttributesData:
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] = 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
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[[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[[], Any] = None
chromosome_impl: Callable[[], Iterable[Any]] = None
population_impl: Callable[[], Iterable[Iterable[Any]]] = None
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[[float], float] = None
dist: Callable[["Attributes", Chromosome, Chromosome], None] = None
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] = 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=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
# );
# """
database: Database = field(default_factory=sql_database.SQL_Database)
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] = matplotlib_graph.Matplotlib_Graph
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)
class AsMethod:
"""
A descriptor for converting function attributes into bound methods.
To support both inheritance and dataclasses, if the method is None,
then nothing is set.
"""
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:
if method is None:
return
elif not callable(method):
raise TypeError(f"'{self.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]
class Attributes(AttributesData):
"""
The Attributes class inherits default attributes from AttributesData
and implements methods, descriptors, and properties.
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.
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
...
"""
# 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:
# 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(
# was self.current_generation, chromosome)
def rand_1_to_10(self: Attributes) -> int:
"""
Saves a chromosome to the database.
Default gene_impl, returning a random integer from 1 to 10.
Returns
-------
rand : int
A random integer between 1 and 10, inclusive.
"""
return random.randint(1, 10)
def use_genes(self: Attributes) -> Iterable[Any]:
"""
Default chromosome_impl, generates a chromosome using the gene_impl and chromosome length.
Attributes
----------
gene_impl() -> Any
A gene implementation.
chromosome_length : int
The length of a chromosome.
Returns
-------
chromosome : Iterable[Any]
Generates the genes for a chromosome.
"""
for _ in range(self.chromosome_length):
yield self.gene_impl()
def use_chromosomes(self: Attributes) -> Iterable[Any]:
"""
Default population_impl, generates a population using the chromosome_impl and population size.
Attributes
----------
chromosome_impl() -> Any
A chromosome implementation.
population_size : int
The size of the population.
Returns
-------
population : Iterable[Iterable[Any]]
Generates the chromosomes for a population.
"""
for _ in range(self.population_size):
yield self.chromosome_impl()
def dist_fitness(self: Attributes, chromosome_1: Chromosome, chromosome_2: Chromosome) -> float:
"""
Measures the distance between two chromosomes based on their fitnesses.
Parameters
----------
chromosome : Chromosome
The chromosome to be saved.
chromosome_1, chromosome_2 : Chromosome
Chromosomes being compared.
Returns
-------
dist : float
The distance between the two chromosomes.
"""
self.database.insert_current_chromosome(self.current_generation, chromosome)
return sqrt(abs(chromosome_1.fitness - chromosome_2.fitness))
#===========================#
# 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)
def simple_linear(self: Attributes, weight: float) -> float:
"""
Returns a random value between 0 and 1, with increased probability
closer towards the side with weight.
#=============#
# Properties: #
#=============#
Parameters
----------
weight : float
A float between 0 and 1 which determines the output distribution.
@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
-------
rand : float
A random value between 0 and 1.
"""
rand = random.random()
if rand < weight:
return rand * (1-weight) / weight
else:
raise ValueError("Max chromosome mutation rate must be between 0 and 1")
return 1 - (1-rand) * weight / (1-weight)
@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
#==================================================#
# Properties for attributes behaving like methods. #
#==================================================#
def get_method(name: str) -> Callable[[Attributes], Callable[..., Any]]:
"""
Creates a getter method for getting a method from the Attributes class.
Parameters
----------
name : str
The name of the method from Attributes.
Returns
-------
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
def set_method(name: str) -> Callable[[Attributes, Optional[Callable[..., Any]]], None]:
"""
Creates a setter method for setting a method from the Attributes class.
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 setter(self: Attributes, method: Optional[Callable[..., Any]]) -> None:
if method is None:
pass
elif not callable(method):
raise TypeError(f"{name} must be a method i.e. callable.")
elif next(iter(signature(method).parameters), None) in ("self", "ga"):
method = MethodType(method, self)
self.properties[name] = method
return setter
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]:
"""
Creates a getter method for getting an attribute from the Attributes class.
Parameters
----------
name : str
The name of the attribute.
Returns
-------
getter(ga) -> Any
A getter method which returns an attribute.
"""
def getter(self: Attributes) -> Any:
return self.properties[name]
return getter
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.
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.
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("Min chromosome mutation rate must be between 0 and 1")
raise ValueError(error)
return setter
@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.
for name in static_checks:
setattr(
Attributes,
name,
property(
get_attr(name),
set_attr(name, static_checks[name]
["check"], static_checks[name]["error"]),
)
)
#==================#
# Other properties #
#==================#
def get_max_chromosome_mutation_rate(self: Attributes) -> float:
return self._max_chromosome_mutation_rate
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.
vars(self)["database_name"] = name
@property
def graph(self: AttributesProperties) -> Graph:
return vars(self)["graph"]
# Set the attribute for itself
self._database_name = name
@graph.setter
def graph(self: AttributesProperties, graph: Callable[[Database], Graph]) -> None:
vars(self)["graph"] = graph(self.database)
@property
def active(self: AttributesProperties) -> Callable[[], bool]:
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)

View File

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

View File

@ -1,24 +1,21 @@
import sqlite3
from sqlite3 import Error
from tabulate import tabulate
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 = """
self.config_structure = f"""
CREATE TABLE IF NOT EXISTS config (
config_id INTEGER,
attribute_name TEXT,
attribute_value TEXT
);
"""
attribute_value TEXT)"""
#=====================================#
@ -26,56 +23,102 @@ class SQL_Database:
#=====================================#
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:
raise Exception("Error! Cannot create the database connection.")
# Create data table.
if self.conn is not None:
# Create data table
self.create_table(ga.sql_create_data_structure)
# Creare config table.
# Creare config table
self.create_table(self.config_structure)
# Set the config id.
# Set the config id
self.config_id = self.get_current_config()
else:
raise Exception("Error! Cannot create the database connection.")
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.
"""
"""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"
)
query = """
# 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.conn.executemany(query, db_config)
VALUES ('{self.config_id}', '{name}','{value}');""")
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,73 +131,63 @@ class SQL_Database:
def past_runs(self):
"""Show a summerization of the past runs that the user has done."""
query_data = self.query_all("""
query_data = self.query_all(f"""
SELECT config_id,attribute_name,attribute_value
FROM config;
""")
FROM config;""")
table = tabulate(
print(
tabulate(
query_data,
headers = [
'config_id',
'attribute_name',
'attribute_value',
'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;
""")
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};
""")
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;
""")
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;
""")
GROUP by generation;""")
def get_all_config_id(self):
@ -162,8 +195,7 @@ class SQL_Database:
return self.query_all(f"""
SELECT DISTINCT config_id
FROM config;
""")
FROM config;""")
def get_each_generation_number(self,config_id):
"""Get an array of all the generation numbers"""
@ -171,8 +203,7 @@ class SQL_Database:
return self.query_all(f"""
SELECT DISTINCT generation
FROM data
WHERE config_id={config_id};
""")
WHERE config_id={config_id};""")
@ -189,14 +220,12 @@ class SQL_Database:
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 SQL_Database:
]
# 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 SQL_Database:
#=====================================#
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 SQL_Database:
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 SQL_Database:
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 SQL_Database:
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 SQL_Database:
@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 SQL_Database:
# 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 SQL_Database:
# 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

View File

@ -1 +0,0 @@

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import random
# Import all survivor decorators
from decorators import *
from EasyGA.decorators import *
def fill_in_best(ga):

View File

@ -1,5 +1,5 @@
# Import all termination decorators
from decorators import _add_by_fitness_goal, _add_by_generation_goal, _add_by_tolerance_goal
from EasyGA.decorators import _add_by_fitness_goal, _add_by_generation_goal, _add_by_tolerance_goal
@_add_by_fitness_goal
@_add_by_generation_goal

View File

@ -1,5 +1,10 @@
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
@ -10,6 +15,7 @@ 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"""
@ -26,6 +32,7 @@ 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."""
@ -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()
@ -97,6 +107,7 @@ def test_attributes_chromosome_impl_lambdas():
# Evolve the genetic algorithm
ga.evolve()
def test_attributes_chromosome_impl_functions():
# Create the Genetic algorithm
ga = GA()
@ -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)

View File

@ -1,4 +1,4 @@
![](https://raw.githubusercontent.com/danielwilczak101/EasyGA/media/images/easyGA_logo.png)
![](https://raw.githubusercontent.com/danielwilczak101/EasyGA/media/images/banner-logo.png)
# EasyGA - Genetic Algorithms made Easy
@ -15,6 +15,9 @@ pip3 install EasyGA
```
## Getting started with EasyGA(Basic Example):
The goal of the basic example is to get all 5's in the chromosome.
```Python
import EasyGA
@ -30,6 +33,7 @@ ga.print_population()
```
### Output:
```bash
Current Generation : 15
Current population:
@ -46,6 +50,7 @@ Chromosome - 9 [7][2][8][10][3][5][5][8][1][7] / Fitness = 2
```
## Getting started with EasyGA (Password Cracker Example):
```Python
import EasyGA
import random
@ -93,6 +98,7 @@ ga.graph.show()
```
## Ouput:
```
Please enter a word:
EasyGA
@ -112,8 +118,8 @@ Chromosome - 9 [E][a][s][Y][G][A] / Fitness = 5
<img width="500px" src="https://raw.githubusercontent.com/danielwilczak101/EasyGA/media/images/password_cracker_results.png" />
## Issues
We would love to know if your having any issues. Please start a new issue on the [Issues Page](https://github.com/danielwilczak101/EasyGA/issues).
We would love to know if your having any issues. Please start a new issue on the [Issues Page](https://github.com/danielwilczak101/EasyGA/issues).
## Local System Approach
@ -122,6 +128,7 @@ Download the repository to some folder on your computer.
```
https://github.com/danielwilczak101/EasyGA/archive/master.zip
```
Use the run.py file inside the EasyGA folder to run your code. This is a local version of the package.
## Check out our [wiki](https://github.com/danielwilczak101/EasyGA/wiki) for more information.

20
docs/Makefile Normal file
View File

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

35
docs/make.bat Normal file
View File

@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

52
docs/source/conf.py Normal file
View File

@ -0,0 +1,52 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'EasyGA'
copyright = '2021, Daniel Wilczak, Jack Nguyen'
author = 'Daniel Wilczak, Jack Nguyen'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

20
docs/source/index.rst Normal file
View File

@ -0,0 +1,20 @@
.. EasyGA documentation master file, created by
sphinx-quickstart on Thu Aug 26 12:53:45 2021.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to EasyGA's documentation!
==================================
.. toctree::
:maxdepth: 2
:caption: Contents:
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@ -20,9 +20,10 @@ setuptools.setup(
"License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)",
"Operating System :: OS Independent",
],
install_requires = ["matplotlib ~= 3.3.2",
install_requires=[
# "matplotlib ~= 3.3.2",
"pyserial ~= 3.4",
"pytest>=3.7",
# "pytest>=3.7",
"tabulate >=0.8.7"
],
)