# 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"
}