Why EvoKit?
Since its conception in 2024, EvoKit has grown to implement a wide range of features. Please take a look at Guides for some tutorials.
EvoKit is designed to several software qualities, with researchers in mind:
Simple Operators
Define only what matters and the framework will handle the rest.
The stock OneMax evaluator, for example, is written just in 3 lines: [1]
class CountBits(Evaluator[BitString]):
def evaluate(self, individual: BitString) -> tuple[float,]:
return (individual.genome.bit_count(),)
(Really) Great Interoperability
Everything are interoperable and most things are interchangeable:
All evaluators and variators of the same representation are interchangeable; all selectors are interchangeable; all algorithms work with all compatible configurations of operators.
All wrappers of the same operator type are interchangeable. For example,
selectors.Elitist()works with every selector andlineage.TrackParents()works with every variator.All operators – variators, evaluators, and selectors – can be parallelised.
All profilers work with all algorithms of the right type.
See Profiling Time and Memory Cost for how these pieces falls together. Here:
Several stock profilers in
watch.profilerecord the memory usage of an algorithm.The algorithm is a simple onemax algorithm where…
…every operator is parallelised with 5 worker processes,
the selector is made elitist with
selectors.Elitist()and,the variator preserves several generations of parents with
lineage.TrackParents().Collected statistics are plotted, against time and generation, with
watch.profile.visual.
You can swap in and out absolutely anything you want. Profit!
Completely Documented
All public members are documented. All private members are documented. Absolutely everything is documented.
See evokit for the API documentation.
Transparent
Every single line of code is written in Python; you can see and control everything that happens.
Furthermore, EvoKit describes exactly what it does:
All methods (public or private) have type hints:
def __init__(self: Self,
population: Population[T],
evaluator: Evaluator[T],
selector: Selector[T],
variator: Variator[T]) -> None:
All return values are explained:
def has_fitness(self) -> bool:
"""Return `True` if :attr:`.fitness` is not None.
Otherwise, return `False`.
"""
return self._fitness is not None
All effects are documented:
def reset_fitness(self) -> None:
"""Reset the fitness of the individual.
Effect:
The :attr:`.fitness` of this individual becomes ``None``.
"""
self._fitness = None
All managed attributes are explained:
"""Advance the population by one generation.
.. note::
After this method is called, but before control is
returned to the caller, two things happen automatically:
#. The :attr:`generation` of the algorithm increments by 1.
#. The algorithm fires an ``POST_STEP`` event to all attached
watchers. For more on events and watchers,
see :class:`.Watcher`.
"""
Portable
EvoKit has no mandatory dependency. All dependencies are optional and can be installed later, only when you need them.
Reproducible
All randomness in stock modules come from random and
can be reproduced by setting the same random.seed().