pax_global_header00006660000000000000000000000064131123365610014513gustar00rootroot0000000000000052 comment=98255cbaf7b697c6f3f00dd2618fc1ee02ddf0c0 genetic-0.1.1b+git20170527.98255cb/000077500000000000000000000000001311233656100157015ustar00rootroot00000000000000genetic-0.1.1b+git20170527.98255cb/.gitignore000066400000000000000000000053641311233656100177010ustar00rootroot00000000000000# Created by .ignore support plugin (hsz.mobi) ### VirtualEnv template # Virtualenv # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ .Python [Bb]in [Ii]nclude [Ll]ib [Ll]ib64 [Ll]ocal [Ss]cripts pyvenv.cfg .venv pip-selfcheck.json ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff: .idea/workspace.xml .idea/tasks.xml .idea/dictionaries .idea/vcs.xml .idea/jsLibraryMappings.xml # Sensitive or high-churn files: .idea/dataSources.ids .idea/dataSources.xml .idea/dataSources.local.xml .idea/sqlDataSources.xml .idea/dynamic.xml .idea/uiDesigner.xml # Gradle: .idea/gradle.xml .idea/libraries # Mongo Explorer plugin: .idea/mongoSettings.xml ## File-based project format: *.iws ## Plugin-specific files: # IntelliJ /out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties ### TextMate template *.tmproj *.tmproject tmtags ### SublimeText template # cache files for sublime text *.tmlanguage.cache *.tmPreferences.cache *.stTheme.cache # workspace files are user-specific *.sublime-workspace # project files should be checked into the repository, unless a significant # proportion of contributors will probably not be using SublimeText # *.sublime-project # sftp configuration file sftp-config.json # Package control specific files Package Control.last-run Package Control.ca-list Package Control.ca-bundle Package Control.system-ca-bundle Package Control.cache/ Package Control.ca-certs/ bh_unicode_properties.cache # Sublime-github package stores a github token in this file # https://packagecontrol.io/packages/sublime-github GitHub.sublime-settings ### OSX template *.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### IPythonNotebook template # Temporary data .ipynb_checkpoints/ ### Xcode template # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ DerivedData/ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ ## Other *.moved-aside *.xccheckout *.xcscmblueprint genetic-0.1.1b+git20170527.98255cb/LICENSE.txt000066400000000000000000000026431311233656100175310ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 genetic contributors genetic uses a shared copyright model: each contributor holds copyright over their contributions to genetic. The project versioning records all such contribution and copyright details. By contributing to the basicNNet repository through pull-request, comment, or otherwise, the contributor releases their content to the license and copyright terms herein. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. genetic-0.1.1b+git20170527.98255cb/README.md000066400000000000000000000073641311233656100171720ustar00rootroot00000000000000### genetic _(ver. 0.1.dev2)_ This package is intended for numerical optimisation. The main goals are flexibility and ease of use. A while ago I needed a genetic algorithm that would allow arbitrary combinations of objects (parameters) in the genome. The lack of such implementation in Python (or my failure to find one) pushed me towards developing this package. As of now it contains 4 main modules - `individuals` - `BaseIndividual` - the base individual class. Any individual you'd wish to create must subclass this base class and implement all its methods and properties to make it compatible with any proper population. Refer to examples and docs for further details - `SingleChromosomeIndividual` – a basic individual that only has one chromosome. It is ageless. Mating is based on the `recombination.binomal` function. - `populations` todo - `recombination` todo - `selection` todo - `util` todo ___ ##### Example 1. Optimising sum First, let's create an individual. To do that we need an engine that will generate gene values ``` >>> import random >>> def engine(_): return random.randint(0, 50) ``` Note that the engine must have a single argument. That is because in some cases one may want to generate a new value based on the current state of the genome. We will generate the values totally randomly. Let's create the first individual. Let it have 10 genes. We can provide unique engine for each gene, but in this case all genes will have the same engine. And let's set the random mutation rate to `0.1`. ``` >>> from genetic.individuals import SingleChromosomeIndividual >>> indiv = SingleChromosomeIndividual(engine, 0.1, 10) ``` Here we have our first individual. Now, let's move on to the population. We will need to create the target (fitness) function to maximise. ``` >>> def fitness(x): """ :type x: SingleChromosomeIndividual """ return - abs(200 - sum(x.genome)) ``` This function takes a `SingleChromosomeIndividual` instance and evaluates the negative of the absolute difference between `200` and the sum of genes (numbers in this case). Now we need a selection model. Let's pick one from the `selection` module ``` >>> from genetic.selection import bimodal >>> selection = bimodal(fittest_fraction=0.2, other_random_survival=0.05) ``` Here we used a `selection` factory, that has two parameters: the fraction of the fittest individuals that survive a generation and the fraction of random survivors in the rest of the population. Now we can start the population. We need to pass at lest 2 individuals to begin with, so we'll just take a copy of the same one. Let's have 100 individuals in the population. And let's keep track of 10 legends ``` >>> from genetic.populations import PanmicticPopulation >>> ancestors = [indiv] * 2 >>> population = PanmicticPopulation(ancestors, 100, fitness, selection, 10) ``` Now, let's make it evolve for 10 generations ``` >>> average_fitness = list(population.evolve(10)) ``` Note that the `PanmicticPopulation.evolve` method returns a lazy generator, hence to make the evolution happen, you need to make it generate values (to iterate over it). Our errors are: ``` >>> print(average_fitness) [-59.979999999999997, -51.509999999999998, -41.18, -36.960000000000001, -30.359999999999999, -28.460000000000001, -28.82, -27.27, -28.5, -33.960000000000001] ``` Let's look at the legends' scores ``` >>> print([legend[0] for legend in population.legends]) [0, 0, 0, -1, -1, -1, -1, -1, -1, -1] ``` As you see, we already have 3 optimal solutions. Let's take a look at the first one ``` >>> print(population.legends[0][1].genome) (8, 31, 11, 8, 48, 2, 17, 25, 43, 7) ``` --- ##### Example 2. Optimising neural network architecture todo genetic-0.1.1b+git20170527.98255cb/genetic/000077500000000000000000000000001311233656100173175ustar00rootroot00000000000000genetic-0.1.1b+git20170527.98255cb/genetic/__init__.py000066400000000000000000000004541311233656100214330ustar00rootroot00000000000000import genetic.individuals import genetic.populations import genetic.recombination import genetic.selection import genetic.util __license__ = "MIT" __version__ = "0.1.dev3" __author__ = "Ilia Korvigo" __maintainer__ = "Ilia Korvigo" __email__ = "ilia.korvigo@gmail.com" __status__ = "Development" genetic-0.1.1b+git20170527.98255cb/genetic/individuals/000077500000000000000000000000001311233656100216325ustar00rootroot00000000000000genetic-0.1.1b+git20170527.98255cb/genetic/individuals/__init__.py000066400000000000000000000003071311233656100237430ustar00rootroot00000000000000from ._individuals import * __license__ = "MIT" __version__ = "0.1.dev3" __author__ = "Ilia Korvigo" __maintainer__ = "Ilia Korvigo" __email__ = "ilia.korvigo@gmail.com" __status__ = "Development" genetic-0.1.1b+git20170527.98255cb/genetic/individuals/_individuals.py000066400000000000000000000153021311233656100246570ustar00rootroot00000000000000from collections.abc import Sequence, Callable from genetic import recombination as rec import abc import numpy as np __all__ = ["BaseIndividual", "SingleChromosomeIndividual"] class BaseIndividual(metaclass=abc.ABCMeta): @abc.abstractmethod def __eq__(self, other): """ :type other: SingleChromosomeIndividual :rtype: bool """ raise NotImplementedError @abc.abstractmethod def __ne__(self, other): """ :type other: SingleChromosomeIndividual :rtype: bool """ raise NotImplementedError @abc.abstractmethod def replicate(self, *args, **kwargs): """ :return: Should return a copy of one's genome (with or without new mutations) :rtype: Sequence[Any] """ raise NotImplementedError @abc.abstractmethod def mate(self, other, *args, **kwargs): """ :return: the result of mating with another BaseIndividual instance of the same type :rtype: BaseIndividual """ raise NotImplementedError @abc.abstractproperty def engine(self): """ :return: one's evolution engine :rtype: Union[Sequence[Callable], Sequence[Sequence[Callable]]] """ raise NotImplementedError @abc.abstractproperty def genome(self): """ :return: one's features :rtype: Sequence[Any] """ raise NotImplementedError @abc.abstractproperty def mutrate(self): """ :return: one's random mutation rate :rtype: float """ raise NotImplementedError class SingleChromosomeIndividual(BaseIndividual): """ This is a basic single chromosome-individual :type _l: int :type _engine: Sequence[Callable] :type _genome: tuple """ __slots__ = ("_l", "_engine", "_mutrate") def __init__(self, engine, mutrate, l=None, starting=None): # TODO add docs about starting_chromosome """ :type engine: Union[Sequence[Callable[Optional]], Callable[Optional]] :param engine: an engine is used to mutate individual's features. It can be: - A single callable object that takes one argument (current value of a feature or the entire chromosome) and returns an new value. It must handle `None` inputs if `starting_chromosome` is `None`. - A sequence of such callable object, one per each feature Note: when `genome` is `None` the first time `engine` is used its callables get `None` as input, which means they should return some starting value; this detail may not be important for your implementation, (e.g. if you don't have any special rules for starting values and mutated values), but make sure the your callables handle `None` inputs if you don't `starting_chr` :type mutrate: float :param mutrate: the binomial random mutation rate for each gene :type l: Optional[int] :param l: the number of features, defaults to `None`. Can't be == 0. - If `l` is set to `None`, it's true value is inferred from `len(engine)`, hence a `ValueError` will be raised if `engine` is not a sequence; - If `l` is not `None` and `engine` is a sequence such that `len(engines) != l`, a `ValueError` will be raised. :type starting: Sequence[Any] :param starting: the genome to begin with :raise ValueError: If `l` is set to `None` and `engine` is not a sequence; """ if not isinstance(mutrate, float) or not (0 <= mutrate <= 1): raise ValueError("`mutrate` must be a float in [0, 1]") if l is not None and not isinstance(l, int) and l <= 0: raise ValueError("`l` is an optional positive integer") if l and isinstance(engine, Sequence) and len(engine) != l: raise ValueError("len(engine) != l, while l is not None") if l is None and not isinstance(engine, Sequence): raise ValueError("`l` is not specified, while `engine` is not a " "sequence, hence `l` cannot be be inferred") if not isinstance(engine, Callable) and not ( isinstance(engine, Sequence) and all(isinstance(gen, Callable) for gen in engine)): raise ValueError("`engine` must be a Callable or a Sequence of " "Callable objects") self._engine = (engine if isinstance(engine, Sequence) else [engine] * l) self._l = l if l else len(engine) if starting and (not isinstance(starting, Sequence) or self._l != len(starting)): raise ValueError("`starting` length doesn't match the number " "of features specified by `l` (or inferred from " "`len(engine)`)") # chromosome is a sequence of genes (features) self._genome = (tuple(starting) if starting else tuple(gen(None) for gen in self._engine)) self._mutrate = mutrate def __eq__(self, other): """ :type other: SingleChromosomeIndividual """ return self.genome == other.genome def __ne__(self, other): """ :type other: SingleChromosomeIndividual """ return not self == other @property def engine(self): """ :rtype: Sequence[Callable] """ return self._engine @property def genome(self): """ :rtype: tuple[Any] """ return self._genome @property def mutrate(self): return self._mutrate def replicate(self): """ :rtype: list[Any] :return: a mutated chromosome """ mutation_mask = np.random.binomial(1, self.mutrate, len(self.genome)) return [gen(val) if mutate else val for (val, mutate, gen) in list(zip(self.genome, mutation_mask, self.engine))] def mate(self, other, *args, **kwargs): """ :type other: SingleChromosomeIndividual """ offspring_genome = rec.binomial(self.replicate(), other.replicate()) return type(self)(engine=self._engine, mutrate=self.mutrate, l=self._l, starting=offspring_genome) if __name__ == "__main__": raise RuntimeError genetic-0.1.1b+git20170527.98255cb/genetic/populations/000077500000000000000000000000001311233656100216745ustar00rootroot00000000000000genetic-0.1.1b+git20170527.98255cb/genetic/populations/__init__.py000066400000000000000000000003071311233656100240050ustar00rootroot00000000000000from ._populations import * __license__ = "MIT" __version__ = "0.1.dev3" __author__ = "Ilia Korvigo" __maintainer__ = "Ilia Korvigo" __email__ = "ilia.korvigo@gmail.com" __status__ = "Development" genetic-0.1.1b+git20170527.98255cb/genetic/populations/_populations.py000066400000000000000000000131601311233656100247630ustar00rootroot00000000000000from collections.abc import Sequence, Callable from operator import itemgetter from random import sample import itertools import abc import numpy as np from genetic.individuals import BaseIndividual from genetic.util import Workers, filter_duplicates __all__ = ["BasePopulation", "PanmicticPopulation"] class BasePopulation: @abc.abstractmethod def evolve(self, *args, **kwargs): raise NotImplementedError @abc.abstractproperty def individuals(self): """ :rtype: Sequence[(Any, BaseIndividual)] """ raise NotImplementedError @abc.abstractproperty def legends(self): """ :rtype: Sequence[(Any, BaseIndividual)] """ raise NotImplementedError @abc.abstractproperty def nlegends(self): """ :rtype: int """ raise NotImplementedError @abc.abstractproperty def fitness_func(self): """ :rtype: Callable """ raise NotImplementedError @abc.abstractproperty def selection(self): """ :rtype: Callable """ raise NotImplementedError @abc.abstractproperty def size(self): raise NotImplementedError class PanmicticPopulation(BasePopulation): """ :type _individuals: list[(Any, BaseIndividual)] :type _legends: list[(Any, BaseIndividual)] :type _fitness_func: (BaseIndividual) -> Any """ def __init__(self, ancestors, size, fitness, selection, nlegends=100): """ :type ancestors: Sequence[BaseIndividual] :param ancestors: a bunch of individuals to begin with :type size: int :param size: population size :type fitness: (BaseIndividual) -> Any :param fitness: a callable that requires one argument - an instance of Individual - and returns an instance of a class that supports comparison operators, i.e. can be used to evaluate and compare fitness of different Individuals. :type selection: Callable :param selection: a selection engine :type nlegends: int :param nlegends: the number of legends to remember :return: """ if not isinstance(nlegends, int) or nlegends < 0: raise ValueError("`n_legends` must be a non-negative integer") if not isinstance(size, int) or size <= 0: raise ValueError("`size` must be a positive integer") if not isinstance(ancestors, Sequence) or len(ancestors) < 2: raise ValueError("`ancestors` must be a nonempty sequence of " "length >= 2") if not all(isinstance(indiv, BaseIndividual) for indiv in ancestors): raise ValueError("`ancestors` can only contain instances of" "`Individual`") try: if fitness(ancestors[0]) is None: raise ValueError("`fitness_function` mustn't return `NoneType` " "values") except (TypeError, AttributeError): raise ValueError("Your `fitness` doesn't suit your Idividuals") self._size = size self._fitness_func = fitness self._selection = selection self._nlegends = nlegends self._evolving = False self._individuals = list(zip(map(fitness, ancestors), ancestors)) self._legends = [] @property def size(self): return self._size @property def legends(self): """ :rtype: list[(Any, BaseIndividual)] """ return self._legends @property def nlegends(self): return self._nlegends @property def individuals(self): return self._individuals @property def fitness_func(self): return self._fitness_func @property def selection(self): return self._selection def evolve(self, n, jobs=1): """ :rtype: Generator[Any] """ if not isinstance(jobs, int) or jobs < 1: raise ValueError("`jobs` must be a positive integer") def repopulate(evaluated_individuals): """ :type evaluated_individuals: list[(Any, BaseIndividual)] :rtype: list[(Any, BaseIndividual)] """ n_pairs = self.size - len(evaluated_individuals) pairs = [sample(self.individuals, 2) for _ in range(n_pairs)] new_individuals = [ind1[1].mate(ind2[1]) for ind1, ind2 in pairs] scores = workers.map(self.fitness_func, new_individuals) return evaluated_individuals + list(zip(scores, new_individuals)) def new_legends(old_legends, contenders): """ :type old_legends: list[(Any, BaseIndividual)] :type contenders: list[(Any, BaseIndividual)] """ merged = sorted(filter_duplicates(old_legends + contenders), key=itemgetter(0), reverse=True) return merged[:self.nlegends] workers = Workers(jobs) if len(self.individuals) < self.size: self._individuals = repopulate(self.individuals) for _ in itertools.repeat(None, n): survivors = self.selection(sorted(self.individuals, key=itemgetter(0), reverse=True)) self._individuals = repopulate(survivors) # Update legends self._legends = new_legends(self.legends, self.individuals) yield np.mean([indiv[0] for indiv in self.individuals]) workers.terminate() if __name__ == "__main__": raise RuntimeError genetic-0.1.1b+git20170527.98255cb/genetic/recombination/000077500000000000000000000000001311233656100221505ustar00rootroot00000000000000genetic-0.1.1b+git20170527.98255cb/genetic/recombination/__init__.py000066400000000000000000000003111311233656100242540ustar00rootroot00000000000000from ._recombination import * __license__ = "MIT" __version__ = "0.1.dev3" __author__ = "Ilia Korvigo" __maintainer__ = "Ilia Korvigo" __email__ = "ilia.korvigo@gmail.com" __status__ = "Development" genetic-0.1.1b+git20170527.98255cb/genetic/recombination/_recombination.py000066400000000000000000000010141311233656100255060ustar00rootroot00000000000000import numpy as np __all__ = ["binomial"] def binomial(chr1, chr2): """ Picks one allele or the other with 50% success :type chr1: Sequence :type chr2: Sequence """ if len(chr1) != len(chr2): raise ValueError("Incompatible chromosome lengths") choice_mask = np.random.binomial(1, 0.5, len(chr1)) return [a if ch else b for (ch, a, b) in zip(choice_mask, chr1, chr2)] def breakpoint(chr1, chr2, rate): raise NotImplemented if __name__ == "__main__": raise RuntimeError genetic-0.1.1b+git20170527.98255cb/genetic/selection/000077500000000000000000000000001311233656100213045ustar00rootroot00000000000000genetic-0.1.1b+git20170527.98255cb/genetic/selection/__init__.py000066400000000000000000000003051311233656100234130ustar00rootroot00000000000000from ._selection import * __license__ = "MIT" __version__ = "0.1.dev3" __author__ = "Ilia Korvigo" __maintainer__ = "Ilia Korvigo" __email__ = "ilia.korvigo@gmail.com" __status__ = "Development" genetic-0.1.1b+git20170527.98255cb/genetic/selection/_selection.py000066400000000000000000000016411311233656100240040ustar00rootroot00000000000000import random __all__ = ["bimodal"] def bimodal(fittest_fraction, other_random_survival): """ :type fittest_fraction: float :param fittest_fraction: :type other_random_survival: float :param other_random_survival: """ def bimodal_(sorted_population): """ :type sorted_population: list[(Any, BaseIndividual)] :param sorted_population: :rtype: list[Any, BaseIndividual)] """ # pick the most fittest and random lesser fit individuals n_fittest = int(len(sorted_population) * fittest_fraction) n_random = int((len(sorted_population) - n_fittest) * other_random_survival) fittest_survivors = sorted_population[:n_fittest] random_survivors = random.sample(sorted_population[n_fittest:], n_random) return fittest_survivors + random_survivors return bimodal_ if __name__ == "__main__": raise RuntimeError genetic-0.1.1b+git20170527.98255cb/genetic/util/000077500000000000000000000000001311233656100202745ustar00rootroot00000000000000genetic-0.1.1b+git20170527.98255cb/genetic/util/__init__.py000066400000000000000000000003001311233656100223760ustar00rootroot00000000000000from ._util import * __license__ = "MIT" __version__ = "0.1.dev3" __author__ = "Ilia Korvigo" __maintainer__ = "Ilia Korvigo" __email__ = "ilia.korvigo@gmail.com" __status__ = "Development" genetic-0.1.1b+git20170527.98255cb/genetic/util/_util.py000066400000000000000000000024051311233656100217630ustar00rootroot00000000000000from collections import Sequence from itertools import starmap import random from multiprocess.pool import Pool __all__ = ["Workers", "random_pairs", "filter_duplicates"] class Workers(Pool): @property def processes(self): return self._processes def map(self, func, iterable, chunksize=None): if self.processes == 1: return list(map(func, iterable)) return super().map(func, iterable, chunksize=chunksize) def starmap(self, func, iterable, chunksize=None): if self.processes == 1: return list(starmap(func, iterable)) return super().starmap(func, iterable, chunksize=chunksize) def random_pairs(sequence: Sequence, n: int): pairs = set() indices = list(range(len(sequence))) while len(pairs) != n: i, j = tuple(random.sample(indices, 2)) if (i, j) in pairs or (j, i) in pairs: continue pairs.add((i, j)) yield from ((sequence[i], sequence[j]) for i, j in pairs) def filter_duplicates(objects): """ Filter duplicated references from a sequence. Utilises object ids. :type objects: Sequence[Any] :rtype: list[Any] """ return {id(obj): obj for obj in objects}.values() if __name__ == "__main__": raise RuntimeError genetic-0.1.1b+git20170527.98255cb/setup.cfg000066400000000000000000000000501311233656100175150ustar00rootroot00000000000000[metadata] description-file = README.md genetic-0.1.1b+git20170527.98255cb/setup.py000066400000000000000000000021761311233656100174210ustar00rootroot00000000000000""" Setup module """ from setuptools import setup, find_packages setup( name='genetic', version='0.1.dev3', description=("A versatile distributable genetic algorithm build with " "flexibility and ease of use in mind"), url="https://github.com/grayfall/genetic.git", # Author details author="Ilia Korvigo", author_email="ilia.korvigo@gmail.com", license="MIT", classifiers=[ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries", "Topic :: Scientific/Engineering ", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", ], # What does your project relate to? keywords=("genetic algorithm, multiprocessing, numerical optimisation," "stochastic optimisation"), packages=find_packages("./"), install_requires=["numpy>=1.11.0", "scipy>=0.17.0", "multiprocess>=0.70.4"], ) genetic-0.1.1b+git20170527.98255cb/test.py000066400000000000000000000101331311233656100172300ustar00rootroot00000000000000import unittest import random import numpy as np from genetic import individuals, recombination, selection, populations #TODO test recombination and selection class TestSingleChromosomeIndividual(unittest.TestCase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.engine = lambda x: random.randint(0, 50) self.mutrate = 0.1 def test_mutrate(self): with self.assertRaises(ValueError): individuals.SingleChromosomeIndividual(self.engine, 1.2, l=10) with self.assertRaises(ValueError): individuals.SingleChromosomeIndividual(self.engine, -0.1, l=10) with self.assertRaises(ValueError): individuals.SingleChromosomeIndividual(self.engine, 1, l=10) individuals.SingleChromosomeIndividual(self.engine, self.mutrate, l=10) def test_engine_and_length(self): with self.assertRaises(ValueError): individuals.SingleChromosomeIndividual([self.engine], self.mutrate, l=10) with self.assertRaises(ValueError): individuals.SingleChromosomeIndividual(self.engine, self.mutrate) with self.assertRaises(ValueError): individuals.SingleChromosomeIndividual([self.engine, 1], self.mutrate) individuals.SingleChromosomeIndividual(self.engine, self.mutrate, l=10) individuals.SingleChromosomeIndividual([self.engine], self.mutrate, l=1) class TestPanmicticPopulation(unittest.TestCase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.engine = lambda x: random.randint(0, 50) self.fitness = lambda x: - abs(200 - sum(x.genome)) self.size = 100 self.indiv = individuals.SingleChromosomeIndividual(self.engine, 0.1, 50) self.select = selection.bimodal(0.2, 0.05) self.nlegends = 10 def test_single_thread(self): ancestors = [self.indiv] * 2 pop = populations.PanmicticPopulation(ancestors, self.size, self.fitness, self.select, self.nlegends) errors1 = list(map(abs, pop.evolve(5, jobs=1))) errors2 = list(map(abs, pop.evolve(100, jobs=1))) self.assertTrue(np.mean(errors2) < np.mean(errors1)) def test_two_threads(self): ancestors = [self.indiv] * 2 pop = populations.PanmicticPopulation(ancestors, self.size, self.fitness, self.select, self.nlegends) errors1 = list(map(abs, pop.evolve(5, jobs=2))) errors2 = list(map(abs, pop.evolve(100, jobs=2))) self.assertTrue(np.mean(errors2) < np.mean(errors1)) def test_legends(self): ancestors = [self.indiv] * 2 with self.assertRaises(ValueError): populations.PanmicticPopulation(ancestors, self.size, self.fitness, self.select, -10) with self.assertRaises(ValueError): populations.PanmicticPopulation(ancestors, self.size, self.fitness, self.select, 0.1) pop = populations.PanmicticPopulation(ancestors, self.size, self.fitness, self.select, self.nlegends) errors1 = list(map(abs, pop.evolve(1, jobs=1))) legends1 = pop.legends errors2 = list(map(abs, pop.evolve(49, jobs=1))) legends2 = pop.legends legendary_scores1 = [abs(legend[0]) for legend in legends1] legendary_scores2 = [abs(legend[0]) for legend in legends2] self.assertTrue(np.mean(legendary_scores2) < np.mean(legendary_scores1)) if __name__ == "__main__": unittest.main()