[
  {
    "path": ".gitignore",
    "content": "# Created by .ignore support plugin (hsz.mobi)\n### Python template\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*,cover\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# PyCharm\n.idea/"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Csaba Gor\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Readme.md",
    "content": "[![codebeat badge](https://codebeat.co/badges/f72db301-fd66-4c05-b1ca-9b8c8196f06e)](https://codebeat.co/projects/github-com-csxeba-evolute-master)\n# Evolute\nEvolutionary algorithm toolbox\n\n## Documentation\n\n**Evolute** is a simple tool for quick experimentation with evolutionary\nalgorithms for numerical optimization.\n\nIt defines a **population** of individuals, represented as floating point\nvectors, and applies a configurable set of evolutionary **operators** to\nthem in a predefined order.\n\nThe order is the following:\n1. **Selection**: a subset of individuals are discarded, depending on\ntheir fitness value\n2. **Reproduction**: the discarded individuals are replaced by new\nindividuals, somehow generated from the survivors\n3. **Mutation**: mutate some of the individuals\n4. **Update**: update the fitnesses of the individuals \n\nSome nomenclature:\n- **individual**: a single member of the population.\n- **genotype**: refers to an individual in the encoded state.\n*Evolute* encodes information in floating point vectors.\n- **locus**: a member of a genotype (a single scalar in the vector)\n- **phenotype**: refers to an individual in the decoded state. In the\ncase of neuroevolution, this would be a neural network object, which\nis encoded as a genotype. Evolute is not doing any *genotype - phenotype*\nconversion, this has to be implemented by the user in the fitness\ncalculation.\n\n\n### Module: *population*\n\nDefines containers for individuals and aggregates the operators\napplied to them. Currently the following population types are defined:\n- **GeneticPopulation**: can be used for genetic algorithms, simply\nevaluates the fitnesses and applies the operators in the above specified\norder. Its constructor accepts the following arguments:\n  - **loci**: number of elements in an individual's chromosome\n  - **fitness_wrapper**: instance of a class in evolute.evaluation\n  - **limit**: maximum number of individuals, defaults to 100\n  - **operators**: an instance of *evolute.operators.Operators*, see operators later\n  - **initializer**: instance of a class defined in evolute.initialization, optional\n\nWill add support for MemeticPopulation, which allows for explicit modification\nof the individuals during fitness calculation\n\nAn evolutionary optimization can be run using the population.run() method, which\naccepts the following arguments:\n- **epochs**: how many iterations should be done\n- **survival_rate**: reset survival rate\n- **mutation_rate**: reset mutation rate\n- **force_update_at_every**: at every *k*th iteration, force a complete\nfitness-recalculation\n- **verbosity**: 0 is silent, > 0 is verbose.\n- **history**: evolute.utils.history.History object, in which runtime metrics\ncan be recorded. If omitted, one is instantiated implicitly.\n\nThe run() method returns a history object with generation, best_grade, mean_grade\nand grade_stdev recorded by default. \n\nA population can be saved and loaded with the save() and load() methods, which\nproduces a gzip-compressed pickle of the Population object.\n\nSingle epoch can be also run with the epoch() method and an update can be forced\nwith the update() method.\n\nFor convenience, these methods are defined:\n- **epoch()**: use this to evaluate a single epoch\n- **update()**: forces a fitness update\n- **simple_fitness()**: class factory method, which takes a nude fitness function\nand wraps it implicitly with SimpleFitness (see below).\n- **get_individual(index)**\n- **set_indivudual(index, individual)**\n- **get_best()**: the current best individual with the lowest fitness/grade\n- **get_champion**: the all-time best individual is alway stored implicitly,\nand can be accessed here.\n \n\n### Module: *evaluation*\n\nDefines different wrappers for fitness functions.\n**Fitness** is a scalar value assigned to every individual.\nCurrently in Evolute, the lower fitness is the better.\n\nSince a fitness function can be any kind of logic, fitness wrappers\nexpect a function pointer or some kind of callable. Some advanced\nwrappers are there to support possibly multiple fitness functions\nor functions with multiple return values. In the end, the fitness\nhas to be reduced to a single scalar.\n\nThe process of combining multiple fitnesses to a single scalar is\ntermed **grading** in Evolute.\n\n#### Fitness wrappers\n\nCurrently the following fitness wappers are defined in\n*evolute.evaluation*:\n\n- **SimpleFitness**: wraps a simple function with a single scalar\nreturn value. Its constructor expects the following arguments:\n  - **fitness_function**: a function reference or callable object\n  - **constants**: optional dictionary of constants to be passed by name\n\nVariables may be passed to the fitness function during the running of\nthe algorithm.\n\n- **MultipleFitnesses**: wraps multiple fitness functions. The\nconstructor expects the following arguments:\n  - **functions_by_name**: dictionary of fitness function references\nor callable objects. They need to be identified by a unique name or ID\n  - **contants_by_function_name**: optional dictionary of arguments (as dicts as well).\nThey have to be referenced by the same name or ID.\n  - **order_by_name**: optional, an ordered iterable of the IDs if call order\nmatters.\n  - **grader**: grader object or any callable which takes the array of\nfitnesses and produces a single scalar grade from them. Defaults to\nsimple summation.\n\n- **MultiReturnFitness**: wrapper for a single fitness function which\nreturns multiple fitness values. The constructor expects the following\narguments:\n  - **number_of_return_values**\n  - **constants**: optional\n  - **grader**: optional, defaults to simple summation\n  \n#### Graders\n\nMore advanced fitness wrappers expect a Grader instance defined in\n*evolute.evaluation.grade* or any callable which accepts a NumPy\narray and returns a single scalar.\n\nSome graders are available in *evolute.evaluation*:\n\n- **SumGrader**: simply sums the fitness values\n- **WeightedSumGrader**: accepts a set of weights in its constructor\nand produces a weighted sum (dot product) with the fitness values.\n\n### Module: *initialization*\n\nThis module defines initializers: objects for random population\ninitialization strategies. Currently the following distributions\nare available in *evolute.initialization*:\n\n- **NormalRandom**: with customizable **mean** and **stdev**\n- **UniformRandom**: with customizable **low** and **high**\n- **OrthogonalNormal**: produces a diagonal random matrix \n \n### Module: *operators*\n \nEvolutionary operators are defined here. This module defines an\n**Operators** class, which aggregates all the operators needed to\nrun an evolutionary optimization. Operators' constructor takes the\nfollowing arguments:\n- **selection_op**\n- **mutate_op**\n- **mate_op**\n\nSubmodules define the particular operator types, which are the following:\n\n#### Selection\n\nDuring selection, a subset of individuals are discarded. The logic by\nwhich these are specified depends on the actual implementation:\n\n- **Elitism**: selects the top m% of individuals. Its constructor\naccepts the following arguments:\n  - **selection_rate**: scalar between 0 and 1 to specify the rate of\ndiscarded individuals\n  - **mate_op**: a mate operator, which is used to fill up the slots\nwhere individuals are missing (applies reproduction). Details on these\nlater.\n  - **exclude_self_mating**: bool, whether to disallow mating an individual\nwith itself\n\nIf no selection operator is defined, every selection-using class defaults\nto *Elitism*.\n\n#### Mate\n\nMating is defined as some kind of combination of two individuals to\nreproduce a new individual. Currently the following mate operators\nare defined:\n\n- **LambdaMate**: wraps a user-defined function, which takes two\nindividuals and produces a single individual\n- **RandomPickMate**: randomly pics loci from the two genotypes\n- **SmoothMate**: takes the mean of the two genotypes\n- **ScatterMateWrapper**: wrapper for any mate operator. When applied,\nit adds gaussian noise to the new individual. Its constructor expects\nthe following parameters:\n  - **base**: reference to the base mate operator object\n  - **stdev**: standard deviation of the additive gaussian noise\n  \nIf no mate operator is defined, every mate-using class defaults to\n*RandomPickMate*.\n\n#### Mutate\n\nThis operator takes the whole population and mutates its individuals\nby perturbing them with some kind of noise. The following mutation\noperators are available:\n\n- **UniformLocuswiseMutation**: adds uniform distributed noise to\nindividuals. Mutants are determined on the locus level, and mutation\nrate (see below) is adjusted for this. Its constructor takes the\nfollowing arguments:\n  - **rate**: rate by which mutations occur in the population. It has\n  to be given as a rate of individuals but it is implicitly corrected\n  for loci.\n  - **low** and **high**: parameters of the uniform distribution\n- **NormalIndividualwiseMutation**: adds normal random noise to\nindividuals. Mutants are determined on the individuals' level. Its\nconstructor takes the following arguments:\n  - **rate**\n  - **stdev**\n \nIf no mutate operator is defined, every mutate-using class defaults\nto *UniformLocuswiseMutation*.\n\n### Module: utility\n\nSome useful stuff here. Maybe the most interesting is the\n*evolute.keras_utility* module, which defines some helpers to interface\nwith Keras and do some Neuroevolution on the weights of a network.\n"
  },
  {
    "path": "evolute/__init__.py",
    "content": "from .population import GeneticPopulation\n"
  },
  {
    "path": "evolute/evaluation/__init__.py",
    "content": "from .fitness import FitnessBase, SimpleFitness, MultipleFitnesses, MultiReturnFitness\nfrom .grade import GraderBase, SumGrader, WeightedSumGrader\n"
  },
  {
    "path": "evolute/evaluation/fitness.py",
    "content": "import numpy as np\n\nfrom .grade import SumGrader\n\n\nclass FitnessBase:\n\n    def __init__(self, no_fitnesses):\n        self.no_fitnesses = no_fitnesses\n\n    def __call__(self, phenotype):\n        raise NotImplementedError\n\n\nclass SimpleFitness(FitnessBase):\n\n    def __init__(self, fitness_function, constants: dict=None, **kw):\n        super().__init__(no_fitnesses=1)\n        self.function = fitness_function\n        self.constants = {} if constants is None else constants\n        self.constants.update(kw)\n\n    def __call__(self, phenotype, **variables):\n        return self.function(phenotype, **self.constants, **variables)\n\n\nclass MultipleFitnesses(FitnessBase):\n\n    def __init__(self, functions_by_name, constants_by_function_name=None, order_by_name=None, grader=None):\n        super().__init__(no_fitnesses=len(functions_by_name))\n        if len(functions_by_name) < 2:\n            raise ValueError(\"MultipleFitnesses needs more than one fitness!\")\n        self.functions = functions_by_name\n        self.order = order_by_name or list(self.functions)\n        self.constants = constants_by_function_name or {k: {} for k in self.order}\n        self.grader = grader or SumGrader()\n        if len(self.order) != len(self.functions) or any(o not in self.functions for o in self.order):\n            raise ValueError(\"The specified order is wrong: {}\".format(self.order))\n        if len(self.constants) != len(self.functions) or any(k not in self.functions for k in self.constants):\n            raise ValueError(\"The specified constants are wrong: {}\".format(self.constants))\n\n    def __call__(self, phenotype, **variables_by_function):\n        fitness = np.array(\n            [self.functions[funcname](phenotype, **self.constants[funcname], **variables_by_function[funcname])\n             for funcname in self.order])\n        return self.grader(fitness)\n\n\nclass MultiReturnFitness(FitnessBase):\n\n    def __init__(self, fitness_function, number_of_return_values, constants: dict=None, grader=None):\n        super().__init__(no_fitnesses=number_of_return_values)\n        self.function = fitness_function\n        self.constants = {} if constants is None else constants\n        self.grader = SumGrader() if grader is None else grader\n\n    def __call__(self, phenotype, **variables):\n        fitness = np.array(self.function(phenotype, **self.constants, **variables))\n        return self.grader(fitness)\n"
  },
  {
    "path": "evolute/evaluation/grade.py",
    "content": "import numpy as np\n\n\nclass GraderBase:\n\n    def __call__(self, fitness):\n        raise NotImplementedError\n\n\nclass SumGrader(GraderBase):\n\n    def __call__(self, fitness):\n        return np.sum(fitness)\n\n\nclass WeightedSumGrader(GraderBase):\n\n    def __init__(self, weights):\n        self.weights = np.ones(1) if weights is None else weights\n\n    def __call__(self, fitness):\n        return np.dot(fitness, self.weights)\n"
  },
  {
    "path": "evolute/initialization/__init__.py",
    "content": "from .initializer import NormalRandom, UniformRandom, OrthogonalNormal\n\nDefaultInitializer = NormalRandom\n"
  },
  {
    "path": "evolute/initialization/initializer.py",
    "content": "import numpy as np\n\n\nclass Initializer:\n\n    def initialize(self, *shape):\n        raise NotImplementedError\n\n\nclass NormalRandom(Initializer):\n\n    def __init__(self, mean=0., stdev=1.):\n        self.mean = mean\n        self.stdev = stdev\n\n    def initialize(self, *shape):\n        return np.random.randn(*shape) * self.stdev + self.mean\n\n\nclass UniformRandom(Initializer):\n\n    def __init__(self, low=-1, high=1.):\n        self.low = low\n        self.high = high\n\n    def initialize(self, *shape):\n        return np.random.uniform(self.low, self.high, shape)\n\n\nclass OrthogonalNormal(Initializer):\n\n    def initialize(self, *shape):\n        individials = NormalRandom().initialize(shape)\n        d, V = np.linalg.eig(np.cov(individials.T))\n        D = np.diag(1. / np.sqrt(d + 1e-7))\n        W = V @ D @ V.T\n        return individials @ W\n"
  },
  {
    "path": "evolute/operators/__init__.py",
    "content": "from .mate import DefaultMate, LambdaMate, SmoothMate, RandomPickMate, ScatterMateWrapper\nfrom .mutate import DefaultMutate, UniformLocuswiseMutation, NormalIndividualwiseMutation\nfrom .selection import DefaultSelection, Elitism\nfrom .operators import Operators\n"
  },
  {
    "path": "evolute/operators/mate.py",
    "content": "import numpy as np\n\n\nclass MateBase:\n\n    def apply(self, ind1, ind2):\n        pass\n\n    def __call__(self, ind1, ind2):\n        return self.apply(ind1, ind2)\n\n\nclass LambdaMate(MateBase):\n\n    def __init__(self, function_ref, **kw):\n        self.kwargs = kw\n        self.apply = lambda ind1, ind2: function_ref(ind1, ind2, **self.kwargs)\n\n\nclass RandomPickMate(MateBase):\n\n    def apply(self, ind1, ind2):\n        return np.where(np.random.uniform(size=ind1.shape) < 0.5, ind1, ind2)\n\n\nclass SmoothMate(MateBase):\n\n    def apply(self, ind1, ind2):\n        return np.mean((ind1, ind2), axis=0)\n\n\nDefaultMate = RandomPickMate\n\n\nclass ScatterMateWrapper(MateBase):\n\n    def __init__(self, base=DefaultMate, stdev=1.):\n        if isinstance(base, type):\n            base = base()\n        self.base = base\n        self.stdev = stdev\n\n    def apply(self, ind1, ind2):\n        return self.base(ind1, ind2) + np.random.randn(*ind1.shape) * self.stdev\n"
  },
  {
    "path": "evolute/operators/mutate.py",
    "content": "import numpy as np\n\n\nclass MutationBase:\n\n    def __init__(self, rate=0.1):\n        self.rate = rate\n        self.mask = None\n\n    def set_rate(self, rate):\n        if rate < 0. or rate > 1.:\n            raise ValueError(\"Mutation rate has to be >= 0 and <= 1\")\n        self.rate = rate\n\n    def apply(self, individuals, inplace=False):\n        raise NotImplementedError\n\n    def __call__(self, individuals, inplace=False):\n        return self.apply(individuals)\n\n\nclass UniformLocuswiseMutation(MutationBase):\n\n    def __init__(self, rate=0.1, low=-1., high=1.):\n        super().__init__(rate)\n        self.low = low\n        self.high = high\n\n    def set_params(self, low=None, high=None):\n        self.low = self.low if low is None else low\n        self.high = self.high if high is None else high\n\n    def apply(self, individuals, inplace=False):\n        indshape = individuals.shape\n        if self.rate == 0.:\n            self.mask = np.zeros(len(individuals), dtype=bool)\n            return individuals\n        elif self.rate == 1.:\n            mask = np.ones(indshape, dtype=bool)\n        else:\n            mask = np.random.uniform(size=indshape) < (self.rate / indshape[-1])\n        self.mask = np.any(mask, axis=1)\n        noise = np.random.uniform(self.low, self.high, size=mask.sum())\n        if not inplace:\n            mutants = individuals.copy()\n            mutants[mask] += noise\n            return mutants\n        individuals[mask] += noise\n\n\nclass NormalIndividualwiseMutation(MutationBase):\n\n    def __init__(self, rate=0.1, stdev=1.):\n        super().__init__(rate)\n        self.std = stdev\n\n    def set_param(self, stdev):\n        self.std = stdev\n\n    def apply(self, individuals, inplace=False):\n        mask = np.random.uniform(size=len(individuals)) < self.rate\n        noise = np.random.normal(loc=0., scale=self.std, size=(mask.sum(), individuals.shape[-1]))\n        if not inplace:\n            mutants = individuals.copy()\n            mutants[mask] += noise\n            return mutants\n        individuals[mask] += noise\n        self.mask = mask\n\n\nDefaultMutate = UniformLocuswiseMutation\n"
  },
  {
    "path": "evolute/operators/operators.py",
    "content": "import numpy as np\nfrom . import DefaultSelection, DefaultMutate, DefaultMate\n\n\nclass Operators:\n\n    def __init__(self, selection_op=None, mutate_op=None, mate_op=None):\n        self.selection = DefaultSelection() if selection_op is None else selection_op\n        self.mutation = DefaultMutate() if mutate_op is None else mutate_op\n        self._clarify_mate_operator(mate_op)\n\n    def _clarify_mate_operator(self, mate_op):\n        mate_set_in_selection = self.selection.mate_op is not None\n        mate_set_here = mate_op is not None\n        if mate_set_in_selection and mate_set_here:\n            print(\" [w] Evolute: differring mate ops, using the one in Selection!\")\n        elif mate_set_in_selection and not mate_set_here:\n            pass\n        elif not mate_set_in_selection and mate_set_here:\n            self.selection.set_mate_operator(mate_op)\n        elif not mate_set_in_selection and not mate_set_here:\n            self.selection.set_mate_operator(DefaultMate())\n        else:\n            assert False, \"O.o\"  # w00t\n\n    def invalid_individual_indices(self):\n        return np.where(self.selection.mask | self.mutation.mask)[0]\n"
  },
  {
    "path": "evolute/operators/selection.py",
    "content": "import numpy as np\n\n\nclass SelectionBase:\n\n    def __init__(self, selection_rate=0.5, mate_op=None, exclude_self_mating=True):\n        self.mate_op = mate_op\n        self.rate = None\n        self._selection_mask = None\n        self.exclude_self_mating = exclude_self_mating\n        self.set_selection_rate(selection_rate)\n\n    @property\n    def mask(self):\n        return self._selection_mask\n\n    def set_mate_operator(self, mate_op):\n        self.mate_op = mate_op\n\n    def _stream_of_parent_indices(self):\n        assert self._selection_mask is not None\n        survivor_mask = ~self._selection_mask\n        arg1 = np.argwhere(survivor_mask)[:, 0]\n        assert len(arg1) > 1\n        arg2 = np.copy(arg1)\n        limit = sum(self._selection_mask)\n        n = 0\n        while n < limit:\n            np.random.shuffle(arg1)\n            np.random.shuffle(arg2)\n            for ix1, ix2 in zip(arg1, arg2):\n                if n >= limit:\n                    raise StopIteration\n                if self.exclude_self_mating and ix1 == ix2:\n                    continue\n                yield ix1, ix2\n                n += 1\n\n    def set_survival_rate(self, survival_rate):\n        if survival_rate <= 0. or survival_rate > 1.:\n            raise ValueError(\"The rate of survival has to be greater than 0 and less or equal to 1\")\n        self.rate = survival_rate\n\n    def set_selection_rate(self, selection_rate):\n        if selection_rate <= 0. or selection_rate > 1.:\n            raise ValueError(\"The rate of selection has to be greater than 0 and less or equal to 1\")\n        self.rate = 1. - selection_rate\n\n    def apply(self, individuals, fitnesses, inplace=False):\n        raise NotImplementedError\n\n    def __call__(self, individuals, fitnesses, inplace=False):\n        return self.apply(individuals, fitnesses, inplace)\n\n\nclass Elitism(SelectionBase):\n\n    def apply(self, individuals, fitnesses, inplace=False):\n        self._selection_step(individuals, fitnesses)\n        if not inplace:\n            return self._reproduction_copy(individuals)\n        self._reproduction_inplace(individuals)\n\n    def _selection_step(self, individuals, grades):\n        limit, loci = individuals.shape\n        self._selection_mask = np.ones(limit, dtype=bool)\n        if self.rate:\n            no_survivors = max(2, int(limit * self.rate))\n            survivors = np.argsort(grades)[:no_survivors]\n            self._selection_mask[survivors] = False\n\n    def _reproduction_inplace(self, individuals):\n        individuals[self._selection_mask] = [\n            self.mate_op(individuals[idx1], individuals[idx2])\n            for idx1, idx2 in self._stream_of_parent_indices()\n        ]\n\n    def _reproduction_copy(self, individuals):\n        offspring = individuals.copy()\n        new_indivs = [\n            self.mate_op(offspring[idx1], offspring[idx2])\n            for idx1, idx2 in self._stream_of_parent_indices()\n        ]\n        offspring[self._selection_mask] = new_indivs\n        return offspring\n\n\nDefaultSelection = Elitism\n"
  },
  {
    "path": "evolute/population/__init__.py",
    "content": "from .population import Population\nfrom .genetic import GeneticPopulation\n"
  },
  {
    "path": "evolute/population/genetic.py",
    "content": "from .population import Population\n\n\nclass GeneticPopulation(Population):\n\n    def update_individual(self, index, **fitness_kw):\n        self.fitnesses[index] = self.fitness(self.get_individual(index), **fitness_kw)\n"
  },
  {
    "path": "evolute/population/population.py",
    "content": "import numpy as np\n\nfrom ..initialization import DefaultInitializer\nfrom ..operators import Operators\nfrom ..evaluation import SimpleFitness\nfrom ..utility.history import History\n\n\nclass Population:\n\n    def __init__(self, loci: int,\n                 fitness_wrapper,\n                 limit=100,\n                 operators=None,\n                 initializer=None):\n        \"\"\"\n        :param loci: number of elements in an individual's chromosome\n        :param fitness_wrapper: accepts an individual, returns fitnesses\n        :param limit: maximum number of individuals\n        :param operators: an instance of Operators\n        :param initializer: instance of a class defined in evolute.initialization\n         and index of mutants\n        \"\"\"\n        self.loci = loci\n        self.limit = limit\n        self.fitness = fitness_wrapper\n        self.fitnesses = None\n        self.operators = Operators() if operators is None else operators\n\n        self.initializer = DefaultInitializer() if initializer is None else initializer\n        self.individuals = self.initializer.initialize(self.limit, self.loci)\n\n        self.age = 0\n        self.champion = 0\n\n    @classmethod\n    def simple_fitness(cls, fitness_callback,\n                       loci, limit=100,\n                       initializer=None,\n                       operators=None,\n                       fitness_constants=None):\n        fitness_wrapper = SimpleFitness(fitness_callback, {} if fitness_constants is None else fitness_constants)\n        return cls(loci=loci, fitness_wrapper=fitness_wrapper, limit=limit,\n                   initializer=initializer, operators=operators)\n\n    def get_individual(self, index):\n        return self.individuals[index]\n\n    def set_individual(self, index, individual):\n        self.individuals[index] = individual\n\n    def get_best(self):\n        return self.get_individual(np.argmin(self.fitnesses))\n\n    def get_champion(self):\n        return self.get_individual(self.champion)\n\n    def get_fitness_weighted_average_individual(self):\n        weights = (self.fitnesses - self.fitnesses.mean()) / self.fitnesses.std()\n        return weights @ self.individuals\n\n    def run(self, epochs: int,\n            survival_rate: float=0.5,\n            mutation_rate: float=0.1,\n            force_update_at_every: int=0,\n            verbosity: int=1,\n            history=None):\n        \"\"\"\n        :param epochs: number of epochs to run for\n        :param survival_rate: 0-1, how many individuals survive the selection\n        :param mutation_rate: 0-1, rate of mutation at each epoch\n        :param force_update_at_every: complete reupdate at specified intervals\n        :param verbosity: 1 is verbose, > 1 also prints out v - 1 individuals\n        :param history: History object in which run stats should be recorded\n        :return: history object containing run statistics\n        \"\"\"\n\n        history = (History([\"generation\", \"best_grade\", \"mean_grade\", \"grade_std\"])\n                   if history is None else history)\n        self.operators.selection.set_survival_rate(survival_rate)\n        self.operators.mutation.set_rate(mutation_rate)\n        for epoch in range(1, epochs+1):\n            if verbosity:\n                print(\"-\" * 50)\n                print(\"Epoch {}/{}\".format(epochs, epoch))\n            self.epoch(force_update=force_update_at_every and epoch % force_update_at_every == 0,\n                       verbosity=verbosity)\n            history.record({\"generation\": self.age,\n                            \"best_grade\": self.fitnesses.min(),\n                            \"mean_grade\": self.fitnesses.mean(),\n                            \"grade_std\": self.fitnesses.std()})\n        if verbosity:\n            print()\n        return history\n\n    def epoch(self, force_update=False, verbosity=1, **fitness_kw):\n        if not self.age:\n            self._initialize(verbosity, **fitness_kw)\n\n        self.operators.selection(self.individuals, self.fitnesses, inplace=True)\n        self.individuals = self.operators.mutation(self.individuals, inplace=False)\n        self.update(force_update, verbose=verbosity, **fitness_kw)\n        self.age += 1\n\n    def _initialize(self, verbosity, **fitness_kw):\n        if verbosity:\n            print(\"EVOLUTION: initial update...\")\n        self.fitnesses = np.empty(self.limit)\n        self.update(forced=True, verbose=verbosity, **fitness_kw)\n        if verbosity:\n            print(\"EVOLUTION: initial mean grade :\", self.fitnesses.mean())\n            print(\"EVOLUTION: initial std of mean:\", self.fitnesses.std())\n            print(\"EVOLUTION: initial best grade :\", self.fitnesses.min())\n\n    def update(self, forced=False, verbose=0, **fitness_kw):\n        inds = self._invalidated_individual_indices(force_update=forced)\n        for ind in inds.flat:\n            if verbose:\n                print(\"\\rUpdating {}/{}\".format(self.limit, ind+1), end=\"\")\n            self.update_individual(ind, **fitness_kw)\n        if verbose:\n            print(\"\\rUpdating {}/{}\".format(self.limit, self.limit), end=\"\")\n            print(\" Best grade:\", self.fitnesses.min())\n        chump = self.fitnesses.argmin()\n        if self.fitnesses[chump] < self.fitnesses[self.champion]:\n            self.champion = chump\n\n    def update_individual(self, index, **fitness_kw):\n        raise NotImplementedError\n\n    def _invalidated_individual_indices(self, force_update):\n        return np.arange(self.limit) if force_update else self.operators.invalid_individual_indices()\n\n    def save(self, path):\n        import pickle\n        import gzip\n        with gzip.open(path, \"wb\") as handle:\n            pickle.dump(self, handle)\n\n    @staticmethod\n    def load(path):\n        import pickle\n        import gzip\n        return pickle.load(gzip.open(path, \"rb\"))\n"
  },
  {
    "path": "evolute/utility/__init__.py",
    "content": ""
  },
  {
    "path": "evolute/utility/describe.py",
    "content": "import numpy as np\n\n\ndef describe(population, show=0):\n    showme = np.argsort(population.grades)[:show]\n    chain = \"-\" * 50 + \"\\n\"\n    shln = len(str(show))\n    for i, index in enumerate(showme, start=1):\n        genomechain = \", \".join(\n            \"{:>6.4f}\".format(loc) for loc in\n            np.round(population.individuals[index], 4))\n        fitnesschain = \"[\" + \", \".join(\n            \"{:^8.4f}\".format(fns) for fns in\n            population.fitnesses[index]) + \"]\"\n        chain += \"TOP {:>{w}}: [{:^14}] F = {:<} G = {:.4f}\\n\".format(\n            i, genomechain, fitnesschain, population.grades[index],\n            w=shln)\n    best_arg = population.grades.argmin()\n    chain += \"Best Grade : {:7>.4f} \".format(population.grades[best_arg])\n    chain += \"Fitnesses: [\"\n    chain += \", \".join(\"{}\".format(f) for f in population.fitnesses[best_arg])\n    chain += \"]\\n\"\n    chain += \"Mean Grade : {:7>.4f}, STD: {:7>.4f}\\n\" \\\n        .format(population.grades.mean(), population.grades.std())\n    print(chain)\n"
  },
  {
    "path": "evolute/utility/history.py",
    "content": "class History:\n\n    def __init__(self, aspects=()):\n        self.history = {aspect: [] for aspect in [\"generation\"] + list(aspects)}\n\n    def record(self, data):\n        for key in data:\n            self.history[key].append(data[key])\n\n    def __getitem__(self, item):\n        return self.history[item]\n"
  },
  {
    "path": "evolute/utility/keras_utility.py",
    "content": "import numpy as np\n\n\ndef get_keras_weights(model, folded=False):\n    w_tensors = model.trainable_weights\n    if folded:\n        return w_tensors\n    return np.concatenate([w.flat for w in w_tensors])\n\n\ndef get_keras_number_of_trainables(model):\n    return sum(w.size for w in model.trainable_weights)\n\n\nclass WeightFolding:\n\n    def __init__(self, model):\n        self.shapes = [w.shape for w in model.get_weights()]\n        self.sizes = [np.prod(shape) for shape in self.shapes]\n\n    def __call__(self, individual):\n        phenotype = []\n        start = 0\n        for shape, size in zip(self.shapes, self.sizes):\n            end = start + size\n            phenotype.append(individual[start:end].reshape(shape))\n            start = end\n        return phenotype\n"
  },
  {
    "path": "evolute/utility/test_utils.py",
    "content": "import numpy as np\n\n\ndef is_standardish(array, globally=False, epsilon=1e-7):\n    if globally:\n        return np.allclose(array.mean(), 0., atol=epsilon) and np.allclose(array.std(), 1., atol=epsilon )\n    return np.allclose(array.mean(axis=0), 0., atol=epsilon) and np.allclose(array.std(axis=0), 1., atol=epsilon)\n\n\ndef is_normalish(array, epsilon=1e-7):\n    return np.allclose(np.linalg.norm(array, axis=1), 1., atol=epsilon)\n"
  },
  {
    "path": "examples/xp_evolve_net.py",
    "content": "import numpy as np\n\nfrom matplotlib import pyplot as plt\n\nfrom brainforge import LayerStack\nfrom brainforge.layers import DenseLayer\nfrom brainforge.cost import costs\n\nfrom evolute.operators import ScatterMateWrapper, SmoothMate, Operators\nfrom evolute import GeneticPopulation\n\nnp.random.seed(1234)\n\nrX = np.linspace(-6., 6., 200)[:, None]\nrY = np.sin(rX)\n\narg = np.arange(len(rX))\nnp.random.shuffle(arg)\ntarg, varg = arg[:100], arg[100:]\ntarg.sort()\nvarg.sort()\n\ntX, tY = rX[targ], rY[targ]\nvX, vY = rX[varg], rY[varg]\n\ntX += np.random.randn(*tX.shape) / np.sqrt(tX.size*0.25)\n\nloss_fn = costs[\"mse\"]\n\n\ndef fitness(phenotype, layerstack, X, Y):\n    layerstack.set_weights(phenotype)\n    return loss_fn(layerstack.predict(X), Y)\n\n\ndef forge_layerstack():\n    return LayerStack(input_shape=(1,), layers=[\n        DenseLayer(30, activation=\"tanh\"),\n        DenseLayer(30, activation=\"tanh\"),\n        DenseLayer(1, activation=\"linear\")\n    ])\n\n\ndef get_population():\n    layers = forge_layerstack()\n    operators = Operators(mate_op=ScatterMateWrapper(SmoothMate, 3.))\n    pop = GeneticPopulation.simple_fitness(limit=100, loci=layers.nparams,\n                                           operators=operators,\n                                           fitness_callback=fitness,\n                                           fitness_constants={\"layerstack\": layers})\n    return layers, pop\n\n\ndef xperiment():\n    layers, pop = get_population()\n    layers = forge_layerstack()\n    tpred = layers.predict(tX)\n    vpred = layers.predict(vX)\n    plt.ion()\n    plt.plot(tX, tY, \"b--\", alpha=0.5, label=\"Training data (noisy)\")\n    plt.plot(rX, rY, \"r--\", alpha=0.5, label=\"Validation data (clean)\")\n    plt.ylim(min(rY)-1, max(rY)+1)\n    plt.plot(rX, np.zeros_like(rX), c=\"grey\", linestyle=\"--\")\n    tobj, = plt.plot(tX, tpred, \"bo\", markersize=3, alpha=0.5, label=\"Training pred\")\n    vobj, = plt.plot(vX, vpred, \"ro\", markersize=3, alpha=0.5, label=\"Validation pred\")\n    templ = \"Batch: {:>5} Cost = {:.4f}\"\n    t = plt.title(templ.format(0, 0))\n    plt.legend()\n    batchno = 1\n    while 1:\n        pop.epoch(X=tX, Y=tY)\n        layers.set_weights(pop.get_champion())\n        tpred = layers.predict(tX)\n        vpred = layers.predict(vX)\n        tobj.set_data(tX, tpred)\n        vobj.set_data(vX, vpred)\n        plt.pause(0.01)\n        t.set_text(templ.format(batchno, pop.fitnesses.min()))\n        batchno += 1\n\n\nif __name__ == '__main__':\n    xperiment()\n"
  },
  {
    "path": "examples/xp_keras.py",
    "content": "import numpy as np\n\nfrom keras.layers import Dense\nfrom keras.models import Sequential\nfrom keras.datasets import mnist\nfrom keras.utils import to_categorical\n\nfrom evolute import GeneticPopulation\nfrom evolute.evaluation import SimpleFitness\nfrom evolute.initialization import NormalRandom\nfrom evolute.operators import RandomPickMate, Operators, UniformLocuswiseMutation, ScatterMateWrapper\nfrom evolute.utility.keras_utility import WeightFolding\n\n\ndef pull_mnist():\n    learning, testing = mnist.load_data()\n    Xs, Ys = (learning[0], testing[0]), (learning[1], testing[1])\n    Xs = map(lambda x: (x.reshape(-1, 784) - 127.5) / 255., Xs)\n    Ys = map(lambda y: to_categorical(y, num_classes=10), Ys)\n    return tuple(Xs), tuple(Ys)\n\n\ndef fitness_callback(phenotype, model: Sequential, w_folder, X, Y):\n    model.set_weights(w_folder(phenotype))\n    cost, acc = model.evaluate(X, Y, verbose=0)\n    return cost\n\n\n(lX, tX), (lY, tY) = pull_mnist()\n\n\nann = Sequential([\n    Dense(64, activation=\"tanh\", input_dim=lX.shape[1]),\n    Dense(lY.shape[1], activation=\"softmax\")\n])\nann.compile(optimizer=\"sgd\", loss=\"categorical_crossentropy\", metrics=[\"acc\"])\n\nw_shapes = [w.shape for w in ann.get_weights()]\nw_flat = np.concatenate([w.flat for w in ann.get_weights()])\nw_folder = WeightFolding(ann)\n\nfitness = SimpleFitness(fitness_function=fitness_callback,\n                        constants={\"model\": ann, \"w_folder\": w_folder})\n\npopulation = GeneticPopulation(\n    limit=100,\n    loci=w_flat.size,\n    fitness_wrapper=fitness,\n    initializer=NormalRandom(mean=w_flat),\n    operators=Operators(mate_op=ScatterMateWrapper(RandomPickMate(), stdev=2.),\n                        mutate_op=UniformLocuswiseMutation(low=-3., high=3.))\n)\n\nBATCH_SIZE = 128\nbatch_stream = ((lX[start:start+BATCH_SIZE], lY[start:start+BATCH_SIZE])\n                for start in range(0, len(lX), BATCH_SIZE))\n\npopulation.operators.selection.set_selection_rate(0.98)\npopulation.operators.mutation.set_rate(0.0)\nfor i, (x, y) in enumerate(batch_stream, start=1):\n    population.epoch(X=x, Y=y, verbosity=0)\n    ann.set_weights(w_folder(population.get_best()))\n    cost, acc = ann.evaluate(tX, tY, verbose=0)\n    print(\"\\rBatch: {} Acc: {:.2%}, Cost: {:.4f}\".format(i, acc, cost))\n"
  },
  {
    "path": "examples/xp_quadratic.py",
    "content": "import numpy as np\nfrom matplotlib import pyplot as plt\n\nfrom evolute import GeneticPopulation\nfrom evolute.evaluation import SimpleFitness\n\n\ndef fitness(ind, target):\n    return np.linalg.norm(target - ind)\n\n\ndef main():\n    TARGET = np.array([3., 3.])\n\n    pop = GeneticPopulation(\n        loci=2,\n        fitness_wrapper=SimpleFitness(fitness, constants={\"target\": TARGET}),\n        limit=100)\n\n    plt.ion()\n    obj = plt.plot(*pop.individuals.T, \"bo\", markersize=2)[0]\n    plt.xlim([-2, 11])\n    plt.ylim([-2, 11])\n\n    X, Y = np.linspace(-2, 11, 50), np.linspace(-2, 11, 50)\n    X, Y = np.meshgrid(X, Y)\n    Z = np.array([fitness(np.array([x, y]), target=TARGET)\n                  for x, y in zip(X.ravel(), Y.ravel())]).reshape(X.shape)\n    CS = plt.contour(X, Y, Z, cmap=\"hot\")\n    plt.clabel(CS, inline=1, fontsize=10)\n    title_template = \"Best: [{:.4f}, {:.4f}], G: {:.4f}\"\n    title_obj = plt.title(title_template.format(0., 0., 0.))\n    plt.show()\n    means, stds, bests = [], [], []\n    for i in range(30):\n        pop.epoch(force_update=True, verbosity=0)\n        means.append(pop.fitnesses.mean())\n        stds.append(pop.fitnesses.std())\n        bests.append(pop.fitnesses.min())\n        obj.set_data(*pop.individuals.T)\n        title_obj.set_text(title_template.format(*pop.get_best(), pop.fitnesses.min()))\n        plt.pause(0.1)\n\n    means, stds, bests = tuple(map(np.array, (means, stds, bests)))\n    plt.close()\n    plt.ioff()\n    Xs = np.arange(1, len(means) + 1)\n    plt.plot(Xs, means, \"b-\", label=\"mean\")\n    plt.plot(Xs, means+stds, \"g--\", label=\"stdev\")\n    plt.plot(Xs, means-stds, \"g--\")\n    plt.plot(Xs, bests, \"r-\", label=\"best\")\n    plt.xlim([Xs.min()-1, Xs.max()+1])\n    plt.ylim([bests.min()-1, (means+stds).max()+1])\n    plt.legend()\n    plt.grid()\n    plt.show()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "examples/xp_simple.py",
    "content": "import numpy as np\n\nfrom matplotlib import pyplot as plt\n\nfrom evolute import GeneticPopulation\nfrom evolute.evaluation import SimpleFitness\n\nTARGET = np.ones(10) * 0.5\n\npop = GeneticPopulation(loci=10,\n                        fitness_wrapper=SimpleFitness(lambda ind: np.linalg.norm(ind - TARGET)))\n\nhistory = pop.run(100)\nhistory = {k: np.array(v) for k, v in history.history.items()}\n\nx = history[\"generation\"]\n\nplt.plot(x, history[\"mean_grade\"], \"r-\", label=\"mean\")\nplt.plot(x, history[\"mean_grade\"] + history[\"grade_std\"], \"b--\", label=\"std\")\nplt.plot(x, history[\"mean_grade\"] - history[\"grade_std\"], \"b--\")\nplt.plot(x, history[\"best_grade\"], \"g-\", label=\"mean\")\n\nplt.title(\"Population convergence\")\nplt.legend()\nplt.grid()\nplt.show()\n"
  },
  {
    "path": "requirements.txt",
    "content": "numpy"
  },
  {
    "path": "setup.py",
    "content": "from setuptools import setup, find_packages\n\nsetup(\n    name='evolute',\n    version='0.9.0',\n    packages=find_packages(),\n    url='https://github.com/csxeba/evolute.git',\n    license='MIT',\n    author='Csaba Gór',\n    author_email='csxeba@gmail.com',\n    description='Evolutionary algorithm toolbox',\n    long_description=open(\"Readme.md\").read(),\n    long_description_content_type='text/markdown'\n)\n"
  },
  {
    "path": "tests/test_evaluation.py",
    "content": "import unittest\n\nimport numpy as np\n\nfrom evolute.evaluation import WeightedSumGrader\n\n\nclass TestGrade(unittest.TestCase):\n\n    def setUp(self):\n        self.sample_fitness_vector = np.ones(3)\n        self.sample_fitness_weigts = np.ones(3) + 1\n\n    def test_weighted_sum_grader(self):\n        grader = WeightedSumGrader(weights=self.sample_fitness_weigts)\n        calced = grader(self.sample_fitness_vector)\n        self.assertEqual(calced, self.sample_fitness_vector @ self.sample_fitness_weigts)\n"
  },
  {
    "path": "tests/test_operator.py",
    "content": "import unittest\n\nimport numpy as np\n\nfrom evolute.operators import SmoothMate, RandomPickMate\nfrom evolute.operators import UniformLocuswiseMutation\nfrom evolute.operators import Elitism\n\n\nclass TestMate(unittest.TestCase):\n\n    def setUp(self):\n        self.sample_individuals = [\n            np.zeros(3), np.ones(3) + 1\n        ]\n\n    def test_random_pick_mated_offspring_only_contains_entries_from_parents(self):\n        mater = RandomPickMate()\n        offspring = mater(*self.sample_individuals)\n        eq = np.logical_or(offspring == 0., offspring == 2.)\n        self.assertTrue(np.all(eq))\n\n    def test_smooth_mate_produces_offspring_which_is_the_mean_of_parents(self):\n        mater = SmoothMate()\n        offspring = mater(*self.sample_individuals)\n        self.assertTrue(np.all(offspring == np.ones_like(offspring)))\n\n\nclass TestMutate(unittest.TestCase):\n\n    def setUp(self):\n        self.sample_individuals = np.zeros((3, 4))\n\n    def test_uniform_locuswise_op_mutates_every_locus_with_rate_1(self):\n        # Test may fail in the very unlikely case of a mutation perturbance element being exactly 0.\n        mutator = UniformLocuswiseMutation(rate=1.)\n        mutant = mutator(self.sample_individuals)\n        self.assertFalse(np.all(mutant == self.sample_individuals))\n\n\nclass TestSelection(unittest.TestCase):\n\n    def setUp(self):\n        self.sample_individuals = np.stack([np.zeros(3)]*9 + [np.ones(3) + 1.], axis=0)\n        self.sample_grades = np.arange(len(self.sample_individuals), 0, -1)\n\n    def test_number_of_selected_individuals_corresponds_to_set_selection_rate(self):\n        rate = 0.5\n        selector = Elitism(selection_rate=rate)\n        selector(self.sample_individuals, self.sample_grades)\n        self.assertEqual(selector.mask.sum(), rate * len(self.sample_individuals))\n\n    def test_elitism_with_an_engineered_population(self):\n        rate = 0.8\n        no_offsprings = int(rate * 10)\n\n        selector = Elitism(selection_rate=rate, mate_op=SmoothMate(), exclude_self_mating=True)\n        offspring = selector(self.sample_individuals, self.sample_grades, inplace=False)\n        self.assertTrue(np.all(offspring[:no_offsprings] == 1.))\n"
  }
]