Skip to content

Commit

Permalink
update doc
Browse files Browse the repository at this point in the history
  • Loading branch information
Freakwill committed Dec 14, 2023
1 parent bd27397 commit 141e9a4
Show file tree
Hide file tree
Showing 10 changed files with 33 additions and 28 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ examples/*.csv
examples/*.mp4
*/__pycache__/
-*.py
*.pdf
Binary file removed Pyrimidine(Chinese).pdf
Binary file not shown.
File renamed without changes.
14 changes: 5 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ It has been uploaded to [pypi](https://pypi.org/project/pyrimidine/), so downloa

## 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.
We view the population as a container of individuals, each individual as a container of chromosomes, and a chromosome as a container (array) of genes. This container could be represented as a list or an array. The Container class has an attribute `element_class`, which specifies the class of the elements within it.

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

Expand All @@ -36,7 +32,7 @@ class BasePopulation(PopulationModel, metaclass=MetaContainer):
default_size = 20
```

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

```python
# individual with chromosomes of type _Chromosome
Expand All @@ -46,13 +42,14 @@ _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.
We could 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.

Expand Down Expand Up @@ -128,7 +125,6 @@ class ExampleIndividual(BaseIndividual):
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
Expand Down Expand Up @@ -206,7 +202,7 @@ 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.
It requires `ezstat` (optional but recommended), a easy statistical tool devoloped by the author.

#### performance

Expand Down
4 changes: 2 additions & 2 deletions docs/source/Examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ class MyPopulation(HOFPopulation):
```

### Visualization and comparison

```python
stat={'Mean Fitness': 'mean_fitness', 'Best Fitness': 'max_fitness'}
mypop = MyPopulation.random()
Expand All @@ -405,7 +406,6 @@ ax.set_xlabel('Generations')
ax.set_ylabel('Fitness')
ax.set_title(f'Demo of (Quantum)GA: {n_bags}-Knapsack Problem')
plt.show()

```

![](QGA.png)
Expand Down Expand Up @@ -575,4 +575,4 @@ ax.set_title("Have a zero-sum game")
plt.show()
```

![](game.png)
![](game.png)
24 changes: 14 additions & 10 deletions docs/source/Home.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,15 @@ Why is the package named as "pyrimidine"? Because it begins with "py".

It has been uploaded to pypi, so download it with `pip install pyrimidine`, and also could download it from github.

## Idea
## Idea of algebra-inspired
We view the population as a container of individuals, each individual as a container of chromosomes, and a chromosome as a container (array) of genes. This container could be represented as a list or an array. The Container class has an attribute `element_class`, which specifies the class of the elements within it.

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.
Mathematically, we denote a container of elements of type `A` as follows:

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

Mathematically, we denote a container of elements in type `A` as following
```
s = {a:A}:S
```

A population is a container of individuals; An indiviudal is a container of chromosomes. Following is the part of the source code of `BaseIndividual` and `BasePopulation`.
A population is a container of individuals; an individual is a container of chromosomes. Below is the partial source code for `BaseIndividual` and `BasePopulation`.

```python
class BaseIndividual(FitnessMixin, metaclass=MetaContainer):
Expand All @@ -54,6 +50,14 @@ _Individual1 = BaseIndividual[_Choromosome] // 20
_Individual2 = MixedIndividual[_Chromosome1, _Chromosome2]
```

An population also could be the container of chromosomes. It will be considered in the case when the indiviudal has only one chromosome.
A population can also serve as a container of chromosomes, particularly in scenarios where an individual possesses only a single chromosome.

In essence, a container - and by extension, a population in genetic algorithms - is regarded as a distinctive algebraic system. This perspective leads us to refer to it as an "algebra-inspired" design.

## Fitness

This is how we compute `fitness`. The method `_fitness` is responsible for the underlying computation. The attribute `fitness` further encapsulates `_fitness`. If caching is enabled, it will first read from the cache; if not, it will call `_fitness`.

It is recommended to add the `@fitness_cache` decorator to individuals. If the individual has not changed, it can reduce computation and improve algorithm efficiency, otherwise it should re-compute fitness.

In fact, a container (so a population in GA) is treated as a special algebraic system. For this reason, we call it "algebra-oriental" design.
Unlike the cache class decorator, the `memory` decorator (e.g., `@basic_memory`) will change the algorithm's behavior. It stores the best results during the individual's changes. `fitness` will first read from memory. Memory itself also has a caching effect, so if you add the memory decorator, there is no need to add the cache decorator.
3 changes: 3 additions & 0 deletions examples/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
from pyrimidine import MonoIndividual, BinaryChromosome, StandardPopulation
from pyrimidine.benchmarks.optimization import *

from pyrimidine.deco import fitness_cache

n_bags = 50
_evaluate = Knapsack.random(n_bags) # : 0-1 array -> float

# Define the individual class
@fitness_cache
class MyIndividual(MonoIndividual):

element_class = BinaryChromosome.set(default_size=n_bags)
Expand Down
2 changes: 1 addition & 1 deletion pyrimidine/_stat.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class Statistics(dict):

def __new__(cls, *args, **kwargs):
obj = super().__new__(cls, *args, **kwargs)
for k, _ in d.items():
for k, _ in obj.items():
if not isinstance(k, str):
raise TypeError(f'The keys must be strings, but `{k}` is not a string.')
return obj
Expand Down
7 changes: 2 additions & 5 deletions pyrimidine/deco.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,14 @@ def _copy(obj, cache=True, *args, **kwargs):
cls.copy = _copy

for a in self.attrs:
if not hasattr(cls, a) and not hasattr(cls, '_'+a):
if not hasattr(cls, a):
raise AttributeError(f'"{a}" should be used in the algorithm!')
def f(obj):
"""get the attribute from cache,
otherwise compute it again by the default method
"""
if obj._cache[a] is None:
if hasattr(cls, '_'+a):
v = getattr(obj, '_'+a)()
else:
v = getattr(super(cls, obj), a)
v = getattr(super(cls, obj), a)
obj._cache[a] = v
return v
else:
Expand Down
6 changes: 5 additions & 1 deletion pyrimidine/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
import numpy as np
import pandas as pd

from ezstat import Statistics
try:
from ezstat import Statistics
except:
from ._stat import Statistics

from .errors import *

from .deco import side_effect
Expand Down

0 comments on commit 141e9a4

Please sign in to comment.