pyrimidine


Namepyrimidine JSON
Version 1.6 PyPI version JSON
download
home_pagehttps://github.com/Freakwill/pyrimidine
SummaryA competitive framework tailored for GA, crafted with an emphasis on OOP and Algebra-Inspired Programming.
upload_time2024-05-12 15:34:41
maintainerNone
docs_urlNone
authorWilliam Song
requires_python<4.0,>=3.7
licenseMIT
keywords genetic algorithm artificial intelligence intelligent optimization
VCS
bugtrack_url
requirements numpy scipy pandas toolz ezstat matplotlib scikit-learn digit_converter pytest setuptools
Travis-CI
coveralls test coverage No coveralls.
            # pyrimidine

`pyrimidine` is an extensible framework of genetic/evolutionary algorithm by Python. See [pyrimidine's document](https://pyrimidine.readthedocs.io/) for more details.

![LOGO](logo.png)

## Why

Why is the package named as “pyrimidine”? Because it begins with “py”.

> — Are you kiding?
>
> — No, I am serious.

## Download

It has been uploaded to [pypi](https://pypi.org/project/pyrimidine/), so download it with `pip install pyrimidine`, and also could download it from github.

## Idea

We regard the population as a container of individuals, an individual as a container of chromosomes
and a chromosome as a container(array) of genes.

The container could be a list or an array.
The container class has an attribute `element_class`, telling itself the type of the elements in it.

Following is the part of the source code of `BaseIndividual` and `BasePopulation`.

```python
class BaseIndividual(FitnessModel, metaclass=MetaContainer):
    element_class = BaseChromosome
    default_size = 1

class BasePopulation(PopulationModel, metaclass=MetaContainer):
    element_class = BaseIndividual
    default_size = 20
```

There is mainly tow kinds of containers: list and tuple as in programming language `Haskell`. See following examples.

```python
# individual with chromosomes of type _Chromosome
_Individual1 = BaseIndividual[_Choromosome]
# individual with 2 chromosomes of type _Chromosome1 and _Chromosome2 respectively
_Individual2 = MixedIndividual[_Chromosome1, _Chromosome2]
```

### Math expression
$s$ of type $S$ is a container of $a:A$, represented as follows:

```
s={a:A}:S
```

We define a population as a container of individuals or chromosomes, and an individual is a container of chromosomes.

Algebraically, 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.

The methods are the functions or operators defined on $s$.

## Use

### Main classes

- BaseGene: the gene of chromosome
- BaseChromosome: sequence of genes, represents part of a solution
- BaseIndividual: sequence of chromosomes, represents a solution of a problem
- BasePopulation: a container of individuals, represents a container of a problem
                also the state of a stachostic process
- BaseMultipopulation: a container of population for more complicated optimalization

### import

To import all algorithms for beginners, simply use the command `from pyrimidine import *`.

To speed the lib, use the following commands 
```
from pyrimidine import BaseChromosome, BaseIndividual, BasePopulation # import the base classes form `base.py` to build your own classes

# Commands used frequently
from pyrimidine.base import BinaryChromosome, FloatChromosome # import the Chromosome classes and utilize them directly
# equivalent to `from pyrimidine import BinaryChromosome, FloatChromosome`
from pyrimidine.population import StandardPopulation, HOFPopulation # For creating population with standard GA 
# the same effect with `from pyrimidine import StandardPopulation, HOFPopulation`
from pyrimidine.indiviual import makeIndividual # a helper to make Individual objects, or `from pyrimidine import makeIndividual`

from pyrimidine import MultiPopulation # build the multi-populations
from pyrimidine import MetaContainer # meta class for socalled container class, that is recommended to be used for creating novel evolutionary algorithms.

from pyrimidine.deco import * # import all decorators
from pyrimidine.deco import fitness_cache, basic_memory # use the cache decorator and memory decorator

from pyrimidine import optimize # do optimization implictly with GAs
```

### subclasses

#### Chromosome

Generally, it is an array of genes.

As an array of 0-1s, `BinaryChromosome` is used most frequently.

#### Individual
just subclass `MonoIndividual` in most cases.

```python
class MyIndividual(MonoIndividual):
    """individual with only one chromosome
    we set the gene is 0 or 1 in the chromosome
    """
    element_class = BinaryChromosome

    def _fitness(self):
        ...
```

Since the helper `makeIndividual(n_chromosomes=1, size=8)` could create such individual, it is equivalent to

```python
class MyIndividual(binaryIndividual()):
    # only need define the fitness
    def _fitness(self):
        ...
```


If 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.


In most cases, we have to decode chromosomes to real numbers.

```python
class _Chromosome(BinaryChromosome):
    def decode(self):
        """Decode a binary chromosome
        
        if the sequence of 0-1 represents a real number, then overide the method
        to transform it to a nubmer
        """

class ExampleIndividual(BaseIndividual):
    element_class = _Chromosome

    def _fitness(self):
        # define the method to calculate the fitness
        x = self.decode()  # will call decode method of _Chromosome
        return evaluate(x)
```


If 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.

```python
class MyIndividual(MixedIndividual):
    """
    Inherit the fitness from ExampleIndividual directly.
    It has 6 chromosomes, 5 are instances of _Chromosome, 1 is instance of FloatChromosome
    """
    element_class = (_Chromosome,)*5 + (FloatChromosome,)
```

It equivalent to `MyIndividual=MixedIndividual[(_Chromosome,)*5 + (FloatChromosome,)]`

#### Population

```python
class MyPopulation(StandardPopulation):
    element_class = MyIndividual
```

It is equivalent to `MyPopulation = StandardPopulation[MyIndividual]`.


### Initialize randomly

`random` is a factory method!

Generate a population, with 50 individuals and each individual has 100 genes:

`pop = MyPopulation.random(n_individuals=50, size=100)`

When each individual contains 5 chromosomes, use

`pop = MyPopulation.random(n_individuals=10, n_chromosomes=5, size=10)`

However, we recommand to set `default_size` in the classes, then run `MyPopulation.random()`

```python
class MyPopulation(StandardPopulation):
    element_class = MyIndividual // 5
    default_size = 10

# equiv. to

MyPopulation = StandardPopulation[MyIndividual//5]//10
```

In 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()`.


### Evolution

#### `evolve` method
Initialize a population with `random` method, then call `evolve` method.

```python
pop = MyPopulation.random(n_individuals=50, size=100)
pop.evolve()
print(pop.solution)
```

set `verbose=True` to display the data for each generation.

`evolve` method mainly excute two methods: 
- `init`: initial configuration of the algo.
- `transition`: do each step of the iteration.

#### History

Get the history of the evolution.

```python
stat={'Mean Fitness':'mean_fitness', 'Best Fitness': lambda pop: pop.best_individual.fitness}
data = pop.history(stat=stat)  # use history instead of evolve
```
`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'}`.

It requires `ezstat`, a easy statistical tool devoloped by the author.

#### performance

Use `pop.perf()` to check the performance, which calls `evolve` several times.


## Example

### Example 1

Description

    select some of ti, ni, i=1,...,L, ti in {1,2,...,T}, ni in {1,2,...,N}
    the sum of ni approx. 10, while ti dose not repeat

The opt. problem is

    min abs(sum_i{ni}-10) + maximum of frequences in {ti}
    where i is selected.

$$
\min_I |\sum_{i\in I} n_i -10| + t_m
\\
I\subset\{1,\cdots,L\}
$$
where $t_m$ is the mode of $\{t_i, i\in I\}$

```python
t = np.random.randint(1, 5, 100)
n = np.random.randint(1, 4, 100)

import collections

from pyrimidine.individual import makeBinaryIndividual
from pyrimidine.population import StandardPopulation

def max_repeat(x):
    # Maximum repetition
    c = collections.Counter(x)
    return np.max([b for a, b in c.items()])


class MyIndividual(makeBinaryIndividual()):

    def _fitness(self):
        x = abs(np.dot(n, self.chromosome)-10)
        y = max_repeat(ti for ti, c in zip(t, self) if c==1)
        return - x - y


class MyPopulation(StandardPopulation):
    element_class = MyIndividual

pop = MyPopulation.random(n_individuals=50, size=100)
pop.evolve()
print(pop.solution)  # or pop.best_individual.decode()
```

Note that there is only one chromosome in `MonoIndividual`, which could be got by `self.chromosome`.

In fact, the population could be the container of chromosomes. Therefore, we can rewrite the classes as follows in a more natural way.

```python
from pyrimidine.chromosome import BinaryChromosome
from pyrimidine.population import StandardPopulation

class MyChromosome(BinaryChromosome):

    def _fitness(self):
        x = abs(np.dot(n, self)-10)
        y = max_repeat(ti for ti, c in zip(t, self) if c==1)
        return - x - y

class MyPopulation(StandardPopulation):
    element_class = MyChromosome
```

It is equiv. to
```python
def _fitness(obj):
    x = abs(np.dot(n, obj)-10)
    y = max_repeat(ti for ti, c in zip(t, obj) if c==1)
    return - x - y

MyPopulation = StandardPopulation[BinaryChromosome].set_fitness(_fitness)
```

### Example2: Knapsack Problem

One of the famous problem is the knapsack problem. It is a good example for GA.

We 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.

see `# examples/example0`

```python
#!/usr/bin/env python3

from pyrimidine import binaryIndividual, StandardPopulation
from pyrimidine.benchmarks.optimization import *

# generate a knapsack problem randomly
evaluate = Knapsack.random(n=20)


class MyIndividual(binaryIndividual(size=20)):
    def _fitness(self):
        return evaluate(self)

class MyPopulation(StandardPopulation):
    element_class = MyIndividual
    default_size = 10


pop = MyPopulation.random()

stat={'Mean Fitness':'mean_fitness', 'Best Fitness':'max_fitness'}
data = pop.evolve(stat=stat, history=True) # an instance of `pandas.DataFrame`

# Visualization
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
data[['Mean Fitness', 'Best Fitness']].plot(ax=ax)
ax.set_xlabel('Generations')
ax.set_ylabel('Fitness')
plt.show()
```

![plot-history](/Users/william/Programming/myGithub/pyrimidine/plot-history.png)


## Extension

`pyrimidine` is extremely extendable. It is easy to implement other iterative models or algorithms, such as simulation annealing(SA) and particle swarm optimization(PSO).

Currently, it is recommended to define subclasses based on `IterativeModel` as a mixin. (not mandatory)

In PSO, we regard a particle as an individual, and `ParticleSwarm` as a population. But in the following, we subclass it from `IterativeModel`

```python
# pso.py
@basic_memory
class Particle(BaseIndividual):
    """A particle in PSO

    Extends BaseIndividual

    Variables:
        default_size {number} -- one individual represented by 2 chromosomes: position and velocity
        phantom {Particle} -- the current state of the particle moving in the solution space.
    """

    element_class = FloatChromosome
    default_size = 2

    # other methods

class ParticleSwarm(PopulationMixin):
    """Standard PSO
    
    Extends:
        PopulationMixin
    """

    element_class = Particle
    default_size = 20

    params = {'learning_factor': 2, 'acceleration_coefficient': 3,
    'inertia':0.75, 'n_best_particles':0.2, 'max_velocity':None}

    def init(self):
        for particle in self:
            particle.init()
        self.hall_of_fame = self.get_best_individuals(self.n_best_particles, copy=True)

    def update_hall_of_fame(self):
        hof_size = len(self.hall_of_fame)
        for ind in self:
            for k in range(hof_size):
                if self.hall_of_fame[-k-1].fitness < ind.fitness:
                    self.hall_of_fame.insert(hof_size-k, ind.copy())
                    self.hall_of_fame.pop(0)
                    break

    @property
    def best_fitness(self):
        if self.hall_of_fame:
            return max(map(attrgetter('fitness'), self.hall_of_fame))
        else:
            return super().best_fitness

    def transition(self, *args, **kwargs):
        """
        Transitation of the states of particles
        """
        self.move()
        self.backup()
        self.update_hall_of_fame()

    def backup(self):
        # overwrite the memory of the particle if its current state is better its memory
        for particle in self:
            particle.backup(check=True)

    def move(self):
        """Move the particles

        Define the moving rule of particles, according to the hall of fame and the best record
        """

        scale = random()
        eta = random()
        scale_fame = random()
        for particle in self:
            for fame in self.hall_of_fame:
                if particle.fitness < fame.fitness:
                    particle.update_vilocity_by_fame(fame, scale, scale_fame, 
                        self.inertia, self.learning_factor, self.acceleration_coefficient)
                    particle.position = particle.position + particle.velocity
                    break
        for particle in self.hall_of_fame:
            particle.update_vilocity(scale, self.inertia, self.learning_factor)
            particle.position = particle.position + particle.velocity
```

If you want to apply PSO, then you can define

```python
class MyParticleSwarm(ParticleSwarm, BasePopulation):
    element_class = _Particle
    default_size = 20

pop = MyParticleSwarm.random()
```

Of course, it is not mandatory. It is allowed to inherit `ParticleSwarm` from for example `HOFPopulation` directly.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/Freakwill/pyrimidine",
    "name": "pyrimidine",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.7",
    "maintainer_email": null,
    "keywords": "Genetic Algorithm, Artificial Intelligence, Intelligent Optimization",
    "author": "William Song",
    "author_email": "30965609+Freakwill@users.noreply.github.com",
    "download_url": "https://files.pythonhosted.org/packages/8c/14/1cd4a6dd4f44f4d2e0501432121c820cbf7b0c431f0eaa76e13b28a2be04/pyrimidine-1.6.tar.gz",
    "platform": null,
    "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 \u201cpyrimidine\u201d? Because it begins with \u201cpy\u201d.\n\n> \u2014 Are you kiding?\n>\n> \u2014 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",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A competitive framework tailored for GA, crafted with an emphasis on OOP and Algebra-Inspired Programming.",
    "version": "1.6",
    "project_urls": {
        "Homepage": "https://github.com/Freakwill/pyrimidine",
        "Repository": "https://github.com/Freakwill/pyrimidine"
    },
    "split_keywords": [
        "genetic algorithm",
        " artificial intelligence",
        " intelligent optimization"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8a74e1a8cd499b5d2a509737c5aa854a994fcf0755080634858d69c384775075",
                "md5": "6798df7f10b254337bf28727ac921570",
                "sha256": "115bd1d38b19514ec2b8f01baa05b17d658465eb51b6371da44086c9f3dff075"
            },
            "downloads": -1,
            "filename": "pyrimidine-1.6-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "6798df7f10b254337bf28727ac921570",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.7",
            "size": 4759207,
            "upload_time": "2024-05-12T15:34:39",
            "upload_time_iso_8601": "2024-05-12T15:34:39.119609Z",
            "url": "https://files.pythonhosted.org/packages/8a/74/e1a8cd499b5d2a509737c5aa854a994fcf0755080634858d69c384775075/pyrimidine-1.6-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8c141cd4a6dd4f44f4d2e0501432121c820cbf7b0c431f0eaa76e13b28a2be04",
                "md5": "8f6be57552eb4454e337332aa537650a",
                "sha256": "c74f165c13ec011a781af7ded70a1200864e80a5c67205a1ee73f7bd37144ed5"
            },
            "downloads": -1,
            "filename": "pyrimidine-1.6.tar.gz",
            "has_sig": false,
            "md5_digest": "8f6be57552eb4454e337332aa537650a",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.7",
            "size": 4661044,
            "upload_time": "2024-05-12T15:34:41",
            "upload_time_iso_8601": "2024-05-12T15:34:41.498747Z",
            "url": "https://files.pythonhosted.org/packages/8c/14/1cd4a6dd4f44f4d2e0501432121c820cbf7b0c431f0eaa76e13b28a2be04/pyrimidine-1.6.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-05-12 15:34:41",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "Freakwill",
    "github_project": "pyrimidine",
    "travis_ci": true,
    "coveralls": false,
    "github_actions": true,
    "requirements": [
        {
            "name": "numpy",
            "specs": []
        },
        {
            "name": "scipy",
            "specs": []
        },
        {
            "name": "pandas",
            "specs": []
        },
        {
            "name": "toolz",
            "specs": []
        },
        {
            "name": "ezstat",
            "specs": []
        },
        {
            "name": "matplotlib",
            "specs": []
        },
        {
            "name": "scikit-learn",
            "specs": []
        },
        {
            "name": "digit_converter",
            "specs": []
        },
        {
            "name": "pytest",
            "specs": []
        },
        {
            "name": "setuptools",
            "specs": []
        }
    ],
    "lcname": "pyrimidine"
}
        
Elapsed time: 0.71454s