Repository: GheodeAI/metaheuristic-designer (language: python)

Issue:
Added tutorials and customizable Encoding, Initializer and Operator modules 



Discussion / hints:

Fix patch:
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index c0ed9ef..7eda6fa 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -12,4 +12,6 @@ sphinx:
 
 python:
    install:
-   - requirements: docs/requirements.txt
\ No newline at end of file
+   - requirements: docs/requirements.txt
+   - method: pip
+     path: .
\ No newline at end of file
diff --git a/README.md b/README.md
index 36f9b73..d2bd989 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,22 @@
 # Metaheuristic-designer
-Python implementation of a general framework for the design and execution of metaheuristic algorithms.
+[![Documentation Status](https://readthedocs.org/projects/metaheuristic-designer/badge/?version=latest)](https://metaheuristic-designer.readthedocs.io/en/latest/?badge=latest)
 
-Created with the purpose of creating the necesary tools for the analysis and design of metaheuristics
-in a more granular way, choosing algorithm components and adding them to a general search framework.
+This is an object-oriented framework for the development, testing and analysis of metaheuristic optimization algorithms.
 
-Inspired by this article: 
-    Swan, Jerry, et al. "Metaheuristics “in the large”." European Journal of Operational Research 297.2 (2022): 393-406.
-   
-Mostly following the book: 
-    Eiben, Agoston E., and James E. Smith. Introduction to evolutionary computing. Springer-Verlag Berlin Heidelberg, 2015.
+It defines the components of a general evolutionary algorithm and offers some implementations of algorithms along with components
+that can be used directly. Those components will be explained below.
+
+It was inspired by the article [Metaheuristics “in the large”](https://doi.org/10.1016/j.ejor.2021.05.042) that 
+discusses some of the issues in the research on metaheuristic optimization, sugesting the development of libraries for the standarization
+of metaheuristic algorithms.
+
+Most of the design decisions are based on the book [Introduction to evolutionary computing by Eiben, Agoston E.,
+and James E. Smith](https://doi.org/10.1007/978-3-662-44874-8) which is very well expained and is highly recomended to anyone willing to learn about the topic.
+
+This framework doesn't claim to have a high performance, specially since the chosen language is Python and the code has not been 
+designed for speed. This shouldn't really be an issue since the highest amount of time spent in these kind of algorithms
+tends to be in the evaluation of the objective function. If you want to compare an algorithm made with this tool with another
+one that is available by other means, it is recomended to use the number of evaluations of the objective function as a metric instead of execution time.
 
 ## Instalation
 
@@ -34,7 +42,8 @@ pip install metaheuristic-designer
             - PSO: simple particle swarm algorithm.
             - NoSearch: no search is done.
         - "-m" use a memetic search like structure, do local search after mutation.
-    - "examples/exec_basic.py": Evolve an image so that it matches the one given as an input. Recieves mostly the same parameters except for one for indicating the input image:
+    - "examples/exec_basic.py": Evolve an image so that it matches the one given as an input. 
+        - The same parameters as the previous script.
         - "-i \[Image path\]" read the image and evolve a random image into this one.
 
 It is recomended that you create a virtual environment to test the examples.
@@ -63,69 +72,179 @@ To run the tests you need to install nox, to execute the tests use the command
 nox test
 ```
 
-
-## General parameters
-- Stopping conditions:
-    - stop_cond: stopping condition, there are various options
-        - "neval": stop after a given number of evaluations of the fitness function
-        - "ngen": stop after a given number of generations
-        - "time": stop after a fixed amount of execution time (real time, not CPU time)
-        - "fit_target": stop after reaching a desired fitness, accounting for whether we have maximization and minimization
-        - All of the above can be combined with the logical operators 'or' or 'and' to make more complex stopping conditions,
-        an example can be "ngen or time" which will stop when the number of generations or the time limit is reached.
-    - Neval: number of evaluations of the fitness function
-    - Ngen: number of generations
-    - time_limit: execution time limit given in seconds
-    - fit_target: value of the fitness function we want to reach
-- Display options:
-    - verbose: shows a report of the state of the algorithm periodicaly
-    - v_timer: amount of time between each report
-
-## Operators available
-- 1 point cross (1point)
-- 2 point cross (2point)
-- Multipoint cross (Multipoint)
-- Weighted average cross (WeightedAvg)
-- BLXalpha cross (BLXalpha)
-- SBX cross (SBX)
-- Multi-individual cross (Multicross)
-- Xor with random vector (Xor)
-- Cross two vectors with Xor (XorCross)
-- Permute of vector components (Perm)
-- Random mutation of vector components (MutRand/MutNoise)
-- Add noise following a prob. distribution (RandNoise)
-- Approximate population with a prob. distribution and sample on some vector components (MutSample)
-- Approximate population with a prob. distribution and sample (RandSample)
-- Mutation adding Gaussian noise (Gauss)
-- Mutation adding Cauchy noise (Cauchy)
-- Mutation adding Laplace noise (Laplace)
-- Mutation adding Uniform noise (Uniform)
-- Differential evolution operators (DE/best/1, DE/best/2, DE/rand/1, DE/rand/2, DE/current-to-rand/1, DE/current-to-best/1, DE/current-to-pbest/1)
-- Particle swarm algorithm step (PSO)
-- Firefly algorithm step (Firefly)
-- Place fixed vector into the population (Dummy) [only intended for debugging]
-- Generate a completely random solution (Random)
-- Replace some of the components with random values (RandomMask)
-- Apply a custom function (Custom) 
-- Return a copy of the individual (Nothing) 
-
-## Parent selection methods available
-- Tournament (Tournament)
-- Taking the n best parents (Best)
-- Roulette method (Roulette)
-- Stochastic Universal Sampling (SUS)
-- Take all parents (Nothing)
-
-## Survivor selection methods avaliable
-- Elitism (Elitism)
-- Conditional Elitism (CondElitism)
-- Generational (Generational)
-- Compare each parent with its child (One-to-one)
-- Compare each parent with its child, take the child with a probability (Prob-one-to-one)
-- (λ+μ) like method ( (m+n) )
-- (λ,μ) like method ( (m,n) )
-- Selection method in the Coral Reef optimization algorithm (CRO)
-
-
-
+## Implemented components
+This package comes with some already made components that can be used in any algorithm in this framework
+
+### Algorithms
+The algorithms implemented are:
+| Class name | Algorithm | Params | Other info |
+|------------|-----------|--------|------------|
+|NoSearch|Do nothing||For debugging purposes||
+|RandomSearch|Random Search|||
+|HillClimb|Hill climb |||
+|LocalSearch|Local seach |**iters** (number of neighbors to test each time)||
+|SA|Simulated annealing|**iter** (iterations per temperature change), **temp_init** (initial temperature), **alpha** (exponent of the temperature change) ||
+|GA|Genetic algorithm|**pmut** (probability of mutation), **pcross** (probability of crossover)||
+|ES|Evolution strategy|**offspringSize** (number of indiviuals to generate each generation)||
+|HS|Harmony search|**HSM**, **HMCR**, **BW**, **PAR**||
+|PSO|Particle Swarm optimization|**w**,**c1**,**c2**||
+|DE|Differential evolution|||
+|CRO|Coral Reef Optimization|**rho**,**Fb**,**Fd**,**Pd**,**attempts**||
+|CRO_SL|Coral Reef Optimization with substrate layers|**rho**,**Fb**,**Fd**,**Pd**,**attempts**||
+|PCRO_SL|probabilistic Coral Reef Optimization with substrate layers|**rho**,**Fb**,**Fd**,**Pd**,**attempts**||
+|DPCRO_SL|Dynamic probabilistic Coral Reef Optimization with substrate layers|**rho**,**Fb**,**Fd**,**Pd**,**attempts**,**group_subs**,**dyn_method**,**dyn_steps**,**prob_amp**||
+|VND| Variable neighborhood descent|||
+|RVNS| Restricted variable neighborhood search||In progress|
+|VNS| Variable neighborhood search||In progress|
+|CMA_ES| Covariance matrix adaptation - Evolution strategy|| Not implemented yet|
+
+### Survivor selection methods
+These are methods of selecting the individuals to use in future generations.
+
+The methods implemented are:
+| Method name | Algorithm | Params | Other info |
+|-------------|-----------|--------|------------|
+|"Elitism"|Elitism|**amount**||
+|"CondElitism"|Conditional Elitism|**amount**||
+|"nothing" or "generational"|Replace all the parents with their children|| Needs the offspring size to be equal to the population size|
+|"One-to-one" or "HillClimb"|One to one (compare each parent with its child)||Needs the offspring size to be equal to the population size|
+|"Prob-one-to-one" or "ProbHillClimb"|Probabilitisc One to one (with a chance to always choose the child)|**p**|Needs the offspring size to be equal to the population size|
+|"(m+n)" or "keepbest"|(λ+μ), or choosing the λ best individuals taking parents and children|||
+|"(m,n)" or "keepoffspring"|(λ,μ), or taking the best λ children||λ must be smaller than μ|
+|"CRO"|A 2 step survivor selection method used in the CRO algorithm. Each individual attempts to enter the population K times and then a percentage of the worse individuals will be eliminated from the population|**Fd**,**Pd**,**attempts**,**maxPopSize**|Can return a population with a variable number of individuals|
+
+### Parent selection methods
+These are methods of selecting the individuals that will be mutated/perturbed in each generation
+
+The methods implemented are:
+| Method name | Algorithm | Params | Other info |
+|-------------|-----------|--------|------------|
+|"Torunament"|Choose parents by tournament|**amount**, **p**||
+|"Best"| Select the n best individuals|**amount**||
+|"Random"| Take n individuals at random|**amount**||
+|"Roulette"| Perform a selection with the roullette method|**amount**, **method**, **F**||
+|"SUS"| Stochastic universal sampling|**amount**, **method**, **F**||
+|"Nothing"| Take all the individuals from the population||
+
+### Operators
+
+| Class name | Domain | Other info|
+|------------|--------|----|
+|OperatorReal|Real valued vectors||
+|OperatorInt|Integer valued vectors||
+|OperatorBinary|Binary vectors||
+|OperatorPerm|Permutations||
+|OperatorList|Variable length lists||
+|OperatorMeta|Other operators||
+|OperatorLambda|Any|Lets you specify a function as an operator|
+
+The Operators functions available in the operator classes are:
+| Method name | Algorithm | Params | Domains |
+|-------------|-----------|--------|---------|
+|"1point"|1 point crossover||Real, Int, Bin|
+|"2point"|2 point crossover||Real, Int, Bin|
+|"Multipoint"|multipoint crossover||Real, Int, Bin|
+|"WeightedAvg"|Weighted average crossover||Real, Int|
+|"BLXalpha"|BLX-alpha crossover||Real|
+|"Multicross"|multi-individual multipoint crossover||Real, Int, Bin|
+|"XOR"|Bytewise XOR with a random vector||Int|
+|"XORCross"|Bytewise XOR between 2 vectors component by component||Int|
+|"sbx"|SBX crossover||Real|
+|"Perm"|Permutate vector components||Real, Int, Bin, Perm|
+|"Gauss"|Add Gaussian noise||Real, Int|
+|"Laplace"|Add noise following a Laplace distribution||Real, Int|
+|"Cauchy"|Add noise following a Cauchy distribution||Real, Int|
+|"Poisson"|Add noise following a Cauchy distribution||Int|
+|"Uniform"|Add Uniform noise||Real, Int|
+|"MutRand" or "MutNoise"|Add random noise to a number of vector components||Real, Int|
+|"MutSample"|Take a sample from a probability distribution and put it on a number of vector components||Real, Int|
+|"RandNoise"|Add random noise||Real, Int|
+|"RandSample"|Sample from a probability distribution||Real, Int|
+|"DE/Rand/1"|Sample from a probability distribution||Real, Int|
+|"DE/Best/1"|Sample from a probability distribution||Real, Int|
+|"DE/Rand/2"|Sample from a probability distribution||Real, Int|
+|"DE/Best/2"|Sample from a probability distribution||Real, Int|
+|"DE/Current-to-rand/1"|Sample from a probability distribution||Real, Int|
+|"DE/Current-to-best/1"|Sample from a probability distribution||Real, Int|
+|"DE/Current-to-pbest/1"|Sample from a probability distribution||Real, Int|
+|"PSO"|Sample from a probability distribution||Real, Int|
+|"Firefly"|Sample from a probability distribution||Real, Int|
+|"Random"|Sample from a probability distribution||Real, Int, Bin, Perm|
+|"RandomMask"|||Real, Int||
+|"Swap"|||Perm||
+|"Insert"|||Perm||
+|"Scramble"|||Perm||
+|"Perm"|||Real, Int, Bin, Perm||
+|"Invert"|||Perm||
+|"Roll"|||Perm||
+|"PMX"|||Perm||
+|"OrderCross"|||Perm||
+|"branch"|||Operators|
+|"sequence"|||Operators|
+|"split"|||Operators|
+|"pick"|||Operators|
+|"Dummy"|Assing the vector to a predefined value||All|
+|"Custom"|||All|
+|"Nothing"|Do nothing||All|
+
+### Initializers
+Initializers create the initial population that will be evolved in the optimization process.
+
+Some of the implemente Initializers are:
+| Class name | Description | Other info |
+|------------|-----------|------------|
+|DirectInitializer|Initialize the population to a preset list of individuals||
+|SeedProbInitializer|Initializes the population with another initializer and inserts user-specified individuals with a probability||
+|SeedDetermInitializer|Initializes the population with another initializer and inserts a number of user-specified individuals into the population||
+|GaussianVectorInitializer|Initialize individuals with normally distributed vectors||
+|UniformVectorInitializer|Initialize individuals with uniformly random distributed vectors||
+|PermInitializer|Initialize individuals with random permuations||
+|LambdaInitializer|Initialize individuals with a user-defined function||
+
+
+### Encodings
+Specifying the Encoding is optional but can be very helpful for some types of problems.
+
+An encoding will represent each solution differently in the optimization process and the evaluation of the fintess, since most algorithm work only with vectors, but we might need other types of datatypes for our optimization.
+
+Some of the implemented Encodings are:
+| Class name | Encoding | Decoding | Other info |
+|------------|----------|----------|------------|
+|DefaultEncoding|Makes no changes to the input|Makes no changes to the input|
+|TypeCastEncoding|Changes the datatype of the vector from **T1** to **T2**|Changes the datatype of the vectorfrom **T1** to **T2**||
+|MatrixEncoding|Converts a vector into a matrix of size **NxM**|Converts a matrix to a vector with the ```.flatten()``` method||
+|ImageEncoding|Converts a vector into a matrix of size **NxMx1** or **NxMx3**, each component is an unsigned 8bit number|Converts a matrix to a vector with the ```.flatten()``` method|
+|LambdaEncoding|Applies the user-defined ```encode``` function|Applies the user-defined ```decode``` function||
+
+
+### Benchmark functions
+The benchmark functions you can use to test the algorithms are:
+| Class name | Function | Domain | Other info |
+|------------|----------|--------|------------|
+|MaxOnes|||
+|DiophantineEq|||
+|MaxOnesReal|||
+|Sphere|||
+|HighCondElliptic|||
+|BentCigar|||
+|Discus|||
+|Rosenbrock|||
+|Ackley|||
+|Weistrass|||
+|Griewank|||
+|Rastrigin|||
+|ModSchwefel|||
+|Katsuura|||
+|HappyCat|||
+|HGBat|||
+|SumPowell|||
+|N4XinSheYang|||
+|ThreeSAT|||
+|BinKnapsack|||
+|MaxClique|||
+|MaxOnes|||
+|TSP|||
+|ImgApprox|||
+|ImgStd|||
+|ImgEntropy|||
 
diff --git a/docs/_static/README.md b/docs/_static/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/conf.py b/docs/conf.py
index 62524fb..a447185 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -12,18 +12,19 @@
 #
 import os
 import sys
-sys.path.insert(0, os.path.abspath('../src/metaheuristic_designer'))
-sys.path.insert(0, os.path.abspath('./src/metaheuristic_designer'))
+
+sys.path.insert(0, os.path.abspath("../src/metaheuristic_designer"))
+sys.path.insert(0, os.path.abspath("./src/metaheuristic_designer"))
 
 
 # -- Project information -----------------------------------------------------
 
-project = 'metaheuristic-designer'
-copyright = '2023, Eugenio Lorente-Ramos'
-author = 'Eugenio Lorente-Ramos'
+project = "metaheuristic-designer"
+copyright = "2023, Eugenio Lorente-Ramos"
+author = "Eugenio Lorente-Ramos"
 
 # The full version, including alpha/beta/rc tags
-release = '0.1.0'
+release = "0.1.3"
 
 
 # -- General configuration ---------------------------------------------------
@@ -35,29 +36,47 @@
     "sphinx.ext.viewcode",
     "sphinx.ext.autodoc",
     "sphinx.ext.autosummary",
-    "sphinx.ext.napoleon"
+    "sphinx.ext.napoleon",
 ]
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+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 = ['_build', 'Thumbs.db', '.DS_Store']
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
 
 add_module_names = False
 
-autodoc_member_order = 'bysource'
+autodoc_member_order = "bysource"
 
 # -- 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 = 'pydata_sphinx_theme'
+html_theme = "sphinx_material"
+
+html_theme_options = {
+    "nav_title": "metaheuristic-designer docs",
+    "color_primary": "teal",
+    "color_accent": "green",
+    "globaltoc_collapse": True,
+    "globaltoc_includehidden": False,
+    "globaltoc_depth": 1,
+    "nav_links": [
+        {
+            "href": "metaheuristic_designer",
+            "title": "API reference",
+            "internal": "api_ref",
+        }
+    ],
+}
 
 # 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']
\ No newline at end of file
+html_static_path = ["_static"]
+
+html_css_files = ["custom.css"]
diff --git a/docs/index.rst b/docs/index.rst
index 03f125f..d99c984 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -4,7 +4,7 @@
    contain the root `toctree` directive.
 
 Welcome to metaheuristic-designer's documentation!
-======================================
+==================================================
 
 Description
 -----------
diff --git a/docs/metaheuristic_designer.Algorithms.Modern.rst b/docs/metaheuristic_designer.Algorithms.Modern.rst
deleted file mode 100644
index 13fd859..0000000
--- a/docs/metaheuristic_designer.Algorithms.Modern.rst
+++ /dev/null
@@ -1,42 +0,0 @@
-metaheuristic_designer.Algorithms.Modern package
-====================================
-
-CMA\_ES
--------------------------------------------
-
-.. automodule:: metaheuristic_designer.Algorithms.Modern.CMA_ES
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-RVNS
-----------------------------------------
-
-.. automodule:: metaheuristic_designer.Algorithms.Modern.RVNS
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-VND
----------------------------------------
-
-.. automodule:: metaheuristic_designer.Algorithms.Modern.VND
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-VNS
----------------------------------------
-
-.. automodule:: metaheuristic_designer.Algorithms.Modern.VNS
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-Vns neighborhood changes
---------------------------------------------------------------
-
-.. automodule:: metaheuristic_designer.Algorithms.Modern.vns_neighborhood_changes
-   :members:
-   :undoc-members:
-   :show-inheritance:
\ No newline at end of file
diff --git a/docs/metaheuristic_designer.Algorithms.rst b/docs/metaheuristic_designer.Algorithms.rst
deleted file mode 100644
index f5d441a..0000000
--- a/docs/metaheuristic_designer.Algorithms.rst
+++ /dev/null
@@ -1,52 +0,0 @@
-metaheuristic_designer.Algorithms package
-=============================
-
-HillClimb
---------------------------------------
-
-.. autoclass:: metaheuristic_designer.Algorithms.HillClimb
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-LocalSearch
-----------------------------------------
-
-.. autoclass:: metaheuristic_designer.Algorithms.LocalSearch
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-StaticPopulation
----------------------------------------------
-
-.. autoclass:: metaheuristic_designer.Algorithms.StaticPopulation
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-VariablePopulation
------------------------------------------------
-
-.. autoclass:: metaheuristic_designer.Algorithms.VariablePopulation
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-NoSearch
--------------------------------------
-
-.. autoclass:: metaheuristic_designer.Algorithms.NoSearch
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-Subpackages
------------
-
-.. toctree::
-   :maxdepth: 2
-
-   metaheuristic_designer.Algorithms.CRO
-   metaheuristic_designer.Algorithms.Classic
-   metaheuristic_designer.Algorithms.Modern
\ No newline at end of file
diff --git a/docs/metaheuristic_designer.Encodings.rst b/docs/metaheuristic_designer.Encodings.rst
deleted file mode 100644
index 88df6d8..0000000
--- a/docs/metaheuristic_designer.Encodings.rst
+++ /dev/null
@@ -1,42 +0,0 @@
-metaheuristic_designer.Encodings package
-============================
-
-DefaultEncoding
--------------------------------------------
-
-.. autoclass:: metaheuristic_designer.Encodings.DefaultEncoding
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-TypeCastEncoding
---------------------------------------------
-
-.. autoclass:: metaheuristic_designer.Encodings.TypeCastEncoding
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-MatrixEncoding
-------------------------------------------
-
-.. autoclass:: metaheuristic_designer.Encodings.MatrixEncoding
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-ImageEncoding
------------------------------------------
-
-.. autoclass:: metaheuristic_designer.Encodings.ImageEncoding
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-CMAEncoding
----------------------------------------
-
-.. autoclass:: metaheuristic_designer.Encodings.CMAEncoding
-   :members:
-   :undoc-members:
-   :show-inheritance:
diff --git a/docs/metaheuristic_designer.Algorithms.CRO.rst b/docs/metaheuristic_designer.algorithms.CRO.rst
similarity index 61%
rename from docs/metaheuristic_designer.Algorithms.CRO.rst
rename to docs/metaheuristic_designer.algorithms.CRO.rst
index 9d409a7..4158201 100644
--- a/docs/metaheuristic_designer.Algorithms.CRO.rst
+++ b/docs/metaheuristic_designer.algorithms.CRO.rst
@@ -1,10 +1,10 @@
-metaheuristic_designer.Algorithms.CRO package
+metaheuristic_designer.algorithms.CRO package
 =================================
 
 CRO
 ------------------------------------
 
-.. autoclass:: metaheuristic_designer.Algorithms.CRO.CRO
+.. autoclass:: metaheuristic_designer.algorithms.CRO.CRO
    :members:
    :undoc-members:
    :show-inheritance:
@@ -12,7 +12,7 @@ CRO
 CRO\_SL
 ----------------------------------------
 
-.. autoclass:: metaheuristic_designer.Algorithms.CRO.CRO_SL
+.. autoclass:: metaheuristic_designer.algorithms.CRO.CRO_SL
    :members:
    :undoc-members:
    :show-inheritance:
@@ -20,7 +20,7 @@ CRO\_SL
 PCRO\_SL
 -----------------------------------------
 
-.. autoclass:: metaheuristic_designer.Algorithms.CRO.PCRO_SL
+.. autoclass:: metaheuristic_designer.algorithms.CRO.PCRO_SL
    :members:
    :undoc-members:
    :show-inheritance:
@@ -28,7 +28,7 @@ PCRO\_SL
 DPCRO\_SL
 ------------------------------------------
 
-.. autoclass:: metaheuristic_designer.Algorithms.CRO.DPCRO_SL
+.. autoclass:: metaheuristic_designer.algorithms.CRO.DPCRO_SL
    :members:
    :undoc-members:
    :show-inheritance:
\ No newline at end of file
diff --git a/docs/metaheuristic_designer.Algorithms.Classic.rst b/docs/metaheuristic_designer.algorithms.Classic.rst
similarity index 61%
rename from docs/metaheuristic_designer.Algorithms.Classic.rst
rename to docs/metaheuristic_designer.algorithms.Classic.rst
index 1e7f7cd..a6f4a0f 100644
--- a/docs/metaheuristic_designer.Algorithms.Classic.rst
+++ b/docs/metaheuristic_designer.algorithms.Classic.rst
@@ -1,10 +1,10 @@
-metaheuristic_designer.Algorithms.Classic package
+metaheuristic_designer.algorithms.Classic package
 =====================================
 
 ES
 ---------------------------------------
 
-.. autoclass:: metaheuristic_designer.Algorithms.Classic.ES
+.. autoclass:: metaheuristic_designer.algorithms.Classic.ES
    :members:
    :undoc-members:
    :show-inheritance:
@@ -12,7 +12,7 @@ ES
 GA
 ---------------------------------------
 
-.. autoclass:: metaheuristic_designer.Algorithms.Classic.GA
+.. autoclass:: metaheuristic_designer.algorithms.Classic.GA
    :members:
    :undoc-members:
    :show-inheritance:
@@ -20,7 +20,7 @@ GA
 PSO
 ----------------------------------------
 
-.. autoclass:: metaheuristic_designer.Algorithms.Classic.PSO
+.. autoclass:: metaheuristic_designer.algorithms.Classic.PSO
    :members:
    :undoc-members:
    :show-inheritance:
@@ -28,7 +28,7 @@ PSO
 SA
 ---------------------------------------
 
-.. autoclass:: metaheuristic_designer.Algorithms.Classic.SA
+.. autoclass:: metaheuristic_designer.algorithms.Classic.SA
    :members:
    :undoc-members:
    :show-inheritance:
@@ -36,7 +36,7 @@ SA
 DE
 ---------------------------------------
 
-.. autoclass:: metaheuristic_designer.Algorithms.Classic.DE
+.. autoclass:: metaheuristic_designer.algorithms.Classic.DE
    :members:
    :undoc-members:
    :show-inheritance:
@@ -44,7 +44,7 @@ DE
 HS
 ---------------------------------------
 
-.. autoclass:: metaheuristic_designer.Algorithms.Classic.HS
+.. autoclass:: metaheuristic_designer.algorithms.Classic.HS
    :members:
    :undoc-members:
    :show-inheritance:
@@ -52,7 +52,7 @@ HS
 RandomSearch
 -------------------------------------------------
 
-.. autoclass:: metaheuristic_designer.Algorithms.Classic.RandomSearch
+.. autoclass:: metaheuristic_designer.algorithms.Classic.RandomSearch
    :members:
    :undoc-members:
    :show-inheritance:
diff --git a/docs/metaheuristic_designer.algorithms.VNS.rst b/docs/metaheuristic_designer.algorithms.VNS.rst
new file mode 100644
index 0000000..7de985b
--- /dev/null
+++ b/docs/metaheuristic_designer.algorithms.VNS.rst
@@ -0,0 +1,34 @@
+metaheuristic_designer.algorithms.VNS package
+=============================================
+
+RVNS
+----------------------------------------
+
+.. automodule:: metaheuristic_designer.algorithms.VNS.RVNS
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+VND
+---------------------------------------
+
+.. automodule:: metaheuristic_designer.algorithms.VNS.VND
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+VNS
+---------------------------------------
+
+.. automodule:: metaheuristic_designer.algorithms.VNS.VNS
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+VNS neighborhood changes
+--------------------------------------------------------------
+
+.. automodule:: metaheuristic_designer.algorithms.VNS.vns_neighborhood_changes
+   :members:
+   :undoc-members:
+   :show-inheritance:
\ No newline at end of file
diff --git a/docs/metaheuristic_designer.algorithms.rst b/docs/metaheuristic_designer.algorithms.rst
new file mode 100644
index 0000000..c5ae3b5
--- /dev/null
+++ b/docs/metaheuristic_designer.algorithms.rst
@@ -0,0 +1,52 @@
+metaheuristic_designer.algorithms package
+=========================================
+
+HillClimb
+---------
+
+.. autoclass:: metaheuristic_designer.algorithms.HillClimb
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+LocalSearch
+-----------
+
+.. autoclass:: metaheuristic_designer.algorithms.LocalSearch
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+StaticPopulation
+----------------
+
+.. autoclass:: metaheuristic_designer.algorithms.StaticPopulation
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+VariablePopulation
+------------------
+
+.. autoclass:: metaheuristic_designer.algorithms.VariablePopulation
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+NoSearch
+--------
+
+.. autoclass:: metaheuristic_designer.algorithms.NoSearch
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 2
+
+   metaheuristic_designer.algorithms.CRO
+   metaheuristic_designer.algorithms.Classic
+   metaheuristic_designer.algorithms.VNS
\ No newline at end of file
diff --git a/docs/metaheuristic_designer.benchmarks.rst b/docs/metaheuristic_designer.benchmarks.rst
index dac62d6..b3cd7a9 100644
--- a/docs/metaheuristic_designer.benchmarks.rst
+++ b/docs/metaheuristic_designer.benchmarks.rst
@@ -1,5 +1,5 @@
 metaheuristic_designer.benchmarks package
-=============================
+=========================================
 
 metaheuristic_designer.benchmarks.classic_problems module
 ---------------------------------------------------------
diff --git a/docs/metaheuristic_designer.encodings.rst b/docs/metaheuristic_designer.encodings.rst
new file mode 100644
index 0000000..b524f45
--- /dev/null
+++ b/docs/metaheuristic_designer.encodings.rst
@@ -0,0 +1,50 @@
+metaheuristic_designer.encodings package
+========================================
+
+DefaultEncoding
+---------------
+
+.. autoclass:: metaheuristic_designer.encodings.DefaultEncoding
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+TypeCastEncoding
+----------------
+
+.. autoclass:: metaheuristic_designer.encodings.TypeCastEncoding
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+MatrixEncoding
+--------------
+
+.. autoclass:: metaheuristic_designer.encodings.MatrixEncoding
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+ImageEncoding
+-------------
+
+.. autoclass:: metaheuristic_designer.encodings.ImageEncoding
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+CMAEncoding
+-----------
+
+.. autoclass:: metaheuristic_designer.encodings.CMAEncoding
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+LambdaEncoding
+--------------
+
+.. autoclass:: metaheuristic_designer.encodings.LambdaEncoding
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/metaheuristic_designer.Initializers.rst b/docs/metaheuristic_designer.initializers.rst
similarity index 53%
rename from docs/metaheuristic_designer.Initializers.rst
rename to docs/metaheuristic_designer.initializers.rst
index ecfebc0..85559bb 100644
--- a/docs/metaheuristic_designer.Initializers.rst
+++ b/docs/metaheuristic_designer.initializers.rst
@@ -1,10 +1,10 @@
-metaheuristic_designer.Initializers package
-===============================
+metaheuristic_designer.initializers package
+===========================================
 
 DirectInitializer
 ------------------------------------------------
 
-.. automodule:: metaheuristic_designer.Initializers.DirectInitializer
+.. automodule:: metaheuristic_designer.initializers.DirectInitializer
    :members:
    :undoc-members:
    :show-inheritance:
@@ -12,7 +12,7 @@ DirectInitializer
 GaussianInitializer
 --------------------------------------------------
 
-.. automodule:: metaheuristic_designer.Initializers.GaussianInitializer
+.. automodule:: metaheuristic_designer.initializers.GaussianInitializer
    :members:
    :undoc-members:
    :show-inheritance:
@@ -20,7 +20,7 @@ GaussianInitializer
 LambdaInitializer
 ------------------------------------------------
 
-.. automodule:: metaheuristic_designer.Initializers.LambdaInitializer
+.. automodule:: metaheuristic_designer.initializers.LambdaInitializer
    :members:
    :undoc-members:
    :show-inheritance:
@@ -28,7 +28,7 @@ LambdaInitializer
 SeedInitializer
 ----------------------------------------------
 
-.. automodule:: metaheuristic_designer.Initializers.SeedInitializer
+.. automodule:: metaheuristic_designer.initializers.SeedInitializer
    :members:
    :undoc-members:
    :show-inheritance:
@@ -36,7 +36,7 @@ SeedInitializer
 UniformInitializer
 -------------------------------------------------
 
-.. automodule:: metaheuristic_designer.Initializers.UniformInitializer
+.. automodule:: metaheuristic_designer.initializers.UniformInitializer
    :members:
    :undoc-members:
    :show-inheritance:
@@ -44,7 +44,15 @@ UniformInitializer
 PermInitializer
 -------------------------------------------------
 
-.. automodule:: metaheuristic_designer.Initializers.PermInitializer
+.. automodule:: metaheuristic_designer.initializers.PermInitializer
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+LambdaInitializer
+-------------------------------------------------
+
+.. automodule:: metaheuristic_designer.initializers.LambdaInitializer
    :members:
    :undoc-members:
    :show-inheritance:
\ No newline at end of file
diff --git a/docs/metaheuristic_designer.Operators.rst b/docs/metaheuristic_designer.operators.rst
similarity index 55%
rename from docs/metaheuristic_designer.Operators.rst
rename to docs/metaheuristic_designer.operators.rst
index 6eeacd2..0a89ade 100644
--- a/docs/metaheuristic_designer.Operators.rst
+++ b/docs/metaheuristic_designer.operators.rst
@@ -1,10 +1,10 @@
-metaheuristic_designer.Operators package
-============================
+metaheuristic_designer.operators package
+========================================
 
 OperatorReal
 ----------------------------------------
 
-.. automodule:: metaheuristic_designer.Operators.OperatorReal
+.. automodule:: metaheuristic_designer.operators.OperatorReal
    :members:
    :undoc-members:
    :show-inheritance:
@@ -12,7 +12,7 @@ OperatorReal
 OperatorInt
 ---------------------------------------
 
-.. automodule:: metaheuristic_designer.Operators.OperatorInt
+.. automodule:: metaheuristic_designer.operators.OperatorInt
    :members:
    :undoc-members:
    :show-inheritance:
@@ -20,7 +20,7 @@ OperatorInt
 OperatorBinary
 ------------------------------------------
 
-.. automodule:: metaheuristic_designer.Operators.OperatorBinary
+.. automodule:: metaheuristic_designer.operators.OperatorBinary
    :members:
    :undoc-members:
    :show-inheritance:
@@ -28,7 +28,7 @@ OperatorBinary
 OperatorPerm
 ------------------------------------------
 
-.. automodule:: metaheuristic_designer.Operators.OperatorPerm
+.. automodule:: metaheuristic_designer.operators.OperatorPerm
    :members:
    :undoc-members:
    :show-inheritance:
@@ -36,7 +36,7 @@ OperatorPerm
 OperatorList
 ----------------------------------------
 
-.. automodule:: metaheuristic_designer.Operators.OperatorList
+.. automodule:: metaheuristic_designer.operators.OperatorList
    :members:
    :undoc-members:
    :show-inheritance:
@@ -44,7 +44,15 @@ OperatorList
 OperatorMeta
 ----------------------------------------
 
-.. automodule:: metaheuristic_designer.Operators.OperatorMeta
+.. automodule:: metaheuristic_designer.operators.OperatorMeta
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+OperatorLambda
+----------------------------------------
+
+.. automodule:: metaheuristic_designer.operators.OperatorLambda
    :members:
    :undoc-members:
    :show-inheritance:
@@ -52,7 +60,7 @@ OperatorMeta
 List operator functions
 -----------------------------------------------------
 
-.. automodule:: metaheuristic_designer.Operators.list_operator_functions
+.. automodule:: metaheuristic_designer.operators.list_operator_functions
    :members:
    :undoc-members:
    :show-inheritance:
@@ -60,7 +68,7 @@ List operator functions
 Vector operator functions
 -------------------------------------------------------
 
-.. automodule:: metaheuristic_designer.Operators.vector_operator_functions
+.. automodule:: metaheuristic_designer.operators.vector_operator_functions
    :members:
    :undoc-members:
    :show-inheritance:
\ No newline at end of file
diff --git a/docs/metaheuristic_designer.rst b/docs/metaheuristic_designer.rst
index 8936f53..9441802 100644
--- a/docs/metaheuristic_designer.rst
+++ b/docs/metaheuristic_designer.rst
@@ -11,12 +11,12 @@ These subpackages provide implementations of the interfaces needed to define an
 
    metaheuristic_designer.simple
    metaheuristic_designer.benchmarks
-   metaheuristic_designer.SearchMethods
-   metaheuristic_designer.Algorithms
-   metaheuristic_designer.Operators
-   metaheuristic_designer.SelectionMethods
-   metaheuristic_designer.Initializers
-   metaheuristic_designer.Encodings
+   metaheuristic_designer.searchMethods
+   metaheuristic_designer.algorithms
+   metaheuristic_designer.operators
+   metaheuristic_designer.selectionMethods
+   metaheuristic_designer.initializers
+   metaheuristic_designer.encodings
 
 
 metaheuristic_designer.ObjectiveFunc module
diff --git a/docs/metaheuristic_designer.SearchMethods.rst b/docs/metaheuristic_designer.searchMethods.rst
similarity index 52%
rename from docs/metaheuristic_designer.SearchMethods.rst
rename to docs/metaheuristic_designer.searchMethods.rst
index 5ec1220..215f238 100644
--- a/docs/metaheuristic_designer.SearchMethods.rst
+++ b/docs/metaheuristic_designer.searchMethods.rst
@@ -1,10 +1,10 @@
-metaheuristic_designer.SearchMethods package
-================================
+metaheuristic_designer.searchMethods package
+============================================
 
 General Search
 ---------------------------------------------
 
-.. autoclass:: metaheuristic_designer.SearchMethods.GeneralSearch
+.. autoclass:: metaheuristic_designer.searchMethods.GeneralSearch
    :members:
    :undoc-members:
    :show-inheritance:
@@ -12,7 +12,7 @@ General Search
 Memetic Search
 ---------------------------------------------
 
-.. autoclass:: metaheuristic_designer.SearchMethods.MemeticSearch
+.. autoclass:: metaheuristic_designer.searchMethods.MemeticSearch
    :members:
    :undoc-members:
    :show-inheritance:
diff --git a/docs/metaheuristic_designer.SelectionMethods.rst b/docs/metaheuristic_designer.selectionMethods.rst
similarity index 63%
rename from docs/metaheuristic_designer.SelectionMethods.rst
rename to docs/metaheuristic_designer.selectionMethods.rst
index 007a70a..ed706d5 100644
--- a/docs/metaheuristic_designer.SelectionMethods.rst
+++ b/docs/metaheuristic_designer.selectionMethods.rst
@@ -1,10 +1,10 @@
-metaheuristic_designer.SelectionMethods package
-===================================
+metaheuristic_designer.selectionMethods package
+===============================================
 
 ParentSelection
 --------------------------------------------------
 
-.. automodule:: metaheuristic_designer.SelectionMethods.ParentSelection
+.. automodule:: metaheuristic_designer.selectionMethods.ParentSelection
    :members:
    :undoc-members:
    :show-inheritance:
@@ -12,7 +12,7 @@ ParentSelection
 SurvivorSelection
 ----------------------------------------------------
 
-.. automodule:: metaheuristic_designer.SelectionMethods.SurvivorSelection
+.. automodule:: metaheuristic_designer.selectionMethods.SurvivorSelection
    :members:
    :undoc-members:
    :show-inheritance:
@@ -20,7 +20,7 @@ SurvivorSelection
 Parent selection functions
 ---------------------------------------------------------------
 
-.. automodule:: metaheuristic_designer.SelectionMethods.parent_selection_functions
+.. automodule:: metaheuristic_designer.selectionMethods.parent_selection_functions
    :members:
    :undoc-members:
    :show-inheritance:
@@ -28,7 +28,7 @@ Parent selection functions
 Survivor selection functions
 -----------------------------------------------------------------
 
-.. automodule:: metaheuristic_designer.SelectionMethods.survivor_selection_functions
+.. automodule:: metaheuristic_designer.selectionMethods.survivor_selection_functions
    :members:
    :undoc-members:
    :show-inheritance:
\ No newline at end of file
diff --git a/docs/requirements.txt b/docs/requirements.txt
index fb895cf..15a5be1 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -8,4 +8,4 @@ pygame>=2.5.0
 opencv-python>=4.8.0.74
 sphinx>=7.1.1
 networkx>=3.1
-pydata-sphinx-theme>=0.13.3
\ No newline at end of file
+sphinx_material>=0.0.36
diff --git a/examples/exec_basic.py b/examples/exec_basic.py
index 0265f04..2276c24 100644
--- a/examples/exec_basic.py
+++ b/examples/exec_basic.py
@@ -1,17 +1,19 @@
-from metaheuristic_designer import ObjectiveFunc, ParentSelection, SurvivorSelection, ParamScheduler
-from metaheuristic_designer.SearchMethods import GeneralSearch, MemeticSearch
-from metaheuristic_designer.Operators import OperatorReal, OperatorInt, OperatorBinary
-from metaheuristic_designer.Initializers import UniformVectorInitializer
-from metaheuristic_designer.Algorithms import *
+from metaheuristic_designer import ObjectiveFunc, ParamScheduler
+from metaheuristic_designer.searchMethods import GeneralSearch, MemeticSearch
+from metaheuristic_designer.operators import OperatorReal, OperatorInt, OperatorBinary
+from metaheuristic_designer.initializers import UniformVectorInitializer
+from metaheuristic_designer.selectionMethods import ParentSelection, SurvivorSelection
+from metaheuristic_designer.algorithms import *
 
 from metaheuristic_designer.benchmarks import *
 
 import argparse
 
-from copy import copy 
+from copy import copy
 import scipy as sp
 import numpy as np
 
+
 def run_algorithm(alg_name, memetic, save_state):
     params = {
         # "stop_cond": "neval or time_limit or fit_target",
@@ -24,83 +26,114 @@ def run_algorithm(alg_name, memetic, save_state):
         "neval": 3e6,
         "fit_target": 1e-10,
         "patience": 200,
-
         "verbose": True,
-        "v_timer": 0.5
+        "v_timer": 0.5,
     }
 
     # objfunc = Sphere(30, "min")
     # objfunc = Rastrigin(30, "min")
     objfunc = Rosenbrock(30, "min")
     # objfunc = Weierstrass(30, "min")
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=100)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=100
+    )
 
-    
     parent_params = ParamScheduler("Linear", {"amount": 20})
     # select_params = ParamScheduler("Linear")
-    
-    mut_params = ParamScheduler("Linear", {"method":"Cauchy", "F": [0.01, 0.00001]})
+
+    mut_params = ParamScheduler("Linear", {"method": "Cauchy", "F": [0.01, 0.00001]})
     mutation_op = OperatorReal("RandNoise", mut_params)
 
     cross_op = OperatorReal("Multipoint")
 
-    DEparams = {"F":0.7, "Cr":0.8}
+    DEparams = {"F": 0.7, "Cr": 0.8}
     op_list = [
         OperatorReal("DE/rand/1", DEparams),
         OperatorReal("DE/best/2", DEparams),
         OperatorReal("DE/current-to-best/1", DEparams),
-        OperatorReal("DE/current-to-rand/1", DEparams)
+        OperatorReal("DE/current-to-rand/1", DEparams),
     ]
 
-    neighborhood_structures = [OperatorReal("Gauss", {"F": f}, name=f"Gauss(s={f:0.5e})") for f in np.logspace(-6, 0, base=10, num=80)]
-
+    neighborhood_structures = [
+        OperatorReal("Gauss", {"F": f}, name=f"Gauss(s={f:0.5e})")
+        for f in np.logspace(-6, 0, base=10, num=80)
+    ]
 
     parent_sel_op = ParentSelection("Best", parent_params)
     selection_op = SurvivorSelection("(m+n)")
-    
-    mem_select = ParentSelection("Best", {"amount": 5})
-    neihbourhood_op = OperatorReal("RandNoise", {"method":"Cauchy", "F": 0.0002})
-    local_search =  LocalSearch(pop_initializer, neihbourhood_op, params={"iters":10})
 
-    
+    mem_select = ParentSelection("Best", {"amount": 5})
+    neihbourhood_op = OperatorReal("RandNoise", {"method": "Cauchy", "F": 0.0002})
+    local_search = LocalSearch(pop_initializer, neihbourhood_op, params={"iters": 10})
 
     if alg_name == "HillClimb":
         pop_initializer.pop_size = 1
         search_strat = HillClimb(pop_initializer, mutation_op)
     elif alg_name == "LocalSearch":
         pop_initializer.pop_size = 1
-        search_strat = LocalSearch(pop_initializer, mutation_op, params={"iters":20})
+        search_strat = LocalSearch(pop_initializer, mutation_op, params={"iters": 20})
     elif alg_name == "SA":
         pop_initializer.pop_size = 1
-        search_strat = SA(pop_initializer, mutation_op, {"iter":100, "temp_init":1, "alpha":0.997})
+        search_strat = SA(
+            pop_initializer, mutation_op, {"iter": 100, "temp_init": 1, "alpha": 0.997}
+        )
     elif alg_name == "ES":
-        search_strat = ES(pop_initializer, mutation_op, cross_op, parent_sel_op, selection_op, {"offspringSize":150})
+        search_strat = ES(
+            pop_initializer,
+            mutation_op,
+            cross_op,
+            parent_sel_op,
+            selection_op,
+            {"offspringSize": 150},
+        )
     elif alg_name == "GA":
-        search_strat = GA(pop_initializer, mutation_op, cross_op, parent_sel_op, selection_op, {"pcross":0.8, "pmut":0.2})
+        search_strat = GA(
+            pop_initializer,
+            mutation_op,
+            cross_op,
+            parent_sel_op,
+            selection_op,
+            {"pcross": 0.8, "pmut": 0.2},
+        )
     elif alg_name == "HS":
-        search_strat = HS(pop_initializer, {"HMCR":0.8, "BW":0.5, "PAR":0.2})
+        search_strat = HS(pop_initializer, {"HMCR": 0.8, "BW": 0.5, "PAR": 0.2})
     elif alg_name == "DE":
-        search_strat = DE(pop_initializer, OperatorReal("DE/best/1", {"F":0.8, "Cr":0.8}))
+        search_strat = DE(
+            pop_initializer, OperatorReal("DE/best/1", {"F": 0.8, "Cr": 0.8})
+        )
     elif alg_name == "PSO":
-        search_strat = PSO(pop_initializer, {"w":0.7, "c1":1.5, "c2":1.5})
+        search_strat = PSO(pop_initializer, {"w": 0.7, "c1": 1.5, "c2": 1.5})
     elif alg_name == "CRO":
-        search_strat = CRO(pop_initializer, mutation_op, cross_op, {"rho":0.6, "Fb":0.95, "Fd":0.1, "Pd":0.9, "attempts":3})
+        search_strat = CRO(
+            pop_initializer,
+            mutation_op,
+            cross_op,
+            {"rho": 0.6, "Fb": 0.95, "Fd": 0.1, "Pd": 0.9, "attempts": 3},
+        )
     elif alg_name == "CRO_SL":
-        search_strat = CRO_SL(pop_initializer, op_list, {"rho":0.6, "Fb":0.95, "Fd":0.1, "Pd":0.9, "attempts":3})
+        search_strat = CRO_SL(
+            pop_initializer,
+            op_list,
+            {"rho": 0.6, "Fb": 0.95, "Fd": 0.1, "Pd": 0.9, "attempts": 3},
+        )
     elif alg_name == "PCRO_SL":
-        search_strat = PCRO_SL(pop_initializer, op_list, {"rho":0.6, "Fb":0.95, "Fd":0.1, "Pd":0.9, "attempts":3})
+        search_strat = PCRO_SL(
+            pop_initializer,
+            op_list,
+            {"rho": 0.6, "Fb": 0.95, "Fd": 0.1, "Pd": 0.9, "attempts": 3},
+        )
     elif alg_name == "DPCRO_SL":
         search_strat_params = {
-            "rho":0.6,
-            "Fb":0.95,
-            "Fd":0.1,
-            "Pd":0.9,
+            "rho": 0.6,
+            "Fb": 0.95,
+            "Fd": 0.1,
+            "Pd": 0.9,
             "attempts": 3,
             "group_subs": True,
             "dyn_method": "diff",
             "dyn_metric": "best",
             "dyn_steps": 75,
-            "prob_amp": 0.1
+            "prob_amp": 0.1,
         }
         search_strat = DPCRO_SL(pop_initializer, op_list, search_strat_params)
     elif alg_name == "RVNS":
@@ -109,12 +142,24 @@ def run_algorithm(alg_name, memetic, save_state):
         search_strat = VND(pop_initializer, neighborhood_structures)
     elif alg_name == "VNS":
         local_search = LocalSearch(pop_initializer, params={"iters": 100})
-        search_strat = VNS(pop_initializer, neighborhood_structures, local_search, params={"iters": 100, "nchange": "seq"})
+        search_strat = VNS(
+            pop_initializer,
+            neighborhood_structures,
+            local_search,
+            params={"iters": 100, "nchange": "seq"},
+        )
         # local_search = HillClimb(pop_initializer)
         # search_strat = VNS(pop_initializer, neighborhood_structures, local_search, params={"iters": 500})
     elif alg_name == "GVNS":
-        local_search = VND(pop_initializer, neighborhood_structures, params={"nchange": "cyclic"})
-        search_strat = VNS(pop_initializer, neighborhood_structures, local_search, params={"iters": 100, "nchange": "seq"})
+        local_search = VND(
+            pop_initializer, neighborhood_structures, params={"nchange": "cyclic"}
+        )
+        search_strat = VNS(
+            pop_initializer,
+            neighborhood_structures,
+            local_search,
+            params={"iters": 100, "nchange": "seq"},
+        )
         # local_search = HillClimb(pop_initializer)
         # search_strat = VNS(pop_initializer, neighborhood_structures, local_search, params={"iters": 500})
     elif alg_name == "RandomSearch":
@@ -122,14 +167,16 @@ def run_algorithm(alg_name, memetic, save_state):
     elif alg_name == "NoSearch":
         search_strat = NoSearch(pop_initializer)
     else:
-        print(f"Error: Algorithm \"{alg_name}\" doesn't exist.")
+        print(f'Error: Algorithm "{alg_name}" doesn\'t exist.')
         exit()
-    
+
     if memetic:
-        alg = MemeticSearch(objfunc, search_strat, local_search, mem_select, params=params)
+        alg = MemeticSearch(
+            objfunc, search_strat, local_search, mem_select, params=params
+        )
     else:
         alg = GeneralSearch(objfunc, search_strat, params=params)
-    
+
     ind, fit = alg.optimize()
     print(ind)
     alg.display_report(show_plots=True)
@@ -140,9 +187,21 @@ def run_algorithm(alg_name, memetic, save_state):
 
 def main():
     parser = argparse.ArgumentParser()
-    parser.add_argument("-a", "--algorithm", dest='alg', help='Specify an algorithm')
-    parser.add_argument("-m", "--memetic", dest='mem', action="store_true", help='Does local search after mutation')
-    parser.add_argument("-s", "--save-state", dest='save_state', action="store_true", help='Saves the state of the search strategy')
+    parser.add_argument("-a", "--algorithm", dest="alg", help="Specify an algorithm")
+    parser.add_argument(
+        "-m",
+        "--memetic",
+        dest="mem",
+        action="store_true",
+        help="Does local search after mutation",
+    )
+    parser.add_argument(
+        "-s",
+        "--save-state",
+        dest="save_state",
+        action="store_true",
+        help="Saves the state of the search strategy",
+    )
     args = parser.parse_args()
 
     algorithm_name = "ES"
@@ -151,14 +210,15 @@ def main():
 
     if args.alg:
         algorithm_name = args.alg
-    
+
     if args.mem:
         mem = True
-    
+
     if args.save_state:
         save_state = True
-   
-    run_algorithm(alg_name = algorithm_name, memetic=mem, save_state=save_state)
+
+    run_algorithm(alg_name=algorithm_name, memetic=mem, save_state=save_state)
+
 
 if __name__ == "__main__":
-    main()
\ No newline at end of file
+    main()
diff --git a/examples/exec_basic_simple.py b/examples/exec_basic_simple.py
index 77fa2dd..8c9fc2a 100644
--- a/examples/exec_basic_simple.py
+++ b/examples/exec_basic_simple.py
@@ -4,6 +4,7 @@
 # from metaheuristic_designer.Initializers import UniformVectorInitializer
 # from metaheuristic_designer.Algorithms import *
 
+import metaheuristic_designer as mhd
 from metaheuristic_designer.simple import hill_climb
 from metaheuristic_designer.simple import genetic_algorithm
 from metaheuristic_designer.simple import evolution_strategy
@@ -15,10 +16,11 @@
 
 import argparse
 
-from copy import copy 
+from copy import copy
 import scipy as sp
 import numpy as np
 
+
 def run_algorithm(alg_name, memetic, save_state):
     params = {
         # General algorithm params
@@ -31,45 +33,37 @@ def run_algorithm(alg_name, memetic, save_state):
         "neval": 3e6,
         "fit_target": 1e-10,
         "patience": 200,
-
         "verbose": True,
         "v_timer": 0.5,
-
         # General
         "encoding": "real",
         "mutstr": 1e-3,
         # "encoding": "int",
         # "mutstr": 1,
-
         # Population based algorithms
         "pop_size": 100,
-        
         # GA
         "pcross": 0.8,
         "pmut": 0.1,
         "cross": "multipoint",
-
         # ES
         "offspring_size": 150,
-        
         # PSO
         "w": 0.7,
         "c1": 1.5,
         "c2": 1.5,
-
         # DE
         "F": 0.7,
         "Cr": 0.9,
-
         # SA
-        "iter":100,
-        "temp_init":1,
-        "alpha":0.997
+        "iter": 100,
+        "temp_init": 1,
+        "alpha": 0.997,
     }
 
     objfunc = Sphere(30, "min")
     # objfunc = Rastrigin(10, "min")
-    # objfunc = Weierstrass(30, "min")    
+    # objfunc = Weierstrass(30, "min")
 
     if alg_name == "HillClimb":
         alg = hill_climb(objfunc, params=params)
@@ -86,9 +80,8 @@ def run_algorithm(alg_name, memetic, save_state):
     elif alg_name == "RandomSearch":
         alg = random_search(objfunc, params=params)
     else:
-        raise ValueError(f"Error: Algorithm \"{alg_name}\" doesn't exist.")
-    
-    
+        raise ValueError(f'Error: Algorithm "{alg_name}" doesn\'t exist.')
+
     ind, fit = alg.optimize()
     print(ind)
     alg.display_report(show_plots=True)
@@ -99,9 +92,21 @@ def run_algorithm(alg_name, memetic, save_state):
 
 def main():
     parser = argparse.ArgumentParser()
-    parser.add_argument("-a", "--algorithm", dest='alg', help='Specify an algorithm')
-    parser.add_argument("-m", "--memetic", dest='mem', action="store_true", help='Does local search after mutation')
-    parser.add_argument("-s", "--save-state", dest='save_state', action="store_true", help='Saves the state of the search strategy')
+    parser.add_argument("-a", "--algorithm", dest="alg", help="Specify an algorithm")
+    parser.add_argument(
+        "-m",
+        "--memetic",
+        dest="mem",
+        action="store_true",
+        help="Does local search after mutation",
+    )
+    parser.add_argument(
+        "-s",
+        "--save-state",
+        dest="save_state",
+        action="store_true",
+        help="Saves the state of the search strategy",
+    )
     args = parser.parse_args()
 
     algorithm_name = "ES"
@@ -110,14 +115,15 @@ def main():
 
     if args.alg:
         algorithm_name = args.alg
-    
+
     if args.mem:
         mem = True
-    
+
     if args.save_state:
         save_state = True
-   
-    run_algorithm(alg_name = algorithm_name, memetic=mem, save_state=save_state)
+
+    run_algorithm(alg_name=algorithm_name, memetic=mem, save_state=save_state)
+
 
 if __name__ == "__main__":
-    main()
\ No newline at end of file
+    main()
diff --git a/examples/image_deblur.py b/examples/image_deblur.py
index 1054ea4..70bf3ef 100644
--- a/examples/image_deblur.py
+++ b/examples/image_deblur.py
@@ -1,10 +1,15 @@
-from metaheuristic_designer import ObjectiveFunc, ParentSelection, SurvivorSelection, ParamScheduler, Individual
-from metaheuristic_designer.SearchMethods import GeneralSearch, MemeticSearch
-from metaheuristic_designer.Operators import OperatorReal, OperatorInt, OperatorBinary
-from metaheuristic_designer.Algorithms import *
-from metaheuristic_designer.Initializers import *
-from metaheuristic_designer.Encodings import ImageEncoding
-from metaheuristic_designer.benchmarks import * 
+from metaheuristic_designer import (
+    ObjectiveFunc,
+    ParamScheduler,
+    Individual,
+)
+from metaheuristic_designer.searchMethods import GeneralSearch, MemeticSearch
+from metaheuristic_designer.operators import OperatorReal, OperatorInt, OperatorBinary
+from metaheuristic_designer.algorithms import *
+from metaheuristic_designer.initializers import *
+from metaheuristic_designer.selectionMethods import ParentSelection, SurvivorSelection
+from metaheuristic_designer.encodings import ImageEncoding
+from metaheuristic_designer.benchmarks import *
 
 import pygame
 import time
@@ -29,17 +34,24 @@ def decode(self, genotype: np.ndarray) -> np.ndarray:
         image_matrix = np.reshape(genotype, self.shape)
         return skimage.filters.gaussian(image_matrix, channel_axis=-1).astype(np.uint8)
 
+
 def render(image, display_dim, src):
-    texture = cv2.resize(image.transpose([1,0,2]), (display_dim[1], display_dim[0]), interpolation = cv2.INTER_NEAREST)
+    texture = cv2.resize(
+        image.transpose([1, 0, 2]),
+        (display_dim[1], display_dim[0]),
+        interpolation=cv2.INTER_NEAREST,
+    )
     pygame.surfarray.blit_array(src, texture)
     pygame.display.flip()
 
+
 def save_to_image(image, img_name="result.png"):
-    if not os.path.exists('./examples/results/'):
-        os.makedirs('./examples/results/')
-    filename = './examples/results/' + img_name
+    if not os.path.exists("./examples/results/"):
+        os.makedirs("./examples/results/")
+    filename = "./examples/results/" + img_name
     Image.fromarray(image.astype(np.uint8)).save(filename)
 
+
 def run_algorithm(alg_name, img_file_name, memetic):
     params = {
         # General
@@ -48,10 +60,9 @@ def run_algorithm(alg_name, img_file_name, memetic):
         "ngen": 1000,
         "neval": 3e5,
         "fit_target": 0,
-
         "verbose": True,
         # "verbose": False,
-        "v_timer": 0.5
+        "v_timer": 0.5,
     }
 
     display = True
@@ -63,24 +74,39 @@ def run_algorithm(alg_name, img_file_name, memetic):
         src = pygame.display.set_mode(display_dim)
         pygame.display.set_caption("Evo graphics")
 
-    
-
     reference_img = Image.open(img_file_name)
     img_name = img_file_name.split("/")[-1]
     img_name = img_name.split(".")[0]
     objfunc = ImgApprox(image_shape, reference_img, img_name=img_name)
     # objfunc = ImgEntropy(image_shape, 256)
     # objfunc = ImgExperimental(image_shape, reference_img, img_name=img_name)
-    
+
+    objfunc.name = "Image debluring"
+
     deblured_encoding = ImageEncoding(image_shape, color=True)
     encoding = ImageBlurEncoding(image_shape, color=True)
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, encoding=encoding, pop_size=100)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize,
+        objfunc.low_lim,
+        objfunc.up_lim,
+        encoding=encoding,
+        pop_size=100,
+    )
 
-    init_population = [Individual(objfunc, deblured_encoding.encode(np.asarray(reference_img)[:,:,:3].flatten())+np.random.normal(0, 2, np.asarray(reference_img)[:,:,:3].size), encoding=encoding) for i in range(100)]
-    pop_initializer = DirectInitializer(pop_initializer, init_population, encoding=encoding)
+    init_population = [
+        Individual(
+            objfunc,
+            deblured_encoding.encode(np.asarray(reference_img)[:, :, :3].flatten())
+            + np.random.normal(0, 2, np.asarray(reference_img)[:, :, :3].size),
+            encoding=encoding,
+        )
+        for i in range(100)
+    ]
+    pop_initializer = DirectInitializer(
+        pop_initializer, init_population, encoding=encoding
+    )
 
-
-    mutation_op = OperatorReal("MutRand", {"method": "Cauchy", "F":4, "N":6})
+    mutation_op = OperatorReal("MutRand", {"method": "Cauchy", "F": 4, "N": 2})
     cross_op = OperatorReal("Multicross", {"Nindiv": 4})
     parent_sel_op = ParentSelection("Best", {"amount": 15})
     selection_op = SurvivorSelection("Elitism", {"amount": 10})
@@ -90,20 +116,38 @@ def run_algorithm(alg_name, img_file_name, memetic):
         search_strat = HillClimb(pop_initializer, mutation_op)
     elif alg_name == "LocalSearch":
         pop_initializer.pop_size = 1
-        search_strat = LocalSearch(pop_initializer, mutation_op, {"iters":20})
+        search_strat = LocalSearch(pop_initializer, mutation_op, {"iters": 20})
     elif alg_name == "SA":
         pop_initializer.pop_size = 1
-        search_strat = SA(pop_initializer, mutation_op, {"iter":100, "temp_init":2, "alpha":0.998})
+        search_strat = SA(
+            pop_initializer, mutation_op, {"iter": 100, "temp_init": 2, "alpha": 0.998}
+        )
     elif alg_name == "ES":
-        search_strat = ES(pop_initializer, mutation_op, cross_op, parent_sel_op, selection_op, {"offspringSize":150})
+        search_strat = ES(
+            pop_initializer,
+            mutation_op,
+            cross_op,
+            parent_sel_op,
+            selection_op,
+            {"offspringSize": 150},
+        )
     elif alg_name == "GA":
-        search_strat = GA(pop_initializer, mutation_op, cross_op, parent_sel_op, selection_op, {"popSize":100, "pcross":0.8, "pmut":0.4})
+        search_strat = GA(
+            pop_initializer,
+            mutation_op,
+            cross_op,
+            parent_sel_op,
+            selection_op,
+            {"popSize": 100, "pcross": 0.8, "pmut": 0.4},
+        )
     elif alg_name == "HS":
-        search_strat = HS(pop_initializer, {"HMCR":0.8, "BW":0.5, "PAR":0.2})
+        search_strat = HS(pop_initializer, {"HMCR": 0.8, "BW": 0.5, "PAR": 0.2})
     elif alg_name == "DE":
-        search_strat = DE(pop_initializer, OperatorReal("DE/best/1", {"F":0.8, "Cr":0.8}))
+        search_strat = DE(
+            pop_initializer, OperatorReal("DE/best/1", {"F": 0.8, "Cr": 0.8})
+        )
     elif alg_name == "PSO":
-        search_strat = PSO(pop_initializer, {"w":0.7, "c1":1.5, "c2":1.5})
+        search_strat = PSO(pop_initializer, {"w": 0.7, "c1": 1.5, "c2": 1.5})
     elif alg_name == "RandomSearch":
         pop_initializer.pop_size = 1
         search_strat = RandomSearch(pop_initializer)
@@ -111,12 +155,21 @@ def run_algorithm(alg_name, img_file_name, memetic):
         pop_initializer.pop_size = 1
         search_strat = NoSearch(pop_initializer)
     else:
-        print(f"Error: Algorithm \"{alg_name}\" doesn't exist.")
+        print(f'Error: Algorithm "{alg_name}" doesn\'t exist.')
         exit()
-    
+
     if memetic:
-        local_search = LocalSearch(OperatorInt("MutRand", {"method": "Uniform", "Low":-3, "Up":-3, "N":3}), {"iters":10})
-        alg = MemeticSearch(objfunc, search_strat, local_search, ParentSelection("Best", {"amount": 10}), params)
+        local_search = LocalSearch(
+            OperatorInt("MutRand", {"method": "Uniform", "Low": -3, "Up": -3, "N": 3}),
+            {"iters": 10},
+        )
+        alg = MemeticSearch(
+            objfunc,
+            search_strat,
+            local_search,
+            ParentSelection("Best", {"amount": 10}),
+            params,
+        )
     else:
         alg = GeneralSearch(objfunc, search_strat, params)
 
@@ -133,8 +186,8 @@ def run_algorithm(alg_name, img_file_name, memetic):
             for event in pygame.event.get():
                 if event.type == pygame.QUIT:
                     exit(0)
-            src.fill('#000000')
-        
+            src.fill("#000000")
+
         alg.step(time_start=real_time_start)
 
         alg.update(real_time_start, cpu_time_start)
@@ -142,18 +195,18 @@ def run_algorithm(alg_name, img_file_name, memetic):
         if alg.verbose and time.time() - display_timer > alg.v_timer:
             alg.step_info(real_time_start)
             display_timer = time.time()
-        
+
         if display:
             img_flat = alg.best_solution()[0]
             image_blur = encoding.decode(img_flat)
             image_orig = deblured_encoding.decode(img_flat)
-            full_texture = np.hstack([image_orig,image_blur])
+            full_texture = np.hstack([image_orig, image_blur])
             # print(full_texture.shape)
             # full_texture = image_orig
             # full_texture = image_blur
             render(full_texture, display_dim, src)
             pygame.display.update()
-    
+
     alg.real_time_spent = time.time() - real_time_start
     alg.time_spent = time.process_time() - cpu_time_start
     img_flat = alg.best_solution()[0]
@@ -162,19 +215,26 @@ def run_algorithm(alg_name, img_file_name, memetic):
         img_flat = alg.best_solution()[0]
         image_blur = encoding.decode(img_flat)
         image_orig = deblured_encoding.decode(img_flat)
-        full_texture = np.hstack([image_orig,image_blur])
+        full_texture = np.hstack([image_orig, image_blur])
         # print(full_texture.shape)
         # full_texture = image_orig
         # full_texture = image_blur
         render(full_texture, display_dim, src)
     alg.display_report(show_plots=True)
-    save_to_image(image, f"{img_name}_{image_shape[0]}x{image_shape[1]}_deblured_{alg_name}.png")
-    
+    save_to_image(
+        image, f"{img_name}_{image_shape[0]}x{image_shape[1]}_deblured_{alg_name}.png"
+    )
+
+
 def main():
     parser = argparse.ArgumentParser()
-    parser.add_argument("-a", "--algorithm", dest='alg', help='Specify an algorithm')
-    parser.add_argument("-i", "--image", dest='img', help='Specify an image as reference')
-    parser.add_argument("-m", "--memetic", dest='mem', action="store_true", help='Specify an algorithm')
+    parser.add_argument("-a", "--algorithm", dest="alg", help="Specify an algorithm")
+    parser.add_argument(
+        "-i", "--image", dest="img", help="Specify an image as reference"
+    )
+    parser.add_argument(
+        "-m", "--memetic", dest="mem", action="store_true", help="Specify an algorithm"
+    )
     args = parser.parse_args()
 
     algorithm_name = "SA"
@@ -183,15 +243,15 @@ def main():
 
     if args.alg:
         algorithm_name = args.alg
-    
+
     if args.img:
         img_file_name = args.img
-    
+
     if args.mem:
         mem = True
-   
-    run_algorithm(alg_name = algorithm_name, img_file_name = img_file_name, memetic=mem)
+
+    run_algorithm(alg_name=algorithm_name, img_file_name=img_file_name, memetic=mem)
 
 
 if __name__ == "__main__":
-    main()
\ No newline at end of file
+    main()
diff --git a/examples/image_evolution.py b/examples/image_evolution.py
index aee9607..5326f19 100644
--- a/examples/image_evolution.py
+++ b/examples/image_evolution.py
@@ -1,10 +1,11 @@
-from metaheuristic_designer import ObjectiveFunc, ParentSelection, SurvivorSelection, ParamScheduler
-from metaheuristic_designer.SearchMethods import GeneralSearch, MemeticSearch
-from metaheuristic_designer.Operators import OperatorReal, OperatorInt, OperatorBinary
-from metaheuristic_designer.Algorithms import *
-from metaheuristic_designer.Initializers import *
-from metaheuristic_designer.Encodings import ImageEncoding
-from metaheuristic_designer.benchmarks import * 
+from metaheuristic_designer import ObjectiveFunc, ParamScheduler
+from metaheuristic_designer.searchMethods import GeneralSearch, MemeticSearch
+from metaheuristic_designer.operators import OperatorReal, OperatorInt, OperatorBinary
+from metaheuristic_designer.algorithms import *
+from metaheuristic_designer.initializers import *
+from metaheuristic_designer.selectionMethods import ParentSelection, SurvivorSelection
+from metaheuristic_designer.encodings import ImageEncoding
+from metaheuristic_designer.benchmarks import *
 
 import pygame
 import time
@@ -14,6 +15,7 @@
 from copy import deepcopy
 from PIL import Image
 from itertools import product
+
 # import matplotlib
 # matplotlib.use("Gtk3Agg")
 
@@ -21,16 +23,20 @@
 
 
 def render(image, display_dim, src):
-    texture = cv2.resize(image.transpose([1,0,2]), display_dim, interpolation = cv2.INTER_NEAREST)
+    texture = cv2.resize(
+        image.transpose([1, 0, 2]), display_dim, interpolation=cv2.INTER_NEAREST
+    )
     pygame.surfarray.blit_array(src, texture)
     pygame.display.flip()
 
+
 def save_to_image(image, img_name="result.png"):
-    if not os.path.exists('./examples/results/'):
-        os.makedirs('./examples/results/')
-    filename = './examples/results/' + img_name
+    if not os.path.exists("./examples/results/"):
+        os.makedirs("./examples/results/")
+    filename = "./examples/results/" + img_name
     Image.fromarray(image.astype(np.uint8)).save(filename)
 
+
 def run_algorithm(alg_name, img_file_name, memetic):
     params = {
         # General
@@ -42,9 +48,8 @@ def run_algorithm(alg_name, img_file_name, memetic):
         "neval": 3e5,
         "fit_target": 0,
         "patience": 200,
-
         "verbose": True,
-        "v_timer": 0.5
+        "v_timer": 0.5,
     }
 
     display = True
@@ -56,7 +61,6 @@ def run_algorithm(alg_name, img_file_name, memetic):
         src = pygame.display.set_mode(display_dim)
         pygame.display.set_caption("Evo graphics")
 
-    
     reference_img = Image.open(img_file_name)
     img_name = img_file_name.split("/")[-1]
     img_name = img_name.split(".")[0]
@@ -64,70 +68,118 @@ def run_algorithm(alg_name, img_file_name, memetic):
     # objfunc = ImgApprox(image_shape, reference_img, img_name=img_name)
     # objfunc = ImgEntropy(image_shape, 256)
     objfunc = ImgExperimental(image_shape, reference_img, img_name=img_name)
-    
-    encoding = ImageEncoding(image_shape, color=True)
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=100, encoding=encoding)
-
 
+    encoding = ImageEncoding(image_shape, color=True)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize,
+        objfunc.low_lim,
+        objfunc.up_lim,
+        pop_size=100,
+        encoding=encoding,
+    )
 
-    mutation_op = OperatorReal("MutRand", {"method": "Uniform", "Low": -20, "Up":20, "N": 15})
+    mutation_op = OperatorReal(
+        "MutRand", {"method": "Uniform", "Low": -20, "Up": 20, "N": 15}
+    )
     cross_op = OperatorReal("Multipoint")
-    
+
     op_list = [
         OperatorReal("Multipoint"),
-        OperatorReal("MutRand", {"method": "Cauchy", "F": 5, "N": 10}, name="MutCauchy"),
+        OperatorReal(
+            "MutRand", {"method": "Cauchy", "F": 5, "N": 10}, name="MutCauchy"
+        ),
         OperatorReal("MutRand", {"method": "Gauss", "F": 5, "N": 10}, name="MutGauss"),
-        OperatorReal("MutSample", {"method": "Uniform", "Low": 0, "Up": 256, "N": 10}, name="MutUniform")
+        OperatorReal(
+            "MutSample",
+            {"method": "Uniform", "Low": 0, "Up": 256, "N": 10},
+            name="MutUniform",
+        ),
     ]
 
     # n_list = np.flip(np.logspace(0, 12, base=2, num=1000))
     # n_list = np.logspace(4, 12, base=2, num=200)
     n_list = np.logspace(5, 12, base=2, num=200)
 
-    neighborhood_structures = [OperatorReal("MutNoise", {"method": "Uniform", "Low": -10, "Up":10, "N": n}, name=f"UniformSample(N={n:0.0f})") for n in n_list]
-
+    neighborhood_structures = [
+        OperatorReal(
+            "MutNoise",
+            {"method": "Uniform", "Low": -10, "Up": 10, "N": n},
+            name=f"UniformSample(N={n:0.0f})",
+        )
+        for n in n_list
+    ]
 
     parent_sel_op = ParentSelection("Best", {"amount": 15})
     selection_op = SurvivorSelection("Elitism", {"amount": 10})
 
-    
     if alg_name == "HillClimb":
         pop_initializer.pop_size = 1
         search_strat = HillClimb(pop_initializer, mutation_op)
     elif alg_name == "LocalSearch":
         pop_initializer.pop_size = 1
-        search_strat = LocalSearch(pop_initializer, mutation_op, params={"iters":20})
+        search_strat = LocalSearch(pop_initializer, mutation_op, params={"iters": 20})
     elif alg_name == "SA":
         pop_initializer.pop_size = 1
-        search_strat = SA(pop_initializer, mutation_op, {"iter":100, "temp_init":1, "alpha":0.997})
+        search_strat = SA(
+            pop_initializer, mutation_op, {"iter": 100, "temp_init": 1, "alpha": 0.997}
+        )
     elif alg_name == "ES":
-        search_strat = ES(pop_initializer, mutation_op, cross_op, parent_sel_op, selection_op, {"offspringSize":150})
+        search_strat = ES(
+            pop_initializer,
+            mutation_op,
+            cross_op,
+            parent_sel_op,
+            selection_op,
+            {"offspringSize": 150},
+        )
     elif alg_name == "GA":
-        search_strat = GA(pop_initializer, mutation_op, cross_op, parent_sel_op, selection_op, {"pcross":0.8, "pmut":0.4})
+        search_strat = GA(
+            pop_initializer,
+            mutation_op,
+            cross_op,
+            parent_sel_op,
+            selection_op,
+            {"pcross": 0.8, "pmut": 0.4},
+        )
     elif alg_name == "HS":
-        search_strat = HS(pop_initializer, {"HMCR":0.8, "BW":0.5, "PAR":0.2})
+        search_strat = HS(pop_initializer, {"HMCR": 0.8, "BW": 0.5, "PAR": 0.2})
     elif alg_name == "DE":
-        search_strat = DE(pop_initializer, OperatorReal("DE/best/1", {"F":0.8, "Cr":0.8}))
+        search_strat = DE(
+            pop_initializer, OperatorReal("DE/best/1", {"F": 0.8, "Cr": 0.8})
+        )
     elif alg_name == "PSO":
-        search_strat = PSO(pop_initializer, {"w":0.7, "c1":1.5, "c2":1.5})
+        search_strat = PSO(pop_initializer, {"w": 0.7, "c1": 1.5, "c2": 1.5})
     elif alg_name == "CRO":
-        search_strat = CRO(pop_initializer, mutation_op, cross_op, {"rho":0.6, "Fb":0.95, "Fd":0.1, "Pd":0.9, "attempts":3})
+        search_strat = CRO(
+            pop_initializer,
+            mutation_op,
+            cross_op,
+            {"rho": 0.6, "Fb": 0.95, "Fd": 0.1, "Pd": 0.9, "attempts": 3},
+        )
     elif alg_name == "CRO_SL":
-        search_strat = CRO_SL(pop_initializer, op_list, {"rho":0.6, "Fb":0.95, "Fd":0.1, "Pd":0.9, "attempts":3})
+        search_strat = CRO_SL(
+            pop_initializer,
+            op_list,
+            {"rho": 0.6, "Fb": 0.95, "Fd": 0.1, "Pd": 0.9, "attempts": 3},
+        )
     elif alg_name == "PCRO_SL":
-        search_strat = PCRO_SL(pop_initializer, op_list, {"rho":0.6, "Fb":0.95, "Fd":0.1, "Pd":0.9, "attempts":3})
+        search_strat = PCRO_SL(
+            pop_initializer,
+            op_list,
+            {"rho": 0.6, "Fb": 0.95, "Fd": 0.1, "Pd": 0.9, "attempts": 3},
+        )
     elif alg_name == "DPCRO_SL":
         search_strat_params = {
-            "rho":0.6,
-            "Fb":0.95,
-            "Fd":0.1,
-            "Pd":0.9,
+            "rho": 0.6,
+            "Fb": 0.95,
+            "Fd": 0.1,
+            "Pd": 0.9,
             "attempts": 3,
             "group_subs": True,
             "dyn_method": "success",
             "dyn_metric": "best",
             "dyn_steps": 75,
-            "prob_amp": 0.1
+            "prob_amp": 0.1,
         }
         search_strat = DPCRO_SL(pop_initializer, op_list, search_strat_params)
     elif alg_name == "RVNS":
@@ -136,7 +188,12 @@ def run_algorithm(alg_name, img_file_name, memetic):
         search_strat = VND(pop_initializer, neighborhood_structures)
     elif alg_name == "VNS":
         local_search = LocalSearch(pop_initializer, params={"iters": 10})
-        search_strat = VNS(pop_initializer, neighborhood_structures, local_search, params={"iters": 200, "nchange":"pipe"})
+        search_strat = VNS(
+            pop_initializer,
+            neighborhood_structures,
+            local_search,
+            params={"iters": 200, "nchange": "pipe"},
+        )
         # local_search = HillClimb(pop_initializer)
         # search_strat = VNS(pop_initializer, neighborhood_structures, local_search, params={"iters": 500})
     elif alg_name == "RandomSearch":
@@ -146,12 +203,21 @@ def run_algorithm(alg_name, img_file_name, memetic):
         pop_initializer.pop_size = 1
         search_strat = NoSearch(pop_initializer)
     else:
-        print(f"Error: Algorithm \"{alg_name}\" doesn't exist.")
+        print(f'Error: Algorithm "{alg_name}" doesn\'t exist.')
         exit()
-    
+
     if memetic:
-        local_search = LocalSearch(OperatorInt("MutRand", {"method": "Uniform", "Low":-3, "Up":-3, "N":3}), {"iters":10})
-        alg = MemeticSearch(objfunc, search_strat, local_search, ParentSelection("Best", {"amount": 10}), params)
+        local_search = LocalSearch(
+            OperatorInt("MutRand", {"method": "Uniform", "Low": -3, "Up": -3, "N": 3}),
+            {"iters": 10},
+        )
+        alg = MemeticSearch(
+            objfunc,
+            search_strat,
+            local_search,
+            ParentSelection("Best", {"amount": 10}),
+            params,
+        )
     else:
         alg = GeneralSearch(objfunc, search_strat, params)
 
@@ -168,8 +234,8 @@ def run_algorithm(alg_name, img_file_name, memetic):
             for event in pygame.event.get():
                 if event.type == pygame.QUIT:
                     exit(0)
-            src.fill('#000000')
-        
+            src.fill("#000000")
+
         alg.step(time_start=real_time_start)
 
         alg.update(real_time_start, cpu_time_start)
@@ -177,12 +243,12 @@ def run_algorithm(alg_name, img_file_name, memetic):
         if alg.verbose and time.time() - display_timer > alg.v_timer:
             alg.step_info(real_time_start)
             display_timer = time.time()
-        
+
         if display:
             img_flat = alg.best_solution()[0]
             render(encoding.decode(img_flat), display_dim, src)
             pygame.display.update()
-    
+
     alg.real_time_spent = time.time() - real_time_start
     alg.cpu_time_spent = time.process_time() - cpu_time_start
     img_flat = alg.best_solution()[0]
@@ -191,12 +257,17 @@ def run_algorithm(alg_name, img_file_name, memetic):
         render(image, display_dim, src)
     alg.display_report(show_plots=True)
     save_to_image(image, f"{img_name}_{image_shape[0]}x{image_shape[1]}_{alg_name}.png")
-    
+
+
 def main():
     parser = argparse.ArgumentParser()
-    parser.add_argument("-a", "--algorithm", dest='alg', help='Specify an algorithm')
-    parser.add_argument("-i", "--image", dest='img', help='Specify an image as reference')
-    parser.add_argument("-m", "--memetic", dest='mem', action="store_true", help='Specify an algorithm')
+    parser.add_argument("-a", "--algorithm", dest="alg", help="Specify an algorithm")
+    parser.add_argument(
+        "-i", "--image", dest="img", help="Specify an image as reference"
+    )
+    parser.add_argument(
+        "-m", "--memetic", dest="mem", action="store_true", help="Specify an algorithm"
+    )
     args = parser.parse_args()
 
     algorithm_name = "SA"
@@ -205,15 +276,15 @@ def main():
 
     if args.alg:
         algorithm_name = args.alg
-    
+
     if args.img:
         img_file_name = args.img
-    
+
     if args.mem:
         mem = True
-   
-    run_algorithm(alg_name = algorithm_name, img_file_name = img_file_name, memetic=mem)
+
+    run_algorithm(alg_name=algorithm_name, img_file_name=img_file_name, memetic=mem)
 
 
 if __name__ == "__main__":
-    main()
\ No newline at end of file
+    main()
diff --git a/examples/make_new_algorithm.ipynb b/examples/make_new_algorithm.ipynb
deleted file mode 100644
index 7407e39..0000000
--- a/examples/make_new_algorithm.ipynb
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "kernelspec": {
-   "display_name": "Python 3",
-   "language": "python",
-   "name": "python3"
-  },
-  "language_info": {
-   "name": "python",
-   "version": "3.10.9 (main, Dec 19 2022, 17:35:49) [GCC 12.2.0]"
-  },
-  "orig_nbformat": 4,
-  "vscode": {
-   "interpreter": {
-    "hash": "e7370f93d1d0cde622a1f8e1c04877d8463912d04d973331ad4851f04de6915a"
-   }
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/examples/make_new_operators.ipynb b/examples/make_new_operators.ipynb
deleted file mode 100644
index 7407e39..0000000
--- a/examples/make_new_operators.ipynb
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "kernelspec": {
-   "display_name": "Python 3",
-   "language": "python",
-   "name": "python3"
-  },
-  "language_info": {
-   "name": "python",
-   "version": "3.10.9 (main, Dec 19 2022, 17:35:49) [GCC 12.2.0]"
-  },
-  "orig_nbformat": 4,
-  "vscode": {
-   "interpreter": {
-    "hash": "e7370f93d1d0cde622a1f8e1c04877d8463912d04d973331ad4851f04de6915a"
-   }
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/examples/np_problem_example.py b/examples/np_problem_example.py
index 078c404..2cedbe0 100644
--- a/examples/np_problem_example.py
+++ b/examples/np_problem_example.py
@@ -1,19 +1,28 @@
-from metaheuristic_designer import ObjectiveFunc, ParentSelection, SurvivorSelection, ParamScheduler
-from metaheuristic_designer.SearchMethods import GeneralSearch, MemeticSearch
-from metaheuristic_designer.Encodings import TypeCastEncoding
-from metaheuristic_designer.Operators import OperatorReal, OperatorInt, OperatorBinary, OperatorPerm
-from metaheuristic_designer.Initializers import UniformVectorInitializer, PermInitializer
-from metaheuristic_designer.Algorithms import *
-
+from metaheuristic_designer import ObjectiveFunc, ParamScheduler
+from metaheuristic_designer.searchMethods import GeneralSearch, MemeticSearch
+from metaheuristic_designer.encodings import TypeCastEncoding
+from metaheuristic_designer.operators import (
+    OperatorReal,
+    OperatorInt,
+    OperatorBinary,
+    OperatorPerm,
+)
+from metaheuristic_designer.initializers import (
+    UniformVectorInitializer,
+    PermInitializer,
+)
+from metaheuristic_designer.algorithms import *
+from metaheuristic_designer.selectionMethods import ParentSelection, SurvivorSelection
 from metaheuristic_designer.benchmarks import *
 
 import argparse
 
-from copy import copy 
+from copy import copy
 import scipy as sp
 import numpy as np
 import networkx as nx
 
+
 def run_algorithm(alg_name, problem_name, memetic, save_state):
     params = {
         # "stop_cond": "neval or time_limit or fit_target",
@@ -26,37 +35,130 @@ def run_algorithm(alg_name, problem_name, memetic, save_state):
         "neval": 3e6,
         "fit_target": 1e-10,
         "patience": 200,
-
         "verbose": True,
-        "v_timer": 0.5
+        "v_timer": 0.5,
     }
 
     if problem_name == "Knapsack":
         values = [
-            360, 83, 59, 130, 431, 67, 230, 52, 93, 125, 670, 892, 600, 38, 48, 147,
-            78, 256, 63, 17, 120, 164, 432, 35, 92, 110, 22, 42, 50, 323, 514, 28,
-            87, 73, 78, 15, 26, 78, 210, 36, 85, 189, 274, 43, 33, 10, 19, 389, 276,
-            312
+            360,
+            83,
+            59,
+            130,
+            431,
+            67,
+            230,
+            52,
+            93,
+            125,
+            670,
+            892,
+            600,
+            38,
+            48,
+            147,
+            78,
+            256,
+            63,
+            17,
+            120,
+            164,
+            432,
+            35,
+            92,
+            110,
+            22,
+            42,
+            50,
+            323,
+            514,
+            28,
+            87,
+            73,
+            78,
+            15,
+            26,
+            78,
+            210,
+            36,
+            85,
+            189,
+            274,
+            43,
+            33,
+            10,
+            19,
+            389,
+            276,
+            312,
         ]
 
         weights = [
-            7, 0, 30, 22, 80, 94, 11, 81, 70, 64, 59, 18, 0, 36, 3, 8, 15, 42, 9, 0,
-            42, 47, 52, 32, 26, 48, 55, 6, 29, 84, 2, 4, 18, 56, 7, 29, 93, 44, 71,
-            3, 86, 66, 31, 65, 0, 79, 20, 65, 52, 13
+            7,
+            0,
+            30,
+            22,
+            80,
+            94,
+            11,
+            81,
+            70,
+            64,
+            59,
+            18,
+            0,
+            36,
+            3,
+            8,
+            15,
+            42,
+            9,
+            0,
+            42,
+            47,
+            52,
+            32,
+            26,
+            48,
+            55,
+            6,
+            29,
+            84,
+            2,
+            4,
+            18,
+            56,
+            7,
+            29,
+            93,
+            44,
+            71,
+            3,
+            86,
+            66,
+            31,
+            65,
+            0,
+            79,
+            20,
+            65,
+            52,
+            13,
         ]
 
         capacity = 850
 
-        objfunc = Bin_Knapsack_problem(weights, values, capacity)
+        objfunc = BinKnapsack(weights, values, capacity)
 
         encoding = TypeCastEncoding(int, bool)
-        pop_initializer = UniformVectorInitializer(objfunc.vecsize, 0, 1, pop_size=100, dtype=int, encoding=encoding)
+        pop_initializer = UniformVectorInitializer(
+            objfunc.vecsize, 0, 1, pop_size=100, dtype=int, encoding=encoding
+        )
 
-        
         parent_params = ParamScheduler("Linear", {"amount": 20})
         # select_params = ParamScheduler("Linear")
-        
-        mut_params = ParamScheduler("Linear", {"N":[10, 1]})
+
+        mut_params = ParamScheduler("Linear", {"N": [10, 1]})
         mutation_op = OperatorBinary("Flip", mut_params)
 
         cross_op = OperatorBinary("Multipoint")
@@ -65,23 +167,26 @@ def run_algorithm(alg_name, problem_name, memetic, save_state):
             OperatorBinary("Flip", {"N": 2}),
             OperatorBinary("1point"),
             OperatorBinary("Multipoint"),
-            OperatorBinary("MutSample", {"method": "Bernoulli", "p": 0.2, "N":2}),
-            
+            OperatorBinary("MutSample", {"method": "Bernoulli", "p": 0.2, "N": 2}),
         ]
 
-        neighborhood_structures = [OperatorBinary("Flip", {"N": n}, name=f"Flip(n={n})") for n in range(objfunc.vecsize)]
+        neighborhood_structures = [
+            OperatorBinary("Flip", {"N": n}, name=f"Flip(n={n})")
+            for n in range(objfunc.vecsize)
+        ]
 
     elif problem_name == "3SAT":
         objfunc = ThreeSAT.from_cnf_file("./data/sat_examples/uf50-03.cnf")
 
         encoding = TypeCastEncoding(int, bool)
-        pop_initializer = UniformVectorInitializer(objfunc.vecsize, 0, 1, pop_size=100, dtype=int, encoding=encoding)
+        pop_initializer = UniformVectorInitializer(
+            objfunc.vecsize, 0, 1, pop_size=100, dtype=int, encoding=encoding
+        )
 
-        
         parent_params = ParamScheduler("Linear", {"amount": 20})
         # select_params = ParamScheduler("Linear")
-        
-        mut_params = ParamScheduler("Linear", {"N":[10, 1]})
+
+        mut_params = ParamScheduler("Linear", {"N": [10, 1]})
         mutation_op = OperatorBinary("Flip", mut_params)
 
         cross_op = OperatorBinary("Multipoint")
@@ -90,11 +195,13 @@ def run_algorithm(alg_name, problem_name, memetic, save_state):
             OperatorBinary("Flip", {"N": 2}),
             OperatorBinary("1point"),
             OperatorBinary("Multipoint"),
-            OperatorBinary("MutSample", {"method": "Bernoulli", "p": 0.2, "N":2}),
-            
+            OperatorBinary("MutSample", {"method": "Bernoulli", "p": 0.2, "N": 2}),
         ]
 
-        neighborhood_structures = [OperatorBinary("Flip", {"N": n}, name=f"Flip(n={n})") for n in range(objfunc.vecsize)]
+        neighborhood_structures = [
+            OperatorBinary("Flip", {"N": n}, name=f"Flip(n={n})")
+            for n in range(objfunc.vecsize)
+        ]
     elif problem_name == "MaxClique":
         g = nx.gnp_random_graph(100, 0.8)
         adj_mat = nx.adjacency_matrix(g).todense()
@@ -105,11 +212,11 @@ def run_algorithm(alg_name, problem_name, memetic, save_state):
         print()
 
         pop_initializer = PermInitializer(objfunc.vecsize, pop_size=100)
-        
+
         parent_params = ParamScheduler("Linear", {"amount": 20})
         # select_params = ParamScheduler("Linear")
-        
-        mut_params = ParamScheduler("Linear", {"N":[10, 1]})
+
+        mut_params = ParamScheduler("Linear", {"N": [10, 1]})
         mutation_op = OperatorPerm("Perm", mut_params)
 
         # cross_op = OperatorPerm("PMX")
@@ -119,60 +226,93 @@ def run_algorithm(alg_name, problem_name, memetic, save_state):
             OperatorPerm("Swap", mut_params),
             OperatorPerm("Insert", mut_params),
             OperatorPerm("Invert", mut_params),
-            OperatorPerm("PMX")
-            
+            OperatorPerm("PMX"),
         ]
 
-        neighborhood_structures = [OperatorPerm("Flip", {"N": n}, name=f"Flip(n={n})") for n in range(objfunc.vecsize)]
+        neighborhood_structures = [
+            OperatorPerm("Flip", {"N": n}, name=f"Flip(n={n})")
+            for n in range(objfunc.vecsize)
+        ]
     else:
         raise ValueError(f"The problem '{problem_name}' does not exist.")
 
     parent_sel_op = ParentSelection("Best", parent_params)
     selection_op = SurvivorSelection("(m+n)")
-    
-    mem_select = ParentSelection("Best", {"amount": 5})
-    neihbourhood_op = OperatorBinary("RandNoise", {"method":"Cauchy", "F": 0.0002})
-    local_search =  LocalSearch(pop_initializer, neihbourhood_op, params={"iters":10})
 
-    
+    mem_select = ParentSelection("Best", {"amount": 5})
+    neihbourhood_op = OperatorBinary("RandNoise", {"method": "Cauchy", "F": 0.0002})
+    local_search = LocalSearch(pop_initializer, neihbourhood_op, params={"iters": 10})
 
     if alg_name == "HillClimb":
         pop_initializer.pop_size = 1
         search_strat = HillClimb(pop_initializer, mutation_op)
     elif alg_name == "LocalSearch":
         pop_initializer.pop_size = 1
-        search_strat = LocalSearch(pop_initializer, mutation_op, params={"iters":20})
+        search_strat = LocalSearch(pop_initializer, mutation_op, params={"iters": 20})
     elif alg_name == "SA":
         pop_initializer.pop_size = 1
-        search_strat = SA(pop_initializer, mutation_op, params={"iter":100, "temp_init":1, "alpha":0.99})
+        search_strat = SA(
+            pop_initializer,
+            mutation_op,
+            params={"iter": 100, "temp_init": 1, "alpha": 0.99},
+        )
     elif alg_name == "ES":
-        search_strat = ES(pop_initializer, mutation_op, cross_op, parent_sel_op, selection_op, params={"offspringSize":150})
+        search_strat = ES(
+            pop_initializer,
+            mutation_op,
+            cross_op,
+            parent_sel_op,
+            selection_op,
+            params={"offspringSize": 150},
+        )
     elif alg_name == "GA":
-        search_strat = GA(pop_initializer, mutation_op, cross_op, parent_sel_op, selection_op, params={"pcross":0.8, "pmut":0.2})
+        search_strat = GA(
+            pop_initializer,
+            mutation_op,
+            cross_op,
+            parent_sel_op,
+            selection_op,
+            params={"pcross": 0.8, "pmut": 0.2},
+        )
     elif alg_name == "HS":
-        search_strat = HS(pop_initializer, params={"HMCR":0.8, "BW":0.5, "PAR":0.2})
+        search_strat = HS(pop_initializer, params={"HMCR": 0.8, "BW": 0.5, "PAR": 0.2})
     elif alg_name == "DE":
-        search_strat = DE(pop_initializer, OperatorReal("DE/best/1", params={"F":0.8, "Cr":0.8}))
+        search_strat = DE(
+            pop_initializer, OperatorReal("DE/best/1", params={"F": 0.8, "Cr": 0.8})
+        )
     elif alg_name == "PSO":
-        search_strat = PSO(pop_initializer, params={"w":0.7, "c1":1.5, "c2":1.5})
+        search_strat = PSO(pop_initializer, params={"w": 0.7, "c1": 1.5, "c2": 1.5})
     elif alg_name == "CRO":
-        search_strat = CRO(pop_initializer, mutation_op, cross_op, params={"rho":0.6, "Fb":0.95, "Fd":0.1, "Pd":0.9, "attempts":3})
+        search_strat = CRO(
+            pop_initializer,
+            mutation_op,
+            cross_op,
+            params={"rho": 0.6, "Fb": 0.95, "Fd": 0.1, "Pd": 0.9, "attempts": 3},
+        )
     elif alg_name == "CRO_SL":
-        search_strat = CRO_SL(pop_initializer, op_list, params={"rho":0.6, "Fb":0.95, "Fd":0.1, "Pd":0.9, "attempts":3})
+        search_strat = CRO_SL(
+            pop_initializer,
+            op_list,
+            params={"rho": 0.6, "Fb": 0.95, "Fd": 0.1, "Pd": 0.9, "attempts": 3},
+        )
     elif alg_name == "PCRO_SL":
-        search_strat = PCRO_SL(pop_initializer, op_list, params={"rho":0.6, "Fb":0.95, "Fd":0.1, "Pd":0.9, "attempts":3})
+        search_strat = PCRO_SL(
+            pop_initializer,
+            op_list,
+            params={"rho": 0.6, "Fb": 0.95, "Fd": 0.1, "Pd": 0.9, "attempts": 3},
+        )
     elif alg_name == "DPCRO_SL":
         search_strat_params = {
-            "rho":0.6,
-            "Fb":0.95,
-            "Fd":0.1,
-            "Pd":0.9,
+            "rho": 0.6,
+            "Fb": 0.95,
+            "Fd": 0.1,
+            "Pd": 0.9,
             "attempts": 3,
             "group_subs": True,
             "dyn_method": "diff",
             "dyn_metric": "best",
             "dyn_steps": 75,
-            "prob_amp": 0.1
+            "prob_amp": 0.1,
         }
         search_strat = DPCRO_SL(pop_initializer, op_list, search_strat_params)
     elif alg_name == "RVNS":
@@ -181,25 +321,39 @@ def run_algorithm(alg_name, problem_name, memetic, save_state):
         search_strat = VND(pop_initializer, neighborhood_structures)
     elif alg_name == "VNS":
         local_search = LocalSearch(pop_initializer, params={"iters": 100})
-        search_strat = VNS(pop_initializer, neighborhood_structures, local_search, params={"iters": 100, "nchange": "seq"})
+        search_strat = VNS(
+            pop_initializer,
+            neighborhood_structures,
+            local_search,
+            params={"iters": 100, "nchange": "seq"},
+        )
         # local_search = HillClimb(pop_initializer)
         # search_strat = VNS(pop_initializer, neighborhood_structures, local_search, params={"iters": 500})
     elif alg_name == "GVNS":
-        local_search = VND(pop_initializer, neighborhood_structures, params={"nchange": "cyclic"})
-        search_strat = VNS(pop_initializer, neighborhood_structures, local_search, params={"iters": 100, "nchange": "seq"})
+        local_search = VND(
+            pop_initializer, neighborhood_structures, params={"nchange": "cyclic"}
+        )
+        search_strat = VNS(
+            pop_initializer,
+            neighborhood_structures,
+            local_search,
+            params={"iters": 100, "nchange": "seq"},
+        )
     elif alg_name == "RandomSearch":
         search_strat = RandomSearch(pop_initializer)
     elif alg_name == "NoSearch":
         search_strat = NoSearch(pop_initializer)
     else:
-        print(f"Error: Algorithm \"{alg_name}\" doesn't exist.")
+        print(f'Error: Algorithm "{alg_name}" doesn\'t exist.')
         exit()
-    
+
     if memetic:
-        alg = MemeticSearch(objfunc, search_strat, local_search, mem_select, params=params)
+        alg = MemeticSearch(
+            objfunc, search_strat, local_search, mem_select, params=params
+        )
     else:
         alg = GeneralSearch(objfunc, search_strat, params=params)
-    
+
     ind, fit = alg.optimize()
     print(ind)
     alg.display_report(show_plots=True)
@@ -210,10 +364,24 @@ def run_algorithm(alg_name, problem_name, memetic, save_state):
 
 def main():
     parser = argparse.ArgumentParser()
-    parser.add_argument("-a", "--algorithm", dest='alg', help='Specify an algorithm')
-    parser.add_argument("-p", "--problem", dest='prob', help='Specify an problem to solve')
-    parser.add_argument("-m", "--memetic", dest='mem', action="store_true", help='Does local search after mutation')
-    parser.add_argument("-s", "--save-state", dest='save_state', action="store_true", help='Saves the state of the search strategy')
+    parser.add_argument("-a", "--algorithm", dest="alg", help="Specify an algorithm")
+    parser.add_argument(
+        "-p", "--problem", dest="prob", help="Specify an problem to solve"
+    )
+    parser.add_argument(
+        "-m",
+        "--memetic",
+        dest="mem",
+        action="store_true",
+        help="Does local search after mutation",
+    )
+    parser.add_argument(
+        "-s",
+        "--save-state",
+        dest="save_state",
+        action="store_true",
+        help="Saves the state of the search strategy",
+    )
     args = parser.parse_args()
 
     algorithm_name = "GA"
@@ -221,20 +389,25 @@ def main():
     mem = False
     save_state = False
 
-
     if args.alg:
         algorithm_name = args.alg
-    
+
     if args.prob:
         problem_name = args.prob
-    
+
     if args.mem:
         mem = True
-    
+
     if args.save_state:
         save_state = True
-   
-    run_algorithm(alg_name = algorithm_name, problem_name=problem_name, memetic=mem, save_state=save_state)
+
+    run_algorithm(
+        alg_name=algorithm_name,
+        problem_name=problem_name,
+        memetic=mem,
+        save_state=save_state,
+    )
+
 
 if __name__ == "__main__":
-    main()
\ No newline at end of file
+    main()
diff --git a/examples/np_problem_example_simple.py b/examples/np_problem_example_simple.py
index dfdefcf..2015d63 100644
--- a/examples/np_problem_example_simple.py
+++ b/examples/np_problem_example_simple.py
@@ -1,9 +1,3 @@
-# from metaheuristic_designer import ObjectiveFunc, ParentSelection, SurvivorSelection, ParamScheduler
-# from metaheuristic_designer.SearchMethods import GeneralSearch, MemeticSearch
-# from metaheuristic_designer.Operators import OperatorReal, OperatorInt, OperatorBinary
-# from metaheuristic_designer.Initializers import UniformVectorInitializer
-# from metaheuristic_designer.Algorithms import *
-
 from metaheuristic_designer.simple import hill_climb
 from metaheuristic_designer.simple import genetic_algorithm
 from metaheuristic_designer.simple import evolution_strategy
@@ -15,10 +9,11 @@
 
 import argparse
 
-from copy import copy 
+from copy import copy
 import scipy as sp
 import numpy as np
 
+
 def run_algorithm(alg_name, memetic, save_state):
     params = {
         # General algorithm params
@@ -32,58 +27,143 @@ def run_algorithm(alg_name, memetic, save_state):
         "neval": 3e6,
         "fit_target": 1e-10,
         "patience": 200,
-
         "verbose": True,
         "v_timer": 0.5,
-
         # General
         "encoding": "bin",
         "mutstr": 1,
         # "encoding": "int",
         # "mutstr": 1,
-
         # Population based algorithms
         "pop_size": 100,
-        
         # GA
         "pcross": 0.8,
         "pmut": 1,
         "cross": "multipoint",
-
         # ES
         "offspring_size": 150,
-        
         # PSO
         "w": 0.7,
         "c1": 1.5,
         "c2": 1.5,
-
         # DE
         "F": 0.7,
         "Cr": 0.9,
-
         # SA
-        "iter":100,
-        "temp_init":1,
-        "alpha":0.997
+        "iter": 100,
+        "temp_init": 1,
+        "alpha": 0.997,
     }
 
     values = [
-        360, 83, 59, 130, 431, 67, 230, 52, 93, 125, 670, 892, 600, 38, 48, 147,
-        78, 256, 63, 17, 120, 164, 432, 35, 92, 110, 22, 42, 50, 323, 514, 28,
-        87, 73, 78, 15, 26, 78, 210, 36, 85, 189, 274, 43, 33, 10, 19, 389, 276,
-        312
+        360,
+        83,
+        59,
+        130,
+        431,
+        67,
+        230,
+        52,
+        93,
+        125,
+        670,
+        892,
+        600,
+        38,
+        48,
+        147,
+        78,
+        256,
+        63,
+        17,
+        120,
+        164,
+        432,
+        35,
+        92,
+        110,
+        22,
+        42,
+        50,
+        323,
+        514,
+        28,
+        87,
+        73,
+        78,
+        15,
+        26,
+        78,
+        210,
+        36,
+        85,
+        189,
+        274,
+        43,
+        33,
+        10,
+        19,
+        389,
+        276,
+        312,
     ]
 
     weights = [
-        7, 0, 30, 22, 80, 94, 11, 81, 70, 64, 59, 18, 0, 36, 3, 8, 15, 42, 9, 0,
-        42, 47, 52, 32, 26, 48, 55, 6, 29, 84, 2, 4, 18, 56, 7, 29, 93, 44, 71,
-        3, 86, 66, 31, 65, 0, 79, 20, 65, 52, 13
+        7,
+        0,
+        30,
+        22,
+        80,
+        94,
+        11,
+        81,
+        70,
+        64,
+        59,
+        18,
+        0,
+        36,
+        3,
+        8,
+        15,
+        42,
+        9,
+        0,
+        42,
+        47,
+        52,
+        32,
+        26,
+        48,
+        55,
+        6,
+        29,
+        84,
+        2,
+        4,
+        18,
+        56,
+        7,
+        29,
+        93,
+        44,
+        71,
+        3,
+        86,
+        66,
+        31,
+        65,
+        0,
+        79,
+        20,
+        65,
+        52,
+        13,
     ]
 
     capacity = 850
 
-    objfunc = Bin_Knapsack_problem(weights, values, capacity)  
+    objfunc = BinKnapsack(weights, values, capacity)
 
     if alg_name == "HillClimb":
         alg = hill_climb(objfunc, params=params)
@@ -100,9 +180,8 @@ def run_algorithm(alg_name, memetic, save_state):
     elif alg_name == "RandomSearch":
         alg = random_search(objfunc, params=params)
     else:
-        raise ValueError(f"Error: Algorithm \"{alg_name}\" doesn't exist.")
-    
-    
+        raise ValueError(f'Error: Algorithm "{alg_name}" doesn\'t exist.')
+
     ind, fit = alg.optimize()
     print(ind)
     alg.display_report(show_plots=True)
@@ -113,9 +192,21 @@ def run_algorithm(alg_name, memetic, save_state):
 
 def main():
     parser = argparse.ArgumentParser()
-    parser.add_argument("-a", "--algorithm", dest='alg', help='Specify an algorithm')
-    parser.add_argument("-m", "--memetic", dest='mem', action="store_true", help='Does local search after mutation')
-    parser.add_argument("-s", "--save-state", dest='save_state', action="store_true", help='Saves the state of the search strategy')
+    parser.add_argument("-a", "--algorithm", dest="alg", help="Specify an algorithm")
+    parser.add_argument(
+        "-m",
+        "--memetic",
+        dest="mem",
+        action="store_true",
+        help="Does local search after mutation",
+    )
+    parser.add_argument(
+        "-s",
+        "--save-state",
+        dest="save_state",
+        action="store_true",
+        help="Saves the state of the search strategy",
+    )
     args = parser.parse_args()
 
     algorithm_name = "ES"
@@ -124,14 +215,15 @@ def main():
 
     if args.alg:
         algorithm_name = args.alg
-    
+
     if args.mem:
         mem = True
-    
+
     if args.save_state:
         save_state = True
-   
-    run_algorithm(alg_name = algorithm_name, memetic=mem, save_state=save_state)
+
+    run_algorithm(alg_name=algorithm_name, memetic=mem, save_state=save_state)
+
 
 if __name__ == "__main__":
-    main()
\ No newline at end of file
+    main()
diff --git a/examples/obj_from_lambda.py b/examples/obj_from_lambda.py
index 54aae30..05a95df 100644
--- a/examples/obj_from_lambda.py
+++ b/examples/obj_from_lambda.py
@@ -1,9 +1,10 @@
 import numpy as np
 
 from metaheuristic_designer import ObjectiveFromLambda, ParamScheduler
-from metaheuristic_designer.SearchMethods import GeneralSearch
-from metaheuristic_designer.Operators import OperatorReal
-from metaheuristic_designer.Algorithms import *
+from metaheuristic_designer.searchMethods import GeneralSearch
+from metaheuristic_designer.operators import OperatorReal
+from metaheuristic_designer.initializers import UniformVectorInitializer
+from metaheuristic_designer.algorithms import *
 
 
 def func(vector):
@@ -18,26 +19,26 @@ def run_algorithm():
         "ngen": 1000,
         "neval": 6e5,
         "fit_target": 1e-30,
-
         "verbose": True,
-        "v_timer": 0.5
+        "v_timer": 0.5,
     }
 
     objfunc = ObjectiveFromLambda(func, 10, "min")
 
-    mutation_op = OperatorReal("RandNoise", {"method":"Cauchy", "F": 0.0001})
+    pop_init = UniformVectorInitializer(
+        objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=1
+    )
+
+    mutation_op = OperatorReal("RandNoise", {"method": "Cauchy", "F": 0.0001})
+
+    search_strat = HillClimb(pop_init, mutation_op)
 
-    search_strat = HillClimb(mutation_op)
-    
     alg = GeneralSearch(objfunc, search_strat, params=params)
-    
+
     ind, fit = alg.optimize()
     print(ind)
     alg.display_report()
 
 
-def main():
-    run_algorithm()
-
 if __name__ == "__main__":
-    main()
\ No newline at end of file
+    run_algorithm()
diff --git a/noxfile.py b/noxfile.py
index c9f6b98..01b01b1 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -1,10 +1,13 @@
 import nox
 
-nox.options.sessions = "test",
+nox.options.sessions = ("test",)
 locations = "noxfile.py"
 
+
 @nox.session
 def test(session):
-    session.install('.')
-    session.install('pytest', 'coverage')
-    session.run('coverage', 'run', '--data-file', '.coverage.nox', '--parallel', '-m', 'pytest')
\ No newline at end of file
+    session.install(".")
+    session.install("pytest", "coverage")
+    session.run(
+        "coverage", "run", "--data-file", ".coverage.nox", "--parallel", "-m", "pytest"
+    )
diff --git a/pyproject.toml b/pyproject.toml
index 78728a7..e418085 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
 
 [project]
 name = "metaheuristic-designer"
-version = "0.1.2"
+version = "0.1.3"
 authors = [
     {name = "eugenioLR", email = "eugeniolorente0@gmail.com"}
 ]
@@ -14,11 +14,11 @@ requires-python = ">=3.10"
 keywords = ["optization", "genetic-algorithm", "evolutionary-algorithms", "metaheuristics"]
 license = {text = "LICENSE"}
 dependencies = [
-    "matplotlib >= 3.5",
-    "numpy >= 1.21",
-    "scipy >= 1.8",
-    "pyparsing >= 3.0",
-    "scikit-image >= 0.21",
+    "matplotlib>=3.5",
+    "numpy>=1.21",
+    "scipy>=1.8",
+    "pyparsing>=3.0",
+    "scikit-image>=0.21",
 ]
 
 [project.urls]
@@ -26,18 +26,19 @@ Homepage = "https://github.com/eugenioLR/metaheuristic-designer"
 
 [project.optional-dependencies]
 dev = [
-    "pytest >=7.4.0",
-    "coverage >=7.3.1",
-    "pydata-sphinx-theme >=0.13.3",
-    "sphinx >=7.1.1",
-    "pygame >=2.5.0",    
-    "networkx >=3.1"
-    "opencv-python >= 4.8"
+    "nox>=2023.4.22",
+    "pytest>=7.4.0",
+    "coverage>=7.3.1",
+    "sphinx_material>=0.0.36",
+    "sphinx>=7.1.1",
+    "pygame>=2.5.0",    
+    "networkx>=3.1",
+    "opencv-python>=4.8"
 ]
 
 examples = [
-    "pygame >=2.5.0",    
-    "networkx >=3.1",
-    "opencv-python"
+    "pygame>=2.5.0",    
+    "networkx>=3.1",
+    "opencv-python>=4.8"
 ]
 
diff --git a/src/metaheuristic_designer/Algorithm.py b/src/metaheuristic_designer/Algorithm.py
index 888ad84..ef756d1 100644
--- a/src/metaheuristic_designer/Algorithm.py
+++ b/src/metaheuristic_designer/Algorithm.py
@@ -2,10 +2,11 @@
 from abc import ABC, abstractmethod
 from .Individual import Individual
 from .ParamScheduler import ParamScheduler
-from .SelectionMethods import ParentSelection, SurvivorSelection
+from .selectionMethods import ParentSelection, SurvivorSelection
 from .Operator import Operator
 import time
 
+
 class Algorithm(ABC):
     """
     This is the class that defines how the optimization will be carried out.
@@ -20,7 +21,12 @@ class Algorithm(ABC):
         The name that will be displayed for this algorithm in the reports.
     """
 
-    def __init__(self, pop_init: Initializer, params: Union[ParamScheduler, dict] = None, name: str = "some algorithm"):
+    def __init__(
+        self,
+        pop_init: Initializer,
+        params: Union[ParamScheduler, dict] = None,
+        name: str = "some algorithm",
+    ):
         """
         Constructor of the Algorithm class
         """
@@ -29,7 +35,7 @@ def __init__(self, pop_init: Initializer, params: Union[ParamScheduler, dict] =
         self.pop_init = pop_init
 
         self.population = None
-        
+
         self.best = None
 
         self.param_scheduler = None
@@ -43,13 +49,12 @@ def __init__(self, pop_init: Initializer, params: Union[ParamScheduler, dict] =
 
         self._find_operator_attributes()
 
-    
     def _find_operator_attributes(self):
         """
         Saves the attributes that represent operators or other relevant information
         about the algorithm.
         """
-        
+
         attr_dict = vars(self).copy()
 
         self.parent_sel = []
@@ -57,7 +62,6 @@ def _find_operator_attributes(self):
         self.surv_sel = []
 
         for var_key in attr_dict:
-            
             attr = attr_dict[var_key]
 
             if attr:
@@ -72,11 +76,11 @@ def _find_operator_attributes(self):
                 # We have a survivor selection method
                 if isinstance(attr, SurvivorSelection):
                     self.surv_sel.append(attr)
-                
+
                 # We have a list of operators
                 if isinstance(attr, list) and isinstance(attr[0], Operator):
                     self.operators += attr
-    
+
     @property
     def pop_size(self):
         """
@@ -110,12 +114,14 @@ def initialize(self, objfunc: ObjectiveFunc):
         objfunc: ObjectiveFunc
             Objective function to be optimized.
         """
-        
+
         self.population = self.pop_init.generate_population(objfunc)
 
         self.best = max(self.population, key=lambda x: x.fitness)
 
-    def select_parents(self, population: List[Individual], progress: float = 0, history: List[float] = None) -> Tuple[List[Individual], List[int]]:
+    def select_parents(
+        self, population: List[Individual], **kwargs
+    ) -> Tuple[List[Individual], List[int]]:
         """
         Selects the individuals that will be perturbed in this generation to generate the offspring.
 
@@ -126,8 +132,8 @@ def select_parents(self, population: List[Individual], progress: float = 0, hist
         progress: float, optional
             Indicator of how close it the algorithm to finishing, 1 means the algorithm should be stopped.
         history: List[float], optional
-            The list of the best fitness value in each iteration. 
-        
+            The list of the best fitness value in each iteration.
+
         Returns
         -------
         parents: Tuple[List[Individual], List[int]]
@@ -137,7 +143,9 @@ def select_parents(self, population: List[Individual], progress: float = 0, hist
         return population
 
     @abstractmethod
-    def perturb(self, parent_list: List[Individual], progress: float, objfunc: ObjectiveFunc, history: List[float]) -> List[Individual]:
+    def perturb(
+        self, parent_list: List[Individual], objfunc: ObjectiveFunc, **kwargs
+    ) -> List[Individual]:
         """
         Applies operators to the population to get the next generation of individuals.
 
@@ -150,15 +158,17 @@ def perturb(self, parent_list: List[Individual], progress: float, objfunc: Objec
         objfunc: ObjectiveFunc
             Objective function to be optimized.
         history: List[float], optional
-            The list of the best fitness value in each iteration. 
-        
+            The list of the best fitness value in each iteration.
+
         Returns
         -------
         offspring: List[Individual]
             The list of individuals modified by the operators of the algorithm.
         """
 
-    def select_individuals(self, population: List[Individual], offspring: List[Individual], progress: float = 0, history: List[float] = None) -> List[Individual]:
+    def select_individuals(
+        self, population: List[Individual], offspring: List[Individual], **kwargs
+    ) -> List[Individual]:
         """
         Selects the individuals that will pass to the next generation.
 
@@ -171,8 +181,8 @@ def select_individuals(self, population: List[Individual], offspring: List[Indiv
         progress: float, optional
             Indicator of how close it the algorithm to finishing, 1 means the algorithm should be stopped.
         history: List[float], optional
-            The list of the best fitness value in each iteration. 
-        
+            The list of the best fitness value in each iteration.
+
         Returns
         -------
         offspring: List[Individual]
@@ -181,7 +191,7 @@ def select_individuals(self, population: List[Individual], offspring: List[Indiv
 
         return offspring
 
-    def update_params(self, progress: float = 0):
+    def update_params(self, **kwargs):
         """
         Updates the parameters of the algorithm and the operators.
 
@@ -221,15 +231,18 @@ def get_state(self, show_pop: bool = False, show_pop_details: bool = False) -> d
 
         if self.parent_sel:
             data["parent_sel"] = [par.get_state() for par in self.parent_sel]
-        
+
         if self.operators:
             data["operators"] = [op.get_state() for op in self.operators]
-        
+
         if self.surv_sel:
             data["survivor_sel"] = [surv.get_state() for surv in self.surv_sel]
-        
+
         if show_pop:
-            data["population"] = [ind.get_state(show_speed=show_pop_details, show_best=show_pop_details) for ind in self.population]
+            data["population"] = [
+                ind.get_state(show_speed=show_pop_details, show_best=show_pop_details)
+                for ind in self.population
+            ]
 
         return data
 
diff --git a/src/metaheuristic_designer/Algorithms/CRO/CRO.py b/src/metaheuristic_designer/Algorithms/CRO/CRO.py
deleted file mode 100644
index a01cdea..0000000
--- a/src/metaheuristic_designer/Algorithms/CRO/CRO.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from __future__ import annotations
-import numpy as np
-import random
-from typing import Union, List
-from copy import copy, deepcopy
-from ...Individual import Individual
-from ...Operators import OperatorReal, OperatorMeta
-from ...SelectionMethods import SurvivorSelection, ParentSelection
-from ..StaticPopulation import StaticPopulation
-from ...ParamScheduler import ParamScheduler
-from ...Algorithm import Algorithm
-
-
-class CRO(StaticPopulation):
-    """
-    Coral Reef Optimization
-    """
-
-    def __init__(self, pop_init: Initializer, mutate: Operator, cross: Operator, 
-                 params: Union[ParamScheduler, dict] = {}, name: str = "CRO"):
-        evolve_op = OperatorMeta("Branch", [cross, mutate], {"p": params["Fb"]})
-
-        selection_op = SurvivorSelection("CRO", {"Fd": params["Fd"], "Pd": params["Pd"], "attempts": params["attempts"], "maxPopSize": pop_init.pop_size})
-        
-        pop_init = deepcopy(pop_init)
-        pop_init.pop_size = round(pop_init.pop_size * params["rho"])
-        
-        super().__init__(
-            pop_init,
-            evolve_op,
-            params=params,
-            selection_op=selection_op,
-            name=name
-        )
\ No newline at end of file
diff --git a/src/metaheuristic_designer/Algorithms/Classic/DE.py b/src/metaheuristic_designer/Algorithms/Classic/DE.py
deleted file mode 100644
index c5ec85b..0000000
--- a/src/metaheuristic_designer/Algorithms/Classic/DE.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from __future__ import annotations
-from typing import Union, List
-from ...SelectionMethods import SurvivorSelection
-from ..StaticPopulation import StaticPopulation
-
-
-class DE(StaticPopulation):
-    """
-    Differential evolution
-    """
-
-    def __init__(self, pop_init: Initializer, de_op: Operator, params: Union[ParamScheduler, dict] = {}, selection_op: SurvivorSelection = None, name: str = "DE"):
-        if selection_op is None:
-            selection_op = SurvivorSelection("One-to-one")
-
-        super().__init__(
-            pop_init, 
-            de_op, 
-            selection_op=selection_op, 
-            params=params,
-            name=name
-        )
\ No newline at end of file
diff --git a/src/metaheuristic_designer/Algorithms/Classic/ES.py b/src/metaheuristic_designer/Algorithms/Classic/ES.py
deleted file mode 100644
index 815e76d..0000000
--- a/src/metaheuristic_designer/Algorithms/Classic/ES.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from __future__ import annotations
-from typing import Union, List
-from ...Operators import OperatorMeta
-from ..VariablePopulation import VariablePopulation
-
-
-class ES(VariablePopulation):
-    """
-    Evolution strategy
-    """
-
-    def __init__(self, pop_init: Initializer, mutation_op: Operator, cross_op: Operator, parent_sel_op: ParentSelection, 
-                 selection_op: SurvivorSelection, params: Union[ParamScheduler, dict] = {}, name: str = "ES"):
-        evolve_op = OperatorMeta("Sequence", [mutation_op, cross_op])
-
-        super().__init__(
-            pop_init, 
-            evolve_op,
-            parent_sel_op=parent_sel_op,
-            selection_op=selection_op,
-            n_offspring=params["offspringSize"],
-            params=params,
-            name=name
-        )
-
-
diff --git a/src/metaheuristic_designer/Algorithms/Classic/GA.py b/src/metaheuristic_designer/Algorithms/Classic/GA.py
deleted file mode 100644
index 0be3674..0000000
--- a/src/metaheuristic_designer/Algorithms/Classic/GA.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from __future__ import annotations
-import numpy as np
-from typing import Union, List
-from ...Operators import OperatorReal, OperatorMeta
-from ..VariablePopulation import VariablePopulation
-
-class GA(VariablePopulation):
-    """
-    Genetic algorithm
-    """
-
-    def __init__(self, pop_init: Initializer, mutation_op: Operator, cross_op: Operator, parent_sel_op: ParentSelection, 
-                 selection_op: SurvivorSelection, params: Union[ParamScheduler, dict] = {}, name: str = "GA"):
-        self.pmut = params["pmut"] if "pmut" in params else 0.1
-        self.pcross = params["pcross"] if "pcross" in params else 0.9
-
-        null_operator = OperatorReal("Nothing", {})
-
-        prob_mut_op = OperatorMeta("Branch", [mutation_op, null_operator], {"p": self.pmut})
-        prob_cross_op = OperatorMeta("Branch", [cross_op, null_operator], {"p": self.pcross}) 
-
-        evolve_op = OperatorMeta("Sequence", [prob_mut_op, prob_cross_op])
-
-        super().__init__(
-            pop_init, 
-            evolve_op,
-            parent_sel_op=parent_sel_op,
-            selection_op=selection_op, 
-            n_offspring=pop_init.pop_size,
-            params=params,
-            name=name
-        )
-
-    def extra_step_info(self):
-        popul_matrix = np.array(list(map(lambda x: x.genotype, self.population)))
-        divesity = popul_matrix.std(axis=1).mean()
-        print(f"\tdiversity: {divesity:0.3}")
diff --git a/src/metaheuristic_designer/Algorithms/Modern/CMA_ES.py b/src/metaheuristic_designer/Algorithms/Modern/CMA_ES.py
deleted file mode 100644
index 31ad262..0000000
--- a/src/metaheuristic_designer/Algorithms/Modern/CMA_ES.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from __future__ import annotations
-from ..StaticPopulation import StaticPopulation
-from ...Operators import OperatorReal, OperatorMeta
-from ...SelectionMethods import  SurvivorSelection, ParentSelection
-from ...Encodings import CMAEncoding
-
-
-class CMA_ES(StaticPopulation):
-    def __init__(self, pop_init: Initializer, mutation_op: Operator, cross_op: Operator, parent_sel_op: ParentSelection, selection_op: SurvivorSelection,
-                 params: Union[ParamScheduler, dict] = {}, name: str = "ES"):
-
-        parent_select = ParentSelection("Nothing")
-        selection = SurvivorSelection("(m+n)")
-
-        cross = OperatorReal("Multicross", {"N": params["HMS"]})
-        mutate1 = OperatorReal("MutNoise", {"method": "Gauss", "F": params["BW"], "Cr": params["HMCR"] * params["PAR"]})
-        rand1 = OperatorReal("RandomMask", {"Cr": 1 - params["HMCR"]})
-
-        mutate = OperatorMeta("Sequence", [mutate1, rand1])
-
-        super().__init__(pop_init, mutate, cross, parent_select, selection, params, name)
-
-    def initialize(self, objfunc):
-        objfunc.encoding = CMAEncoding(self.params["nparams"], pre_encoding=objfunc.encoding)
-        super().initialize(objfunc)
diff --git a/src/metaheuristic_designer/Encoding.py b/src/metaheuristic_designer/Encoding.py
index 1225de4..4b27a58 100644
--- a/src/metaheuristic_designer/Encoding.py
+++ b/src/metaheuristic_designer/Encoding.py
@@ -16,7 +16,7 @@ def encode(self, phenotype) -> Any:
         ----------
         phenotype: Any
             Information that should be encoded.
-        
+
         Returns
         -------
         genotype: Any
@@ -32,7 +32,7 @@ def decode(self, genotype) -> Any:
         ----------
         genotype: Any
             Information that should be decoded.
-        
+
         Returns
         -------
         phenotype: Any
diff --git a/src/metaheuristic_designer/Individual.py b/src/metaheuristic_designer/Individual.py
index 816920d..4ea7729 100644
--- a/src/metaheuristic_designer/Individual.py
+++ b/src/metaheuristic_designer/Individual.py
@@ -2,7 +2,8 @@
 from copy import copy
 import numpy as np
 from numpy import ndarray
-from .Encodings import DefaultEncoding
+from .encodings import DefaultEncoding
+from .utils import RAND_GEN
 
 
 class Individual:
@@ -19,7 +20,13 @@ class Individual:
 
     _last_id = 0
 
-    def __init__(self, objfunc: ObjectiveFunc, genotype: Any, speed: ndarray = None, encoding: Encoding = None):
+    def __init__(
+        self,
+        objfunc: ObjectiveFunc,
+        genotype: Any,
+        speed: ndarray = None,
+        encoding: Encoding = None,
+    ):
         """
         Constructor of the Individual class.
         """
@@ -29,16 +36,16 @@ def __init__(self, objfunc: ObjectiveFunc, genotype: Any, speed: ndarray = None,
 
         self.objfunc = objfunc
         self._genotype = genotype
-        
+
         if speed is None and isinstance(genotype, np.ndarray):
-            speed = np.random.random(size=genotype.shape)
+            speed = RAND_GEN.random(size=genotype.shape)
         self.speed = speed
-        
+
         self._fitness = 0
         self.best_fitness = None
         self.fitness_calculated = False
         self.best = genotype
-        
+
         if encoding is None:
             encoding = DefaultEncoding()
         self.encoding = encoding
@@ -48,7 +55,9 @@ def __copy__(self) -> Individual:
         Returns a copy of the Individual.
         """
 
-        copied_ind = Individual(self.objfunc, copy(self._genotype), copy(self.speed), self.encoding)
+        copied_ind = Individual(
+            self.objfunc, copy(self._genotype), copy(self.speed), self.encoding
+        )
         copied_ind._fitness = self._fitness
         copied_ind.fitness_calculated = self.fitness_calculated
         copied_ind.best = copy(self.best)
@@ -81,7 +90,9 @@ def apply_speed(self) -> Individual:
             Individual with the speed applied.
         """
 
-        return Individual(self.objfunc, self._genotype + self.speed, self.speed, self.encoding)
+        return Individual(
+            self.objfunc, self._genotype + self.speed, self.speed, self.encoding
+        )
 
     @property
     def fitness(self) -> float:
@@ -102,10 +113,10 @@ def fitness(self, fit: float):
         if self.best_fitness is None or self.best_fitness < fit:
             self.best_fitness = fit
             self.best = self.genotype
-        
+
         self._fitness = fit
         self.fitness_calculated = True
-    
+
     def get_state(self, show_speed: bool = True, show_best: bool = False) -> dict:
         """
         Gets the current state of the algorithm as a dictionary.
@@ -123,15 +134,12 @@ def get_state(self, show_speed: bool = True, show_best: bool = False) -> dict:
             The current state of this individual.
         """
 
-        data = {
-            "genotype": self._genotype,
-            "fitness": self._fitness
-        }
+        data = {"genotype": self._genotype, "fitness": self._fitness}
 
         if show_speed:
             data["speed"] = self.speed
-        
+
         if show_best:
-            data["best_genotype"] =  self.best
+            data["best_genotype"] = self.best
 
         return data
diff --git a/src/metaheuristic_designer/Initializer.py b/src/metaheuristic_designer/Initializer.py
index 1efca18..26fb002 100644
--- a/src/metaheuristic_designer/Initializer.py
+++ b/src/metaheuristic_designer/Initializer.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 from abc import ABC, abstractmethod
-from .Encodings import DefaultEncoding
+from .encodings import DefaultEncoding
 
 
 class Initializer(ABC):
@@ -24,7 +24,7 @@ def __init__(self, pop_size: int = 1, encoding: Encoding = None):
         if encoding is None:
             encoding = DefaultEncoding()
         self.encoding = encoding
-    
+
     @abstractmethod
     def generate_random(self, objfunc: ObjectiveFunc) -> Individual:
         """
@@ -34,22 +34,22 @@ def generate_random(self, objfunc: ObjectiveFunc) -> Individual:
         ----------
         objfunc: ObjectiveFunc
             Objective function that will be propagated to the individual.
-        
+
         Returns
         -------
         new_individual: Individual
             Newly generated individual.
         """
-    
+
     def generate_individual(self, objfunc: ObjectiveFunc) -> Individual:
         """
         Define how an individual is initialized
-        
+
         Parameters
         ----------
         objfunc: ObjectiveFunc
             Objective function that will be propagated to the individual.
-        
+
         Returns
         -------
         new_individual: Individual
@@ -57,8 +57,10 @@ def generate_individual(self, objfunc: ObjectiveFunc) -> Individual:
         """
 
         return self.generate_random(objfunc)
-    
-    def generate_population(self, objfunc: ObjectiveFunc, n_indiv: int = None) -> List[Individual]:
+
+    def generate_population(
+        self, objfunc: ObjectiveFunc, n_indiv: int = None
+    ) -> List[Individual]:
         """
         Generate n_indiv Individuals using the generate_individual method.
 
@@ -68,14 +70,14 @@ def generate_population(self, objfunc: ObjectiveFunc, n_indiv: int = None) -> Li
             Objective function that will be propagated to each individual.
         n_indiv: int, optional
             Number of individuals to generate
-        
+
         Returns
         -------
         generated_population: List[Individual]
             Newly generated population.
         """
-        
+
         if n_indiv is None:
             n_indiv = self.pop_size
-        
+
         return [self.generate_individual(objfunc) for i in range(n_indiv)]
diff --git a/src/metaheuristic_designer/ObjectiveFunc.py b/src/metaheuristic_designer/ObjectiveFunc.py
index 0d81a4a..f794e43 100644
--- a/src/metaheuristic_designer/ObjectiveFunc.py
+++ b/src/metaheuristic_designer/ObjectiveFunc.py
@@ -1,8 +1,8 @@
 from __future__ import annotations
 from abc import ABC, abstractmethod
 import numpy as np
-from numpy import ndarray 
-from .Initializers import UniformVectorInitializer
+from numpy import ndarray
+from .initializers import UniformVectorInitializer
 
 
 class ObjectiveFunc(ABC):
@@ -29,11 +29,11 @@ def __init__(self, mode: str = "max", name: str = "some function"):
         self.name = name
         self.counter = 0
         self.factor = 1
-        
+
         self.mode = mode
         if mode not in ["max", "min"]:
-            raise ValueError("Optimization objective (mode) must be \"min\" or \"max\".")
-        
+            raise ValueError('Optimization objective (mode) must be "min" or "max".')
+
         if self.mode == "min":
             self.factor = -1
 
@@ -101,7 +101,7 @@ def repair_solution(self, solution: Any) -> Any:
         Returns
         -------
         repaired_solution: Any
-            A modified version of the solution passed that satisfies the restrictions of the problem.             
+            A modified version of the solution passed that satisfies the restrictions of the problem.
         """
 
         return vector
@@ -118,7 +118,7 @@ def repair_speed(self, speed: ndarray) -> ndarray:
         Returns
         -------
         repaired_speed: ndarray
-            A modified version of the speed vector passed that satisfies the restrictions of the problem.   
+            A modified version of the speed vector passed that satisfies the restrictions of the problem.
         """
 
         result = None
@@ -137,7 +137,7 @@ def penalize(self, solution: Any) -> float:
         ----------
         solution: Any
             A solution that could be violating the restrictions of the problem.
-        
+
         Returns
         -------
         penalty: float
@@ -158,14 +158,21 @@ class ObjectiveVectorFunc(ObjectiveFunc):
     mode: str, optional
         Whether to maximize or minimize the function (using the string 'max' or 'min').
     low_lim: float, optional
-        Lower limit restriction for the vectors. 
+        Lower limit restriction for the vectors.
     up_lim: float, optional
         Upper limit restriction for the vectors.
     name: str, optional
         The name that will be displayed to represent this function.
     """
 
-    def __init__(self, vecsize: int, mode: str = "max", low_lim: float = -100, up_lim: float = 100, name: str = "some function"):
+    def __init__(
+        self,
+        vecsize: int,
+        mode: str = "max",
+        low_lim: float = -100,
+        up_lim: float = 100,
+        name: str = "some function",
+    ):
         """
         Constructor for the ObjectiveVectorFunc class
         """
@@ -193,14 +200,22 @@ class ObjectiveFromLambda(ObjectiveVectorFunc):
     mode: str, optional
         Whether to maximize or minimize the function (using the string 'max' or 'min').
     low_lim: float, optional
-        Lower limit restriction for the vectors. 
+        Lower limit restriction for the vectors.
     up_lim: float, optional
         Upper limit restriction for the vectors.
     name: str, optional
         The name that will be displayed to represent this function.
     """
 
-    def __init__(self, obj_func: callable, vecsize: int, mode: str = "max", low_lim: float = -100, up_lim: float = 100, name: str = "some function"):
+    def __init__(
+        self,
+        obj_func: callable,
+        vecsize: int,
+        mode: str = "max",
+        low_lim: float = -100,
+        up_lim: float = 100,
+        name: str = "some function",
+    ):
         """
         Constructor for the ObjectiveFromLambda class
         """
@@ -211,9 +226,9 @@ def __init__(self, obj_func: callable, vecsize: int, mode: str = "max", low_lim:
         super().__init__(vecsize, mode, low_lim, up_lim, name)
 
         self.obj_func = obj_func
-    
+
     def objective(self, vector):
         return self.obj_func(vector)
-    
+
     def repair_solution(self, vector):
         return np.clip(vector, self.low_lim, self.up_lim)
diff --git a/src/metaheuristic_designer/Operator.py b/src/metaheuristic_designer/Operator.py
index 0a92f29..415175c 100644
--- a/src/metaheuristic_designer/Operator.py
+++ b/src/metaheuristic_designer/Operator.py
@@ -30,8 +30,6 @@ def __init__(self, params: Union[ParamScheduler, dict] = None, name: str = None)
         self.name = name
 
         if params is None:
-
-            # Default parameters
             self.params = {}
         elif params == "default":
             self.params = {
@@ -55,7 +53,7 @@ def __init__(self, params: Union[ParamScheduler, dict] = None, name: str = None)
                 "w": 0.7,
                 "c1": 1.5,
                 "c2": 1.5,
-                "function": lambda x, y, z, w: x.genotype
+                "function": lambda x, y, z, w: x.genotype,
             }
         else:
             if "method" in params:
@@ -67,7 +65,14 @@ def __init__(self, params: Union[ParamScheduler, dict] = None, name: str = None)
             else:
                 self.params = params
 
-    def __call__(self, solution: Individual, population: List[Individual], objfunc: ObjectiveFunc, global_best: Individual, initializer: Initializer) -> Individual:
+    def __call__(
+        self,
+        solution: Individual,
+        population: List[Individual],
+        objfunc: ObjectiveFunc,
+        global_best: Individual,
+        initializer: Initializer,
+    ) -> Individual:
         """
         A shorthand for calling the 'evolve' method.
         """
@@ -87,7 +92,7 @@ def step(self, progress: float):
         if self.param_scheduler:
             self.param_scheduler.step(progress)
             self.params = self.param_scheduler.get_params()
-    
+
     def get_state(self) -> dict:
         """
         Gets the current state of the algorithm as a dictionary.
@@ -97,10 +102,8 @@ def get_state(self) -> dict:
         state: dict
             The complete state of the operator.
         """
-        
-        data = {
-            "name": self.name
-        }
+
+        data = {"name": self.name}
 
         if self.param_scheduler:
             data["param_scheduler"] = self.param_scheduler.get_state()
@@ -109,11 +112,18 @@ def get_state(self) -> dict:
         elif self.params:
             data["params"] = self.params
             data["params"].pop("function", None)
-        
+
         return data
 
     @abstractmethod
-    def evolve(self, indiv: Individual, population: List[Individual], objfunc: ObjectiveFunc, global_best: Individual, initializer: Initializer) -> Individual:
+    def evolve(
+        self,
+        indiv: Individual,
+        population: List[Individual],
+        objfunc: ObjectiveFunc,
+        global_best: Individual,
+        initializer: Initializer,
+    ) -> Individual:
         """
         Evolves an individual using a given strategy.
 
diff --git a/src/metaheuristic_designer/ParamScheduler.py b/src/metaheuristic_designer/ParamScheduler.py
index 40487d0..2f850b3 100644
--- a/src/metaheuristic_designer/ParamScheduler.py
+++ b/src/metaheuristic_designer/ParamScheduler.py
@@ -1,12 +1,10 @@
+from __future__ import annotations
 import random
 import numpy as np
 import math
 
 
-_par_sch_methods = [
-    "linear",
-    "exp"
-]
+_par_sch_methods = ["linear", "exp"]
 
 
 class ParamScheduler:
@@ -29,22 +27,22 @@ def __init__(self, strategy: str, param_schedule: dict):
         self.strategy = strategy.lower()
 
         if strategy.lower() not in _par_sch_methods:
-            raise ValueError(f"Parameter scheduler strategy \"{self.name}\" not defined")
+            raise ValueError(f'Parameter scheduler strategy "{self.name}" not defined')
 
         self.param_schedule = param_schedule
 
         self.reset()
 
-    def __getitem__(self, idx: str) -> type:
+    def __getitem__(self, idx: str) -> Any:
         """
-        Gets the current value of a parameter given it's name
+        Gets the current value of a parameter given its name
         """
 
         return self.current_params[idx]
 
-    def __setitem__(self, idx: str, value: type):
+    def __setitem__(self, idx: str, value: Any):
         """
-        Sets the current value of a parameter given it's name
+        Sets the current value of a parameter given its name
         """
 
         self.current_params[idx] = value
@@ -56,6 +54,13 @@ def __contains__(self, value: str) -> bool:
 
         return value in self.current_params
 
+    def get(key: str, def_value: Any = None) -> Any:
+        """
+        Gets the current value of a parameter given its name using a default value if it's missing
+        """
+
+        return self.current_params.get(key, def_value)
+
     def reset(self):
         """
         Sets all the parameters to their initial values.
@@ -78,7 +83,7 @@ def get_params(self) -> dict:
         """
 
         return self.current_params
-    
+
     def get_state(self) -> dict:
         """
         Gets the current state of the algorithm as a dictionary.
@@ -88,10 +93,7 @@ def get_state(self) -> dict:
         state: dict
         """
 
-        data = {
-            "strategy": self.strategy,
-            "param_schedule": self.param_schedule
-        }
+        data = {"strategy": self.strategy, "param_schedule": self.param_schedule}
 
         return data
 
@@ -105,12 +107,13 @@ def step(self, progress: float):
         """
 
         if self.strategy == "linear":
-
             for key in self.param_schedule:
                 if type(self.param_schedule[key]) in (list, tuple):
                     start_param = self.param_schedule[key][0]
                     end_param = self.param_schedule[key][1]
-                    self.current_params[key] = (1 - progress) * start_param + progress * end_param
+                    self.current_params[key] = (
+                        1 - progress
+                    ) * start_param + progress * end_param
 
         elif self.strategy == "exp":
             # with f(x) = k·e^{a·x}+b,  f(0) = p[0],  f(1) = p[1]
diff --git a/src/metaheuristic_designer/Search.py b/src/metaheuristic_designer/Search.py
index eb2fd1a..98d225d 100644
--- a/src/metaheuristic_designer/Search.py
+++ b/src/metaheuristic_designer/Search.py
@@ -4,11 +4,12 @@
 import numpy as np
 import pyparsing as pp
 from .ObjectiveFunc import ObjectiveVectorFunc
-from .Initializers import UniformVectorInitializer
+from .initializers import UniformVectorInitializer
 
 import json
 from .utils import NumpyEncoder
 
+
 class Search(ABC):
     """
     General framework for metaheuristic algorithms.
@@ -24,7 +25,12 @@ class Search(ABC):
         Dictionary of parameters to define the stopping condition and output of the algorithm.
     """
 
-    def __init__(self, objfunc: ObjectiveFunc, search_strategy: Algorithm, params: Union[ParamScheduler, dict] = None):
+    def __init__(
+        self,
+        objfunc: ObjectiveFunc,
+        search_strategy: Algorithm,
+        params: Union[ParamScheduler, dict] = None,
+    ):
         """
         Constructor of the Search class
         """
@@ -37,22 +43,26 @@ def __init__(self, objfunc: ObjectiveFunc, search_strategy: Algorithm, params: U
             params = {}
 
         # Verbose parameters
-        self.verbose = params["verbose"] if "verbose" in params else True
-        self.v_timer = params["v_timer"] if "v_timer" in params else 1
+        self.verbose = params.get("verbose", True)
+        self.v_timer = params.get("v_timer", 1)
 
         # Stopping conditions
-        self.stop_cond = params["stop_cond"] if "stop_cond" in params else "time_limit"
+        self.stop_cond = params.get("stop_cond", "time_limit")
         self.stop_cond_parsed = parse_stopping_cond(self.stop_cond)
 
-        self.progress_metric = params["progress_metric"] if "progress_metric" in params else self.stop_cond
-        self.progress_metric_parsed = parse_stopping_cond(self.progress_metric) if "progress_metric" in params else self.stop_cond_parsed
-
-        self.Ngen = params["ngen"] if "ngen" in params else 100
-        self.Neval = params["neval"] if "neval" in params else 1e5
-        self.time_limit = params["time_limit"] if "time_limit" in params else 10.0
-        self.cpu_time_limit = params["cpu_time_limit"] if "cpu_time_limit" in params else 10.0
-        self.fit_target = params["fit_target"] if "fit_target" in params else 0
-        self.max_patience = params["patience"] if "patience" in params else 1
+        self.progress_metric = params.get("progress_metric", self.stop_cond)
+        self.progress_metric_parsed = (
+            parse_stopping_cond(self.progress_metric)
+            if "progress_metric" in params
+            else self.stop_cond_parsed
+        )
+
+        self.Ngen = params.get("ngen", 100)
+        self.Neval = params.get("neval", 1e5)
+        self.time_limit = params.get("time_limit", 10.0)
+        self.cpu_time_limit = params.get("cpu_time_limit", 10.0)
+        self.fit_target = params.get("fit_target", 1e-10)
+        self.max_patience = params.get("patience", 1)
         self.patience_left = self.max_patience
 
         # Metrics
@@ -92,7 +102,7 @@ def save_solution(self, file_name: str = "solution.csv"):
         """
 
         ind, fit = self.search_strategy.best_solution()
-        np.savetxt(file_name, ind.reshape([1, -1]), delimiter=',')
+        np.savetxt(file_name, ind.reshape([1, -1]), delimiter=",")
 
     def best_solution(self) -> Tuple[Individual, float]:
         """
@@ -105,9 +115,10 @@ def best_solution(self) -> Tuple[Individual, float]:
         """
 
         return self.search_strategy.best_solution()
-    
 
-    def stopping_condition(self, gen: int, real_time_start: float, cpu_time_start: float) -> bool:
+    def stopping_condition(
+        self, gen: int, real_time_start: float, cpu_time_start: float
+    ) -> bool:
         """
         Given the state of the algorithm, returns wether we have finished or not.
 
@@ -119,7 +130,7 @@ def stopping_condition(self, gen: int, real_time_start: float, cpu_time_start: f
             The time in seconds that passed since the algorithm was executed.
         cpu_time_start: float
             The time in seconds that the CPU has executed code in this algorithm.
-        
+
         Returns
         -------
         has_stopped: bool
@@ -141,9 +152,19 @@ def stopping_condition(self, gen: int, real_time_start: float, cpu_time_start: f
 
         patience_reached = self.patience_left < 0
 
-        return process_condition(self.stop_cond_parsed, neval_reached, ngen_reached, real_time_reached, cpu_time_reached, target_reached, patience_reached)
-
-    def get_progress(self, gen: int, real_time_start: float, cpu_time_start: float) -> float:
+        return process_condition(
+            self.stop_cond_parsed,
+            neval_reached,
+            ngen_reached,
+            real_time_reached,
+            cpu_time_reached,
+            target_reached,
+            patience_reached,
+        )
+
+    def get_progress(
+        self, gen: int, real_time_start: float, cpu_time_start: float
+    ) -> float:
         """
         Given the state of the algorithm, returns a number between 0 and 1 indicating
         how close to the end of the algorithm we are, 0 when starting and 1 when finished.
@@ -156,34 +177,44 @@ def get_progress(self, gen: int, real_time_start: float, cpu_time_start: float)
             The time in seconds that passed since the algorithm was executed.
         cpu_time_start: float
             The time in seconds that the CPU has executed code in this algorithm.
-        
+
         Returns
         -------
         progress: float
             Indicator of how close it the algorithm to finishing, 1 means the algorithm should be stopped.
         """
 
-        neval_reached = self.objfunc.counter/self.Neval
-        
-        ngen_reached = gen/self.Ngen
+        neval_reached = self.objfunc.counter / self.Neval
+
+        ngen_reached = gen / self.Ngen
 
-        real_time_reached = (time.time() - real_time_start)/self.time_limit
+        real_time_reached = (time.time() - real_time_start) / self.time_limit
 
-        cpu_time_reached = (time.process_time() - cpu_time_start)/self.cpu_time_limit
+        cpu_time_reached = (time.process_time() - cpu_time_start) / self.cpu_time_limit
 
         best_fitness = self.best_solution()[1]
-        if self.objfunc.mode == "max":
-            target_reached = best_fitness/self.fit_target
+        if self.objfunc.mode == "max":    
+            target_reached = best_fitness / self.fit_target
         else:
             if best_fitness == 0:
                 best_fitness = 1e-40
-            target_reached = self.fit_target/best_fitness
-        
-        patience_prec = 1 - self.patience_left/self.max_patience
-
-        return process_progress(self.stop_cond_parsed, neval_reached, ngen_reached, real_time_reached, cpu_time_reached, target_reached, patience_prec)     
-
-    def update(self, real_time_start: float, cpu_time_start: float, pass_step:bool = True):
+            target_reached = self.fit_target / best_fitness
+
+        patience_prec = 1 - self.patience_left / self.max_patience
+
+        return process_progress(
+            self.stop_cond_parsed,
+            neval_reached,
+            ngen_reached,
+            real_time_reached,
+            cpu_time_reached,
+            target_reached,
+            patience_prec,
+        )
+
+    def update(
+        self, real_time_start: float, cpu_time_start: float, pass_step: bool = True
+    ):
         """
         Updates the attributes of the optimization algorithm.
         This function should be called once per iteration of the algorithm.
@@ -196,21 +227,23 @@ def update(self, real_time_start: float, cpu_time_start: float, pass_step:bool =
             The time in seconds that the CPU has executed code in this algorithm.
         pass_step: bool
             Whether to increment the iteration counter or not.
-        """        
+        """
 
         if pass_step:
             self.steps += 1
-        
+
         if self.best_solution()[1] == self.prev_best_fitness:
             self.patience_left -= 1
         else:
             self.patience_left = self.max_patience
-        
+
         self.prev_best_fitness = self.best_solution()[1]
 
         self.progress = self.get_progress(self.steps, real_time_start, cpu_time_start)
 
-        self.ended = self.stopping_condition(self.steps, real_time_start, cpu_time_start)
+        self.ended = self.stopping_condition(
+            self.steps, real_time_start, cpu_time_start
+        )
 
     def initialize(self):
         """
@@ -221,7 +254,9 @@ def initialize(self):
         self.search_strategy.initialize(self.objfunc)
 
     @abstractmethod
-    def step(self, time_start: float = 0, verbose: bool = False) -> Tuple[Individual, float]:
+    def step(
+        self, time_start: float = 0, verbose: bool = False
+    ) -> Tuple[Individual, float]:
         """
         Performs an iteration of the algorithm.
 
@@ -230,8 +265,8 @@ def step(self, time_start: float = 0, verbose: bool = False) -> Tuple[Individual
         time_start: float, optional
             Indicates to the algorihm how much time has already passed.
         verbose: bool, optional
-            Indicates whether to show the status of the algorithm or not. 
-        
+            Indicates whether to show the status of the algorithm or not.
+
         Returns
         -------
         best_solution: Tuple[Individual, float]
@@ -241,7 +276,7 @@ def step(self, time_start: float = 0, verbose: bool = False) -> Tuple[Individual
     def optimize(self) -> Tuple[Individual, float]:
         """
         Execute the algorithm to get the best solution possible along with its evaluation.
-        It will initialize the algorithm and repeat steps of the algorithm untill the 
+        It will initialize the algorithm and repeat steps of the algorithm untill the
         stopping condition is met.
 
         Returns
@@ -259,16 +294,16 @@ def optimize(self) -> Tuple[Individual, float]:
         real_time_start = time.time()
         cpu_time_start = time.process_time()
         display_timer = time.time()
-        
+
         # Initizalize search strategy
         self.initialize()
 
         # Search untill the stopping condition is met
         self.update(real_time_start, cpu_time_start, pass_step=False)
-        
+
         if self.verbose:
             self.step_info(real_time_start)
-        
+
         while not self.ended:
             self.step(real_time_start)
 
@@ -278,7 +313,6 @@ def optimize(self) -> Tuple[Individual, float]:
             if self.verbose and time.time() - display_timer > self.v_timer:
                 self.step_info(real_time_start)
                 display_timer = time.time()
-            
 
         # Store the time spent optimizing
         self.real_time_spent = time.time() - real_time_start
@@ -286,15 +320,21 @@ def optimize(self) -> Tuple[Individual, float]:
 
         return self.best_solution()
 
-    def get_state(self, show_best_solution: bool = True, show_fit_history: bool = False, show_gen_history: bool = False,
-                  show_pop: bool = False, show_pop_details:bool = False) -> dict:
+    def get_state(
+        self,
+        show_best_solution: bool = True,
+        show_fit_history: bool = False,
+        show_gen_history: bool = False,
+        show_pop: bool = False,
+        show_pop_details: bool = False,
+    ) -> dict:
         """
         Gets the current state of the algorithm as a dictionary.
 
         Parameters
         ----------
         show_best_solution: bool, optional
-            Save the best solution found by the algorithm. 
+            Save the best solution found by the algorithm.
         show_fit_history: bool, optional
             Save the fitness of the best individual of each iteration.
         show_gen_history: bool, optional
@@ -309,7 +349,7 @@ def get_state(self, show_best_solution: bool = True, show_fit_history: bool = Fa
         state: dict
             The complete state of the algorithm.
         """
-        
+
         data = {
             "ended": self.ended,
             "progress": self.progress,
@@ -322,7 +362,9 @@ def get_state(self, show_best_solution: bool = True, show_fit_history: bool = Fa
 
         if show_best_solution:
             data["best_fitness"] = self.best_solution()[1]
-            data["best_individual"] = self.search_strategy.best.get_state(show_speed=False, show_best=False)
+            data["best_individual"] = self.search_strategy.best.get_state(
+                show_speed=False, show_best=False
+            )
 
         if show_fit_history:
             data["fit_history"] = self.fit_history
@@ -330,13 +372,22 @@ def get_state(self, show_best_solution: bool = True, show_fit_history: bool = Fa
         if show_gen_history:
             data["best_history"] = self.best_history
 
-        data["search_strat_state"] = self.search_strategy.get_state(show_pop, show_pop_details)
+        data["search_strat_state"] = self.search_strategy.get_state(
+            show_pop, show_pop_details
+        )
 
         return data
-    
-    def store_state(self, file_name: str = "dumped_state.json", readable: bool = False, show_best_solution: bool = True,
-                    show_fit_history: bool = False, show_gen_history: bool = False, show_pop: bool = False, 
-                    show_pop_details: bool = False):
+
+    def store_state(
+        self,
+        file_name: str = "dumped_state.json",
+        readable: bool = False,
+        show_best_solution: bool = True,
+        show_fit_history: bool = False,
+        show_gen_history: bool = False,
+        show_pop: bool = False,
+        show_pop_details: bool = False,
+    ):
         """
         Dumps the current state of the algorithm to a JSON file.
 
@@ -347,7 +398,7 @@ def store_state(self, file_name: str = "dumped_state.json", readable: bool = Fal
         readable: bool, optional
             Indent the JSON file to make it human-readable (comes at the cost of a higher file size).
         show_best_solution: bool, optional
-            Save the best solution found by the algorithm. 
+            Save the best solution found by the algorithm.
         show_fit_history: bool, optional
             Save the fitness of the best individual of each iteration.
         show_gen_history: bool, optional
@@ -358,19 +409,30 @@ def store_state(self, file_name: str = "dumped_state.json", readable: bool = Fal
             Save the detailed information of each individual.
         """
 
-        dumped = json.dumps(self.get_state(show_best_solution, show_fit_history,
-                                           show_gen_history, show_pop, show_pop_details), 
-                                           cls=NumpyEncoder, indent = 4 if readable else None)
+        dumped = json.dumps(
+            self.get_state(
+                show_best_solution,
+                show_fit_history,
+                show_gen_history,
+                show_pop,
+                show_pop_details,
+            ),
+            cls=NumpyEncoder,
+            indent=4 if readable else None,
+        )
 
         with open(file_name, "w") as fp:
             fp.write(dumped)
-    
+
     def init_info(self):
-        print(f"Initializing optimization of {self.objfunc.name} using {self.search_strategy.name}")
-        print(f"-----------------------------{'-'*len(self.objfunc.name)}-------{'-'*len(self.search_strategy.name)}")
+        print(
+            f"Initializing optimization of {self.objfunc.name} using {self.search_strategy.name}"
+        )
+        print(
+            f"-----------------------------{'-'*len(self.objfunc.name)}-------{'-'*len(self.search_strategy.name)}"
+        )
         print()
 
-    
     @abstractmethod
     def step_info(self, start_time: float = 0):
         """
@@ -403,7 +465,7 @@ def parse_stopping_cond(condition_str: str) -> List[str | List]:
     ----------
     condition_str: str
         The string to be parsed.
-    
+
     Returns
     -------
     token_list: List[str | List]
@@ -412,21 +474,26 @@ def parse_stopping_cond(condition_str: str) -> List[str | List]:
 
     orop = pp.Literal("and")
     andop = pp.Literal("or")
-    condition = pp.oneOf(["neval", "ngen", "time_limit", "cpu_time_limit", "fit_target", "convergence"])
+    condition = pp.oneOf(
+        ["neval", "ngen", "time_limit", "cpu_time_limit", "fit_target", "convergence"]
+    )
 
     expr = pp.infixNotation(
-        condition,
-        [
-            (orop, 2, pp.opAssoc.RIGHT),
-            (andop, 2, pp.opAssoc.RIGHT)
-        ]
+        condition, [(orop, 2, pp.opAssoc.RIGHT), (andop, 2, pp.opAssoc.RIGHT)]
     )
 
     return expr.parse_string(condition_str).as_list()
 
 
-def process_condition(cond_parsed: List[str | List], neval: int, ngen: int, real_time: float, 
-                      cpu_time: float, target: float, patience: int) -> bool:
+def process_condition(
+    cond_parsed: List[str | List],
+    neval: int,
+    ngen: int,
+    real_time: float,
+    cpu_time: float,
+    target: float,
+    patience: int,
+) -> bool:
     """
     This function recieves as an input an expression for the stopping condition
     and the truth variable of the possible stopping conditions and returns wether to stop or not.
@@ -447,7 +514,7 @@ def process_condition(cond_parsed: List[str | List], neval: int, ngen: int, real
         Fitness target.
     patience: int
         Number of time the algorithm has reached the same fitness value in a row.
-    
+
     Returns
     -------
     has_stopped: bool
@@ -458,8 +525,12 @@ def process_condition(cond_parsed: List[str | List], neval: int, ngen: int, real
 
     if isinstance(cond_parsed, list):
         if len(cond_parsed) == 3:
-            cond1 = process_condition(cond_parsed[0], neval, ngen, real_time, cpu_time, target, patience)
-            cond2 = process_condition(cond_parsed[2], neval, ngen, real_time, cpu_time, target, patience)
+            cond1 = process_condition(
+                cond_parsed[0], neval, ngen, real_time, cpu_time, target, patience
+            )
+            cond2 = process_condition(
+                cond_parsed[2], neval, ngen, real_time, cpu_time, target, patience
+            )
 
             if cond_parsed[1] == "or":
                 result = cond1 or cond2
@@ -467,7 +538,9 @@ def process_condition(cond_parsed: List[str | List], neval: int, ngen: int, real
                 result = cond1 and cond2
 
         elif len(cond_parsed) == 1:
-            result = process_condition(cond_parsed[0], neval, ngen, real_time, cpu_time, target, patience)
+            result = process_condition(
+                cond_parsed[0], neval, ngen, real_time, cpu_time, target, patience
+            )
 
     else:
         if cond_parsed == "neval":
@@ -482,14 +555,22 @@ def process_condition(cond_parsed: List[str | List], neval: int, ngen: int, real
             result = target
         elif cond_parsed == "convergence":
             result = patience
-    
+
     return result
 
-def process_progress(cond_parsed: List[str | List], neval: int, ngen: int, real_time: float, 
-                     cpu_time: float, target: float, patience: int) -> float:
+
+def process_progress(
+    cond_parsed: List[str | List],
+    neval: int,
+    ngen: int,
+    real_time: float,
+    cpu_time: float,
+    target: float,
+    patience: int,
+) -> float:
     """
-    This function recieves as an input an expression for the stopping condition 
-    and the truth variable of the possible stopping conditions and returns wether to stop or not. 
+    This function recieves as an input an expression for the stopping condition
+    and the truth variable of the possible stopping conditions and returns wether to stop or not.
 
     Parameters
     ----------
@@ -507,7 +588,7 @@ def process_progress(cond_parsed: List[str | List], neval: int, ngen: int, real_
         Fitness target.
     patience: int
         Number of time the algorithm has reached the same fitness value in a row.
-    
+
     Returns
     -------
     has_stopped: bool
@@ -515,20 +596,25 @@ def process_progress(cond_parsed: List[str | List], neval: int, ngen: int, real_
     """
 
     result = None
-    
+
     if isinstance(cond_parsed, list):
         if len(cond_parsed) == 3:
-            
-            progress1 = process_progress(cond_parsed[0], neval, ngen, real_time, cpu_time, target, patience)
-            progress2 = process_progress(cond_parsed[2], neval, ngen, real_time, cpu_time, target, patience)
+            progress1 = process_progress(
+                cond_parsed[0], neval, ngen, real_time, cpu_time, target, patience
+            )
+            progress2 = process_progress(
+                cond_parsed[2], neval, ngen, real_time, cpu_time, target, patience
+            )
 
             if cond_parsed[1] == "or":
                 result = max(progress1, progress2)
             elif cond_parsed[1] == "and":
                 result = min(progress1, progress2)
-            
+
         elif len(cond_parsed) == 1:
-            result = process_progress(cond_parsed[0], neval, ngen, real_time, cpu_time, target, patience)
+            result = process_progress(
+                cond_parsed[0], neval, ngen, real_time, cpu_time, target, patience
+            )
     else:
         if cond_parsed == "neval":
             result = neval
diff --git a/src/metaheuristic_designer/SelectionMethod.py b/src/metaheuristic_designer/SelectionMethod.py
index c2a6231..ad016f3 100644
--- a/src/metaheuristic_designer/SelectionMethod.py
+++ b/src/metaheuristic_designer/SelectionMethod.py
@@ -2,6 +2,7 @@
 from abc import ABC, abstractmethod
 from .ParamScheduler import ParamScheduler
 
+
 class SelectionMethod(ABC):
     """
     Survivor selection methods
@@ -16,7 +17,12 @@ class SelectionMethod(ABC):
         The name that will be assigned to this selection method.
     """
 
-    def __init__(self, params: Union[ParamScheduler, dict] = None, padding: bool = False, name: str = None):
+    def __init__(
+        self,
+        params: Union[ParamScheduler, dict] = None,
+        padding: bool = False,
+        name: str = None,
+    ):
         """
         Constructor for the SurvivorSelection class
         """
@@ -32,15 +38,19 @@ def __init__(self, params: Union[ParamScheduler, dict] = None, padding: bool = F
         else:
             self.params = params
 
-    def __call__(self, popul: List[Individual], offspring: List[Individual] = None) -> List[Individual]:
+    def __call__(
+        self, popul: List[Individual], offspring: List[Individual] = None
+    ) -> List[Individual]:
         """
         Shorthand for calling the 'select' method
         """
 
         return self.select(popul, offspring)
-    
+
     @abstractmethod
-    def select(self, population: List[Individual], offspring: List[Individual] = None) -> List[Individual]:     
+    def select(
+        self, population: List[Individual], offspring: List[Individual] = None
+    ) -> List[Individual]:
         """
         Takes a population with its offspring and returns the individuals that survive
         to produce the next generation.
@@ -51,7 +61,7 @@ def select(self, population: List[Individual], offspring: List[Individual] = Non
             Population of individuals that will be selected.
         offspring: List[Individual]
             Newly generated individuals to be selected.
-        
+
         Returns
         -------
         selected: List[Individual]
@@ -69,7 +79,7 @@ def step(self, progress: float):
 
             if "amount" in self.params:
                 self.params["amount"] = round(self.params["amount"])
-            
+
             if "maxPopSize" in self.params:
                 self.params["maxPopSize"] = round(self.params["maxPopSize"])
 
@@ -78,14 +88,12 @@ def get_state(self):
         Gets the current state of the algorithm as a dictionary.
         """
 
-        data = {
-            "name": self.name
-        }
+        data = {"name": self.name}
 
         if self.param_scheduler:
             data["param_scheduler"] = self.param_scheduler.get_state()
             data["params"] = self.param_scheduler.get_params()
         elif self.params:
             data["params"] = self.params
-        
-        return data
\ No newline at end of file
+
+        return data
diff --git a/src/metaheuristic_designer/__init__.py b/src/metaheuristic_designer/__init__.py
index a391ee8..4b61848 100644
--- a/src/metaheuristic_designer/__init__.py
+++ b/src/metaheuristic_designer/__init__.py
@@ -1,10 +1,30 @@
+from .utils import RAND_GEN, reset_seed
+
 from .ObjectiveFunc import ObjectiveFunc, ObjectiveVectorFunc, ObjectiveFromLambda
+
 from .Search import Search
+from . import searchMethods
+from .searchMethods import GeneralSearch, MemeticSearch
+
 from .Algorithm import Algorithm
+from . import algorithms
+
 from .Individual import Individual
+
 from .Encoding import Encoding
+from . import encodings
+
 from .Initializer import Initializer
+from . import initializers
+
 from .Operator import Operator
+from . import operators
+
 from .SelectionMethod import SelectionMethod
-from .SelectionMethods import SurvivorSelection, ParentSelection
-from .ParamScheduler import ParamScheduler
\ No newline at end of file
+from . import selectionMethods
+
+from . import simple
+
+from . import benchmarks
+
+from .ParamScheduler import ParamScheduler
diff --git a/src/metaheuristic_designer/algorithms/CRO/CRO.py b/src/metaheuristic_designer/algorithms/CRO/CRO.py
new file mode 100644
index 0000000..9d2bf58
--- /dev/null
+++ b/src/metaheuristic_designer/algorithms/CRO/CRO.py
@@ -0,0 +1,44 @@
+from __future__ import annotations
+import numpy as np
+import random
+from typing import Union, List
+from copy import copy, deepcopy
+from ...Individual import Individual
+from ...operators import OperatorReal, OperatorMeta
+from ...selectionMethods import SurvivorSelection, ParentSelection
+from ..StaticPopulation import StaticPopulation
+from ...ParamScheduler import ParamScheduler
+from ...Algorithm import Algorithm
+
+
+class CRO(StaticPopulation):
+    """
+    Coral Reef Optimization
+    """
+
+    def __init__(
+        self,
+        pop_init: Initializer,
+        mutate: Operator,
+        cross: Operator,
+        params: Union[ParamScheduler, dict] = {},
+        name: str = "CRO",
+    ):
+        evolve_op = OperatorMeta("Branch", [cross, mutate], {"p": params["Fb"]})
+
+        selection_op = SurvivorSelection(
+            "CRO",
+            {
+                "Fd": params["Fd"],
+                "Pd": params["Pd"],
+                "attempts": params["attempts"],
+                "maxPopSize": pop_init.pop_size,
+            },
+        )
+
+        pop_init = deepcopy(pop_init)
+        pop_init.pop_size = round(pop_init.pop_size * params["rho"])
+
+        super().__init__(
+            pop_init, evolve_op, params=params, selection_op=selection_op, name=name
+        )
diff --git a/src/metaheuristic_designer/Algorithms/CRO/CRO_SL.py b/src/metaheuristic_designer/algorithms/CRO/CRO_SL.py
similarity index 65%
rename from src/metaheuristic_designer/Algorithms/CRO/CRO_SL.py
rename to src/metaheuristic_designer/algorithms/CRO/CRO_SL.py
index cc18976..e00eeae 100644
--- a/src/metaheuristic_designer/Algorithms/CRO/CRO_SL.py
+++ b/src/metaheuristic_designer/algorithms/CRO/CRO_SL.py
@@ -4,8 +4,8 @@
 from typing import Union, List
 from copy import copy, deepcopy
 from ...Individual import Individual
-from ...Operators import OperatorReal, OperatorMeta
-from ...SelectionMethods import SurvivorSelection, ParentSelection
+from ...operators import OperatorReal, OperatorMeta
+from ...selectionMethods import SurvivorSelection, ParentSelection
 from ..StaticPopulation import StaticPopulation
 from ...ParamScheduler import ParamScheduler
 from ...Algorithm import Algorithm
@@ -17,7 +17,13 @@ class CRO_SL(Algorithm):
     Coral Reef Optimization with Substrate Layers
     """
 
-    def __init__(self, pop_init: Initializer, operator_list: List[Operator], params: Union[ParamScheduler, dict] = None, name: str = "CRO-SL"):
+    def __init__(
+        self,
+        pop_init: Initializer,
+        operator_list: List[Operator],
+        params: Union[ParamScheduler, dict] = None,
+        name: str = "CRO-SL",
+    ):
         pop_init = deepcopy(pop_init)
         pop_init.pop_size = round(pop_init.pop_size * params["rho"])
 
@@ -26,17 +32,24 @@ def __init__(self, pop_init: Initializer, operator_list: List[Operator], params:
         # Hyperparameters of the algorithm
         self.maxpopsize = pop_init.pop_size
         self.operator_list = operator_list
-        self.operator_idx = [i%len(operator_list) for i in range(pop_init.pop_size)]
+        self.operator_idx = [i % len(operator_list) for i in range(pop_init.pop_size)]
 
-        self.selection_op = SurvivorSelection("CRO", {"Fd": params["Fd"], "Pd": params["Pd"], "attempts": params["attempts"], "maxPopSize": pop_init.pop_size})
-    
-    def perturb(self, parent_list, objfunc, progress, history):
+        self.selection_op = SurvivorSelection(
+            "CRO",
+            {
+                "Fd": params["Fd"],
+                "Pd": params["Pd"],
+                "attempts": params["attempts"],
+                "maxPopSize": pop_init.pop_size,
+            },
+        )
+
+    def perturb(self, parent_list, objfunc, **kwargs):
         offspring = []
         for idx, indiv in enumerate(parent_list):
-            
             # Select operator
             op_idx = self.operator_idx[idx]
-            
+
             op = self.operator_list[op_idx]
 
             # Apply operator
@@ -53,11 +66,13 @@ def perturb(self, parent_list, objfunc, progress, history):
             self.best = current_best
 
         return offspring
-    
-    def select_individuals(self, population, offspring, progress=0, history=None):
+
+    def select_individuals(self, population, offspring, **kwargs):
         return self.selection_op(population, offspring)
 
-    def update_params(self, progress=0):
+    def update_params(self, **kwargs):
+        progress = kwargs["progress"]
+
         self.pop_init.pop_size = len(self.population)
 
         for op in self.operator_list:
@@ -65,4 +80,4 @@ def update_params(self, progress=0):
                 op.step(progress)
 
         if isinstance(self.params, ParamScheduler):
-            self.params.step(progress)
\ No newline at end of file
+            self.params.step(progress)
diff --git a/src/metaheuristic_designer/Algorithms/CRO/DPCRO_SL.py b/src/metaheuristic_designer/algorithms/CRO/DPCRO_SL.py
similarity index 72%
rename from src/metaheuristic_designer/Algorithms/CRO/DPCRO_SL.py
rename to src/metaheuristic_designer/algorithms/CRO/DPCRO_SL.py
index 3389a9f..9ecca4c 100644
--- a/src/metaheuristic_designer/Algorithms/CRO/DPCRO_SL.py
+++ b/src/metaheuristic_designer/algorithms/CRO/DPCRO_SL.py
@@ -4,8 +4,8 @@
 from typing import Union, List
 from copy import copy, deepcopy
 from ...Individual import Individual
-from ...Operators import OperatorReal, OperatorMeta
-from ...SelectionMethods import SurvivorSelection, ParentSelection
+from ...operators import OperatorReal, OperatorMeta
+from ...selectionMethods import SurvivorSelection, ParentSelection
 from ..StaticPopulation import StaticPopulation
 from ...ParamScheduler import ParamScheduler
 from ...Algorithm import Algorithm
@@ -22,7 +22,13 @@ class DPCRO_SL(CRO_SL):
     Original implementation in https://github.com/jperezaracil/PyCROSL/
     """
 
-    def __init__(self, pop_init: Initializer, operator_list: List[Operator], params: Union[ParamScheduler, dict] = {}, name: str = "DPCRO-SL"):
+    def __init__(
+        self,
+        pop_init: Initializer,
+        operator_list: List[Operator],
+        params: Union[ParamScheduler, dict] = {},
+        name: str = "DPCRO-SL",
+    ):
         super().__init__(pop_init, operator_list, params=params, name=name)
 
         self.group_subs = params["group_subs"]
@@ -31,8 +37,10 @@ def __init__(self, pop_init: Initializer, operator_list: List[Operator], params:
         self.dyn_steps = params["dyn_steps"]
         self.prob_amp = params["prob_amp"]
 
-        self.operator_idx = random.choices(range(len(self.operator_list)), k=self.maxpopsize)
-        self.operator_weight = [1/len(operator_list)]*len(operator_list)
+        self.operator_idx = random.choices(
+            range(len(self.operator_list)), k=self.maxpopsize
+        )
+        self.operator_weight = [1 / len(operator_list)] * len(operator_list)
 
         if self.dyn_method == "success":
             self.operator_data = [[0] for i in operator_list]
@@ -40,32 +48,31 @@ def __init__(self, pop_init: Initializer, operator_list: List[Operator], params:
             self.operator_data = [[] for i in operator_list]
 
         self.operator_metric_prev = [0 for i in operator_list]
-        
+
         self.larva_count = [0 for i in operator_list]
-        
+
         self.operator_w_history = []
         self.op_steps = 0
-        self.operator_metric = [0]*len(operator_list)
+        self.operator_metric = [0] * len(operator_list)
         self.operator_history = []
-    
-    def perturb(self, parent_list, objfunc, progress, history):
-        offspring = []
 
+    def perturb(self, parent_list, objfunc, **kwargs):
+        offspring = []
 
         divided_population = [[] for i in self.operator_list]
         for idx, indiv in enumerate(parent_list):
             op_idx = self.operator_idx[idx]
             divided_population[op_idx].append(indiv)
 
-
         for idx, indiv in enumerate(parent_list):
-            
             # Select operator
             op_idx = self.operator_idx[idx]
-            
+
             op = self.operator_list[op_idx]
 
-            new_parent_list = divided_population[op_idx] if self.group_subs else parent_list
+            new_parent_list = (
+                divided_population[op_idx] if self.group_subs else parent_list
+            )
 
             # Apply operator
             new_indiv = op(indiv, new_parent_list, objfunc, self.best, self.pop_init)
@@ -85,8 +92,8 @@ def perturb(self, parent_list, objfunc, progress, history):
             self.best = current_best
 
         return offspring
-    
-    def select_individuals(self, population, offspring, progress=0, history=None):
+
+    def select_individuals(self, population, offspring, **kwargs):
         offspring_ids = [i.id for i in offspring]
         new_population = self.selection_op(population, offspring)
         new_ids = [i.id for i in new_population]
@@ -98,21 +105,22 @@ def select_individuals(self, population, offspring, progress=0, history=None):
 
             if off_id in new_ids:
                 self.operator_data[op_idx][0] += 1
-        
+
         return new_population
 
-    def update_params(self, progress=0):
+    def update_params(self, **kwargs):
+        progress = kwargs["progress"]
+
         self._generate_substrates(progress)
-        super().update_params(progress)
-    
+        super().update_params(progress=progress)
+
     def extra_step_info(self):
         print(f"\n\tSubstrate probability:")
         op_names = [i.name for i in self.operator_list]
         weights = [round(i, 6) for i in self.operator_weight]
         adjust = max([len(i) for i in op_names])
         for idx, val in enumerate(op_names):
-            print(f"\t\t{val}:".ljust(adjust+3, " ") + f"{weights[idx]}")
-    
+            print(f"\t\t{val}:".ljust(adjust + 3, " ") + f"{weights[idx]}")
 
     def _operator_metric(self, data):
         result = 0
@@ -122,62 +130,65 @@ def _operator_metric(self, data):
             if self.dyn_metric == "best":
                 result = max(data)
             elif self.dyn_metric == "avg":
-                result = sum(data)/len(data)
+                result = sum(data) / len(data)
             elif self.dyn_metric == "med":
                 data = sorted(data)
                 if len(data) % 2 == 0:
-                    result = (data[len(data)//2-1]+data[len(data)//2])/2
+                    result = (data[len(data) // 2 - 1] + data[len(data) // 2]) / 2
                 else:
-                    result = data[len(data)//2]
+                    result = data[len(data) // 2]
             elif self.dyn_metric == "worse":
                 result = min(data)
-        
+
         return result
-    
+
     def _operator_probability(self, values):
-        # Normalization to avoid passing big values to softmax 
+        # Normalization to avoid passing big values to softmax
         weight = np.array(values)
         weight_sum = np.abs(weight).sum()
         if weight_sum != 0:
-            weight = weight/weight_sum
+            weight = weight / weight_sum
         else:
-            weight = weight/(weight_sum+1e-5)
-        
+            weight = weight / (weight_sum + 1e-5)
+
         # softmax to convert to a probability distribution
         exp_vec = np.exp(weight)
-        amplified_vec = exp_vec**(1/self.prob_amp)
-        
+        amplified_vec = exp_vec ** (1 / self.prob_amp)
+
         # if there are numerical error default repeat with a default value
         if (amplified_vec == 0).any() or not np.isfinite(amplified_vec).all():
             if not self.prob_amp_warned:
-                print("Warning: the probability amplification parameter is too small, defaulting to prob_amp = 1")
+                print(
+                    "Warning: the probability amplification parameter is too small, defaulting to prob_amp = 1"
+                )
                 self.prob_amp_warned = True
-            prob = exp_vec/exp_vec.sum()
+            prob = exp_vec / exp_vec.sum()
         else:
-            prob = amplified_vec/amplified_vec.sum()
+            prob = amplified_vec / amplified_vec.sum()
 
         # If probabilities get too low, equalize them
-        if (prob <= 0.02/len(values)).any():
-            prob += 0.02/len(values)
-            prob = prob/prob.sum()
+        if (prob <= 0.02 / len(values)).any():
+            prob += 0.02 / len(values)
+            prob = prob / prob.sum()
 
         return prob
-    
+
     def _evaluate_operators(self):
         metric = 0
-        
+
         # take reference data for the calculation of the difference of the next evaluation
         if self.dyn_method == "diff":
-            full_data = [data_point for op_data in self.operator_data for data_point in op_data]
+            full_data = [
+                data_point for op_data in self.operator_data for data_point in op_data
+            ]
             metric = self._operator_metric(full_data)
-        
+
         # calculate the value of each operator with the data gathered
         for idx, s_data in enumerate(self.operator_data):
             if self.dyn_method == "success":
-
                 # obtain the rate of success of the larvae
                 if self.larva_count[idx] > 0:
-                    self.operator_metric[idx] = s_data[0]/self.larva_count[idx]
+                    self.operator_metric[idx] = s_data[0] / self.larva_count[idx]
                 else:
                     self.operator_metric[idx] = 0
 
@@ -186,33 +197,35 @@ def _evaluate_operators(self):
                 self.larva_count[idx] = 0
 
             elif self.dyn_method == "fitness" or self.dyn_method == "diff":
-
-                # obtain the value used in the evaluation of the operator 
+                # obtain the value used in the evaluation of the operator
                 self.operator_metric[idx] = self._operator_metric(s_data)
 
                 # Calculate the difference of the fitness in this generation to the previous one and
                 # store the current value for the next evaluation
                 if self.dyn_method == "diff":
-                    self.operator_metric[idx] =  self.operator_metric[idx] - self.operator_metric_prev[idx]
+                    self.operator_metric[idx] = (
+                        self.operator_metric[idx] - self.operator_metric_prev[idx]
+                    )
                     self.operator_metric_prev[idx] = metric
-                
+
                 # Reset data for next iteration
                 self.operator_data[idx] = []
-    
+
     def _generate_substrates(self, progress=0):
         n_operators = len(self.operator_list)
 
-        if progress > self.op_steps/self.dyn_steps:
+        if progress > self.op_steps / self.dyn_steps:
             self.op_steps += 1
             self._evaluate_operators()
-        
+
         # Assign the probability of each operator
         self.operator_weight = self._operator_probability(self.operator_metric)
         self.operator_w_history.append(self.operator_weight)
-        
+
         # Choose each operator with the weights chosen
-        self.operator_idx = random.choices(range(n_operators), 
-                                            weights=self.operator_weight, k=self.maxpopsize)
+        self.operator_idx = random.choices(
+            range(n_operators), weights=self.operator_weight, k=self.maxpopsize
+        )
 
         # save the evaluation of each operator
         self.operator_history.append(np.array(self.operator_metric))
diff --git a/src/metaheuristic_designer/Algorithms/CRO/PCRO_SL.py b/src/metaheuristic_designer/algorithms/CRO/PCRO_SL.py
similarity index 53%
rename from src/metaheuristic_designer/Algorithms/CRO/PCRO_SL.py
rename to src/metaheuristic_designer/algorithms/CRO/PCRO_SL.py
index 3c0f198..7fa4d62 100644
--- a/src/metaheuristic_designer/Algorithms/CRO/PCRO_SL.py
+++ b/src/metaheuristic_designer/algorithms/CRO/PCRO_SL.py
@@ -4,8 +4,8 @@
 from typing import Union, List
 from copy import copy, deepcopy
 from ...Individual import Individual
-from ...Operators import OperatorReal, OperatorMeta
-from ...SelectionMethods import SurvivorSelection, ParentSelection
+from ...operators import OperatorReal, OperatorMeta
+from ...selectionMethods import SurvivorSelection, ParentSelection
 from ..StaticPopulation import StaticPopulation
 from ...ParamScheduler import ParamScheduler
 from ...Algorithm import Algorithm
@@ -22,11 +22,23 @@ class PCRO_SL(CRO_SL):
     Original implementation in https://github.com/jperezaracil/PyCROSL/
     """
 
-    def __init__(self, pop_init: Initializer, operator_list: List[Operator], params: Union[ParamScheduler, dict] = None, name: str = "PCRO-SL"):
+    def __init__(
+        self,
+        pop_init: Initializer,
+        operator_list: List[Operator],
+        params: Union[ParamScheduler, dict] = None,
+        name: str = "PCRO-SL",
+    ):
         super().__init__(pop_init, operator_list, params=params, name=name)
-        self.operator_idx = random.choices(range(len(self.operator_list)), k=self.maxpopsize)
+        self.operator_idx = random.choices(
+            range(len(self.operator_list)), k=self.maxpopsize
+        )
 
-    def update_params(self, progress=0):
-        self.operator_idx = random.choices(range(len(self.operator_list)), k=self.maxpopsize)
+    def update_params(self, **kwargs):
+        progress = kwargs["progress"]
 
-        super().update_params(progress)
\ No newline at end of file
+        self.operator_idx = random.choices(
+            range(len(self.operator_list)), k=self.maxpopsize
+        )
+
+        super().update_params(progress=progress)
diff --git a/src/metaheuristic_designer/Algorithms/CRO/__init__.py b/src/metaheuristic_designer/algorithms/CRO/__init__.py
similarity index 71%
rename from src/metaheuristic_designer/Algorithms/CRO/__init__.py
rename to src/metaheuristic_designer/algorithms/CRO/__init__.py
index 18bccfc..63fb1c4 100644
--- a/src/metaheuristic_designer/Algorithms/CRO/__init__.py
+++ b/src/metaheuristic_designer/algorithms/CRO/__init__.py
@@ -1,4 +1,4 @@
 from .CRO import CRO
 from .CRO_SL import CRO_SL
 from .PCRO_SL import PCRO_SL
-from .DPCRO_SL import DPCRO_SL
\ No newline at end of file
+from .DPCRO_SL import DPCRO_SL
diff --git a/src/metaheuristic_designer/algorithms/Classic/CMA_ES.py b/src/metaheuristic_designer/algorithms/Classic/CMA_ES.py
new file mode 100644
index 0000000..be21196
--- /dev/null
+++ b/src/metaheuristic_designer/algorithms/Classic/CMA_ES.py
@@ -0,0 +1,43 @@
+from __future__ import annotations
+from ..StaticPopulation import StaticPopulation
+from ...Operators import OperatorReal, OperatorMeta
+from ...SelectionMethods import SurvivorSelection, ParentSelection
+from ...Encodings import CMAEncoding
+
+
+class CMA_ES(StaticPopulation):
+    def __init__(
+        self,
+        pop_init: Initializer,
+        mutation_op: Operator,
+        cross_op: Operator,
+        parent_sel_op: ParentSelection,
+        selection_op: SurvivorSelection,
+        params: Union[ParamScheduler, dict] = {},
+        name: str = "ES",
+    ):
+        parent_select = ParentSelection("Nothing")
+        selection = SurvivorSelection("(m+n)")
+
+        cross = OperatorReal("Multicross", {"N": params["HMS"]})
+        mutate1 = OperatorReal(
+            "MutNoise",
+            {
+                "method": "Gauss",
+                "F": params["BW"],
+                "Cr": params["HMCR"] * params["PAR"],
+            },
+        )
+        rand1 = OperatorReal("RandomMask", {"Cr": 1 - params["HMCR"]})
+
+        mutate = OperatorMeta("Sequence", [mutate1, rand1])
+
+        super().__init__(
+            pop_init, mutate, cross, parent_select, selection, params, name
+        )
+
+    def initialize(self, objfunc):
+        objfunc.encoding = CMAEncoding(
+            self.params["nparams"], pre_encoding=objfunc.encoding
+        )
+        super().initialize(objfunc)
diff --git a/src/metaheuristic_designer/algorithms/Classic/DE.py b/src/metaheuristic_designer/algorithms/Classic/DE.py
new file mode 100644
index 0000000..c425f8f
--- /dev/null
+++ b/src/metaheuristic_designer/algorithms/Classic/DE.py
@@ -0,0 +1,25 @@
+from __future__ import annotations
+from typing import Union, List
+from ...selectionMethods import SurvivorSelection
+from ..StaticPopulation import StaticPopulation
+
+
+class DE(StaticPopulation):
+    """
+    Differential evolution
+    """
+
+    def __init__(
+        self,
+        pop_init: Initializer,
+        de_op: Operator,
+        params: Union[ParamScheduler, dict] = {},
+        selection_op: SurvivorSelection = None,
+        name: str = "DE",
+    ):
+        if selection_op is None:
+            selection_op = SurvivorSelection("One-to-one")
+
+        super().__init__(
+            pop_init, de_op, selection_op=selection_op, params=params, name=name
+        )
diff --git a/src/metaheuristic_designer/algorithms/Classic/ES.py b/src/metaheuristic_designer/algorithms/Classic/ES.py
new file mode 100644
index 0000000..4269b29
--- /dev/null
+++ b/src/metaheuristic_designer/algorithms/Classic/ES.py
@@ -0,0 +1,33 @@
+from __future__ import annotations
+from typing import Union, List
+from ...operators import OperatorMeta
+from ..VariablePopulation import VariablePopulation
+
+
+class ES(VariablePopulation):
+    """
+    Evolution strategy
+    """
+
+    def __init__(
+        self,
+        pop_init: Initializer,
+        mutation_op: Operator,
+        cross_op: Operator,
+        parent_sel_op: ParentSelection,
+        selection_op: SurvivorSelection,
+        params: Union[ParamScheduler, dict] = {},
+        name: str = "ES",
+    ):
+        offspring_size = params.get("offspringSize", pop_init.pop_size)
+        evolve_op = OperatorMeta("Sequence", [mutation_op, cross_op])
+
+        super().__init__(
+            pop_init,
+            evolve_op,
+            parent_sel_op=parent_sel_op,
+            selection_op=selection_op,
+            n_offspring=offspring_size,
+            params=params,
+            name=name,
+        )
diff --git a/src/metaheuristic_designer/algorithms/Classic/GA.py b/src/metaheuristic_designer/algorithms/Classic/GA.py
new file mode 100644
index 0000000..870f84a
--- /dev/null
+++ b/src/metaheuristic_designer/algorithms/Classic/GA.py
@@ -0,0 +1,50 @@
+from __future__ import annotations
+import numpy as np
+from typing import Union, List
+from ...operators import OperatorReal, OperatorMeta
+from ..VariablePopulation import VariablePopulation
+
+
+class GA(VariablePopulation):
+    """
+    Genetic algorithm
+    """
+
+    def __init__(
+        self,
+        pop_init: Initializer,
+        mutation_op: Operator,
+        cross_op: Operator,
+        parent_sel_op: ParentSelection,
+        selection_op: SurvivorSelection,
+        params: Union[ParamScheduler, dict] = {},
+        name: str = "GA",
+    ):
+        self.pmut = params.get("pmut", 0.1)
+        self.pcross = params.get("pcross", 0.9)
+
+        null_operator = OperatorReal("Nothing")
+
+        prob_mut_op = OperatorMeta(
+            "Branch", [mutation_op, null_operator], {"p": self.pmut}
+        )
+        prob_cross_op = OperatorMeta(
+            "Branch", [cross_op, null_operator], {"p": self.pcross}
+        )
+
+        evolve_op = OperatorMeta("Sequence", [prob_mut_op, prob_cross_op])
+
+        super().__init__(
+            pop_init,
+            evolve_op,
+            parent_sel_op=parent_sel_op,
+            selection_op=selection_op,
+            n_offspring=pop_init.pop_size,
+            params=params,
+            name=name,
+        )
+
+    def extra_step_info(self):
+        popul_matrix = np.array(list(map(lambda x: x.genotype, self.population)))
+        divesity = popul_matrix.std(axis=1).mean()
+        print(f"\tdiversity: {divesity:0.3}")
diff --git a/src/metaheuristic_designer/Algorithms/Classic/HS.py b/src/metaheuristic_designer/algorithms/Classic/HS.py
similarity index 59%
rename from src/metaheuristic_designer/Algorithms/Classic/HS.py
rename to src/metaheuristic_designer/algorithms/Classic/HS.py
index 3a3909f..bc67075 100644
--- a/src/metaheuristic_designer/Algorithms/Classic/HS.py
+++ b/src/metaheuristic_designer/algorithms/Classic/HS.py
@@ -1,36 +1,47 @@
 from __future__ import annotations
 from typing import Union, List
-from ...Operators import OperatorReal, OperatorMeta
-from ...SelectionMethods import SurvivorSelection, ParentSelection
+from ...operators import OperatorReal, OperatorMeta
+from ...selectionMethods import SurvivorSelection, ParentSelection
 from ..VariablePopulation import VariablePopulation
 
+
 class HS(VariablePopulation):
     """
     Harmony search
     """
 
-    def __init__(self, pop_init: Initializer, params: Union[ParamScheduler, dict] = {}, name: str = "HS"):
+    def __init__(
+        self,
+        pop_init: Initializer,
+        params: Union[ParamScheduler, dict] = {},
+        name: str = "HS",
+    ):
         parent_sel_op = ParentSelection("Nothing")
         selection_op = SurvivorSelection("(m+n)")
 
-
         HSM = pop_init.pop_size
         cross = OperatorReal("Multicross", {"Nindiv": HSM})
 
-        mutate1 = OperatorReal("MutNoise", {"method": "Gauss", "F": params["BW"], "Cr": params["HMCR"] * params["PAR"]})
+        mutate1 = OperatorReal(
+            "MutNoise",
+            {
+                "method": "Gauss",
+                "F": params["BW"],
+                "Cr": params["HMCR"] * params["PAR"],
+            },
+        )
         rand1 = OperatorReal("RandomMask", {"Cr": 1 - params["HMCR"]})
 
         mutate = OperatorMeta("Sequence", [mutate1, rand1])
 
         evolve_op = OperatorMeta("Sequence", [cross, mutate])
-        
+
         super().__init__(
-            pop_init, 
+            pop_init,
             evolve_op,
             parent_sel_op=parent_sel_op,
             selection_op=selection_op,
             n_offspring=1,
             params=params,
-            name=name
+            name=name,
         )
-
diff --git a/src/metaheuristic_designer/Algorithms/Classic/PSO.py b/src/metaheuristic_designer/algorithms/Classic/PSO.py
similarity index 51%
rename from src/metaheuristic_designer/Algorithms/Classic/PSO.py
rename to src/metaheuristic_designer/algorithms/Classic/PSO.py
index 44a8f6a..b8fb36d 100644
--- a/src/metaheuristic_designer/Algorithms/Classic/PSO.py
+++ b/src/metaheuristic_designer/algorithms/Classic/PSO.py
@@ -1,8 +1,8 @@
 from __future__ import annotations
 import numpy as np
 from typing import Union, List
-from ...Operators import OperatorReal
-from ...SelectionMethods import SurvivorSelection
+from ...operators import OperatorReal
+from ...selectionMethods import SurvivorSelection
 from ..StaticPopulation import StaticPopulation
 from ...ParamScheduler import ParamScheduler
 
@@ -12,21 +12,30 @@ class PSO(StaticPopulation):
     Particle swarm optimization
     """
 
-    def __init__(self, pop_init: Initializer, params: Union[ParamScheduler, dict] = {}, pso_op: Operator = None, name: str = "PSO"):
+    def __init__(
+        self,
+        pop_init: Initializer,
+        params: Union[ParamScheduler, dict] = {},
+        pso_op: Operator = None,
+        name: str = "PSO",
+    ):
         if pso_op is None:
-            w = params["w"] if "w" in params else 0.7
-            c1 = params["c1"] if "c1" in params else 1.5
-            c2 = params["c2"] if "c2" in params else 1.5
-            pso_op = OperatorReal("PSO", ParamScheduler("Linear", {"w": w, "c1": c1, "c2": c2}))
+            pso_op = OperatorReal(
+                "PSO",
+                ParamScheduler(
+                    "Linear",
+                    {
+                        "w": params.get("w", 0.7),
+                        "c1": params.get("c1", 1.5),
+                        "c2": params.get("c2", 1.5),
+                    },
+                ),
+            )
 
         selection_op = SurvivorSelection("Generational")
 
         super().__init__(
-            pop_init,
-            pso_op,
-            selection_op=selection_op,
-            params=params,
-            name=name
+            pop_init, pso_op, selection_op=selection_op, params=params, name=name
         )
 
     def extra_step_info(self):
diff --git a/src/metaheuristic_designer/Algorithms/Classic/RandomSearch.py b/src/metaheuristic_designer/algorithms/Classic/RandomSearch.py
similarity index 52%
rename from src/metaheuristic_designer/Algorithms/Classic/RandomSearch.py
rename to src/metaheuristic_designer/algorithms/Classic/RandomSearch.py
index 71c1c81..96ccb43 100644
--- a/src/metaheuristic_designer/Algorithms/Classic/RandomSearch.py
+++ b/src/metaheuristic_designer/algorithms/Classic/RandomSearch.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 from typing import Union, List
-from ...Operators import OperatorReal, OperatorMeta
+from ...operators import OperatorReal, OperatorMeta
 from ..HillClimb import HillClimb
 
 
@@ -10,11 +10,5 @@ class RandomSearch(HillClimb):
     """
 
     def __init__(self, pop_init, name="RandomSearch"):
-        random_op = OperatorReal("Random", {})
-        super().__init__(
-            pop_init,
-            random_op,
-            name=name
-        )
-
-
+        random_op = OperatorReal("Random")
+        super().__init__(pop_init, random_op, name=name)
diff --git a/src/metaheuristic_designer/Algorithms/Classic/SA.py b/src/metaheuristic_designer/algorithms/Classic/SA.py
similarity index 71%
rename from src/metaheuristic_designer/Algorithms/Classic/SA.py
rename to src/metaheuristic_designer/algorithms/Classic/SA.py
index 9c89081..4bb13a3 100644
--- a/src/metaheuristic_designer/Algorithms/Classic/SA.py
+++ b/src/metaheuristic_designer/algorithms/Classic/SA.py
@@ -13,21 +13,28 @@ class SA(Algorithm):
     Simulated annealing
     """
 
-    def __init__(self, pop_init: Initializer, perturb_op: Operator, params: Union[ParamScheduler, dict] = {}, name: str = "SA"):
-        self.iter = params["iter"] if "iter" in params else 100
-        self.temp_init = params["temp_init"] if "temp_init" in params else 100
+    def __init__(
+        self,
+        pop_init: Initializer,
+        perturb_op: Operator,
+        params: Union[ParamScheduler, dict] = {},
+        name: str = "SA",
+    ):
+        self.iter = params.get("iter", 100)
+        self.temp_init = params.get("temp_init", 100)
         self.temp = self.temp_init
-        self.alpha = params["alpha"] if "alpha" in params else 0.99
+        self.alpha = params.get("alpha", 0.99)
 
         self.perturb_op = perturb_op
 
         super().__init__(pop_init, params=params, name=name)
-    
 
-    def perturb(self, indiv_list, objfunc, progress=None, history=None):
+    def perturb(self, indiv_list, objfunc, **kwargs):
         indiv = indiv_list[0]
         for j in range(self.iter):
-            new_indiv = self.perturb_op(indiv, indiv_list, objfunc, self.best, self.pop_init)
+            new_indiv = self.perturb_op(
+                indiv, indiv_list, objfunc, self.best, self.pop_init
+            )
             new_indiv.genotype = objfunc.repair_solution(new_indiv.genotype)
 
             # Accept the new solution even if it is worse with a probability
@@ -40,7 +47,9 @@ def perturb(self, indiv_list, objfunc, progress=None, history=None):
 
         return [indiv]
 
-    def update_params(self, progress):
+    def update_params(self, **kwargs):
+        progress = kwargs["progress"]
+
         if isinstance(self.perturb_op, Operator):
             self.perturb_op.step(progress)
 
diff --git a/src/metaheuristic_designer/Algorithms/Classic/__init__.py b/src/metaheuristic_designer/algorithms/Classic/__init__.py
similarity index 73%
rename from src/metaheuristic_designer/Algorithms/Classic/__init__.py
rename to src/metaheuristic_designer/algorithms/Classic/__init__.py
index fa37456..12cbbef 100644
--- a/src/metaheuristic_designer/Algorithms/Classic/__init__.py
+++ b/src/metaheuristic_designer/algorithms/Classic/__init__.py
@@ -4,4 +4,6 @@
 from .PSO import PSO
 from .DE import DE
 from .RandomSearch import RandomSearch
-from .HS import HS
\ No newline at end of file
+from .HS import HS
+
+# from .CMA_ES import CMA_ES
diff --git a/src/metaheuristic_designer/Algorithms/HillClimb.py b/src/metaheuristic_designer/algorithms/HillClimb.py
similarity index 67%
rename from src/metaheuristic_designer/Algorithms/HillClimb.py
rename to src/metaheuristic_designer/algorithms/HillClimb.py
index 44b7338..245bb8c 100644
--- a/src/metaheuristic_designer/Algorithms/HillClimb.py
+++ b/src/metaheuristic_designer/algorithms/HillClimb.py
@@ -4,8 +4,8 @@
 from ..ParamScheduler import ParamScheduler
 from ..Algorithm import Algorithm
 from ..Operator import Operator
-from ..Operators import OperatorReal
-from ..SelectionMethods import SurvivorSelection
+from ..operators import OperatorReal
+from ..selectionMethods import SurvivorSelection
 
 
 class HillClimb(Algorithm):
@@ -13,9 +13,15 @@ class HillClimb(Algorithm):
     Hill Climbing algorithm
     """
 
-    def __init__(self, pop_init: Initializer, perturb_op: Operator = None, selection_op: SurvivorSelection = None,
-                 params: Union[ParamScheduler, dict] = {}, name: str = "HillClimb"):
-        self.iterations = params["iters"] if "iters" in params else 1
+    def __init__(
+        self,
+        pop_init: Initializer,
+        perturb_op: Operator = None,
+        selection_op: SurvivorSelection = None,
+        params: Union[ParamScheduler, dict] = {},
+        name: str = "HillClimb",
+    ):
+        self.iterations = params.get("iters", 1)
 
         if perturb_op is None:
             perturb_op = OperatorReal("Nothing")
@@ -27,15 +33,15 @@ def __init__(self, pop_init: Initializer, perturb_op: Operator = None, selection
 
         super().__init__(pop_init, params=params, name=name)
 
-    def perturb(self, indiv_list, objfunc, progress=0, history=None):
+    def perturb(self, indiv_list, objfunc, **kwargs):
         next_indiv_list = copy(indiv_list)
         for i in range(self.iterations):
-            
             offspring = []
             for indiv in next_indiv_list:
-
                 # Perturb individual
-                new_indiv = self.perturb_op(indiv, next_indiv_list, objfunc, self.best, self.pop_init)
+                new_indiv = self.perturb_op(
+                    indiv, next_indiv_list, objfunc, self.best, self.pop_init
+                )
                 new_indiv.genotype = objfunc.repair_solution(new_indiv.genotype)
 
                 offspring.append(new_indiv)
@@ -46,9 +52,11 @@ def perturb(self, indiv_list, objfunc, progress=0, history=None):
                 self.best = current_best
 
             next_indiv_list = self.selection_op(next_indiv_list, offspring)
-        
+
         return next_indiv_list
 
-    def update_params(self, progress=0):
+    def update_params(self, **kwargs):
+        progress = kwargs["progress"]
+
         if isinstance(self.perturb_op, Operator):
             self.perturb_op.step(progress)
diff --git a/src/metaheuristic_designer/Algorithms/LocalSearch.py b/src/metaheuristic_designer/algorithms/LocalSearch.py
similarity index 67%
rename from src/metaheuristic_designer/Algorithms/LocalSearch.py
rename to src/metaheuristic_designer/algorithms/LocalSearch.py
index 021e4f4..de699fe 100644
--- a/src/metaheuristic_designer/Algorithms/LocalSearch.py
+++ b/src/metaheuristic_designer/algorithms/LocalSearch.py
@@ -4,8 +4,8 @@
 from ..ParamScheduler import ParamScheduler
 from ..Algorithm import Algorithm
 from ..Operator import Operator
-from ..Operators import OperatorReal
-from ..SelectionMethods import SurvivorSelection
+from ..operators import OperatorReal
+from ..selectionMethods import SurvivorSelection
 
 
 class LocalSearch(Algorithm):
@@ -13,10 +13,15 @@ class LocalSearch(Algorithm):
     Local search algorithm
     """
 
-    def __init__(self, pop_init: Initializer, perturb_op: Operator = None, selection_op: SurvivorSelection = None,
-                 params: Union[ParamScheduler, dict] = {}, name: str = "LocalSearch"):
-        
-        self.iterations = params["iters"] if "iters" in params else 100
+    def __init__(
+        self,
+        pop_init: Initializer,
+        perturb_op: Operator = None,
+        selection_op: SurvivorSelection = None,
+        params: Union[ParamScheduler, dict] = {},
+        name: str = "LocalSearch",
+    ):
+        self.iterations = params.get("iters", 100)
 
         if perturb_op is None:
             perturb_op = OperatorReal("Nothing")
@@ -28,15 +33,15 @@ def __init__(self, pop_init: Initializer, perturb_op: Operator = None, selection
 
         super().__init__(pop_init, params=params, name=name)
 
-    def perturb(self, indiv_list, objfunc, progress=0, history=None):
+    def perturb(self, indiv_list, objfunc, **kwargs):
         next_indiv_list = copy(indiv_list)
         for i in range(self.iterations):
-            
             offspring = []
             for indiv in indiv_list:
-
                 # Perturb individual
-                new_indiv = self.perturb_op(indiv, indiv_list, objfunc, self.best, self.pop_init)
+                new_indiv = self.perturb_op(
+                    indiv, indiv_list, objfunc, self.best, self.pop_init
+                )
                 new_indiv.genotype = objfunc.repair_solution(new_indiv.genotype)
 
                 offspring.append(new_indiv)
@@ -47,9 +52,11 @@ def perturb(self, indiv_list, objfunc, progress=0, history=None):
                 self.best = current_best
 
             next_indiv_list = self.selection_op(next_indiv_list, offspring)
-        
+
         return next_indiv_list
 
-    def update_params(self, progress=0):
+    def update_params(self, **kwargs):
+        progress = kwargs["progress"]
+
         if isinstance(self.perturb_op, Operator):
             self.perturb_op.step(progress)
diff --git a/src/metaheuristic_designer/Algorithms/NoSearch.py b/src/metaheuristic_designer/algorithms/NoSearch.py
similarity index 78%
rename from src/metaheuristic_designer/Algorithms/NoSearch.py
rename to src/metaheuristic_designer/algorithms/NoSearch.py
index 862082f..c073bc9 100644
--- a/src/metaheuristic_designer/Algorithms/NoSearch.py
+++ b/src/metaheuristic_designer/algorithms/NoSearch.py
@@ -10,7 +10,5 @@ class NoSearch(Algorithm):
     def __init__(self, pop_init: Initializer, name: str = "No search"):
         super().__init__(pop_init, params={}, name=name)
 
-    def perturb(self, parent_list, pop_init, objfunc, progress=0, history=None):
+    def perturb(self, parent_list, objfunc, **kwargs):
         return parent_list
-
-
diff --git a/src/metaheuristic_designer/Algorithms/StaticPopulation.py b/src/metaheuristic_designer/algorithms/StaticPopulation.py
similarity index 68%
rename from src/metaheuristic_designer/Algorithms/StaticPopulation.py
rename to src/metaheuristic_designer/algorithms/StaticPopulation.py
index 7c8de65..63376cf 100644
--- a/src/metaheuristic_designer/Algorithms/StaticPopulation.py
+++ b/src/metaheuristic_designer/algorithms/StaticPopulation.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 from ..ParamScheduler import ParamScheduler
-from ..SelectionMethods import SurvivorSelection, ParentSelection
+from ..selectionMethods import SurvivorSelection, ParentSelection
 from ..Algorithm import Algorithm
 from ..Operator import Operator
 
@@ -10,15 +10,22 @@ class StaticPopulation(Algorithm):
     Population-based algorithm where each individual is iteratively evolved with a given operator
     """
 
-    def __init__(self, pop_init: Initializer, operator: Operator, parent_sel_op: ParentSelection = None, 
-                 selection_op: SurvivorSelection = None, params: Union[ParamScheduler, dict] = {}, name: str = "Static Population Evolution"):
+    def __init__(
+        self,
+        pop_init: Initializer,
+        operator: Operator,
+        parent_sel_op: ParentSelection = None,
+        selection_op: SurvivorSelection = None,
+        params: Union[ParamScheduler, dict] = {},
+        name: str = "Static Population Evolution",
+    ):
         self.params = params
         self.operator = operator
 
         if parent_sel_op is None:
             parent_sel_op = ParentSelection("Nothing")
         self.parent_sel_op = parent_sel_op
-        
+
         if selection_op is None:
             selection_op = SurvivorSelection("Generational")
         self.selection_op = selection_op
@@ -26,17 +33,17 @@ def __init__(self, pop_init: Initializer, operator: Operator, parent_sel_op: Par
         self.best = None
 
         super().__init__(pop_init, params=params, name=name)
-    
 
-    def select_parents(self, population, progress=0, history=None):
+    def select_parents(self, population, **kwargs):
         return self.parent_sel_op(population)
-    
-    def perturb(self, parent_list, objfunc, progress=0, history=None):
+
+    def perturb(self, parent_list, objfunc, **kwargs):
         offspring = []
         for indiv in parent_list:
-
             # Apply operator
-            new_indiv = self.operator(indiv, parent_list, objfunc, self.best, self.pop_init)
+            new_indiv = self.operator(
+                indiv, parent_list, objfunc, self.best, self.pop_init
+            )
             new_indiv.genotype = objfunc.repair_solution(new_indiv.genotype)
             new_indiv.speed = objfunc.repair_speed(new_indiv.speed)
 
@@ -50,13 +57,15 @@ def perturb(self, parent_list, objfunc, progress=0, history=None):
 
         return offspring
 
-    def select_individuals(self, population, offspring, progress=0, history=None):
+    def select_individuals(self, population, offspring, **kwargs):
         return self.selection_op(population, offspring)
 
-    def update_params(self, progress=0):
+    def update_params(self, **kwargs):
+        progress = kwargs["progress"]
+
         if isinstance(self.operator, Operator):
             self.operator.step(progress)
-        
+
         self.selection_op.step(progress)
 
         if self.param_scheduler:
diff --git a/src/metaheuristic_designer/Algorithms/Modern/RVNS.py b/src/metaheuristic_designer/algorithms/VNS/RVNS.py
similarity index 62%
rename from src/metaheuristic_designer/Algorithms/Modern/RVNS.py
rename to src/metaheuristic_designer/algorithms/VNS/RVNS.py
index 4166b10..ae371bc 100644
--- a/src/metaheuristic_designer/Algorithms/Modern/RVNS.py
+++ b/src/metaheuristic_designer/algorithms/VNS/RVNS.py
@@ -5,10 +5,11 @@
 from ...ParamScheduler import ParamScheduler
 from ...Algorithm import Algorithm
 from ...Operator import Operator
-from ...Operators import OperatorMeta
-from ...SelectionMethods import SurvivorSelection
+from ...operators import OperatorMeta
+from ...selectionMethods import SurvivorSelection
 import time
 
+
 class RVNS(Algorithm):
     """
     Reduced Variable Neighborhood Search
@@ -17,9 +18,14 @@ class RVNS(Algorithm):
         Hansen, P., & Mladenovic, N. (2003). A tutorial on variable neighborhood search. Les Cahiers du GERAD ISSN, 711, 2440.
     """
 
-    def __init__(self, pop_init: Initializer, op_list: List[Operator], selection_op: SurvivorSelection = None,
-                 params: Union[ParamScheduler, dict] = {}, name: str = "RVNS"):
-
+    def __init__(
+        self,
+        pop_init: Initializer,
+        op_list: List[Operator],
+        selection_op: SurvivorSelection = None,
+        params: Union[ParamScheduler, dict] = {},
+        name: str = "RVNS",
+    ):
         self.op_list = op_list
         self.perturb_op = OperatorMeta("Pick", op_list, {"init_idx": 0})
 
@@ -31,16 +37,20 @@ def __init__(self, pop_init: Initializer, op_list: List[Operator], selection_op:
 
         if pop_init.pop_size > 1:
             pop_init.pop_size = 1
-            warnings.warn("The RVNS algorithm work on a single individual. The population size has been set to 1.", stacklevel=2)
+            warnings.warn(
+                "The RVNS algorithm work on a single individual. The population size has been set to 1.",
+                stacklevel=2,
+            )
 
         super().__init__(pop_init, params=params, name=name)
 
-    def perturb(self, indiv_list, objfunc, progress=0, history=None):
+    def perturb(self, indiv_list, objfunc, **kwargs):
         offspring = []
         for indiv in indiv_list:
-
             # Perturb individual
-            new_indiv = self.perturb_op(indiv, indiv_list, objfunc, self.best, self.pop_init)
+            new_indiv = self.perturb_op(
+                indiv, indiv_list, objfunc, self.best, self.pop_init
+            )
             new_indiv.genotype = objfunc.repair_solution(new_indiv.genotype)
 
             offspring.append(new_indiv)
@@ -49,29 +59,34 @@ def perturb(self, indiv_list, objfunc, progress=0, history=None):
         current_best = max(offspring, key=lambda x: x.fitness)
         if self.best.fitness < current_best.fitness:
             self.best = current_best
-        
+
         return offspring
 
-    def select_individuals(self, population, offspring, progress=0, history=None):
+    def select_individuals(self, population, offspring, **kwargs):
         new_population = self.selection_op(population, offspring)
 
         if new_population[0].id == population[0].id:
             self.perturb_op.chosen_idx += 1
         else:
             self.perturb_op.chosen_idx = 0
-        
+
         return new_population
 
-    def update_params(self, progress=0):
+    def update_params(self, **kwargs):
+        progress = kwargs["progress"]
+
         if isinstance(self.perturb_op, Operator):
             self.perturb_op.step(progress)
-        
-        if self.perturb_op.chosen_idx >= len(self.op_list) or self.perturb_op.chosen_idx < 0:
+
+        if (
+            self.perturb_op.chosen_idx >= len(self.op_list)
+            or self.perturb_op.chosen_idx < 0
+        ):
             self.perturb_op.chosen_idx = 0
-    
+
     def extra_step_info(self):
         idx = self.perturb_op.chosen_idx
 
-        print(f"\tCurrent Operator: {idx}/{len(self.op_list)}, {self.op_list[idx].name}")
-        
-        
\ No newline at end of file
+        print(
+            f"\tCurrent Operator: {idx}/{len(self.op_list)}, {self.op_list[idx].name}"
+        )
diff --git a/src/metaheuristic_designer/Algorithms/Modern/VND.py b/src/metaheuristic_designer/algorithms/VNS/VND.py
similarity index 57%
rename from src/metaheuristic_designer/Algorithms/Modern/VND.py
rename to src/metaheuristic_designer/algorithms/VNS/VND.py
index 1052529..30a838d 100644
--- a/src/metaheuristic_designer/Algorithms/Modern/VND.py
+++ b/src/metaheuristic_designer/algorithms/VNS/VND.py
@@ -5,11 +5,12 @@
 from ...ParamScheduler import ParamScheduler
 from ...Algorithm import Algorithm
 from ...Operator import Operator
-from ...Operators import OperatorMeta
-from ...SelectionMethods import SurvivorSelection
+from ...operators import OperatorMeta
+from ...selectionMethods import SurvivorSelection
 from .vns_neighborhood_changes import *
 import time
 
+
 class VND(Algorithm):
     """
     Variable Neighborhood Descent
@@ -18,15 +19,24 @@ class VND(Algorithm):
         Hansen, P., & Mladenovic, N. (2003). A tutorial on variable neighborhood search. Les Cahiers du GERAD ISSN, 711, 2440.
     """
 
-    def __init__(self, pop_init: Initializer, op_list: List[Operator], selection_op: SurvivorSelection = None,
-                 params: Union[ParamScheduler, dict] = {}, name: str = "VND"):
-
-        self.iterations = params["iters"] if "iters" in params else 100
+    def __init__(
+        self,
+        pop_init: Initializer,
+        op_list: List[Operator],
+        selection_op: SurvivorSelection = None,
+        params: Union[ParamScheduler, dict] = {},
+        name: str = "VND",
+    ):
+        self.iterations = params.get("iters", 100)
 
         self.op_list = op_list
         self.perturb_op = OperatorMeta("Pick", op_list, {"init_idx": 0})
 
-        self.nchange = NeighborhoodChange.from_str(params["nchange"]) if "nchange" in params else NeighborhoodChange.SEQ
+        self.nchange = (
+            NeighborhoodChange.from_str(params["nchange"])
+            if "nchange" in params
+            else NeighborhoodChange.SEQ
+        )
 
         self.current_op = 0
 
@@ -38,19 +48,22 @@ def __init__(self, pop_init: Initializer, op_list: List[Operator], selection_op:
 
         if pop_init.pop_size > 1:
             pop_init.pop_size = 1
-            warnings.warn("The RVNS algorithm work on a single individual. The population size has been set to 1.", stacklevel=2)
+            warnings.warn(
+                "The RVNS algorithm work on a single individual. The population size has been set to 1.",
+                stacklevel=2,
+            )
 
         super().__init__(pop_init, params=params, name=name)
 
-    def perturb(self, indiv_list, objfunc, progress=0, history=None):
+    def perturb(self, indiv_list, objfunc, **kwargs):
         next_indiv_list = copy(indiv_list)
         for i in range(self.iterations):
-            
             offspring = []
             for indiv in indiv_list:
-
                 # Perturb individual
-                new_indiv = self.perturb_op(indiv, indiv_list, objfunc, self.best, self.pop_init)
+                new_indiv = self.perturb_op(
+                    indiv, indiv_list, objfunc, self.best, self.pop_init
+                )
                 new_indiv.genotype = objfunc.repair_solution(new_indiv.genotype)
 
                 offspring.append(new_indiv)
@@ -61,27 +74,34 @@ def perturb(self, indiv_list, objfunc, progress=0, history=None):
                 self.best = current_best
 
             next_indiv_list = self.inner_selection_op(next_indiv_list, offspring)
-        
+
         return next_indiv_list
 
-    def select_individuals(self, population, offspring, progress=0, history=None):
+    def select_individuals(self, population, offspring, **kwargs):
         new_population = self.selection_op(population, offspring)
-        
-        self.perturb_op.chosen_idx = next_neighborhood(offspring[0], population[0], self.perturb_op.chosen_idx, self.nchange)
-        
+
+        self.perturb_op.chosen_idx = next_neighborhood(
+            offspring[0], population[0], self.perturb_op.chosen_idx, self.nchange
+        )
+
         return new_population
 
-    def update_params(self, progress=0):
+    def update_params(self, **kwargs):
+        progress = kwargs["progress"]
+
         if isinstance(self.perturb_op, Operator):
             self.perturb_op.step(progress)
-        
-        if self.perturb_op.chosen_idx >= len(self.op_list) or self.perturb_op.chosen_idx < 0:
+
+        if (
+            self.perturb_op.chosen_idx >= len(self.op_list)
+            or self.perturb_op.chosen_idx < 0
+        ):
             self.perturb_op.chosen_idx = 0
-    
+
     def extra_step_info(self):
         idx = self.perturb_op.chosen_idx
 
-        print(f"\tCurrent Operator: {idx}/{len(self.op_list)}, {self.op_list[idx].name}")
+        print(
+            f"\tCurrent Operator: {idx}/{len(self.op_list)}, {self.op_list[idx].name}"
+        )
         # time.sleep(0.25)
-        
-        
\ No newline at end of file
diff --git a/src/metaheuristic_designer/Algorithms/Modern/VNS.py b/src/metaheuristic_designer/algorithms/VNS/VNS.py
similarity index 51%
rename from src/metaheuristic_designer/Algorithms/Modern/VNS.py
rename to src/metaheuristic_designer/algorithms/VNS/VNS.py
index e41ebba..5672811 100644
--- a/src/metaheuristic_designer/Algorithms/Modern/VNS.py
+++ b/src/metaheuristic_designer/algorithms/VNS/VNS.py
@@ -5,23 +5,35 @@
 from ...ParamScheduler import ParamScheduler
 from ...Algorithm import Algorithm
 from ...Operator import Operator
-from ...Operators import OperatorMeta
-from ...SelectionMethods import SurvivorSelection
+from ...operators import OperatorMeta
+from ...selectionMethods import SurvivorSelection
 from .vns_neighborhood_changes import *
 
+
 class VNS(Algorithm):
     """
     Variable neighborhood search
     """
 
-    def __init__(self, pop_init: Initializer, op_list: List[Operator], local_search = Algorithm, selection_op: SurvivorSelection = None, params: Union[ParamScheduler, dict] = {}, name: str = "VNS"):
-
-        self.iterations = params["iters"] if "iters" in params else 100
+    def __init__(
+        self,
+        pop_init: Initializer,
+        op_list: List[Operator],
+        local_search=Algorithm,
+        selection_op: SurvivorSelection = None,
+        params: Union[ParamScheduler, dict] = {},
+        name: str = "VNS",
+    ):
+        self.iterations = params.get("iters", 100)
 
         self.op_list = op_list
         self.perturb_op = OperatorMeta("Pick", op_list, {"init_idx": 0})
 
-        self.nchange = NeighborhoodChange.from_str(params["nchange"]) if "nchange" in params else NeighborhoodChange.SEQ
+        self.nchange = (
+            NeighborhoodChange.from_str(params["nchange"])
+            if "nchange" in params
+            else NeighborhoodChange.SEQ
+        )
 
         self.local_search = local_search
 
@@ -31,34 +43,44 @@ def __init__(self, pop_init: Initializer, op_list: List[Operator], local_search
 
         if pop_init.pop_size > 1:
             pop_init.pop_size = 1
-            warnings.warn("The VNS algorithm work on a single individual. The population size has been set to 1.", stacklevel=2)
+            warnings.warn(
+                "The VNS algorithm work on a single individual. The population size has been set to 1.",
+                stacklevel=2,
+            )
 
         super().__init__(pop_init, params=params, name=name)
-    
+
     def initialize(self, objfunc):
         super().initialize(objfunc)
         self.local_search.initialize(objfunc)
 
-    def perturb(self, indiv_list, objfunc, progress=0, history=None):   
+    def perturb(self, indiv_list, objfunc, **kwargs):
         offspring = []
         for indiv in indiv_list:
-
             # Perturb individual
-            new_indiv = self.perturb_op(indiv, indiv_list, objfunc, self.best, self.pop_init)
+            new_indiv = self.perturb_op(
+                indiv, indiv_list, objfunc, self.best, self.pop_init
+            )
             new_indiv.genotype = objfunc.repair_solution(new_indiv.genotype)
 
             # Local search
             population = [new_indiv]
             self.local_search.perturb_op = self.perturb_op
             for _ in range(self.iterations):
-                parents, _ = self.local_search.select_parents(population, progress, history)
+                parents, _ = self.local_search.select_parents(
+                    population, progress, history
+                )
 
-                offspring = self.local_search.perturb(parents, objfunc, progress, history)
+                offspring = self.local_search.perturb(
+                    parents, objfunc, progress, history
+                )
 
-                population = self.local_search.select_individuals(population, offspring, progress, history)
+                population = self.local_search.select_individuals(
+                    population, offspring, progress, history
+                )
 
                 self.local_search.update_params()
-            
+
             new_indiv = self.local_search.population[0]
 
             offspring.append(new_indiv)
@@ -67,24 +89,33 @@ def perturb(self, indiv_list, objfunc, progress=0, history=None):
         current_best = max(offspring, key=lambda x: x.fitness)
         if self.best.fitness < current_best.fitness:
             self.best = current_best
-        
+
         return offspring
-    
-    def select_individuals(self, population, offspring, progress=0, history=None):
+
+    def select_individuals(self, population, offspring, **kwargs):
         new_population = self.selection_op(population, offspring)
-        
-        self.perturb_op.chosen_idx = next_neighborhood(offspring[0], population[0], self.perturb_op.chosen_idx, self.nchange)
-        
+
+        self.perturb_op.chosen_idx = next_neighborhood(
+            offspring[0], population[0], self.perturb_op.chosen_idx, self.nchange
+        )
+
         return new_population
 
-    def update_params(self, progress=0):
+    def update_params(self, **kwargs):
+        progress = kwargs["progress"]
+
         if isinstance(self.perturb_op, Operator):
             self.perturb_op.step(progress)
-        
-        if self.perturb_op.chosen_idx >= len(self.op_list) or self.perturb_op.chosen_idx < 0:
+
+        if (
+            self.perturb_op.chosen_idx >= len(self.op_list)
+            or self.perturb_op.chosen_idx < 0
+        ):
             self.perturb_op.chosen_idx = 0
-    
+
     def extra_step_info(self):
         idx = self.perturb_op.chosen_idx
 
-        print(f"\tCurrent Operator: {idx}/{len(self.op_list)}, {self.op_list[idx].name}")
\ No newline at end of file
+        print(
+            f"\tCurrent Operator: {idx}/{len(self.op_list)}, {self.op_list[idx].name}"
+        )
diff --git a/src/metaheuristic_designer/Algorithms/Modern/__init__.py b/src/metaheuristic_designer/algorithms/VNS/__init__.py
similarity index 67%
rename from src/metaheuristic_designer/Algorithms/Modern/__init__.py
rename to src/metaheuristic_designer/algorithms/VNS/__init__.py
index eed0b1b..09e5ee2 100644
--- a/src/metaheuristic_designer/Algorithms/Modern/__init__.py
+++ b/src/metaheuristic_designer/algorithms/VNS/__init__.py
@@ -1,3 +1,3 @@
+from .VNS import VNS
 from .RVNS import RVNS
 from .VND import VND
-from .VNS import VNS
\ No newline at end of file
diff --git a/src/metaheuristic_designer/Algorithms/Modern/vns_neighborhood_changes.py b/src/metaheuristic_designer/algorithms/VNS/vns_neighborhood_changes.py
similarity index 94%
rename from src/metaheuristic_designer/Algorithms/Modern/vns_neighborhood_changes.py
rename to src/metaheuristic_designer/algorithms/VNS/vns_neighborhood_changes.py
index c4c3633..8cfcd45 100644
--- a/src/metaheuristic_designer/Algorithms/Modern/vns_neighborhood_changes.py
+++ b/src/metaheuristic_designer/algorithms/VNS/vns_neighborhood_changes.py
@@ -9,11 +9,10 @@ class NeighborhoodChange(Enum):
 
     @staticmethod
     def from_str(str_input):
-
         str_input = str_input.lower()
 
         if str_input not in neigh_change_map:
-            raise ValueError(f"Neighborhood change method \"{str_input}\" not defined")
+            raise ValueError(f'Neighborhood change method "{str_input}" not defined')
 
         return neigh_change_map[str_input]
 
@@ -39,7 +38,7 @@ def next_neighborhood(new_indiv, prev_indiv, chosen_idx, method):
         next_idx = cyclic_n_change(new_indiv, prev_indiv, chosen_idx)
     elif method == NeighborhoodChange.PIPE:
         next_idx = pipe_n_change(new_indiv, prev_indiv, chosen_idx)
-    
+
     return next_idx
 
 
@@ -50,12 +49,14 @@ def sequential_n_change(new_indiv, prev_indiv, chosen_idx):
         next_idx = 0
     else:
         next_idx = chosen_idx + 1
-    
+
     return next_idx
 
+
 def cyclic_n_change(new_indiv, prev_indiv, chosen_idx):
     return chosen_idx + 1
 
+
 def pipe_n_change(new_indiv, prev_indiv, chosen_idx):
     next_idx = None
 
@@ -63,8 +64,5 @@ def pipe_n_change(new_indiv, prev_indiv, chosen_idx):
         next_idx = chosen_idx
     else:
         next_idx = chosen_idx + 1
-    
-    return next_idx
-
-
 
+    return next_idx
diff --git a/src/metaheuristic_designer/Algorithms/VariablePopulation.py b/src/metaheuristic_designer/algorithms/VariablePopulation.py
similarity index 71%
rename from src/metaheuristic_designer/Algorithms/VariablePopulation.py
rename to src/metaheuristic_designer/algorithms/VariablePopulation.py
index 4b26e6d..2fd567c 100644
--- a/src/metaheuristic_designer/Algorithms/VariablePopulation.py
+++ b/src/metaheuristic_designer/algorithms/VariablePopulation.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 from ..ParamScheduler import ParamScheduler
-from ..SelectionMethods import SurvivorSelection, ParentSelection
+from ..selectionMethods import SurvivorSelection, ParentSelection
 from ..Algorithm import Algorithm
 from ..Operator import Operator
 import random
@@ -11,9 +11,16 @@ class VariablePopulation(Algorithm):
     Population-based optimization algorithm where the number of individuals generated is different from the size of the population
     """
 
-    def __init__(self, pop_init: Initializer, operator: Operator, parent_sel_op: ParentSelection = None, 
-                 selection_op: SurvivorSelection = None, n_offspring: int = None, params: Union[ParamScheduler, dict] = {}, 
-                 name: str = "Variable Population Evolution"):
+    def __init__(
+        self,
+        pop_init: Initializer,
+        operator: Operator,
+        parent_sel_op: ParentSelection = None,
+        selection_op: SurvivorSelection = None,
+        n_offspring: int = None,
+        params: Union[ParamScheduler, dict] = {},
+        name: str = "Variable Population Evolution",
+    ):
         self.params = params
         self.operator = operator
 
@@ -24,7 +31,7 @@ def __init__(self, pop_init: Initializer, operator: Operator, parent_sel_op: Par
         if parent_sel_op is None:
             parent_sel_op = ParentSelection("Nothing")
         self.parent_sel_op = parent_sel_op
-        
+
         if selection_op is None:
             selection_op = SurvivorSelection("Generational")
         self.selection_op = selection_op
@@ -32,18 +39,19 @@ def __init__(self, pop_init: Initializer, operator: Operator, parent_sel_op: Par
         self.best = None
 
         super().__init__(pop_init, params=params, name=name)
-    
-    def select_parents(self, population, progress=0, history=None):
+
+    def select_parents(self, population, **kwargs):
         return self.parent_sel_op(population)
-    
-    def perturb(self, parent_list, objfunc, progress=0, history=None):
+
+    def perturb(self, parent_list, objfunc, **kwargs):
         offspring = []
 
         while len(offspring) < self.n_offspring:
-
             # Apply operator
             indiv = random.choice(parent_list)
-            new_indiv = self.operator(indiv, parent_list, objfunc, self.best, self.pop_init)
+            new_indiv = self.operator(
+                indiv, parent_list, objfunc, self.best, self.pop_init
+            )
             new_indiv.genotype = objfunc.repair_solution(new_indiv.genotype)
             new_indiv.speed = objfunc.repair_speed(new_indiv.speed)
 
@@ -57,13 +65,15 @@ def perturb(self, parent_list, objfunc, progress=0, history=None):
 
         return offspring
 
-    def select_individuals(self, population, offspring, progress=0, history=None):
+    def select_individuals(self, population, offspring, **kwargs):
         return self.selection_op(population, offspring)
 
-    def update_params(self, progress=0):
+    def update_params(self, **kwargs):
+        progress = kwargs["progress"]
+
         if isinstance(self.operator, Operator):
             self.operator.step(progress)
-            
+
         if isinstance(self.operator, SurvivorSelection):
             self.selection_op.step(progress)
 
diff --git a/src/metaheuristic_designer/Algorithms/__init__.py b/src/metaheuristic_designer/algorithms/__init__.py
similarity index 92%
rename from src/metaheuristic_designer/Algorithms/__init__.py
rename to src/metaheuristic_designer/algorithms/__init__.py
index 1df96c5..45603b0 100644
--- a/src/metaheuristic_designer/Algorithms/__init__.py
+++ b/src/metaheuristic_designer/algorithms/__init__.py
@@ -8,4 +8,4 @@
 
 from .Classic import *
 from .CRO import *
-from .Modern import *
\ No newline at end of file
+from .VNS import *
diff --git a/src/metaheuristic_designer/benchmarks/__init__.py b/src/metaheuristic_designer/benchmarks/__init__.py
index af8f809..ce8689b 100644
--- a/src/metaheuristic_designer/benchmarks/__init__.py
+++ b/src/metaheuristic_designer/benchmarks/__init__.py
@@ -1,3 +1,3 @@
 from .benchmark_funcs import *
 from .img_funcs import *
-from .classic_problems import *
\ No newline at end of file
+from .classic_problems import *
diff --git a/src/metaheuristic_designer/benchmarks/benchmark_funcs.py b/src/metaheuristic_designer/benchmarks/benchmark_funcs.py
index 7b87eea..e889b0e 100644
--- a/src/metaheuristic_designer/benchmarks/benchmark_funcs.py
+++ b/src/metaheuristic_designer/benchmarks/benchmark_funcs.py
@@ -1,6 +1,8 @@
 import numpy as np
+
 # from numba import jit
 from ..ObjectiveFunc import ObjectiveVectorFunc
+from ..utils import RAND_GEN
 
 
 class MaxOnes(ObjectiveVectorFunc):
@@ -10,7 +12,7 @@ def __init__(self, size, opt="max"):
 
     def objective(self, solution):
         return solution.sum()
-    
+
     def repair_solution(self, solution):
         return (solution >= 0.5).astype(np.int32)
 
@@ -21,10 +23,10 @@ def __init__(self, size, coeff, target, opt="min"):
         self.coeff = coeff
         self.target = target
         super().__init__(self.size, opt, name="Diophantine equation")
-    
+
     def objective(self, solution):
-        return abs((solution*self.coeff).sum() - self.target)
-    
+        return abs((solution * self.coeff).sum() - self.target)
+
     def repair_solution(self, solution):
         return solution.astype(np.int32)
 
@@ -36,7 +38,7 @@ def __init__(self, size, opt="max"):
 
     def objective(self, solution):
         return solution.sum()
-    
+
     def repair_solution(self, solution):
         return np.clip(solution.copy(), 0, 1)
 
@@ -54,7 +56,9 @@ def objective(self, solution):
 class HighCondElliptic(ObjectiveVectorFunc):
     def __init__(self, size, opt="min"):
         self.size = size
-        super().__init__(self.size, opt, -5.12, 5.12, name="High condition elliptic function")
+        super().__init__(
+            self.size, opt, -5.12, 5.12, name="High condition elliptic function"
+        )
 
     def objective(self, solution):
         return _high_cond_elipt_f(solution)
@@ -162,7 +166,9 @@ def objective(self, solution):
 class ExpandedGriewankPlusRosenbrock(ObjectiveVectorFunc):
     def __init__(self, size, opt="min"):
         self.size = size
-        super().__init__(self.size, opt, -100, 100, name="Expanded Griewank + Rosenbrock")
+        super().__init__(
+            self.size, opt, -100, 100, name="Expanded Griewank + Rosenbrock"
+        )
 
     def objective(self, solution):
         return _exp_griewank_plus_rosenbrock(solution)
@@ -181,11 +187,14 @@ class SumPowell(ObjectiveVectorFunc):
     """
     Sum of Powell function
     """
+
     def __init__(self, size, opt="min", lim_min=-1, lim_max=1):
         self.size = size
         self.lim_min = lim_min
         self.lim_max = lim_max
-        super().__init__(self.size, opt, low_lim=lim_min, up_lim=lim_max, name="Sum Powell")
+        super().__init__(
+            self.size, opt, low_lim=lim_min, up_lim=lim_max, name="Sum Powell"
+        )
 
     def objective(self, solution):
         return _sum_powell(solution)
@@ -193,14 +202,21 @@ def objective(self, solution):
     def repair_solution(self, solution, parent=None):
         # bounce back method
         if parent:
-            mask_inf = (solution < self.lim_min) 
-            mask_sup = (solution > self.lim_max)
-            solution[mask_inf] = parent[mask_inf] + np.random.random() * (self.lim_min - parent[mask_inf])
-            solution[mask_sup] = parent[mask_sup] + np.random.random() * (parent[mask_sup] - self.lim_max)
+            mask_inf = solution < self.lim_min
+            mask_sup = solution > self.lim_max
+            solution[mask_inf] = parent[mask_inf] + RAND_GEN.random() * (
+                self.lim_min - parent[mask_inf]
+            )
+            solution[mask_sup] = parent[mask_sup] + RAND_GEN.random() * (
+                parent[mask_sup] - self.lim_max
+            )
         # random in range
         else:
             mask = (solution < self.lim_min) | (solution > self.lim_max)
-            solution[mask] = np.random.random(len(mask[mask==True])) * (self.lim_max - self.lim_min) - self.lim_min
+            solution[mask] = (
+                RAND_GEN.random(len(mask[mask == True])) * (self.lim_max - self.lim_min)
+                - self.lim_min
+            )
         return solution
 
 
@@ -208,26 +224,36 @@ class N4XinSheYang(ObjectiveVectorFunc):
     """
     N4 Xin-She Yang function
     """
+
     def __init__(self, size, opt="min", lim_min=-10, lim_max=10):
         self.size = size
         self.lim_min = lim_min
         self.lim_max = lim_max
-        super().__init__(self.size, opt, low_lim=lim_min, up_lim=lim_max, name="N4 Xin-She Yang")
+        super().__init__(
+            self.size, opt, low_lim=lim_min, up_lim=lim_max, name="N4 Xin-She Yang"
+        )
 
     def objective(self, solution):
         return _n4xinshe_yang(solution)
-    
+
     def repair_solution(self, solution, parent=None):
         # bounce back method
         if parent:
-            mask_inf = (solution < self.lim_min) 
-            mask_sup = (solution > self.lim_max)
-            solution[mask_inf] = parent[mask_inf] + np.random.random() * (self.lim_min - parent[mask_inf])
-            solution[mask_sup] = parent[mask_sup] + np.random.random() * (parent[mask_sup] - self.lim_max)
+            mask_inf = solution < self.lim_min
+            mask_sup = solution > self.lim_max
+            solution[mask_inf] = parent[mask_inf] + RAND_GEN.random() * (
+                self.lim_min - parent[mask_inf]
+            )
+            solution[mask_sup] = parent[mask_sup] + RAND_GEN.random() * (
+                parent[mask_sup] - self.lim_max
+            )
         # random in range
         else:
             mask = (solution < self.lim_min) | (solution > self.lim_max)
-            solution[mask] = np.random.random(len(mask[mask==True])) * (self.lim_max - self.lim_min) - self.lim_min
+            solution[mask] = (
+                RAND_GEN.random(len(mask[mask == True])) * (self.lim_max - self.lim_min)
+                - self.lim_min
+            )
         return solution
 
 
@@ -235,46 +261,62 @@ def repair_solution(self, solution, parent=None):
 def _sphere(solution):
     return (solution**2).sum()
 
+
 # @jit(nopython=True)
 def _high_cond_elipt_f(vect):
-    c = 1.0e6**((np.arange(vect.shape[0])/(vect.shape[0]-1)))
-    return np.sum(c*vect*vect)
+    c = 1.0e6 ** ((np.arange(vect.shape[0]) / (vect.shape[0] - 1)))
+    return np.sum(c * vect * vect)
+
 
 # @jit(nopython=True)
 def _bent_cigar(solution):
-    return solution[0]**2 + 1e6*(solution[1:]**2).sum()
+    return solution[0] ** 2 + 1e6 * (solution[1:] ** 2).sum()
+
 
 # @jit(nopython=True)
 def _discus(solution):
-    return 1e6*solution[0]**2 + (solution[1:]**2).sum()
+    return 1e6 * solution[0] ** 2 + (solution[1:] ** 2).sum()
+
 
 # @jit(nopython=True)
 def _rosenbrock(solution):
-    term1 = solution[1:] - solution[:-1]**2
+    term1 = solution[1:] - solution[:-1] ** 2
     term2 = 1 - solution[:-1]
-    result = 100*term1**2 + term2**2
+    result = 100 * term1**2 + term2**2
     return result.sum()
 
+
 # @jit(nopython=True)
 def _ackley(solution):
     term1 = (solution**2).sum()
-    term1 = -0.2 * np.sqrt(term1/solution.size)
-    term2 = (np.cos(2*np.pi*solution)).sum()/solution.size
+    term1 = -0.2 * np.sqrt(term1 / solution.size)
+    term2 = (np.cos(2 * np.pi * solution)).sum() / solution.size
     return np.exp(1) - 20 * np.exp(term1) - np.exp(term2) + 20
 
-#@jit(nopython=False)
+
+# @jit(nopython=False)
 def _weierstrass(solution, iter=20):
-    return np.sum(np.array([0.5**k * np.cos(2*np.pi*3**k*(solution+0.5)) for k in range(iter)]))
+    return np.sum(
+        np.array(
+            [
+                0.5**k * np.cos(2 * np.pi * 3**k * (solution + 0.5))
+                for k in range(iter)
+            ]
+        )
+    )
+
 
 # @jit(nopython=True)
 def _griewank(solution):
     term1 = (solution**2).sum()
-    term2 = np.prod(np.cos(solution/np.sqrt(np.arange(1, solution.size+1))))
-    return 1 + term1/4000 - term2
+    term2 = np.prod(np.cos(solution / np.sqrt(np.arange(1, solution.size + 1))))
+    return 1 + term1 / 4000 - term2
+
 
 # @jit(nopython=True)
 def _rastrigin(solution, A=10):
-    return (A * len(solution) + (solution**2 - A*np.cos(2*np.pi*solution)).sum())
+    return A * len(solution) + (solution**2 - A * np.cos(2 * np.pi * solution)).sum()
+
 
 # @jit(nopython=True)
 def _mod_schwefel(solution):
@@ -282,70 +324,93 @@ def _mod_schwefel(solution):
     for i in range(solution.size):
         z = solution[i] + 4.209687462275036e2
         if z > 500:
-            fit = fit - (500 - z % 500) * np.sin((500 - z%500)** 0.5)
+            fit = fit - (500 - z % 500) * np.sin((500 - z % 500) ** 0.5)
             tmp = (z - 500) / 100
             fit = fit + tmp * tmp / solution.size
         elif z < -500:
-            fit = fit - (-500 - abs(z) % 500) * np.sin((500 - abs(z)%500)** 0.5)
+            fit = fit - (-500 - abs(z) % 500) * np.sin((500 - abs(z) % 500) ** 0.5)
             tmp = (z + 500) / 100
             fit = fit + tmp * tmp / solution.size
         else:
-            fit = fit - z * np.sin(abs( z )**0.5)
+            fit = fit - z * np.sin(abs(z) ** 0.5)
     return fit + 4.189828872724338e2 * solution.size
 
+
 # @jit(nopython=True)
 def _katsuura(solution):
-    A = 10/solution.size**2
+    A = 10 / solution.size**2
+
+    temp_list = [
+        1
+        + (i + 1)
+        * np.sum(
+            (
+                np.abs(
+                    2 ** (np.arange(1, 32 + 1)) * solution[i]
+                    - np.round(2 ** (np.arange(1, 32 + 1)) * solution[i])
+                )
+                * 2 ** (-np.arange(1, 32 + 1, dtype=float))
+            )
+            ** (10 / solution.size**1.2)
+        )
+        for i in range(solution.size)
+    ]
+    prod_val = np.prod(temp_list)
+    return A * prod_val - A
 
-    temp_list = [1 + (i+1)*np.sum((np.abs(2**(np.arange(1, 32+1))*solution[i]-np.round(2**(np.arange(1, 32+1))*solution[i]))*2**(-np.arange(1, 32+1, dtype=float)))**(10/solution.size**1.2)) for i in range(solution.size)]
-    prod_val = np.prod(temp_list) 
-    return  A * prod_val - A
 
 # @jit(nopython=True)
 def _happy_cat(solution):
-    z = solution+4.189828872724338e2
+    z = solution + 4.189828872724338e2
     r2 = (z * solution).sum()
     s = solution.sum()
-    return np.abs(r2-solution.size)**0.25 + (0.5*r2+s)/solution.size + 0.5
+    return np.abs(r2 - solution.size) ** 0.25 + (0.5 * r2 + s) / solution.size + 0.5
+
 
 # @jit(nopython=True)
 def _hgbat(solution):
-    z = solution+4.189828872724338e2
+    z = solution + 4.189828872724338e2
     r2 = (z * solution).sum()
     s = solution.sum()
-    return np.abs((r2**2-s**2))**0.5 + (0.5*r2 + s)/solution.size + 0.5
+    return np.abs((r2**2 - s**2)) ** 0.5 + (0.5 * r2 + s) / solution.size + 0.5
+
 
 # @jit(nopython=True)
 def _exp_griewank_plus_rosenbrock(solution):
-    z = solution[:-1]+4.189828872724338e2
-    tmp1 = solution[:-1]**2-solution[1:]
+    z = solution[:-1] + 4.189828872724338e2
+    tmp1 = solution[:-1] ** 2 - solution[1:]
     tmp2 = z - 1
-    tmp = 100*tmp1**2 + tmp2**2
-    grw = (tmp**2/4000 - np.cos(tmp) + 1).sum()
+    tmp = 100 * tmp1**2 + tmp2**2
+    grw = (tmp**2 / 4000 - np.cos(tmp) + 1).sum()
 
-    term1 = solution[1:] - solution[:-1]**2
+    term1 = solution[1:] - solution[:-1] ** 2
     term2 = 1 - solution[:-1]
-    ros = (100*term1**2 + term2**2).sum()
-    
-    return grw + ros**2/4000 - np.cos(ros) + 1
+    ros = (100 * term1**2 + term2**2).sum()
+
+    return grw + ros**2 / 4000 - np.cos(ros) + 1
+
 
 # @jit(nopython=True)
 def _exp_shafferF6(solution):
-    term1 = np.sin(np.sqrt(np.sum(solution[:-1]**2 + solution[1:]**2)))**2 - 0.5
-    term2 = 1 + 0.001*(solution[:-1]**2 + solution[1:]**2).sum()
-    temp = 0.5 + term1/term2
+    term1 = np.sin(np.sqrt(np.sum(solution[:-1] ** 2 + solution[1:] ** 2))) ** 2 - 0.5
+    term2 = 1 + 0.001 * (solution[:-1] ** 2 + solution[1:] ** 2).sum()
+    temp = 0.5 + term1 / term2
+
+    term1 = (
+        np.sin(np.sqrt(np.sum((solution.size - 1) ** 2 + solution[0] ** 2))) ** 2 - 0.5
+    )
+    term2 = 1 + 0.001 * ((solution.size - 1) ** 2 + solution[0] ** 2)
 
-    term1 = np.sin(np.sqrt(np.sum((solution.size - 1)**2 + solution[0]**2)))**2 - 0.5
-    term2 = 1 + 0.001*((solution.size - 1)**2 + solution[0]**2)
+    return temp + 0.5 + term1 / term2
 
-    return temp + 0.5 + term1/term2
 
 # @jit(nopython=True)
 def _sum_powell(solution):
-    return (np.abs(solution)**np.arange(2,solution.shape[0]+2)).sum()
+    return (np.abs(solution) ** np.arange(2, solution.shape[0] + 2)).sum()
+
 
 # @jit(nopython=True)
 def _n4xinshe_yang(solution):
     sum_1 = np.exp(-(solution**2).sum())
-    sum_2 = np.exp(-(np.sin(np.sqrt(np.abs(solution)))**2).sum())
-    return (np.sin(solution)**2 - sum_1).sum() * sum_2
\ No newline at end of file
+    sum_2 = np.exp(-(np.sin(np.sqrt(np.abs(solution))) ** 2).sum())
+    return (np.sin(solution) ** 2 - sum_1).sum() * sum_2
diff --git a/src/metaheuristic_designer/benchmarks/classic_problems.py b/src/metaheuristic_designer/benchmarks/classic_problems.py
index 29674df..1f3c93d 100644
--- a/src/metaheuristic_designer/benchmarks/classic_problems.py
+++ b/src/metaheuristic_designer/benchmarks/classic_problems.py
@@ -23,13 +23,15 @@ class ThreeSAT(ObjectiveVectorFunc):
 
     def __init__(self, clauses):
         if not isinstance(clauses, np.ndarray) or clauses.shape[1] != 3:
-            raise ValueError("The caluses must be represented as an array of size (n_clauses, 3).")
-        
+            raise ValueError(
+                "The caluses must be represented as an array of size (n_clauses, 3)."
+            )
+
         self.clauses = clauses
         self.n_vars = np.abs(clauses).max()
 
         super().__init__(self.n_vars, name="3-SAT")
-    
+
     @staticmethod
     def from_cnf_file(path):
         n_vars = 0
@@ -41,18 +43,22 @@ def from_cnf_file(path):
                 if line[0] == "p":
                     n_vars = int(line_splitted[2])
                     n_clauses = int(line_splitted[3])
-                    
+
                 elif line[0] != "c" and "%" not in line and len(line_splitted) >= 3:
                     clauses.append([int(i) for i in line_splitted[:3]])
-        
+
         clauses_arr = np.asarray(clauses)
-        
+
         if len(clauses) != n_clauses:
-            warnings.warn("The number of clauses in the file was incorrect.", stacklevel=2)
+            warnings.warn(
+                "The number of clauses in the file was incorrect.", stacklevel=2
+            )
 
         if np.abs(clauses_arr).max() != n_vars:
-            warnings.warn("The number of variables in the file was incorrect.", stacklevel=2)
-        
+            warnings.warn(
+                "The number of variables in the file was incorrect.", stacklevel=2
+            )
+
         return ThreeSAT(clauses_arr)
 
     def objective(self, solution):
@@ -63,7 +69,7 @@ def objective(self, solution):
         ----------
         solution: ndarray
             A binary vector representing the value of each binary variable.
-        
+
         Returns
         -------
         perc_satisfied:
@@ -72,16 +78,16 @@ def objective(self, solution):
 
         n_satisfied = 0
         for clause in self.clauses:
-            bool_vals = solution[np.abs(clause)-1].astype(bool)
-            satisfied = np.logical_xor(bool_vals, clause < 0) # Flip if negative
+            bool_vals = solution[np.abs(clause) - 1].astype(bool)
+            satisfied = np.logical_xor(bool_vals, clause < 0)  # Flip if negative
             n_satisfied += np.any(satisfied).astype(int)
-        
-        return n_satisfied/self.clauses.shape[0]
+
+        return n_satisfied / self.clauses.shape[0]
 
 
-class Bin_Knapsack_problem(ObjectiveVectorFunc):
+class BinKnapsack(ObjectiveVectorFunc):
     """
-    This is the 0-1 Knapsack problem that consist in choosing from set of elements 
+    This is the 0-1 Knapsack problem that consist in choosing from set of elements
     which have a certain cost and value to maximize the value without reaching a weight threshold.
 
     Parameters
@@ -98,8 +104,10 @@ def __init__(self, cost, value, max_weight):
         cost = np.asarray(cost)
         value = np.asarray(value)
         if cost.size != value.size:
-            raise ValueError("The value vector must have the same dimension as the cost vector.")
-        
+            raise ValueError(
+                "The value vector must have the same dimension as the cost vector."
+            )
+
         self.cost = cost
         self.value = value
         self.max_weight = max_weight
@@ -107,7 +115,7 @@ def __init__(self, cost, value, max_weight):
 
     def objective(self, solution):
         """
-        Calculates the total value of the selection of elements. If the weight is higher 
+        Calculates the total value of the selection of elements. If the weight is higher
         than the maxmimum weight, the value is replaced by the negative weight of the elements.
 
         Parameters
@@ -122,14 +130,14 @@ def objective(self, solution):
         """
 
         valid = np.inner(solution, self.cost) < self.max_weight
-        
+
         if valid:
             result = np.inner(solution, self.value)
         else:
             result = -np.inner(solution, self.cost)
-        
+
         return result
-    
+
     def repair_solution(self, solution):
         return (np.round(solution) != 0).astype(int)
 
@@ -156,9 +164,9 @@ def objective(self, solution):
         solution: ndarray
             A sequence of nodes that will be read from left to right, starting from the
             first node that creates a clique of size 0, checking if adding a new node from
-            the sequence produces a clique of a larger size. If this is not the case the 
+            the sequence produces a clique of a larger size. If this is not the case the
             algorithm ends.
-        
+
         Returns
         -------
         clique_size: int
@@ -174,7 +182,7 @@ def objective(self, solution):
                 idx_i = solution[i]
                 idx_j = solution[n_cliques]
                 is_clique = is_clique and self.adj_mat[idx_i, idx_j] != 0
-            
+
             if is_clique:
                 n_cliques += 1
 
@@ -189,9 +197,8 @@ def objective(self, solution):
         """
         Not implemented.
         """
-    
+
     def repair_solution(self, solution):
         """
         Not implemented.
         """
-        
\ No newline at end of file
diff --git a/src/metaheuristic_designer/benchmarks/img_funcs.py b/src/metaheuristic_designer/benchmarks/img_funcs.py
index eb5b21d..dad5412 100644
--- a/src/metaheuristic_designer/benchmarks/img_funcs.py
+++ b/src/metaheuristic_designer/benchmarks/img_funcs.py
@@ -1,6 +1,7 @@
 from ..ObjectiveFunc import ObjectiveVectorFunc
-from ..Encodings import ImageEncoding
+from ..encodings import ImageEncoding
 import numpy as np
+
 # from numba import jit
 from skimage import metrics
 
@@ -8,63 +9,75 @@
 class ImgApprox(ObjectiveVectorFunc):
     def __init__(self, img_dim, reference, opt="min", img_name=""):
         self.img_dim = tuple(img_dim) + (3,)
-        self.size = img_dim[0]*img_dim[1]*3
-        self.reference = reference.resize((img_dim[0],img_dim[1]))
+        self.size = img_dim[0] * img_dim[1] * 3
+        self.reference = reference.resize((img_dim[0], img_dim[1]))
         self.reference = np.asarray(self.reference)[:, :, :3].astype(np.uint8)
 
         if img_name == "":
             name = "Image approximation"
         else:
-            name = f"Approximating \"{img_name}\""
-        
-        opt="max"
+            name = f'Approximating "{img_name}"'
+
+        opt = "max"
 
         super().__init__(self.size, opt, 0, 256, name=name)
-    
+
     def objective(self, solution):
-        #return imgdistance(solution, self.reference)
-        return sum(metrics.structural_similarity(solution[:,:,i], self.reference[:,:,i]) for i in range(3))/3
-    
+        # return imgdistance(solution, self.reference)
+        return (
+            sum(
+                metrics.structural_similarity(
+                    solution[:, :, i], self.reference[:, :, i]
+                )
+                for i in range(3)
+            )
+            / 3
+        )
+
     def repair_solution(self, solution):
         return np.clip(solution, 0, 255)
 
+
 # @jit(nopython=True)
 def imgdistance(img, reference):
-    return np.sum((img-reference)**2)
+    return np.sum((img - reference) ** 2)
 
 
 class ImgStd(ObjectiveVectorFunc):
     def __init__(self, img_dim, opt="max"):
-        self.size = img_dim[0]*img_dim[1]*3
-        
+        self.size = img_dim[0] * img_dim[1] * 3
+
         if encoding is None:
             encoding = ImageEncoding(img_dim, color=True)
-        
+
         super().__init__(self.size, opt, 0, 256, name="Image standard deviation")
-    
+
     def objective(self, solution):
-        solution_color = solution.reshape([3,-1])
+        solution_color = solution.reshape([3, -1])
         return solution_color.std(axis=1).max()
-    
+
     def repair_solution(self, solution):
         return np.clip(solution, 0, 255).astype(np.uint8)
 
 
 class ImgEntropy(ObjectiveVectorFunc):
     def __init__(self, img_dim, nbins=10, opt="min"):
-        self.size = img_dim[0]*img_dim[1]*3
+        self.size = img_dim[0] * img_dim[1] * 3
         self.nbins = 10
 
         super().__init__(self.size, opt, 0, 256, name="Image entropy")
-    
+
     def objective(self, solution):
         solution_channels = solution.reshape([3, -1])
-        img_hists = [np.histogram(solution_channels[i], bins=np.linspace(0,256,self.nbins))[0] for i in range(3)]
+        img_hists = [
+            np.histogram(solution_channels[i], bins=np.linspace(0, 256, self.nbins))[0]
+            for i in range(3)
+        ]
         img_hists = np.array(img_hists) / solution_channels.shape[1]
         img_hists_no_zeros = img_hists
-        img_hists_no_zeros[img_hists==0] = 1
-        return np.sum(-img_hists*np.log(img_hists_no_zeros))
-    
+        img_hists_no_zeros[img_hists == 0] = 1
+        return np.sum(-img_hists * np.log(img_hists_no_zeros))
+
     def repair_solution(self, solution):
         return np.clip(solution, 0, 255).astype(np.uint8)
 
@@ -72,28 +85,30 @@ def repair_solution(self, solution):
 class ImgExperimental(ObjectiveVectorFunc):
     def __init__(self, img_dim, reference, img_name, opt="min"):
         self.img_dim = tuple(img_dim) + (3,)
-        self.size = img_dim[0]*img_dim[1]*3
-        self.reference = np.asarray(reference.resize([img_dim[0], img_dim[1]]))[:,:,:3].astype(np.uint32)
+        self.size = img_dim[0] * img_dim[1] * 3
+        self.reference = np.asarray(reference.resize([img_dim[0], img_dim[1]]))[
+            :, :, :3
+        ].astype(np.uint32)
 
         super().__init__(self.size, opt, 0, 256, name="Image approx and std")
-    
+
     def objective(self, solution):
         dist = imgdistance(solution, self.reference)
-        dist_norm = dist/(np.sqrt(self.size)*255)
+        dist_norm = dist / (np.sqrt(self.size) * 255)
 
-        solution_rounded = solution//75
-        solution_color = solution_rounded.reshape([3,-1])
+        solution_rounded = solution // 75
+        solution_color = solution_rounded.reshape([3, -1])
         _, counts = np.unique(solution_color, axis=1, return_counts=True)
-        freq = counts/self.size
-        entropy = -(freq*np.log(freq)).sum()
+        freq = counts / self.size
+        entropy = -(freq * np.log(freq)).sum()
 
-        solution_color = solution.reshape([3,-1])
+        solution_color = solution.reshape([3, -1])
         dev = -solution_color.std(axis=1).max()
 
         return dist_norm + dev
-    
+
     def repair_solution(self, solution):
         return np.clip(solution, 0, 255)
-    
+
     def repair_speed(self, solution):
-        return np.clip(solution, -255, 255)
\ No newline at end of file
+        return np.clip(solution, -255, 255)
diff --git a/src/metaheuristic_designer/Encodings/CMAEncoding.py b/src/metaheuristic_designer/encodings/CMAEncoding.py
similarity index 94%
rename from src/metaheuristic_designer/Encodings/CMAEncoding.py
rename to src/metaheuristic_designer/encodings/CMAEncoding.py
index 890ff55..9570d3e 100644
--- a/src/metaheuristic_designer/Encodings/CMAEncoding.py
+++ b/src/metaheuristic_designer/encodings/CMAEncoding.py
@@ -11,7 +11,7 @@ class CMAEncoding(Encoding):
 
     def __init__(self, nparams: int, pre_encoding: Encoding = None):
         self.nparams = nparams
-        
+
         if pre_encoding is None:
             pre_encoding = DefaultDecoder()
         self.pre_encoding = pre_encoding
@@ -23,5 +23,5 @@ def encode(self, phenotype: np.ndarray, param_vec: np.ndarray = None) -> np.ndar
         return np.concatenate(encoded, param_vec)
 
     def decode(self, genotype: np.ndarray) -> np.ndarray:
-        decoded = genotype[:-self.nparams]
+        decoded = genotype[: -self.nparams]
         return self.pre_encoding.decode(decoded)
diff --git a/src/metaheuristic_designer/Encodings/DefaultEncoding.py b/src/metaheuristic_designer/encodings/DefaultEncoding.py
similarity index 100%
rename from src/metaheuristic_designer/Encodings/DefaultEncoding.py
rename to src/metaheuristic_designer/encodings/DefaultEncoding.py
diff --git a/src/metaheuristic_designer/Encodings/ImageEncoding.py b/src/metaheuristic_designer/encodings/ImageEncoding.py
similarity index 100%
rename from src/metaheuristic_designer/Encodings/ImageEncoding.py
rename to src/metaheuristic_designer/encodings/ImageEncoding.py
diff --git a/src/metaheuristic_designer/encodings/LambdaEncoding.py b/src/metaheuristic_designer/encodings/LambdaEncoding.py
new file mode 100644
index 0000000..b7ea5e5
--- /dev/null
+++ b/src/metaheuristic_designer/encodings/LambdaEncoding.py
@@ -0,0 +1,19 @@
+from __future__ import annotations
+import numpy as np
+from ..Encoding import Encoding
+
+
+class LambdaEncoding(Encoding):
+    """
+    Decoder that uses user specified functions
+    """
+
+    def __init__(self, encode_fn: callable, decode_fn: callable):
+        self.encode_fn = encode_fn
+        self.decode_fn = decode_fn
+
+    def encode(self, phenotype: Any) -> Any:
+        return self.encode_fn(phenotype)
+
+    def decode(self, genotype: Any) -> Any:
+        return self.encode_fn(genotype)
diff --git a/src/metaheuristic_designer/Encodings/MatrixEncoding.py b/src/metaheuristic_designer/encodings/MatrixEncoding.py
similarity index 100%
rename from src/metaheuristic_designer/Encodings/MatrixEncoding.py
rename to src/metaheuristic_designer/encodings/MatrixEncoding.py
diff --git a/src/metaheuristic_designer/Encodings/TypeCastEncoding.py b/src/metaheuristic_designer/encodings/TypeCastEncoding.py
similarity index 100%
rename from src/metaheuristic_designer/Encodings/TypeCastEncoding.py
rename to src/metaheuristic_designer/encodings/TypeCastEncoding.py
diff --git a/src/metaheuristic_designer/Encodings/__init__.py b/src/metaheuristic_designer/encodings/__init__.py
similarity index 83%
rename from src/metaheuristic_designer/Encodings/__init__.py
rename to src/metaheuristic_designer/encodings/__init__.py
index 2426a9d..c343637 100644
--- a/src/metaheuristic_designer/Encodings/__init__.py
+++ b/src/metaheuristic_designer/encodings/__init__.py
@@ -3,3 +3,4 @@
 from .ImageEncoding import ImageEncoding
 from .MatrixEncoding import MatrixEncoding
 from .TypeCastEncoding import TypeCastEncoding
+from .LambdaEncoding import LambdaEncoding
diff --git a/src/metaheuristic_designer/Initializers/DirectInitializer.py b/src/metaheuristic_designer/initializers/DirectInitializer.py
similarity index 87%
rename from src/metaheuristic_designer/Initializers/DirectInitializer.py
rename to src/metaheuristic_designer/initializers/DirectInitializer.py
index 153357c..18ec872 100644
--- a/src/metaheuristic_designer/Initializers/DirectInitializer.py
+++ b/src/metaheuristic_designer/initializers/DirectInitializer.py
@@ -19,21 +19,24 @@ class DirectInitializer(Initializer):
         Encoding that will be passed to each individual.
     """
 
-    def __init__(self, default_init: Initializer, solutions: List[Individual], encoding: Encoding = None):
+    def __init__(
+        self,
+        default_init: Initializer,
+        solutions: List[Individual],
+        encoding: Encoding = None,
+    ):
         super().__init__(len(solutions), encoding=encoding)
         self.solutions = solutions
         self.default_init = default_init
-    
+
     def generate_random(self, objfunc):
         return self.default_init.generate_random(objfunc)
-    
+
     def generate_individual(self, objfunc):
         return random.choice(self.solutions)
 
     def generate_population(self, objfunc, n_indiv=None):
         if n_indiv is None:
             n_indiv = self.pop_size
-        
-        return self.solutions[:n_indiv]
-
 
+        return self.solutions[:n_indiv]
diff --git a/src/metaheuristic_designer/Initializers/GaussianInitializer.py b/src/metaheuristic_designer/initializers/GaussianInitializer.py
similarity index 75%
rename from src/metaheuristic_designer/Initializers/GaussianInitializer.py
rename to src/metaheuristic_designer/initializers/GaussianInitializer.py
index 9c87604..ab9f67e 100644
--- a/src/metaheuristic_designer/Initializers/GaussianInitializer.py
+++ b/src/metaheuristic_designer/initializers/GaussianInitializer.py
@@ -3,6 +3,8 @@
 import random
 from ..Initializer import Initializer
 from ..Individual import Individual
+from ..utils import RAND_GEN
+
 
 class GaussianInitializer(Initializer):
     """
@@ -24,39 +26,46 @@ class GaussianInitializer(Initializer):
         Data type used in each of the components of the vector in the individual.
     """
 
-    def __init__(self, genotype_size, g_mean, g_std, pop_size = 1, encoding = None, dtype = float):
+    def __init__(
+        self, genotype_size, g_mean, g_std, pop_size=1, encoding=None, dtype=float
+    ):
         super().__init__(pop_size, encoding)
-        
+
         self.genotype_size = genotype_size
 
         if type(g_mean) in [list, tuple, np.ndarray]:
             if len(g_mean) != genotype_size:
-                raise ValueError(f"If g_mean is a sequence it must be of length {genotype_size}.")
-            
+                raise ValueError(
+                    f"If g_mean is a sequence it must be of length {genotype_size}."
+                )
+
             self.g_mean = g_mean
         else:
             self.g_mean = np.repeat(g_mean, self.genotype_size)
 
         if type(g_std) in [list, tuple, np.ndarray]:
             if len(g_std) != genotype_size:
-                raise ValueError(f"If g_std is a sequence it must be of length {genotype_size}.")
-            
+                raise ValueError(
+                    f"If g_std is a sequence it must be of length {genotype_size}."
+                )
+
             self.g_std = g_std
         else:
             self.g_std = np.repeat(g_std, self.genotype_size)
-        
+
         self.dtype = dtype
 
 
 class GaussianVectorInitializer(GaussianInitializer):
-    
     def generate_random(self, objfunc):
-        new_vector_float = np.random.normal(self.g_mean, self.g_std, size=self.genotype_size)
+        new_vector_float = RAND_GEN.normal(
+            self.g_mean, self.g_std, size=self.genotype_size
+        )
         if self.dtype is int:
             new_vector = np.round(new_vector_float).astype(self.dtype)
         else:
             new_vector = new_vector_float.astype(self.dtype)
-        
+
         return Individual(objfunc, new_vector, encoding=self.encoding)
 
     def generate_individual(self, objfunc):
@@ -65,8 +74,8 @@ def generate_individual(self, objfunc):
 
 class GaussianListInitializer(GaussianInitializer):
     def generate_random(self, objfunc):
-        new_list = [np.random.normal(m, s) for m, s in zip(self.g_mean, self.g_std)]
+        new_list = [RAND_GEN.normal(m, s) for m, s in zip(self.g_mean, self.g_std)]
         return Individual(objfunc, new_list, encoding=self.encoding)
-    
+
     def generate_individual(self, objfunc):
-        return self.generate_random(objfunc)
\ No newline at end of file
+        return self.generate_random(objfunc)
diff --git a/src/metaheuristic_designer/Initializers/LambdaInitializer.py b/src/metaheuristic_designer/initializers/LambdaInitializer.py
similarity index 86%
rename from src/metaheuristic_designer/Initializers/LambdaInitializer.py
rename to src/metaheuristic_designer/initializers/LambdaInitializer.py
index f024ef4..116b26e 100644
--- a/src/metaheuristic_designer/Initializers/LambdaInitializer.py
+++ b/src/metaheuristic_designer/initializers/LambdaInitializer.py
@@ -3,7 +3,7 @@
 import random
 from ..Initializer import Initializer
 from ..Individual import Individual
-from ..Encodings import DefaultEncoding
+from ..encodings import DefaultEncoding
 
 
 class LambdaInitializer(Initializer):
@@ -20,13 +20,15 @@ class LambdaInitializer(Initializer):
         Encoding that will be passed to each individual.
     """
 
-    def __init__(self, generator: callable, pop_size: int = 1, encoding: Encoding = None):
+    def __init__(
+        self, generator: callable, pop_size: int = 1, encoding: Encoding = None
+    ):
         self.pop_size = pop_size
         self.generator = generator
 
         if encoding is None:
             encoding = DefaultEncoding()
         self.encoding = encoding
-    
+
     def generate_random(self, objfunc: ObjectiveFunc) -> Individual:
         return Individual(objfunc, self.generator(), encoding=self.encoding)
diff --git a/src/metaheuristic_designer/Initializers/PermInitializer.py b/src/metaheuristic_designer/initializers/PermInitializer.py
similarity index 86%
rename from src/metaheuristic_designer/Initializers/PermInitializer.py
rename to src/metaheuristic_designer/initializers/PermInitializer.py
index 99c395e..e56e98f 100644
--- a/src/metaheuristic_designer/Initializers/PermInitializer.py
+++ b/src/metaheuristic_designer/initializers/PermInitializer.py
@@ -3,6 +3,7 @@
 import random
 from ..Initializer import Initializer
 from ..Individual import Individual
+from ..utils import RAND_GEN
 
 
 class PermInitializer(Initializer):
@@ -17,13 +18,12 @@ class PermInitializer(Initializer):
         Number of individuals to be generated.
     """
 
-    def __init__(self, genotype_size, pop_size = 1):
+    def __init__(self, genotype_size, pop_size=1):
         self.genotype_size = genotype_size
 
         super().__init__(pop_size, encoding=None)
-    
+
     def generate_random(self, objfunc):
         rang_vec = np.arange(self.genotype_size)
-        np.random.shuffle(rang_vec)
+        RAND_GEN.shuffle(rang_vec)
         return Individual(objfunc, rang_vec)
-
diff --git a/src/metaheuristic_designer/Initializers/SeedInitializer.py b/src/metaheuristic_designer/initializers/SeedInitializer.py
similarity index 89%
rename from src/metaheuristic_designer/Initializers/SeedInitializer.py
rename to src/metaheuristic_designer/initializers/SeedInitializer.py
index 5fc4a2f..35cbc85 100644
--- a/src/metaheuristic_designer/Initializers/SeedInitializer.py
+++ b/src/metaheuristic_designer/initializers/SeedInitializer.py
@@ -19,14 +19,19 @@ class SeedProbInitializer(Initializer):
     insert_prob: float
         Probability of inserting one of the predefined solutions into the population.
     """
-       
-    def __init__(self, default_init: Initializer, solutions: List[Individual], insert_prob: float = 0.1):
+
+    def __init__(
+        self,
+        default_init: Initializer,
+        solutions: List[Individual],
+        insert_prob: float = 0.1,
+    ):
         super().__init__(default_init.pop_size)
 
         self.default_init = default_init
         self.solutions = solutions
         self.insert_prob = insert_prob
-    
+
     def generate_random(self, objfunc):
         return self.default_init.generate_random(objfunc)
 
@@ -51,21 +56,26 @@ class SeedDetermInitializer(Initializer):
     solutions: List[Individual]
         The predefined individuals that will be inserted into the population.
     n_to_insert: float
-        Amount of predefined individuals to insert in the population. 
+        Amount of predefined individuals to insert in the population.
     """
-    
-    def __init__(self, default_init: Initializer, solutions: List[Individual], n_to_insert: int = None):
+
+    def __init__(
+        self,
+        default_init: Initializer,
+        solutions: List[Individual],
+        n_to_insert: int = None,
+    ):
         super().__init__(default_init.pop_size)
 
         self.default_init = default_init
         self.solutions = solutions
-        
+
         self.number_to_insert = n_to_insert
         if n_to_insert is None:
             self.number_to_insert = len(solutions)
-        
+
         self.inserted = 0
-    
+
     def generate_random(self, objfunc):
         return self.default_init.generate_random(objfunc)
 
@@ -75,15 +85,14 @@ def generate_individual(self, objfunc):
             new_indiv = self.solutions[self.inserted % len(self.solutions)]
         else:
             new_indiv = self.default_init.generate_individual(objfunc)
-        
+
         self.inserted += 1
         return new_indiv
-    
+
     def generate_population(self, objfunc, n_indiv=None):
         self.inserted = 0
 
         if n_indiv is None:
             n_indiv = self.pop_size
-    
-        return super().generate_population(objfunc, n_indiv)
 
+        return super().generate_population(objfunc, n_indiv)
diff --git a/src/metaheuristic_designer/Initializers/UniformInitializer.py b/src/metaheuristic_designer/initializers/UniformInitializer.py
similarity index 72%
rename from src/metaheuristic_designer/Initializers/UniformInitializer.py
rename to src/metaheuristic_designer/initializers/UniformInitializer.py
index a031ecf..6840285 100644
--- a/src/metaheuristic_designer/Initializers/UniformInitializer.py
+++ b/src/metaheuristic_designer/initializers/UniformInitializer.py
@@ -3,6 +3,8 @@
 import random
 from ..Initializer import Initializer
 from ..Individual import Individual
+from ..utils import RAND_GEN
+
 
 class UniformInitializer(Initializer):
     """
@@ -13,7 +15,7 @@ class UniformInitializer(Initializer):
     genotype_size: ndarray
         The dimension of the vectors accepted by the objective function.
     low_lim: ndarray or float
-        Lower limit restriction for the vectors. 
+        Lower limit restriction for the vectors.
     up_lim: ndarray or float
         Upper limit restriction for the vectors.
     pop_size: int, optional
@@ -24,33 +26,41 @@ class UniformInitializer(Initializer):
         Data type used in each of the components of the vector in the individual.
     """
 
-    def __init__(self, genotype_size, low_lim, up_lim, pop_size = 1, encoding = None, dtype = float):
+    def __init__(
+        self, genotype_size, low_lim, up_lim, pop_size=1, encoding=None, dtype=float
+    ):
         super().__init__(pop_size, encoding)
-        
+
         self.genotype_size = genotype_size
 
         if type(low_lim) in [list, tuple, np.ndarray]:
             if len(low_lim) != genotype_size:
-                raise ValueError(f"If low_lim is a sequence it must be of length {genotype_size}.")
-            
+                raise ValueError(
+                    f"If low_lim is a sequence it must be of length {genotype_size}."
+                )
+
             self.low_lim = low_lim
         else:
             self.low_lim = np.repeat(low_lim, self.genotype_size)
 
         if type(up_lim) in [list, tuple, np.ndarray]:
             if len(up_lim) != genotype_size:
-                raise ValueError(f"If up_lim is a sequence it must be of length {genotype_size}.")
-            
+                raise ValueError(
+                    f"If up_lim is a sequence it must be of length {genotype_size}."
+                )
+
             self.up_lim = up_lim
         else:
             self.up_lim = np.repeat(up_lim, self.genotype_size)
-        
+
         self.dtype = dtype
 
 
 class UniformVectorInitializer(UniformInitializer):
     def generate_random(self, objfunc):
-        new_vector_float = np.random.uniform(self.low_lim, self.up_lim, size=self.genotype_size)
+        new_vector_float = RAND_GEN.uniform(
+            self.low_lim, self.up_lim, size=self.genotype_size
+        )
         if self.dtype is int:
             new_vector = np.round(new_vector_float).astype(self.dtype)
         else:
@@ -64,8 +74,10 @@ def generate_individual(self, objfunc):
 
 class UniformListInitializer(UniformInitializer):
     def generate_random(self, objfunc):
-        new_list = [np.random.uniform(low, up) for low, up in zip(self.low_lim, self.up_lim)]
+        new_list = [
+            RAND_GEN.uniform(low, up) for low, up in zip(self.low_lim, self.up_lim)
+        ]
         return Individual(objfunc, new_list, encoding=self.encoding)
-    
+
     def generate_individual(self, objfunc):
-        return self.generate_random(objfunc)
\ No newline at end of file
+        return self.generate_random(objfunc)
diff --git a/src/metaheuristic_designer/Initializers/__init__.py b/src/metaheuristic_designer/initializers/__init__.py
similarity index 88%
rename from src/metaheuristic_designer/Initializers/__init__.py
rename to src/metaheuristic_designer/initializers/__init__.py
index e090007..b26f420 100644
--- a/src/metaheuristic_designer/Initializers/__init__.py
+++ b/src/metaheuristic_designer/initializers/__init__.py
@@ -3,4 +3,4 @@
 from .LambdaInitializer import LambdaInitializer
 from .SeedInitializer import SeedDetermInitializer, SeedProbInitializer
 from .DirectInitializer import DirectInitializer
-from .PermInitializer import PermInitializer
\ No newline at end of file
+from .PermInitializer import PermInitializer
diff --git a/src/metaheuristic_designer/Operators/OperatorBinary.py b/src/metaheuristic_designer/operators/OperatorBinary.py
similarity index 87%
rename from src/metaheuristic_designer/Operators/OperatorBinary.py
rename to src/metaheuristic_designer/operators/OperatorBinary.py
index 0ed6cfe..85f8447 100644
--- a/src/metaheuristic_designer/Operators/OperatorBinary.py
+++ b/src/metaheuristic_designer/operators/OperatorBinary.py
@@ -24,11 +24,10 @@ class BinOpMethods(Enum):
 
     @staticmethod
     def from_str(str_input):
-
         str_input = str_input.lower()
 
         if str_input not in bin_ops_map:
-            raise ValueError(f"Binary operator \"{str_input}\" not defined")
+            raise ValueError(f'Binary operator "{str_input}" not defined')
 
         return bin_ops_map[str_input]
 
@@ -70,7 +69,9 @@ class OperatorBinary(Operator):
         Name that is associated with the operator.
     """
 
-    def __init__(self, method: str, params: Union[ParamScheduler, dict] = None, name: str = None):
+    def __init__(
+        self, method: str, params: Union[ParamScheduler, dict] = None, name: str = None
+    ):
         """
         Constructor for the Operator class
         """
@@ -95,7 +96,6 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
             indiv2 = indiv
         else:
             indiv2 = random.choice(others)
-            
 
         if global_best is None:
             global_best = indiv
@@ -103,7 +103,9 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
         params = copy(self.params)
 
         if "Cr" in params and "N" not in params:
-            params["N"] = np.count_nonzero(np.random.random(indiv.genotype.size) < params["Cr"])
+            params["N"] = np.count_nonzero(
+                RAND_GEN.random(indiv.genotype.size) < params["Cr"]
+            )
 
         if "N" in params:
             params["N"] = round(params["N"])
@@ -119,7 +121,9 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
             new_indiv.genotype = cross_mp(new_indiv.genotype, indiv2.genotype.copy())
 
         elif self.method == BinOpMethods.MULTICROSS:
-            new_indiv.genotype = multi_cross(new_indiv.genotype, others, params["Nindiv"])
+            new_indiv.genotype = multi_cross(
+                new_indiv.genotype, others, params["Nindiv"]
+            )
 
         elif self.method == BinOpMethods.PERM:
             new_indiv.genotype = permutation(new_indiv.genotype, params["N"])
@@ -140,10 +144,14 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
             new_indiv = initializer.generate_random(objfunc)
 
         elif self.method == BinOpMethods.RANDOM_MASK:
-            mask_pos = np.hstack([np.ones(params["N"]), np.zeros(new_indiv.genotype.size - params["N"])]).astype(bool)
-            np.random.shuffle(mask_pos)
-
-            new_indiv.genotype[mask_pos] = initializer.generate_random(objfunc).genotype[mask_pos]
+            mask_pos = np.hstack(
+                [np.ones(params["N"]), np.zeros(new_indiv.genotype.size - params["N"])]
+            ).astype(bool)
+            RAND_GEN.shuffle(mask_pos)
+
+            new_indiv.genotype[mask_pos] = initializer.generate_random(
+                objfunc
+            ).genotype[mask_pos]
 
         elif self.method == BinOpMethods.DUMMY:
             new_indiv.genotype = dummy_op(new_indiv.genotype, params["F"])
diff --git a/src/metaheuristic_designer/Operators/OperatorInt.py b/src/metaheuristic_designer/operators/OperatorInt.py
similarity index 73%
rename from src/metaheuristic_designer/Operators/OperatorInt.py
rename to src/metaheuristic_designer/operators/OperatorInt.py
index 3f50f83..77509b7 100644
--- a/src/metaheuristic_designer/Operators/OperatorInt.py
+++ b/src/metaheuristic_designer/operators/OperatorInt.py
@@ -4,6 +4,7 @@
 from copy import copy
 import enum
 from enum import Enum
+from ..utils import RAND_GEN
 
 
 class IntOpMethods(Enum):
@@ -44,11 +45,10 @@ class IntOpMethods(Enum):
 
     @staticmethod
     def from_str(str_input):
-
         str_input = str_input.lower()
 
         if str_input not in int_ops_map:
-            raise ValueError(f"Integer operator \"{str_input}\" not defined")
+            raise ValueError(f'Integer operator "{str_input}" not defined')
 
         return int_ops_map[str_input]
 
@@ -107,7 +107,9 @@ class OperatorInt(Operator):
         Name that is associated with the operator.
     """
 
-    def __init__(self, method: str, params: Union[ParamScheduler, dict] = None, name: str = None):
+    def __init__(
+        self, method: str, params: Union[ParamScheduler, dict] = None, name: str = None
+    ):
         """
         Constructor for the Operator class
         """
@@ -119,12 +121,17 @@ def __init__(self, method: str, params: Union[ParamScheduler, dict] = None, name
 
         self.method = IntOpMethods.from_str(method)
 
-        if self.method in [IntOpMethods.MUTNOISE, IntOpMethods.MUTSAMPLE, IntOpMethods.RANDNOISE, IntOpMethods.RANDSAMPLE]:
+        if self.method in [
+            IntOpMethods.MUTNOISE,
+            IntOpMethods.MUTSAMPLE,
+            IntOpMethods.RANDNOISE,
+            IntOpMethods.RANDSAMPLE,
+        ]:
             self.params["method"] = ProbDist.from_str(self.params["method"])
 
         elif self.method == IntOpMethods.RANDRESET:
             self.params["method"] = ProbDist.UNIFORM
-            
+
             if "Low" not in self.params:
                 self.params["Low"] = 0
 
@@ -138,7 +145,6 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
             indiv2 = indiv
         else:
             indiv2 = random.choice(others)
-            
 
         if global_best is None:
             global_best = indiv
@@ -146,7 +152,9 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
         params = copy(self.params)
 
         if "Cr" in params and "N" not in params:
-            params["N"] = np.count_nonzero(np.random.random(indiv.genotype.size) < params["Cr"])
+            params["N"] = np.count_nonzero(
+                RAND_GEN.random(indiv.genotype.size) < params["Cr"]
+            )
 
         if "N" in params:
             params["N"] = round(params["N"])
@@ -162,13 +170,19 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
             new_indiv.genotype = cross_mp(new_indiv.genotype, indiv2.genotype.copy())
 
         elif self.method == IntOpMethods.WEIGHTED_AVG:
-            new_indiv.genotype = weighted_average(new_indiv.genotype, indiv2.genotype.copy(), params["F"])
+            new_indiv.genotype = weighted_average(
+                new_indiv.genotype, indiv2.genotype.copy(), params["F"]
+            )
 
         elif self.method == IntOpMethods.BLXALPHA:
-            new_indiv.genotype = blxalpha(new_indiv.genotype, indiv2.genotype.copy(), params["Cr"])
+            new_indiv.genotype = blxalpha(
+                new_indiv.genotype, indiv2.genotype.copy(), params["Cr"]
+            )
 
         elif self.method == IntOpMethods.MULTICROSS:
-            new_indiv.genotype = multi_cross(new_indiv.genotype, others, params["Nindiv"])
+            new_indiv.genotype = multi_cross(
+                new_indiv.genotype, others, params["Nindiv"]
+            )
 
         elif self.method == IntOpMethods.XOR:
             new_indiv.genotype = xor_mask(new_indiv.genotype, params["N"])
@@ -177,7 +191,9 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
             new_indiv.genotype = xor_cross(new_indiv.genotype, indiv2.genotype.copy())
 
         elif self.method == IntOpMethods.CROSSINTERAVG:
-            new_indiv.genotype = cross_inter_avg(new_indiv.genotype, others, params["N"])
+            new_indiv.genotype = cross_inter_avg(
+                new_indiv.genotype, others, params["N"]
+            )
 
         elif self.method == IntOpMethods.PERM:
             new_indiv.genotype = permutation(new_indiv.genotype, params["N"])
@@ -192,7 +208,9 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
             new_indiv.genotype = cauchy(new_indiv.genotype, params["F"])
 
         elif self.method == IntOpMethods.UNIFORM:
-            new_indiv.genotype = uniform(new_indiv.genotype, params["Low"], params["Up"])
+            new_indiv.genotype = uniform(
+                new_indiv.genotype, params["Low"], params["Up"]
+            )
 
         elif self.method == IntOpMethods.POISSON:
             new_indiv.genotype = poisson(new_indiv.genotype, params["F"])
@@ -208,45 +226,73 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
 
         elif self.method == IntOpMethods.RANDSAMPLE:
             new_indiv.genotype = rand_sample(new_indiv.genotype, others, params)
-        
+
         elif self.method == IntOpMethods.RANDRESET:
             new_indiv.genotype = mutate_sample(new_indiv.genotype, others, params)
 
         elif self.method == IntOpMethods.DE_RAND_1:
-            new_indiv.genotype = DE_rand1(new_indiv.genotype, others, params["F"], params["Cr"])
+            new_indiv.genotype = DE_rand1(
+                new_indiv.genotype, others, params["F"], params["Cr"]
+            )
 
         elif self.method == IntOpMethods.DE_BEST_1:
-            new_indiv.genotype = DE_best1(new_indiv.genotype, others, params["F"], params["Cr"])
+            new_indiv.genotype = DE_best1(
+                new_indiv.genotype, others, params["F"], params["Cr"]
+            )
 
         elif self.method == IntOpMethods.DE_RAND_2:
-            new_indiv.genotype = DE_rand2(new_indiv.genotype, others, params["F"], params["Cr"])
+            new_indiv.genotype = DE_rand2(
+                new_indiv.genotype, others, params["F"], params["Cr"]
+            )
 
         elif self.method == IntOpMethods.DE_BEST_2:
-            new_indiv.genotype = DE_best2(new_indiv.genotype, others, params["F"], params["Cr"])
+            new_indiv.genotype = DE_best2(
+                new_indiv.genotype, others, params["F"], params["Cr"]
+            )
 
         elif self.method == IntOpMethods.DE_CTRAND_1:
-            new_indiv.genotype = DE_current_to_rand1(new_indiv.genotype, others, params["F"], params["Cr"])
+            new_indiv.genotype = DE_current_to_rand1(
+                new_indiv.genotype, others, params["F"], params["Cr"]
+            )
 
         elif self.method == IntOpMethods.DE_CTBEST_1:
-            new_indiv.genotype = DE_current_to_best1(new_indiv.genotype, others, params["F"], params["Cr"])
+            new_indiv.genotype = DE_current_to_best1(
+                new_indiv.genotype, others, params["F"], params["Cr"]
+            )
 
         elif self.method == IntOpMethods.DE_CTPBEST_1:
-            new_indiv.genotype = DE_current_to_pbest1(new_indiv.genotype, others, params["F"], params["Cr"], params["P"])
+            new_indiv.genotype = DE_current_to_pbest1(
+                new_indiv.genotype, others, params["F"], params["Cr"], params["P"]
+            )
 
         elif self.method == IntOpMethods.PSO:
-            new_indiv = pso_operator(indiv, others, global_best, params["w"], params["c1"], params["c2"])
+            new_indiv = pso_operator(
+                indiv, others, global_best, params["w"], params["c1"], params["c2"]
+            )
 
         elif self.method == IntOpMethods.FIREFLY:
-            new_indiv.genotype = firefly(indiv, others, objfunc, params["a"], params["b"], params["d"], params["g"])
-        
+            new_indiv.genotype = firefly(
+                indiv,
+                others,
+                objfunc,
+                params["a"],
+                params["b"],
+                params["d"],
+                params["g"],
+            )
+
         elif self.method == IntOpMethods.RANDOM:
             new_indiv = initializer.generate_random(objfunc)
 
         elif self.method == IntOpMethods.RANDOM_MASK:
-            mask_pos = np.hstack([np.ones(params["N"]), np.zeros(new_indiv.genotype.size - params["N"])]).astype(bool)
-            np.random.shuffle(mask_pos)
-
-            new_indiv.genotype[mask_pos] = initializer.generate_random(objfunc).genotype[mask_pos]
+            mask_pos = np.hstack(
+                [np.ones(params["N"]), np.zeros(new_indiv.genotype.size - params["N"])]
+            ).astype(bool)
+            RAND_GEN.shuffle(mask_pos)
+
+            new_indiv.genotype[mask_pos] = initializer.generate_random(
+                objfunc
+            ).genotype[mask_pos]
 
         elif self.method == IntOpMethods.DUMMY:
             new_indiv.genotype = dummy_op(new_indiv.genotype, params["F"])
diff --git a/src/metaheuristic_designer/operators/OperatorLambda.py b/src/metaheuristic_designer/operators/OperatorLambda.py
new file mode 100644
index 0000000..38e3dcb
--- /dev/null
+++ b/src/metaheuristic_designer/operators/OperatorLambda.py
@@ -0,0 +1,46 @@
+from __future__ import annotations
+import numpy as np
+from ..Operator import Operator
+from .vector_operator_functions import *
+from ..ParamScheduler import ParamScheduler
+from copy import copy
+import enum
+from enum import Enum
+from ..utils import RAND_GEN
+
+
+class OperatorLambda(Operator):
+    """
+    Operator class that applies a custom operator specified as a function.
+
+    Parameters
+    ----------
+    fn: callable
+        Function that will be applied when operating on an individual.
+    params: ParamScheduler or dict, optional
+        Dictionary of parameters to define the operator.
+    name: str, optional
+        Name that is associated with the operator.
+    """
+
+    def __init__(
+        self, fn: callable, params: Union[ParamScheduler, dict] = None, name: str = None
+    ):
+        """
+        Constructor for the OperatorReal class
+        """
+
+        self.fn = fn
+
+        if name is None:
+            name = fn.__name__
+
+        super().__init__(params, name)
+
+    def evolve(self, indiv, population, objfunc, global_best, initializer):
+        new_indiv = copy(indiv)
+        others = [i for i in population if i != indiv]
+
+        new_indiv.genotype = self.fn(indiv, others, objfunc, **self.params)
+
+        return new_indiv
diff --git a/src/metaheuristic_designer/Operators/OperatorList.py b/src/metaheuristic_designer/operators/OperatorList.py
similarity index 74%
rename from src/metaheuristic_designer/Operators/OperatorList.py
rename to src/metaheuristic_designer/operators/OperatorList.py
index 143a21b..027f502 100644
--- a/src/metaheuristic_designer/Operators/OperatorList.py
+++ b/src/metaheuristic_designer/operators/OperatorList.py
@@ -15,11 +15,10 @@ class ListOpMethods(Enum):
 
     @staticmethod
     def from_str(str_input):
-
         str_input = str_input.lower()
 
         if str_input not in list_ops_map:
-            raise ValueError(f"List operator \"{str_input}\" not defined")
+            raise ValueError(f'List operator "{str_input}" not defined')
 
         return list_ops_map[str_input]
 
@@ -45,7 +44,9 @@ class OperatorList(Operator):
         Name that is associated with the operator.
     """
 
-    def __init__(self, method: str, params: Union[ParamScheduler, dict] = None, name: str = None):
+    def __init__(
+        self, method: str, params: Union[ParamScheduler, dict] = None, name: str = None
+    ):
         """
         Constructor for the OperatorReal class
         """
@@ -63,8 +64,16 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
         params = copy(self.params)
 
         if self.method == ListOpMethods.EXPAND:
-            nex_indiv.genotype = expand(new_indiv.genotype, params["N"], params["method"], params["maxlen"], params["generator"])
+            nex_indiv.genotype = expand(
+                new_indiv.genotype,
+                params["N"],
+                params["method"],
+                params["maxlen"],
+                params["generator"],
+            )
         elif self.method == ListOpMethods.SHRINK:
-            nex_indiv.genotype = shrink(new_indiv.genotype, params["N"], params["method"])
+            nex_indiv.genotype = shrink(
+                new_indiv.genotype, params["N"], params["method"]
+            )
 
         return new_indiv
diff --git a/src/metaheuristic_designer/Operators/OperatorMeta.py b/src/metaheuristic_designer/operators/OperatorMeta.py
similarity index 79%
rename from src/metaheuristic_designer/Operators/OperatorMeta.py
rename to src/metaheuristic_designer/operators/OperatorMeta.py
index 9b74692..15a5634 100644
--- a/src/metaheuristic_designer/Operators/OperatorMeta.py
+++ b/src/metaheuristic_designer/operators/OperatorMeta.py
@@ -15,11 +15,10 @@ class MetaOpMethods(Enum):
 
     @staticmethod
     def from_str(str_input):
-
         str_input = str_input.lower()
 
         if str_input not in meta_ops_map:
-            raise ValueError(f"Operator on operators \"{str_input}\" not defined")
+            raise ValueError(f'Operator on operators "{str_input}" not defined')
 
         return meta_ops_map[str_input]
 
@@ -28,7 +27,7 @@ def from_str(str_input):
     "branch": MetaOpMethods.BRANCH,
     "sequence": MetaOpMethods.SEQUENCE,
     "split": MetaOpMethods.SPLIT,
-    "pick": MetaOpMethods.PICK
+    "pick": MetaOpMethods.PICK,
 }
 
 
@@ -48,7 +47,13 @@ class OperatorMeta(Operator):
         Name that is associated with the operator.
     """
 
-    def __init__(self, method: str, op_list: List[Operator], params: Union[ParamScheduler, dict] = None, name: str = None):
+    def __init__(
+        self,
+        method: str,
+        op_list: List[Operator],
+        params: Union[ParamScheduler, dict] = None,
+        name: str = None,
+    ):
         """
         Constructor for the Operator class
         """
@@ -56,13 +61,12 @@ def __init__(self, method: str, op_list: List[Operator], params: Union[ParamSche
         self.op_list = op_list
 
         if params is None:
-
             # Default parameters
             params = {
                 "p": 0.5,
                 "weights": [1] * len(op_list),
                 "mask": 0,
-                "init_idx": -1
+                "init_idx": -1,
             }
 
         if name is None:
@@ -80,20 +84,27 @@ def __init__(self, method: str, op_list: List[Operator], params: Union[ParamSche
 
         self.method = MetaOpMethods.from_str(method)
 
-        # Record of the index of the last operator used 
-        self.chosen_idx = params["init_idx"] if "init_idx" in params else -1
-        self.mask = params["mask"] if "mask" in params else 0
+        # Record of the index of the last operator used
+        self.chosen_idx = params.get("init_idx", -1)
+        self.mask = params.get("mask", 0)
 
         # If we have a branch with 2 operators and "p" is given as an input
-        if self.method == MetaOpMethods.BRANCH and "weights" not in params and "p" in params and len(op_list) == 2:
+        if (
+            self.method == MetaOpMethods.BRANCH
+            and "weights" not in params
+            and "p" in params
+            and len(op_list) == 2
+        ):
             params["weights"] = [params["p"], 1 - params["p"]]
 
     def evolve(self, indiv, population, objfunc, global_best, initializer=None):
         if self.method == MetaOpMethods.BRANCH:
-            self.chosen_idx = random.choices(range(len(self.op_list)), k=1, weights=self.params["weights"])[0]
+            self.chosen_idx = random.choices(
+                range(len(self.op_list)), k=1, weights=self.params["weights"]
+            )[0]
             chosen_op = self.op_list[self.chosen_idx]
             result = chosen_op(indiv, population, objfunc, global_best, initializer)
-        
+
         elif self.method == MetaOpMethods.PICK:
             # the chosen index is assumed to be changed by the user
             chosen_op = self.op_list[self.chosen_idx]
@@ -103,7 +114,7 @@ def evolve(self, indiv, population, objfunc, global_best, initializer=None):
             result = indiv
             for op in self.op_list:
                 result = op(result, population, objfunc, global_best, initializer)
-        
+
         elif self.method == MetaOpMethods.SPLIT:
             result = copy(indiv)
             indiv_copy = copy(indiv)
@@ -113,23 +124,27 @@ def evolve(self, indiv, population, objfunc, global_best, initializer=None):
             for idx_op, op in enumerate(self.op_list):
                 if np.any(self.mask == idx_op):
                     indiv_copy.genotype = indiv.genotype[self.mask == idx_op]
-                    global_best_copy.genotype = global_best.genotype[self.mask == idx_op]
+                    global_best_copy.genotype = global_best.genotype[
+                        self.mask == idx_op
+                    ]
 
                     for idx_pop, val in enumerate(population_copy):
                         val.genotype = population[idx_pop].genotype[self.mask == idx_op]
 
-                    aux_indiv = op(indiv_copy, population_copy, objfunc, global_best, initializer)
+                    aux_indiv = op(
+                        indiv_copy, population_copy, objfunc, global_best, initializer
+                    )
                     result.genotype[self.mask == idx_op] = aux_indiv.genotype
 
         return result
-    
+
     def step(self, progress: float):
         super().step(progress)
-        
+
         for op in self.op_list:
             if isinstance(op, Operator):
                 op.step(progress)
-            
+
     def get_state(self) -> dict:
         data = super().get_state()
 
@@ -140,6 +155,5 @@ def get_state(self) -> dict:
                 data["op_list"].append(op.get_state())
             else:
                 data["op_list"].append("lambda_func")
-        
-        return data
 
+        return data
diff --git a/src/metaheuristic_designer/Operators/OperatorPerm.py b/src/metaheuristic_designer/operators/OperatorPerm.py
similarity index 91%
rename from src/metaheuristic_designer/Operators/OperatorPerm.py
rename to src/metaheuristic_designer/operators/OperatorPerm.py
index 9dc7a70..f493b11 100644
--- a/src/metaheuristic_designer/Operators/OperatorPerm.py
+++ b/src/metaheuristic_designer/operators/OperatorPerm.py
@@ -4,6 +4,7 @@
 from copy import copy
 import enum
 from enum import Enum
+from ..utils import RAND_GEN
 
 
 class PermOpMethods(Enum):
@@ -21,11 +22,10 @@ class PermOpMethods(Enum):
 
     @staticmethod
     def from_str(str_input):
-
         str_input = str_input.lower()
 
         if str_input not in perm_ops_map:
-            raise ValueError(f"Permutation operator \"{str_input}\" not defined")
+            raise ValueError(f'Permutation operator "{str_input}" not defined')
 
         return perm_ops_map[str_input]
 
@@ -60,7 +60,9 @@ class OperatorPerm(Operator):
         Name that is associated with the operator.
     """
 
-    def __init__(self, method: str, params: Union[ParamScheduler, dict] = None, name: str = None):
+    def __init__(
+        self, method: str, params: Union[ParamScheduler, dict] = None, name: str = None
+    ):
         """
         Constructor for the Operator class
         """
@@ -82,7 +84,6 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
             indiv2 = indiv
         else:
             indiv2 = random.choice(others)
-            
 
         if global_best is None:
             global_best = indiv
@@ -90,7 +91,9 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
         params = copy(self.params)
 
         if "Cr" in params and "N" not in params:
-            params["N"] = np.count_nonzero(np.random.random(indiv.genotype.size) < params["Cr"])
+            params["N"] = np.count_nonzero(
+                RAND_GEN.random(indiv.genotype.size) < params["Cr"]
+            )
 
         if "N" in params:
             params["N"] = round(params["N"])
@@ -101,22 +104,22 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
 
         elif self.method == PermOpMethods.SCRAMBLE:
             new_indiv.genotype = permutation(new_indiv.genotype, params["N"])
-        
+
         elif self.method == PermOpMethods.INSERT:
             new_indiv.genotype = roll(new_indiv.genotype, 1)
 
         elif self.method == PermOpMethods.ROLL:
             new_indiv.genotype = roll(new_indiv.genotype, params["N"])
-        
+
         elif self.method == PermOpMethods.INVERT:
             new_indiv.genotype = invert_mutation(new_indiv.genotype)
-        
+
         elif self.method == PermOpMethods.PMX:
             new_indiv.genotype = pmx(new_indiv.genotype, indiv2.genotype.copy())
-        
+
         elif self.method == PermOpMethods.ORDERCROSS:
             new_indiv.genotype = order_cross(new_indiv.genotype, indiv2.genotype.copy())
-        
+
         elif self.method == PermOpMethods.RANDOM:
             new_indiv = initializer.generate_random(objfunc)
 
@@ -126,5 +129,5 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
         elif self.method == PermOpMethods.CUSTOM:
             fn = params["function"]
             new_indiv.genotype = fn(indiv, population, objfunc, params)
-        
+
         return new_indiv
diff --git a/src/metaheuristic_designer/Operators/OperatorReal.py b/src/metaheuristic_designer/operators/OperatorReal.py
similarity index 68%
rename from src/metaheuristic_designer/Operators/OperatorReal.py
rename to src/metaheuristic_designer/operators/OperatorReal.py
index ef8c847..9a85dac 100644
--- a/src/metaheuristic_designer/Operators/OperatorReal.py
+++ b/src/metaheuristic_designer/operators/OperatorReal.py
@@ -6,6 +6,7 @@
 from copy import copy
 import enum
 from enum import Enum
+from ..utils import RAND_GEN
 
 
 class RealOpMethods(Enum):
@@ -46,11 +47,10 @@ class RealOpMethods(Enum):
 
     @staticmethod
     def from_str(str_input):
-
         str_input = str_input.lower()
 
         if str_input not in real_ops_map:
-            raise ValueError(f"Real operator \"{str_input}\" not defined")
+            raise ValueError(f'Real operator "{str_input}" not defined')
 
         return real_ops_map[str_input]
 
@@ -108,7 +108,9 @@ class OperatorReal(Operator):
         Name that is associated with the operator.
     """
 
-    def __init__(self, method: str, params: Union[ParamScheduler, dict] = None, name: str = None):
+    def __init__(
+        self, method: str, params: Union[ParamScheduler, dict] = None, name: str = None
+    ):
         """
         Constructor for the OperatorReal class
         """
@@ -120,7 +122,12 @@ def __init__(self, method: str, params: Union[ParamScheduler, dict] = None, name
 
         self.method = RealOpMethods.from_str(method)
 
-        if self.method in [RealOpMethods.MUTNOISE, RealOpMethods.MUTSAMPLE, RealOpMethods.RANDNOISE, RealOpMethods.RANDSAMPLE]:
+        if self.method in [
+            RealOpMethods.MUTNOISE,
+            RealOpMethods.MUTSAMPLE,
+            RealOpMethods.RANDNOISE,
+            RealOpMethods.RANDSAMPLE,
+        ]:
             self.params["method"] = ProbDist.from_str(self.params["method"])
 
     def evolve(self, indiv, population, objfunc, global_best, initializer):
@@ -133,7 +140,6 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
             indiv2 = indiv
         else:
             indiv2 = random.choice(others)
-            
 
         if global_best is None:
             global_best = indiv
@@ -141,7 +147,9 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
         params = copy(self.params)
 
         if "Cr" in params and "N" not in params:
-            params["N"] = np.count_nonzero(np.random.random(indiv.genotype.size) < params["Cr"])
+            params["N"] = np.count_nonzero(
+                RAND_GEN.random(indiv.genotype.size) < params["Cr"]
+            )
 
         if "N" in params:
             params["N"] = round(params["N"])
@@ -157,28 +165,47 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
             new_indiv.genotype = cross_mp(new_indiv.genotype, indiv2.genotype.copy())
 
         elif self.method == RealOpMethods.WEIGHTED_AVG:
-            new_indiv.genotype = weighted_average(new_indiv.genotype, indiv2.genotype.copy(), params["F"])
+            new_indiv.genotype = weighted_average(
+                new_indiv.genotype, indiv2.genotype.copy(), params["F"]
+            )
 
         elif self.method == RealOpMethods.BLXALPHA:
-            new_indiv.genotype = blxalpha(new_indiv.genotype, indiv2.genotype.copy(), params["Cr"])
+            new_indiv.genotype = blxalpha(
+                new_indiv.genotype, indiv2.genotype.copy(), params["Cr"]
+            )
 
         elif self.method == RealOpMethods.SBX:
-            new_indiv.genotype = sbx(new_indiv.genotype, indiv2.genotype.copy(), params["Cr"])
+            new_indiv.genotype = sbx(
+                new_indiv.genotype, indiv2.genotype.copy(), params["Cr"]
+            )
 
         elif self.method == RealOpMethods.MULTICROSS:
-            new_indiv.genotype = multi_cross(new_indiv.genotype, others, params["Nindiv"])
+            new_indiv.genotype = multi_cross(
+                new_indiv.genotype, others, params["Nindiv"]
+            )
 
         elif self.method == RealOpMethods.CROSSINTERAVG:
-            new_indiv.genotype = cross_inter_avg(new_indiv.genotype, others, params["N"])
+            new_indiv.genotype = cross_inter_avg(
+                new_indiv.genotype, others, params["N"]
+            )
 
         elif self.method == RealOpMethods.MUTATE1SIGMA:
-            new_indiv.genotype = mutate_1_sigma(new_indiv.genotype[0], params["epsilon"], params["tau"])
+            new_indiv.genotype = mutate_1_sigma(
+                new_indiv.genotype[0], params["epsilon"], params["tau"]
+            )
 
         elif self.method == RealOpMethods.MUTATENSIGMAS:
-            new_indiv.genotype = mutate_n_sigmas(new_indiv.genotype, params["epsilon"], params["tau"], params["tau_multiple"])
+            new_indiv.genotype = mutate_n_sigmas(
+                new_indiv.genotype,
+                params["epsilon"],
+                params["tau"],
+                params["tau_multiple"],
+            )
 
         elif self.method == RealOpMethods.SAMPLESIGMA:
-            new_indiv.genotype = sample_1_sigma(new_indiv.genotype, params["N"], params["epsilon"], params["tau"])
+            new_indiv.genotype = sample_1_sigma(
+                new_indiv.genotype, params["N"], params["epsilon"], params["tau"]
+            )
 
         elif self.method == RealOpMethods.PERM:
             new_indiv.genotype = permutation(new_indiv.genotype, params["N"])
@@ -193,7 +220,9 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
             new_indiv.genotype = cauchy(new_indiv.genotype, params["F"])
 
         elif self.method == RealOpMethods.UNIFORM:
-            new_indiv.genotype = uniform(new_indiv.genotype, params["Low"], params["Up"])
+            new_indiv.genotype = uniform(
+                new_indiv.genotype, params["Low"], params["Up"]
+            )
 
         elif self.method == RealOpMethods.MUTNOISE:
             new_indiv.genotype = mutate_rand(new_indiv.genotype, others, params)
@@ -208,40 +237,68 @@ def evolve(self, indiv, population, objfunc, global_best, initializer):
             new_indiv.genotype = rand_sample(new_indiv.genotype, others, params)
 
         elif self.method == RealOpMethods.DE_RAND_1:
-            new_indiv.genotype = DE_rand1(new_indiv.genotype, others, params["F"], params["Cr"])
+            new_indiv.genotype = DE_rand1(
+                new_indiv.genotype, others, params["F"], params["Cr"]
+            )
 
         elif self.method == RealOpMethods.DE_BEST_1:
-            new_indiv.genotype = DE_best1(new_indiv.genotype, others, params["F"], params["Cr"])
+            new_indiv.genotype = DE_best1(
+                new_indiv.genotype, others, params["F"], params["Cr"]
+            )
 
         elif self.method == RealOpMethods.DE_RAND_2:
-            new_indiv.genotype = DE_rand2(new_indiv.genotype, others, params["F"], params["Cr"])
+            new_indiv.genotype = DE_rand2(
+                new_indiv.genotype, others, params["F"], params["Cr"]
+            )
 
         elif self.method == RealOpMethods.DE_BEST_2:
-            new_indiv.genotype = DE_best2(new_indiv.genotype, others, params["F"], params["Cr"])
+            new_indiv.genotype = DE_best2(
+                new_indiv.genotype, others, params["F"], params["Cr"]
+            )
 
         elif self.method == RealOpMethods.DE_CTRAND_1:
-            new_indiv.genotype = DE_current_to_rand1(new_indiv.genotype, others, params["F"], params["Cr"])
+            new_indiv.genotype = DE_current_to_rand1(
+                new_indiv.genotype, others, params["F"], params["Cr"]
+            )
 
         elif self.method == RealOpMethods.DE_CTBEST_1:
-            new_indiv.genotype = DE_current_to_best1(new_indiv.genotype, others, params["F"], params["Cr"])
+            new_indiv.genotype = DE_current_to_best1(
+                new_indiv.genotype, others, params["F"], params["Cr"]
+            )
 
         elif self.method == RealOpMethods.DE_CTPBEST_1:
-            new_indiv.genotype = DE_current_to_pbest1(new_indiv.genotype, others, params["F"], params["Cr"], params["P"])
+            new_indiv.genotype = DE_current_to_pbest1(
+                new_indiv.genotype, others, params["F"], params["Cr"], params["P"]
+            )
 
         elif self.method == RealOpMethods.PSO:
-            new_indiv = pso_operator(indiv, others, global_best, params["w"], params["c1"], params["c2"])
+            new_indiv = pso_operator(
+                indiv, others, global_best, params["w"], params["c1"], params["c2"]
+            )
 
         elif self.method == RealOpMethods.FIREFLY:
-            new_indiv.genotype = firefly(indiv, others, objfunc, params["a"], params["b"], params["d"], params["g"])
+            new_indiv.genotype = firefly(
+                indiv,
+                others,
+                objfunc,
+                params["a"],
+                params["b"],
+                params["d"],
+                params["g"],
+            )
 
         elif self.method == RealOpMethods.RANDOM:
             new_indiv = initializer.generate_random(objfunc)
 
         elif self.method == RealOpMethods.RANDOM_MASK:
-            mask_pos = np.hstack([np.ones(params["N"]), np.zeros(new_indiv.genotype.size - params["N"])]).astype(bool)
-            np.random.shuffle(mask_pos)
-
-            new_indiv.genotype[mask_pos] = initializer.generate_random(objfunc).genotype[mask_pos]
+            mask_pos = np.hstack(
+                [np.ones(params["N"]), np.zeros(new_indiv.genotype.size - params["N"])]
+            ).astype(bool)
+            RAND_GEN.shuffle(mask_pos)
+
+            new_indiv.genotype[mask_pos] = initializer.generate_random(
+                objfunc
+            ).genotype[mask_pos]
 
         elif self.method == RealOpMethods.DUMMY:
             new_indiv.genotype = dummy_op(new_indiv.genotype, params["F"])
diff --git a/src/metaheuristic_designer/Operators/__init__.py b/src/metaheuristic_designer/operators/__init__.py
similarity index 89%
rename from src/metaheuristic_designer/Operators/__init__.py
rename to src/metaheuristic_designer/operators/__init__.py
index 88f8d02..2183127 100644
--- a/src/metaheuristic_designer/Operators/__init__.py
+++ b/src/metaheuristic_designer/operators/__init__.py
@@ -3,4 +3,5 @@
 from .OperatorPerm import OperatorPerm, PermOpMethods, perm_ops_map
 from .OperatorBinary import OperatorBinary, BinOpMethods, bin_ops_map
 from .OperatorMeta import OperatorMeta, MetaOpMethods, meta_ops_map
+from .OperatorLambda import OperatorLambda
 from .vector_operator_functions import *
diff --git a/src/metaheuristic_designer/Operators/list_operator_functions.py b/src/metaheuristic_designer/operators/list_operator_functions.py
similarity index 90%
rename from src/metaheuristic_designer/Operators/list_operator_functions.py
rename to src/metaheuristic_designer/operators/list_operator_functions.py
index 9aacd07..f6dd0e5 100644
--- a/src/metaheuristic_designer/Operators/list_operator_functions.py
+++ b/src/metaheuristic_designer/operators/list_operator_functions.py
@@ -6,7 +6,7 @@ def expand(input_list, n, method, maxlen, val_generator=None):
 
     if val_generator is None:
         val_generator = random.random
-    
+
     if len(input_list) < maxlen:
         if len(input_list) + n >= maxlen:
             n = maxlen - len(input_list)
@@ -24,7 +24,9 @@ def expand(input_list, n, method, maxlen, val_generator=None):
 def expand_rand(input_list, n, val_generator):
     for i in range(n):
         inject_idx = random.randrange(len(input_list))
-        input_list = input_list[:inject_idx] + [val_generator()] + input_list[inject_idx:]
+        input_list = (
+            input_list[:inject_idx] + [val_generator()] + input_list[inject_idx:]
+        )
 
     return input_list + new_values
 
@@ -53,7 +55,7 @@ def shrink(input_list, n, method):
 
 
 def shrink_rand(input_list, n):
-    n_idx_chosen = max(len(input_list)-n, 0)
+    n_idx_chosen = max(len(input_list) - n, 0)
 
     idxs = sorted(random.choices(range(len(input_list)), k=n_idx_chosen))
     result_list = [input_list[i] for i in idxs]
diff --git a/src/metaheuristic_designer/Operators/vector_operator_functions.py b/src/metaheuristic_designer/operators/vector_operator_functions.py
similarity index 81%
rename from src/metaheuristic_designer/Operators/vector_operator_functions.py
rename to src/metaheuristic_designer/operators/vector_operator_functions.py
index f4ccab8..7e9662c 100644
--- a/src/metaheuristic_designer/Operators/vector_operator_functions.py
+++ b/src/metaheuristic_designer/operators/vector_operator_functions.py
@@ -5,6 +5,8 @@
 import scipy.stats
 import enum
 from enum import Enum
+from ..utils import RAND_GEN
+
 
 class ProbDist(Enum):
     UNIFORM = enum.auto()
@@ -16,11 +18,10 @@ class ProbDist(Enum):
 
     @staticmethod
     def from_str(str_input):
-
         str_input = str_input.lower()
 
         if str_input not in prob_dist_map:
-            raise ValueError(f"Probability distribution \"{str_input}\" not defined")
+            raise ValueError(f'Probability distribution "{str_input}" not defined')
 
         return prob_dist_map[str_input]
 
@@ -32,7 +33,7 @@ def from_str(str_input):
     "cauchy": ProbDist.CAUCHY,
     "laplace": ProbDist.LAPLACE,
     "poisson": ProbDist.POISSON,
-    "bernoulli": ProbDist.BERNOULLI
+    "bernoulli": ProbDist.BERNOULLI,
 }
 
 
@@ -44,12 +45,12 @@ def mutate_rand(vector, population, params):
     method = params["method"]
     n = round(params["N"])
 
-    low = params["Low"] if "Low" in params else -1
-    up = params["Up"] if "Low" in params else 1
-    strength = params["F"] if "F" in params else 1
+    low = params.get("Low", -1)
+    up = params.get("Up", 1)
+    strength = params.get("F", 1)
 
     mask_pos = np.hstack([np.ones(n), np.zeros(vector.size - n)]).astype(bool)
-    np.random.shuffle(mask_pos)
+    RAND_GEN.shuffle(mask_pos)
 
     rand_vec = sample_distribution(method, n, 0, strength, low, up)
 
@@ -65,15 +66,17 @@ def mutate_sample(vector, population, params):
     method = params["method"]
     n = round(params["N"])
 
-    low = params["Low"] if "Low" in params else -1
-    up = params["Up"] if "Low" in params else 1
-    strength = params["F"] if "F" in params else 1
+    low = params.get("Low", -1)
+    up = params.get("Up", 1)
+    strength = params.get("F", 1)
 
     mask_pos = np.hstack([np.ones(n), np.zeros(vector.size - n)]).astype(bool)
-    np.random.shuffle(mask_pos)
+    RAND_GEN.shuffle(mask_pos)
     popul_matrix = np.vstack([i.genotype for i in population])
     mean = popul_matrix.mean(axis=0)[mask_pos]
-    std = (popul_matrix.std(axis=0)[mask_pos] + 1e-6) * strength  # ensure there will be some standard deviation
+    std = (
+        popul_matrix.std(axis=0)[mask_pos] + 1e-6
+    ) * strength  # ensure there will be some standard deviation
 
     rand_vec = sample_distribution(method, n, mean, std, low, up)
 
@@ -88,13 +91,15 @@ def rand_sample(vector, population, params):
 
     method = params["method"]
 
-    low = params["Low"] if "Low" in params else -1
-    up = params["Up"] if "Low" in params else 1
-    strength = params["F"] if "F" in params else 1
+    low = params.get("Low", -1)
+    up = params.get("Up", 1)
+    strength = params.get("F", 1)
 
     popul_matrix = np.vstack([i.genotype for i in population])
     mean = popul_matrix.mean(axis=0)
-    std = (popul_matrix.std(axis=0) + 1e-6) * strength  # ensure there will be some standard deviation
+    std = (
+        popul_matrix.std(axis=0) + 1e-6
+    ) * strength  # ensure there will be some standard deviation
 
     rand_vec = sample_distribution(method, vector.shape, mean, std, low, up)
 
@@ -108,9 +113,9 @@ def rand_noise(vector, params):
 
     method = params["method"]
 
-    low = params["Low"] if "Low" in params else -1
-    up = params["Up"] if "Low" in params else 1
-    strength = params["F"] if "F" in params else 1
+    low = params.get("Low", -1)
+    up = params.get("Up", 1)
+    strength = params.get("F", 1)
 
     noise = sample_distribution(method, vector.shape, 0, strength, low, up)
 
@@ -124,9 +129,9 @@ def sample_distribution(method, n, mean=0, strength=0.01, low=0, up=1):
 
     sample = 0
     if method == ProbDist.GAUSS:
-        sample = np.random.normal(mean, strength, size=n)
+        sample = RAND_GEN.normal(mean, strength, size=n)
     elif method == ProbDist.UNIFORM:
-        sample = np.random.uniform(low, up, size=n)
+        sample = RAND_GEN.uniform(low, up, size=n)
     elif method == ProbDist.CAUCHY:
         sample = sp.stats.cauchy.rvs(mean, strength, size=n)
     elif method == ProbDist.LAPLACE:
@@ -135,7 +140,7 @@ def sample_distribution(method, n, mean=0, strength=0.01, low=0, up=1):
         sample = sp.stats.poisson.rvs(strength, size=n)
     elif method == ProbDist.BERNOULLI:
         sample = sp.stats.bernoulli.rvs(strength, size=n)
-    
+
     return sample
 
 
@@ -188,7 +193,7 @@ def sample_1_sigma(vector, n, epsilon, tau):
     """
 
     mask_pos = np.hstack([np.ones(n), np.zeros(vector.size - n)]).astype(bool)
-    np.random.shuffle(mask_pos)
+    RAND_GEN.shuffle(mask_pos)
 
     sampled = np.array([mutate_1_sigma(vector[pos], epsilon, tau) for pos in mask_pos])
     vector[mask_pos] = sampled[mask_pos]
@@ -200,7 +205,7 @@ def mutate_1_sigma(sigma, epsilon, tau):
     Mutate a sigma value in base of tau param, where epsilon is de minimum value that a sigma can have.
     """
 
-    return max(epsilon, np.exp(tau * np.random.normal()))
+    return max(epsilon, np.exp(tau * RAND_GEN.normal()))
 
 
 def mutate_n_sigmas(list_sigmas, epsilon, tau, tau_multiple):
@@ -208,8 +213,11 @@ def mutate_n_sigmas(list_sigmas, epsilon, tau, tau_multiple):
     Mutate a list of sigmas values in base of tau and tau_multiple params, where epsilon is de minimum value that a sigma can have.
     """
 
-    base_tau = tau * np.random.normal()
-    new_sigmas = [max(epsilon, sigma * np.exp(base_tau + tau_multiple * np.random.normal())) for sigma in list_sigmas]
+    base_tau = tau * RAND_GEN.normal()
+    new_sigmas = [
+        max(epsilon, sigma * np.exp(base_tau + tau_multiple * RAND_GEN.normal()))
+        for sigma in list_sigmas
+    ]
     return new_sigmas
 
 
@@ -219,14 +227,14 @@ def xor_mask(vector, n, mode="byte"):
     """
 
     mask_pos = np.hstack([np.ones(n), np.zeros(vector.size - n)]).astype(bool)
-    np.random.shuffle(mask_pos)
+    RAND_GEN.shuffle(mask_pos)
 
     if mode == "bin":
         mask = mask_pos
     elif mode == "byte":
-        mask = np.random.randint(1, 0xFF, size=vector.shape) * mask_pos
+        mask = RAND_GEN.integers(1, 0xFF, size=vector.shape) * mask_pos
     elif mode == "int":
-        mask = np.random.randint(1, 0xFFFF, size=vector.shape) * mask_pos
+        mask = RAND_GEN.integers(1, 0xFFFF, size=vector.shape) * mask_pos
 
     return vector ^ mask
 
@@ -237,13 +245,13 @@ def permutation(vector, n):
     """
 
     mask_pos = np.hstack([np.ones(n), np.zeros(vector.size - n)]).astype(bool)
-    np.random.shuffle(mask_pos)
+    RAND_GEN.shuffle(mask_pos)
 
     if np.count_nonzero(mask_pos == 1) < 2:
         mask_pos[random.sample(range(mask_pos.size), 2)] = 1
 
     shuffled_vec = vector[mask_pos]
-    np.random.shuffle(shuffled_vec)
+    RAND_GEN.shuffle(shuffled_vec)
     vector[mask_pos] = shuffled_vec
 
     return vector
@@ -289,7 +297,13 @@ def cross_2p(vector1, vector2):
 
     cross_point1 = random.randrange(0, vector1.size - 2)
     cross_point2 = random.randrange(cross_point1, vector1.size)
-    return np.hstack([vector1[:cross_point1], vector2[cross_point1:cross_point2], vector1[cross_point2:]])
+    return np.hstack(
+        [
+            vector1[:cross_point1],
+            vector2[cross_point1:cross_point2],
+            vector1[cross_point2:],
+        ]
+    )
 
 
 def cross_mp(vector1, vector2):
@@ -297,7 +311,7 @@ def cross_mp(vector1, vector2):
     Performs a multipoint cross between two vectors.
     """
 
-    mask_pos = 1 * (np.random.rand(vector1.size) > 0.5)
+    mask_pos = 1 * (RAND_GEN.random(vector1.size) > 0.5)
     aux = np.copy(vector1)
     aux[mask_pos == 1] = vector2[mask_pos == 1]
     return aux
@@ -312,7 +326,7 @@ def multi_cross(vector, population, n_ind):
         n_ind = len(population)
 
     other_parents = random.sample(population, n_ind - 1)
-    mask_pos = np.random.randint(n_ind, size=vector.size) - 1
+    mask_pos = RAND_GEN.integers(n_ind, size=vector.size) - 1
     for i in range(0, n_ind - 1):
         vector[mask_pos == i] = other_parents[i].genotype[mask_pos == i]
     return vector
@@ -350,7 +364,7 @@ def pmx(vector1, vector2):
     overlap = np.isin(remaining, segment)
     conflicting = remaining[overlap]
     no_conflict = np.sort(remaining[~overlap])
-    
+
     # Añadimos los elementos sin conflicto (que no están dentro del segmento del genotipo 2)
     idx_no_conflict = np.where(np.isin(vector2, no_conflict))[0]
     child[idx_no_conflict] = no_conflict
@@ -358,7 +372,7 @@ def pmx(vector1, vector2):
     # Tratamos conflicto
     for elem in conflicting:
         pos = elem.copy()
-        while(pos != -1):
+        while pos != -1:
             genotype_in_pos = pos
             pos = child[np.where(vector2 == genotype_in_pos)][0]
         child[np.where(vector2 == genotype_in_pos)] = elem
@@ -382,7 +396,6 @@ def order_cross(vector1, vector2):
     return child
 
 
-
 def cross_inter_avg(vector, population, n_ind):
     """
     Performs an intermediate average crossover between the vector and 'n-1' individuals the population
@@ -409,7 +422,7 @@ def blxalpha(vector1, vector2, alpha):
     Performs the BLX alpha crossing operator between two vectors.
     """
 
-    alpha *= np.random.random()
+    alpha *= RAND_GEN.random()
     return alpha * vector1 + (1 - alpha) * vector2
 
 
@@ -419,12 +432,12 @@ def sbx(vector1, vector2, strength):
     """
 
     beta = np.zeros(vector1.shape)
-    u = np.random.random(vector1.shape)
+    u = RAND_GEN.random(vector1.shape)
     for idx, val in enumerate(u):
         if val <= 0.5:
-            beta[idx] = (2 * val)**(1 / (strength + 1))
+            beta[idx] = (2 * val) ** (1 / (strength + 1))
         else:
-            beta[idx] = (0.5 * (1 - val))**(1 / (strength + 1))
+            beta[idx] = (0.5 * (1 - val)) ** (1 / (strength + 1))
 
     sign = random.choice([-1, 1])
     return 0.5 * (vector1 + vector2) + sign * 0.5 * beta * (vector1 - vector2)
@@ -439,7 +452,7 @@ def DE_rand1(vector, population, F, CR):
         r1, r2, r3 = random.sample(population, 3)
 
         v = r1.genotype + F * (r2.genotype - r3.genotype)
-        mask_pos = np.random.random(vector.shape) <= CR
+        mask_pos = RAND_GEN.random(vector.shape) <= CR
         vector[mask_pos] = v[mask_pos]
     return vector
 
@@ -455,7 +468,7 @@ def DE_best1(vector, population, F, CR):
         r1, r2 = random.sample(population, 2)
 
         v = best.genotype + F * (r1.genotype - r2.genotype)
-        mask_pos = np.random.random(vector.shape) <= CR
+        mask_pos = RAND_GEN.random(vector.shape) <= CR
         vector[mask_pos] = v[mask_pos]
     return vector
 
@@ -468,8 +481,12 @@ def DE_rand2(vector, population, F, CR):
     if len(population) > 5:
         r1, r2, r3, r4, r5 = random.sample(population, 5)
 
-        v = r1.genotype + F * (r2.genotype - r3.genotype) + F * (r4.genotype - r5.genotype)
-        mask_pos = np.random.random(vector.shape) <= CR
+        v = (
+            r1.genotype
+            + F * (r2.genotype - r3.genotype)
+            + F * (r4.genotype - r5.genotype)
+        )
+        mask_pos = RAND_GEN.random(vector.shape) <= CR
         vector[mask_pos] = v[mask_pos]
     return vector
 
@@ -484,8 +501,12 @@ def DE_best2(vector, population, F, CR):
         best = population[fitness.index(max(fitness))]
         r1, r2, r3, r4 = random.sample(population, 4)
 
-        v = best.genotype + F * (r1.genotype - r2.genotype) + F * (r3.genotype - r4.genotype)
-        mask_pos = np.random.random(vector.shape) <= CR
+        v = (
+            best.genotype
+            + F * (r1.genotype - r2.genotype)
+            + F * (r3.genotype - r4.genotype)
+        )
+        mask_pos = RAND_GEN.random(vector.shape) <= CR
         vector[mask_pos] = v[mask_pos]
     return vector
 
@@ -498,8 +519,12 @@ def DE_current_to_rand1(vector, population, F, CR):
     if len(population) > 3:
         r1, r2, r3 = random.sample(population, 3)
 
-        v = vector + np.random.random() * (r1.genotype - vector) + F * (r2.genotype - r3.genotype)
-        mask_pos = np.random.random(vector.shape) <= CR
+        v = (
+            vector
+            + RAND_GEN.random() * (r1.genotype - vector)
+            + F * (r2.genotype - r3.genotype)
+        )
+        mask_pos = RAND_GEN.random(vector.shape) <= CR
         vector[mask_pos] = v[mask_pos]
     return vector
 
@@ -515,7 +540,7 @@ def DE_current_to_best1(vector, population, F, CR):
         r1, r2 = random.sample(population, 2)
 
         v = vector + F * (best.genotype - vector) + F * (r1.genotype - r2.genotype)
-        mask_pos = np.random.random(vector.shape) <= CR
+        mask_pos = RAND_GEN.random(vector.shape) <= CR
         vector[mask_pos] = v[mask_pos]
     return vector
 
@@ -533,7 +558,7 @@ def DE_current_to_pbest1(vector, population, F, CR, P):
         r1, r2 = random.sample(population, 2)
 
         v = vector + F * (pbest.genotype - vector) + F * (r1.genotype - r2.genotype)
-        mask_pos = np.random.random(vector.shape) <= CR
+        mask_pos = RAND_GEN.random(vector.shape) <= CR
         vector[mask_pos] = v[mask_pos]
     return vector
 
@@ -543,10 +568,14 @@ def pso_operator(indiv, population, global_best, w, c1, c2):
     Performs a step of the Particle Swarm algorithm
     """
 
-    c1 = c1 * np.random.random(indiv.genotype.shape)
-    c2 = c2 * np.random.random(indiv.genotype.shape)
+    c1 = c1 * RAND_GEN.random(indiv.genotype.shape)
+    c2 = c2 * RAND_GEN.random(indiv.genotype.shape)
 
-    indiv.speed = w * indiv.speed + c1 * (indiv.best - indiv.genotype) + c2 * (global_best.genotype - indiv.genotype)
+    indiv.speed = (
+        w * indiv.speed
+        + c1 * (indiv.best - indiv.genotype)
+        + c2 * (global_best.genotype - indiv.genotype)
+    )
     return indiv.apply_speed()
 
 
@@ -561,9 +590,14 @@ def firefly(solution, population, objfunc, alpha_0, beta_0, delta, gamma):
     for idx, ind in enumerate(population):
         if solution.fitness < ind.fitness:
             r = np.linalg.norm(solution.genotype - ind.genotype)
-            alpha = alpha_0 * delta ** idx
-            beta = beta_0 * np.exp(-gamma * (r / (sol_range * np.sqrt(n_dim)))**2)
-            new_vector = new_vector + beta * (ind.genotype - new_vector) + alpha * sol_range * random.random() - 0.5
+            alpha = alpha_0 * delta**idx
+            beta = beta_0 * np.exp(-gamma * (r / (sol_range * np.sqrt(n_dim))) ** 2)
+            new_vector = (
+                new_vector
+                + beta * (ind.genotype - new_vector)
+                + alpha * sol_range * random.random()
+                - 0.5
+            )
             new_vector = objfunc.repair_solution(new_vector)
 
     return new_vector
diff --git a/src/metaheuristic_designer/SearchMethods/GeneralSearch.py b/src/metaheuristic_designer/searchMethods/GeneralSearch.py
similarity index 79%
rename from src/metaheuristic_designer/SearchMethods/GeneralSearch.py
rename to src/metaheuristic_designer/searchMethods/GeneralSearch.py
index c839e48..db0b4ff 100644
--- a/src/metaheuristic_designer/SearchMethods/GeneralSearch.py
+++ b/src/metaheuristic_designer/searchMethods/GeneralSearch.py
@@ -20,7 +20,12 @@ class GeneralSearch(Search):
         Dictionary of parameters to define the stopping condition and output of the algorithm.
     """
 
-    def __init__(self, objfunc: ObjectiveFunc, search_strategy: Algorithm, params: Union[ParamScheduler, dict] = None):
+    def __init__(
+        self,
+        objfunc: ObjectiveFunc,
+        search_strategy: Algorithm,
+        params: Union[ParamScheduler, dict] = None,
+    ):
         """
         Constructor of the Metaheuristic class
         """
@@ -30,16 +35,22 @@ def __init__(self, objfunc: ObjectiveFunc, search_strategy: Algorithm, params: U
     def step(self, time_start=0, verbose=False):
         population = self.search_strategy.population
 
-        parents = self.search_strategy.select_parents(population, self.progress, self.best_history)
+        parents = self.search_strategy.select_parents(
+            population, progress=self.progress, history=self.best_history
+        )
 
-        offspring = self.search_strategy.perturb(parents, self.objfunc, self.progress, self.best_history)
+        offspring = self.search_strategy.perturb(
+            parents, self.objfunc, progress=self.progress, history=self.best_history
+        )
 
-        population = self.search_strategy.select_individuals(population, offspring, self.progress, self.best_history)
+        population = self.search_strategy.select_individuals(
+            population, offspring, progress=self.progress, history=self.best_history
+        )
 
         self.search_strategy.population = population
 
         best_individual, best_fitness = self.search_strategy.best_solution()
-        self.search_strategy.update_params(self.progress)
+        self.search_strategy.update_params(progress=self.progress)
 
         # Store information
         self.best_history.append(best_individual)
@@ -63,7 +74,7 @@ def display_report(self, show_plots=True):
         print("Real time spent: ", round(self.real_time_spent, 5), "s", sep="")
         print("CPU time spent: ", round(self.cpu_time_spent, 5), "s", sep="")
         print("Number of fitness evaluations:", self.objfunc.counter)
-        
+
         best_fitness = self.best_solution()[1]
         print("Best fitness:", best_fitness)
 
diff --git a/src/metaheuristic_designer/SearchMethods/MemeticSearch.py b/src/metaheuristic_designer/searchMethods/MemeticSearch.py
similarity index 78%
rename from src/metaheuristic_designer/SearchMethods/MemeticSearch.py
rename to src/metaheuristic_designer/searchMethods/MemeticSearch.py
index 85fe65a..ce44b61 100644
--- a/src/metaheuristic_designer/SearchMethods/MemeticSearch.py
+++ b/src/metaheuristic_designer/searchMethods/MemeticSearch.py
@@ -24,11 +24,13 @@ class MemeticSearch(Search):
         Dictionary of parameters to define the stopping condition and output of the algorithm.
     """
 
-    def __init__(self, objfunc, search_strategy, local_search, improve_choice, params = None):
+    def __init__(
+        self, objfunc, search_strategy, local_search, improve_choice, params=None
+    ):
         """
         Constructor of the Metaheuristic class
         """
-        
+
         super().__init__(objfunc, search_strategy, params)
 
         self.local_search = local_search
@@ -60,18 +62,24 @@ def _do_local_search(self, offspring):
     def step(self, time_start=0, verbose=False):
         population = self.search_strategy.population
 
-        parents = self.search_strategy.select_parents(population, self.progress, self.best_history)
+        parents = self.search_strategy.select_parents(
+            population, progress=self.progress, history=self.best_history
+        )
 
-        offspring = self.search_strategy.perturb(parents, self.objfunc, self.progress, self.best_history)
+        offspring = self.search_strategy.perturb(
+            parents, self.objfunc, progress=self.progress, history=self.best_history
+        )
 
         offspring = self._do_local_search(offspring)
 
-        population = self.search_strategy.select_individuals(population, offspring, self.progress, self.best_history)
+        population = self.search_strategy.select_individuals(
+            population, offspring, progress=self.progress, history=self.best_history
+        )
 
         self.search_strategy.population = population
 
         best_individual, best_fitness = self.search_strategy.best_solution()
-        self.search_strategy.update_params(self.progress)
+        self.search_strategy.update_params(progress=self.progress)
         self.steps += 1
 
         # Store information
@@ -86,10 +94,22 @@ def step(self, time_start=0, verbose=False):
         self.update(self.steps, time_start)
 
         return (best_individual, best_fitness)
-    
-    def get_state(self, show_best_solution: bool = True, show_fit_history: bool = False, show_gen_history: bool = False,
-                  show_pop: bool = False, show_pop_details:bool = False):
-        data = super().get_state(show_best_solution, show_fit_history, show_gen_history, show_pop, show_pop_details)
+
+    def get_state(
+        self,
+        show_best_solution: bool = True,
+        show_fit_history: bool = False,
+        show_gen_history: bool = False,
+        show_pop: bool = False,
+        show_pop_details: bool = False,
+    ):
+        data = super().get_state(
+            show_best_solution,
+            show_fit_history,
+            show_gen_history,
+            show_pop,
+            show_pop_details,
+        )
 
         # Add parent selection method for local search
         data["improve_selection"] = self.improve_choice.get_state()
@@ -103,11 +123,13 @@ def get_state(self, show_best_solution: bool = True, show_fit_history: bool = Fa
         # push search strategy data to the bottom
         search_strat_data = data.pop("search_strat_state", None)
         data["search_strat_state"] = search_strat_data
-        
+
         return data
 
     def step_info(self, start_time):
-        print(f"Optimizing {self.objfunc.name} using {self.search_strategy.name}+{self.local_search.name}:")
+        print(
+            f"Optimizing {self.objfunc.name} using {self.search_strategy.name}+{self.local_search.name}:"
+        )
         print(f"\tReal time Spent: {round(time.time() - start_time,2)} s")
         print(f"\tCPU time Spent:  {round(time.time() - start_time,2)} s")
         print(f"\tGeneration: {self.steps}")
@@ -128,7 +150,6 @@ def display_report(self, show_plots=True):
         print("Best fitness:", best_fitness)
 
         if show_plots:
-
             # Plot fitness history
             plt.axhline(y=0, color="black", alpha=0.9)
             plt.axvline(x=0, color="black", alpha=0.9)
diff --git a/src/metaheuristic_designer/SearchMethods/__init__.py b/src/metaheuristic_designer/searchMethods/__init__.py
similarity index 100%
rename from src/metaheuristic_designer/SearchMethods/__init__.py
rename to src/metaheuristic_designer/searchMethods/__init__.py
diff --git a/src/metaheuristic_designer/SelectionMethods/ParentSelection.py b/src/metaheuristic_designer/selectionMethods/ParentSelection.py
similarity index 72%
rename from src/metaheuristic_designer/SelectionMethods/ParentSelection.py
rename to src/metaheuristic_designer/selectionMethods/ParentSelection.py
index 11e32fb..b97d0d6 100644
--- a/src/metaheuristic_designer/SelectionMethods/ParentSelection.py
+++ b/src/metaheuristic_designer/selectionMethods/ParentSelection.py
@@ -16,11 +16,10 @@ class ParentSelMethod(Enum):
 
     @staticmethod
     def from_str(str_input):
-
         str_input = str_input.lower()
 
         if str_input not in parent_sel_map:
-            raise ValueError(f"Survivor selection method \"{str_input}\" not defined")
+            raise ValueError(f'Survivor selection method "{str_input}" not defined')
 
         return parent_sel_map[str_input]
 
@@ -31,7 +30,7 @@ def from_str(str_input):
     "random": ParentSelMethod.RANDOM,
     "roulette": ParentSelMethod.ROULETTE,
     "sus": ParentSelMethod.SUS,
-    "nothing": ParentSelMethod.NOTHING
+    "nothing": ParentSelMethod.NOTHING,
 }
 
 
@@ -51,43 +50,61 @@ class ParentSelection(SelectionMethod):
         The name that will be assigned to this selection method.
     """
 
-    def __init__(self, method: str, params: Union[ParamScheduler, dict] = None, padding: bool = False, name: str = None):
+    def __init__(
+        self,
+        method: str,
+        params: Union[ParamScheduler, dict] = None,
+        padding: bool = False,
+        name: str = None,
+    ):
         """
         Constructor for the ParentSelection class
         """
 
         if name is None:
             name = method
-        
+
         super().__init__(params, padding, name)
 
         self.method = ParentSelMethod.from_str(method)
-        
+
         if self.method in [ParentSelMethod.ROULETTE, ParentSelMethod.SUS]:
             self.params["method"] = SelectionDist.from_str(self.params["method"])
             if "F" not in self.params:
                 self.params["F"] = None
-        
-        
 
-    def select(self, population: List[Individual], offsping: List[Individual] = None) -> List[Individual]:
+    def select(
+        self, population: List[Individual], offsping: List[Individual] = None
+    ) -> List[Individual]:
         population = population.copy()
         parents = []
-        
+
         if self.method == ParentSelMethod.TOURNAMENT:
-            parents = prob_tournament(population, self.params["amount"], self.params["p"])
+            parents = prob_tournament(
+                population, self.params["amount"], self.params["p"]
+            )
 
         elif self.method == ParentSelMethod.BEST:
             parents = select_best(population, self.params["amount"])
-        
+
         elif self.method == ParentSelMethod.RANDOM:
             parents = uniform_selection(population, self.params["amount"])
-        
+
         elif self.method == ParentSelMethod.ROULETTE:
-            parents = roulette(population, self.params["amount"], self.params["method"], self.params["F"])
-        
+            parents = roulette(
+                population,
+                self.params["amount"],
+                self.params["method"],
+                self.params["F"],
+            )
+
         elif self.method == ParentSelMethod.SUS:
-            parents = sus(population, self.params["amount"], self.params["method"], self.params["F"])
+            parents = sus(
+                population,
+                self.params["amount"],
+                self.params["method"],
+                self.params["F"],
+            )
 
         elif self.method == ParentSelMethod.NOTHING:
             parents = population
diff --git a/src/metaheuristic_designer/SelectionMethods/SurvivorSelection.py b/src/metaheuristic_designer/selectionMethods/SurvivorSelection.py
similarity index 81%
rename from src/metaheuristic_designer/SelectionMethods/SurvivorSelection.py
rename to src/metaheuristic_designer/selectionMethods/SurvivorSelection.py
index eba0888..82b5843 100644
--- a/src/metaheuristic_designer/SelectionMethods/SurvivorSelection.py
+++ b/src/metaheuristic_designer/selectionMethods/SurvivorSelection.py
@@ -18,11 +18,10 @@ class SurvSelMethod(Enum):
 
     @staticmethod
     def from_str(str_input):
-
         str_input = str_input.lower()
 
         if str_input not in surv_method_map:
-            raise ValueError(f"Parent selection method \"{str_input}\" not defined")
+            raise ValueError(f'Parent selection method "{str_input}" not defined')
 
         return surv_method_map[str_input]
 
@@ -40,7 +39,7 @@ def from_str(str_input):
     "keepbest": SurvSelMethod.MU_PLUS_LAMBDA,
     "(m,n)": SurvSelMethod.MU_COMMA_LAMBDA,
     "keepoffsping": SurvSelMethod.MU_COMMA_LAMBDA,
-    "cro": SurvSelMethod.CRO
+    "cro": SurvSelMethod.CRO,
 }
 
 
@@ -60,19 +59,27 @@ class SurvivorSelection(SelectionMethod):
         The name that will be assigned to this selection method.
     """
 
-    def __init__(self, method: str, params: Union[ParamScheduler, dict] = None, padding: bool = False, name: str = None):
+    def __init__(
+        self,
+        method: str,
+        params: Union[ParamScheduler, dict] = None,
+        padding: bool = False,
+        name: str = None,
+    ):
         """
         Constructor for the SurvivorSelection class
         """
 
         if name is None:
             name = method
-        
+
         super().__init__(params, padding, name)
 
         self.method = SurvSelMethod.from_str(method)
-    
-    def select(self, popul: List[Individual], offspring: List[Individual]) -> List[Individual]:
+
+    def select(
+        self, popul: List[Individual], offspring: List[Individual]
+    ) -> List[Individual]:
         result = []
         if self.method == SurvSelMethod.ELITISM:
             result = elitism(popul, offspring, self.params["amount"])
@@ -94,8 +101,15 @@ def select(self, popul: List[Individual], offspring: List[Individual]) -> List[I
 
         elif self.method == SurvSelMethod.MU_COMMA_LAMBDA:
             result = lamb_comma_mu(popul, offspring)
-        
+
         elif self.method == SurvSelMethod.CRO:
-            result = cro_selection(popul, offspring, self.params["Fd"], self.params["Pd"], self.params["attempts"], self.params["maxPopSize"])
+            result = cro_selection(
+                popul,
+                offspring,
+                self.params["Fd"],
+                self.params["Pd"],
+                self.params["attempts"],
+                self.params["maxPopSize"],
+            )
 
         return result
diff --git a/src/metaheuristic_designer/SelectionMethods/__init__.py b/src/metaheuristic_designer/selectionMethods/__init__.py
similarity index 82%
rename from src/metaheuristic_designer/SelectionMethods/__init__.py
rename to src/metaheuristic_designer/selectionMethods/__init__.py
index 7d8841a..65417eb 100644
--- a/src/metaheuristic_designer/SelectionMethods/__init__.py
+++ b/src/metaheuristic_designer/selectionMethods/__init__.py
@@ -1,4 +1,4 @@
 from .SurvivorSelection import SurvivorSelection, SurvSelMethod, surv_method_map
 from .survivor_selection_functions import *
 from .ParentSelection import ParentSelection, ParentSelMethod, parent_sel_map
-from .parent_selection_functions import *
\ No newline at end of file
+from .parent_selection_functions import *
diff --git a/src/metaheuristic_designer/SelectionMethods/parent_selection_functions.py b/src/metaheuristic_designer/selectionMethods/parent_selection_functions.py
similarity index 91%
rename from src/metaheuristic_designer/SelectionMethods/parent_selection_functions.py
rename to src/metaheuristic_designer/selectionMethods/parent_selection_functions.py
index 2ce0dea..4a5edc6 100644
--- a/src/metaheuristic_designer/SelectionMethods/parent_selection_functions.py
+++ b/src/metaheuristic_designer/selectionMethods/parent_selection_functions.py
@@ -4,6 +4,7 @@
 import enum
 from enum import Enum
 
+
 class SelectionDist(Enum):
     FIT_PROP = enum.auto()
     SIGMA_SCALE = enum.auto()
@@ -12,11 +13,10 @@ class SelectionDist(Enum):
 
     @staticmethod
     def from_str(str_input):
-
         str_input = str_input.lower()
 
         if str_input not in select_dist_map:
-            raise ValueError(f"Selection distribution \"{str_input}\" not defined")
+            raise ValueError(f'Selection distribution "{str_input}" not defined')
 
         return select_dist_map[str_input]
 
@@ -82,7 +82,6 @@ def prob_tournament(population, tourn_size, prob):
     order = []
 
     for _ in population:
-
         # Choose 'tourn_size' individuals for the torunament
         parent_idxs = random.sample(range(len(population)), tourn_size)
         parents = [population[i] for i in parent_idxs]
@@ -102,6 +101,7 @@ def prob_tournament(population, tourn_size, prob):
     # return parent_pool, order
     return parent_pool
 
+
 def uniform_selection(population, amount):
     """
     Chooses a number of individuals from the population at random.
@@ -128,7 +128,7 @@ def uniform_selection(population, amount):
 
 def selection_distribution(population, method, f=2):
     """
-    Gives the weights that will be applied to each individual in 
+    Gives the weights that will be applied to each individual in
     the selection process.
 
     Parameters
@@ -143,28 +143,29 @@ def selection_distribution(population, method, f=2):
     Returns
     -------
     weights: ndarray
-        Weight assinged to each of the individuals 
+        Weight assinged to each of the individuals
     """
     fit_list = np.fromiter((i.fitness for i in population), float)
 
     if method == SelectionDist.FIT_PROP:
         weights = fit_list
     elif method == SelectionDist.SIGMA_SCALE:
-        weights = np.maximum(fit_list-(fit_list.mean() - f*fit_list.std()), 0)
+        weights = np.maximum(fit_list - (fit_list.mean() - f * fit_list.std()), 0)
     elif method == SelectionDist.LIN_RANK:
         fit_order = np.argsort(fit_list)
         n_parents = len(population)
-        weights = (2 - f) + (2*fit_order*(f-1))/(n_parents-1)
+        weights = (2 - f) + (2 * fit_order * (f - 1)) / (n_parents - 1)
     elif method == SelectionDist.EXP_RANK:
         fit_order = np.argsort(fit_list)
         weights = 1 - np.exp(-fit_order)
-    
+
     weight_norm = weights.sum()
     if weight_norm == 0:
         weights += 1
         weight_norm = weights.sum()
 
-    return weights/weight_norm
+    return weights / weight_norm
+
 
 def roulette(population, amount, method=None, f=None):
     """
@@ -189,14 +190,17 @@ def roulette(population, amount, method=None, f=None):
 
     if method is None:
         method = "basic"
-    
+
     if f is None:
         f = 2
 
     weights = selection_distribution(population, method, f)
 
     if np.any(weights < 0):
-        warnings.warn("Some values of fitness resulted in negative selection probabilities in the parent selection step.", stacklevel=2)
+        warnings.warn(
+            "Some values of fitness resulted in negative selection probabilities in the parent selection step.",
+            stacklevel=2,
+        )
 
     order = random.choices(range(len(population)), k=amount, weights=weights)
     parents = [population[i] for i in order]
@@ -228,21 +232,21 @@ def sus(population, amount, method=None, f=None):
 
     if method is None:
         method = "basic"
-    
+
     if f is None:
         f = 2
 
     weights = selection_distribution(population, method, f)
-    
+
     cum_weights = np.cumsum(weights)
-    
+
     order = []
     current_member = i = 1
     while current_member < amount:
-        r = random.random()/amount
+        r = random.random() / amount
         while r <= cum_weights[i] and current_member < amount:
             order.append(i)
-            r = r + 1/amount
+            r = r + 1 / amount
             current_member += 1
         i += 1
 
@@ -250,6 +254,3 @@ def sus(population, amount, method=None, f=None):
 
     # return parents, order
     return parents
-
-
-    
diff --git a/src/metaheuristic_designer/SelectionMethods/survivor_selection_functions.py b/src/metaheuristic_designer/selectionMethods/survivor_selection_functions.py
similarity index 97%
rename from src/metaheuristic_designer/SelectionMethods/survivor_selection_functions.py
rename to src/metaheuristic_designer/selectionMethods/survivor_selection_functions.py
index 26d4a54..fe25c7c 100644
--- a/src/metaheuristic_designer/SelectionMethods/survivor_selection_functions.py
+++ b/src/metaheuristic_designer/selectionMethods/survivor_selection_functions.py
@@ -1,6 +1,7 @@
 from copy import copy
 import random
 
+
 def argsort(seq):
     """
     Implementation of argsort for python-style lists.
@@ -10,11 +11,11 @@ def argsort(seq):
     ----------
     seq: Iterable
         Iterable for which we want to obtain the order of.
-    
+
     Returns
     -------
     order: List
-        The positions of the original elements of the list in order. 
+        The positions of the original elements of the list in order.
     """
 
     return sorted(range(len(seq)), key=seq.__getitem__)
@@ -31,7 +32,7 @@ def one_to_one(popul, offspring):
         Original population of individuals before being operated on.
     offspring: List[Individual]
         Individuals resulting from an iteration of the algorithm.
-    
+
     Returns
     -------
     survivors: List[Individual]
@@ -54,7 +55,7 @@ def one_to_one(popul, offspring):
 
 def prob_one_to_one(popul, offspring, p):
     """
-    Compares each new individual with its parent and it replaces it with a 
+    Compares each new individual with its parent and it replaces it with a
     probability of p or if it has a better fitness.
 
     Parameters
@@ -65,7 +66,7 @@ def prob_one_to_one(popul, offspring, p):
         Individuals resulting from an iteration of the algorithm.
     p: float
         Probability that an individual will be replaced by its child even if it has a worse fitness.
-    
+
     Returns
     -------
     survivors: List[Individual]
@@ -99,13 +100,15 @@ def elitism(popul, offspring, amount):
         Individuals resulting from an iteration of the algorithm.
     amount: int
         Amount of parents from the original population that will be kept.
-    
+
     Returns
     -------
     survivors: List[Individual]
         The individuals selected for the next generation.
     """
-    selected_offspring = sorted(offspring, reverse=True, key=lambda x: x.fitness)[:len(popul) - amount]
+    selected_offspring = sorted(offspring, reverse=True, key=lambda x: x.fitness)[
+        : len(popul) - amount
+    ]
     best_parents = sorted(popul, reverse=True, key=lambda x: x.fitness)[:amount]
 
     return best_parents + selected_offspring
@@ -121,7 +124,7 @@ def cond_elitism(popul, offspring, amount):
         Individuals resulting from an iteration of the algorithm.
     amount: int
         Amount of parents from the original population that will be kept.
-    
+
     Returns
     -------
     survivors: List[Individual]
@@ -129,7 +132,9 @@ def cond_elitism(popul, offspring, amount):
     """
 
     best_parents = sorted(popul, reverse=True, key=lambda x: x.fitness)[:amount]
-    new_offspring = sorted(offspring, reverse=True, key=lambda x: x.fitness)[:len(popul)]
+    new_offspring = sorted(offspring, reverse=True, key=lambda x: x.fitness)[
+        : len(popul)
+    ]
     best_offspring = new_offspring[:amount]
 
     for idx, val in enumerate(best_parents):
@@ -151,7 +156,7 @@ def lamb_plus_mu(popul, offspring):
         Original population of individuals before being operated on.
     offspring: List[Individual]
         Individuals resulting from an iteration of the algorithm.
-    
+
     Returns
     -------
     survivors: List[Individual]
@@ -159,7 +164,7 @@ def lamb_plus_mu(popul, offspring):
     """
 
     population = popul + offspring
-    return sorted(population, reverse=True, key=lambda x: x.fitness)[:len(popul)]
+    return sorted(population, reverse=True, key=lambda x: x.fitness)[: len(popul)]
 
 
 def lamb_comma_mu(popul, offspring):
@@ -172,22 +177,22 @@ def lamb_comma_mu(popul, offspring):
         Original population of individuals before being operated on.
     offspring: List[Individual]
         Individuals resulting from an iteration of the algorithm.
-    
+
     Returns
     -------
     survivors: List[Individual]
         The individuals selected for the next generation.
     """
 
-    return sorted(offspring, reverse=True, key=lambda x: x.fitness)[:len(popul)]
+    return sorted(offspring, reverse=True, key=lambda x: x.fitness)[: len(popul)]
 
 
 def _cro_set_larvae(population, offspring, attempts, maxpopsize):
     """
     First step of the CRO selection function.
-    
-    Each individual in the offsring tries to settle down into the reef, 
-    if the spot they find is empty they are accepted, if there is already 
+
+    Each individual in the offsring tries to settle down into the reef,
+    if the spot they find is empty they are accepted, if there is already
     an individual in that spot, the one with the best fitness is kept.
     """
 
@@ -205,7 +210,7 @@ def _cro_set_larvae(population, offspring, attempts, maxpopsize):
                 new_population[idx] = larva
 
             attempts_left -= 1
-    
+
     return new_population
 
 
@@ -220,7 +225,7 @@ def _cro_depredation(population, Fd, Pd):
     kept.
     """
 
-    amount = int(len(population)*Fd)
+    amount = int(len(population) * Fd)
 
     fitness_values = [coral.fitness for coral in population]
     affected_corals = argsort(fitness_values)[:amount]
@@ -231,7 +236,7 @@ def _cro_depredation(population, Fd, Pd):
     for idx, val in enumerate(affected_corals):
         if alive_count <= 2:
             break
-        
+
         dies = random.random() <= Pd
         dead_list[idx] = dies
         if dies:
@@ -264,13 +269,13 @@ def cro_selection(popul, offspring, Fd, Pd, attempts, maxpopsize):
         position with an individual with a better fitness value.
     maxpopsize: int
         Maximum size of the population.
-    
+
     Returns
     -------
     survivors: List[Individual]
         The individuals selected for the next generation.
     """
-    
+
     setted_corals = _cro_set_larvae(popul, offspring, attempts, maxpopsize)
     reduced_population = _cro_depredation(setted_corals, Fd, Pd)
     return reduced_population
diff --git a/src/metaheuristic_designer/simple/__init__.py b/src/metaheuristic_designer/simple/__init__.py
index 02f970a..4f3e13c 100644
--- a/src/metaheuristic_designer/simple/__init__.py
+++ b/src/metaheuristic_designer/simple/__init__.py
@@ -4,4 +4,4 @@
 from .random_search import random_search
 from .evolution_strategy import evolution_strategy
 from .differential_evolution import differential_evolution
-from .simulated_annealing import simulated_annealing
\ No newline at end of file
+from .simulated_annealing import simulated_annealing
diff --git a/src/metaheuristic_designer/simple/differential_evolution.py b/src/metaheuristic_designer/simple/differential_evolution.py
index d61935b..54f87dc 100644
--- a/src/metaheuristic_designer/simple/differential_evolution.py
+++ b/src/metaheuristic_designer/simple/differential_evolution.py
@@ -1,10 +1,11 @@
 from __future__ import annotations
-from ..Initializers import UniformVectorInitializer
-from ..Operators import OperatorInt, OperatorReal, OperatorBinary
-from ..Encodings import TypeCastEncoding
-from ..SelectionMethods import  SurvivorSelection, ParentSelection
-from ..Algorithms import DE
-from ..SearchMethods import GeneralSearch
+from ..initializers import UniformVectorInitializer
+from ..operators import OperatorInt, OperatorReal, OperatorBinary
+from ..encodings import TypeCastEncoding
+from ..selectionMethods import SurvivorSelection, ParentSelection
+from ..algorithms import DE
+from ..searchMethods import GeneralSearch
+
 
 def differential_evolution(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
     """
@@ -23,7 +24,12 @@ def differential_evolution(objfunc: ObjectiveVectorFunc, params: dict) -> Search
         Configured optimization algorithm.
     """
 
-    encoding_str = params["encoding"] if "encoding" in params else "real"
+    if "encoding" not in params:
+        raise ValueError(
+            f'You must specify the encoding in the params structure, the options are "real", "int" and "bin"'
+        )
+    
+    encoding_str = params["encoding"]
 
     if encoding_str.lower() == "real":
         alg = _differential_evolution_real_vec(objfunc, params)
@@ -32,28 +38,41 @@ def differential_evolution(objfunc: ObjectiveVectorFunc, params: dict) -> Search
     elif encoding_str.lower() == "bin":
         alg = _differential_evolution_bin_vec(objfunc, params)
     else:
-        raise ValueError(f"The encoding \"{encoding_str}\" does not exist, try \"real\", \"int\" or \"bin\"")
-    
+        raise ValueError(
+            f'The encoding "{encoding_str}" does not exist, try "real", "int" or "bin"'
+        )
+
     return alg
 
+
 def _differential_evolution_real_vec(objfunc, params):
     """
     Instantiates a differential evolution algorithm to optimize the given objective function.
     This objective function should accept real coded vectors.
     """
 
-    pop_size = params["pop_size"] if "pop_size" in params else 100
-    f = params["F"] if "F" in params else 0.8
-    cr = params["Cr"] if "Cr" in params else 0.9
-    de_type = params["DE_type"] if "DE_type" in params else "de/best/1"
+    pop_size = params.get("pop_size", 100)
+    f = params.get("F", 0.8)
+    cr = params.get("Cr", 0.9)
+    de_type = params.get("DE_type", "de/best/1")
 
-    if de_type not in ["de/rand/1", "de/best/1", "de/rand/2", "de/best/2", "de/current-to-rand/1", "de/current-to-best/1", "de/current-to-pbest/1"]:
-        raise ValueError(f"Differential evolution strategy \"{de_type}\" does not exist.")
+    if de_type not in [
+        "de/rand/1",
+        "de/best/1",
+        "de/rand/2",
+        "de/best/2",
+        "de/current-to-rand/1",
+        "de/current-to-best/1",
+        "de/current-to-pbest/1",
+    ]:
+        raise ValueError(f'Differential evolution strategy "{de_type}" does not exist.')
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=float)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=float
+    )
+
+    de_op = OperatorReal(de_type, {"F": f, "Cr": cr})
 
-    de_op = OperatorReal(de_type, {"F":f, "Cr":cr})
-    
     search_strat = DE(pop_initializer, de_op)
 
     return GeneralSearch(objfunc, search_strat, params=params)
@@ -65,20 +84,35 @@ def _differential_evolution_int_vec(objfunc, params):
     This objective function should accept real coded vectors.
     """
 
-    pop_size = params["pop_size"] if "pop_size" in params else 100
-    f = params["F"] if "F" in params else 0.8
-    cr = params["Cr"] if "Cr" in params else 0.9
-    de_type = params["DE_type"] if "DE_type" in params else "de/best/1"
-
-    if de_type not in ["de/rand/1", "de/best/1", "de/rand/2", "de/best/2", "de/current-to-rand/1", "de/current-to-best/1", "de/current-to-pbest/1"]:
-        raise ValueError(f"Differential evolution strategy \"{de_type}\" does not exist.")
+    pop_size = params.get("pop_size", 100)
+    f = params.get("F", 0.8)
+    cr = params.get("Cr", 0.9)
+    de_type = params.get("DE_type", "de/best/1")
+
+    if de_type not in [
+        "de/rand/1",
+        "de/best/1",
+        "de/rand/2",
+        "de/best/2",
+        "de/current-to-rand/1",
+        "de/current-to-best/1",
+        "de/current-to-pbest/1",
+    ]:
+        raise ValueError(f'Differential evolution strategy "{de_type}" does not exist.')
 
     encoding = TypeCastEncoding(float, int)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=float, encoding=encoding)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize,
+        objfunc.low_lim,
+        objfunc.up_lim,
+        pop_size=pop_size,
+        dtype=float,
+        encoding=encoding,
+    )
+
+    de_op = OperatorReal(de_type, {"F": f, "Cr": cr})
 
-    de_op = OperatorReal(de_type, {"F":f, "Cr":cr})
-    
     search_strat = DE(pop_initializer, de_op)
 
     return GeneralSearch(objfunc, search_strat, params=params)
@@ -90,20 +124,35 @@ def _differential_evolution_bin_vec(objfunc, params):
     This objective function should accept real coded vectors.
     """
 
-    pop_size = params["pop_size"] if "pop_size" in params else 100
-    f = params["F"] if "F" in params else 0.8
-    cr = params["Cr"] if "Cr" in params else 0.9
-    de_type = params["DE_type"] if "DE_type" in params else "de/best/1"
-
-    if de_type not in ["de/rand/1", "de/best/1", "de/rand/2", "de/best/2", "de/current-to-rand/1", "de/current-to-best/1", "de/current-to-pbest/1"]:
-        raise ValueError(f"Differential evolution strategy \"{de_type}\" does not exist.")
+    pop_size = params.get("pop_size", 100)
+    f = params.get("F", 0.8)
+    cr = params.get("Cr", 0.9)
+    de_type = params.get("DE_type", "de/best/1")
+
+    if de_type not in [
+        "de/rand/1",
+        "de/best/1",
+        "de/rand/2",
+        "de/best/2",
+        "de/current-to-rand/1",
+        "de/current-to-best/1",
+        "de/current-to-pbest/1",
+    ]:
+        raise ValueError(f'Differential evolution strategy "{de_type}" does not exist.')
 
     encoding = TypeCastEncoding(float, bool)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=float, encoding=encoding)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize,
+        objfunc.low_lim,
+        objfunc.up_lim,
+        pop_size=pop_size,
+        dtype=float,
+        encoding=encoding,
+    )
+
+    de_op = OperatorReal(de_type, {"F": f, "Cr": cr})
 
-    de_op = OperatorReal(de_type, {"F":f, "Cr":cr})
-    
     search_strat = DE(pop_initializer, de_op)
 
     return GeneralSearch(objfunc, search_strat, params=params)
diff --git a/src/metaheuristic_designer/simple/evolution_strategy.py b/src/metaheuristic_designer/simple/evolution_strategy.py
index dfd6b17..4ad2588 100644
--- a/src/metaheuristic_designer/simple/evolution_strategy.py
+++ b/src/metaheuristic_designer/simple/evolution_strategy.py
@@ -1,10 +1,11 @@
 from __future__ import annotations
-from ..Initializers import UniformVectorInitializer
-from ..Operators import OperatorInt, OperatorReal, OperatorBinary
-from ..SelectionMethods import  SurvivorSelection, ParentSelection
-from ..Encodings import TypeCastEncoding
-from ..Algorithms import ES
-from ..SearchMethods import GeneralSearch
+from ..initializers import UniformVectorInitializer
+from ..operators import OperatorInt, OperatorReal, OperatorBinary
+from ..selectionMethods import SurvivorSelection, ParentSelection
+from ..encodings import TypeCastEncoding
+from ..algorithms import ES
+from ..searchMethods import GeneralSearch
+
 
 def evolution_strategy(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
     """
@@ -23,7 +24,12 @@ def evolution_strategy(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
         Configured optimization algorithm.
     """
 
-    encoding_str = params["encoding"] if "encoding" in params else "bin"
+    if "encoding" not in params:
+        raise ValueError(
+            f'You must specify the encoding in the params structure, the options are "real", "int" and "bin"'
+        )
+    
+    encoding_str = params["encoding"]
 
     if encoding_str.lower() == "bin":
         alg = _evolution_strategy_bin_vec(objfunc, params)
@@ -32,8 +38,10 @@ def evolution_strategy(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
     elif encoding_str.lower() == "real":
         alg = _evolution_strategy_real_vec(objfunc, params)
     else:
-        raise ValueError(f"The encoding \"{encoding_str}\" does not exist, try \"real\", \"int\" or \"bin\"")
-    
+        raise ValueError(
+            f'The encoding "{encoding_str}" does not exist, try "real", "int" or "bin"'
+        )
+
     return alg
 
 
@@ -43,22 +51,31 @@ def _evolution_strategy_bin_vec(objfunc, params):
     This objective function should accept binary coded vectors.
     """
 
-    pop_size = params["pop_size"] if "pop_size" in params else 100
-    offspring_size = params["offspring_size"] if "offspring_size" in params else 150
-    n_parents = params["n_parents"] if "n_parents" in params else 100
-    mutstr = params["mutstr"] if "mutstr" in params else 1
+    pop_size = params.get("pop_size", 100)
+    offspring_size = params.get("offspring_size", 150)
+    n_parents = params.get("n_parents", 100)
+    mutstr = params.get("mutstr", 1)
 
     encoding = TypeCastEncoding(int, bool)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, 0, 1, pop_size=pop_size, dtype=int, encoding=encoding)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, 0, 1, pop_size=pop_size, dtype=int, encoding=encoding
+    )
 
     cross_op = OperatorBinary("Nothing")
-    mutation_op = OperatorBinary("Flip", {"N":mutstr})
+    mutation_op = OperatorBinary("Flip", {"N": mutstr})
 
     parent_sel_op = ParentSelection("Nothing")
     selection_op = SurvivorSelection("(m+n)")
 
-    search_strat = ES(pop_initializer, mutation_op, cross_op, parent_sel_op, selection_op, {"offspringSize":offspring_size})
+    search_strat = ES(
+        pop_initializer,
+        mutation_op,
+        cross_op,
+        parent_sel_op,
+        selection_op,
+        {"offspringSize": offspring_size},
+    )
 
     return GeneralSearch(objfunc, search_strat, params=params)
 
@@ -69,20 +86,37 @@ def _evolution_strategy_int_vec(objfunc, params):
     This objective function should accept integer coded vectors.
     """
 
-    pop_size = params["pop_size"] if "pop_size" in params else 100
-    offspring_size = params["offspring_size"] if "offspring_size" in params else 150
-    n_parents = params["n_parents"] if "n_parents" in params else 100
-    mutstr = params["mutstr"] if "mutstr" in params else 1
+    pop_size = params.get("pop_size", 100)
+    offspring_size = params.get("offspring_size", 150)
+    n_parents = params.get("n_parents", 100)
+    mutstr = params.get("mutstr", 1)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=int)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=int
+    )
 
     cross_op = OperatorInt("Nothing")
-    mutation_op = OperatorInt("MutRand", {"method":"Uniform", "Low":objfunc.low_lim, "Up":objfunc.up_lim, "N":mutstr})
+    mutation_op = OperatorInt(
+        "MutRand",
+        {
+            "method": "Uniform",
+            "Low": objfunc.low_lim,
+            "Up": objfunc.up_lim,
+            "N": mutstr,
+        },
+    )
 
     parent_sel_op = ParentSelection("Nothing")
     selection_op = SurvivorSelection("(m+n)")
 
-    search_strat = ES(pop_initializer, mutation_op, cross_op, parent_sel_op, selection_op, {"offspringSize":offspring_size})
+    search_strat = ES(
+        pop_initializer,
+        mutation_op,
+        cross_op,
+        parent_sel_op,
+        selection_op,
+        {"offspringSize": offspring_size},
+    )
 
     return GeneralSearch(objfunc, search_strat, params=params)
 
@@ -93,18 +127,27 @@ def _evolution_strategy_real_vec(objfunc, params):
     This objective function should accept real coded vectors.
     """
 
-    pop_size = params["pop_size"] if "pop_size" in params else 100
-    offspring_size = params["offspring_size"] if "offspring_size" in params else 150
-    n_parents = params["n_parents"] if "n_parents" in params else 100
-    mutstr = params["mutstr"] if "mutstr" in params else 1e-5
+    pop_size = params.get("pop_size", 100)
+    offspring_size = params.get("offspring_size", 150)
+    n_parents = params.get("n_parents", 100)
+    mutstr = params.get("mutstr", 1e-5)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=float)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=float
+    )
 
     cross_op = OperatorReal("Nothing")
-    mutation_op = OperatorReal("RandNoise", {"method":"Gauss", "F":mutstr})
+    mutation_op = OperatorReal("RandNoise", {"method": "Gauss", "F": mutstr})
     parent_sel_op = ParentSelection("Nothing")
     selection_op = SurvivorSelection("(m+n)")
-    
-    search_strat = ES(pop_initializer, mutation_op, cross_op, parent_sel_op, selection_op, {"offspringSize":offspring_size})
+
+    search_strat = ES(
+        pop_initializer,
+        mutation_op,
+        cross_op,
+        parent_sel_op,
+        selection_op,
+        {"offspringSize": offspring_size},
+    )
 
     return GeneralSearch(objfunc, search_strat, params=params)
diff --git a/src/metaheuristic_designer/simple/genetic_algorithm.py b/src/metaheuristic_designer/simple/genetic_algorithm.py
index fc62b48..dfe284b 100644
--- a/src/metaheuristic_designer/simple/genetic_algorithm.py
+++ b/src/metaheuristic_designer/simple/genetic_algorithm.py
@@ -1,10 +1,11 @@
 from __future__ import annotations
-from ..Initializers import UniformVectorInitializer
-from ..Operators import OperatorInt, OperatorReal, OperatorBinary
-from ..SelectionMethods import  SurvivorSelection, ParentSelection
-from ..Encodings import TypeCastEncoding
-from ..Algorithms import GA
-from ..SearchMethods import GeneralSearch
+from ..initializers import UniformVectorInitializer
+from ..operators import OperatorInt, OperatorReal, OperatorBinary
+from ..selectionMethods import SurvivorSelection, ParentSelection
+from ..encodings import TypeCastEncoding
+from ..algorithms import GA
+from ..searchMethods import GeneralSearch
+
 
 def genetic_algorithm(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
     """
@@ -23,7 +24,12 @@ def genetic_algorithm(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
         Configured optimization algorithm.
     """
 
-    encoding_str = params["encoding"] if "encoding" in params else "bin"
+    if "encoding" not in params:
+        raise ValueError(
+            f'You must specify the encoding in the params structure, the options are "real", "int" and "bin"'
+        )
+    
+    encoding_str = params["encoding"]
 
     if encoding_str.lower() == "bin":
         alg = _genetic_algorithm_bin_vec(objfunc, params)
@@ -32,8 +38,10 @@ def genetic_algorithm(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
     elif encoding_str.lower() == "real":
         alg = _genetic_algorithm_real_vec(objfunc, params)
     else:
-        raise ValueError(f"The encoding \"{encoding_str}\" does not exist, try \"real\", \"int\" or \"bin\"")
-    
+        raise ValueError(
+            f'The encoding "{encoding_str}" does not exist, try "real", "int" or "bin"'
+        )
+
     return alg
 
 
@@ -43,24 +51,33 @@ def _genetic_algorithm_bin_vec(objfunc, params):
     This objective function should accept binary coded vectors.
     """
 
-    pop_size = params["pop_size"] if "pop_size" in params else 100
-    n_parents = params["n_parents"] if "n_parents" in params else 20
-    cross_method = params["cross"] if "cross" in params else "Multipoint"
-    pcross = params["pcross"] if "pcross" in params else 0.8
-    pmut = params["pmut"] if "pmut" in params else 0.1
-    mutstr = params["mutstr"] if "mutstr" in params else 1
+    pop_size = params.get("pop_size", 100)
+    n_parents = params.get("n_parents", 20)
+    cross_method = params.get("cross", "Multipoint")
+    pcross = params.get("pcross", 0.8)
+    pmut = params.get("pmut", 0.1)
+    mutstr = params.get("mutstr", 1)
 
     encoding = TypeCastEncoding(int, bool)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, 0, 1, pop_size=pop_size, dtype=int, encoding=encoding)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, 0, 1, pop_size=pop_size, dtype=int, encoding=encoding
+    )
 
     cross_op = OperatorBinary(cross_method)
-    mutation_op = OperatorBinary("Flip", {"N":mutstr})
+    mutation_op = OperatorBinary("Flip", {"N": mutstr})
 
     parent_sel_op = ParentSelection("Best", {"amount": n_parents})
     selection_op = SurvivorSelection("KeepBest")
-    
-    search_strat = GA(pop_initializer, mutation_op, cross_op, parent_sel_op, selection_op, {"pcross":pcross, "pmut":pmut})
+
+    search_strat = GA(
+        pop_initializer,
+        mutation_op,
+        cross_op,
+        parent_sel_op,
+        selection_op,
+        {"pcross": pcross, "pmut": pmut},
+    )
 
     return GeneralSearch(objfunc, search_strat, params=params)
 
@@ -71,23 +88,39 @@ def _genetic_algorithm_int_vec(objfunc, params):
     This objective function should accept integer coded vectors.
     """
 
-    pop_size = params["pop_size"] if "pop_size" in params else 100
-    n_parents = params["n_parents"] if "n_parents" in params else 20
-    cross_method = params["cross"] if "cross" in params else "Multipoint"
-    pcross = params["pcross"] if "pcross" in params else 0.8
-    pmut = params["pmut"] if "pmut" in params else 0.1
-    mutstr = params["mutstr"] if "mutstr" in params else 1
+    pop_size = params.get("pop_size", 100)
+    n_parents = params.get("n_parents", 20)
+    cross_method = params.get("cross", "Multipoint")
+    pcross = params.get("pcross", 0.8)
+    pmut = params.get("pmut", 0.1)
+    mutstr = params.get("mutstr", 1)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=int)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=int
+    )
 
     cross_op = OperatorInt(cross_method)
-    mutation_op = OperatorInt("MutRand", {"method":"Uniform", "Low":objfunc.low_lim, "Up":objfunc.up_lim, "N":mutstr})
+    mutation_op = OperatorInt(
+        "MutRand",
+        {
+            "method": "Uniform",
+            "Low": objfunc.low_lim,
+            "Up": objfunc.up_lim,
+            "N": mutstr,
+        },
+    )
 
     parent_sel_op = ParentSelection("Best", {"amount": n_parents})
     selection_op = SurvivorSelection("KeepBest")
 
-    
-    search_strat = GA(pop_initializer, mutation_op, cross_op, parent_sel_op, selection_op, {"pcross":pcross, "pmut":pmut})
+    search_strat = GA(
+        pop_initializer,
+        mutation_op,
+        cross_op,
+        parent_sel_op,
+        selection_op,
+        {"pcross": pcross, "pmut": pmut},
+    )
 
     return GeneralSearch(objfunc, search_strat, params=params)
 
@@ -98,22 +131,30 @@ def _genetic_algorithm_real_vec(objfunc, params):
     This objective function should accept real coded vectors.
     """
 
-    pop_size = params["pop_size"] if "pop_size" in params else 100
-    n_parents = params["n_parents"] if "n_parents" in params else 20
-    cross_method = params["cross"] if "cross" in params else "Multipoint"
-    pcross = params["pcross"] if "pcross" in params else 0.8
-    pmut = params["pmut"] if "pmut" in params else 0.1
-    mutstr = params["mutstr"] if "mutstr" in params else 1e-5 
-
+    pop_size = params.get("pop_size", 100)
+    n_parents = params.get("n_parents", 20)
+    cross_method = params.get("cross", "Multipoint")
+    pcross = params.get("pcross", 0.8)
+    pmut = params.get("pmut", 0.1)
+    mutstr = params.get("mutstr", 1e-5)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=float)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=float
+    )
 
     cross_op = OperatorReal(cross_method)
-    mutation_op = OperatorReal("RandNoise", {"method":"Gauss", "F":mutstr, "N":1})
+    mutation_op = OperatorReal("RandNoise", {"method": "Gauss", "F": mutstr, "N": 1})
 
     parent_sel_op = ParentSelection("Best", {"amount": n_parents})
     selection_op = SurvivorSelection("KeepBest")
-    
-    search_strat = GA(pop_initializer, mutation_op, cross_op, parent_sel_op, selection_op, {"pcross":pcross, "pmut":pmut})
+
+    search_strat = GA(
+        pop_initializer,
+        mutation_op,
+        cross_op,
+        parent_sel_op,
+        selection_op,
+        {"pcross": pcross, "pmut": pmut},
+    )
 
     return GeneralSearch(objfunc, search_strat, params=params)
diff --git a/src/metaheuristic_designer/simple/hill_climb.py b/src/metaheuristic_designer/simple/hill_climb.py
index 413235e..8be867d 100644
--- a/src/metaheuristic_designer/simple/hill_climb.py
+++ b/src/metaheuristic_designer/simple/hill_climb.py
@@ -1,9 +1,10 @@
 from __future__ import annotations
-from ..Initializers import UniformVectorInitializer
-from ..Operators import OperatorInt, OperatorReal, OperatorBinary
-from ..Algorithms import HillClimb
-from ..Encodings import TypeCastEncoding
-from ..SearchMethods import GeneralSearch
+from ..initializers import UniformVectorInitializer
+from ..operators import OperatorInt, OperatorReal, OperatorBinary
+from ..algorithms import HillClimb
+from ..encodings import TypeCastEncoding
+from ..searchMethods import GeneralSearch
+
 
 def hill_climb(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
     """
@@ -22,7 +23,12 @@ def hill_climb(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
         Configured optimization algorithm.
     """
 
-    encoding_str = params["encoding"] if "encoding" in params else "bin"
+    if "encoding" not in params:
+        raise ValueError(
+            f'You must specify the encoding in the params structure, the options are "real", "int" and "bin"'
+        )
+    
+    encoding_str = params["encoding"]
 
     if encoding_str.lower() == "bin":
         alg = _hill_climb_bin_vec(objfunc, params)
@@ -31,23 +37,28 @@ def hill_climb(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
     elif encoding_str.lower() == "real":
         alg = _hill_climb_real_vec(objfunc, params)
     else:
-        raise ValueError(f"The encoding \"{encoding_str}\" does not exist, try \"real\", \"int\" or \"bin\"")
-    
+        raise ValueError(
+            f'The encoding "{encoding_str}" does not exist, try "real", "int" or "bin"'
+        )
+
     return alg
 
+
 def _hill_climb_bin_vec(objfunc, params):
     """
     Instantiates a hill climbing algorithm to optimize the given objective function.
     This objective function should accept binary coded vectors.
     """
 
-    mutstr = params["mutstr"] if "mutstr" in params else 1
+    mutstr = params.get("mutstr", 1)
 
     encoding = TypeCastEncoding(int, bool)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, 0, 1, pop_size=1, dtype=int, encoding=encoding)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, 0, 1, pop_size=1, dtype=int, encoding=encoding
+    )
 
-    mutation_op = OperatorBinary("Flip", {"N":mutstr})
+    mutation_op = OperatorBinary("Flip", {"N": mutstr})
 
     search_strat = HillClimb(pop_initializer, mutation_op)
 
@@ -60,11 +71,21 @@ def _hill_climb_int_vec(objfunc, params):
     This objective function should accept integer coded vectors.
     """
 
-    mutstr = params["mutstr"] if "mutstr" in params else 1
+    mutstr = params.get("mutstr", 1)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=1, dtype=int)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=1, dtype=int
+    )
 
-    mutation_op = OperatorInt("MutRand", {"method":"Uniform", "Low":objfunc.low_lim, "Up":objfunc.up_lim, "N":mutstr})
+    mutation_op = OperatorInt(
+        "MutRand",
+        {
+            "method": "Uniform",
+            "Low": objfunc.low_lim,
+            "Up": objfunc.up_lim,
+            "N": mutstr,
+        },
+    )
 
     search_strat = HillClimb(pop_initializer, mutation_op)
 
@@ -77,12 +98,14 @@ def _hill_climb_real_vec(objfunc, params):
     This objective function should accept real coded vectors.
     """
 
-    mutstr = params["mutstr"] if "mutstr" in params else 1e-5 
+    mutstr = params.get("mutstr", 1e-5)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=1, dtype=float)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=1, dtype=float
+    )
+
+    mutation_op = OperatorReal("RandNoise", {"method": "Gauss", "F": mutstr})
 
-    mutation_op = OperatorReal("RandNoise", {"method":"Gauss", "F":mutstr})
-    
     search_strat = HillClimb(pop_initializer, mutation_op)
 
-    return GeneralSearch(objfunc, search_strat, params=params)
\ No newline at end of file
+    return GeneralSearch(objfunc, search_strat, params=params)
diff --git a/src/metaheuristic_designer/simple/particle_swarm.py b/src/metaheuristic_designer/simple/particle_swarm.py
index 1b7d751..95f1513 100644
--- a/src/metaheuristic_designer/simple/particle_swarm.py
+++ b/src/metaheuristic_designer/simple/particle_swarm.py
@@ -1,8 +1,9 @@
 from __future__ import annotations
-from ..Initializers import UniformVectorInitializer
-from ..Encodings import TypeCastEncoding
-from ..Algorithms import PSO
-from ..SearchMethods import GeneralSearch
+from ..initializers import UniformVectorInitializer
+from ..encodings import TypeCastEncoding
+from ..algorithms import PSO
+from ..searchMethods import GeneralSearch
+
 
 def particle_swarm(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
     """
@@ -21,7 +22,12 @@ def particle_swarm(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
         Configured optimization algorithm.
     """
 
-    encoding_str = params["encoding"] if "encoding" in params else "real"
+    if "encoding" not in params:
+        raise ValueError(
+            f'You must specify the encoding in the params structure, the options are "real", "int" and "bin"'
+        )
+    
+    encoding_str = params["encoding"]
 
     if encoding_str.lower() == "real":
         alg = _particle_swarm_real_vec(objfunc, params)
@@ -30,8 +36,10 @@ def particle_swarm(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
     elif encoding_str.lower() == "bin":
         alg = _particle_swarm_bin_vec(objfunc, params)
     else:
-        raise ValueError(f"The encoding \"{encoding_str}\" does not exist, try \"real\", \"int\" or \"bin\"")
-    
+        raise ValueError(
+            f'The encoding "{encoding_str}" does not exist, try "real", "int" or "bin"'
+        )
+
     return alg
 
 
@@ -41,33 +49,43 @@ def _particle_swarm_real_vec(objfunc, params):
     This objective function should accept real coded vectors.
     """
 
-    pop_size = params["pop_size"] if "pop_size" in params else 100
-    w = params["w"] if "w" in params else 0.7
-    c1 = params["c1"] if "c1" in params else 1.5
-    c2 = params["c2"] if "c2" in params else 1.5
+    pop_size = params.get("pop_size", 100)
+    w = params.get("w", 0.7)
+    c1 = params.get("c1", 1.5)
+    c2 = params.get("c2", 1.5)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=float)
-    
-    search_strat = PSO(pop_initializer, {"w":w, "c1":c1, "c2":c2})
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=float
+    )
+
+    search_strat = PSO(pop_initializer, {"w": w, "c1": c1, "c2": c2})
 
     return GeneralSearch(objfunc, search_strat, params=params)
 
+
 def _particle_swarm_int_vec(objfunc, params):
     """
     Instantiates a particle swarm algorithm to optimize the given objective function.
     This objective function should accept real coded vectors.
     """
 
-    pop_size = params["pop_size"] if "pop_size" in params else 100
-    w = params["w"] if "w" in params else 0.7
-    c1 = params["c1"] if "c1" in params else 1.5
-    c2 = params["c2"] if "c2" in params else 1.5
+    pop_size = params.get("pop_size", 100)
+    w = params.get("w", 0.7)
+    c1 = params.get("c1", 1.5)
+    c2 = params.get("c2", 1.5)
 
     encoding = TypeCastEncoding(float, int)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=float, encoding=encoding)
-    
-    search_strat = PSO(pop_initializer, {"w":w, "c1":c1, "c2":c2})
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize,
+        objfunc.low_lim,
+        objfunc.up_lim,
+        pop_size=pop_size,
+        dtype=float,
+        encoding=encoding,
+    )
+
+    search_strat = PSO(pop_initializer, {"w": w, "c1": c1, "c2": c2})
 
     return GeneralSearch(objfunc, search_strat, params=params)
 
@@ -78,15 +96,22 @@ def _particle_swarm_bin_vec(objfunc, params):
     This objective function should accept real coded vectors.
     """
 
-    pop_size = params["pop_size"] if "pop_size" in params else 100
-    w = params["w"] if "w" in params else 0.7
-    c1 = params["c1"] if "c1" in params else 1.5
-    c2 = params["c2"] if "c2" in params else 1.5
+    pop_size = params.get("pop_size", 100)
+    w = params.get("w", 0.7)
+    c1 = params.get("c1", 1.5)
+    c2 = params.get("c2", 1.5)
 
     encoding = TypeCastEncoding(float, bool)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=float, encoding=encoding)
-    
-    search_strat = PSO(pop_initializer, {"w":w, "c1":c1, "c2":c2})
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize,
+        objfunc.low_lim,
+        objfunc.up_lim,
+        pop_size=pop_size,
+        dtype=float,
+        encoding=encoding,
+    )
+
+    search_strat = PSO(pop_initializer, {"w": w, "c1": c1, "c2": c2})
 
     return GeneralSearch(objfunc, search_strat, params=params)
diff --git a/src/metaheuristic_designer/simple/random_search.py b/src/metaheuristic_designer/simple/random_search.py
index 4f38b20..c657a4c 100644
--- a/src/metaheuristic_designer/simple/random_search.py
+++ b/src/metaheuristic_designer/simple/random_search.py
@@ -1,8 +1,9 @@
 from __future__ import annotations
-from ..Initializers import UniformVectorInitializer
-from ..Encodings import TypeCastEncoding
-from ..Algorithms import RandomSearch
-from ..SearchMethods import GeneralSearch
+from ..initializers import UniformVectorInitializer
+from ..encodings import TypeCastEncoding
+from ..algorithms import RandomSearch
+from ..searchMethods import GeneralSearch
+
 
 def random_search(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
     """
@@ -21,7 +22,12 @@ def random_search(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
         Configured optimization algorithm.
     """
 
-    encoding_str = params["encoding"] if "encoding" in params else "bin"
+    if "encoding" not in params:
+        raise ValueError(
+            f'You must specify the encoding in the params structure, the options are "real", "int" and "bin"'
+        )
+    
+    encoding_str = params["encoding"]
 
     if encoding_str.lower() == "bin":
         alg = _random_search_bin_vec(objfunc, params)
@@ -30,10 +36,13 @@ def random_search(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
     elif encoding_str.lower() == "real":
         alg = _random_search_real_vec(objfunc, params)
     else:
-        raise ValueError(f"The encoding \"{encoding_str}\" does not exist, try \"real\", \"int\" or \"bin\"")
-    
+        raise ValueError(
+            f'The encoding "{encoding_str}" does not exist, try "real", "int" or "bin"'
+        )
+
     return alg
 
+
 def _random_search_bin_vec(objfunc, params):
     """
     Instantiates a random search algorithm to optimize the given objective function.
@@ -42,7 +51,9 @@ def _random_search_bin_vec(objfunc, params):
 
     encoding = TypeCastEncoding(int, bool)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, 0, 1, pop_size=1, dtype=int, encoding=encoding)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, 0, 1, pop_size=1, dtype=int, encoding=encoding
+    )
 
     search_strat = RandomSearch(pop_initializer)
 
@@ -55,7 +66,9 @@ def _random_search_int_vec(objfunc, params):
     This objective function should accept integer coded vectors.
     """
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=int)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=int
+    )
 
     search_strat = RandomSearch(pop_initializer)
 
@@ -68,14 +81,16 @@ def _random_search_real_vec(objfunc, params):
     This objective function should accept real coded vectors.
     """
 
-    pop_size = params["pop_size"] if "pop_size" in params else 100
-    n_parents = params["n_parents"] if "n_parents" in params else 20
-    pcross = params["pcross"] if "pcross" in params else 0.8
-    pmut = params["pmut"] if "pmut" in params else 0.1
-    mutstr = params["mutstr"] if "mutstr" in params else 1e-5 
+    pop_size = params.get("pop_size", 100)
+    n_parents = params.get("n_parents", 20)
+    pcross = params.get("pcross", 0.8)
+    pmut = params.get("pmut", 0.1)
+    mutstr = params.get("mutstr", 1e-5)
+
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=float
+    )
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=float)
-    
     search_strat = RandomSearch(pop_initializer)
 
     return GeneralSearch(objfunc, search_strat, params=params)
diff --git a/src/metaheuristic_designer/simple/simulated_annealing.py b/src/metaheuristic_designer/simple/simulated_annealing.py
index 84bc535..1d12dc9 100644
--- a/src/metaheuristic_designer/simple/simulated_annealing.py
+++ b/src/metaheuristic_designer/simple/simulated_annealing.py
@@ -1,9 +1,10 @@
 from __future__ import annotations
-from ..Initializers import UniformVectorInitializer
-from ..Operators import OperatorInt, OperatorReal, OperatorBinary
-from ..Algorithms import SA
-from ..Encodings import TypeCastEncoding
-from ..SearchMethods import GeneralSearch
+from ..initializers import UniformVectorInitializer
+from ..operators import OperatorInt, OperatorReal, OperatorBinary
+from ..algorithms import SA
+from ..encodings import TypeCastEncoding
+from ..searchMethods import GeneralSearch
+
 
 def simulated_annealing(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
     """
@@ -22,7 +23,12 @@ def simulated_annealing(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
         Configured optimization algorithm.
     """
 
-    encoding_str = params["encoding"] if "encoding" in params else "bin"
+    if "encoding" not in params:
+        raise ValueError(
+            f'You must specify the encoding in the params structure, the options are "real", "int" and "bin"'
+        )
+    
+    encoding_str = params["encoding"]
 
     if encoding_str.lower() == "bin":
         alg = _simulated_annealing_bin_vec(objfunc, params)
@@ -31,28 +37,37 @@ def simulated_annealing(objfunc: ObjectiveVectorFunc, params: dict) -> Search:
     elif encoding_str.lower() == "real":
         alg = _simulated_annealing_real_vec(objfunc, params)
     else:
-        raise ValueError(f"The encoding \"{encoding_str}\" does not exist, try \"real\", \"int\" or \"bin\"")
-    
+        raise ValueError(
+            f'The encoding "{encoding_str}" does not exist, try "real", "int" or "bin"'
+        )
+
     return alg
 
+
 def _simulated_annealing_bin_vec(objfunc, params):
     """
     Instantiates a simulated annealing algorithm to optimize the given objective function.
     This objective function should accept binary coded vectors.
     """
 
-    n_iter = params["iter"] if "iter" in params else 100
-    temp_init = params["temp_init"] if "temp_init" in params else 100
-    alpha = params["alpha"] if "alpha" in params else 0.99
-    mutstr = params["mutstr"] if "mutstr" in params else 1
+    n_iter = params.get("iter", 100)
+    temp_init = params.get("temp_init", 100)
+    alpha = params.get("alpha", 0.99)
+    mutstr = params.get("mutstr", 1)
 
     encoding = TypeCastEncoding(int, bool)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, 0, 1, pop_size=1, dtype=int, encoding=encoding)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, 0, 1, pop_size=1, dtype=int, encoding=encoding
+    )
 
-    mutation_op = OperatorBinary("Flip", {"N":mutstr})
+    mutation_op = OperatorBinary("Flip", {"N": mutstr})
 
-    search_strat = SA(pop_initializer, mutation_op, {"iter":n_iter, "temp_init":temp_init, "alpha":alpha})
+    search_strat = SA(
+        pop_initializer,
+        mutation_op,
+        {"iter": n_iter, "temp_init": temp_init, "alpha": alpha},
+    )
 
     return GeneralSearch(objfunc, search_strat, params=params)
 
@@ -63,16 +78,30 @@ def _simulated_annealing_int_vec(objfunc, params):
     This objective function should accept integer coded vectors.
     """
 
-    n_iter = params["iter"] if "iter" in params else 100
-    temp_init = params["temp_init"] if "temp_init" in params else 100
-    alpha = params["alpha"] if "alpha" in params else 0.99
-    mutstr = params["mutstr"] if "mutstr" in params else 1
-
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=1, dtype=int)
-
-    mutation_op = OperatorInt("MutRand", {"method":"Uniform", "Low":objfunc.low_lim, "Up":objfunc.up_lim, "N":mutstr})
-
-    search_strat = SA(pop_initializer, mutation_op, {"iter":n_iter, "temp_init":temp_init, "alpha":alpha})
+    n_iter = params.get("iter", 100)
+    temp_init = params.get("temp_init", 100)
+    alpha = params.get("alpha", 0.99)
+    mutstr = params.get("mutstr", 1)
+
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=1, dtype=int
+    )
+
+    mutation_op = OperatorInt(
+        "MutRand",
+        {
+            "method": "Uniform",
+            "Low": objfunc.low_lim,
+            "Up": objfunc.up_lim,
+            "N": mutstr,
+        },
+    )
+
+    search_strat = SA(
+        pop_initializer,
+        mutation_op,
+        {"iter": n_iter, "temp_init": temp_init, "alpha": alpha},
+    )
 
     return GeneralSearch(objfunc, search_strat, params=params)
 
@@ -83,15 +112,21 @@ def _simulated_annealing_real_vec(objfunc, params):
     This objective function should accept real coded vectors.
     """
 
-    n_iter = params["iter"] if "iter" in params else 100
-    temp_init = params["temp_init"] if "temp_init" in params else 100
-    alpha = params["alpha"] if "alpha" in params else 0.99
-    mutstr = params["mutstr"] if "mutstr" in params else 1e-5 
+    n_iter = params.get("iter", 100)
+    temp_init = params.get("temp_init", 100)
+    alpha = params.get("alpha", 0.99)
+    mutstr = params.get("mutstr", 1e-5)
 
-    pop_initializer = UniformVectorInitializer(objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=1, dtype=float)
+    pop_initializer = UniformVectorInitializer(
+        objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=1, dtype=float
+    )
 
-    mutation_op = OperatorReal("RandNoise", {"method":"Gauss", "F":mutstr})
-    
-    search_strat = SA(pop_initializer, mutation_op, {"iter":n_iter, "temp_init":temp_init, "alpha":alpha})
+    mutation_op = OperatorReal("RandNoise", {"method": "Gauss", "F": mutstr})
 
-    return GeneralSearch(objfunc, search_strat, params=params)
\ No newline at end of file
+    search_strat = SA(
+        pop_initializer,
+        mutation_op,
+        {"iter": n_iter, "temp_init": temp_init, "alpha": alpha},
+    )
+
+    return GeneralSearch(objfunc, search_strat, params=params)
diff --git a/src/metaheuristic_designer/utils.py b/src/metaheuristic_designer/utils.py
index 3159440..4645e47 100644
--- a/src/metaheuristic_designer/utils.py
+++ b/src/metaheuristic_designer/utils.py
@@ -1,9 +1,11 @@
-from enum import Enum 
+from enum import Enum
 import numpy as np
 import json
 
+
 class NumpyEncoder(json.JSONEncoder):
-    """ Special json encoder for numpy types """
+    """Special json encoder for numpy types"""
+
     def default(self, obj):
         if isinstance(obj, np.integer):
             return int(obj)
@@ -13,4 +15,28 @@ def default(self, obj):
             return obj.tolist()
         elif isinstance(obj, Enum):
             return str(obj)
-        return json.JSONEncoder.default(self, obj)
\ No newline at end of file
+        return json.JSONEncoder.default(self, obj)
+
+
+RAND_GEN = np.random.default_rng()
+
+
+def reset_seed(seed=0):
+    """
+    Resets the seed of the random generator.
+
+    Parameters
+    ----------
+    seed: int, optional
+
+    Returns
+    -------
+    RAND_GEN: RandomGenerator
+        random generator
+    """
+
+    BitGen = type(RAND_GEN.bit_generator)
+
+    RAND_GEN.bit_generator.state = BitGen(seed).state
+
+    return RAND_GEN
diff --git a/tutorials/complete_tutorial.ipynb b/tutorials/complete_tutorial.ipynb
new file mode 100644
index 0000000..36454ea
--- /dev/null
+++ b/tutorials/complete_tutorial.ipynb
@@ -0,0 +1,712 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Solving an optimization problem with the framework"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import metaheuristic_designer as mhd\n",
+    "from metaheuristic_designer import ObjectiveVectorFunc\n",
+    "from metaheuristic_designer.initializers import UniformVectorInitializer\n",
+    "from metaheuristic_designer.searchMethods import GeneralSearch\n",
+    "from metaheuristic_designer.algorithms import HillClimb, GA, CRO\n",
+    "from metaheuristic_designer.operators import OperatorInt\n",
+    "from metaheuristic_designer.selectionMethods import SurvivorSelection, ParentSelection\n",
+    "import numpy as np"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## The objective function\n",
+    "To start, we need to define the function we want to optimize.\n",
+    "\n",
+    "This is done by creating a class that inherits from ```ObjectiveFunc```, or in this case ```ObjectiveVectorFunc``` since it provides the framework to create objective functions that take vectors as inputs.\n",
+    "\n",
+    "For this example, we will try to optimize the path on a grid with randomly placed treasures that the agent must collect. The higher the number of treasures collected, the higher the reward."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "class TresureHunt(ObjectiveVectorFunc):\n",
+    "    \"\"\"\n",
+    "    Our objective function function.\n",
+    "\n",
+    "    Takes an integer-valued vector and outputs the number of treasures collected.\n",
+    "    \"\"\"\n",
+    "\n",
+    "    def __init__(\n",
+    "        self, n_moves, n_tresures, grid_size=(10, 10), name: str = \"Tresure Hunt\"\n",
+    "    ):\n",
+    "        self.n_moves = n_moves\n",
+    "        self.n_tresures = n_tresures\n",
+    "        self.grid_size = np.array(grid_size)\n",
+    "\n",
+    "        # We define a grid with n_treasures\n",
+    "        grid_flat = np.zeros(grid_size).flatten()\n",
+    "        grid_flat[:n_tresures] = 1\n",
+    "        np.random.shuffle(grid_flat)\n",
+    "        self.grid = grid_flat.reshape(grid_size)\n",
+    "\n",
+    "        # We define some extra information for displaying the grid\n",
+    "        self.grid_print_dict = {0: \" _\", 1: \" x\", 2: \" o\", 3: \" #\"}\n",
+    "\n",
+    "        # We specify the size of the vector (n_moves), wether to maximize or minimize (mode),\n",
+    "        # lower limit (low_lim), upper limit (up_lim) and name of the function (name).\n",
+    "        super().__init__(n_moves, mode=\"max\", low_lim=0, up_lim=3, name=name)\n",
+    "\n",
+    "    def objective(self, move_vector):\n",
+    "        \"\"\"\n",
+    "        Go through the specified path and count the amount of treasures collected\n",
+    "        \"\"\"\n",
+    "\n",
+    "        # Set of moves the agent can take, accessed from 0 to 3\n",
+    "        move_set = np.array([[0, 1], [1, 0], [0, -1], [-1, 0]])\n",
+    "\n",
+    "        # Inital position\n",
+    "        pos = np.array([0, 0])\n",
+    "\n",
+    "        # Accumulator of treasures collected\n",
+    "        collected = 0\n",
+    "\n",
+    "        # Copy of the grid\n",
+    "        grid_copy = self.grid.copy()\n",
+    "\n",
+    "        # Travel the path specified by the vector\n",
+    "        for move in move_vector:\n",
+    "            # Move to the next position\n",
+    "            new_pos = pos + move_set[move]\n",
+    "\n",
+    "            # Check if it is on bounds\n",
+    "            if (\n",
+    "                new_pos[0] >= 0\n",
+    "                and new_pos[0] < self.grid_size[0]\n",
+    "                and new_pos[1] >= 0\n",
+    "                and new_pos[1] < self.grid_size[1]\n",
+    "            ):\n",
+    "                pos = new_pos\n",
+    "\n",
+    "                # Collect the treasure if there is one\n",
+    "                if grid_copy[*pos] == 1:\n",
+    "                    collected += 1\n",
+    "                    grid_copy[*pos] = 0\n",
+    "\n",
+    "        return collected\n",
+    "\n",
+    "    def display(self):\n",
+    "        \"\"\"\n",
+    "        Display the grid as text.\n",
+    "        \"\"\"\n",
+    "\n",
+    "        grid_str = \"\"\n",
+    "        for row in self.grid:\n",
+    "            for cell in row:\n",
+    "                grid_str += self.grid_print_dict[cell]\n",
+    "            grid_str += \"\\n\"\n",
+    "        print(grid_str)\n",
+    "\n",
+    "    def display_solution(self, move_vector):\n",
+    "        \"\"\"\n",
+    "        Display the grid with the path taken.\n",
+    "        \"\"\"\n",
+    "\n",
+    "        collected = 0\n",
+    "        move_set = np.array([[0, 1], [1, 0], [0, -1], [-1, 0]])\n",
+    "        pos = np.array([0, 0])\n",
+    "        grid_copy = self.grid.copy()\n",
+    "\n",
+    "        if grid_copy[*pos] == 0:\n",
+    "            grid_copy[*pos] = 2\n",
+    "        elif grid_copy[*pos] == 1:\n",
+    "            grid_copy[*pos] = 3\n",
+    "\n",
+    "        for move in move_vector:\n",
+    "            new_pos = pos + move_set[move]\n",
+    "            if (\n",
+    "                new_pos[0] >= 0\n",
+    "                and new_pos[0] < self.grid_size[0]\n",
+    "                and new_pos[1] >= 0\n",
+    "                and new_pos[1] < self.grid_size[1]\n",
+    "            ):\n",
+    "                pos = new_pos\n",
+    "                if grid_copy[*pos] == 0:\n",
+    "                    grid_copy[*pos] = 2\n",
+    "                elif grid_copy[*pos] == 1:\n",
+    "                    grid_copy[*pos] = 3\n",
+    "\n",
+    "        grid_str = \"\"\n",
+    "        for row in grid_copy:\n",
+    "            for cell in row:\n",
+    "                grid_str += self.grid_print_dict[cell]\n",
+    "            grid_str += \"\\n\"\n",
+    "\n",
+    "        print(grid_str)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "The map we use:\n",
+      " _ _ _ _ _ _ _ _ _ _ _ _ x _ _ _ x _ _ _\n",
+      " _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n",
+      " _ _ _ _ _ _ _ x _ _ _ _ _ _ _ x _ _ x _\n",
+      " _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n",
+      " _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n",
+      " _ _ _ _ x _ _ _ _ _ _ _ _ _ _ _ _ _ x _\n",
+      " _ _ _ _ _ _ _ _ _ _ _ _ _ x _ _ x _ _ _\n",
+      " _ x _ _ _ _ _ _ x x _ _ _ _ _ _ x _ _ _\n",
+      " x _ x _ _ _ x x _ _ _ _ _ _ _ _ x _ _ _\n",
+      " _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ x _ x _\n",
+      " _ _ _ _ x _ _ _ _ _ _ _ _ _ _ _ x _ x _\n",
+      " _ _ _ _ x x _ _ _ _ _ _ _ _ _ _ _ _ _ _\n",
+      " _ _ _ _ _ _ _ _ _ _ x x _ _ _ _ _ _ x _\n",
+      " _ _ _ _ x _ _ _ _ _ _ _ _ _ x _ x x _ _\n",
+      " _ _ _ _ _ x _ _ _ _ _ _ _ _ _ _ _ _ _ _\n",
+      " _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n",
+      " _ _ _ x x x _ _ _ _ _ _ _ _ _ _ _ _ _ _\n",
+      " _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n",
+      " _ _ x _ _ _ _ _ x _ _ _ _ _ _ _ x _ _ _\n",
+      " _ _ _ _ _ _ _ _ _ x _ _ _ _ _ _ _ _ _ _\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Define the objective function\n",
+    "objfunc = TresureHunt(n_moves=200, n_tresures=40, grid_size=(20, 20))\n",
+    "print(\"The map we use:\")\n",
+    "objfunc.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Initializing optimization of Tresure Hunt using HillClimb\n",
+      "---------------------------------------------------------\n",
+      "\n",
+      "Optimizing Tresure Hunt using HillClimb:\n",
+      "\tReal time Spent: 0.0 s\n",
+      "\tCPU time Spent:  0.0 s\n",
+      "\tGeneration: 0\n",
+      "\tBest fitness: 5\n",
+      "\tEvaluations of fitness: 1\n",
+      "\n",
+      "Optimizing Tresure Hunt using HillClimb:\n",
+      "\tReal time Spent: 2.0 s\n",
+      "\tCPU time Spent:  2.0 s\n",
+      "\tGeneration: 4376\n",
+      "\tBest fitness: 16\n",
+      "\tEvaluations of fitness: 4377\n",
+      "\n",
+      "Optimizing Tresure Hunt using HillClimb:\n",
+      "\tReal time Spent: 4.0 s\n",
+      "\tCPU time Spent:  4.0 s\n",
+      "\tGeneration: 8630\n",
+      "\tBest fitness: 19\n",
+      "\tEvaluations of fitness: 8631\n",
+      "\n",
+      "Optimizing Tresure Hunt using HillClimb:\n",
+      "\tReal time Spent: 6.0 s\n",
+      "\tCPU time Spent:  6.0 s\n",
+      "\tGeneration: 12992\n",
+      "\tBest fitness: 20\n",
+      "\tEvaluations of fitness: 12993\n",
+      "\n",
+      "Optimizing Tresure Hunt using HillClimb:\n",
+      "\tReal time Spent: 8.0 s\n",
+      "\tCPU time Spent:  8.0 s\n",
+      "\tGeneration: 17345\n",
+      "\tBest fitness: 20\n",
+      "\tEvaluations of fitness: 17346\n",
+      "\n",
+      "Optimizing Tresure Hunt using HillClimb:\n",
+      "\tReal time Spent: 10.0 s\n",
+      "\tCPU time Spent:  10.0 s\n",
+      "\tGeneration: 21715\n",
+      "\tBest fitness: 20\n",
+      "\tEvaluations of fitness: 21716\n",
+      "\n",
+      "Optimizing Tresure Hunt using HillClimb:\n",
+      "\tReal time Spent: 12.0 s\n",
+      "\tCPU time Spent:  12.0 s\n",
+      "\tGeneration: 26064\n",
+      "\tBest fitness: 20\n",
+      "\tEvaluations of fitness: 26065\n",
+      "\n",
+      "Optimizing Tresure Hunt using HillClimb:\n",
+      "\tReal time Spent: 14.0 s\n",
+      "\tCPU time Spent:  14.0 s\n",
+      "\tGeneration: 30461\n",
+      "\tBest fitness: 20\n",
+      "\tEvaluations of fitness: 30462\n",
+      "\n",
+      "Optimizing Tresure Hunt using HillClimb:\n",
+      "\tReal time Spent: 16.0 s\n",
+      "\tCPU time Spent:  16.0 s\n",
+      "\tGeneration: 34866\n",
+      "\tBest fitness: 20\n",
+      "\tEvaluations of fitness: 34867\n",
+      "\n",
+      "Optimizing Tresure Hunt using HillClimb:\n",
+      "\tReal time Spent: 18.0 s\n",
+      "\tCPU time Spent:  18.0 s\n",
+      "\tGeneration: 39185\n",
+      "\tBest fitness: 20\n",
+      "\tEvaluations of fitness: 39186\n",
+      "\n",
+      "Optimizing Tresure Hunt using HillClimb:\n",
+      "\tReal time Spent: 20.0 s\n",
+      "\tCPU time Spent:  20.0 s\n",
+      "\tGeneration: 43568\n",
+      "\tBest fitness: 20\n",
+      "\tEvaluations of fitness: 43569\n",
+      "\n",
+      "Optimizing Tresure Hunt using HillClimb:\n",
+      "\tReal time Spent: 22.0 s\n",
+      "\tCPU time Spent:  22.0 s\n",
+      "\tGeneration: 47928\n",
+      "\tBest fitness: 21\n",
+      "\tEvaluations of fitness: 47929\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "params = {\"stop_cond\": \"neval\", \"neval\": 50000, \"verbose\": True, \"v_timer\": 2}\n",
+    "\n",
+    "# Define a population initialization module\n",
+    "pop_init = UniformVectorInitializer(\n",
+    "    objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=1, dtype=int\n",
+    ")\n",
+    "\n",
+    "# Define the operators to use\n",
+    "mutate_op = OperatorInt(\"MutSample\", {\"method\": \"Uniform\", \"Low\": 0, \"Up\": 3, \"N\": 8})\n",
+    "\n",
+    "# Instanciate the algorithm\n",
+    "algorithm = HillClimb(pop_init, mutate_op)\n",
+    "\n",
+    "# Define a Search method\n",
+    "search = GeneralSearch(objfunc, algorithm, params)\n",
+    "\n",
+    "# Optimize the objective function\n",
+    "best_solution, best_fitness = search.optimize()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Number of treasures obtained: 21\n",
+      " o o o o _ _ _ _ _ _ _ _ x _ _ _ x _ _ _\n",
+      " _ _ o o o _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n",
+      " o o o o o o o # _ _ _ _ _ _ _ x _ _ x _\n",
+      " _ o o o o o _ _ _ _ _ _ _ _ _ _ _ _ _ _\n",
+      " o o o _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n",
+      " o o o o # o o _ _ _ _ _ _ _ _ _ _ _ x _\n",
+      " o o o o o o _ _ _ _ _ _ _ x _ _ x _ _ _\n",
+      " o # _ _ o _ _ o # # _ _ _ _ _ _ x _ _ _\n",
+      " # o # o _ _ # # _ o o _ _ _ _ _ x _ _ _\n",
+      " _ _ _ o o _ o _ _ o o _ _ _ _ _ x _ x _\n",
+      " _ _ o o # o o o o _ o o _ _ _ _ x _ x _\n",
+      " _ _ o o # # o _ o o o o _ _ _ _ _ _ _ _\n",
+      " _ _ _ _ o o o o o _ # # _ _ _ _ _ _ x _\n",
+      " _ _ _ _ # o o o o o o o _ _ x _ x x _ _\n",
+      " _ _ o o o # o o o o _ _ _ _ _ _ _ _ _ _\n",
+      " _ _ _ o _ _ _ _ o o _ _ _ _ _ _ _ _ _ _\n",
+      " _ _ _ # # # o o _ _ _ _ _ _ _ _ _ _ _ _\n",
+      " _ _ _ _ _ _ _ o o _ _ _ _ _ _ _ _ _ _ _\n",
+      " _ _ x _ o o o o # _ _ _ _ _ _ _ x _ _ _\n",
+      " _ _ _ _ _ _ o o o # _ _ _ _ _ _ _ _ _ _\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "print(f\"Number of treasures obtained: {best_fitness}\")\n",
+    "objfunc.display_solution(best_solution)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Initializing optimization of Tresure Hunt using GA\n",
+      "--------------------------------------------------\n",
+      "\n",
+      "Optimizing Tresure Hunt using GA:\n",
+      "\tReal time Spent: 0.04 s\n",
+      "\tCPU time Spent:  0.04 s\n",
+      "\tGeneration: 0\n",
+      "\tBest fitness: 9\n",
+      "\tEvaluations of fitness: 100\n",
+      "\tdiversity: 0.955\n",
+      "\n",
+      "Optimizing Tresure Hunt using GA:\n",
+      "\tReal time Spent: 2.01 s\n",
+      "\tCPU time Spent:  2.01 s\n",
+      "\tGeneration: 49\n",
+      "\tBest fitness: 19\n",
+      "\tEvaluations of fitness: 5000\n",
+      "\tdiversity: 1.03\n",
+      "\n",
+      "Optimizing Tresure Hunt using GA:\n",
+      "\tReal time Spent: 4.02 s\n",
+      "\tCPU time Spent:  4.02 s\n",
+      "\tGeneration: 95\n",
+      "\tBest fitness: 23\n",
+      "\tEvaluations of fitness: 9600\n",
+      "\tdiversity: 1.07\n",
+      "\n",
+      "Optimizing Tresure Hunt using GA:\n",
+      "\tReal time Spent: 6.05 s\n",
+      "\tCPU time Spent:  6.05 s\n",
+      "\tGeneration: 142\n",
+      "\tBest fitness: 23\n",
+      "\tEvaluations of fitness: 14300\n",
+      "\tdiversity: 1.07\n",
+      "\n",
+      "Optimizing Tresure Hunt using GA:\n",
+      "\tReal time Spent: 8.06 s\n",
+      "\tCPU time Spent:  8.06 s\n",
+      "\tGeneration: 188\n",
+      "\tBest fitness: 23\n",
+      "\tEvaluations of fitness: 18900\n",
+      "\tdiversity: 1.07\n",
+      "\n",
+      "Optimizing Tresure Hunt using GA:\n",
+      "\tReal time Spent: 10.1 s\n",
+      "\tCPU time Spent:  10.1 s\n",
+      "\tGeneration: 235\n",
+      "\tBest fitness: 23\n",
+      "\tEvaluations of fitness: 23600\n",
+      "\tdiversity: 1.07\n",
+      "\n",
+      "Optimizing Tresure Hunt using GA:\n",
+      "\tReal time Spent: 12.13 s\n",
+      "\tCPU time Spent:  12.13 s\n",
+      "\tGeneration: 282\n",
+      "\tBest fitness: 23\n",
+      "\tEvaluations of fitness: 28300\n",
+      "\tdiversity: 1.07\n",
+      "\n",
+      "Optimizing Tresure Hunt using GA:\n",
+      "\tReal time Spent: 14.14 s\n",
+      "\tCPU time Spent:  14.14 s\n",
+      "\tGeneration: 328\n",
+      "\tBest fitness: 24\n",
+      "\tEvaluations of fitness: 32900\n",
+      "\tdiversity: 1.07\n",
+      "\n",
+      "Optimizing Tresure Hunt using GA:\n",
+      "\tReal time Spent: 16.15 s\n",
+      "\tCPU time Spent:  16.15 s\n",
+      "\tGeneration: 373\n",
+      "\tBest fitness: 24\n",
+      "\tEvaluations of fitness: 37400\n",
+      "\tdiversity: 1.07\n",
+      "\n",
+      "Optimizing Tresure Hunt using GA:\n",
+      "\tReal time Spent: 18.17 s\n",
+      "\tCPU time Spent:  18.17 s\n",
+      "\tGeneration: 419\n",
+      "\tBest fitness: 25\n",
+      "\tEvaluations of fitness: 42000\n",
+      "\tdiversity: 1.07\n",
+      "\n",
+      "Optimizing Tresure Hunt using GA:\n",
+      "\tReal time Spent: 20.18 s\n",
+      "\tCPU time Spent:  20.18 s\n",
+      "\tGeneration: 465\n",
+      "\tBest fitness: 25\n",
+      "\tEvaluations of fitness: 46600\n",
+      "\tdiversity: 1.07\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Define the parameters of the optimization process\n",
+    "params = {\"stop_cond\": \"neval\", \"neval\": 50000, \"verbose\": True, \"v_timer\": 2}\n",
+    "pop_size = 100\n",
+    "\n",
+    "# Define a population initialization module\n",
+    "pop_init = UniformVectorInitializer(\n",
+    "    objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=int\n",
+    ")\n",
+    "\n",
+    "# Define the operators to use\n",
+    "mutate_op = OperatorInt(\"MutSample\", {\"method\": \"Uniform\", \"Low\": 0, \"Up\": 3, \"N\": 8})\n",
+    "cross_op = OperatorInt(\"Multipoint\")\n",
+    "\n",
+    "# Define the parent selection method\n",
+    "parent_sel = ParentSelection(\"Tournament\", {\"amount\": 3, \"p\": 1})\n",
+    "\n",
+    "# Define the survivor selection method\n",
+    "surv_sel = SurvivorSelection(\"KeepBest\")\n",
+    "\n",
+    "# Instanciate the algorithm\n",
+    "algorithm = GA(\n",
+    "    pop_init, mutate_op, cross_op, parent_sel, surv_sel, {\"pmut\": 0.1, \"pcross\": 0.8}\n",
+    ")\n",
+    "\n",
+    "# Define a Search method\n",
+    "search = GeneralSearch(objfunc, algorithm, params)\n",
+    "\n",
+    "# Optimize the objective function\n",
+    "best_solution, best_fitness = search.optimize()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Number of treasures obtained: 25\n",
+      " o o _ _ _ _ _ _ _ _ _ _ x _ _ _ x _ _ _\n",
+      " _ o o _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n",
+      " _ _ o _ _ _ _ x _ _ _ _ _ _ _ x _ _ x _\n",
+      " _ o o _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n",
+      " o o o _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n",
+      " o _ _ _ x _ _ o o o _ _ _ _ _ _ _ _ x _\n",
+      " o o _ _ _ _ o o o o o o o # o o # _ _ _\n",
+      " o # _ _ _ _ o o # # o _ _ o _ _ # _ _ _\n",
+      " # o # o _ _ # # o _ _ _ _ _ _ _ # _ _ _\n",
+      " _ o o o _ _ o _ _ _ _ _ _ _ _ _ # o # o\n",
+      " _ _ _ o # o o _ _ _ _ _ _ _ _ o # o # o\n",
+      " _ _ _ o # # o _ _ _ _ _ _ _ _ o o _ o o\n",
+      " _ _ _ _ _ _ _ _ _ _ x x _ _ o o o o # o\n",
+      " _ _ _ _ x _ _ _ _ _ _ _ _ _ # o # # o o\n",
+      " _ _ _ _ _ x _ _ _ _ _ _ _ _ o _ o o _ _\n",
+      " _ _ _ _ _ _ _ _ _ _ _ _ _ _ o o _ _ _ _\n",
+      " _ _ _ x x x _ _ _ _ _ _ _ _ _ o _ _ _ _\n",
+      " _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ o _ _ _ _\n",
+      " _ _ x _ _ _ _ o # _ _ _ _ _ _ o # _ _ _\n",
+      " _ _ _ _ _ _ o o o # o o o o o o o _ _ _\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "print(f\"Number of treasures obtained: {best_fitness}\")\n",
+    "objfunc.display_solution(best_solution)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Initializing optimization of Tresure Hunt using CRO\n",
+      "---------------------------------------------------\n",
+      "\n",
+      "Optimizing Tresure Hunt using CRO:\n",
+      "\tReal time Spent: 0.03 s\n",
+      "\tCPU time Spent:  0.03 s\n",
+      "\tGeneration: 0\n",
+      "\tBest fitness: 10\n",
+      "\tEvaluations of fitness: 80\n",
+      "\n",
+      "Optimizing Tresure Hunt using CRO:\n",
+      "\tReal time Spent: 2.01 s\n",
+      "\tCPU time Spent:  2.01 s\n",
+      "\tGeneration: 51\n",
+      "\tBest fitness: 20\n",
+      "\tEvaluations of fitness: 4824\n",
+      "\n",
+      "Optimizing Tresure Hunt using CRO:\n",
+      "\tReal time Spent: 4.03 s\n",
+      "\tCPU time Spent:  4.03 s\n",
+      "\tGeneration: 100\n",
+      "\tBest fitness: 24\n",
+      "\tEvaluations of fitness: 9412\n",
+      "\n",
+      "Optimizing Tresure Hunt using CRO:\n",
+      "\tReal time Spent: 6.06 s\n",
+      "\tCPU time Spent:  6.06 s\n",
+      "\tGeneration: 151\n",
+      "\tBest fitness: 26\n",
+      "\tEvaluations of fitness: 14170\n",
+      "\n",
+      "Optimizing Tresure Hunt using CRO:\n",
+      "\tReal time Spent: 8.08 s\n",
+      "\tCPU time Spent:  8.09 s\n",
+      "\tGeneration: 201\n",
+      "\tBest fitness: 28\n",
+      "\tEvaluations of fitness: 18866\n",
+      "\n",
+      "Optimizing Tresure Hunt using CRO:\n",
+      "\tReal time Spent: 10.11 s\n",
+      "\tCPU time Spent:  10.11 s\n",
+      "\tGeneration: 250\n",
+      "\tBest fitness: 30\n",
+      "\tEvaluations of fitness: 23430\n",
+      "\n",
+      "Optimizing Tresure Hunt using CRO:\n",
+      "\tReal time Spent: 12.14 s\n",
+      "\tCPU time Spent:  12.14 s\n",
+      "\tGeneration: 300\n",
+      "\tBest fitness: 32\n",
+      "\tEvaluations of fitness: 28101\n",
+      "\n",
+      "Optimizing Tresure Hunt using CRO:\n",
+      "\tReal time Spent: 14.15 s\n",
+      "\tCPU time Spent:  14.15 s\n",
+      "\tGeneration: 350\n",
+      "\tBest fitness: 35\n",
+      "\tEvaluations of fitness: 32770\n",
+      "\n",
+      "Optimizing Tresure Hunt using CRO:\n",
+      "\tReal time Spent: 16.16 s\n",
+      "\tCPU time Spent:  16.16 s\n",
+      "\tGeneration: 399\n",
+      "\tBest fitness: 36\n",
+      "\tEvaluations of fitness: 37363\n",
+      "\n",
+      "Optimizing Tresure Hunt using CRO:\n",
+      "\tReal time Spent: 18.2 s\n",
+      "\tCPU time Spent:  18.2 s\n",
+      "\tGeneration: 449\n",
+      "\tBest fitness: 36\n",
+      "\tEvaluations of fitness: 42043\n",
+      "\n",
+      "Optimizing Tresure Hunt using CRO:\n",
+      "\tReal time Spent: 20.23 s\n",
+      "\tCPU time Spent:  20.23 s\n",
+      "\tGeneration: 499\n",
+      "\tBest fitness: 36\n",
+      "\tEvaluations of fitness: 46732\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Define the parameters of the optimization process\n",
+    "params = {\"stop_cond\": \"neval\", \"neval\": 50000, \"verbose\": True, \"v_timer\": 2}\n",
+    "pop_size = 100\n",
+    "\n",
+    "# Define a population initialization module\n",
+    "pop_init = UniformVectorInitializer(\n",
+    "    objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=pop_size, dtype=int\n",
+    ")\n",
+    "\n",
+    "# Define the operators to use\n",
+    "mutate_op = OperatorInt(\"MutSample\", {\"method\": \"Uniform\", \"Low\": 0, \"Up\": 3, \"N\": 8})\n",
+    "cross_op = OperatorInt(\"Multipoint\")\n",
+    "\n",
+    "# Instanciate the algorithm\n",
+    "cro_params = {\"rho\": 0.8, \"Fb\": 0.8, \"Fd\": 0.3, \"Pd\": 0.2, \"attempts\": 3}\n",
+    "algorithm = CRO(pop_init, mutate_op, cross_op, cro_params)\n",
+    "\n",
+    "# Define a Search method\n",
+    "search = GeneralSearch(objfunc, algorithm, params)\n",
+    "\n",
+    "# Optimize the objective function\n",
+    "best_solution, best_fitness = search.optimize()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Number of treasures obtained: 36\n",
+      " o _ _ _ _ _ _ _ _ _ _ o # o o o # o o o\n",
+      " o _ _ _ _ _ _ _ _ _ _ _ _ _ o _ _ _ o o\n",
+      " o _ _ _ _ _ _ x _ _ _ _ o o o # _ _ # o\n",
+      " o _ _ _ _ _ _ _ _ o o o o _ _ _ _ _ o o\n",
+      " o o _ _ _ _ _ _ _ o o _ _ _ _ _ _ o o _\n",
+      " _ o _ _ x _ _ _ _ _ o _ _ _ _ _ _ o # o\n",
+      " _ o _ _ _ _ _ _ o o o _ _ x _ _ # o o _\n",
+      " o # o _ _ _ _ _ # # _ _ _ _ _ _ # o o _\n",
+      " # o # _ _ _ # # o o _ _ _ _ _ _ # o o o\n",
+      " _ o o _ _ o o _ _ o _ _ _ _ _ _ # o # o\n",
+      " _ _ o o # o _ _ _ _ _ _ _ _ _ _ # o # o\n",
+      " _ _ _ _ # # o _ _ _ _ _ _ _ _ _ _ _ o o\n",
+      " _ _ _ _ _ _ o _ o o # # o o _ _ _ _ # _\n",
+      " _ _ _ _ # o o _ o _ _ _ _ o # o # # o _\n",
+      " _ _ o o o # o _ o _ _ _ _ _ _ o o _ _ _\n",
+      " _ _ o o o _ _ o o _ _ _ _ _ _ _ _ _ _ _\n",
+      " _ _ o # # # o o o _ _ _ _ _ _ _ _ _ _ _\n",
+      " _ o o o _ _ o o o _ _ _ _ _ _ _ _ _ _ _\n",
+      " _ _ # o o _ _ o # o _ _ _ _ _ _ x _ _ _\n",
+      " _ _ _ _ o o o o o # _ _ _ _ _ _ _ _ _ _\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "print(f\"Number of treasures obtained: {best_fitness}\")\n",
+    "objfunc.display_solution(best_solution)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "evolcomp",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.5"
+  },
+  "orig_nbformat": 4
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/tutorials/create_algorithm.ipynb b/tutorials/create_algorithm.ipynb
new file mode 100644
index 0000000..c0f25c1
--- /dev/null
+++ b/tutorials/create_algorithm.ipynb
@@ -0,0 +1,622 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Creating an algorithm"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "In this notebook, we are going to explain how to implement a new optimization algorithm using the metaheuristic-designer framework.\n",
+    "\n",
+    "For this example, we are going to implement a basic evolutionary strategy."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import random\n",
+    "import metaheuristic_designer as mhd\n",
+    "from metaheuristic_designer import GeneralSearch\n",
+    "from metaheuristic_designer.selectionMethods import SurvivorSelection, ParentSelection\n",
+    "from metaheuristic_designer.operators import OperatorReal\n",
+    "from metaheuristic_designer.initializers import UniformVectorInitializer\n",
+    "from metaheuristic_designer.benchmarks import Sphere"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "For simplicity, we will assume we are working with real coded vectors.\n",
+    "\n",
+    "We will also predefine the operators, but in real situations it might be wiser to let the user specify the user they want to use.\n",
+    "\n",
+    "To configure our evolution strategy, we will need to specify the number of parents and offpring in each generation.\n",
+    "The operator will be a gaussian mutation, it will have a standard deviation associated which will be another parameter.\n",
+    "\n",
+    "To create the algorithm, we will make a class that inherits from ```Algorithm``` and will implement its methods.\n",
+    "\n",
+    "In this case we will implement the ```select_parents``` method that chooses the individuals to be chosen for mutation, the ```perturb``` method that will perform a mutation on the selected indivuals, the ```select_individuals``` that will choose the individuals that will remain for the next generation.\n",
+    "\n",
+    "Here, we will make extensive use of already implemented components, like ```ParentSelection```, ```SurvivorSelection```, ```Operator```, but you can provide your own implementation of those, which will be explained in different notebooks."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The ```Algorithm``` class has some attributes that must be used for the \n",
+    "algorithm to work correctly.\n",
+    "\n",
+    "The algorithm will have a ```name```, if unspecified, a default name will be chosen. This is the name of the algorithm showed in logs.\n",
+    "\n",
+    "A population initialization module ```pop_init``` must be present to perform the initialization of the algorithm (an explanation will be present in its respective notebook). It will also specify the size of the initialized population.\n",
+    "\n",
+    "The algorithm has a list of individuals called ```population``` which holds. It is a standard python list with variable size.\n",
+    "\n",
+    "We also have a ```best``` attribute that holds the best individual in each generation.\n",
+    "\n",
+    "You can optionally store the parameters of the algorithm in a dictionary or an instance of ```ParamScheduler``` in the attribute ```params```. It is recomended to use the latter because it allows parameters to change over time. We will not worry about this in this example.\n",
+    "\n",
+    "All of these attributes are generated on the ```__init__``` method of the ```Algorithm``` class."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# ES algorithm class\n",
+    "class BasicES(mhd.Algorithm):\n",
+    "    # Class constructor\n",
+    "    def __init__(self, pop_init, parent_size, offspring_size, sigma):\n",
+    "        self.parent_size = parent_size\n",
+    "        self.offspring_size = offspring_size\n",
+    "\n",
+    "        # Create the mutation operator for use later\n",
+    "        self.mutate_op = OperatorReal(\n",
+    "            \"MutRand\", {\"method\": \"Gauss\", \"F\": sigma, \"N\": 1}\n",
+    "        )\n",
+    "\n",
+    "        # Force pop_init to agree with the population size, this is not mandatory.\n",
+    "        pop_init.pop_size = parent_size\n",
+    "\n",
+    "        # Create the parent selection module, select the best half of the parents\n",
+    "        self.parent_sel_op = ParentSelection(\"Best\", {\"amount\": parent_size // 2})\n",
+    "\n",
+    "        # Create the parent selection module, select the best half of the parents\n",
+    "        self.surv_sel_op = SurvivorSelection(\"keepbest\", {\"amount\": parent_size})\n",
+    "\n",
+    "        super().__init__(pop_init, name=\"Basic ES\")\n",
+    "\n",
+    "    # This method receives the population from the previous generation\n",
+    "    # and some extra information (that we don't need here) and returns\n",
+    "    # a list of selected individuals.\n",
+    "    def select_parents(self, population, **kwargs):\n",
+    "        return self.parent_sel_op(population)\n",
+    "\n",
+    "    # This method receives the parents that we are going to mutate,\n",
+    "    # the objective function class and some extra information that we\n",
+    "    # are not going to use.\n",
+    "    def perturb(self, parent_list, objfunc, **kwargs):\n",
+    "        offspring = []\n",
+    "\n",
+    "        # Iterate until the list of individuals has the\n",
+    "        # size we want.\n",
+    "        while len(offspring) < self.offspring_size:\n",
+    "            # Choose an individual from the parent list.\n",
+    "            indiv = random.choice(parent_list)\n",
+    "\n",
+    "            # Apply the operator.\n",
+    "            new_indiv = self.mutate_op(\n",
+    "                indiv, parent_list, objfunc, self.best, self.pop_init\n",
+    "            )\n",
+    "\n",
+    "            # Repair the solution to keep the vector within the bounds\n",
+    "            # of the problem.\n",
+    "            new_indiv.genotype = objfunc.repair_solution(new_indiv.genotype)\n",
+    "\n",
+    "            # Add the individual to the offspring list.\n",
+    "            offspring.append(new_indiv)\n",
+    "\n",
+    "        # Update the best solution\n",
+    "        current_best = max(offspring, key=lambda x: x.fitness)\n",
+    "        if self.best.fitness < current_best.fitness:\n",
+    "            self.best = current_best\n",
+    "\n",
+    "        return offspring\n",
+    "\n",
+    "    # This method selects the individuals that will be chosen for the next\n",
+    "    # generation, it gets the original population along with the newly\n",
+    "    # generated individuals.\n",
+    "    def select_individuals(self, prev_population, offspring, **kwargs):\n",
+    "        return self.surv_sel_op(prev_population, offspring)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "To begin with the optimization we need one more component, the ```SearchMethod```. This is mostly the same for all algorithms, so it is recomended that you use the already implemented ```GeneralSearch``` class for this purpose unless you REALLY need something different. If this is the case, you have a ```Search``` base class available that you can inherit from.\n",
+    "\n",
+    "We will be using one of the benchmark functions already implemented in this framework that consist on adding the square of the vector components."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Initializing optimization of Sphere function using Basic ES\n",
+      "-----------------------------------------------------------\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 0.0 s\n",
+      "\tCPU time Spent:  0.0 s\n",
+      "\tGeneration: 0\n",
+      "\tBest fitness: 474.26900015044083\n",
+      "\tEvaluations of fitness: 50\n",
+      "\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 2.0 s\n",
+      "\tCPU time Spent:  2.0 s\n",
+      "\tGeneration: 522\n",
+      "\tBest fitness: 445.6753418571568\n",
+      "\tEvaluations of fitness: 130550\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 4.0 s\n",
+      "\tCPU time Spent:  4.0 s\n",
+      "\tGeneration: 1036\n",
+      "\tBest fitness: 418.89163192004537\n",
+      "\tEvaluations of fitness: 259050\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 6.0 s\n",
+      "\tCPU time Spent:  6.0 s\n",
+      "\tGeneration: 1557\n",
+      "\tBest fitness: 392.8736899354275\n",
+      "\tEvaluations of fitness: 389300\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 8.01 s\n",
+      "\tCPU time Spent:  8.01 s\n",
+      "\tGeneration: 2073\n",
+      "\tBest fitness: 367.91964310765144\n",
+      "\tEvaluations of fitness: 518300\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 10.01 s\n",
+      "\tCPU time Spent:  10.01 s\n",
+      "\tGeneration: 2587\n",
+      "\tBest fitness: 344.0850492752297\n",
+      "\tEvaluations of fitness: 646800\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 12.01 s\n",
+      "\tCPU time Spent:  12.01 s\n",
+      "\tGeneration: 3086\n",
+      "\tBest fitness: 321.77287522907795\n",
+      "\tEvaluations of fitness: 771550\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 14.01 s\n",
+      "\tCPU time Spent:  14.01 s\n",
+      "\tGeneration: 3605\n",
+      "\tBest fitness: 299.4967622962263\n",
+      "\tEvaluations of fitness: 901300\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 16.01 s\n",
+      "\tCPU time Spent:  16.01 s\n",
+      "\tGeneration: 4112\n",
+      "\tBest fitness: 278.45527333944773\n",
+      "\tEvaluations of fitness: 1028050\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 18.02 s\n",
+      "\tCPU time Spent:  18.02 s\n",
+      "\tGeneration: 4631\n",
+      "\tBest fitness: 258.0268992956653\n",
+      "\tEvaluations of fitness: 1157800\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 20.02 s\n",
+      "\tCPU time Spent:  20.02 s\n",
+      "\tGeneration: 5147\n",
+      "\tBest fitness: 238.55865505316766\n",
+      "\tEvaluations of fitness: 1286800\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 22.02 s\n",
+      "\tCPU time Spent:  22.02 s\n",
+      "\tGeneration: 5657\n",
+      "\tBest fitness: 220.09785758580568\n",
+      "\tEvaluations of fitness: 1414300\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 24.02 s\n",
+      "\tCPU time Spent:  24.02 s\n",
+      "\tGeneration: 6155\n",
+      "\tBest fitness: 202.67605451430904\n",
+      "\tEvaluations of fitness: 1538800\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 26.02 s\n",
+      "\tCPU time Spent:  26.02 s\n",
+      "\tGeneration: 6650\n",
+      "\tBest fitness: 186.28377520391257\n",
+      "\tEvaluations of fitness: 1662550\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 28.03 s\n",
+      "\tCPU time Spent:  28.03 s\n",
+      "\tGeneration: 7161\n",
+      "\tBest fitness: 170.15562402287517\n",
+      "\tEvaluations of fitness: 1790300\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 30.03 s\n",
+      "\tCPU time Spent:  30.03 s\n",
+      "\tGeneration: 7655\n",
+      "\tBest fitness: 155.38952680822632\n",
+      "\tEvaluations of fitness: 1913800\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 32.03 s\n",
+      "\tCPU time Spent:  32.03 s\n",
+      "\tGeneration: 8171\n",
+      "\tBest fitness: 140.6143770091562\n",
+      "\tEvaluations of fitness: 2042800\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 34.03 s\n",
+      "\tCPU time Spent:  34.03 s\n",
+      "\tGeneration: 8690\n",
+      "\tBest fitness: 126.68228254171893\n",
+      "\tEvaluations of fitness: 2172550\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 36.04 s\n",
+      "\tCPU time Spent:  36.04 s\n",
+      "\tGeneration: 9215\n",
+      "\tBest fitness: 113.36831450203461\n",
+      "\tEvaluations of fitness: 2303800\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 38.04 s\n",
+      "\tCPU time Spent:  38.04 s\n",
+      "\tGeneration: 9731\n",
+      "\tBest fitness: 101.10213882406848\n",
+      "\tEvaluations of fitness: 2432800\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 40.04 s\n",
+      "\tCPU time Spent:  40.04 s\n",
+      "\tGeneration: 10250\n",
+      "\tBest fitness: 89.42460275114755\n",
+      "\tEvaluations of fitness: 2562550\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 42.04 s\n",
+      "\tCPU time Spent:  42.04 s\n",
+      "\tGeneration: 10764\n",
+      "\tBest fitness: 78.50760053778954\n",
+      "\tEvaluations of fitness: 2691050\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 44.04 s\n",
+      "\tCPU time Spent:  44.04 s\n",
+      "\tGeneration: 11280\n",
+      "\tBest fitness: 68.37978185973749\n",
+      "\tEvaluations of fitness: 2820050\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 46.05 s\n",
+      "\tCPU time Spent:  46.05 s\n",
+      "\tGeneration: 11794\n",
+      "\tBest fitness: 58.99786004781749\n",
+      "\tEvaluations of fitness: 2948550\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 48.05 s\n",
+      "\tCPU time Spent:  48.05 s\n",
+      "\tGeneration: 12298\n",
+      "\tBest fitness: 50.376761934240456\n",
+      "\tEvaluations of fitness: 3074550\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 50.05 s\n",
+      "\tCPU time Spent:  50.05 s\n",
+      "\tGeneration: 12816\n",
+      "\tBest fitness: 42.22669206686289\n",
+      "\tEvaluations of fitness: 3204050\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 52.05 s\n",
+      "\tCPU time Spent:  52.05 s\n",
+      "\tGeneration: 13332\n",
+      "\tBest fitness: 34.88279395584162\n",
+      "\tEvaluations of fitness: 3333050\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 54.05 s\n",
+      "\tCPU time Spent:  54.05 s\n",
+      "\tGeneration: 13851\n",
+      "\tBest fitness: 28.24160820445085\n",
+      "\tEvaluations of fitness: 3462800\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 56.06 s\n",
+      "\tCPU time Spent:  56.06 s\n",
+      "\tGeneration: 14367\n",
+      "\tBest fitness: 22.31592553100657\n",
+      "\tEvaluations of fitness: 3591800\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 58.06 s\n",
+      "\tCPU time Spent:  58.06 s\n",
+      "\tGeneration: 14884\n",
+      "\tBest fitness: 17.154596268280304\n",
+      "\tEvaluations of fitness: 3721050\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 60.06 s\n",
+      "\tCPU time Spent:  60.06 s\n",
+      "\tGeneration: 15402\n",
+      "\tBest fitness: 12.602964800830804\n",
+      "\tEvaluations of fitness: 3850550\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 62.06 s\n",
+      "\tCPU time Spent:  62.06 s\n",
+      "\tGeneration: 15919\n",
+      "\tBest fitness: 8.68720171293756\n",
+      "\tEvaluations of fitness: 3979800\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 64.07 s\n",
+      "\tCPU time Spent:  64.07 s\n",
+      "\tGeneration: 16442\n",
+      "\tBest fitness: 5.483073214201059\n",
+      "\tEvaluations of fitness: 4110550\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 66.07 s\n",
+      "\tCPU time Spent:  66.07 s\n",
+      "\tGeneration: 16961\n",
+      "\tBest fitness: 3.0518548944608503\n",
+      "\tEvaluations of fitness: 4240300\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 68.07 s\n",
+      "\tCPU time Spent:  68.07 s\n",
+      "\tGeneration: 17479\n",
+      "\tBest fitness: 1.3265745603534682\n",
+      "\tEvaluations of fitness: 4369800\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 70.07 s\n",
+      "\tCPU time Spent:  70.07 s\n",
+      "\tGeneration: 18002\n",
+      "\tBest fitness: 0.3075037855308809\n",
+      "\tEvaluations of fitness: 4500550\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 72.07 s\n",
+      "\tCPU time Spent:  72.07 s\n",
+      "\tGeneration: 18525\n",
+      "\tBest fitness: 3.711100170023667e-11\n",
+      "\tEvaluations of fitness: 4631300\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 74.07 s\n",
+      "\tCPU time Spent:  74.07 s\n",
+      "\tGeneration: 19045\n",
+      "\tBest fitness: 1.0858869571486062e-13\n",
+      "\tEvaluations of fitness: 4761300\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 76.08 s\n",
+      "\tCPU time Spent:  76.08 s\n",
+      "\tGeneration: 19565\n",
+      "\tBest fitness: 4.019675684606048e-14\n",
+      "\tEvaluations of fitness: 4891300\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 78.08 s\n",
+      "\tCPU time Spent:  78.08 s\n",
+      "\tGeneration: 20084\n",
+      "\tBest fitness: 1.5080629277858004e-14\n",
+      "\tEvaluations of fitness: 5021050\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 80.08 s\n",
+      "\tCPU time Spent:  80.08 s\n",
+      "\tGeneration: 20606\n",
+      "\tBest fitness: 6.631750492233388e-15\n",
+      "\tEvaluations of fitness: 5151550\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 82.08 s\n",
+      "\tCPU time Spent:  82.08 s\n",
+      "\tGeneration: 21127\n",
+      "\tBest fitness: 6.631750492233388e-15\n",
+      "\tEvaluations of fitness: 5281800\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 84.09 s\n",
+      "\tCPU time Spent:  84.09 s\n",
+      "\tGeneration: 21644\n",
+      "\tBest fitness: 5.478004747378101e-15\n",
+      "\tEvaluations of fitness: 5411050\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 86.09 s\n",
+      "\tCPU time Spent:  86.09 s\n",
+      "\tGeneration: 22163\n",
+      "\tBest fitness: 3.125636490386383e-15\n",
+      "\tEvaluations of fitness: 5540800\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 88.09 s\n",
+      "\tCPU time Spent:  88.09 s\n",
+      "\tGeneration: 22683\n",
+      "\tBest fitness: 2.4151940423116095e-15\n",
+      "\tEvaluations of fitness: 5670800\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 90.09 s\n",
+      "\tCPU time Spent:  90.09 s\n",
+      "\tGeneration: 23201\n",
+      "\tBest fitness: 2.4151940423116095e-15\n",
+      "\tEvaluations of fitness: 5800300\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 92.1 s\n",
+      "\tCPU time Spent:  92.1 s\n",
+      "\tGeneration: 23725\n",
+      "\tBest fitness: 2.4151940423116095e-15\n",
+      "\tEvaluations of fitness: 5931300\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 94.1 s\n",
+      "\tCPU time Spent:  94.1 s\n",
+      "\tGeneration: 24246\n",
+      "\tBest fitness: 2.4151940423116095e-15\n",
+      "\tEvaluations of fitness: 6061550\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 96.1 s\n",
+      "\tCPU time Spent:  96.1 s\n",
+      "\tGeneration: 24768\n",
+      "\tBest fitness: 2.1893065780301543e-15\n",
+      "\tEvaluations of fitness: 6192050\n",
+      "\n",
+      "Optimizing Sphere function using Basic ES:\n",
+      "\tReal time Spent: 98.1 s\n",
+      "\tCPU time Spent:  98.1 s\n",
+      "\tGeneration: 25285\n",
+      "\tBest fitness: 7.612488702166364e-16\n",
+      "\tEvaluations of fitness: 6321300\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Define the parameters of the optimization process\n",
+    "params = {\"stop_cond\": \"time_limit\", \"time_limit\": 100.0, \"verbose\": True, \"v_timer\": 2}\n",
+    "\n",
+    "# Define the parameters of the algorithm\n",
+    "n_parents = 50\n",
+    "n_offspring = 250\n",
+    "\n",
+    "# Define the objective function\n",
+    "objfunc = Sphere(3)\n",
+    "\n",
+    "# Define a population initialization module\n",
+    "pop_init = UniformVectorInitializer(\n",
+    "    objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=n_parents\n",
+    ")\n",
+    "\n",
+    "# Instanciate the newly created algorithm\n",
+    "algorithm = BasicES(pop_init, n_parents, n_offspring, 1e-3)\n",
+    "\n",
+    "# Define a Search method\n",
+    "search = GeneralSearch(objfunc, algorithm, params)\n",
+    "\n",
+    "# Optimize the objective function\n",
+    "best_solution, best_fitness = search.optimize()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Best solution: [-1.79028782e-08  1.76993574e-08  1.12901980e-08]\n",
+      "Solution fitness: 7.612488702166364e-16\n",
+      "\n",
+      "Number of generations: 25775\n",
+      "Real time spent: 100.00265s\n",
+      "CPU time spent: 99.92333s\n",
+      "Number of fitness evaluations: 6443800\n",
+      "Best fitness: 7.612488702166364e-16\n"
+     ]
+    },
+    {
+     "data": {
+      "image/png": "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0aVLF1544QVmzZpFRkaGXadU4BwOGD/eWp49G44ft7ceERERV2Z72Bk6dChdu3YlJiYmx/qNGzdy4cKFHOtr165NxYoVSUhIACAhIYH69esTHh7u3KdTp06kpqay3c1nzezSxRp7Jy1Nd3dERESuxdawM3/+fH788UdiY2Mv25aYmIiPjw+hoaE51oeHh5OYmOjc549BJ3t79rarSU9PJzU1NcejqHE4YMIEa3nWLN3dERERuRrbws7hw4d5/PHHef/99/Hz8yvU946NjSUkJMT5qFChQqG+f37p2hWaNIFz5+CVV+yuRkRExDXZFnY2btxIcnIyTZo0wcvLCy8vL1avXs3MmTPx8vIiPDycjIwMTp8+neN1SUlJREREABAREXFZ76zs59n7XMmYMWNISUlxPg4X0QFrHI5L4+68/jqcOGFvPSIiIq7ItrDToUMHtm7dyubNm52PZs2a0adPH+eyt7c3y5cvd75m165dHDp0iOjoaACio6PZunUrycnJzn3i4+MJDg6mbt26V31vX19fgoODczyKqr/+FRo1grNnNe6OiIjIlXjZ9cZBQUHUq1cvx7qAgABKlSrlXD9w4EBGjBhBWFgYwcHBPProo0RHR9OyZUsAOnbsSN26denbty9TpkwhMTGRsWPHMnToUHx9fQv9nOzg4QHPPw/du8Nrr8ETT0DZsnZXJSIi4jps7411LdOnT6dbt2707NmTtm3bEhERwaeffurc7unpyeLFi/H09CQ6Opr777+ffv36MXHiRBurLnx//Ss0a2a13Zkyxe5qREREXIvDGGPsLsJuqamphISEkJKSkm9faaWlpVG9enUA9u7di7+/f74c92qWLoXbbwc/P9i/HyIjC/TtREREbJfbv98ufWdHcq9zZ2jZEs6fh5desrsaERER16Gw4yYcDsj+9m7OHPjtN3vrERERcRUKO24kJgbatIH0dJg0ye5qREREXIPCjhv5492duXPh0CF76xEREXEFCjtupn17uOUWuHABXnzR7mpERETsp7DjhrLv7rzzDhw4YG8tIiIidlPYcUOtW0PHjnDxIrzwgt3ViIiI2Ethx01l3935979hzx57axEREbGTwo6batHCmhU9M1N3d0REpHhT2HFjzz9v/Xz/fdi5095aRERE7KKw48aaNrUmCM3KgnHj7K5GRETEHgo7bm7iRGv8nYUL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9dVXcTgcOBwODh48eMWvsT755BP+8pe/4OvrS+XKlXnllVdy1Fe5cmUmTZrEgw8+SFBQEBUrVuTNN990bs/IyGDYsGFERkbi5+dHpUqViI2NzYf/AiJyTYUyA5eIuLWHHnrIVKpUyXzzzTdm69at5s477zRBQUHm8ccfd26/+eabzZo1a8zevXvN1KlTja+vr9m9e7cxxph58+YZb29vExMTY9avX282btxo6tSpY3r37u18j/fee89ERkaaTz75xOzfv9988sknJiwszMTFxRljjDlw4IABTOXKlZ37HDlyxPz6669m6tSpZtOmTWbfvn1m5syZxtPT06xbt84YY802Hh0dbQYNGuScEfrixYvOyT9PnTpljDFmw4YNxsPDw0ycONHs2rXLzJs3z5QoUSLHZIeVKlUyYWFhZtasWWbPnj0mNjbWeHh4mJ07dxpjjJk6daqpUKGCWbNmjTl48KD59ttvzQcffFDA/3VERGFHRG5Iamqq8fb2NgsXLnSuO336tPH39zePP/64+eWXX4ynp6f57bffcryuQ4cOZsyYMcYYK+wAOWa9nzVrlgkPD3c+r1at2mXB4IUXXjDR0dHGmEthZ8aMGdetuWvXrmbkyJHO5+3atXMGs2z/G3Z69+5tbrvtthz7jB492tStW9f5vFKlSub+++93Ps/KyjJly5Y1s2fPNsYY8+ijj5pbb73VZGVlXbdGEck/XrbeVhKRIm///v1cuHCB5s2bO9eFhIRQq1YtALZu3UpmZiY1a9bM8br09HRKlSrlfO7v70+1atWczyMjI0lOTgbg3Llz7Nu3j4EDBzJo0CDnPhcvXiQkJCTHcZs1a5bjeWZmJpMmTWLBggX89ttvZGRkkJ6ejr+/f57O8+eff6Z79+451rVq1YoZM2aQmZmJp6cnAA0aNHBudzgcREREOM9jwIAB3HbbbdSqVYvOnTvTrVs3OnbsmKc6RCTvFHZEpECdPXsWT09PNm7c6AwE2QIDA53L3t7eObY5HA7M/3cWPXv2LABz586lRYsWOfb732MGBATkeD516lReffVVZsyYQf369QkICGD48OFkZGTc2IldxZXOIysrC4AmTZpw4MABli5dyjfffMM999xDTEwMH3/8cYHUIiIWhR0RuSFVq1bF29ub9evXU7FiRQBSUlLYvXs3bdu2pXHjxmRmZpKcnEybNm3+1HuEh4cTFRXF/v376dOnT55eu3btWrp37879998PQFZWFrt376Zu3brOfXx8fMjMzLzmcerUqcPatWsvO3bNmjUvC1zXEhwcTK9evejVqxd33303nTt35uTJk4SFheXhrEQkLxR2ROSGBAUF0b9/f0aPHk1YWBhly5Zl/PjxeHh44HA4qFmzJn369KFfv3688sorNG7cmGPHjrF8+XIaNGhA165dc/U+zz//PI899hghISF07tyZ9PR0NmzYwKlTpxgxYsRVX1ejRg0+/vhjvv/+e0qWLMm0adNISkrKEXYqV67MunXrOHjwIIGBgVcMHiNHjuSmm27ihRdeoFevXiQkJPD666/n6BF2PdOmTSMyMpLGjRvj4eHBwoULiYiIIDQ0NNfHEJG8U9dzEblh06ZNIzo6mm7duhETE0OrVq2cXcQB5s2bR79+/Rg5ciS1atWiR48eOe4E5cZDDz3EW2+9xbx586hfvz7t2rUjLi6OKlWqXPN1Y8eOpUmTJnTq1In27dsTERFBjx49cuwzatQoPD09qVu3LmXKlOHQoUOXHadJkyYsWLCA+fPnU69ePcaNG8fEiRMZMGBArs8hKCiIKVOm0KxZM2666SYOHjzIkiVL8PDQR7FIQdIIyiKS786dO0e5cuV45ZVXGDhwoN3liEgxp6+xROSGbdq0iZ07d9K8eXNSUlKYOHEiwGW9l0RE7KCwIyL54uWXX2bXrl34+PjQtGlTvv32W0qXLm13WSIi+hpLRERE3JtaxYmIiIhbU9gRERERt6awIyIiIm5NYUdERETcmsKOiIiIuDWFHREREXFrCjsiIiLi1hR2RERExK0p7IiIiIhb+z/wR5KwdPGYVAAAAABJRU5ErkJggg==",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "print(f\"Best solution: {best_solution}\")\n",
+    "print(f\"Solution fitness: {best_fitness}\\n\")\n",
+    "search.display_report()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.4"
+  },
+  "orig_nbformat": 4,
+  "vscode": {
+   "interpreter": {
+    "hash": "e7370f93d1d0cde622a1f8e1c04877d8463912d04d973331ad4851f04de6915a"
+   }
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/tutorials/create_encoding.ipynb b/tutorials/create_encoding.ipynb
new file mode 100644
index 0000000..16a98c0
--- /dev/null
+++ b/tutorials/create_encoding.ipynb
@@ -0,0 +1,403 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Defining custom operators"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "In this notebook we are going to show how you can define custom operators that can be used with any metaheuristic algorithm.\n",
+    "\n",
+    "We will give a number of different ways of doing it, so you can choose which one is more appropiate for you."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import metaheuristic_designer as mhd\n",
+    "from metaheuristic_designer import GeneralSearch, Operator\n",
+    "from metaheuristic_designer.operators import OperatorLambda\n",
+    "from metaheuristic_designer.algorithms import HillClimb\n",
+    "from metaheuristic_designer.initializers import UniformVectorInitializer\n",
+    "from metaheuristic_designer.benchmarks import Sphere\n",
+    "from copy import copy\n",
+    "import numpy as np\n",
+    "import random"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## With an already defined function"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Let's say you have an already programmed function that will serve as a mutation or a cross operator for an algorithm. To use it in this framework, it will need to accept 4 parameters:\n",
+    "- An individual that we are attempting to modify.\n",
+    "- A population of individuals that are the parents selected for mutation.\n",
+    "- The objective function we are optimizing.\n",
+    "- A dictionary with other additional parameters.\n",
+    "\n",
+    "Here's an example.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def mutate_custom(indiv, population, objfunc, **params):\n",
+    "    vector = indiv.genotype\n",
+    "    return vector + np.random.normal(0, params[\"F\"], vector.shape)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "To convert it into an operator, we will pass it to the ```OperatorLambda``` class to along with its name and other parameters."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "operator = OperatorLambda(\n",
+    "    mutate_custom, name=\"GaussianNoise_lambda\", params={\"F\": 1e-3}\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Now we can directly use it with any algorithm we want, in this case, we will use it in a Hill Climb algorithm."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Initializing optimization of Sphere function using HillClimb\n",
+      "------------------------------------------------------------\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 0.0 s\n",
+      "\tCPU time Spent:  0.0 s\n",
+      "\tGeneration: 0\n",
+      "\tBest fitness: 5415.286292004781\n",
+      "\tEvaluations of fitness: 1\n",
+      "\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 2.0 s\n",
+      "\tCPU time Spent:  2.0 s\n",
+      "\tGeneration: 133069\n",
+      "\tBest fitness: 412.2064937741036\n",
+      "\tEvaluations of fitness: 133070\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 4.0 s\n",
+      "\tCPU time Spent:  4.0 s\n",
+      "\tGeneration: 259371\n",
+      "\tBest fitness: 1.2397215290188667e-09\n",
+      "\tEvaluations of fitness: 259372\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 6.0 s\n",
+      "\tCPU time Spent:  6.0 s\n",
+      "\tGeneration: 392311\n",
+      "\tBest fitness: 1.1174567301937887e-09\n",
+      "\tEvaluations of fitness: 392312\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 8.0 s\n",
+      "\tCPU time Spent:  8.0 s\n",
+      "\tGeneration: 525680\n",
+      "\tBest fitness: 1.1174567301937887e-09\n",
+      "\tEvaluations of fitness: 525681\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Define the parameters of the optimization process\n",
+    "params = {\"stop_cond\": \"time_limit\", \"time_limit\": 10.0, \"verbose\": True, \"v_timer\": 2}\n",
+    "\n",
+    "# Define the objective function\n",
+    "objfunc = Sphere(3)\n",
+    "\n",
+    "# Define a population initialization module\n",
+    "pop_init = UniformVectorInitializer(\n",
+    "    objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=1\n",
+    ")\n",
+    "\n",
+    "# Instanciate the newly created algorithm\n",
+    "algorithm = HillClimb(pop_init, operator)\n",
+    "\n",
+    "# Define a Search method\n",
+    "search = GeneralSearch(objfunc, algorithm, params)\n",
+    "\n",
+    "# Optimize the objective function\n",
+    "best_solution, best_fitness = search.optimize()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Best solution: [ 4.43236770e-06 -2.22897807e-06  2.18093450e-05]\n",
+      "Solution fitness: 5.002617550341376e-10\n",
+      "\n",
+      "Number of generations: 658842\n",
+      "Real time spent: 10.00002s\n",
+      "CPU time spent: 9.99193s\n",
+      "Number of fitness evaluations: 658843\n",
+      "Best fitness: 5.002617550341376e-10\n"
+     ]
+    },
+    {
+     "data": {
+      "image/png": "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ZLsYiIio1j3yyCMAgK+++krhSqoynX250bFjx+7qTts3u/mu25a65557AAD+/v6Ii4ur8jKNWrux/dSpU/Hzzz/j0KFDqKiowPvvv2/WpkuXLnj77bexb98+fP311zh8+DCWLVt2V3US2TMGIiKqdXq9Hn379sWSJUuwZs2aKusrKirw4osv1n1hANasWYOzZ8/K7/fs2YP09HT069fPat/h6emJoqKiO/58fHw8dDod3nnnnWr7/ZiG6JeVleHq1atm6+655x54e3ujvLwcgHQZ7uYzZe3btwcAuQ2RGrEPERHVif/+97/o06cPBg4ciEceeQS9evWCp6cnjh8/jmXLluH8+fN39VyxOxUREYFu3brh2WefRXl5OebPn48GDRpg+vTpVvuOjh07YvHixfjnP/+JiIgI+Pv7/2V/oOrodDosXrwYw4cPR3R0NJ588kk0atQIeXl5WLduHbp27YqPP/4Yx44dQ69evfDEE08gKioKLi4uWL16NQoKCvDkk08CAL744gssWrQIAwYMwD333IOSkhJ8/vnn0Ol0eOihh6y2z0T2hoGIiOpEo0aNsGvXLixatAjLly/HK6+8goqKCoSGhuLRRx/FCy+8oEhdI0aMgJOTE+bPn4/CwkJ07twZH3/8MYKCgqz2HTNnzsTp06cxZ84clJSUoEePHhYFIgAYOnQogoOD8e6772Lu3LkoLy9H48aN0b17d4wePRqA1JF8yJAh2Lx5M7788ku4uLggMjISK1askG+I2aNHD+zZswfLli1DQUEBfHx80LlzZ3z99dcIDw+32j4T2RuNsNVehkRERER1hH2IiIiISPUYiIiIiEj1GIiIiIhI9RiIiIiISPUYiIiIiEj1GIiIiIhI9XgfohowGo04d+4cvL297/oW/ERERFQ3hBAoKSlBcHAwnJxufQ6IgagGzp07V+XJ2URERGQfzpw5gyZNmtyyDQNRDXh7ewOQDqhOp7PadsvKyuRnCGVmZsLDw8Nq2yYiIlI7g8GAkJAQ+e/4rTAQ1YDpMplOp7NqIHJxcZFP4el0OgYiIiKiWlCT7i7sVE1ERESqx0BEREREqsdARERERKrHQERERESqx0BEREREqsdARERERKrHQERERESqx0BEREREqsdARERERKrHQERERESqx0BEREREqsdARERERKrHh7sqzGj0RWWlv9JlEBER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zdrdvE1PT0+z93PnzsWCBQswf/58tGnTBp6enpg0aRIqKirubsf+QnX7YTQaAQDR0dHIycnBhg0b8Msvv+CJJ55AXFwcVq1aVSu1EJGEgYiIal2zZs3g6uqKvXv3omnTpgCA4uJiHDt2DPfffz86dOiAyspKFBYWonv37nf0HQEBAQgODsapU6cwbNgwiz67c+dOPPbYY3jqqacAAEajEceOHUNUVJTcxs3NDZWVlbfcTsuWLbFz584q227evHmVUHYrOp0OgwcPxuDBg/H444+jb9++uHjxIurXr2/BXhGRJRiIiKjWeXt7Y+TIkZg2bRrq168Pf39/zJo1C05OTtBoNGjevDmGDRuGESNG4P3330eHDh3w+++/Y/PmzWjbti0SEhJq9D1vvPEGnn/+efj4+KBv374oLy/Hvn37cOnSJUyZMuUvP3fvvfdi1apV2LVrF/z8/PDBBx+goKDALBCFhYUhPT0dubm58PLyqjacTJ06FZ06dcJbb72FwYMHIy0tDR9//LHZSLfb+eCDDxAUFIQOHTrAyckJK1euRGBgIHx9fWu8DSKyHIfdE1Gd+OCDD6DX6/Hwww8jLi4OXbt2lYfHA8DSpUsxYsQITJ06FS1atED//v3NzijVxLhx47BkyRIsXboUbdq0QY8ePZCUlITw8PBbfu7VV19FdHQ04uPj0bNnTwQGBqJ///5mbV588UU4OzsjKioKjRo1Ql5eXpXtREdHY8WKFVi2bBlat26NmTNn4s0338SoUaNqvA/e3t6YM2cOYmJi0KlTJ+Tm5mL9+vVwcuKva6LaxDtVE5EiSktL0bhxY7z//vsYO3as0uUQkcrxkhkR1Yn9+/fjt99+Q+fOnVFcXIw333wTAKqMyiIiUgIDERHVmX/961/Izs6Gm5sbOnbsiO3bt6Nhw4ZKl0VExEtmREREROylR0RERKrHQERERESqx0BEREREqsdARERERKrHQERERESqx0BEREREqsdARERERKrHQERERESqx0BEREREqvd/mUOer2yus5oAAAAASUVORK5CYII=",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "print(f\"Best solution: {best_solution}\")\n",
+    "print(f\"Solution fitness: {best_fitness}\\n\")\n",
+    "search.display_report()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## With an operator class"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "If we want a little bit more control of how our operator will be applied, we can make a class that inherits from the base ```Operator``` class.\n",
+    "\n",
+    "We will need to implement the ```evolve``` method, which will receive a number of parameters shown below."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'GaussianNoise_Class'"
+      ]
+     },
+     "execution_count": 14,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "class OperatorCustom(Operator):\n",
+    "    def __init__(self, sigma, params=None, name=None):\n",
+    "        self.sigma = sigma\n",
+    "        super().__init__(params, name)\n",
+    "\n",
+    "    def evolve(self, indiv, population, objfunc, global_best, initializer):\n",
+    "        # It is good practice to always make a copy of the individual to ensure that we don't\n",
+    "        # modify it directly\n",
+    "        new_indiv = copy(indiv)\n",
+    "\n",
+    "        # We apply a mutation to the individual\n",
+    "        vector = new_indiv.genotype\n",
+    "        new_indiv.genotype = vector + np.random.normal(0, self.sigma, vector.shape)\n",
+    "\n",
+    "        return new_indiv\n",
+    "\n",
+    "\n",
+    "op_custom = OperatorCustom(1e-3, name=\"GaussianNoise_Class\")\n",
+    "op_custom.name"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We can once again use this operator as normal."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Initializing optimization of Sphere function using HillClimb\n",
+      "------------------------------------------------------------\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 0.0 s\n",
+      "\tCPU time Spent:  0.0 s\n",
+      "\tGeneration: 0\n",
+      "\tBest fitness: 4837.711925988624\n",
+      "\tEvaluations of fitness: 1\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 2.0 s\n",
+      "\tCPU time Spent:  2.0 s\n",
+      "\tGeneration: 126770\n",
+      "\tBest fitness: 360.528564563992\n",
+      "\tEvaluations of fitness: 126771\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 4.0 s\n",
+      "\tCPU time Spent:  4.0 s\n",
+      "\tGeneration: 257540\n",
+      "\tBest fitness: 9.136899843218854e-10\n",
+      "\tEvaluations of fitness: 257541\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 6.0 s\n",
+      "\tCPU time Spent:  6.0 s\n",
+      "\tGeneration: 388027\n",
+      "\tBest fitness: 9.136899843218854e-10\n",
+      "\tEvaluations of fitness: 388028\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 8.0 s\n",
+      "\tCPU time Spent:  8.0 s\n",
+      "\tGeneration: 521886\n",
+      "\tBest fitness: 1.0248925258923705e-10\n",
+      "\tEvaluations of fitness: 521887\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Define the parameters of the optimization process\n",
+    "params = {\"stop_cond\": \"time_limit\", \"time_limit\": 10.0, \"verbose\": True, \"v_timer\": 2}\n",
+    "\n",
+    "# Define the objective function\n",
+    "objfunc = Sphere(3)\n",
+    "\n",
+    "# Define a population initialization module\n",
+    "pop_init = UniformVectorInitializer(\n",
+    "    objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=1\n",
+    ")\n",
+    "\n",
+    "# Instanciate the newly created algorithm\n",
+    "algorithm = HillClimb(pop_init, operator)\n",
+    "\n",
+    "# Define a Search method\n",
+    "search = GeneralSearch(objfunc, algorithm, params)\n",
+    "\n",
+    "# Optimize the objective function\n",
+    "best_solution, best_fitness = search.optimize()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Best solution: [ 8.06079626e-06  3.11855865e-06 -5.27137631e-06]\n",
+      "Solution fitness: 1.0248925258923705e-10\n",
+      "\n",
+      "Number of generations: 653847\n",
+      "Real time spent: 10.00002s\n",
+      "CPU time spent: 9.99331s\n",
+      "Number of fitness evaluations: 653848\n",
+      "Best fitness: 1.0248925258923705e-10\n"
+     ]
+    },
+    {
+     "data": {
+      "image/png": "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SmS9GIiIyOIeeeQRAMBnn32mcCXVGc++XO/o0aN3NdL2jW4cdbuuWrRoAQDw8/NDZGRktZfxrrXr20+dOhU//PADDh06hPLycrz99tsmbXr27Il///vf2LdvH1auXImsrCysWrXqruoksmUMRERkcXq9HgMHDsTy5cvxzTffVFtfXl6Ol156qf4LA/DNN9/g9OnT8vs9e/YgLS0NgwYNMtt3eHh4oKio6I4/HxUVBa1WizfffLPGfj/GW/RLS0tx9epVk3UtWrSAl5cXysrKAEiX4W48U9a5c2cAkNsQOSL2ISKievHJJ59gwIABGDp0KB555BH0798fHh4eOHbsGFatWoWzZ8/e1XPF7lRYWBh69+6N5557DmVlZVi4cCEaNWqE6dOnm+07unXrhqVLl+Jf//oXwsLC4Ofnd9P+QDXRarVYunQpRo0aha5du+LJJ59EkyZNkJeXhw0bNqBXr1547733cPToUfTv3x9PPPEEwsPD4eLignXr1qGgoABPPvkkAODjjz/GkiVLMGTIELRo0QIlJSX48MMPodVq8dBDD5ltn4lsDQMREdWLJk2aYNeuXViyZAm+/PJLvPzyyygvL0dISAgeffRRvPjii4rUNXr0aDg5OWHhwoUoLCxEjx498N577yEgIMBs3zFnzhycPHkS8+bNQ0lJCfr27VunQAQAI0eORGBgIN566y3Mnz8fZWVlaNq0Kfr06YOxY8cCkDqSjxgxAsnJyfj000/h4uKCNm3aYPXq1fKAmH379sWePXuwatUqFBQUwNvbGz169MDKlSsRGhpqtn0msjUqYa29DImIiIjqCfsQERERkcNjICIiIiKHx0BEREREDo+BiIiIiBweAxERERE5PAYiIiIicngch6gWqqqqcObMGXh5ed31EPxERERUP4QQKCkpQWBgIJycbn0OiIGoFs6cOVPtydlERERkG06dOoVmzZrdso2igejVV1/Fa6+9ZrKsdevWOHLkCADg6tWrmDp1KlatWoWysjJERUVhyZIl8Pf3l9vn5eXhueeew7Zt2+Dp6YnY2FgkJCTAxeWvXUtJSUF8fDyysrIQFBSE2bNnY8yYMbWu08vLC4B0QLVa7V3ssanS0lL5GUIZGRlwd3c327aJiIgcncFgQFBQkPx3/FYUP0PUrl07/Pjjj/L764PMlClTsGHDBqxZswbe3t6YOHEi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a1fk5ORg06ZN+PHHH/HEE08gMjISa9eutUgtRCRhICIii7vnnnvg6uqKvXv3Ijg4GABQXFyMo0eP4r777kOXLl1QWVmJwsJC9OnT546+w9/fH4GBgThx4gRiYmLq9NmdO3fisccew1NPPQUAqKqqwtGjRxEeHi63UavVqKysvOV22rZti507d1bbdqtWraqFslvRarUYPnw4hg8fjscffxwDBw7EhQsX0LBhwzrsFRHVBQMREVmcl5cXYmNjMW3aNDRs2BB+fn6YO3cunJycoFKp0KpVK8TExGD06NF4++230aVLF/zxxx9ITk5Gx44dER0dXavvee211/DCCy/A29sbAwcORFlZGfbt24eLFy8iPj7+pp9r2bIl1q5di127dsHX1xfvvPMOCgoKTAJR8+bNkZaWhtzcXHh6etYYTqZOnYp7770Xb7zxBoYPH47U1FS89957Jne63c4777yDgIAAdOnSBU5OTlizZg10Oh18fHxqvQ0iqjvedk9E9eKdd96BXq/Hww8/jMjISPTq1Uu+PR4AVqxYgdGjR2Pq1Klo3bo1Bg8ebHJGqTbGjx+P5cuXY8WKFejQoQP69u2LxMREhIaG3vJzs2fPRteuXREVFYV+/fpBp9Nh8ODBJm1eeuklODs7Izw8HE2aNEFeXl617XTt2hWrV6/GqlWr0L59e8yZMwevv/46xowZU+t98PLywrx589C9e3fce++9yM3NxcaNG+HkxF/XRJbEkaqJSBGXL19G06ZN8fbbb2PcuHFKl0NEDo6XzIioXhw4cABHjhxBjx49UFxcjNdffx0Aqt2VRUSkBAYiIqo3//3vf5GdnQ21Wo1u3brhp59+QuPGjZUui4iIl8yIiIiI2EuPiIiIHB4DERERETk8BiIiIiJyeAxERERE5PAYiIiIiMjhMRARERGRw2MgIiIiIofHQEREREQOj4GIiIiIHN7/AxQKm692jwkmAAAAAElFTkSuQmCC",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "print(f\"Best solution: {best_solution}\")\n",
+    "print(f\"Solution fitness: {best_fitness}\\n\")\n",
+    "search.display_report()"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.4"
+  },
+  "orig_nbformat": 4,
+  "vscode": {
+   "interpreter": {
+    "hash": "e7370f93d1d0cde622a1f8e1c04877d8463912d04d973331ad4851f04de6915a"
+   }
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/tutorials/create_operators.ipynb b/tutorials/create_operators.ipynb
new file mode 100644
index 0000000..a5ad127
--- /dev/null
+++ b/tutorials/create_operators.ipynb
@@ -0,0 +1,403 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Defining custom operators"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "In this notebook we are going to show how you can define custom operators that can be used with any metaheuristic algorithm.\n",
+    "\n",
+    "We will give a number of different ways of doing it, so you can choose which one is more appropiate for you."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import metaheuristic_designer as mhd\n",
+    "from metaheuristic_designer import GeneralSearch, Operator\n",
+    "from metaheuristic_designer.operators import OperatorLambda\n",
+    "from metaheuristic_designer.algorithms import HillClimb\n",
+    "from metaheuristic_designer.initializers import UniformVectorInitializer\n",
+    "from metaheuristic_designer.benchmarks import Sphere\n",
+    "from copy import copy\n",
+    "import numpy as np\n",
+    "import random"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## With an already defined function"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Let's say you have an already programmed function that will serve as a mutation or a cross operator for an algorithm. To use it in this framework, it will need to accept 4 parameters:\n",
+    "- An individual that we are attempting to modify.\n",
+    "- A population of individuals that are the parents selected for mutation.\n",
+    "- The objective function we are optimizing.\n",
+    "- A dictionary with other additional parameters.\n",
+    "\n",
+    "Here's an example.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def mutate_custom(indiv, population, objfunc, **params):\n",
+    "    vector = indiv.genotype\n",
+    "    return vector + np.random.normal(0, params[\"F\"], vector.shape)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "To convert it into an operator, we will pass it to the ```OperatorLambda``` class to along with its name and other parameters."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "operator = OperatorLambda(\n",
+    "    mutate_custom, name=\"GaussianNoise_lambda\", params={\"F\": 1e-3}\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Now we can directly use it with any algorithm we want, in this case, we will use it in a Hill Climb algorithm."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Initializing optimization of Sphere function using HillClimb\n",
+      "------------------------------------------------------------\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 0.0 s\n",
+      "\tCPU time Spent:  0.0 s\n",
+      "\tGeneration: 0\n",
+      "\tBest fitness: 7247.989947214412\n",
+      "\tEvaluations of fitness: 1\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 2.0 s\n",
+      "\tCPU time Spent:  2.0 s\n",
+      "\tGeneration: 126804\n",
+      "\tBest fitness: 1187.1022955410133\n",
+      "\tEvaluations of fitness: 126805\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 4.0 s\n",
+      "\tCPU time Spent:  4.0 s\n",
+      "\tGeneration: 257474\n",
+      "\tBest fitness: 3.4401817585912525e-10\n",
+      "\tEvaluations of fitness: 257475\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 6.0 s\n",
+      "\tCPU time Spent:  6.0 s\n",
+      "\tGeneration: 390801\n",
+      "\tBest fitness: 3.4401817585912525e-10\n",
+      "\tEvaluations of fitness: 390802\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 8.0 s\n",
+      "\tCPU time Spent:  8.0 s\n",
+      "\tGeneration: 521464\n",
+      "\tBest fitness: 3.4401817585912525e-10\n",
+      "\tEvaluations of fitness: 521465\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Define the parameters of the optimization process\n",
+    "params = {\"stop_cond\": \"time_limit\", \"time_limit\": 10.0, \"verbose\": True, \"v_timer\": 2}\n",
+    "\n",
+    "# Define the objective function\n",
+    "objfunc = Sphere(3)\n",
+    "\n",
+    "# Define a population initialization module\n",
+    "pop_init = UniformVectorInitializer(\n",
+    "    objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=1\n",
+    ")\n",
+    "\n",
+    "# Instanciate the newly created algorithm\n",
+    "algorithm = HillClimb(pop_init, operator)\n",
+    "\n",
+    "# Define a Search method\n",
+    "search = GeneralSearch(objfunc, algorithm, params)\n",
+    "\n",
+    "# Optimize the objective function\n",
+    "best_solution, best_fitness = search.optimize()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Best solution: [ 6.14504540e-06  2.34705093e-06 -1.73420859e-05]\n",
+      "Solution fitness: 3.4401817585912525e-10\n",
+      "\n",
+      "Number of generations: 653005\n",
+      "Real time spent: 10.00002s\n",
+      "CPU time spent: 9.9849s\n",
+      "Number of fitness evaluations: 653006\n",
+      "Best fitness: 3.4401817585912525e-10\n"
+     ]
+    },
+    {
+     "data": {
+      "image/png": "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",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "print(f\"Best solution: {best_solution}\")\n",
+    "print(f\"Solution fitness: {best_fitness}\\n\")\n",
+    "search.display_report()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## With an operator class"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "If we want a little bit more control of how our operator will be applied, we can make a class that inherits from the base ```Operator``` class.\n",
+    "\n",
+    "We will need to implement the ```evolve``` method, which will receive a number of parameters shown below."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'GaussianNoise_Class'"
+      ]
+     },
+     "execution_count": 6,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "class OperatorCustom(Operator):\n",
+    "    def __init__(self, sigma, params=None, name=None):\n",
+    "        self.sigma = sigma\n",
+    "        super().__init__(params, name)\n",
+    "\n",
+    "    def evolve(self, indiv, population, objfunc, global_best, initializer):\n",
+    "        # It is good practice to always make a copy of the individual to ensure that we don't\n",
+    "        # modify it directly\n",
+    "        new_indiv = copy(indiv)\n",
+    "\n",
+    "        # We apply a mutation to the individual\n",
+    "        vector = new_indiv.genotype\n",
+    "        new_indiv.genotype = vector + np.random.normal(0, self.sigma, vector.shape)\n",
+    "\n",
+    "        return new_indiv\n",
+    "\n",
+    "\n",
+    "op_custom = OperatorCustom(1e-3, name=\"GaussianNoise_Class\")\n",
+    "op_custom.name"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We can once again use this operator as normal."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Initializing optimization of Sphere function using HillClimb\n",
+      "------------------------------------------------------------\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 0.0 s\n",
+      "\tCPU time Spent:  0.0 s\n",
+      "\tGeneration: 0\n",
+      "\tBest fitness: 11580.664753311505\n",
+      "\tEvaluations of fitness: 1\n",
+      "\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 2.0 s\n",
+      "\tCPU time Spent:  2.0 s\n",
+      "\tGeneration: 129226\n",
+      "\tBest fitness: 3140.3453083655336\n",
+      "\tEvaluations of fitness: 129227\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 4.0 s\n",
+      "\tCPU time Spent:  4.0 s\n",
+      "\tGeneration: 256369\n",
+      "\tBest fitness: 26.386176996621234\n",
+      "\tEvaluations of fitness: 256370\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 6.0 s\n",
+      "\tCPU time Spent:  6.0 s\n",
+      "\tGeneration: 387606\n",
+      "\tBest fitness: 4.311855373529104e-10\n",
+      "\tEvaluations of fitness: 387607\n",
+      "\n",
+      "Optimizing Sphere function using HillClimb:\n",
+      "\tReal time Spent: 8.0 s\n",
+      "\tCPU time Spent:  8.0 s\n",
+      "\tGeneration: 519880\n",
+      "\tBest fitness: 1.1344859990074184e-10\n",
+      "\tEvaluations of fitness: 519881\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Define the parameters of the optimization process\n",
+    "params = {\"stop_cond\": \"time_limit\", \"time_limit\": 10.0, \"verbose\": True, \"v_timer\": 2}\n",
+    "\n",
+    "# Define the objective function\n",
+    "objfunc = Sphere(3)\n",
+    "\n",
+    "# Define a population initialization module\n",
+    "pop_init = UniformVectorInitializer(\n",
+    "    objfunc.vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=1\n",
+    ")\n",
+    "\n",
+    "# Instanciate the newly created algorithm\n",
+    "algorithm = HillClimb(pop_init, operator)\n",
+    "\n",
+    "# Define a Search method\n",
+    "search = GeneralSearch(objfunc, algorithm, params)\n",
+    "\n",
+    "# Optimize the objective function\n",
+    "best_solution, best_fitness = search.optimize()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Best solution: [ 6.86495357e-06 -5.11245020e-06  6.33907448e-06]\n",
+      "Solution fitness: 1.1344859990074184e-10\n",
+      "\n",
+      "Number of generations: 650301\n",
+      "Real time spent: 10.00001s\n",
+      "CPU time spent: 9.99535s\n",
+      "Number of fitness evaluations: 650302\n",
+      "Best fitness: 1.1344859990074184e-10\n"
+     ]
+    },
+    {
+     "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAk0AAAHHCAYAAACiOWx7AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABUUElEQVR4nO3deXwM9/8H8NcmsZtzkziS3RChlLiLVOSL0kqFRutqXakzpQd1tlpVRy9RqmhdxfeHtm5K3b6ps9U0SIWggoqjKkmL7EaQaz+/P6aZWgk2bMwer+fjMY+dnfnszHumvtnXd+czn1EJIQSIiIiI6J5clC6AiIiIyB4wNBERERFZgKGJiIiIyAIMTUREREQWYGgiIiIisgBDExEREZEFGJqIiIiILMDQRERERGQBhiYiIiIiCzA0EZHVVatWDf3795ff79mzByqVCnv27JGX9e/fH9WqVXvgfUyaNAkqleqe+31Y586dg0qlwmefffbA2zh9+jTatWsHX19fqFQqbNiwAUuWLIFKpcK5c+esVisRlT2GJiK6p6Iv+EOHDpW4vk2bNqhfv77V9nfr1i3MmDED4eHh8PX1hbu7O2rVqoWhQ4fi1KlTVtvPo9KvXz+kpKTgk08+wTfffIOwsLAS282dOxdLlix5tMURUam4KV0AETme1NRUuLiU/v+T/f3332jfvj2SkpLQsWNH9O7dG97e3khNTcXKlSuxYMEC5OXlWX2/ZeXmzZtISEjAuHHjMHToUHl5nz590LNnT2g0GnnZ3LlzUbFiRav+UkZE1sXQRERWd3sYKI3+/fvj8OHDWLt2Lbp162a27qOPPsK4cePKZL9l5a+//gIA+Pn5mS13dXWFq6urAhUR0cOwnf9LRkQO40H6FiUmJmLLli2IjY0tFpgAKRDdr2/RnfsturT4008/YdiwYahUqRL8/Pzw6quvIi8vD1lZWejbty/8/f3h7++PMWPGQAhR4rZnzJiBkJAQeHh4oHXr1jh27Ng9a5k0aRJCQkIAAG+//TZUKpXch+vOPk3VqlXD8ePHsXfvXqhUKqhUKrRp08as7f79+zFq1ChUqlQJXl5e6NKlixzKbrdt2za0atUKXl5e8PHxQXR0NI4fP27WJj09HQMGDECVKlWg0Wig1+vRqVMnsz5Whw4dQlRUFCpWrAgPDw9Ur14dAwcOvOcxEzk6/tJERBYxGAz4+++/iy3Pz8+3yvY3btwIQLp0ZW1vvvkmdDodPvjgA/zyyy9YsGAB/Pz88PPPP6Nq1aqYPHkytm7dimnTpqF+/fro27ev2ee//vprZGdnY8iQIbh16xZmzZqFZ555BikpKQgMDCxxn127doWfnx9GjhyJXr164bnnnoO3t3eJbWfOnIk333wT3t7e8q9pd273zTffhL+/PyZOnIhz585h5syZGDp0KFatWiW3+eabb9CvXz9ERUXh008/xY0bNzBv3jy0bNkShw8flkNbt27dcPz4cbz55puoVq0aMjMzER8fjwsXLsjv27Vrh0qVKuHdd9+Fn58fzp07h+++++5B/xMQOQZBRHQPixcvFgDuOdWrV8/sMyEhIaJfv37y+927dwsAYvfu3fKyfv36iZCQEPl9ly5dBABx7do1i+qaOHGiuPNP2J37Lao9KipKmEwmeXlERIRQqVTitddek5cVFBSIKlWqiNatW8vL0tLSBADh4eEh/vjjD3l5YmKiACBGjhx5zxqLPj9t2jSz5UV1paWlycvq1atntu8720ZGRpodw8iRI4Wrq6vIysoSQgiRnZ0t/Pz8xKBBg8w+n56eLnx9feXl165dK7Gm261fv14AEAcPHrzn8RE5G16eIyKLzJkzB/Hx8cWmhg0bWmX7RqMRAODj42OV7d0uNjbWbHiC8PBwCCEQGxsrL3N1dUVYWBjOnj1b7POdO3dG5cqV5ffNmjVDeHg4tm7davVa72bw4MFmx9CqVSsUFhbi/PnzAID4+HhkZWWhV69e+Pvvv+XJ1dUV4eHh2L17NwDAw8MDarUae/bswbVr10rcV1EfrM2bN1vtl0QiR8DLc0RkkWbNmpV4u7y/v3+Jl+1KS6vVAgCys7OLdZx+WFWrVjV77+vrCwAIDg4utrykIPH4448XW1arVi2sXr3ailXe253H4O/vDwByvadPnwYAPPPMMyV+vuj8ajQafPrppxg9ejQCAwPRvHlzdOzYEX379oVOpwMAtG7dGt26dcMHH3yAGTNmoE2bNujcuTN69+5tc53tiR4l/tJERDYhNDQUAJCSkmL1bd/tTrWSlou7dARX2t2Ooahek8kEQOrXVNIvgt9//738mREjRuDUqVOIi4uDu7s7xo8fjzp16uDw4cMAAJVKhbVr1yIhIQFDhw7FpUuXMHDgQDRt2hTXr18v4yMlsl0MTURkE55//nkAwLfffqtwJcUV/Ypzu1OnTj3UiOZ3unN089KqUaMGACAgIACRkZHFpqK78W5vP3r0aPzvf//DsWPHkJeXh+nTp5u1ad68OT755BMcOnQIy5Ytw/Hjx7Fy5cqHqpPInjE0EZFNiIiIQPv27bFo0SJs2LCh2Pq8vDy89dZbj74wABs2bMClS5fk9wcOHEBiYiI6dOhgtX14eXkhKyvrgT8fFRUFrVaLyZMnl9gPqWh4ghs3buDWrVtm62rUqAEfHx/k5uYCkC753fmL2xNPPAEAchsiZ8Q+TURkM77++mu0a9cOXbt2xfPPP4+2bdvCy8sLp0+fxsqVK3H58uWHeg7cg6pZsyZatmyJ119/Hbm5uZg5cyYqVKiAMWPGWG0fTZs2xbx58/Dxxx+jZs2aCAgIuGv/pJJotVrMmzcPffr0QZMmTdCzZ09UqlQJFy5cwJYtW9CiRQvMnj0bp06dQtu2bdG9e3fUrVsXbm5uWL9+PTIyMtCzZ08AwNKlSzF37lx06dIFNWrUQHZ2NhYuXAitVovnnnvOasdMZG8YmojIZlSqVAk///wz5s6di1WrVmHcuHHIy8tDSEgIXnjhBQwfPlyRuvr27QsXFxfMnDkTmZmZaNasGWbPng29Xm+1fUyYMAHnz5/H1KlTkZ2djdatW5cqNAFA7969ERQUhClTpmDatGnIzc1F5cqV0apVKwwYMACA1Pm9V69e2LlzJ7755hu4ubkhNDQUq1evlgcVbd26NQ4cOICVK1ciIyMDvr6+aNasGZYtW4bq1atb7ZiJ7I1K2GqvRyIiIiIbwj5NRERERBZgaCIiIiKyAEMTERERkQUYmoiIiIgswNBEREREZAGGJiIiIiILcJwmKzGZTPjzzz/h4+Pz0I9DICIiokdDCIHs7GwEBQXBxeU+vyUJBe3du1d07NhR6PV6AUCsX79eXpeXlyfGjBkj6tevLzw9PYVerxd9+vQRly5dMtvGlStXRO/evYWPj4/w9fUVAwcOFNnZ2WZtjhw5Ilq2bCk0Go2oUqWK+PTTT4vVsnr1alG7dm2h0WhE/fr1xZYtW0p1LBcvXhQAOHHixIkTJ052OF28ePG+3/WK/tKUk5ODRo0aYeDAgejatavZuhs3buDXX3/F+PHj0ahRI1y7dg3Dhw/HCy+8gEOHDsntYmJicPnyZcTHxyM/Px8DBgzA4MGDsXz5cgCA0WhEu3btEBkZifnz5yMlJQUDBw6En58fBg8eDAD4+eef0atXL8TFxaFjx45Yvnw5OnfujF9//RX169e36Fh8fHwAABcvXoRWq7XG6ZHPQ9Ezn5KTk+Hp6Wm1bRMRETk7o9GI4OBg+Xv8XmxmRHCVSoX169ejc+fOd21z8OBBNGvWDOfPn0fVqlXx22+/oW7dujh48CDCwsIAANu3b8dzzz2HP/74A0FBQZg3bx7GjRuH9PR0qNVqAMC7776LDRs24OTJkwCAHj16ICcnB5s3b5b31bx5czzxxBOYP3++RfUbjUb4+vrCYDBYPTTVrFkTAHDmzBmGJiIiIisqzfe3XXUENxgMUKlU8PPzAwAkJCTAz89PDkwAEBkZCRcXFyQmJsptnnrqKTkwAdLTwFNTU3Ht2jW5TWRkpNm+oqKikJCQUMZHRERERPbCbjqC37p1C++88w569eolJ8H09HQEBASYtXNzc0P58uWRnp4ut7nzAZOBgYHyOn9/f6Snp8vLbm9TtI2S5ObmIjc3V35vNBof/OCIiIjI5tnFL035+fno3r07hBCYN2+e0uUAAOLi4uDr6ytPwcHBSpdEREREZcjmQ1NRYDp//jzi4+PNrjfqdDpkZmaatS8oKMDVq1eh0+nkNhkZGWZtit7fr03R+pKMHTsWBoNBni5evPjgB0lEREQ2z6ZDU1FgOn36NH744QdUqFDBbH1ERASysrKQlJQkL9u1axdMJhPCw8PlNvv27UN+fr7cJj4+HrVr14a/v7/cZufOnWbbjo+PR0RExF1r02g00Gq1ZhMRERE5LkVD0/Xr15GcnIzk5GQAQFpaGpKTk3HhwgXk5+fjxRdfxKFDh7Bs2TIUFhYiPT0d6enpyMvLAwDUqVMH7du3x6BBg3DgwAHs378fQ4cORc+ePREUFAQA6N27N9RqNWJjY3H8+HGsWrUKs2bNwqhRo+Q6hg8fju3bt2P69Ok4efIkJk2ahEOHDmHo0KGP/JwQERGRjSrVCI5Wtnv37hIHmOrXr59IS0u76wBUu3fvlrdx5coV0atXL+Ht7S20Wq0YMGDAPQe3rFy5spgyZUqxWlavXi1q1aol1Gq1qFevXqkHtzQYDAKAMBgMD3Qu7iYnJ0fo9Xqh1+tFTk6OVbdNRETk7Erz/W0z4zTZO47TREREZH8cdpwmIiIiIqUwNBERERFZgKGJiIiIyAIMTUREREQWsJvHqDizwkI9hPBQugwiIiKnxtBk4+bOdUNmZhLc3TcqXQoREZFT4+U5G9ekiQkAkJvbCoWFChdDRETkxBiabFxYmAkqlRFC+CM5mf+5iIiIlMJvYRvn5gao1fsBADt38j8XERGRUvgtbAc0mj0AgB9+cFW2ECIiIifG0GQHikLTL7+4wGBQthYiIiJnxdBkB9zcLsLV9XcUFqqwd6/S1RARETknhiY7odH8DADYvVvhQoiIiJwUQ5OdKOoM/r//KVwIERGRk2JoshMazV64ugqcOAH8/rvS1RARETkfhiY74eJiQMuW0kCXW7YoXAwREZETYmiyI08/LQ0Jzn5NREREjx5Dkx155hnpl6bdu8FHqhARET1iDE12pFEjE3x9AYMBOHBA6WqIiIicC0OTHXFzA559Vpr/4QdlayEiInI2DE12pig07dihbB1ERETOhqHJzkRFSa8JCcC1a8rWQkRE5EwYmuxMSAhQpw5gMvESHRER0aPE0GSHOnSQXrdtU7YOIiIiZ8LQZIduD01CKFsLERGRs2BoskOtWgGenkB6OnDkiNLVEBEROQeGJjuk0QBt20rz27crWwsREZGzYGiyU+3aSa/sDE5ERPRoMDTZqaJfmn76Cbh5U9laiIiInAFDk50KDQWqVAFyc4F9+5SuhoiIyPExNNkplQpo316a37JF2VqIiIicAUOTHevYUXrduJFDDxAREZU1hiY7FhkpDT1w/jyQlKR0NURERI6NocmOeXn9O9Dl1q3K1kJEROToGJrsXGSk9Lpzp7J1EBEROTqGJjtXNPTAL78A168rWwsREZEjY2iyczVrAtWrA3l5wJ49SldDRETkuBia7ByHHiAiIno0GJocQHS09Lp9O4ceICIiKisMTQ6gdWtArQbOnQNOnlS6GiIiIsfE0OQAvL2BNm2k+e3bFS2FiIjIYTE0OQj2ayIiIipbDE0OouiRKnv3AllZipZCRETkkBiaHMTjjwOhoUBBAS/RERERlQWGJgfywgvSKx+pQkREZH0MTQ6kqF/T9u2AyaRsLURERI6GocmBtGgh3Un311/AoUNKV0NERORYFA1N+/btw/PPP4+goCCoVCps2LDBbL0QAhMmTIBer4eHhwciIyNx+vRpszZXr15FTEwMtFot/Pz8EBsbi+t3PITt6NGjaNWqFdzd3REcHIypU6cWq2XNmjUIDQ2Fu7s7GjRogK12eI1LrQY6dJDmv/tO2VqIiIgcjaKhKScnB40aNcKcOXNKXD916lR88cUXmD9/PhITE+Hl5YWoqCjcunVLbhMTE4Pjx48jPj4emzdvxr59+zB48GB5vdFoRLt27RASEoKkpCRMmzYNkyZNwoIFC+Q2P//8M3r16oXY2FgcPnwYnTt3RufOnXHs2LGyO/gy8vzz0usPPyhbBxERkcMRNgKAWL9+vfzeZDIJnU4npk2bJi/LysoSGo1GrFixQgghxIkTJwQAcfDgQbnNtm3bhEqlEpcuXRJCCDF37lzh7+8vcnNz5TbvvPOOqF27tvy+e/fuIjo62qye8PBw8eqrr1pcv8FgEACEwWCw+DOWyMnJEXq9Xuj1epGTk3Pf9pcvC6FSCQEI8eefVi2FiIjI4ZTm+9tm+zSlpaUhPT0dkZGR8jJfX1+Eh4cjISEBAJCQkAA/Pz+EhYXJbSIjI+Hi4oLExES5zVNPPQW1Wi23iYqKQmpqKq5duya3uX0/RW2K9mNPdDqgSRNp/n//U7YWIiIiR2KzoSk9PR0AEBgYaLY8MDBQXpeeno6AgACz9W5ubihfvrxZm5K2cfs+7tamaH1JcnNzYTQazSZb8dxz0uvmzcrWQURE5EhsNjTZuri4OPj6+spTcHCw0iXJisZr2r4duK37FxERET0Emw1NOp0OAJCRkWG2PCMjQ16n0+mQmZlptr6goABXr141a1PSNm7fx93aFK0vydixY2EwGOTp4sWLpT3EMtO0KRAcDFy/DsTHK10NERGRY7DZ0FS9enXodDrs3LlTXmY0GpGYmIiIiAgAQEREBLKyspCUlCS32bVrF0wmE8LDw+U2+/btQ35+vtwmPj4etWvXhr+/v9zm9v0UtSnaT0k0Gg20Wq3ZZCtUKqBzZ2l+/XpFSyEiInIYioam69evIzk5GcnJyQCkzt/Jycm4cOECVCoVRowYgY8//hgbN25ESkoK+vbti6CgIHT+JxHUqVMH7du3x6BBg3DgwAHs378fQ4cORc+ePREUFAQA6N27N9RqNWJjY3H8+HGsWrUKs2bNwqhRo+Q6hg8fju3bt2P69Ok4efIkJk2ahEOHDmHo0KGP+pRYTVFo2rRJeh4dERERPaRHcDffXe3evVsAKDb169dPCCENOzB+/HgRGBgoNBqNaNu2rUhNTTXbxpUrV0SvXr2Et7e30Gq1YsCAASI7O9uszZEjR0TLli2FRqMRlStXFlOmTClWy+rVq0WtWrWEWq0W9erVE1u2bCnVsdjKkANF8vKEKF9eGnpg926rlkREROQwSvP9rRJCCAUzm8MwGo3w9fWFwWCw6qW6GzduoGbNmgCAM2fOwNPT0+LPDhwILF4MvPYaMG+e1UoiIiJyGKX5/rbZPk308Hr0kF43bAAYjYmIiB4OQ5MDa90a8PQE0tOBo0eVroaIiMi+MTQ5MHd34JlnpPlt25SthYiIyN4xNDm4Dh2k102blK2DiIjI3jE0ObhOnaTXn38G/vxT2VqIiIjsGUOTg6tcGfhnnE9s3KhsLURERPaMockJFA10+f33ipZBRERk1xianEDRJbpduwCjUdlaiIiI7BVDkxMIDZWmvDw+i46IiOhBMTQ5AZUK6N1bml+xQtlaiIiI7BVDk5N46SXpddcuICdH2VqIiIjsEUOTk6hdG6haFcjPB/buVboaIiIi+8PQ5CRUKqBjR2l+1SplayEiIrJHDE1OpGdP6XXTJqCgQNlaiIiI7A1DkxOJiAAqVACuXQP271e6GiIiIvvC0ORE3NyA556T5jk6OBERUekwNDmZotHBN2wAhFCyEiIiIvvC0ORknn0WUKuBs2eB1FSlqyEiIrIfDE1OxscHaNNGmt+8WdFSiIiI7ApDkxMqGnpgyxZl6yAiIrInDE1OKDpaev3xR+lOOiIiIro/hiYn9NhjQL16QGEhL9ERERFZiqHJSXXpIr2uX69sHURERPaCoclJFYWm7dv5AF8iIiJLMDQ5qcaNgZAQ4OZNID5e6WqIiIhsH0OTk1KpgK5dpfm1a5WthYiIyB4wNDmxF1+UXjdtAvLzla2FiIjI1jE0ObHmzYGAAMBoBHbvVroaIiIi28bQ5MRcXP59Ft133ylaChERkc1jaHJyRaFp40Zp3CYiIiIqGUOTk2vbFvDzAy5fBvbvV7oaIiIi28XQ5OTUauCFF6R53kVHRER0dwxNhJ49pdeVK3mJjoiI6G4YmgiRkYBWC/z1F3DokNLVEBER2SaGJkK5ckBUlDT//ffK1kJERGSrGJoIwL930a1bBwihaClEREQ2iaGJAADPPw+4uwOnTgHJyUpXQ0REZHsYmggA4OMDREdL86tWKVsLERGRLWJoItlLL0mvvERHRERUHEMTyZ57DtBogDNngJQUpashIiKyLQxNJPPx+fcuunXrlK2FiIjI1jA0kZlu3aRXDj1ARERkjqGJzDz3HODqChw5AqSlKV0NERGR7WBoIjMVKwKtW0vzfBYdERHRvxiaqJgXX5Rely9Xtg4iIiJbwtBExXTvDri5SYNcnj6tdDVERES2gaGJiqlQAWjTRprnJToiIiIJQxOVqGigS4YmIiIiiU2HpsLCQowfPx7Vq1eHh4cHatSogY8++gjituGqhRCYMGEC9Ho9PDw8EBkZidN3XFO6evUqYmJioNVq4efnh9jYWFy/ft2szdGjR9GqVSu4u7sjODgYU6dOfSTHaKs6dwZcXIBff+VddERERICNh6ZPP/0U8+bNw+zZs/Hbb7/h008/xdSpU/Hll1/KbaZOnYovvvgC8+fPR2JiIry8vBAVFYVbt27JbWJiYnD8+HHEx8dj8+bN2LdvHwYPHiyvNxqNaNeuHUJCQpCUlIRp06Zh0qRJWLBgwSM9XlsSEAA89ZQ0/913ytZCRERkE4QNi46OFgMHDjRb1rVrVxETEyOEEMJkMgmdTiemTZsmr8/KyhIajUasWLFCCCHEiRMnBABx8OBBuc22bduESqUSly5dEkIIMXfuXOHv7y9yc3PlNu+8846oXbu2xbUaDAYBQBgMhtIf6D3k5OQIvV4v9Hq9yMnJseq27+fLL4UAhAgPf6S7JSIiemRK8/1t0780/ec//8HOnTtx6tQpAMCRI0fw008/oUOHDgCAtLQ0pKenIzIyUv6Mr68vwsPDkZCQAABISEiAn58fwsLC5DaRkZFwcXFBYmKi3Oapp56CWq2W20RFRSE1NRXXrl0rsbbc3FwYjUazydG8+KJ0iS4xkZfoiIiIbDo0vfvuu+jZsydCQ0NRrlw5NG7cGCNGjEBMTAwAID09HQAQGBho9rnAwEB5XXp6OgICAszWu7m5oXz58mZtStrG7fu4U1xcHHx9feUpODj4IY/W9uh0HOiSiIioiE2HptWrV2PZsmVYvnw5fv31VyxduhSfffYZli5dqnRpGDt2LAwGgzxdvHhR6ZLKRPfu0uvKlcrWQUREpDSbDk1vv/22/GtTgwYN0KdPH4wcORJxcXEAAJ1OBwDIyMgw+1xGRoa8TqfTITMz02x9QUEBrl69atampG3cvo87aTQaaLVas8kRdesmPYvu11+B339XuhoiIiLl2HRounHjBlxczEt0dXWFyWQCAFSvXh06nQ47d+6U1xuNRiQmJiIiIgIAEBERgaysLCQlJcltdu3aBZPJhPDwcLnNvn37kJ+fL7eJj49H7dq14e/vX2bHZw8qVfr3Et2aNcrWQkREpCSbDk3PP/88PvnkE2zZsgXnzp3D+vXr8fnnn6NLly4AAJVKhREjRuDjjz/Gxo0bkZKSgr59+yIoKAidO3cGANSpUwft27fHoEGDcODAAezfvx9Dhw5Fz549ERQUBADo3bs31Go1YmNjcfz4caxatQqzZs3CqFGjlDp0m1J0iW7dOmXrICIiUtQjuJvvgRmNRjF8+HBRtWpV4e7uLh577DExbtw4s6EBTCaTGD9+vAgMDBQajUa0bdtWpKammm3nypUrolevXsLb21totVoxYMAAkZ2dbdbmyJEjomXLlkKj0YjKlSuLKVOmlKpWRxxyoEh6uhCurtLwA3ecWiIiIrtWmu9vlRC3Da9ND8xoNMLX1xcGg8Gq/Ztu3LiBmjVrAgDOnDkDT09Pq227NDp0ALZvBz74AJgwQZESiIiIrK403982fXmObEevXtLrihUAYzYRETkjhiaySKdOgEYDnDwJHD2qdDVERESPHkMTWcTXF3juOWmeYzYREZEzYmgiixVdolu5kpfoiIjI+TA0kcWiowEvL+DcOeDAAaWrISIierQYmshinp5S3yZA6hBORETkTBiaqFSKLtGtXg0UFipbCxER0aPE0ESl0q4d4O8PXL4M/Pij0tUQERE9OgxNVCpqtfQQX4CX6IiIyLkwNFGp9ewpva5dC9z2jGMiIiKHxtBEpdamDRAYCFy9Cvzwg9LVEBERPRoMTVRqrq5A9+7SPAe6JCIiZ8HQRA+k6BLd+vXAzZvK1kJERPQoMDTRA2neHKhaFcjOBrZuVboaIiKissfQRA/ExQXo0UOaX7ZM2VqIiIgeBYYmemAxMdLrtm2A0ahsLURERGWNoYkeWMOGQGgocOsWsGGD0tUQERGVLYYmemAq1b930a1Zo2wtREREZY2hiR5KUb+m7duBa9eUrYWIiKgsMTTRQ6lbV5oKCqTgRERE5KgYmuihdeokvS5frmwdREREZYmhiR5anz7S67ZtQEaGsrUQERGVFYYmemh16gDNmgGFhcCqVUpXQ0REVDYYmsgqevWSXnmJjoiIHBVDE1lFz56AmxuQmAgcP650NURERNbH0ERWodMBzz0nzfOxKkRE5IgYmshqih6rsny51L+JiIjIkTA0kdU8/zzg5wecPw/s2KF0NURERNbF0ERW4+EB9OsnzX/9tbK1EBERWRtDE1lV377S64YNfKwKERE5FoYmsqrGjYEGDYDcXGD1aqWrISIish6GJrIqlerfS3RLlypbCxERkTUxNJHVxcQArq5AQgKQmqp0NURERNbB0ERWp9MBUVHSPDuEExGRo2BoojLRv7/0+vXXHLOJiIgcA0MTlYmiMZv++APYvVvpaoiIiB7eQ4emwsJCJCcn4xrvL6fbuLtLz6MD2CGciIgcQ6lD04gRI/Df//4XgBSYWrdujSZNmiA4OBh79uyxdn1kx4ou0a1bBxiNipZCRET00EodmtauXYtGjRoBADZt2oS0tDScPHkSI0eOxLhx46xeINmvZs2A2rWBmzeBtWuVroaIiOjhlDo0/f3339DpdACArVu34qWXXkKtWrUwcOBApKSkWL1Asl+3j9m0eLGytRARET2sUoemwMBAnDhxAoWFhdi+fTueffZZAMCNGzfg6upq9QLJvvXtK4Wnn36SHuRLRERkr0odmgYMGIDu3bujfv36UKlUiIyMBAAkJiYiNDTU6gWSfatcGXjqKWn+22+VrYWIiOhhlDo0TZo0CYsWLcLgwYOxf/9+aDQaAICrqyveffddqxdI9m/gQOl1wQKO2URERPZLJYQQD7uRrKws+Pn5WaEc+2U0GuHr6wuDwQCtVmu17d64cQM1a9YEAJw5cwaenp5W2/ajcusWEBQEXLsGbN0KdOigdEVERESS0nx/l/qXpk8//RSrVq2S33fv3h0VKlRAlSpVcPTo0dJXSw7P3V3q2wSwQzgREdmvUoem+fPnIzg4GAAQHx+P+Ph4bNu2De3bt8dbb71l9QLJMfTpI71u2gRkZSlaChER0QMpdWhKT0+XQ9PmzZvRvXt3tGvXDmPGjMHBgwetXiA5hiZNgPr1pUt1K1cqXQ0REVHplTo0+fv74+LFiwCA7du3y3fPCSFQWAa9fC9duoSXX34ZFSpUgIeHBxo0aIBDhw7J64UQmDBhAvR6PTw8PBAZGYnTp0+bbePq1auIiYmBVquFn58fYmNjcf36dbM2R48eRatWreDu7o7g4GBMnTrV6sfizFQqYMAAaZ6X6IiIyB6VOjR17doVvXv3xrPPPosrV66gwz+9eg8fPix3WLaWa9euoUWLFihXrhy2bduGEydOYPr06fD395fbTJ06FV988QXmz5+PxMREeHl5ISoqCrdu3ZLbxMTE4Pjx44iPj8fmzZuxb98+DB48WF5vNBrRrl07hISEICkpCdOmTcOkSZOwYMECqx6Ps3v5ZcDNDThwADhxQulqiIiISkmUUl5enpg2bZoYNmyY+PXXX+Xln3/+uVi4cGFpN3dP77zzjmjZsuVd15tMJqHT6cS0adPkZVlZWUKj0YgVK1YIIYQ4ceKEACAOHjwot9m2bZtQqVTi0qVLQggh5s6dK/z9/UVubq7ZvmvXrm1xrQaDQQAQBoPB4s9YIicnR+j1eqHX60VOTo5Vt62ETp2EAIQYPlzpSoiIiEr3/V3qX5rKlSuHt956C7NmzULjxo3l5SNHjsQrr7xivTQHYOPGjQgLC8NLL72EgIAANG7cGAsXLpTXp6WlIT09Xb5ECAC+vr4IDw9HQkICACAhIQF+fn4ICwuT20RGRsLFxQWJiYlym6eeegpqtVpuExUVhdTUVFy7dq3E2nJzc2E0Gs0mur+iH/i+/lrq30RERGQvSh2aAOCbb75By5YtERQUhPP/PBtj5syZ+P77761a3NmzZzFv3jw8/vjj2LFjB15//XUMGzYMS5cuBSB1SgekR7vcLjAwUF6Xnp6OgIAAs/Vubm4oX768WZuStnH7Pu4UFxcHX19feSrqHE/3FhUFBAdLYzatW6d0NURERJYrdWiaN28eRo0ahQ4dOiArK0vu/O3n54eZM2datTiTyYQmTZpg8uTJaNy4MQYPHoxBgwZh/vz5Vt3Pgxg7diwMBoM8FXWOp3tzdQWKfpD873+VrYWIiKg0Sh2avvzySyxcuBDjxo0ze0BvWFgYUlJSrFqcXq9H3bp1zZbVqVMHFy5cAADodDoAQEZGhlmbjIwMeZ1Op0NmZqbZ+oKCAly9etWsTUnbuH0fd9JoNNBqtWYTWaZfP+luut27gbQ0pashIiKyTKlDU1pamllfpiIajQY5OTlWKapIixYtkJqaarbs1KlTCAkJAQBUr14dOp0OO3fulNcbjUYkJiYiIiICABAREYGsrCwkJSXJbXbt2gWTyYTw8HC5zb59+5Cfny+3iY+PR+3atc3u1CPrCAkBirqhcfgBIiKyF6UOTdWrV0dycnKx5du3b0edOnWsUZNs5MiR+OWXXzB58mScOXMGy5cvx4IFCzBkyBAAgEqlwogRI/Dxxx9j48aNSElJQd++fREUFITOnTsDkH6Zat++PQYNGoQDBw5g//79GDp0KHr27ImgoCAAQO/evaFWqxEbG4vjx49j1apVmDVrFkaNGmXV46F/FY3ZtGQJUFCgaClERESWKe2teQsXLhSVK1cWK1euFF5eXmLFihXi448/luetbdOmTaJ+/fpCo9GI0NBQsWDBArP1JpNJjB8/XgQGBgqNRiPatm0rUlNTzdpcuXJF9OrVS3h7ewutVisGDBggsrOzzdocOXJEtGzZUmg0GlG5cmUxZcqUUtXJIQdK5+ZNIcqXl4Yf2L5d6WqIiMhZleb7WyWEEKUNWsuWLcOkSZPw+++/AwCCgoLwwQcfIDY21sqRzn6U5inJpXHjxg150NAzZ87A09PTattW2ptvArNnA716AcuXK10NERE5o9J8fz9QaCpy48YNXL9+vdgt/c6Ioan0EhOB5s0BT08gMxPw8lK6IiIicjal+f5+oHGainh6ejIw0QNr1gyoUQO4cQNYtUrpaoiIiO6t1KEpIyMDffr0QVBQENzc3ODq6mo2EVlKpfp3hHAbGHqLiIjontxK+4H+/fvjwoULGD9+PPR6PVQqVVnURU6if3/gvfeAgweBo0eBhg2VroiIiKhkpQ5NP/30E3788Uc88cQTZVAOOZuAAKBLF2DtWqlT+IIFSldERERUslJfngsODsZD9B0nKuafYbewYgWQna1sLURERHdT6tA0c+ZMvPvuuzh37lwZlEPOqHVroHZt4Pp1YNkypashIiIqWalDU48ePbBnzx7UqFEDPj4+KF++vNlEVFoqFfDqq9L8/PkAf8gkIiJbVOo+TTNmzGDnb7K6fv2AsWOBI0eAAweAfx4LSEREZDMe6O45ImsrXx7o3h345hvp1yaGJiIisjWlvjzn6uqKzMzMYsuvXLnCcZroobz2mvS6ejU7hBMRke0pdWi6251zubm5UKvVD10QOa+ICCA0VBoh/P/+T+lqiIiIzFl8ee6LL74AAKhUKixatAje3t7yusLCQuzbtw+hoaHWr5CchkoFDBsGvPGGNGbTm28CLg/1oB8iIiLrsTg0zZgxA4D0S9P8+fPNLsWp1WpUq1YN8/ksDHpIffpIHcLPnAF27gSefVbpioiIiCQWh6a0tDQAwNNPP43vvvsO/v7+ZVYUOS9vbyAmBpg7VxodnKGJiIhsRakvfuzevZuBicpUUYfwDRuAP/9UtBQiIiKZRb80jRo1Ch999BG8vLwwatSoe7b9/PPPrVIYOa8GDYCWLYGffgIWLQImTFC6IiIiIgtD05IlS/Dee+/By8sLhw8fvms7DnpJ1vLGG1Jo+uorqY9TuXJKV0RERM7OotCUlZUFk8kEADh//jwOHjyIChUqlGlh5Ny6dgUCAqTLc999B/TooXRFRETk7Czq0+Tv7y93BD937pwcoIjKikYDvP66NP/ll8rWQkREBFj4S1O3bt3QunVr6PV6qFQqhIWF3XX077Nnz1q1QHJer74KfPIJsH8/kJIi9XUiIiJSikWhacGCBejatSvOnDmDYcOGYdCgQfDx8Snr2sjJ6fVAp07AunXArFlSp3AiIiKlWDxOU/v27QEASUlJGD58OEMTPRKjRkmh6dtvgcmTpX5ORERESij1OE2LFy9mYKJHJiICaNYMyM2VBrskIiJSCp/sRTZNpZKeQQcA8+YBBQXK1kNERM6LoYlsXvfuQKVK0vAD69YpXQ0RETkrhiayeWr1v8MPzJqlbC1EROS8GJrILrz+uhSeEhKkIQiIiIgeNYYmsgs6HdC3rzT/2WfK1kJERM6JoYnsRtGzor//Hvj9d2VrISIi58PQRHajTh2gfXtACGDGDKWrISIiZ8PQRHZl5Ejp9dtvAaNR2VqIiMi5MDSRXYmMBEJDAYMBWLhQ6WqIiMiZMDSRXXFx+bdv0+zZQGGhsvUQEZHzYGgiu/Pyy0CFCsC5c8DGjUpXQ0REzoKhieyOhwfw6qvSPDuEExHRo8LQRHbpjTcAV1fgxx+BlBSlqyEiImfA0ER2qXJloHNnaX7OHEVLISIiJ8HQRHZr6FDp9euvgb/+UrYWIiJyfAxNZLdatwaaNgVu3gS+/FLpaoiIyNExNJHdUqmAsWOl+S++AK5fV7YeIiJybAxNZNe6dAFq1JAGu1yxQulqiIjIkTE0kV1zcZHupAOAzz8HTCZl6yEiIsfF0ER275VXAK0WOHkS2LpV6WqIiMhRMTSR3dNqgcGDpfkpU5SthYiIHBdDEzmEkSMBtRrYvx/4+WelqyEiIkfE0EQOIShIeiYdAEybpmwtRETkmOwqNE2ZMgUqlQojRoyQl926dQtDhgxBhQoV4O3tjW7duiEjI8PscxcuXEB0dDQ8PT0REBCAt99+GwUFBWZt9uzZgyZNmkCj0aBmzZpYsmTJIzgisqa33pKGIdiwAUhOVroaIiJyNHYTmg4ePIivvvoKDRs2NFs+cuRIbNq0CWvWrMHevXvx559/omvXrvL6wsJCREdHIy8vDz///DOWLl2KJUuWYMKECXKbtLQ0REdH4+mnn0ZycjJGjBiBV155BTt27Hhkx0cPr04doEcPaX76dGVrISIiByTsQHZ2tnj88cdFfHy8aN26tRg+fLgQQoisrCxRrlw5sWbNGrntb7/9JgCIhIQEIYQQW7duFS4uLiI9PV1uM2/ePKHVakVubq4QQogxY8aIevXqme2zR48eIioqyuIaDQaDACAMBsODHmaJcnJyhF6vF3q9XuTk5Fh1247o0CEhACFcXYX4/XelqyEiIltXmu9vu/ilaciQIYiOjkZkZKTZ8qSkJOTn55stDw0NRdWqVZGQkAAASEhIQIMGDRAYGCi3iYqKgtFoxPHjx+U2d247KipK3kZJcnNzYTQazSZSXtOmQFQUUFgIxMUpXQ0RETkSmw9NK1euxK+//oq4Er4B09PToVar4efnZ7Y8MDAQ6enpcpvbA1PR+qJ192pjNBpx8+bNEuuKi4uDr6+vPAUHBz/Q8ZH1jR8vvX79NXDpkrK1EBGR47Dp0HTx4kUMHz4cy5Ytg7u7u9LlmBk7diwMBoM8Xbx4UemS6B//+Q/QsiWQlwd89pnS1RARkaOw6dCUlJSEzMxMNGnSBG5ubnBzc8PevXvxxRdfwM3NDYGBgcjLy0NWVpbZ5zIyMqDT6QAAOp2u2N10Re/v10ar1cLDw6PE2jQaDbRardlEtkGl+vfXpq++AjIzla2HiIgcg02HprZt2yIlJQXJycnyFBYWhpiYGHm+XLly2Llzp/yZ1NRUXLhwAREREQCAiIgIpKSkIPO2b874+HhotVrUrVtXbnP7NoraFG2D7M+zzwJPPgncvCk9k46IiOhhuSldwL34+Pigfv36Zsu8vLxQoUIFeXlsbCxGjRqF8uXLQ6vV4s0330RERASaN28OAGjXrh3q1q2LPn36YOrUqUhPT8f777+PIUOGQKPRAABee+01zJ49G2PGjMHAgQOxa9curF69Glu2bHm0B0xWU/Rr0wsvAHPmAGPGAOXLK10VERHZM5v+pckSM2bMQMeOHdGtWzc89dRT0Ol0+O677+T1rq6u2Lx5M1xdXREREYGXX34Zffv2xYcffii3qV69OrZs2YL4+Hg0atQI06dPx6JFixAVFaXEIZGVdOwINGoEXL8OzJypdDVERGTvVEIIoXQRjsBoNMLX1xcGg8Gq/Ztu3LiBmjVrAgDOnDkDT09Pq23bGaxbB7z4IuDtDfz+OxAQoHRFRERkS0rz/W33vzQR3UuXLtLYTdevA598onQ1RERkzxiayKG5uABTpkjzCxYAly8rWw8REdkvhiZyeG3bAhERwK1bHCWciIgeHEMTOTyVCijq9//VVwDHISUiogfB0EROoW1boE0baZTwTz9VuhoiIrJHDE3kFFQqYMIEaX7hQuD8eWXrISIi+8PQRE7j6aelKS8P+OADpashIiJ7w9BETmXyZOl16VLg5EllayEiIvvC0EROpXlzoFMnwGT696G+RERElmBoIqfz8cdSH6e1a4GkJKWrISIie8HQRE6nfn3g5Zel+ffeU7YWIiKyHwxN5JQmTQLc3ID//Q/Ys0fpaoiIyB4wNJFTeuwxYPBgaf699wA+tpqIiO6HoYmc1vvvAx4eQEICsHmz0tUQEZGtY2gip6XXA8OHS/Pjxkl31BEREd0NQxM5tTFjAD8/ICUFWLFC6WqIiMiWMTSRU/P3l4ITID1mJS9P2XqIiMh2MTSR0xs2DAgMBM6eBf77X6WrISIiW8XQRE7Py0vqFA4AH30E3LihbD1ERGSbGJqIIA0/UK0acPkyMHu20tUQEZEtYmgiAqBWAx98IM1Pngxcu6ZsPUREZHsYmoj+ERMjPWLFYACmT1e6GiIisjUMTUT/cHUFPvxQmp89G7h6Vdl6iIjItjA0Ed2mUyegYUPp16YpU5SuhoiIbAlDE9FtXFyATz6R5ufMAS5dUrYeIiKyHQxNRHeIjgYiIqShB8aNU7oaIiKyFQxNRHdQqYAZM6T5r78GDh9Wth4iIrINDE1EJQgPB3r2BIQA3nlH6WqIiMgWMDQR3cUnnwDlygHx8cAPPyhdDRERKY2hieguHnsMeOMNaX70aKCwUNl6iIhIWQxNRPcwYQLg7w8cPQosXqx0NUREpCSGJqJ7KF9eCk6AdCfdX38pWw8RESmHoYnoPt54A6hXD8jMBMaMUboaIiJSCkMT0X2o1cCiRdL8kiXAoUOKlkNERAphaCKyQPPmwMsvS/ODBrFTOBGRM2JoIrLQZ58Bfn5AcjKwcKHS1RAR0aPG0ERkocBA4IMPpPkxY/hcOiIiZ8PQRFQKb7whjRaenQ2MHKl0NURE9CgxNBGVgpsb8NVXgIsLsGYNsHOn0hUREdGjwtBEVEqNGv07UvjrrwO5ucrWQ0REjwZDE9ED+PhjQKcDTp+WOogTEZHjY2giegC+vv+GpY8+AlJTla2HiIjKHkMT0QPq3Rto3166PDdkCCCE0hUREVFZYmgiekAqFTB7NuDuLnUIX7pU6YqIiKgsMTQRPYQaNYBJk6T5ESOAP/5QshoiIipLDE1ED2n0aKBZM8BgkOaJiMgxMTQRPSQ3N2D+fMDVFVi9GvjuO6UrIiKismDToSkuLg5PPvkkfHx8EBAQgM6dOyP1jtuUbt26hSFDhqBChQrw9vZGt27dkJGRYdbmwoULiI6OhqenJwICAvD222+joKDArM2ePXvQpEkTaDQa1KxZE0uWLCnrwyMH0rix9GgVQBq76e+/la2HiIisz6ZD0969ezFkyBD88ssviI+PR35+Ptq1a4ecnBy5zciRI7Fp0yasWbMGe/fuxZ9//omuXbvK6wsLCxEdHY28vDz8/PPPWLp0KZYsWYIJEybIbdLS0hAdHY2nn34aycnJGDFiBF555RXs2LHjkR4v2beJE4G6dYHMTGDYMKWrISIiqxN2JDMzUwAQe/fuFUIIkZWVJcqVKyfWrFkjt/ntt98EAJGQkCCEEGLr1q3CxcVFpKeny23mzZsntFqtyM3NFUIIMWbMGFGvXj2zffXo0UNERUVZXJvBYBAAhMFgeODjK0lOTo7Q6/VCr9eLnJwcq26brO/AASFcXYUAhPjuO6WrISKi+ynN97dN/9J0J4PBAAAoX748ACApKQn5+fmIjIyU24SGhqJq1apISEgAACQkJKBBgwYIDAyU20RFRcFoNOL48eNym9u3UdSmaBslyc3NhdFoNJuInnzy38t0r70GXLmibD1ERGQ9dhOaTCYTRowYgRYtWqB+/foAgPT0dKjVavj5+Zm1DQwMRHp6utzm9sBUtL5o3b3aGI1G3Lx5s8R64uLi4OvrK0/BwcEPfYzkGHiZjojIMdlNaBoyZAiOHTuGlStXKl0KAGDs2LEwGAzydPHiRaVLIhuh0QBLlkh30y1fDmzYoHRFRERkDXYRmoYOHYrNmzdj9+7dqFKlirxcp9MhLy8PWVlZZu0zMjKg0+nkNnfeTVf0/n5ttFotPDw8SqxJo9FAq9WaTURFeJmOiMjx2HRoEkJg6NChWL9+PXbt2oXq1aubrW/atCnKlSuHnTt3ystSU1Nx4cIFREREAAAiIiKQkpKCzMxMuU18fDy0Wi3q1q0rt7l9G0VtirZB9CCKLtNlZPAyHRGRI7Dp0DRkyBB8++23WL58OXx8fJCeno709HS5n5Gvry9iY2MxatQo7N69G0lJSRgwYAAiIiLQvHlzAEC7du1Qt25d9OnTB0eOHMGOHTvw/vvvY8iQIdBoNACA1157DWfPnsWYMWNw8uRJzJ07F6tXr8bIkSMVO3ayf0WX6VxcpMt0a9cqXRERET2Usr+Z78EBKHFavHix3ObmzZvijTfeEP7+/sLT01N06dJFXL582Ww7586dEx06dBAeHh6iYsWKYvTo0SI/P9+sze7du8UTTzwh1Gq1eOyxx8z2YQkOOUB389570hAEFSoIccc/TSIiUlhpvr9VQgihXGRzHEajEb6+vjAYDFbt33Tjxg3UrFkTAHDmzBl4enpabdv0aOTlAeHhQHIy8PTTQHy81EmciIiUV5rvb5u+PEfkCNRq6fKclxewezfwySdKV0RERA+CoYnoEahTR3qoLwB89BGQmKhsPUREVHoMTUSPSEwM0L07UFAA9OgB/DPAPRER2QmGJqJHRKUCFi4EqlcHzp8HBg8G2KOQiMh+MDQRPUJardS/ydUVWL1aClFERGQfGJqIHrHmzYG4OGl+2DDgyBFl6yEiIsswNBEpYPRoIDoayM2V+jkZjUpXRERE98PQRKQAFxdptPAqVYBTp4CBA9m/iYjI1jE0ESmkYkXp0SpubsC6dcDs2UpXRERE98LQRKSg8HDg88+l+TFjgGPHlK2HiIjujqGJSGFDhwLt2wO3bgGdOgFXrihdERERlYShiUhhKhXwzTfS+E1nz0rBKTdX6aqIiOhODE1ENqBiRWDTJsDXF9i/X/r1iYiIbAtDE5GNqFcPWLlSurNu0SLg//5P6YqIiOh2DE1ENqR9e2DSJGn+tdeAXbsULYeIiG7D0ERkY8aNkwa8zM+X+jcdOqR0RUREBDA0EdkcFxdg6VKgbVvg+nXghReAP/5QuioiImJoIrJB7u7Ad99J/ZwuXwa6dJGGJCAiIuUwNBHZKK1WuqOuQgXpEl2fPkBBgdJVERE5L4YmIhtWvTqwZg1Qrpz0yJXYWD6jjohIKQxNRDbu6aeB1asBV1fg66+ljuJERPToMTQR2YHOnYGFC6X5uDhpIiKiR4uhichODBjwb1h67z3giy+UrYeIyNkwNBHZkXffBSZMkOaHDwemTFG2HiIiZ8LQRGRnJk0C3n9fmh87FpgxQ9FyiIicBkMTkZ1RqYCPPgI++EB6P2oU8OWXytZEROQMGJqI7NT48cA770jzw4ZJQYrDERARlR2GJiI7pVJJHcMnTpTeT5gAvPkmUFiobF1ERI6KoYnIjqlUUh+nmTOl+TlzpEeu5OQoXRkRkeNhaCJyAMOHSwNgajTSo1fatAHS05WuiojIsTA0ETmIF18Edu3691l1ERHAb78pXRURkeNgaCJyIP/5D5CQANSsCZw7BzRvDmzfrnRVRESOgaGJyME8/rgUnFq0AIxGIDpaGsuJd9YRET0chiYiB1SxonSpLjYWMJmksZwGDADy8pSujIjIfjE0ETkotVp6yO+MGYCrK7B0KfDMM8Dly0pXRkRknxiaiByYSgWMGAFs3gxotcD+/cATTwDx8UpXRkRkfxiaiJxA+/bAwYNAgwZAZiYQFQWMGwfk5ytdGRGR/WBoInIStWoBiYnA4MFSp/DJk6XO4idPKl0ZEZF9YGgiciIeHsBXX0kDYfr5Sb8+NW4MfPYZUFCgdHVERLaNoYnICb30EpCSArRrB9y6Bbz9NvDkk9KgmEREVDKGJiInVaWKNPDlf/8L+PsDyclAeDgwciRw/brS1RER2R6GJiInplIBAwdK/Zp695bGdJo5E6hXT7rjjoiI/sXQREQICACWLQO2bQOqVQMuXACef1568O/+/UpXR0RkGxiaiEjWvj1w7JjUx0mtBvbuBVq2BDp0AH76SenqiIiUxdBERGa8vICpU4HffwcGDZJGE9++HWjVSpq2beNz7IjIOTE0EVGJqlQBFiwAUlOl8KRWS782PfecNKr4l18Cf/2ldJVERI8OQxMR3VONGlJ4SksDRo+Wfok6ehQYNgwICgJeeAFYu1YauoCIyJExNN1hzpw5qFatGtzd3REeHo4DBw4oXRKRTQgKkgbBvHgRmDULCAuTBsTctEka90mvl+7E27SJQxYQkWNiaLrNqlWrMGrUKEycOBG//vorGjVqhKioKGRmZipdGpHN8PeXfmU6eBA4cQIYOxYIDgaysoDFi6VfnsqXB1q3BsaPB3bsAK5cUbpqIqKHpxKCXTqLhIeH48knn8Ts2bMBACaTCcHBwXjzzTfx7rvv3vOzRqMRvr6+uHz5MrRardVqunHjBho0aAAASElJgaenp9W2TWQtJhOwf78L1q93xY4drjh3rvj/H9PrTahZU6BGDYHHHjOhShWBChUE/P0BT08BF4X+L5xK5Rz7dLb9OtOxKrVfJfbp6SlQqZJ1t2k0GqHX62EwGO77/e1m3V3br7y8PCQlJWHs2LHyMhcXF0RGRiIhIaFY+9zcXOTm5srvjUYjAOCJJ56AixX/+gsh5F+6GjRoAJVS/4skspAQQKVK1ZGXF4G8vObIy2uCwsLHcPmyCy5fBn78UekKicheubuvh7//EKtu02QyWdyWoekff//9NwoLCxEYGGi2PDAwECdLeAx8XFwcPvjgg0dVHpHdUKkAN7c0uLmlwdNzOQDAZPJBQUFNFBRUR2HhYygsrIrCQh1MpvIwmXwhhMejrNDB9sN92c9+HHNfQjy6Y1Kp8h7ZvkrC0PSAxo4di1GjRsnvjUYjgoODkZyczMtzRETkRB5lL5/n/5msp+jynCUYmv5RsWJFuLq6IiMjw2x5RkYGdDpdsfYajQYajabYck9PT6sHm6JLcmWxbSIiImdWUFBgcVvePfcPtVqNpk2bYufOnfIyk8mEnTt3IiIiQsHKiIiIyBbwl6bbjBo1Cv369UNYWBiaNWuGmTNnIicnBwMGDFC6NCIiIlIYQ9NtevTogb/++gsTJkxAeno6nnjiCWzfvr1Y53AiIiJyPhynyUqKxmmyZJyH0rhx4wZq1qwJADhz5gz7NBEREVlRab6/2aeJiIiIyAIMTUREREQWYGgiIiIisgBDExEREZEFGJqIiIiILMDQRERERGQBhiYiIiIiCzA0EREREVmAoYmIiIjIAnyMipUUDaxuNBqtut0bN27AZDLJ2y7N05iJiIjo3oq+ty15QApDk5VkZ2cDAIKDg8tsH3q9vsy2TURE5Myys7Ph6+t7zzZ89pyVmEwm/Pnnn/Dx8YFKpbLqto1GI4KDg3Hx4kWrPtfOnvGcFMdzUjKel+J4TorjOSmZM5wXIQSys7MRFBQEF5d791riL01W4uLigipVqpTpPrRarcP+o31QPCfF8ZyUjOelOJ6T4nhOSubo5+V+vzAVYUdwIiIiIgswNBERERFZgKHJDmg0GkycOBEajUbpUmwGz0lxPCcl43kpjuekOJ6TkvG8mGNHcCIiIiIL8JcmIiIiIgswNBERERFZgKGJiIiIyAIMTUREREQWYGiycXPmzEG1atXg7u6O8PBwHDhwQOmSLLJv3z48//zzCAoKgkqlwoYNG8zWCyEwYcIE6PV6eHh4IDIyEqdPnzZrc/XqVcTExECr1cLPzw+xsbG4fv26WZujR4+iVatWcHd3R3BwMKZOnVqsljVr1iA0NBTu7u5o0KABtm7dWuparCEuLg5PPvkkfHx8EBAQgM6dOyM1NdWsza1btzBkyBBUqFAB3t7e6NatGzIyMszaXLhwAdHR0fD09ERAQADefvvtYs8k3LNnD5o0aQKNRoOaNWtiyZIlxeq5378tS2qxhnnz5qFhw4by4HkRERHYtm1bqepwtHNyuylTpkClUmHEiBGlqsPRzsmkSZOgUqnMptDQ0FLV4WjnBAAuXbqEl19+GRUqVICHhwcaNGiAQ4cOyeud8W9tmRJks1auXCnUarX4v//7P3H8+HExaNAg4efnJzIyMpQu7b62bt0qxo0bJ7777jsBQKxfv95s/ZQpU4Svr6/YsGGDOHLkiHjhhRdE9erVxc2bN+U27du3F40aNRK//PKL+PHHH0XNmjVFr1695PUGg0EEBgaKmJgYcezYMbFixQrh4eEhvvrqK7nN/v37haurq5g6dao4ceKEeP/990W5cuVESkpKqWqxhqioKLF48WJx7NgxkZycLJ577jlRtWpVcf36dbnNa6+9JoKDg8XOnTvFoUOHRPPmzcV//vMfeX1BQYGoX7++iIyMFIcPHxZbt24VFStWFGPHjpXbnD17Vnh6eopRo0aJEydOiC+//FK4urqK7du3y20s+bd1v1qsZePGjWLLli3i1KlTIjU1Vbz33nuiXLly4tixY057ToocOHBAVKtWTTRs2FAMHz7c4joc8ZxMnDhR1KtXT1y+fFme/vrrL6c+J1evXhUhISGif//+IjExUZw9e1bs2LFDnDlzRm7jjH9ryxJDkw1r1qyZGDJkiPy+sLBQBAUFibi4OAWrKr07Q5PJZBI6nU5MmzZNXpaVlSU0Go1YsWKFEEKIEydOCADi4MGDcptt27YJlUolLl26JIQQYu7cucLf31/k5ubKbd555x1Ru3Zt+X337t1FdHS0WT3h4eHi1VdftbiWspKZmSkAiL1798r7LVeunFizZo3c5rfffhMAREJCghBCCqMuLi4iPT1dbjNv3jyh1Wrl8zBmzBhRr149s3316NFDREVFye/v92/LklrKkr+/v1i0aJFTn5Ps7Gzx+OOPi/j4eNG6dWs5NDnrOZk4caJo1KhRieuc9Zy88847omXLlnddz7+11sfLczYqLy8PSUlJiIyMlJe5uLggMjISCQkJClb28NLS0pCenm52bL6+vggPD5ePLSEhAX5+fggLC5PbREZGwsXFBYmJiXKbp556Cmq1Wm4TFRWF1NRUXLt2TW5z+36K2hTtx5JayorBYAAAlC9fHgCQlJSE/Px8s1pCQ0NRtWpVs/PSoEEDBAYGmh2P0WjE8ePH5Tb3OmZL/m1ZUktZKCwsxMqVK5GTk4OIiAinPidDhgxBdHR0sbqd+ZycPn0aQUFBeOyxxxATE4MLFy5YXIcjnpONGzciLCwML730EgICAtC4cWMsXLhQXs+/tdbH0GSj/v77bxQWFpr9DxwAAgMDkZ6erlBV1lFU/72OLT09HQEBAWbr3dzcUL58ebM2JW3j9n3crc3t6+9XS1kwmUwYMWIEWrRogfr168u1qNVq+Pn53bPeBz1mo9GImzdvWvRvy5JarCklJQXe3t7QaDR47bXXsH79etStW9dpz8nKlSvx66+/Ii4urtg6Zz0n4eHhWLJkCbZv34558+YhLS0NrVq1QnZ2ttOek7Nnz2LevHl4/PHHsWPHDrz++usYNmwYli5danZczvy31trclC6AyBkNGTIEx44dw08//aR0KTahdu3aSE5OhsFgwNq1a9GvXz/s3btX6bIUcfHiRQwfPhzx8fFwd3dXuhyb0aFDB3m+YcOGCA8PR0hICFavXg0PDw8FK1OOyWRCWFgYJk+eDABo3Lgxjh07hvnz56Nfv34KV+eY+EuTjapYsSJcXV2L3XGRkZEBnU6nUFXWUVT/vY5Np9MhMzPTbH1BQQGuXr1q1qakbdy+j7u1uX39/WqxtqFDh2Lz5s3YvXs3qlSpIi/X6XTIy8tDVlbWPet90GPWarXw8PCw6N+WJbVYk1qtRs2aNdG0aVPExcWhUaNGmDVrllOek6SkJGRmZqJJkyZwc3ODm5sb9u7diy+++AJubm4IDAx0unNSEj8/P9SqVQtnzpxxyn8nAKDX61G3bl2zZXXq1JEvWzr739qywNBko9RqNZo2bYqdO3fKy0wmE3bu3ImIiAgFK3t41atXh06nMzs2o9GIxMRE+dgiIiKQlZWFpKQkuc2uXbtgMpkQHh4ut9m3bx/y8/PlNvHx8ahduzb8/f3lNrfvp6hN0X4sqcVahBAYOnQo1q9fj127dqF69epm65s2bYpy5cqZ1ZKamooLFy6YnZeUlBSzP3Lx8fHQarXyH8/7HbMl/7YsqaUsmUwm5ObmOuU5adu2LVJSUpCcnCxPYWFhiImJkeed7ZyU5Pr16/j999+h1+ud8t8JALRo0aLYsCWnTp1CSEgIAOf9W1umlO6JTne3cuVKodFoxJIlS8SJEyfE4MGDhZ+fn9ndH7YqOztbHD58WBw+fFgAEJ9//rk4fPiwOH/+vBBCuvXUz89PfP/99+Lo0aOiU6dOJd4G27hxY5GYmCh++ukn8fjjj5vdBpuVlSUCAwNFnz59xLFjx8TKlSuFp6dnsdtg3dzcxGeffSZ+++03MXHixBJvg71fLdbw+uuvC19fX7Fnzx6z26Zv3Lght3nttddE1apVxa5du8ShQ4dERESEiIiIkNcX3Tbdrl07kZycLLZv3y4qVapU4m3Tb7/9tvjtt9/EnDlzSrxt+n7/tu5Xi7W8++67Yu/evSItLU0cPXpUvPvuu0KlUon//e9/TntO7nT73XOW1OGI52T06NFiz549Ii0tTezfv19ERkaKihUriszMTKc9JwcOHBBubm7ik08+EadPnxbLli0Tnp6e4ttvv5XbOOPf2rLE0GTjvvzyS1G1alWhVqtFs2bNxC+//KJ0SRbZvXu3AFBs6tevnxBCuv10/PjxIjAwUGg0GtG2bVuRmppqto0rV66IXr16CW9vb6HVasWAAQNEdna2WZsjR46Ili1bCo1GIypXriymTJlSrJbVq1eLWrVqCbVaLerVqye2bNlitt6SWqyhpPMBQCxevFhuc/PmTfHGG28If39/4enpKbp06SIuX75stp1z586JDh06CA8PD1GxYkUxevRokZ+fb9Zm9+7d4oknnhBqtVo89thjZvsocr9/W5bUYg0DBw4UISEhQq1Wi0qVKom2bdvKgcnSOhztnNzpztDkjOekR48eQq/XC7VaLSpXrix69OhhNh6RM54TIYTYtGmTqF+/vtBoNCI0NFQsWLDAbL0z/q0tSyohhFDmNy4iIiIi+8E+TUREREQWYGgiIiIisgBDExEREZEFGJqIiIiILMDQRERERGQBhiYiIiIiCzA0EREREVmAoYmIqAzt2bMHKpWq2LPIiMj+cHBLIiIradOmDZ544gnMnDlTXpaXl4erV68iMDAQKpVKueKI6KHxlyYiovu4/UGlpaVWq6HT6RiYiBwAQxMR2Yzs7GzExMTAy8sLer0eM2bMQJs2bTBixAgAQG5uLt566y1UrlwZXl5eCA8Px549e+TPL1myBH5+ftixYwfq1KkDb29vtG/fHpcvXzbbz6JFi1CnTh24u7sjNDQUc+fOldedO3cOKpUKq1atQuvWreHu7o5ly5bhypUr6NWrFypXrgxPT080aNAAK1askD/Xv39/7N27F7NmzYJKpYJKpcK5c+dKvDy3bt061KtXDxqNBtWqVcP06dPN6qtWrRomT56MgQMHwsfHB1WrVsWCBQvk9Xl5eRg6dCj0ej3c3d0REhKCuLg4K/wXIKJ7UvbRd0RE/3rllVdESEiI+OGHH0RKSoro0qWL8PHxkR9W+8orr4j//Oc/Yt++feLMmTNi2rRpQqPRiFOnTgkhhFi8eLEoV66ciIyMFAcPHhRJSUmiTp06onfv3vI+vv32W6HX68W6devE2bNnxbp160T58uXFkiVLhBBCpKWlCQCiWrVqcps///xT/PHHH2LatGni8OHD4vfffxdffPGFcHV1FYmJiUII6UnwERERYtCgQeLy5cvi8uXLoqCgQH549bVr14QQQhw6dEi4uLiIDz/8UKSmporFixcLDw8PswfDhoSEiPLly4s5c+aI06dPi7i4OOHi4iJOnjwphBBi2rRpIjg4WOzbt0+cO3dO/Pjjj2L58uVl/F+HiBiaiMgmGI1GUa5cObFmzRp5WVZWlvD09BTDhw8X58+fF66uruLSpUtmn2vbtq0YO3asEEIKTQDEmTNn5PVz5swRgYGB8vsaNWoUCxgfffSRiIiIEEL8G5pmzpx535qjo6PF6NGj5fetW7eWA16RO0NT7969xbPPPmvW5u233xZ169aV34eEhIiXX35Zfm8ymURAQICYN2+eEEKIN998UzzzzDPCZDLdt0Yish43RX/mIiL6x9mzZ5Gfn49mzZrJy3x9fVG7dm0AQEpKCgoLC1GrVi2zz+Xm5qJChQrye09PT9SoUUN+r9frkZmZCQDIycnB77//jtjYWAwaNEhuU1BQAF9fX7PthoWFmb0vLCzE5MmTsXr1aly6dAl5eXnIzc2Fp6dnqY7zt99+Q6dOncyWtWjRAjNnzkRhYSFcXV0BAA0bNpTXq1Qq6HQ6+Tj69++PZ599FrVr10b79u3RsWNHtGvXrlR1EFHpMTQRkV24fv06XF1dkZSUJAeLIt7e3vJ8uXLlzNapVCqIf24Svn79OgBg4cKFCA8PN2t35za9vLzM3k+bNg2zZs3CzJkz0aBBA3h5eWHEiBHIy8t7uAO7i5KOw2QyAQCaNGmCtLQ0bNu2DT/88AO6d++OyMhIrF27tkxqISIJQxMR2YTHHnsM5cqVw8GDB1G1alUAgMFgwKlTp/DUU0+hcePGKCwsRGZmJlq1avVA+wgMDERQUBDOnj2LmJiYUn12//796NSpE15++WUAgMlkwqlTp1C3bl25jVqtRmFh4T23U6dOHezfv7/YtmvVqlUsuN2LVqtFjx490KNHD7z44oto3749rl69ivLly5fiqIioNBiaiMgm+Pj4oF+/fnj77bdRvnx5BAQEYOLEiXBxcYFKpUKtWrUQExODvn37Yvr06WjcuDH++usv7Ny5Ew0bNkR0dLRF+/nggw8wbNgw+Pr6on379sjNzcWhQ4dw7do1jBo16q6fe/zxx7F27Vr8/PPP8Pf3x+eff46MjAyz0FStWjUkJibi3Llz8Pb2LjHAjB49Gk8++SQ++ugj9OjRAwkJCZg9e7bZHXz38/nnn0Ov16Nx48ZwcXHBmjVroNPp4OfnZ/E2iKj0OOQAEdmMzz//HBEREejYsSMiIyPRokULeWgAAFi8eDH69u2L0aNHo3bt2ujcubPZL1OWeOWVV7Bo0SIsXrwYDRo0QOvWrbFkyRJUr179np97//330aRJE0RFRaFNmzbQ6XTo3LmzWZu33noLrq6uqFu3LipVqoQLFy4U206TJk2wevVqrFy5EvXr18eECRPw4Ycfon///hYfg4+PD6ZOnYqwsDA8+eSTOHfuHLZu3QoXF/5JJypLHBGciGxWTk4OKleujOnTpyM2NlbpcojIyfHyHBHZjMOHD+PkyZNo1qwZDAYDPvzwQwAodrcZEZESGJqIyKZ89tlnSE1NhVqtRtOmTfHjjz+iYsWKSpdFRMTLc0RERESWYK9BIiIiIgswNBERERFZgKGJiIiIyAIMTUREREQWYGgiIiIisgBDExEREZEFGJqIiIiILMDQRERERGQBhiYiIiIiC/w/imCgHSAz/4gAAAAASUVORK5CYII=",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "print(f\"Best solution: {best_solution}\")\n",
+    "print(f\"Solution fitness: {best_fitness}\\n\")\n",
+    "search.display_report()"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.4"
+  },
+  "orig_nbformat": 4,
+  "vscode": {
+   "interpreter": {
+    "hash": "e7370f93d1d0cde622a1f8e1c04877d8463912d04d973331ad4851f04de6915a"
+   }
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}


Test patch:
diff --git a/tests/test_benchmarks.py b/tests/test_benchmarks.py
index 568decf..ede75e5 100644
--- a/tests/test_benchmarks.py
+++ b/tests/test_benchmarks.py
@@ -1,7 +1,11 @@
 import pytest
 
-from metaheuristic_designer.Initializers import UniformVectorInitializer
+from metaheuristic_designer.initializers import UniformVectorInitializer
 from metaheuristic_designer.benchmarks import *
+import metaheuristic_designer as mhd
+
+mhd.reset_seed(0)
+
 
 benchmark_functions = [
     MaxOnes,
@@ -22,19 +26,23 @@
     ExpandedGriewankPlusRosenbrock,
     ExpandedShafferF6,
     SumPowell,
-    N4XinSheYang
+    N4XinSheYang,
 ]
 
+
 @pytest.mark.parametrize("vecsize", [2, 5, 10, 20, 30])
 @pytest.mark.parametrize("bench_class", benchmark_functions)
 def test_real_benchmarks(vecsize, bench_class):
     objfunc = bench_class(vecsize)
-    pop_init = UniformVectorInitializer(vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=100)
+    pop_init = UniformVectorInitializer(
+        vecsize, objfunc.low_lim, objfunc.up_lim, pop_size=100
+    )
 
     population = pop_init.generate_population(objfunc)
     for indiv in population:
-        objfunc.fitness(indiv, adjusted = False)
-        objfunc.fitness(indiv, adjusted = True)
+        objfunc.fitness(indiv, adjusted=False)
+        objfunc.fitness(indiv, adjusted=True)
+
 
 @pytest.mark.parametrize("bench_class", benchmark_functions)
 def test_real_benchmarks(bench_class):
@@ -43,4 +51,4 @@ def test_real_benchmarks(bench_class):
 
     population = pop_init.generate_population(objfunc)
     for indiv in population:
-        objfunc.repair_solution(indiv.genotype)
\ No newline at end of file
+        objfunc.repair_solution(indiv.genotype)
diff --git a/tests/test_bin_ops.py b/tests/test_bin_ops.py
index 5add6af..edc3df0 100644
--- a/tests/test_bin_ops.py
+++ b/tests/test_bin_ops.py
@@ -2,21 +2,34 @@
 
 import numpy as np
 from metaheuristic_designer import Individual
-from metaheuristic_designer.Operators import OperatorBinary, bin_ops_map
+from metaheuristic_designer.operators import OperatorBinary, bin_ops_map
 from metaheuristic_designer.benchmarks.benchmark_funcs import MaxOnes
-from metaheuristic_designer.Initializers import UniformVectorInitializer
+from metaheuristic_designer.initializers import UniformVectorInitializer
+import metaheuristic_designer as mhd
+
+mhd.reset_seed(0)
 
 pop_size = 100
 
-example_populaton1 = [Individual(MaxOnes(3), np.random.random(3) > 0.5) for i in range(pop_size)]
-example_populaton2 = [Individual(MaxOnes(20), np.random.random(20) > 0.5) for i in range(pop_size)]
-example_populaton3 = [Individual(MaxOnes(100), np.random.random(100) > 0.5) for i in range(pop_size)]
+example_populaton1 = [
+    Individual(MaxOnes(3), np.random.random(3) > 0.5) for i in range(pop_size)
+]
+example_populaton2 = [
+    Individual(MaxOnes(20), np.random.random(20) > 0.5) for i in range(pop_size)
+]
+example_populaton3 = [
+    Individual(MaxOnes(100), np.random.random(100) > 0.5) for i in range(pop_size)
+]
+
 
 def test_errors():
     with pytest.raises(ValueError):
         operator = OperatorBinary("not_a_method")
 
-@pytest.mark.parametrize("population", [example_populaton1, example_populaton2, example_populaton3])
+
+@pytest.mark.parametrize(
+    "population", [example_populaton1, example_populaton2, example_populaton3]
+)
 @pytest.mark.parametrize("op_method", bin_ops_map.keys())
 def test_basic_working(population, op_method):
     pop_init = UniformVectorInitializer(population[0].genotype.size, 0, 1, pop_size)
@@ -24,4 +37,4 @@ def test_basic_working(population, op_method):
 
     indiv = population[0]
     new_indiv = operator.evolve(indiv, population, indiv.objfunc, indiv, pop_init)
-    assert type(new_indiv.genotype) == np.ndarray
\ No newline at end of file
+    assert type(new_indiv.genotype) == np.ndarray
diff --git a/tests/test_encodings.py b/tests/test_encodings.py
index d533dd5..71a2ec4 100644
--- a/tests/test_encodings.py
+++ b/tests/test_encodings.py
@@ -2,15 +2,25 @@
 
 import numpy as np
 from metaheuristic_designer import Individual
-from metaheuristic_designer.Encodings import MatrixEncoding, ImageEncoding, DefaultEncoding, TypeCastEncoding
+from metaheuristic_designer.encodings import (
+    MatrixEncoding,
+    ImageEncoding,
+    DefaultEncoding,
+    TypeCastEncoding,
+)
+import metaheuristic_designer as mhd
+
+mhd.reset_seed(0)
+
 
 @pytest.mark.parametrize(
-    "genotype, phenotype", [
-        (1,1),
-        ([1,2,3],[1,2,3]),
-        (np.array([1,2,3,4]), np.array([1,2,3,4])),
-        ([2,[3,4],[[5,6],[7,8],9]], [2,[3,4],[[5,6],[7,8],9]])
-    ]
+    "genotype, phenotype",
+    [
+        (1, 1),
+        ([1, 2, 3], [1, 2, 3]),
+        (np.array([1, 2, 3, 4]), np.array([1, 2, 3, 4])),
+        ([2, [3, 4], [[5, 6], [7, 8], 9]], [2, [3, 4], [[5, 6], [7, 8], 9]]),
+    ],
 )
 def test_default(genotype, phenotype):
     encoding = DefaultEncoding()
@@ -20,36 +30,68 @@ def test_default(genotype, phenotype):
         np.testing.assert_array_equal(encoding.encode(phenotype), genotype)
     else:
         assert encoding.decode(genotype) == phenotype
-        assert encoding.encode(phenotype) == genotype 
+        assert encoding.encode(phenotype) == genotype
 
 
 @pytest.mark.parametrize(
-    "genotype, phenotype, type_in, type_out", [
-        (np.array([1,2,6,4,6], dtype=int), np.array([1,2,6,4,6], dtype=int), int, int),
-        (np.array([1.5,2.2,6.1,4.4,6.2], dtype=float), np.array([1.5,2.2,6.1,4.4,6.2], dtype=float), float, float),
-        (np.array([1.5,2.2,6.1,4.4,6.2], dtype=float), np.array([1,2,6,4,6], dtype=int), float, int),
-        (np.array([1,2,6,4,6], dtype=int), np.array([2.0,2.0,6.0,4.0,6.0], dtype=float), int, float),
-        (np.array([0,1,1,0,0], dtype=int), np.array([False, True, True, False, False], dtype=bool), int, bool),
-        (np.array([False, True, True, False, False], dtype=bool), np.array([0,1,1,0,0], dtype=int), bool, int)
-    ]
+    "genotype, phenotype, type_in, type_out",
+    [
+        (
+            np.array([1, 2, 6, 4, 6], dtype=int),
+            np.array([1, 2, 6, 4, 6], dtype=int),
+            int,
+            int,
+        ),
+        (
+            np.array([1.5, 2.2, 6.1, 4.4, 6.2], dtype=float),
+            np.array([1.5, 2.2, 6.1, 4.4, 6.2], dtype=float),
+            float,
+            float,
+        ),
+        (
+            np.array([1.5, 2.2, 6.1, 4.4, 6.2], dtype=float),
+            np.array([1, 2, 6, 4, 6], dtype=int),
+            float,
+            int,
+        ),
+        (
+            np.array([1, 2, 6, 4, 6], dtype=int),
+            np.array([2.0, 2.0, 6.0, 4.0, 6.0], dtype=float),
+            int,
+            float,
+        ),
+        (
+            np.array([0, 1, 1, 0, 0], dtype=int),
+            np.array([False, True, True, False, False], dtype=bool),
+            int,
+            bool,
+        ),
+        (
+            np.array([False, True, True, False, False], dtype=bool),
+            np.array([0, 1, 1, 0, 0], dtype=int),
+            bool,
+            int,
+        ),
+    ],
 )
 def test_typecast(genotype, phenotype, type_in, type_out):
     encoding = TypeCastEncoding(type_in, type_out)
 
     assert encoding.decode(genotype).dtype is np.dtype(type_out)
     assert encoding.encode(phenotype).dtype is np.dtype(type_in)
-    
 
 
+example = np.random.random([30, 40])
+
 
-example = np.random.random([30,40])
 @pytest.mark.parametrize(
-    "genotype, phenotype", [
-        (np.array([1,2,3,4]), np.array([[1,2],[3,4]])),
-        (np.ones([100]), np.ones([10,10])),
-        (np.ones([200]), np.ones([10,20])),
+    "genotype, phenotype",
+    [
+        (np.array([1, 2, 3, 4]), np.array([[1, 2], [3, 4]])),
+        (np.ones([100]), np.ones([10, 10])),
+        (np.ones([200]), np.ones([10, 20])),
         (example.flatten(), example),
-    ]
+    ],
 )
 def test_matrix(genotype, phenotype):
     encoding = MatrixEncoding(phenotype.shape)
@@ -58,14 +100,17 @@ def test_matrix(genotype, phenotype):
     np.testing.assert_array_equal(encoding.encode(phenotype), genotype)
 
 
-example_img1 = np.random.randint(0,256,[30,40,1])
+example_img1 = np.random.randint(0, 256, [30, 40, 1])
+
+
 @pytest.mark.parametrize(
-    "genotype, phenotype", [
-        (np.array([1,2,3,4]), np.array([[[1],[2]],[[3],[4]]])),
-        (np.ones([100]), np.ones([10,10,1])),
-        (np.ones([200]), np.ones([10,20,1])),
+    "genotype, phenotype",
+    [
+        (np.array([1, 2, 3, 4]), np.array([[[1], [2]], [[3], [4]]])),
+        (np.ones([100]), np.ones([10, 10, 1])),
+        (np.ones([200]), np.ones([10, 20, 1])),
         (example_img1.flatten(), example_img1),
-    ]
+    ],
 )
 def test_gray_img(genotype, phenotype):
     encoding = ImageEncoding(phenotype.shape[:2], color=False)
@@ -74,18 +119,23 @@ def test_gray_img(genotype, phenotype):
     np.testing.assert_array_equal(encoding.encode(phenotype), genotype)
 
 
-example_img2 = np.random.randint(0,256,[30,40,3])
+example_img2 = np.random.randint(0, 256, [30, 40, 3])
+
+
 @pytest.mark.parametrize(
-    "genotype, phenotype", [
-        (np.array([1,2,3,4,5,6,7,8,9,10,11,12]), np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])),
-        (np.ones([300]), np.ones([10,10,3])),
-        (np.ones([600]), np.ones([10,20,3])),
+    "genotype, phenotype",
+    [
+        (
+            np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]),
+            np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]),
+        ),
+        (np.ones([300]), np.ones([10, 10, 3])),
+        (np.ones([600]), np.ones([10, 20, 3])),
         (example_img2.flatten(), example_img2),
-    ]
+    ],
 )
 def test_rgb_img(genotype, phenotype):
     encoding = ImageEncoding(phenotype.shape[:2], color=True)
 
     np.testing.assert_array_equal(encoding.decode(genotype), phenotype)
     np.testing.assert_array_equal(encoding.encode(phenotype), genotype)
-    
diff --git a/tests/test_initializers.py b/tests/test_initializers.py
index 0a1452a..7f42ce1 100644
--- a/tests/test_initializers.py
+++ b/tests/test_initializers.py
@@ -2,24 +2,26 @@
 
 import numpy as np
 from metaheuristic_designer import Individual
-from metaheuristic_designer.Initializers import *
+from metaheuristic_designer.initializers import *
+import metaheuristic_designer as mhd
+
+mhd.reset_seed(0)
 
 pop_size = 100
 
-example_populaton1 = [Individual(None, np.random.uniform(-100, 100, 3)) for i in range(100)]
-example_populaton2 = [Individual(None, np.random.uniform(-100, 100, 20)) for i in range(100)]
-example_populaton3 = [Individual(None, np.random.uniform(-100, 100, 100)) for i in range(100)]
+example_populaton1 = [
+    Individual(None, np.random.uniform(-100, 100, 3)) for i in range(100)
+]
+example_populaton2 = [
+    Individual(None, np.random.uniform(-100, 100, 20)) for i in range(100)
+]
+example_populaton3 = [
+    Individual(None, np.random.uniform(-100, 100, 100)) for i in range(100)
+]
 
 
 @pytest.mark.parametrize("vec_size", [1, 2, 10, 100])
-@pytest.mark.parametrize(
-    "min_val, max_val", [
-        (0,1),
-        (-1,1),
-        (-100,2),
-        (2, 24)
-    ]
-)
+@pytest.mark.parametrize("min_val, max_val", [(0, 1), (-1, 1), (-100, 2), (2, 24)])
 @pytest.mark.parametrize("pop_size", [1, 10, 100])
 def test_uniform_vec_init(vec_size, min_val, max_val, pop_size):
     pop_init = UniformVectorInitializer(vec_size, min_val, max_val, pop_size)
@@ -29,12 +31,12 @@ def test_uniform_vec_init(vec_size, min_val, max_val, pop_size):
         assert rand_inidv.genotype.max() <= max_val
         assert rand_inidv.genotype.min() >= min_val
         assert rand_inidv.genotype.size == vec_size
-    
+
         rand_inidv = pop_init.generate_individual(None)
         assert rand_inidv.genotype.max() <= max_val
         assert rand_inidv.genotype.min() >= min_val
         assert rand_inidv.genotype.size == vec_size
-    
+
     rand_pop = pop_init.generate_population(None)
     assert len(rand_pop) == pop_size
 
@@ -45,12 +47,13 @@ def test_uniform_vec_init(vec_size, min_val, max_val, pop_size):
 
 
 @pytest.mark.parametrize(
-    "min_val, max_val", [
+    "min_val, max_val",
+    [
         (np.zeros(10), np.ones(10)),
         (np.full(10, -1), np.ones(10)),
         (np.full(10, -100), np.full(10, 2)),
-        (np.full(10, 2), np.full(10, 24))
-    ]
+        (np.full(10, 2), np.full(10, 24)),
+    ],
 )
 @pytest.mark.parametrize("pop_size", [1, 10, 100])
 def test_uniform_arr_param_vec_init(min_val, max_val, pop_size):
@@ -61,12 +64,12 @@ def test_uniform_arr_param_vec_init(min_val, max_val, pop_size):
         assert np.all(rand_inidv.genotype <= max_val)
         assert np.all(rand_inidv.genotype >= min_val)
         assert rand_inidv.genotype.size == 10
-    
+
         rand_inidv = pop_init.generate_individual(None)
         assert np.all(rand_inidv.genotype <= max_val)
         assert np.all(rand_inidv.genotype >= min_val)
         assert rand_inidv.genotype.size == 10
-    
+
     rand_pop = pop_init.generate_population(None)
     assert len(rand_pop) == pop_size
 
@@ -86,20 +89,13 @@ def test_uniform_int_vec_init():
 def test_uniform_err_vec_init():
     with pytest.raises(ValueError):
         UniformVectorInitializer(10, np.zeros(10), np.ones(9))
-    
+
     with pytest.raises(ValueError):
         UniformVectorInitializer(10, np.zeros(9), np.ones(10))
 
 
 @pytest.mark.parametrize("list_size", [1, 2, 10, 100])
-@pytest.mark.parametrize(
-    "min_val, max_val", [
-        (0,1),
-        (-1,1),
-        (-100,2),
-        (2, 24)
-    ]
-)
+@pytest.mark.parametrize("min_val, max_val", [(0, 1), (-1, 1), (-100, 2), (2, 24)])
 @pytest.mark.parametrize("pop_size", [1, 10, 100])
 def test_uniform_list_init(list_size, min_val, max_val, pop_size):
     pop_init = UniformListInitializer(list_size, min_val, max_val, pop_size)
@@ -109,12 +105,12 @@ def test_uniform_list_init(list_size, min_val, max_val, pop_size):
         assert max(rand_inidv.genotype) <= max_val
         assert min(rand_inidv.genotype) >= min_val
         assert len(rand_inidv.genotype) == list_size
-    
+
         rand_inidv = pop_init.generate_individual(None)
         assert max(rand_inidv.genotype) <= max_val
         assert min(rand_inidv.genotype) >= min_val
         assert len(rand_inidv.genotype) == list_size
-    
+
     rand_pop = pop_init.generate_population(None)
     assert len(rand_pop) == pop_size
 
@@ -126,12 +122,13 @@ def test_uniform_list_init(list_size, min_val, max_val, pop_size):
 
 @pytest.mark.parametrize("vec_size", [1, 2, 10, 100])
 @pytest.mark.parametrize(
-    "mean_val, std_val", [
-        (0,1),
-        (-1,1),
-        (-100,2),
+    "mean_val, std_val",
+    [
+        (0, 1),
+        (-1, 1),
+        (-100, 2),
         (2, 24),
-    ]
+    ],
 )
 @pytest.mark.parametrize("pop_size", [1, 10, 100])
 def test_gaussian_vec_init(vec_size, mean_val, std_val, pop_size):
@@ -140,10 +137,10 @@ def test_gaussian_vec_init(vec_size, mean_val, std_val, pop_size):
     for _ in range(30):
         rand_inidv = pop_init.generate_random(None)
         assert rand_inidv.genotype.size == vec_size
-    
+
         rand_inidv = pop_init.generate_individual(None)
         assert rand_inidv.genotype.size == vec_size
-    
+
     rand_pop = pop_init.generate_population(None)
     assert len(rand_pop) == pop_size
 
@@ -152,14 +149,7 @@ def test_gaussian_vec_init(vec_size, mean_val, std_val, pop_size):
 
 
 @pytest.mark.parametrize("list_size", [1, 2, 10, 100])
-@pytest.mark.parametrize(
-    "mean_val, std_val", [
-        (0,1),
-        (-1,1),
-        (-100,2),
-        (2, 24)
-    ]
-)
+@pytest.mark.parametrize("mean_val, std_val", [(0, 1), (-1, 1), (-100, 2), (2, 24)])
 @pytest.mark.parametrize("pop_size", [1, 10, 100])
 def test_gaussian_list_init(list_size, mean_val, std_val, pop_size):
     pop_init = GaussianListInitializer(list_size, mean_val, std_val, pop_size)
@@ -167,10 +157,10 @@ def test_gaussian_list_init(list_size, mean_val, std_val, pop_size):
     for _ in range(30):
         rand_inidv = pop_init.generate_random(None)
         assert len(rand_inidv.genotype) == list_size
-    
+
         rand_inidv = pop_init.generate_individual(None)
         assert len(rand_inidv.genotype) == list_size
-    
+
     rand_pop = pop_init.generate_population(None)
     assert len(rand_pop) == pop_size
 
@@ -179,12 +169,13 @@ def test_gaussian_list_init(list_size, mean_val, std_val, pop_size):
 
 
 @pytest.mark.parametrize(
-    "min_val, max_val", [
+    "min_val, max_val",
+    [
         (np.zeros(10), np.ones(10)),
         (np.full(10, -1), np.ones(10)),
         (np.full(10, -100), np.full(10, 2)),
-        (np.full(10, 2), np.full(10, 24))
-    ]
+        (np.full(10, 2), np.full(10, 24)),
+    ],
 )
 @pytest.mark.parametrize("pop_size", [1, 10, 100])
 def test_gaussian_arr_param_vec_init(min_val, max_val, pop_size):
@@ -193,10 +184,10 @@ def test_gaussian_arr_param_vec_init(min_val, max_val, pop_size):
     for _ in range(30):
         rand_inidv = pop_init.generate_random(None)
         assert rand_inidv.genotype.size == 10
-    
+
         rand_inidv = pop_init.generate_individual(None)
         assert rand_inidv.genotype.size == 10
-    
+
     rand_pop = pop_init.generate_population(None)
     assert len(rand_pop) == pop_size
 
@@ -208,15 +199,18 @@ def test_gaussian_int_vec_init():
     pop_init = GaussianVectorInitializer(200, 0, 1, 100, dtype=int)
     rand_inidv = pop_init.generate_random(None)
 
+
 def test_gaussian_err_vec_init():
     with pytest.raises(ValueError):
         GaussianVectorInitializer(10, np.zeros(10), np.ones(9))
-    
+
     with pytest.raises(ValueError):
         GaussianVectorInitializer(10, np.zeros(9), np.ones(10))
 
 
-@pytest.mark.parametrize("population", [example_populaton1, example_populaton2, example_populaton3])
+@pytest.mark.parametrize(
+    "population", [example_populaton1, example_populaton2, example_populaton3]
+)
 def test_direct_initializer(population):
     default_pop_init = GaussianVectorInitializer(population[0].genotype.size, -100, 100)
     pop_init = DirectInitializer(default_pop_init, population)
@@ -226,10 +220,10 @@ def test_direct_initializer(population):
     for _ in range(30):
         rand_inidv = pop_init.generate_random(None)
         assert rand_inidv.id not in ids_in_pop
-    
+
         rand_inidv = pop_init.generate_individual(None)
         assert rand_inidv.id in ids_in_pop
-    
+
     rand_pop = pop_init.generate_population(None)
     assert len(rand_pop) == pop_size
 
@@ -237,7 +231,9 @@ def test_direct_initializer(population):
         assert indiv.id in ids_in_pop
 
 
-@pytest.mark.parametrize("population", [example_populaton1, example_populaton2, example_populaton3])
+@pytest.mark.parametrize(
+    "population", [example_populaton1, example_populaton2, example_populaton3]
+)
 def test_seed_prob_initializer(population):
     default_pop_init = GaussianVectorInitializer(population[0].genotype.size, -100, 100)
     pop_init = SeedProbInitializer(default_pop_init, population)
@@ -247,13 +243,15 @@ def test_seed_prob_initializer(population):
     for _ in range(30):
         rand_inidv = pop_init.generate_random(None)
         assert rand_inidv.id not in ids_in_pop
-    
+
         rand_inidv = pop_init.generate_individual(None)
-    
+
     rand_pop = pop_init.generate_population(None)
 
 
-@pytest.mark.parametrize("population", [example_populaton1, example_populaton2, example_populaton3])
+@pytest.mark.parametrize(
+    "population", [example_populaton1, example_populaton2, example_populaton3]
+)
 def test_seed_determ_initializer(population):
     default_pop_init = GaussianVectorInitializer(population[0].genotype.size, -100, 100)
     pop_init = SeedDetermInitializer(default_pop_init, population, 4)
@@ -263,13 +261,15 @@ def test_seed_determ_initializer(population):
     for _ in range(30):
         rand_inidv = pop_init.generate_random(None)
         assert rand_inidv.id not in ids_in_pop
-    
+
         rand_inidv = pop_init.generate_individual(None)
-    
+
     rand_pop = pop_init.generate_population(None)
 
 
-@pytest.mark.parametrize("population", [example_populaton1, example_populaton2, example_populaton3])
+@pytest.mark.parametrize(
+    "population", [example_populaton1, example_populaton2, example_populaton3]
+)
 def test_seed_determ_null_param_initializer(population):
     default_pop_init = GaussianVectorInitializer(population[0].genotype.size, -100, 100)
     pop_init = SeedDetermInitializer(default_pop_init, population)
@@ -284,10 +284,10 @@ def test_lambda_init(vec_size, pop_size):
     for _ in range(30):
         rand_inidv = pop_init.generate_random(None)
         assert rand_inidv.genotype.size == vec_size
-    
+
         rand_inidv = pop_init.generate_individual(None)
         assert rand_inidv.genotype.size == vec_size
-    
+
     rand_pop = pop_init.generate_population(None)
     assert len(rand_pop) == pop_size
 
@@ -303,10 +303,10 @@ def test_perm_init(vec_size, pop_size):
     for _ in range(30):
         rand_inidv = pop_init.generate_random(None)
         assert np.all(np.isin(np.arange(vec_size), rand_inidv.genotype))
-    
+
         rand_inidv = pop_init.generate_individual(None)
         assert np.all(np.isin(np.arange(vec_size), rand_inidv.genotype))
-    
+
     rand_pop = pop_init.generate_population(None)
     assert len(rand_pop) == pop_size
 
diff --git a/tests/test_int_ops.py b/tests/test_int_ops.py
index f1ca563..bf23449 100644
--- a/tests/test_int_ops.py
+++ b/tests/test_int_ops.py
@@ -2,21 +2,34 @@
 
 import numpy as np
 from metaheuristic_designer import Individual
-from metaheuristic_designer.Operators import OperatorInt, int_ops_map
+from metaheuristic_designer.operators import OperatorInt, int_ops_map
 from metaheuristic_designer.benchmarks.benchmark_funcs import Sphere
-from metaheuristic_designer.Initializers import UniformVectorInitializer
+from metaheuristic_designer.initializers import UniformVectorInitializer
+import metaheuristic_designer as mhd
+
+mhd.reset_seed(0)
 
 pop_size = 100
 
-example_populaton1 = [Individual(Sphere(3), np.random.randint(-100, 100, 3)) for i in range(pop_size)]
-example_populaton2 = [Individual(Sphere(20), np.random.randint(-100, 100, 20)) for i in range(pop_size)]
-example_populaton3 = [Individual(Sphere(100), np.random.randint(-100, 100, 100)) for i in range(pop_size)]
+example_populaton1 = [
+    Individual(Sphere(3), np.random.randint(-100, 100, 3)) for i in range(pop_size)
+]
+example_populaton2 = [
+    Individual(Sphere(20), np.random.randint(-100, 100, 20)) for i in range(pop_size)
+]
+example_populaton3 = [
+    Individual(Sphere(100), np.random.randint(-100, 100, 100)) for i in range(pop_size)
+]
+
 
 def test_errors():
     with pytest.raises(ValueError):
         operator = OperatorInt("not_a_method")
 
-@pytest.mark.parametrize("population", [example_populaton1, example_populaton2, example_populaton3])
+
+@pytest.mark.parametrize(
+    "population", [example_populaton1, example_populaton2, example_populaton3]
+)
 @pytest.mark.parametrize("op_method", int_ops_map.keys())
 def test_basic_working(population, op_method):
     pop_init = UniformVectorInitializer(population[0].genotype.size, 0, 1, pop_size)
@@ -24,4 +37,4 @@ def test_basic_working(population, op_method):
 
     indiv = population[0]
     new_indiv = operator.evolve(indiv, population, indiv.objfunc, indiv, pop_init)
-    assert type(new_indiv.genotype) == np.ndarray
\ No newline at end of file
+    assert type(new_indiv.genotype) == np.ndarray
diff --git a/tests/test_meta_ops.py b/tests/test_meta_ops.py
index f3d4bd8..1db1acf 100644
--- a/tests/test_meta_ops.py
+++ b/tests/test_meta_ops.py
@@ -2,81 +2,138 @@
 
 import numpy as np
 from metaheuristic_designer import Individual
-from metaheuristic_designer.Operators import OperatorMeta, meta_ops_map, OperatorReal
+from metaheuristic_designer.operators import OperatorMeta, meta_ops_map, OperatorReal
 from metaheuristic_designer.benchmarks.benchmark_funcs import Sphere
-from metaheuristic_designer.Initializers import UniformVectorInitializer
+from metaheuristic_designer.initializers import UniformVectorInitializer
+import metaheuristic_designer as mhd
+
+mhd.reset_seed(0)
 
 pop_size = 100
 
 example_individual = Individual(None, np.zeros(100))
 pop_init = UniformVectorInitializer(100, 0, 1, pop_size)
 
+
 def test_errors():
     with pytest.raises(ValueError):
         operator = OperatorMeta("not_a_method", [])
 
+
 @pytest.mark.parametrize("indiv", [example_individual])
-@pytest.mark.parametrize("op_list, args", [
-    ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 2})], {"p": 0.2}),
-    ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 2})], {"weights": [0.2, 0.8]}),
-    ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 2}), OperatorReal("dummy", {"F": 3})], {"weights": [0.2, 0.4, 0.4]})
-])
+@pytest.mark.parametrize(
+    "op_list, args",
+    [
+        (
+            [OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 2})],
+            {"p": 0.2},
+        ),
+        (
+            [OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 2})],
+            {"weights": [0.2, 0.8]},
+        ),
+        (
+            [
+                OperatorReal("dummy", {"F": 1}),
+                OperatorReal("dummy", {"F": 2}),
+                OperatorReal("dummy", {"F": 3}),
+            ],
+            {"weights": [0.2, 0.4, 0.4]},
+        ),
+    ],
+)
 def test_branch_op(indiv, op_list, args):
     operator = OperatorMeta("branch", op_list, args)
 
-    new_indiv = operator.evolve(example_individual, [example_individual], None, example_individual, pop_init)
+    new_indiv = operator.evolve(
+        example_individual, [example_individual], None, example_individual, pop_init
+    )
     assert type(new_indiv.genotype) == np.ndarray
     assert np.all(new_indiv.genotype == new_indiv.genotype[0])
     assert new_indiv.genotype[0] != 0
 
 
 @pytest.mark.parametrize("indiv", [example_individual])
-@pytest.mark.parametrize("op_list, expected_val", [
-    ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 2})], 2),
-    ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 23})], 23),
-    ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 2}), OperatorReal("dummy", {"F": 3})], 3)
-])
+@pytest.mark.parametrize(
+    "op_list, expected_val",
+    [
+        ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 2})], 2),
+        ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 23})], 23),
+        (
+            [
+                OperatorReal("dummy", {"F": 1}),
+                OperatorReal("dummy", {"F": 2}),
+                OperatorReal("dummy", {"F": 3}),
+            ],
+            3,
+        ),
+    ],
+)
 def test_sequence_op(indiv, op_list, expected_val):
     operator = OperatorMeta("sequence", op_list)
 
-    new_indiv = operator.evolve(example_individual, [example_individual], None, example_individual, pop_init)
+    new_indiv = operator.evolve(
+        example_individual, [example_individual], None, example_individual, pop_init
+    )
     assert type(new_indiv.genotype) == np.ndarray
     assert np.all(new_indiv.genotype == new_indiv.genotype[0])
     assert new_indiv.genotype[0] == expected_val
 
 
 @pytest.mark.parametrize("indiv", [example_individual])
-@pytest.mark.parametrize("op_list, values", [
-    ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 2})], (1,2)),
-    ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 23})], (1,23)),
-    ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 2}), OperatorReal("dummy", {"F": 3})], (1,2,3)),
-])
+@pytest.mark.parametrize(
+    "op_list, values",
+    [
+        ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 2})], (1, 2)),
+        ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 23})], (1, 23)),
+        (
+            [
+                OperatorReal("dummy", {"F": 1}),
+                OperatorReal("dummy", {"F": 2}),
+                OperatorReal("dummy", {"F": 3}),
+            ],
+            (1, 2, 3),
+        ),
+    ],
+)
 def test_pick_op(indiv, op_list, values):
     operator = OperatorMeta("pick", op_list, {})
     for i, _ in enumerate(op_list):
         operator.chosen_idx = i
-        new_indiv = operator.evolve(example_individual, [example_individual], None, example_individual, pop_init)
+        new_indiv = operator.evolve(
+            example_individual, [example_individual], None, example_individual, pop_init
+        )
         assert type(new_indiv.genotype) == np.ndarray
         assert np.all(new_indiv.genotype == values[i])
 
 
 @pytest.mark.parametrize("indiv", [example_individual])
-@pytest.mark.parametrize("op_list, values", [
-    ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 2})], (1,2)),
-    ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 23})], (1,23)),
-    ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 2}), OperatorReal("dummy", {"F": 3})], (1,2,3))
-])
-@pytest.mark.parametrize("mask",[
-    np.zeros(100),
-    np.ones(100),
-    (np.arange(100) > 50).astype(int)
-])
+@pytest.mark.parametrize(
+    "op_list, values",
+    [
+        ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 2})], (1, 2)),
+        ([OperatorReal("dummy", {"F": 1}), OperatorReal("dummy", {"F": 23})], (1, 23)),
+        (
+            [
+                OperatorReal("dummy", {"F": 1}),
+                OperatorReal("dummy", {"F": 2}),
+                OperatorReal("dummy", {"F": 3}),
+            ],
+            (1, 2, 3),
+        ),
+    ],
+)
+@pytest.mark.parametrize(
+    "mask", [np.zeros(100), np.ones(100), (np.arange(100) > 50).astype(int)]
+)
 def test_split_op(indiv, op_list, values, mask):
     operator = OperatorMeta("split", op_list, {"mask": mask})
 
-    new_indiv = operator.evolve(example_individual, [example_individual], None, example_individual, pop_init)
+    new_indiv = operator.evolve(
+        example_individual, [example_individual], None, example_individual, pop_init
+    )
     assert type(new_indiv.genotype) == np.ndarray
 
     for idx, val in enumerate(values):
         if np.any(mask == idx):
-            assert np.all(new_indiv.genotype[mask==idx] == val)
\ No newline at end of file
+            assert np.all(new_indiv.genotype[mask == idx] == val)
diff --git a/tests/test_parent_sel.py b/tests/test_parent_sel.py
index 66cdb80..e7282f0 100644
--- a/tests/test_parent_sel.py
+++ b/tests/test_parent_sel.py
@@ -1,6 +1,10 @@
 import pytest
 
-from metaheuristic_designer import Individual, ParentSelection
+from metaheuristic_designer import Individual
+from metaheuristic_designer.selectionMethods import ParentSelection
+import metaheuristic_designer as mhd
+
+mhd.reset_seed(0)
 
 pop_size = 100
 
@@ -13,10 +17,12 @@
 for idx, ind in enumerate(example_populaton2):
     example_populaton2[idx].fitness = 1
 
+
 def test_error():
     with pytest.raises(ValueError):
         surv_selection = ParentSelection("not_a_method")
 
+
 @pytest.mark.parametrize("population", [example_populaton1, example_populaton2])
 def test_nothing(population):
     parent_sel = ParentSelection("Nothing")
@@ -36,7 +42,7 @@ def test_best(population, amount):
 
     for indiv in parents:
         assert indiv.id in id_list
-    
+
     fit_list = [i.fitness for i in population]
     parent_fit_list = [i.fitness for i in parents]
     assert max(fit_list) == max(parent_fit_list)
@@ -45,7 +51,7 @@ def test_best(population, amount):
 @pytest.mark.parametrize("population", [example_populaton1, example_populaton2])
 @pytest.mark.parametrize("amount", [1, 5, 20])
 @pytest.mark.parametrize("prob", [0.01, 0.1, 0.2, 0.5])
-@pytest.mark.parametrize("dummy_var", range(10)) # makes the test repeat 10 times
+@pytest.mark.parametrize("dummy_var", range(10))  # makes the test repeat 10 times
 def test_tournament(population, amount, prob, dummy_var):
     parent_sel = ParentSelection("Tournament", {"amount": amount, "p": prob})
     parents = parent_sel.select(population)
@@ -64,15 +70,19 @@ def test_random(population, amount):
 
     for indiv in parents:
         assert indiv.id in id_list
-        
+
 
 @pytest.mark.parametrize("population", [example_populaton1, example_populaton2])
 @pytest.mark.parametrize("amount", [1, 5, 20])
-@pytest.mark.parametrize("method", ["FitnessProp", "SigmaScaling", "LinRank", "ExpRank"])
+@pytest.mark.parametrize(
+    "method", ["FitnessProp", "SigmaScaling", "LinRank", "ExpRank"]
+)
 @pytest.mark.parametrize("f", [0, 0.5, 1, 1.5, 2])
-@pytest.mark.parametrize("dummy_var", range(5)) # makes the test repeat 5 times
+@pytest.mark.parametrize("dummy_var", range(5))  # makes the test repeat 5 times
 def test_roullete(population, amount, method, f, dummy_var):
-    parent_sel = ParentSelection("Roulette", {"amount": amount, "method": method, "f": f})
+    parent_sel = ParentSelection(
+        "Roulette", {"amount": amount, "method": method, "f": f}
+    )
     parents = parent_sel.select(population)
     id_list = [i.id for i in population]
 
@@ -80,16 +90,17 @@ def test_roullete(population, amount, method, f, dummy_var):
         assert indiv.id in id_list
 
 
-
 @pytest.mark.parametrize("population", [example_populaton1, example_populaton2])
 @pytest.mark.parametrize("amount", [1, 5, 20])
-@pytest.mark.parametrize("method", ["FitnessProp", "SigmaScaling", "LinRank", "ExpRank"])
+@pytest.mark.parametrize(
+    "method", ["FitnessProp", "SigmaScaling", "LinRank", "ExpRank"]
+)
 @pytest.mark.parametrize("f", [0, 0.5, 1, 1.5, 2])
-@pytest.mark.parametrize("dummy_var", range(5)) # makes the test repeat 5 times
+@pytest.mark.parametrize("dummy_var", range(5))  # makes the test repeat 5 times
 def test_sus(population, amount, method, f, dummy_var):
     parent_sel = ParentSelection("Sus", {"amount": amount, "method": method, "f": f})
     parents = parent_sel.select(population)
     id_list = [i.id for i in population]
 
     for indiv in parents:
-        assert indiv.id in id_list
\ No newline at end of file
+        assert indiv.id in id_list
diff --git a/tests/test_perm_ops.py b/tests/test_perm_ops.py
index 2edc154..189a95f 100644
--- a/tests/test_perm_ops.py
+++ b/tests/test_perm_ops.py
@@ -2,9 +2,12 @@
 
 import numpy as np
 from metaheuristic_designer import Individual
-from metaheuristic_designer.Operators import OperatorPerm, perm_ops_map
+from metaheuristic_designer.operators import OperatorPerm, perm_ops_map
 from metaheuristic_designer.benchmarks.benchmark_funcs import Sphere
-from metaheuristic_designer.Initializers import PermInitializer
+from metaheuristic_designer.initializers import PermInitializer
+import metaheuristic_designer as mhd
+
+mhd.reset_seed(0)
 
 perm_ops = [i for i in perm_ops_map.keys()]
 
@@ -14,7 +17,10 @@
 example_populaton2 = [Individual(Sphere(20), np.arange(20)) for i in range(pop_size)]
 example_populaton3 = [Individual(Sphere(100), np.arange(100)) for i in range(pop_size)]
 
-@pytest.mark.parametrize("population", [example_populaton1, example_populaton2, example_populaton3])
+
+@pytest.mark.parametrize(
+    "population", [example_populaton1, example_populaton2, example_populaton3]
+)
 @pytest.mark.parametrize("op_method", perm_ops)
 def test_basic_working(population, op_method):
     pop_init = PermInitializer(population[0].genotype.size, pop_size)
@@ -22,4 +28,4 @@ def test_basic_working(population, op_method):
 
     indiv = population[0]
     new_indiv = operator.evolve(indiv, population, indiv.objfunc, indiv, pop_init)
-    assert type(new_indiv.genotype) == np.ndarray
\ No newline at end of file
+    assert type(new_indiv.genotype) == np.ndarray
diff --git a/tests/test_real_ops.py b/tests/test_real_ops.py
index 64c0409..f96528d 100644
--- a/tests/test_real_ops.py
+++ b/tests/test_real_ops.py
@@ -2,23 +2,38 @@
 
 import numpy as np
 from metaheuristic_designer import Individual
-from metaheuristic_designer.Operators import OperatorReal, real_ops_map
+from metaheuristic_designer.operators import OperatorReal, real_ops_map
 from metaheuristic_designer.benchmarks.benchmark_funcs import Sphere
-from metaheuristic_designer.Initializers import UniformVectorInitializer
+from metaheuristic_designer.initializers import UniformVectorInitializer
+import metaheuristic_designer as mhd
 
-real_ops = [i for i in real_ops_map.keys() if i not in ["mutate1sigma", "mutatensigmas"]]
+mhd.reset_seed(0)
+
+real_ops = [
+    i for i in real_ops_map.keys() if i not in ["mutate1sigma", "mutatensigmas"]
+]
 
 pop_size = 100
 
-example_populaton1 = [Individual(Sphere(3), np.random.uniform(-100, 100, 3)) for i in range(pop_size)]
-example_populaton2 = [Individual(Sphere(20), np.random.uniform(-100, 100, 20)) for i in range(pop_size)]
-example_populaton3 = [Individual(Sphere(100), np.random.uniform(-100, 100, 100)) for i in range(pop_size)]
+example_populaton1 = [
+    Individual(Sphere(3), np.random.uniform(-100, 100, 3)) for i in range(pop_size)
+]
+example_populaton2 = [
+    Individual(Sphere(20), np.random.uniform(-100, 100, 20)) for i in range(pop_size)
+]
+example_populaton3 = [
+    Individual(Sphere(100), np.random.uniform(-100, 100, 100)) for i in range(pop_size)
+]
+
 
 def test_errors():
     with pytest.raises(ValueError):
         operator = OperatorReal("not_a_method")
 
-@pytest.mark.parametrize("population", [example_populaton1, example_populaton2, example_populaton3])
+
+@pytest.mark.parametrize(
+    "population", [example_populaton1, example_populaton2, example_populaton3]
+)
 @pytest.mark.parametrize("op_method", real_ops)
 def test_basic_working(population, op_method):
     pop_init = UniformVectorInitializer(population[0].genotype.size, 0, 1, pop_size)
@@ -27,7 +42,7 @@ def test_basic_working(population, op_method):
     indiv = population[0]
     new_indiv = operator.evolve(indiv, population, indiv.objfunc, indiv, pop_init)
     assert type(new_indiv.genotype) == np.ndarray
-    
+
     # Test when global best is not defined
     new_indiv = operator.evolve(indiv, population, indiv.objfunc, None, pop_init)
-    assert type(new_indiv.genotype) == np.ndarray
\ No newline at end of file
+    assert type(new_indiv.genotype) == np.ndarray
diff --git a/tests/test_real_vector.py b/tests/test_real_vector.py
index bbb428a..4acd2f1 100644
--- a/tests/test_real_vector.py
+++ b/tests/test_real_vector.py
@@ -4,30 +4,32 @@
 
 import numpy as np
 
-from metaheuristic_designer import ObjectiveFunc, ParentSelection, SurvivorSelection, ParamScheduler
-from metaheuristic_designer.SearchMethods import GeneralSearch, MemeticSearch
-from metaheuristic_designer.Operators import OperatorReal
-from metaheuristic_designer.Initializers import UniformVectorInitializer
-from metaheuristic_designer.Algorithms import *
-
+from metaheuristic_designer import ObjectiveFunc, ParamScheduler
+from metaheuristic_designer.selectionMethods import ParentSelection, SurvivorSelection
+from metaheuristic_designer.searchMethods import GeneralSearch, MemeticSearch
+from metaheuristic_designer.operators import OperatorReal
+from metaheuristic_designer.initializers import UniformVectorInitializer
+from metaheuristic_designer.algorithms import *
 from metaheuristic_designer.benchmarks import Sphere
 
+import metaheuristic_designer as mhd
+
+mhd.reset_seed(0)
 
 test_params = {
     "stop_cond": "neval or cpu_time_limit",
     "cpu_time_limit": 2.0,
     "neval": 10000,
-
     "verbose": False,
-    "v_timer": -1
+    "v_timer": -1,
 }
 
-mut_params = ParamScheduler("Linear", {"method":"Gauss", "F": [0.001, 0.00001]})
+mut_params = ParamScheduler("Linear", {"method": "Gauss", "F": [0.001, 0.00001]})
 mutation_op = OperatorReal("RandNoise", mut_params)
 
 cross_op = OperatorReal("Multipoint")
 
-de_params = ParamScheduler("Linear", {"F":[0.8, 0.9], "Cr":[0.8, 0.5]})
+de_params = ParamScheduler("Linear", {"F": [0.8, 0.9], "Cr": [0.8, 0.5]})
 de_op = OperatorReal("DE/best/1", de_params)
 
 parent_params = ParamScheduler("Linear", {"amount": [30, 15]})
@@ -37,12 +39,16 @@
 
 objfunc = Sphere(10, "min")
 
-pop_init_single = UniformVectorInitializer(10, objfunc.low_lim, objfunc.up_lim, pop_size=1)
+pop_init_single = UniformVectorInitializer(
+    10, objfunc.low_lim, objfunc.up_lim, pop_size=1
+)
 pop_init = UniformVectorInitializer(10, objfunc.low_lim, objfunc.up_lim, pop_size=100)
 
+
 def test_hillclimb_empty():
     search_strat = HillClimb(pop_init_single)
 
+
 def test_hillclimb():
     search_strat = HillClimb(pop_init_single, mutation_op)
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
@@ -50,9 +56,11 @@ def test_hillclimb():
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size == 1
 
+
 def test_localseach_empty():
     search_strat = LocalSearch(pop_init_single)
 
+
 def test_localseach():
     search_strat = LocalSearch(pop_init_single, mutation_op)
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
@@ -60,6 +68,7 @@ def test_localseach():
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size == 1
 
+
 def test_sa():
     search_strat = SA(pop_init_single, mutation_op)
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
@@ -67,6 +76,7 @@ def test_sa():
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size == 1
 
+
 def test_random():
     search_strat = RandomSearch(pop_init)
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
@@ -74,12 +84,14 @@ def test_random():
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size == pop_init.pop_size
 
+
 def test_nosearch():
     search_strat = NoSearch(pop_init)
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
     ind, fit = alg.optimize()
     assert search_strat.pop_size == pop_init.pop_size
 
+
 def test_staticpop():
     search_strat = StaticPopulation(pop_init, mutation_op, parent_sel_op, selection_op)
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
@@ -87,20 +99,32 @@ def test_staticpop():
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size == pop_init.pop_size
 
+
 def test_varpop():
-    search_strat = VariablePopulation(pop_init, mutation_op, parent_sel_op, selection_op, 200)
+    search_strat = VariablePopulation(
+        pop_init, mutation_op, parent_sel_op, selection_op, 200
+    )
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
     ind, fit = alg.optimize()
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size == pop_init.pop_size
 
+
 def test_es():
-    search_strat = ES(pop_init, mutation_op, cross_op, parent_sel_op, selection_op, {"offspringSize": 200})
+    search_strat = ES(
+        pop_init,
+        mutation_op,
+        cross_op,
+        parent_sel_op,
+        selection_op,
+        {"offspringSize": 200},
+    )
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
     ind, fit = alg.optimize()
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size == pop_init.pop_size
 
+
 def test_ga():
     search_strat = GA(pop_init, mutation_op, cross_op, parent_sel_op, selection_op)
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
@@ -108,13 +132,15 @@ def test_ga():
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size == pop_init.pop_size
 
+
 def test_hs():
-    search_strat = HS(pop_init, {"HMCR":0.8, "BW":0.5, "PAR":0.2})
+    search_strat = HS(pop_init, {"HMCR": 0.8, "BW": 0.5, "PAR": 0.2})
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
     ind, fit = alg.optimize()
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size == pop_init.pop_size
 
+
 def test_de():
     search_strat = DE(pop_init, de_op)
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
@@ -122,48 +148,66 @@ def test_de():
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size == pop_init.pop_size
 
+
 def test_pso():
-    search_strat = PSO(pop_init, {"w":0.7, "c1":1.5, "c2":1.5})
+    search_strat = PSO(pop_init, {"w": 0.7, "c1": 1.5, "c2": 1.5})
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
     ind, fit = alg.optimize()
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size == pop_init.pop_size
 
+
 def test_cro():
-    search_strat = CRO(pop_init, mutation_op, cross_op, {"rho":0.5, "Fb":0.75, "Fd":0.2, "Pd":0.7, "attempts":4})
+    search_strat = CRO(
+        pop_init,
+        mutation_op,
+        cross_op,
+        {"rho": 0.5, "Fb": 0.75, "Fd": 0.2, "Pd": 0.7, "attempts": 4},
+    )
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
     ind, fit = alg.optimize()
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size <= pop_init.pop_size
 
+
 def test_cro_sl():
-    search_strat = CRO_SL(pop_init, [mutation_op, cross_op], {"rho":0.5, "Fb":0.75, "Fd":0.2, "Pd":0.7, "attempts":4})
+    search_strat = CRO_SL(
+        pop_init,
+        [mutation_op, cross_op],
+        {"rho": 0.5, "Fb": 0.75, "Fd": 0.2, "Pd": 0.7, "attempts": 4},
+    )
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
     ind, fit = alg.optimize()
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size <= pop_init.pop_size
 
+
 def test_pcro_sl():
-    search_strat = PCRO_SL(pop_init, [mutation_op, cross_op], {"rho":0.5, "Fb":0.75, "Fd":0.2, "Pd":0.7, "attempts":4})
+    search_strat = PCRO_SL(
+        pop_init,
+        [mutation_op, cross_op],
+        {"rho": 0.5, "Fb": 0.75, "Fd": 0.2, "Pd": 0.7, "attempts": 4},
+    )
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
     ind, fit = alg.optimize()
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size <= pop_init.pop_size
 
+
 @pytest.mark.parametrize("dyn_method", ["success", "fitness", "diff"])
 @pytest.mark.parametrize("dyn_metric", ["best", "avg", "med", "worse"])
 def test_dpcro_sl(dyn_method, dyn_metric):
     search_strat_params = {
-        "rho":0.6,
-        "Fb":0.95,
-        "Fd":0.1,
-        "Pd":0.9,
+        "rho": 0.6,
+        "Fb": 0.95,
+        "Fd": 0.1,
+        "Pd": 0.9,
         "attempts": 3,
         "group_subs": True,
         "dyn_method": dyn_method,
         "dyn_metric": dyn_metric,
         "dyn_steps": 75,
-        "prob_amp": 0.1
+        "prob_amp": 0.1,
     }
     search_strat = DPCRO_SL(pop_init, [mutation_op, cross_op], search_strat_params)
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
@@ -171,35 +215,42 @@ def test_dpcro_sl(dyn_method, dyn_metric):
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size <= pop_init.pop_size
 
+
 def test_memetic():
     search_strat = GA(pop_init, mutation_op, cross_op, parent_sel_op, selection_op)
     mem_select = ParentSelection("Best", {"amount": 5})
-    neihbourhood_op = OperatorReal("RandNoise", {"method":"Cauchy", "F": 0.0002})
-    local_search =  LocalSearch(pop_init, neihbourhood_op, params={"iters":10})
-    alg = MemeticSearch(objfunc, search_strat, local_search, mem_select, params=test_params)
+    neihbourhood_op = OperatorReal("RandNoise", {"method": "Cauchy", "F": 0.0002})
+    local_search = LocalSearch(pop_init, neihbourhood_op, params={"iters": 10})
+    alg = MemeticSearch(
+        objfunc, search_strat, local_search, mem_select, params=test_params
+    )
     ind, fit = alg.optimize()
     assert alg.fit_history[0] > alg.fit_history[-1]
     assert search_strat.pop_size <= pop_init.pop_size
 
+
 def test_reporting():
     test_params["verbose"] = True
     search_strat = GA(pop_init, mutation_op, cross_op, parent_sel_op, selection_op)
     alg = GeneralSearch(objfunc, search_strat, params=test_params)
     ind, fit = alg.optimize()
     test_params["verbose"] = False
-    
+
     alg.store_state("temp_pytest.json", True, True, True, True, True, True)
     os.remove("temp_pytest.json")
 
+
 def test_reporting_memetic():
     test_params["verbose"] = True
     search_strat = GA(pop_init, mutation_op, cross_op, parent_sel_op, selection_op)
     mem_select = ParentSelection("Best", {"amount": 5})
-    neihbourhood_op = OperatorReal("RandNoise", {"method":"Cauchy", "F": 0.0002})
-    local_search =  LocalSearch(pop_init, neihbourhood_op, params={"iters":10})
-    alg = MemeticSearch(objfunc, search_strat, local_search, mem_select, params=test_params)
+    neihbourhood_op = OperatorReal("RandNoise", {"method": "Cauchy", "F": 0.0002})
+    local_search = LocalSearch(pop_init, neihbourhood_op, params={"iters": 10})
+    alg = MemeticSearch(
+        objfunc, search_strat, local_search, mem_select, params=test_params
+    )
     ind, fit = alg.optimize()
     test_params["verbose"] = False
-    
+
     alg.store_state("temp_pytest.json", True, True, True, True, True, True)
     os.remove("temp_pytest.json")
diff --git a/tests/test_surv_sel.py b/tests/test_surv_sel.py
index 928aab5..db88134 100644
--- a/tests/test_surv_sel.py
+++ b/tests/test_surv_sel.py
@@ -1,13 +1,17 @@
 import pytest
 import numpy as np
-from metaheuristic_designer import Individual, SurvivorSelection
+from metaheuristic_designer import Individual
+from metaheuristic_designer.selectionMethods import SurvivorSelection
+import metaheuristic_designer as mhd
+
+mhd.reset_seed(0)
 
 pop_size = 100
 
 example_populaton = [Individual(None, None) for i in range(pop_size)]
 example_offspring = [Individual(None, None) for i in range(pop_size)]
-example_offspring_small = [Individual(None, None) for i in range(pop_size//2)]
-example_offspring_big = [Individual(None, None) for i in range(pop_size*2)]
+example_offspring_small = [Individual(None, None) for i in range(pop_size // 2)]
+example_offspring_big = [Individual(None, None) for i in range(pop_size * 2)]
 
 for idx, ind in enumerate(example_populaton):
     example_populaton[idx].fitness = 10
@@ -26,58 +30,69 @@ def test_error():
     with pytest.raises(ValueError):
         surv_selection = SurvivorSelection("not_a_method")
 
+
 @pytest.mark.parametrize("population", [example_populaton])
-@pytest.mark.parametrize("offspring", [example_offspring, example_offspring_small, example_offspring_big])
+@pytest.mark.parametrize(
+    "offspring", [example_offspring, example_offspring_small, example_offspring_big]
+)
 @pytest.mark.parametrize("keep_amount", [1, 5, 20])
 def test_elitism(population, offspring, keep_amount):
     surv_selection = SurvivorSelection("Elitism", {"amount": keep_amount})
     survivors = surv_selection.select(population, offspring)
     assert len(population) >= len(survivors)
     assert set(population[:keep_amount]) == set(survivors[:keep_amount])
-    pop_fit_avg = sum([i.fitness for i in population])/len(population)
-    surv_fit_avg = sum([i.fitness for i in survivors])/len(survivors)
+    pop_fit_avg = sum([i.fitness for i in population]) / len(population)
+    surv_fit_avg = sum([i.fitness for i in survivors]) / len(survivors)
     assert pop_fit_avg < surv_fit_avg
 
 
 @pytest.mark.parametrize("population", [example_populaton])
-@pytest.mark.parametrize("offspring", [example_offspring, example_offspring_small, example_offspring_big])
+@pytest.mark.parametrize(
+    "offspring", [example_offspring, example_offspring_small, example_offspring_big]
+)
 @pytest.mark.parametrize("keep_amount", [1, 5, 20])
 def test_condelitism(population, offspring, keep_amount):
     surv_selection = SurvivorSelection("CondElitism", {"amount": keep_amount})
     survivors = surv_selection.select(population, offspring)
     assert len(population) >= len(survivors)
-    pop_fit_avg = sum([i.fitness for i in population])/len(population)
-    surv_fit_avg = sum([i.fitness for i in survivors])/len(survivors)
+    pop_fit_avg = sum([i.fitness for i in population]) / len(population)
+    surv_fit_avg = sum([i.fitness for i in survivors]) / len(survivors)
     assert pop_fit_avg < surv_fit_avg
 
 
 @pytest.mark.parametrize("population", [example_populaton])
-@pytest.mark.parametrize("offspring", [example_offspring, example_offspring_small, example_offspring_big])
+@pytest.mark.parametrize(
+    "offspring", [example_offspring, example_offspring_small, example_offspring_big]
+)
 def test_one_to_one(population, offspring):
     surv_selection = SurvivorSelection("One-to-one")
     survivors = surv_selection.select(population, offspring)
     assert len(population) >= len(survivors)
-    assert set(population[len(offspring):]) == set(survivors[len(offspring):])
-    pop_fit_avg = sum([i.fitness for i in population])/len(population)
-    surv_fit_avg = sum([i.fitness for i in survivors])/len(survivors)
+    assert set(population[len(offspring) :]) == set(survivors[len(offspring) :])
+    pop_fit_avg = sum([i.fitness for i in population]) / len(population)
+    surv_fit_avg = sum([i.fitness for i in survivors]) / len(survivors)
     assert pop_fit_avg < surv_fit_avg
 
 
 @pytest.mark.parametrize("population", [example_populaton])
-@pytest.mark.parametrize("offspring", [example_offspring, example_offspring_small, example_offspring_big])
+@pytest.mark.parametrize(
+    "offspring", [example_offspring, example_offspring_small, example_offspring_big]
+)
 @pytest.mark.parametrize("p", [0, 0.1, 0.25, 0.5, 0.75, 1])
 def test_prob_one_to_one(population, offspring, p):
     surv_selection = SurvivorSelection("Prob-One-to-one", {"p": p})
     survivors = surv_selection.select(population, offspring)
     assert len(population) >= len(survivors)
-    assert set(population[len(offspring):]) == set(survivors[len(offspring):])
-    pop_fit_avg = sum([i.fitness for i in population])/len(population)
-    surv_fit_avg = sum([i.fitness for i in survivors])/len(survivors)
+    assert set(population[len(offspring) :]) == set(survivors[len(offspring) :])
+    pop_fit_avg = sum([i.fitness for i in population]) / len(population)
+    surv_fit_avg = sum([i.fitness for i in survivors]) / len(survivors)
     assert pop_fit_avg <= surv_fit_avg
 
 
 @pytest.mark.parametrize("population", [example_populaton])
-@pytest.mark.parametrize("offspring", [example_offspring, example_offspring_small, example_offspring_big])
+@pytest.mark.parametrize(
+    "offspring", [example_offspring, example_offspring_small, example_offspring_big]
+)
 def test_generational(population, offspring):
     surv_selection = SurvivorSelection("Generational")
     survivors = surv_selection.select(population, offspring)
@@ -85,19 +100,23 @@ def test_generational(population, offspring):
 
 
 @pytest.mark.parametrize("population", [example_populaton])
-@pytest.mark.parametrize("offspring", [example_offspring, example_offspring_small, example_offspring_big])
+@pytest.mark.parametrize(
+    "offspring", [example_offspring, example_offspring_small, example_offspring_big]
+)
 def test_m_plus_n(population, offspring):
     surv_selection = SurvivorSelection("(m+n)")
     survivors = surv_selection.select(population, offspring)
     assert len(population) >= len(survivors)
 
-    pop_fit_avg = sum([i.fitness for i in population])/len(population)
-    surv_fit_avg = sum([i.fitness for i in survivors])/len(survivors)
+    pop_fit_avg = sum([i.fitness for i in population]) / len(population)
+    surv_fit_avg = sum([i.fitness for i in survivors]) / len(survivors)
     assert pop_fit_avg < surv_fit_avg
 
 
 @pytest.mark.parametrize("population", [example_populaton])
-@pytest.mark.parametrize("offspring", [example_offspring, example_offspring_small, example_offspring_big])
+@pytest.mark.parametrize(
+    "offspring", [example_offspring, example_offspring_small, example_offspring_big]
+)
 def test_m_comma_n(population, offspring):
     surv_selection = SurvivorSelection("(m,n)")
     survivors = surv_selection.select(population, offspring)
@@ -106,21 +125,32 @@ def test_m_comma_n(population, offspring):
     for parent in population:
         assert parent not in survivors
 
-    pop_fit_avg = sum([i.fitness for i in population])/len(population)
-    surv_fit_avg = sum([i.fitness for i in survivors])/len(survivors)
+    pop_fit_avg = sum([i.fitness for i in population]) / len(population)
+    surv_fit_avg = sum([i.fitness for i in survivors]) / len(survivors)
     assert pop_fit_avg < surv_fit_avg
 
+
 @pytest.mark.parametrize("population", [example_populaton])
-@pytest.mark.parametrize("offspring", [example_offspring, example_offspring_small, example_offspring_big])
-@pytest.mark.parametrize("fd", np.linspace(0,1,10))
-@pytest.mark.parametrize("pd", np.linspace(0,1,10))
+@pytest.mark.parametrize(
+    "offspring", [example_offspring, example_offspring_small, example_offspring_big]
+)
+@pytest.mark.parametrize("fd", np.linspace(0, 1, 10))
+@pytest.mark.parametrize("pd", np.linspace(0, 1, 10))
 @pytest.mark.parametrize("attempts", [1, 3, 5, 10])
 def test_cro_selection(population, offspring, fd, pd, attempts):
-    surv_selection = SurvivorSelection("CRO", {"Fd": fd, "Pd": pd, "attempts": attempts, "maxPopSize":len(example_populaton)})
+    surv_selection = SurvivorSelection(
+        "CRO",
+        {
+            "Fd": fd,
+            "Pd": pd,
+            "attempts": attempts,
+            "maxPopSize": len(example_populaton),
+        },
+    )
     survivors = surv_selection.select(population, offspring)
     assert len(population) >= len(survivors)
 
-    pop_fit_avg = sum([i.fitness for i in population])/len(population)
-    surv_fit_avg = sum([i.fitness for i in survivors])/len(survivors)
+    pop_fit_avg = sum([i.fitness for i in population]) / len(population)
+    surv_fit_avg = sum([i.fitness for i in survivors]) / len(survivors)
     if fd != 1 and pd != 1:
-        assert pop_fit_avg < surv_fit_avg
\ No newline at end of file
+        assert pop_fit_avg < surv_fit_avg