# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['pyrimidine',
 'pyrimidine.benchmarks',
 'pyrimidine.learn',
 'pyrimidine.local_search']

package_data = \
{'': ['*']}

install_requires = \
['matplotlib>=3.8.0,<4.0.0',
 'numpy>=1.26.0,<2.0.0',
 'pandas>=2.1.0,<3.0.0',
 'scikit-learn>=1.1.0,<2.0.0',
 'scipy>=1.12.0,<2.0.0',
 'toolz>=0.12.0,<0.13.0']

setup_kwargs = {
    'name': 'pyrimidine',
    'version': '1.6',
    'description': 'A competitive framework tailored for GA, crafted with an emphasis on OOP and Algebra-Inspired Programming.',
    'long_description': '# pyrimidine\n\n`pyrimidine` is an extensible framework of genetic/evolutionary algorithm by Python. See [pyrimidine\'s document](https://pyrimidine.readthedocs.io/) for more details.\n\n![LOGO](logo.png)\n\n## Why\n\nWhy is the package named as “pyrimidine”? Because it begins with “py”.\n\n> — Are you kiding?\n>\n> — No, I am serious.\n\n## Download\n\nIt has been uploaded to [pypi](https://pypi.org/project/pyrimidine/), so download it with `pip install pyrimidine`, and also could download it from github.\n\n## Idea\n\nWe regard the population as a container of individuals, an individual as a container of chromosomes\nand a chromosome as a container(array) of genes.\n\nThe container could be a list or an array.\nThe container class has an attribute `element_class`, telling itself the type of the elements in it.\n\nFollowing is the part of the source code of `BaseIndividual` and `BasePopulation`.\n\n```python\nclass BaseIndividual(FitnessModel, metaclass=MetaContainer):\n    element_class = BaseChromosome\n    default_size = 1\n\nclass BasePopulation(PopulationModel, metaclass=MetaContainer):\n    element_class = BaseIndividual\n    default_size = 20\n```\n\nThere is mainly tow kinds of containers: list and tuple as in programming language `Haskell`. See following examples.\n\n```python\n# individual with chromosomes of type _Chromosome\n_Individual1 = BaseIndividual[_Choromosome]\n# individual with 2 chromosomes of type _Chromosome1 and _Chromosome2 respectively\n_Individual2 = MixedIndividual[_Chromosome1, _Chromosome2]\n```\n\n### Math expression\n$s$ of type $S$ is a container of $a:A$, represented as follows:\n\n```\ns={a:A}:S\n```\n\nWe define a population as a container of individuals or chromosomes, and an individual is a container of chromosomes.\n\nAlgebraically, an indivdiual has only one chromosome is equivalent to a chromosome mathematically. A population could also be a container of chromosomes. If the individual has only one chromosome, then just build the population based on chromosomes directly.\n\nThe methods are the functions or operators defined on $s$.\n\n## Use\n\n### Main classes\n\n- BaseGene: the gene of chromosome\n- BaseChromosome: sequence of genes, represents part of a solution\n- BaseIndividual: sequence of chromosomes, represents a solution of a problem\n- BasePopulation: a container of individuals, represents a container of a problem\n                also the state of a stachostic process\n- BaseMultipopulation: a container of population for more complicated optimalization\n\n### import\n\nTo import all algorithms for beginners, simply use the command `from pyrimidine import *`.\n\nTo speed the lib, use the following commands \n```\nfrom pyrimidine import BaseChromosome, BaseIndividual, BasePopulation # import the base classes form `base.py` to build your own classes\n\n# Commands used frequently\nfrom pyrimidine.base import BinaryChromosome, FloatChromosome # import the Chromosome classes and utilize them directly\n# equivalent to `from pyrimidine import BinaryChromosome, FloatChromosome`\nfrom pyrimidine.population import StandardPopulation, HOFPopulation # For creating population with standard GA \n# the same effect with `from pyrimidine import StandardPopulation, HOFPopulation`\nfrom pyrimidine.indiviual import makeIndividual # a helper to make Individual objects, or `from pyrimidine import makeIndividual`\n\nfrom pyrimidine import MultiPopulation # build the multi-populations\nfrom pyrimidine import MetaContainer # meta class for socalled container class, that is recommended to be used for creating novel evolutionary algorithms.\n\nfrom pyrimidine.deco import * # import all decorators\nfrom pyrimidine.deco import fitness_cache, basic_memory # use the cache decorator and memory decorator\n\nfrom pyrimidine import optimize # do optimization implictly with GAs\n```\n\n### subclasses\n\n#### Chromosome\n\nGenerally, it is an array of genes.\n\nAs an array of 0-1s, `BinaryChromosome` is used most frequently.\n\n#### Individual\njust subclass `MonoIndividual` in most cases.\n\n```python\nclass MyIndividual(MonoIndividual):\n    """individual with only one chromosome\n    we set the gene is 0 or 1 in the chromosome\n    """\n    element_class = BinaryChromosome\n\n    def _fitness(self):\n        ...\n```\n\nSince the helper `makeIndividual(n_chromosomes=1, size=8)` could create such individual, it is equivalent to\n\n```python\nclass MyIndividual(binaryIndividual()):\n    # only need define the fitness\n    def _fitness(self):\n        ...\n```\n\n\nIf an individual contains several chromosomes, then subclass `MultiIndividual` or `PolyIndividual`. It could be applied in multi-real-variable optimization problems where each variable has a separative binary encoding.\n\n\nIn most cases, we have to decode chromosomes to real numbers.\n\n```python\nclass _Chromosome(BinaryChromosome):\n    def decode(self):\n        """Decode a binary chromosome\n        \n        if the sequence of 0-1 represents a real number, then overide the method\n        to transform it to a nubmer\n        """\n\nclass ExampleIndividual(BaseIndividual):\n    element_class = _Chromosome\n\n    def _fitness(self):\n        # define the method to calculate the fitness\n        x = self.decode()  # will call decode method of _Chromosome\n        return evaluate(x)\n```\n\n\nIf the chromosomes in an individual are different with each other, then subclass `MixedIndividual`, meanwhile, the property `element_class` should be assigned with a tuple of classes for each chromosome.\n\n```python\nclass MyIndividual(MixedIndividual):\n    """\n    Inherit the fitness from ExampleIndividual directly.\n    It has 6 chromosomes, 5 are instances of _Chromosome, 1 is instance of FloatChromosome\n    """\n    element_class = (_Chromosome,)*5 + (FloatChromosome,)\n```\n\nIt equivalent to `MyIndividual=MixedIndividual[(_Chromosome,)*5 + (FloatChromosome,)]`\n\n#### Population\n\n```python\nclass MyPopulation(StandardPopulation):\n    element_class = MyIndividual\n```\n\nIt is equivalent to `MyPopulation = StandardPopulation[MyIndividual]`.\n\n\n### Initialize randomly\n\n`random` is a factory method!\n\nGenerate a population, with 50 individuals and each individual has 100 genes:\n\n`pop = MyPopulation.random(n_individuals=50, size=100)`\n\nWhen each individual contains 5 chromosomes, use\n\n`pop = MyPopulation.random(n_individuals=10, n_chromosomes=5, size=10)`\n\nHowever, we recommand to set `default_size` in the classes, then run `MyPopulation.random()`\n\n```python\nclass MyPopulation(StandardPopulation):\n    element_class = MyIndividual // 5\n    default_size = 10\n\n# equiv. to\n\nMyPopulation = StandardPopulation[MyIndividual//5]//10\n```\n\nIn fact, `random` method of `BasePopulation` will call random method of `BaseIndividual`. If you want to generate an individual, then just execute `MyIndividual.random(n_chromosomes=5, size=10)`, or set `default_size`, then execute `MyIndividual.random()`.\n\n\n### Evolution\n\n#### `evolve` method\nInitialize a population with `random` method, then call `evolve` method.\n\n```python\npop = MyPopulation.random(n_individuals=50, size=100)\npop.evolve()\nprint(pop.solution)\n```\n\nset `verbose=True` to display the data for each generation.\n\n`evolve` method mainly excute two methods: \n- `init`: initial configuration of the algo.\n- `transition`: do each step of the iteration.\n\n#### History\n\nGet the history of the evolution.\n\n```python\nstat={\'Mean Fitness\':\'mean_fitness\', \'Best Fitness\': lambda pop: pop.best_individual.fitness}\ndata = pop.history(stat=stat)  # use history instead of evolve\n```\n`stat` is a dict mapping keys to function, where string \'mean_fitness\' means function `lambda pop:pop.mean_fitness` which gets the mean fitness of the individuals in `pop`. Since we have defined pop.best_individual.fitness as a property, `stat` could be redefined as `{\'Fitness\': \'fitness\', \'Best Fitness\': \'max_fitness\'}`.\n\nIt requires `ezstat`, a easy statistical tool devoloped by the author.\n\n#### performance\n\nUse `pop.perf()` to check the performance, which calls `evolve` several times.\n\n\n## Example\n\n### Example 1\n\nDescription\n\n    select some of ti, ni, i=1,...,L, ti in {1,2,...,T}, ni in {1,2,...,N}\n    the sum of ni approx. 10, while ti dose not repeat\n\nThe opt. problem is\n\n    min abs(sum_i{ni}-10) + maximum of frequences in {ti}\n    where i is selected.\n\n$$\n\\min_I |\\sum_{i\\in I} n_i -10| + t_m\n\\\\\nI\\subset\\{1,\\cdots,L\\}\n$$\nwhere $t_m$ is the mode of $\\{t_i, i\\in I\\}$\n\n```python\nt = np.random.randint(1, 5, 100)\nn = np.random.randint(1, 4, 100)\n\nimport collections\n\nfrom pyrimidine.individual import makeBinaryIndividual\nfrom pyrimidine.population import StandardPopulation\n\ndef max_repeat(x):\n    # Maximum repetition\n    c = collections.Counter(x)\n    return np.max([b for a, b in c.items()])\n\n\nclass MyIndividual(makeBinaryIndividual()):\n\n    def _fitness(self):\n        x = abs(np.dot(n, self.chromosome)-10)\n        y = max_repeat(ti for ti, c in zip(t, self) if c==1)\n        return - x - y\n\n\nclass MyPopulation(StandardPopulation):\n    element_class = MyIndividual\n\npop = MyPopulation.random(n_individuals=50, size=100)\npop.evolve()\nprint(pop.solution)  # or pop.best_individual.decode()\n```\n\nNote that there is only one chromosome in `MonoIndividual`, which could be got by `self.chromosome`.\n\nIn fact, the population could be the container of chromosomes. Therefore, we can rewrite the classes as follows in a more natural way.\n\n```python\nfrom pyrimidine.chromosome import BinaryChromosome\nfrom pyrimidine.population import StandardPopulation\n\nclass MyChromosome(BinaryChromosome):\n\n    def _fitness(self):\n        x = abs(np.dot(n, self)-10)\n        y = max_repeat(ti for ti, c in zip(t, self) if c==1)\n        return - x - y\n\nclass MyPopulation(StandardPopulation):\n    element_class = MyChromosome\n```\n\nIt is equiv. to\n```python\ndef _fitness(obj):\n    x = abs(np.dot(n, obj)-10)\n    y = max_repeat(ti for ti, c in zip(t, obj) if c==1)\n    return - x - y\n\nMyPopulation = StandardPopulation[BinaryChromosome].set_fitness(_fitness)\n```\n\n### Example2: Knapsack Problem\n\nOne of the famous problem is the knapsack problem. It is a good example for GA.\n\nWe set `history=True` in `evolve` method for the example, that will record the main data of the whole evolution. It will return an object of `pandas.DataFrame`. The argument `stat`  is a dict from a key to function/str(corresponding to a method) representing a mapping from a population to a number. these numbers of one generation will be stored in a row of the dataframe.\n\nsee `# examples/example0`\n\n```python\n#!/usr/bin/env python3\n\nfrom pyrimidine import binaryIndividual, StandardPopulation\nfrom pyrimidine.benchmarks.optimization import *\n\n# generate a knapsack problem randomly\nevaluate = Knapsack.random(n=20)\n\n\nclass MyIndividual(binaryIndividual(size=20)):\n    def _fitness(self):\n        return evaluate(self)\n\nclass MyPopulation(StandardPopulation):\n    element_class = MyIndividual\n    default_size = 10\n\n\npop = MyPopulation.random()\n\nstat={\'Mean Fitness\':\'mean_fitness\', \'Best Fitness\':\'max_fitness\'}\ndata = pop.evolve(stat=stat, history=True) # an instance of `pandas.DataFrame`\n\n# Visualization\nimport matplotlib.pyplot as plt\nfig = plt.figure()\nax = fig.add_subplot(111)\ndata[[\'Mean Fitness\', \'Best Fitness\']].plot(ax=ax)\nax.set_xlabel(\'Generations\')\nax.set_ylabel(\'Fitness\')\nplt.show()\n```\n\n![plot-history](/Users/william/Programming/myGithub/pyrimidine/plot-history.png)\n\n\n## Extension\n\n`pyrimidine` is extremely extendable. It is easy to implement other iterative models or algorithms, such as simulation annealing(SA) and particle swarm optimization(PSO).\n\nCurrently, it is recommended to define subclasses based on `IterativeModel` as a mixin. (not mandatory)\n\nIn PSO, we regard a particle as an individual, and `ParticleSwarm` as a population. But in the following, we subclass it from `IterativeModel`\n\n```python\n# pso.py\n@basic_memory\nclass Particle(BaseIndividual):\n    """A particle in PSO\n\n    Extends BaseIndividual\n\n    Variables:\n        default_size {number} -- one individual represented by 2 chromosomes: position and velocity\n        phantom {Particle} -- the current state of the particle moving in the solution space.\n    """\n\n    element_class = FloatChromosome\n    default_size = 2\n\n    # other methods\n\nclass ParticleSwarm(PopulationMixin):\n    """Standard PSO\n    \n    Extends:\n        PopulationMixin\n    """\n\n    element_class = Particle\n    default_size = 20\n\n    params = {\'learning_factor\': 2, \'acceleration_coefficient\': 3,\n    \'inertia\':0.75, \'n_best_particles\':0.2, \'max_velocity\':None}\n\n    def init(self):\n        for particle in self:\n            particle.init()\n        self.hall_of_fame = self.get_best_individuals(self.n_best_particles, copy=True)\n\n    def update_hall_of_fame(self):\n        hof_size = len(self.hall_of_fame)\n        for ind in self:\n            for k in range(hof_size):\n                if self.hall_of_fame[-k-1].fitness < ind.fitness:\n                    self.hall_of_fame.insert(hof_size-k, ind.copy())\n                    self.hall_of_fame.pop(0)\n                    break\n\n    @property\n    def best_fitness(self):\n        if self.hall_of_fame:\n            return max(map(attrgetter(\'fitness\'), self.hall_of_fame))\n        else:\n            return super().best_fitness\n\n    def transition(self, *args, **kwargs):\n        """\n        Transitation of the states of particles\n        """\n        self.move()\n        self.backup()\n        self.update_hall_of_fame()\n\n    def backup(self):\n        # overwrite the memory of the particle if its current state is better its memory\n        for particle in self:\n            particle.backup(check=True)\n\n    def move(self):\n        """Move the particles\n\n        Define the moving rule of particles, according to the hall of fame and the best record\n        """\n\n        scale = random()\n        eta = random()\n        scale_fame = random()\n        for particle in self:\n            for fame in self.hall_of_fame:\n                if particle.fitness < fame.fitness:\n                    particle.update_vilocity_by_fame(fame, scale, scale_fame, \n                        self.inertia, self.learning_factor, self.acceleration_coefficient)\n                    particle.position = particle.position + particle.velocity\n                    break\n        for particle in self.hall_of_fame:\n            particle.update_vilocity(scale, self.inertia, self.learning_factor)\n            particle.position = particle.position + particle.velocity\n```\n\nIf you want to apply PSO, then you can define\n\n```python\nclass MyParticleSwarm(ParticleSwarm, BasePopulation):\n    element_class = _Particle\n    default_size = 20\n\npop = MyParticleSwarm.random()\n```\n\nOf course, it is not mandatory. It is allowed to inherit `ParticleSwarm` from for example `HOFPopulation` directly.\n',
    'author': 'William Song',
    'author_email': '30965609+Freakwill@users.noreply.github.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/Freakwill/pyrimidine',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.7,<4.0',
}


setup(**setup_kwargs)
