From 8a77bc13bb6ad592eb5fc15c79186657726193d4 Mon Sep 17 00:00:00 2001 From: William Song <30965609+Freakwill@users.noreply.github.com> Date: Sun, 6 Oct 2024 21:45:45 +0800 Subject: [PATCH] modify paper.md --- paper/example.py | 73 ++++++ paper/paper(longer).md | 231 ++++++++++++++++++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 756 bytes .../__pycache__/optimization.cpython-312.pyc | Bin 0 -> 14535 bytes .../__pycache__/special.cpython-312.pyc | Bin 0 -> 3769 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1933 bytes .../linear_regression.cpython-312.pyc | Bin 0 -> 3117 bytes .../neural_network.cpython-312.pyc | Bin 0 -> 3442 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 274 bytes .../__pycache__/random_walk.cpython-312.pyc | Bin 0 -> 1458 bytes .../simulated_annealing.cpython-312.pyc | Bin 0 -> 1968 bytes .../__pycache__/tabu_search.cpython-312.pyc | Bin 0 -> 3300 bytes pyrimidine/misc/__init__.py | 4 + pyrimidine/misc/aco.py | 141 +++++++++++ pyrimidine/misc/fa.py | 65 +++++ pyrimidine/misc/gsa.py | 123 ++++++++++ pyrimidine/misc/sma.py | 75 ++++++ pyrimidine/misc/ssa.py | 115 +++++++++ 18 files changed, 827 insertions(+) create mode 100755 paper/example.py create mode 100644 paper/paper(longer).md create mode 100644 pyrimidine/benchmarks/__pycache__/__init__.cpython-312.pyc create mode 100644 pyrimidine/benchmarks/__pycache__/optimization.cpython-312.pyc create mode 100644 pyrimidine/benchmarks/__pycache__/special.cpython-312.pyc create mode 100644 pyrimidine/learn/__pycache__/__init__.cpython-312.pyc create mode 100644 pyrimidine/learn/__pycache__/linear_regression.cpython-312.pyc create mode 100644 pyrimidine/learn/__pycache__/neural_network.cpython-312.pyc create mode 100644 pyrimidine/local_search/__pycache__/__init__.cpython-312.pyc create mode 100644 pyrimidine/local_search/__pycache__/random_walk.cpython-312.pyc create mode 100644 pyrimidine/local_search/__pycache__/simulated_annealing.cpython-312.pyc create mode 100644 pyrimidine/local_search/__pycache__/tabu_search.cpython-312.pyc create mode 100755 pyrimidine/misc/__init__.py create mode 100644 pyrimidine/misc/aco.py create mode 100755 pyrimidine/misc/fa.py create mode 100644 pyrimidine/misc/gsa.py create mode 100755 pyrimidine/misc/sma.py create mode 100644 pyrimidine/misc/ssa.py diff --git a/paper/example.py b/paper/example.py new file mode 100755 index 0000000..c323857 --- /dev/null +++ b/paper/example.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +""" +An ordinary example of the usage of `pyrimidine` +""" + +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) +# def _fitness(self) -> float: +# # To evaluate an individual! +# return _evaluate(self.chromosome) + +# Equiv. to +MyIndividual = (BinaryChromosome // n_bags).set_fitness(_evaluate) + + +# Define the population class +class MyPopulation(StandardPopulation): + element_class = MyIndividual + default_size = 20 + +""" Equiv. to + MyPopulation = StandardPopulation[MyIndividual] // 20 + or, as a population of chromosomes + MyPopulation = StandardPopulation[(BinaryChromosome // n_bags).set_fitness(_evaluate)] // 8 +""" + +pop = MyPopulation.random() + + +if __name__ == '__main__': + + # Define statistics of population + stat = { + 'Max Fitness': 'max_fitness', + 'Mean Fitness': 'mean_fitness', + 'Standard Deviation of Fitnesses': 'std_fitness', + # '0.85-Quantile': lambda pop: np.quantile(pop.get_all_fitness(), 0.85), + # '0.15-Quantile': lambda pop: np.quantile(pop.get_all_fitness(), 0.15), + # 'Median Fitness': lambda pop: np.median(pop.get_all_fitness()) + } + + # Do statistical task and print the results through the evoluation + data = pop.evolve(stat=stat, max_iter=100, verbose=True) + + # Visualize the data + # import matplotlib.pyplot as plt + # fig = plt.figure() + # ax = fig.add_subplot(111) + # data[['Best Fitness', 'Mean Fitness']].plot(ax=ax) + # ax.legend(loc='upper left') + # std = data['Standard Deviation of Fitnesses'] + # ax.set_xlabel('Generations') + # ax.set_ylabel('Fitness') + + # y = data['Mean Fitness'] + # ub = y + std * 0.5 + # lb = y - std * 0.5 + # ax.fill_between(np.arange(101), lb, ub, + # alpha=0.5, edgecolor='#CC4F1B', facecolor='#FF9848') + + # plt.savefig('plot-history.png') diff --git a/paper/paper(longer).md b/paper/paper(longer).md new file mode 100644 index 0000000..816ec83 --- /dev/null +++ b/paper/paper(longer).md @@ -0,0 +1,231 @@ +--- +title: 'Pyrimidine: An algebra-inspired Programming framework for evolutionary algorithms' +tags: + - Python + - genetic algorithms + - evolutionary algorithms + - intelligent algorithms + - algebraic system + - meta-programming +authors: + - name: Congwei Song + orcid: 0000-0002-4409-7276 + affiliation: "1" # (Multiple affiliations must be quoted) +affiliations: + - name: Beijing Institute of Mathematical Sciences and Applications, Beijing, China + index: 1 +date: 12 December 2023 +bibliography: paper.bib +toccolor: teal +citecolor: teal +linkcolor: teal +urlcolor: teal +output: + pdf_document: + toc: no + toc_depth: 4 + number_sections: yes +--- + +# `Pyrimidine`: An algebra-inspired Programming framework for evolutionary algorithms + +# Summary + +[`Pyrimidine`](https://github.com/Freakwill/pyrimidine) stands as a versatile framework designed for GAs, offering exceptional extensibility for a wide array of evolutionary algorithms, including particle swarm optimization and difference evolution. + +Leveraging the principles of object-oriented programming (OOP) and the meta-programming, we introduce a distinctive design paradigm is coined as "algebra-inspired Programming" signifying the fusion of algebraic methodologies with the software architecture. + +# Statement of need + +As one of the earliest developed optimization algorithms [@holland; @katoch], the genetic algorithm (GA) has found extensive application across various domains and has undergone modifications and integrations with new algorithms [@alam; @cheng; @katoch]. The principles of GA will not be reviewed in this article. For a detailed understanding, please refer to references [@holland; @simon] and the associated literatures. + +In a typical Python implementation, populations are initially defined as lists of individuals, with each individual represented by a chromosome composed of a list of genes. Creating an individual can be achieved utilizing either the standard library's `array` or the widely-used third-party library [`numpy`](https://numpy.org/) [@numpy]. Following this, evolutionary operators are defined and applied to these structures. + +A concise comparison between `pyrimidine` and several popular frameworks is provided in \autoref{frameworks}, such as [`DEAP`](https://deap.readthedocs.io/) [@fortin] and [`gaft`](https://github.com/PytLab/gaft), which have significantly influenced the design of `pyrimidine`. + + +| Library | Design Style | Versatility | Extensibility | Visualization | +|:----------:|:-------|:--------|:--------|:----------| +| `pyrimidine`| OOP, Meta-programming, Algebra-insprited | Universal | Extensible | export the data in `DataFrame` | +| `DEAP` | OOP, Functional, Meta-programming | Universal | Limited by its philosophy | export the data in the class `LogBook` | +| `gaft` | OOP, decoration pattern | Universal | Extensible | Easy to Implement | +| [`geppy`](https://geppy.readthedocs.io/) | based on `DEAP` | Symbolic Regression | Limited | - | +| [`tpot`](https://github.com/EpistasisLab/tpot) /[`gama`](https://github.com/openml-labs/gama) | [scikit-learn](https://scikit-learn.org/) Style | Hyperparameter Optimization | Limited | - | +| [`gplearn`](https://gplearn.readthedocs.io/)/[`pysr`](https://astroautomata.com/PySR/) | scikit-learn Style | Symbolic Regression | Limited | - | +| [`scikit-opt`](https://github.com/guofei9987/scikit-opt)| scikit-learn Style | Numerical Optimization | Unextensible | Encapsulated as a data frame | +|[`scikit-optimize`](https://scikit-optimize.github.io/stable/)|scikit-learn Style | Numerical Optimization | Very Limited | provide some plotting function | +|[`NEAT`](https://neat-python.readthedocs.io/) | OOP | Neuroevolution | Limited | use the visualization tools | + +: Comparison of the popular genetic algorithm frameworks. \label{frameworks} + +`Tpot`/`gama`[@olson; @pieter], `gplearn`/`pysr`, and `scikit-opt` follow the scikit-learn style [@sklearn_api], providing fixed APIs with limited extensibility. They are merely serving their respective fields effectively (as well as `NEAT`[@neat-python]). + +`DEAP` is feature-rich and mature. However, it primarily adopts a tedious meta-programming style. Some parts of the source code lack sufficient decoupling, limiting its extensibility. `Gaft` is a highly object-oriented software with excellent scalability, but it is currently inactive. + +`Pyrimidine` fully utilizes the OOP and meta-programming capabilities of Python, making the design of the APIs and the extension of the program more natural. So far, we have implemented a variety of optimization algorithms by `pyrimidine`, including adaptive GA [@hinterding], quantum GA [@supasil], differential evolution [@radtke], evolutionary programming [@fogel], particle swarm optimization [@wang], as well as some local search algorithms, such as simulated annealing [@kirkpatrick]. + +To meet diverse demands, it provides enough encoding schemes for solutions to optimization problems, including Boolean, integer, real number types and their hybrid forms. + +# Algebra-inspired programming + +The innovative approach is termed "algebra-inspired Programming." It should not be confused with so-called algebraic programming [@kapitonova], but it draws inspiration from its underlying principles. + +The advantages of the model are summarized as follows: + +1. The population system and genetic operations are treated as an algebraic system, and genetic algorithms are constructed by imitating algebraic operations. +2. It is highly extensible. For example it is easy to define multi-populations, even so-called hybrid-populations. +3. The code is more robust and concise. + +## Basic concepts + +We introduce the concept of a **container**, simulating an **(algebraic) system** where specific operators are not yet defined. + +A container $s$ of type $S$, with elements of type $A$, is represented by the following expression: +\begin{equation}\label{eq:container} +s = \{a:A\}: S \quad \text{or} \quad s:S[A]\,, +\end{equation} +where the symbol $\{\cdot\}$ signifies either a set, or a sequence to emphasize the order of the elements. The notation $S[A]$ mimicks Python syntax, borrowed from the module [typing](https://docs.python.org/3.11/library/typing.html?highlight=typing#module-typing). + +Building upon the concept, we define a population in `pyrimidine` as a container of individuals. The introduction of multi-population further extends this notion, representing a container of populations, referred to as "the high-order container". `Pyrimidine` distinguishes itself with its inherent ability to seamlessly implement multi-population GAs. It even allows to define containers in higher order, such as a container of multi-populations. + +While an individual can be conceptualized as a container of chromosomes, it will not necessarily be considered a system. Similarly, a chromosome might be viewed as a container of genes (implemented by the arrays in practice). + +In a population system $s$, the formal representation of the crossover operation between two individuals is denoted as $a \times_s b$, that can be implemented as the command `s.cross(a, b)`. Although this system concept aligns with algebraic systems, the current version diverges from this notion, and the operators are directly defined as methods of the elements, such as `a.cross(b)`. + +The lifting of a function/method $f$ is a common approach to defining the function/method for the system: +$$ +f(s) := \{f(a)\}\,, +$$ +unless explicitly redefined. For example, the mutation of a population typically involves the mutation of all individuals in it, but there are cases where it may be defined as the mutation of a randomly selected individual. Another type of lifting is that the fitness of a population is determined as the maximum of the fitness values among the individuals in the population. + +`transition` is the primary method in the iterative algorithms, denoted as a transform: +$$ +T(s):S\to S\,. +$$ +The iterative algorithms can be represented as $T^n(s)$. + +## Metaclasses + +A metaclass should be defined to simulate abstract algebraic systems, which are instantiated as a set containing a set of elements, as well as operators and functions on them. Currently, the metaclass `MetaContainer` is proposed to create container classes without defining operators explicitly. + +## Mixin classes + +Mixin classes specify the basic functionality of the algorithm. + +The `FitnessMixin` class is dedicated to the iteration process focused on maximizing fitness, and its subclass `PopulationMixin` represents the collective form. + +When designing a novel algorithm, significantly differing from the GA, it is advisable to start by inheriting from the mixin classes and redefining the `transition` method. + +## Base Classes + +There are three base classes in `pyrimidine`: `BaseChromosome`, `BaseIndividual`, `BasePopulation`, to create chromosomes, individuals and populations respectively. + +For convenience, `pyrimidine` provides some commonly used subclasses, where the genetic operations are implemented such as, `cross` and `mutate`. Especially, `pyrimidine` offers `BinaryChromosome` for the binary encoding as used in the classical GA. + +Generally, the algorithm design starts as follows, where `MonoIndividual`, a subclass of `BaseIndividual`, just enforces that the individuals can only have one chromosome. + +```python +class UserIndividual(MonoIndividual): + # The individual with only one chromosome, + # in type of `BinaryChromosome` + element_class = BinaryChromosome + # default_size = 1 + + def _fitness(self): + # Compute the fitness + +class UserPopulation(StandardPopulation): + element_class = UserIndividual + default_size = 10 +``` + +In the template code above, `UserIndividual` (or `UserPopulation`) serves as a container of elements in type of `BinaryChromosome` (or `UserIndividual`), and employs the operators of the elements in the lifting form by default. Following is the equivalent expression, using the notion in \autoref{eq:container}: + +```python +UserIndividual = MonoIndividual[BinaryChromosome] +UserPopulation = StandardPopulation[UserIndividual] // 10 +``` + +Instead of overriding the `fitness` attribute, users are recommended to override the `_fitness` method, where the concrete fitness computation is defined. The operator `// 10` is equivalent to set `default_size = 10`. + +Algebraically, there is no difference between `MonoIndividual`, the individual class with a single chromosome, and `Chromosome`. Meanwhile the population also can be treated as a container of chromosomes. So the code can be further simplified as follows. + +```python +class UserChromosome(BaseChromosome): + def _fitness(self): + # Compute the fitness + +UserPopulation = StandardPopulation[UserChromosome] // 10 +``` + +# An example to begin + +Here, we demonstrate the basic usage of `pyrimidine` with the classic 0-1 knapsack problem, whose solution can be naturally encoded in binary format: + +$$ +\max \sum_i c_ix_i \\ +\text{st}~ \sum_i w_ix_i \leq W \\ +\quad x_i=0,1; i=1,\cdots,n +$$ + +where $c_i$ and $w_i$ represents the value and the weight of the $i$-th bag respectively, and $x_i$ is a binary variable indicating whether the $i$-th bag is taken or not. + +```python +from pyrimidine import BinaryChromosome, MonoIndividual, StandardPopulation +from pyrimidine.benchmarks.optimization import Knapsack + +n = 50 +_evaluate = Knapsack.random(n) # the objective function + +class UserIndividual(MonoIndividual): + element_class = BinaryChromosome // n + def _fitness(self): + return _evaluate(self[0]) + +# equivalent to: +# UserIndividual = MonoIndividual[BinaryChromosome // n] +# .set_fitness(lambda o: _evaluate(o[0])) + +UserPopulation = StandardPopulation[UserIndividual] // 20 +``` + +Using chromosome as the population's elements, we arrange all the components in a single line: +```python +UserPopulation = StandardPopulation[BinaryChromosome // n].set_fitness(_evaluate) +``` + +Then we execute the evolutionary program as follows. +```python +pop = UserPopulation.random() +pop.evolve(max_iter=100) +# to avoid unnecessary computations, use the method `ezolve` +# pop.ezolve(max_iter=100) +``` + +Finally, the optimal individual can be obtained with `pop.best_individual`. + +# Visualization + +Instead of implementing visualization methods, `pyrimidine` yields a `pandas.DataFrame` object [@mckinney] that encapsulates statistical results for each generation by setting `history=True` in the `evolve` method. Users can harness this object to plot the performance curves. Generally, users are required to furnish a "statistic dictionary" whose keys are the names of the statistics, and values are functions mapping the population to numerical values, or strings presenting pre-defined methods or attributes of the population. + +```python +# statistic dictionary, computing the mean, the maximum and +# the standard deviation of the fitnesses for each generation +stat = {'Mean Fitness': 'mean_fitness', +'Best Fitness': 'max_fitness', +'Standard Deviation of Fitnesses': lambda pop: np.std(pop.get_all_fitness()) +} + +# obtain the statistical results through the evolution. +data = pop.evolve(stat=stat, max_iter=100, history=True) +``` + +`data` is an `pandas.DataFrame` object, with the columns "Mean Fitness", "Best Fitness" and "Standard Deviation of Fitnesses". Now utilize the `plot` method of the object (or the Python library `matplotlib`) to show the iteration history \autoref{history}. + +![The fitness evolution curve of the population. \label{history}](plot-history.png) + +# Conclusion + +`Pyrimidine` is a versatile framework suitable for implementing various evolution algorithms. Its design offers strong extensibility. A key factor is that it was developed inspired by algebra. For users developing novel algorithms, `pyrimidine` is a promising choice. + +# References diff --git a/pyrimidine/benchmarks/__pycache__/__init__.cpython-312.pyc b/pyrimidine/benchmarks/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d11619f3e5d41e1704ada6bc6a45be30751dae23 GIT binary patch literal 756 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 Ko1eA`d%(=egCF$GpZ}+9E zjj8L0f|dzwb<$J^rZTWW!3+f}4F7=OM8Q0tFo^pg2tvPg6j6qt=iZl>ymoQw7cabf z&wJHR&!k?ND`kB7yjVlZ5?|`s?MC3;z7P%;naegjl@|$A3 zpU3o@DO%?@i+sp@^g4ZUbqsT2-8ApFTtjaLeZw{M^`N(kcF{78{WcgIV6?+%g|Shz zO(Xwx6DDtCy3KkZ8I*!T)Eyp+2UAiaF1y1C;tqu3!O@sNj#*c!U0zekBunuUFCQnV z5|@;QO8A8!u9}SMK?ZlA_1B0WQP?qXYMVE$EB3ISve>s4>bwjHnXjkcfSO0{zO6`Jkaq(oSUXTSsgb6t0 zSS*i;8B{Wz>Avzr=TmZs$erU-G%5+P&iy12Awn!B#Uq`ui9J$kbS%)BoFGz65~X;^ zC{}zZcgo37P!ggY$%&GcB;-&$KoY@Y#0kHkXp()#TLgI@fioacba_t#P(@ zrg!e4Y**Ho9nRjHbLM(-gE`l-b7|{h|FTbM=}?+G3eG}LVX)v*ns=^XrTP99LD|x; z+6IdJz&dP-HUtS=r3Gmkgntf_h^eb}Cr;uNUC&yIhMTnv*7f?RttyAXB=Q-oeM!S= z79h53+J+Dd3jqN|w9S6%Ck^!NkNoBD_XYO%dwF7qcS3W7P;?Or5iiFARN_dtOoQll zlj~t9S>)iT5Dte3xdC1s@W=zufnOVJv#uG}9G>f%=TF@Vz+YwPwaxkFw`H5o1l|rWhI78NWa-J}wxxq_#g(nwm8R{g z?d}4n*t&~+_th?<(PDf+w|tqBAx?p$wOX7I=3YTzAj@g{B+g(sm}!WGd4uB{Vf_|( z)_;NS4g7CiUvU~oVR!8?7_^meXjaocV_Z#Pu&EhV(|~MhqG<-Gp9b!|GArtqQM+_m zA1RL+0-S>gvh?tpt65RvlO!RMt*|A_jgna)LOc@6mvaQiwG^4sf{w%KW{7xb9OfMB z=!+(R`Q_amVY*%(g6?;SzQZ|tV&=r0kafJL2tgd(DqT_ zmGIY6Kd~%rF*}6mruQQSaA#&EyD~JUs|6L4=f)qIPuYz zXxYP}M{b4v$ZfhAIu>jO3&@?IZKX2|XxM8&!`}ZF4fMidXh;K{ZbU;ZVlz-eP1fIx z0mJ+nm@#N;>^5`NZLHlQ8p@2QG3-;OA~;p08MHP2ay4!Z^Z&EWA_pGej9H));NRI! z&!Jrq6|@^KJ;LD4DA5fkx*FcjQ6UaBm;u=ge{aC#D*v4hEJ*WG4(GaZzNH@3b^H5G zAGq?akGPK;&NZyGeP~y`4?wZy?f)Dr-T}oms5&0{GVoRSi}1I@-#z>Fv&E;MDf$l; zpF6Dh53Bp0|6wF>aU`sagnx)g7b8g}k}Mu4#nf2w#JG|gS0gW|BNN5+i^|B0#muBK zGO6r)NqzLB;y78foV-?|n6-M_$XXS+xt1{t(Igb#WCt99l7)O1bd@^h zv}d7xzWt2n?e@j?obxSjF1XkU<@44KwW(9J?I`j)n1j7|l~SVDQK~<3L`(#a94YZJ zAvIbpJQ-z{>f&QDDEJwXQjfDLo|T-ej$(HgqbwE^W>X$%A*5yM6-!?%A&x~uyGbv+ z(uzzz1l^hmV|>}nVg5G@!nY`>^+(jS#&dWO&owQy%(tu~Dz8~N-1iEZP0yt1hrTr{ W#y$E35Z5gx+_-M9b{KcDpZ^6>QM0Q6 literal 0 HcmV?d00001 diff --git a/pyrimidine/learn/__pycache__/__init__.cpython-312.pyc b/pyrimidine/learn/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a13a8b98e1e5776e9e286732ca33de64e7f38d08 GIT binary patch literal 1933 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 Ef3?~wYXATM literal 0 HcmV?d00001 diff --git a/pyrimidine/local_search/__pycache__/__init__.cpython-312.pyc b/pyrimidine/local_search/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e6469156d809a3e337cc5868eae5609b4bd0686 GIT binary patch literal 274 zcmX@j%ge<81e_L$X@x-gF^B^LOi;#WB_LxuLkdF*V-CYwCPoO05zb-)u_~D~nO`yj z6>2ixV$|}}WWFU_oS9pilUS0P5}%ltmztQ9nU{WxyC^X)B|kU5JTWKx7I#TvQfYi~ zYGP4x#!80IAnSew>4z4l78UE4XXfN&Cg$n~6y>KECFbS=HS6bAx@VSTlqTsHR2F6C zW~OB3r6MzP@{<#DpvLLP$7kkcmc+;F6;%G>u*uC&Da}c>D*|~I9R{hp3{piLKtTYuQ%%SK literal 0 HcmV?d00001 diff --git a/pyrimidine/local_search/__pycache__/random_walk.cpython-312.pyc b/pyrimidine/local_search/__pycache__/random_walk.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d88bfdfd9ef660ebf297be8c8d8614fd742d8091 GIT binary patch literal 1458 zcmZ`(-EZ4e6hGHr@shM9r7djjASw$e($q*u6G%g#OktH6L;a{H>K->0jemi6p1KZ*tUZ7b4}GE#hQyvYH%>~J%8`70?z!j7_xHR0 zZe%10C}&n|MxzZDSi}X7XoP?zSfVR+>#CRnm9?u&7b zCN-3N;)pNuB$8@A^^UMSU!<=(^_tDtJI<$0&5Xli%>%e}FQ96J4H~gP6D*>Myq`lf ziTBZDOGd5m4pH(wqh|Tu`-WQ$reZtG7ulj)@#m6Rlt^Yy#WRiwRy7AbcpnL$F?_mz zp=!g1)Px&oJt-Dxl0XFr@H~QXfOQ_%|IXo93%(Zic@va%xhdZmoL}tf>83P@Ab=6d z&{p5X+NXE}L}^`VDwbF+0nisP7bkjBnxOM<>dF6A*H0#IKm+npxpHs*=V0RAZ*L$r ziNzQj+QUkwyhkLN{&FKyyw$o?0y?Bs*F(jQ- zI$ZheQs?>Ey^Y!tpcU$KP$CYD2wG7QlR18t_$@Ol9pgwG=&OQ`6!D75SwCCIF(l7X`~rYEx>NKgfC_#tb;X%NYX$L>3P>6 zFO&zc^BL*Ws3MVm!lFb?uh@8H=_tDL66I$ZgG*{ra)=6?d>mjp$s zH+u!uCOk|()ylNq{Jb2JhYKJ4Fn#^oVCStnnQtcVPTmQAEEEq4#l!rS!}?7 z_v*8Mqrv#K`YKlxr7}`a)%BX;*}5Jjb=~u=RhQdoU0+!>-2P5N*Dc>fx3X*?%^01+ z3cc~}0)2+ty?Hr0l&drNu=A*H!tVl+)nf(5rdliEhnrXbkW~4l?t~y~B+lgV<-8oF znCUDx<`~`)M)~03U3TztHu}BDcqyjnrD`fvv P#_@OCnt25L-An!l5pqF4 literal 0 HcmV?d00001 diff --git a/pyrimidine/local_search/__pycache__/simulated_annealing.cpython-312.pyc b/pyrimidine/local_search/__pycache__/simulated_annealing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9628966face5b24f44b15c0702b22a1acad83b6 GIT binary patch literal 1968 zcmZ`(%Wo4$7@vLl729zjArL4G5{NiT>;h>LqEZ?qZ4?SgRA?(%qN~mB#9p!wGrNY` zbqWU#j+!drPz9t)loLg%=)cforCva^!mQdu+Y{=cfxsoFezSgrM;Lp*`M&v{GxPiA z*H|oqV1$R1+4m)c{$WC6fb5U;0hm^hhB8RwG~VD$KErd27l0Qs0*81sjx_Nq(j>yK z3N^QkSPUeCrEeo=bhBV6F46FaWf8^Dtt=ifvNqM-oEeGqd`xD0B4<+gBc0CW6_@Jj zTrVC?;jt7RC*bJz;*&Jhi%+NUbP9h;R5hok@KoN_O}(VJx^3Z^BHkiP3Lic;baWUW zIr4UQ|2xNqlEZjl@NnP2;IYVK9*RtIUfXm|v0U4nur&Rmt`!sm7F$i?Qaf)Ox+Bwq zLDbrYYBW$6)_Z|jK?G$uI2m5!GJ?j#PtZh7(gNQ}8Bq&fMHxv80Sy$x$%wbDS!DAh zO7f_oI1Zk*=~i2aU>H6bap7cU3NCSmeVHlp3RO%8cWb)p!sdLDxP&@bxA2(}-!6N1 z{I62`-TzKJ{sjtttO3dPjqdsP?}4rpNy(GQ7p|;&l5VjnWXU9Nt2u`)=O(J})@U3qESwUrV9_Clayx`VhfS3jId1b8DV~(M7M-iGA!7BKQ35aE(VaEXv zHlf#TO54C+@U=deU$&Ton|8Rxqa3n0k<}7=S1k?b;jT^SowQjTht4U?r1M1zEuiTZd66}2RWW3TC{)d* zoks7;N~8Bu`Jxwbp=3vAy;FMYbx@`@)q9~Nhc&qghL&nx0Lk#$IeRMwe|!EnQHw0;lBo3M7iiL{2>=8ewtZsQq4 zW&V7lc{dH;fZwewOK_`Y?niF5ULnIA-2+j#z$D=8g&Yl5ZuUWuFY-yD^x0`Z3(nWx zpk`Y)nGu>Tn8b2lXnBT?v-nG^V9pRakNf(tOUyi>id&$h>6qj_Zj5$7-bnymu%5t+ zWr-_WM9=1C>;k2%8ZS7jyB2XAuSLyK+q50qBu-MK&{W8e;0Y=qMw~U2tiwE51+_vnE&0YwvZ7sQ-Q2EC9hTZ&0! z*^9`sX>0IM10R*;3-F59H9=X{Y!#LRwWpHq1ZThQJID`I+X#aNWrXd>VU=G+Pk2!Z z0akbJskU`g+dHdm?bWu9>hQu-zuEPXScz2ig^~7~+4+w7 z<~!%-En7qcqv@zJG$0`K7yYRYe`Qe~g2fC{Q4*<)%Ib{4CRv8AIh`~3ByaeVKKGy3 z{YD@eU=R+l=oC_Yw~^|{>;uk|^q?XzDY)1GUTk`kWLbqKZ#4HbZZn7Ni(Is{-Z}E#Z;vsix`TojRAqT-7ktg7x54# z*i2))NA!3O)MQYmbBdYAx^z~_=J5E_iEpL-ef|A!%kf6;Mq25hbU`6G z>9osqI&t#+#dDXWGjZv-Wn}ZYxYS1_9qpHofyRdp9zJyBzl+>d=HZ&mw+_B}I4(Y8 z!NxM6C#Dixkoo-?)>n@>4zp ziy4Gbk^%3tDwE_?HiI%qQO{@)%Dm$nRrEY|nsUIJ(ltBh1eA16vrN0PXM=Oo5FoWdH4Vd8{u?en zgK{-o5sgq&G>iuo{7nw<)myHiDRMOmzDQ}RIk1Lilp#MA`4PyL#sTyj>|Q&85P2Us z+)P`~Q$(qn7Qnq_J(m7$auPp6Z{0Ae`K2=V*y=qPWR`L*Rl*ttdkw7fO11iOW9yEz zRdub;b+5o}u5V(I&29hbtF!$kcWkfUt-4x!N8s98-LKD8-!Z6wWG(?I9T?JbCbn%y zuq{1L^CUAK{qfYFW8VzOK|%wI(1>!jWNf& zlwqlPovwqa)OC0WR4{>5O109k?awNZdp79+Nv_2RJ^BaaO7rYU4PP>Q0-+_zK4`%yWN$ w;a{{Ort^2y`5Wqh$YVpR>;b0p&ebx4sm#H~3f)+}%J`YKJGWjSm|Qjg1*=W4t^fc4 literal 0 HcmV?d00001 diff --git a/pyrimidine/misc/__init__.py b/pyrimidine/misc/__init__.py new file mode 100755 index 0000000..9bd2fd5 --- /dev/null +++ b/pyrimidine/misc/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 + +from .aco import * +from .fa import * \ No newline at end of file diff --git a/pyrimidine/misc/aco.py b/pyrimidine/misc/aco.py new file mode 100644 index 0000000..143e377 --- /dev/null +++ b/pyrimidine/misc/aco.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 + +"""Ant Colony Optimization + +*Ref* +Blum, Christian. "Ant colony optimization: Introduction and recent trends." Physics of Life reviews 2.4 (2005): 353-373. +""" + + +import numpy as np +from .base import FitnessMixin, PopulationMixin +from .meta import MetaContainer +from scipy.stats import rv_discrete + +from random import random + + +class BaseAnt(FitnessMixin): + + params = {"initial_position": None, + "path": None, + "n_steps": 1, + "greedy_degree": 1, + "move_flag": True} + + def init(self): + self.path = [0] + + def move(self, colony=None, n_steps=1): + """To move an ant + + Args: + colony (None, optional): the population of ants + n_steps (int, optional): the number of steps in each move + + Returns: + bool + """ + colony = colony or self._colony + i = self.path[-1] + self.new_path = [i] + for _ in range(n_steps): + next_positions = [j for j in colony.positions if j not in self.path] + if not next_positions or not self.move_flag: break + elif len(next_positions) == 1: + i = next_positions[0] + else: + ps = np.array([colony.move_proba[i, j] for j in colony.positions if j not in self.path]) + ps += 0.0001 + ps **= self.greedy_degree + ps /= np.sum(ps) + rv = rv_discrete(values=(next_positions, ps)) + i = rv.rvs(size=1)[0] + self.new_path.append(i) + self.path.append(i) + if len(self.new_path) >= 2: + self.release_pheromone(colony) + self.move_flag = True + else: + self.move_flag = False + + def release_pheromone(self, colony): + self.pheromone = colony.sedimentation / np.sum(colony.distances[i,j] for i, j in zip(self.new_path[:-1], self.new_path[1:])) + + def get_length(self, distances=None): + distances = distances or self._colony.distances + return np.sum(distances[i,j] for i, j in zip(self.path[:-1], self.path[1:])) + + def _fitness(self): + n = len(self._colony.positions) - len(self.path) + if n == 0: + return 1 / self.get_length() + else: + rest_positions = [self.path[-1]] + [j for j in self._colony.positions if j not in self.path] + M = np.mean([self._colony.distances[i,j] for i in rest_positions for j in rest_positions if i fi.fitness: + fi.move(self.alpha * att[i, j] * (fj.position - fi.position)) + + for f in self: + f.random_move() + + for f in self: + f.backup() diff --git a/pyrimidine/misc/gsa.py b/pyrimidine/misc/gsa.py new file mode 100644 index 0000000..6eeed23 --- /dev/null +++ b/pyrimidine/misc/gsa.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 + +""" +The Gravity Searching Algorithm (GSA) is a metaheuristic optimization method that simulates the law of gravity in physics. +It was introduced as a nature-inspired algorithm for solving optimization problems, particularly in continuous domains. + +*References* +1. Rashedi, E., Nezamabadi-Pour, H., & Saryazdi, S. (2009). "GSA: A Gravitational Search Algorithm". Information Sciences, 179(13), 2232-2248. +2. Rashedi, E., Nezamabadi-Pour, H., & Saryazdi, S. (2011). "A New Method for Solving Optimization Problems Using Gravitational Search Algorithm". International Journal of Computer Applications, 22(8), 1-6. +3. Niazi, M., Mirjalili, S., Mirjalili, S. M., & Yang, X. S. (2016). "Enhanced Gravity Search Algorithm". Swarm and Evolutionary Computation, 6(1), 10-21. +""" + +from scipy.spatial.distance import pdist, squareform +from .base import PopulationMixin +from .chromosome import FloatChromosome +from .pso import BaseParticle +from .utils import euclidean, random, exp, metropolis_rule +from .deco import side_effect + +import numpy as np + + +class Particle(BaseParticle): + """A particle in GSA + + Extends: + PolyIndividual + + 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 + + params = {'accelerate': 0} + + @property + def position(self): + return self.chromosomes[0] + + @position.setter + def position(self, x): + self.chromosomes[0] = x + self.after_setter() + + @property + def velocity(self): + return self.chromosomes[1] + + @velocity.setter + def velocity(self, v): + self.chromosomes[1] = v + + @side_effect + def move(self): + """Moving the particl with Newton's mechanics + """ + r = random() + cpy = self.copy(fitness=None) + cpy.velocity = r * cpy.velocity + cpy.accelerate + cpy.position = cpy.position + cpy.velocity + flag = metropolis_rule(D=cpy.fitness - self.fitness, T=10) + D = cpy.fitness - self.fitness + if flag: + self.chromosomes = cpy.chromosomes + + +class GravitySearch(PopulationMixin): + """Standard GSA + + Extends: + PopulationMixin + """ + + alias = {'particles': 'elements', + 'n_particles': 'n_elements'} + + element_class = Particle + default_size = 20 + + params = {'gravity_coefficient': 100, 'attenuation_coefficient': 10} + + def compute_mass(self): + fitnesses = np.asarray([particle.fitness for particle in self]) + worst_fitness = np.min(fitnesses) + best_fitness = np.max(fitnesses) + epsilon = 0.0001 + m = (fitnesses - worst_fitness + epsilon) / (best_fitness - worst_fitness + epsilon) + return m / m.sum() + + def compute_accelerate(self): + # compute force + D = np.array([[pj.position - pi.position for pi in self] for pj in self]) + R = squareform(pdist([p.position for p in self])) + for i in range(self.n_particles): + R[i, i]=1 + m = self.compute_mass() + M = np.tile(m, (self.n_particles, 1)) + for i in range(self.n_particles): + M[i, i]=0 + M /= R**3 + M *= self.gravity_coefficient * np.random.random((self.n_particles, self.n_particles)) + A = M[:,:,None] * D + A = A.sum(axis=0) + + # set accelerate + for i, particle in enumerate(self): + particle.accelerate = A[i, :] + + def transition(self, k): + """ + Transitation of the states of particles + """ + self.compute_accelerate() + self.move() + self.gravity_coefficient = exp(-self.attenuation_coefficient*k / self.max_iter) + + def move(self): + for particle in self: + particle.move() + diff --git a/pyrimidine/misc/sma.py b/pyrimidine/misc/sma.py new file mode 100755 index 0000000..c3f42b6 --- /dev/null +++ b/pyrimidine/misc/sma.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +"""Slime Mould Algorithm +""" + +from scipy.spatial.distance import pdist, squareform +import numpy as np + +from .base import BaseIndividual +from .mixin import PopulationMixin, FitnessMixin +from .chromosome import FloatChromosome +from .population import HOFPopulation +from random import random + +from .deco import basic_memory +from .utils import randint2 + + +@basic_memory +class SlimyMaterial(FitnessMixin): + + def approach_food(self, fame, direction, p, vb, vc): + if random() < p: + self[:] = (fame + vb * direction)[:] + else: + self[:] = (vc * self)[:] + + def random_move(self): + raise NotImplementedError + + +class SlimeMould(HOFPopulation): + """Slime Mould Algorithm + """ + + element_class = SlimyMaterial + + params = { + "max_iter": 100 + } + + def get_ranks(self): + all_fitness = np.array([i._fitness() for i in self]) + max_fitness = np.max(all_fitness) + min_fitness = np.min(all_fitness) + return (max_fitness - all_fitness)/(max_fitness - min_fitness) + + def approach_food(self, t): + N = len(self) + # calculate vc and a + vc = 1 - t/self.max_iter + a = np.arctanh(vc) + # all fitness, max/min fitness + all_fitness = self.get_all_fitness() + max_fitness = np.max(all_fitness) + # calculate p and w + ps = np.tanh(np.subtract(max_fitness, all_fitness)) + ks = np.argsort(all_fitness) + sign = np.ones(N); sign[ks[:N//2]] = -1 + ws = 1 + sign * random() * np.log1p(self.get_ranks()) + fame = self[ks[-1]] + for sm, p, w in zip(self, ps, ws): + i, j = randint2(0, N-1) + direction = w * self[i] - self[j] + vb = a * (2*random() - 1) + if random()< 0.03: + sm.random_move() + else: + sm.approach_food(fame, direction, p, vb, vc) + + def transition(self, t): + + self.approach_food(t) + for i in self: + i.backup() diff --git a/pyrimidine/misc/ssa.py b/pyrimidine/misc/ssa.py new file mode 100644 index 0000000..2098bbc --- /dev/null +++ b/pyrimidine/misc/ssa.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 + +"""Sparrow Search Algorithm + +Algorithm 1 The framework of the SSA. +Input: +G: the maximum iterations +PD: the number of producers +SD: the number of sparrows who perceive the danger R2 : the alarm value +n: the number of sparrows +Initialize a population of n sparrows and define its relevant parameters. +Output: Xbest, fg. +while (t < G) + Rank the fitness values and find the current best individual and the current worst individual. + R2 = rand(1) + for i = 1 : PD + update the sparrow’s location; + for i = (PD + 1) : n + update the sparrow’s location; + for l = 1 : SD + update the sparrow’s location; + Get the current new location; + If the new location is better than before, update it; + t = t + 1 +return Xbest, fg. + +*References* +Jiankai Xuea, and Bo Shena, A novel swarm intelligence optimization approach: sparrow search algorithm. +""" + +from random import gauss, random, randint +import numpy as np +from scipy.spatial.distance import pdist, squareform + +from .mixin import PopulationMixin +from .chromosome import FloatChromosome +from .individual import PolyIndividual + + +@basic_memory +class BaseSparrow(FloatChromosome): + + def move(self, ST, i): + r = random() + Q = gauss() + alpha = random()+0.01 + if r