├── .coveragerc ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CHANGELOG.md ├── INSTALL.md ├── LICENSE ├── MANIFEST.in ├── README.rst ├── examples ├── 1D_optimization_example.ipynb ├── ex01 │ ├── animation.gif │ ├── best_fit.py │ ├── envolution_curve.png │ ├── ex01.py │ ├── plot_animation.py │ └── plot_envolution_curve.py └── ex02 │ ├── best_fit.py │ ├── envolution_curve.png │ ├── ex02.py │ ├── plot_envolution_curve.py │ ├── plot_surf.py │ ├── surface.png │ └── surface_animation.gif ├── gaft ├── __init__.py ├── analysis │ ├── __init__.py │ ├── console_output.py │ └── fitness_store.py ├── components │ ├── __init__.py │ ├── binary_individual.py │ ├── decimal_individual.py │ ├── individual.py │ └── population.py ├── engine.py ├── mpiutil.py ├── operators │ ├── __init__.py │ ├── crossover │ │ ├── __init__.py │ │ └── uniform_crossover.py │ ├── mutation │ │ ├── __init__.py │ │ └── flip_bit_mutation.py │ └── selection │ │ ├── __init__.py │ │ ├── exponential_ranking_selection.py │ │ ├── linear_ranking_selection.py │ │ ├── roulette_wheel_selection.py │ │ └── tournament_selection.py ├── plugin_interfaces │ ├── __init__.py │ ├── analysis.py │ ├── metaclasses.py │ └── operators │ │ ├── __init__.py │ │ ├── crossover.py │ │ ├── mutation.py │ │ └── selection.py └── tests │ ├── __init__.py │ ├── dynamic_linear_scaling_test.py │ ├── engine_test.py │ ├── exponential_ranking_selection_test.py │ ├── flip_bit_big_mutation_test.py │ ├── flip_bit_mutation_test.py │ ├── individual_test.py │ ├── linear_ranking_selection_test.py │ ├── linear_scaling_test.py │ ├── mpiutil_test.py │ ├── population_test.py │ ├── roulette_wheel_selection_test.py │ ├── test_all.py │ ├── tournament_selection_test.py │ └── uniform_crossover_test.py ├── gasol ├── CMakeLists.txt ├── README.md ├── example │ ├── 1d_search.cpp │ └── CMakeLists.txt ├── src │ ├── CMakeLists.txt │ ├── crossover.cpp │ ├── crossover.h │ ├── engine.cpp │ ├── engine.h │ ├── individual.cpp │ ├── individual.h │ ├── mpih.h │ ├── mpiutils.cpp │ ├── mpiutils.h │ ├── mutation.cpp │ ├── mutation.h │ ├── population.cpp │ ├── population.h │ ├── selection.cpp │ └── selection.h └── unittest │ ├── CMakeLists.txt │ ├── crossover_test.cpp │ ├── engine_test.cpp │ ├── gasol_test.cpp │ ├── individual_test.cpp │ ├── mpiutils_test.cpp │ ├── mutation_test.cpp │ ├── population_test.cpp │ └── selection_test.cpp ├── setup.cfg └── setup.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = gaft 4 | 5 | [report] 6 | exclude_lines = 7 | raise NotImplementedError 8 | if __name__ == .__main__.: 9 | ignore_errors = True 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | *.swp 104 | .DS_Store 105 | 106 | *.prof 107 | # Prerequisites 108 | *.d 109 | 110 | # Compiled Object files 111 | *.slo 112 | *.lo 113 | *.o 114 | *.obj 115 | 116 | # Precompiled Headers 117 | *.gch 118 | *.pch 119 | 120 | # Compiled Dynamic libraries 121 | *.so 122 | *.dylib 123 | *.dll 124 | 125 | # Fortran module files 126 | *.mod 127 | *.smod 128 | 129 | # Compiled Static libraries 130 | *.lai 131 | *.la 132 | *.a 133 | *.lib 134 | 135 | # Executables 136 | *.exe 137 | *.out 138 | *.app 139 | 140 | /build 141 | /parallel_build 142 | /serial_build 143 | 144 | # Vim temp files 145 | *.swp 146 | 147 | mpiflag.h 148 | 149 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs"] 2 | path = docs 3 | url = https://github.com/PytLab/gaft-docs.git 4 | [submodule "gasol/unittest/googletest"] 5 | path = gasol/unittest/googletest 6 | url = https://github.com/google/googletest.git 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | 5 | notifications: 6 | email: false 7 | 8 | install: 9 | - sudo apt-get update 10 | - sudo apt-get install mpich 11 | - pip install coverage codecov mpi4py 12 | - python setup.py install 13 | 14 | script: 15 | - coverage run -m gaft.tests.test_all 16 | # Test parallel running. 17 | - mpirun -np 2 python -m gaft.tests.test_all 18 | # Upload test coverage report 19 | - codecov 20 | 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Version: 0.5.5 2 | ### Date: 2018-10-25 3 | 1. Make MPIUtil a singleton and remove in-module instantiation. 4 | 2. Standardize docstring for Sphinx documentation 5 | 6 | ## Version: 0.5.4 7 | ### Date: 2018-01-30 8 | 1. Fixed important bugs in individual clone. 9 | 10 | ## Version: 0.5.3 11 | ### Date: 2018-01-15 12 | 1. Removed redundant calling of fitness function in iteration. 13 | 2. Improved the efficiency. 14 | 15 | ## Version: 0.5.2 16 | ### Date: 2017-12-15 17 | 1. Removed verboisty parameter for `BinaryIndividual` class. 18 | 2. Added unittest for decimal individual. 19 | 20 | ## Version: 0.5.1 21 | ### Date: 2017-12-13 22 | 1. Added `DecimalIndividual` class. 23 | 2. Added decimal encoding individual support for flip bit mutation classes. 24 | 3. Fixed some bugs. 25 | 26 | ## Version: 0.5.0 27 | ### Date: 2017-12-12 28 | 1. Changed class names: 29 | - `GAIndividual` -> `BinaryIndividual` 30 | - `GAPopulation` -> `Population` 31 | - `GASelection` -> `Selection` 32 | - `GACrossover` -> `Crossover` 33 | - `GAMutation` -> `Mutation` 34 | - `variants` -> `solution` 35 | 36 | 2. Added abstract Individual base class `IndividualBase`: 37 | - Users can inherit the `IndividualBase` class and add implementations of `encode` and `decode` methods to define you own custom individual class with different encoding methods. 38 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # GAFT Installation Instruction 2 | 3 | ## 1. Install MPI implementations ([MPICH](http://www.mpich.org/downloads/), [OpenMPI](https://www.open-mpi.org/software/ompi/v3.0/)) 4 | 5 | ### Ubuntu 6 | 7 | ``` shell 8 | sudo apt install mpich 9 | ``` 10 | 11 | ### macOS 12 | 13 | ``` shell 14 | brew install mpich 15 | ``` 16 | 17 | ### Windows 18 | 19 | Download the [Microsoft MPI (MS-MPI)](http://msdn.microsoft.com/en-us/library/bb524831%28v=vs.85%29.aspx) 20 | 21 | ### Other platforms 22 | 23 | See more details in http://www.mpich.org/downloads/ 24 | 25 | ## 2. Install GAFT 26 | 27 | ### Via pip 28 | 29 | ``` shell 30 | pip install gaft 31 | ``` 32 | ### From source 33 | 34 | ``` shell 35 | git clone git@github.com:PytLab/gaft.git 36 | 37 | cd gaft 38 | 39 | python setup.py install 40 | ``` 41 | 42 | ## 3. Run test 43 | 44 | ``` shell 45 | cd gaft 46 | 47 | python tests/gaft_test.py 48 | ``` 49 | 50 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.md 2 | include *.rst 3 | recursive-include gaft *.py 4 | recursive-include example *.py 5 | prune gasol * 6 | 7 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ==== 2 | GAFT 3 | ==== 4 | 5 | A **G**\ enetic **A**\ lgorithm **F**\ ramework in py\ **T**\ hon 6 | 7 | .. image:: https://travis-ci.org/PytLab/gaft.svg?branch=master 8 | :target: https://travis-ci.org/PytLab/gaft 9 | :alt: Build Status 10 | 11 | .. image:: https://img.shields.io/codecov/c/github/PytLab/gaft/master.svg 12 | :target: https://codecov.io/gh/PytLab/gaft 13 | :alt: Codecov 14 | 15 | .. image:: https://landscape.io/github/PytLab/gaft/master/landscape.svg?style=flat 16 | :target: https://landscape.io/github/PytLab/gaft/master 17 | :alt: Code Health 18 | 19 | .. image:: https://img.shields.io/badge/python-3.5-green.svg 20 | :target: https://www.python.org/downloads/release/python-351/ 21 | :alt: platform 22 | 23 | .. image:: https://img.shields.io/badge/pypi-v0.5.7-blue.svg 24 | :target: https://pypi.python.org/pypi/gaft/ 25 | :alt: versions 26 | 27 | .. image:: https://readthedocs.org/projects/gaft/badge/?version=latest 28 | :target: https://gaft.readthedocs.io/en/latest/?badge=latest 29 | :alt: Documentation Status 30 | 31 | 32 | Introduction 33 | ------------ 34 | 35 | **GAFT** is a general Python Framework for genetic algorithm computation. It provides built-in genetic operators for target optimization and plugin interfaces for users to define your own genetic operators and on-the-fly analysis for algorithm testing. 36 | 37 | **GAFT** is now accelerated using MPI parallelization interfaces. You can run it on your cluster in parallel with MPI environment. 38 | 39 | Python Support 40 | -------------- 41 | 42 | **GAFT** requires Python version 3.x (Python 2.x is not supported). 43 | 44 | Installation 45 | ------------ 46 | 47 | 1. Via pip:: 48 | 49 | pip install gaft 50 | 51 | 2. From source:: 52 | 53 | python setup.py install 54 | 55 | If you want GAFT to run in MPI env, please install mpi4py explicitly:: 56 | 57 | pip install mpi4py 58 | 59 | See `INSTALL.md `_ for more installation details. 60 | 61 | Test 62 | ---- 63 | 64 | Run unit test:: 65 | 66 | python setup.py test 67 | 68 | Quick start 69 | ----------- 70 | 71 | 1. Importing 72 | ```````````` 73 | 74 | .. code-block:: python 75 | 76 | from gaft import GAEngine 77 | from gaft.components import BinaryIndividual, Population 78 | from gaft.operators import RouletteWheelSelection, UniformCrossover, FlipBitMutation 79 | 80 | # Analysis plugin base class. 81 | from gaft.plugin_interfaces.analysis import OnTheFlyAnalysis 82 | 83 | 2. Define population 84 | ```````````````````` 85 | 86 | .. code-block:: python 87 | 88 | indv_template = BinaryIndividual(ranges=[(0, 10)], eps=0.001) 89 | population = Population(indv_template=indv_template, size=50) 90 | population.init() # Initialize population with individuals. 91 | 92 | 3. Create genetic operators 93 | ``````````````````````````` 94 | 95 | .. code-block:: python 96 | 97 | # Use built-in operators here. 98 | selection = RouletteWheelSelection() 99 | crossover = UniformCrossover(pc=0.8, pe=0.5) 100 | mutation = FlipBitMutation(pm=0.1) 101 | 102 | 4. Create genetic algorithm engine to run optimization 103 | `````````````````````````````````````````````````````` 104 | 105 | .. code-block:: python 106 | 107 | engine = GAEngine(population=population, selection=selection, 108 | crossover=crossover, mutation=mutation, 109 | analysis=[FitnessStore]) 110 | 111 | 5. Define and register fitness function 112 | ``````````````````````````````````````` 113 | 114 | .. code-block:: python 115 | 116 | @engine.fitness_register 117 | def fitness(indv): 118 | x, = indv.solution 119 | return x + 10*sin(5*x) + 7*cos(4*x) 120 | 121 | or if you want to minimize it, you can add a minimization decorator on it 122 | 123 | .. code-block:: python 124 | 125 | @engine.fitness_register 126 | @engine.minimize 127 | def fitness(indv): 128 | x, = indv.solution 129 | return x + 10*sin(5*x) + 7*cos(4*x) 130 | 131 | 6. Define and register an on-the-fly analysis (optional) 132 | ```````````````````````````````````````````````````````` 133 | 134 | .. code-block:: python 135 | 136 | @engine.analysis_register 137 | class ConsoleOutput(OnTheFlyAnalysis): 138 | master_only = True 139 | interval = 1 140 | def register_step(self, g, population, engine): 141 | best_indv = population.best_indv(engine.fitness) 142 | msg = 'Generation: {}, best fitness: {:.3f}'.format(g, engine.fmax) 143 | engine.logger.info(msg) 144 | 145 | 7. Run 146 | `````` 147 | 148 | .. code-block:: python 149 | 150 | if '__main__' == __name__: 151 | engine.run(ng=100) 152 | 153 | 8. Evolution curve 154 | `````````````````` 155 | 156 | .. image:: https://github.com/PytLab/gaft/blob/master/examples/ex01/envolution_curve.png 157 | 158 | 9. Optimization animation 159 | ````````````````````````` 160 | 161 | .. image:: https://github.com/PytLab/gaft/blob/master/examples/ex01/animation.gif 162 | 163 | See `example 01 `_ for a one-dimension search for the global maximum of function `f(x) = x + 10sin(5x) + 7cos(4x)` 164 | 165 | Global maximum search for binary function 166 | ----------------------------------------- 167 | 168 | .. image:: https://github.com/PytLab/gaft/blob/master/examples/ex02/surface_animation.gif 169 | 170 | See `example 02 `_ for a two-dimension search for the global maximum of function `f(x, y) = y*sin(2*pi*x) + x*cos(2*pi*y)` 171 | 172 | Plugins 173 | ------- 174 | 175 | You can define your own genetic operators for GAFT and run your algorithm test. 176 | 177 | The plugin interfaces are defined in `/gaft/plugin_interfaces/ `_, you can extend the interface class and define your own analysis class or genetic operator class. The `built-in operators `_ and `built-in on-the-fly analysis `_ can be treated as an official example for plugins development. 178 | 179 | Blogs(Chinese Simplified) 180 | ------------------------- 181 | - `GAFT-一个使用Python实现的遗传算法框架 `_ 182 | 183 | - `使用MPI并行化遗传算法框架GAFT `_ 184 | 185 | - `遗传算法中几种不同选择算子的比较 `_ 186 | 187 | - `遗传算法中适值函数的标定与大变异算法 `_ 188 | 189 | - `遗传算法框架GAFT优化小记 `_ 190 | 191 | - `机器学习算法实践-Platt SMO和遗传算法优化SVM `_ 192 | 193 | - `遗传算法框架GAFT已支持自定义个体编码方式 `_ 194 | 195 | TODO 196 | ---- 197 | 1. ✅ Parallelization 198 | 2. ✅ Add more built-in genetic operators with different algorithms 199 | 3. 🏃 Add C++ backend(See `GASol `_) 200 | 201 | Obtain a copy 202 | ------------- 203 | 204 | The GAFT framework is distributed under the GPLv3 license and can be obtained from the GAFT git repository or PyPI 205 | 206 | - https://github.com/PytLab/gaft 207 | - https://pypi.python.org/pypi/gaft/ 208 | 209 | -------------------------------------------------------------------------------- /examples/1D_optimization_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Find the global minima of function $f(x) = x + 10sin(5x) + 7cos(4x)$" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Create individual (use binary encoding)" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "from gaft.components import BinaryIndividual\n", 24 | "indv = BinaryIndividual(ranges=[(0, 10)], eps=0.001)" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "metadata": {}, 30 | "source": [ 31 | "## Create a population with `50` individuals" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 2, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "from gaft.components import Population\n", 41 | "population = Population(indv_template=indv, size=50).init()" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "## Create genetic operators: selection, crossover, mutation" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "### 1. Tournament selection" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 3, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "from gaft.operators import TournamentSelection\n", 65 | "selection = TournamentSelection()" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "### 2. Uniform crossover\n", 73 | "\n", 74 | "- `pc` is the probabililty of crossover operation\n", 75 | "- `pe` is the exchange probabiltiy for each possible gene bit in chromsome" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 4, 81 | "metadata": {}, 82 | "outputs": [], 83 | "source": [ 84 | "from gaft.operators import UniformCrossover\n", 85 | "crossover = UniformCrossover(pc=0.8, pe=0.5)" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "metadata": {}, 91 | "source": [ 92 | "### 3. Flip bit mutation\n", 93 | "- `pm` is the probability of mutation" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 5, 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [ 102 | "from gaft.operators import FlipBitMutation\n", 103 | "mutation = FlipBitMutation(pm=0.1)" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "## Import an on-the-fly analysis plugin to output info to console" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": 6, 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "from gaft.analysis import ConsoleOutput" 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": {}, 125 | "source": [ 126 | "## Create an engine to run" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": 7, 132 | "metadata": {}, 133 | "outputs": [], 134 | "source": [ 135 | "from gaft import GAEngine\n", 136 | "\n", 137 | "engine = GAEngine(population=population, selection=selection,\n", 138 | " crossover=crossover, mutation=mutation,\n", 139 | " analysis=[ConsoleOutput])" 140 | ] 141 | }, 142 | { 143 | "cell_type": "markdown", 144 | "metadata": {}, 145 | "source": [ 146 | "## Define target function to optimize\n", 147 | "\n", 148 | "here we try to find the global minima of $f(x) = x + 10sin(5x) + 7cos(4x)$\n", 149 | "\n", 150 | "***GAFT*** find the maxima of the fitness function, here we use the `engine.minimize` decorator to tell ***GAFT*** to find the minima." 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 8, 156 | "metadata": {}, 157 | "outputs": [], 158 | "source": [ 159 | "from math import sin, cos\n", 160 | "\n", 161 | "@engine.fitness_register\n", 162 | "@engine.minimize\n", 163 | "def fitness(indv):\n", 164 | " x, = indv.solution\n", 165 | " return x + 10*sin(5*x) + 7*cos(4*x)" 166 | ] 167 | }, 168 | { 169 | "cell_type": "markdown", 170 | "metadata": {}, 171 | "source": [ 172 | "## Run the engine" 173 | ] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": 9, 178 | "metadata": {}, 179 | "outputs": [ 180 | { 181 | "name": "stdout", 182 | "output_type": "stream", 183 | "text": [ 184 | "gaft.ConsoleOutput INFO Generation number: 50 Population number: 50\n", 185 | "gaft.ConsoleOutput INFO Generation: 1, best fitness: 15.077, scaled fitness: 15.077\n", 186 | "gaft.ConsoleOutput INFO Generation: 2, best fitness: 15.164, scaled fitness: 15.164\n", 187 | "gaft.ConsoleOutput INFO Generation: 3, best fitness: 15.164, scaled fitness: 15.164\n", 188 | "gaft.ConsoleOutput INFO Generation: 4, best fitness: 15.164, scaled fitness: 15.164\n", 189 | "gaft.ConsoleOutput INFO Generation: 5, best fitness: 15.164, scaled fitness: 15.164\n", 190 | "gaft.ConsoleOutput INFO Generation: 6, best fitness: 15.164, scaled fitness: 15.164\n", 191 | "gaft.ConsoleOutput INFO Generation: 7, best fitness: 15.164, scaled fitness: 15.164\n", 192 | "gaft.ConsoleOutput INFO Generation: 8, best fitness: 15.164, scaled fitness: 15.164\n", 193 | "gaft.ConsoleOutput INFO Generation: 9, best fitness: 15.164, scaled fitness: 15.164\n", 194 | "gaft.ConsoleOutput INFO Generation: 10, best fitness: 15.164, scaled fitness: 15.164\n", 195 | "gaft.ConsoleOutput INFO Generation: 11, best fitness: 15.164, scaled fitness: 15.164\n", 196 | "gaft.ConsoleOutput INFO Generation: 12, best fitness: 15.164, scaled fitness: 15.164\n", 197 | "gaft.ConsoleOutput INFO Generation: 13, best fitness: 15.164, scaled fitness: 15.164\n", 198 | "gaft.ConsoleOutput INFO Generation: 14, best fitness: 15.164, scaled fitness: 15.164\n", 199 | "gaft.ConsoleOutput INFO Generation: 15, best fitness: 15.164, scaled fitness: 15.164\n", 200 | "gaft.ConsoleOutput INFO Generation: 16, best fitness: 15.164, scaled fitness: 15.164\n", 201 | "gaft.ConsoleOutput INFO Generation: 17, best fitness: 15.164, scaled fitness: 15.164\n", 202 | "gaft.ConsoleOutput INFO Generation: 18, best fitness: 15.164, scaled fitness: 15.164\n", 203 | "gaft.ConsoleOutput INFO Generation: 19, best fitness: 15.164, scaled fitness: 15.164\n", 204 | "gaft.ConsoleOutput INFO Generation: 20, best fitness: 15.164, scaled fitness: 15.164\n", 205 | "gaft.ConsoleOutput INFO Generation: 21, best fitness: 15.164, scaled fitness: 15.164\n", 206 | "gaft.ConsoleOutput INFO Generation: 22, best fitness: 15.164, scaled fitness: 15.164\n", 207 | "gaft.ConsoleOutput INFO Generation: 23, best fitness: 15.164, scaled fitness: 15.164\n", 208 | "gaft.ConsoleOutput INFO Generation: 24, best fitness: 15.164, scaled fitness: 15.164\n", 209 | "gaft.ConsoleOutput INFO Generation: 25, best fitness: 15.164, scaled fitness: 15.164\n", 210 | "gaft.ConsoleOutput INFO Generation: 26, best fitness: 15.164, scaled fitness: 15.164\n", 211 | "gaft.ConsoleOutput INFO Generation: 27, best fitness: 15.164, scaled fitness: 15.164\n", 212 | "gaft.ConsoleOutput INFO Generation: 28, best fitness: 15.164, scaled fitness: 15.164\n", 213 | "gaft.ConsoleOutput INFO Generation: 29, best fitness: 15.164, scaled fitness: 15.164\n", 214 | "gaft.ConsoleOutput INFO Generation: 30, best fitness: 15.164, scaled fitness: 15.164\n", 215 | "gaft.ConsoleOutput INFO Generation: 31, best fitness: 15.164, scaled fitness: 15.164\n", 216 | "gaft.ConsoleOutput INFO Generation: 32, best fitness: 15.164, scaled fitness: 15.164\n", 217 | "gaft.ConsoleOutput INFO Generation: 33, best fitness: 15.164, scaled fitness: 15.164\n", 218 | "gaft.ConsoleOutput INFO Generation: 34, best fitness: 15.164, scaled fitness: 15.164\n", 219 | "gaft.ConsoleOutput INFO Generation: 35, best fitness: 15.164, scaled fitness: 15.164\n", 220 | "gaft.ConsoleOutput INFO Generation: 36, best fitness: 15.164, scaled fitness: 15.164\n", 221 | "gaft.ConsoleOutput INFO Generation: 37, best fitness: 15.164, scaled fitness: 15.164\n", 222 | "gaft.ConsoleOutput INFO Generation: 38, best fitness: 15.164, scaled fitness: 15.164\n", 223 | "gaft.ConsoleOutput INFO Generation: 39, best fitness: 15.164, scaled fitness: 15.164\n", 224 | "gaft.ConsoleOutput INFO Generation: 40, best fitness: 15.164, scaled fitness: 15.164\n", 225 | "gaft.ConsoleOutput INFO Generation: 41, best fitness: 15.164, scaled fitness: 15.164\n", 226 | "gaft.ConsoleOutput INFO Generation: 42, best fitness: 15.164, scaled fitness: 15.164\n", 227 | "gaft.ConsoleOutput INFO Generation: 43, best fitness: 15.164, scaled fitness: 15.164\n", 228 | "gaft.ConsoleOutput INFO Generation: 44, best fitness: 15.164, scaled fitness: 15.164\n", 229 | "gaft.ConsoleOutput INFO Generation: 45, best fitness: 15.164, scaled fitness: 15.164\n", 230 | "gaft.ConsoleOutput INFO Generation: 46, best fitness: 15.164, scaled fitness: 15.164\n", 231 | "gaft.ConsoleOutput INFO Generation: 47, best fitness: 15.164, scaled fitness: 15.164\n", 232 | "gaft.ConsoleOutput INFO Generation: 48, best fitness: 15.164, scaled fitness: 15.164\n", 233 | "gaft.ConsoleOutput INFO Generation: 49, best fitness: 15.164, scaled fitness: 15.164\n", 234 | "gaft.ConsoleOutput INFO Generation: 50, best fitness: 15.164, scaled fitness: 15.164\n", 235 | "gaft.ConsoleOutput INFO Optimal solution: ([0.892333984375], 15.164338102247847)\n" 236 | ] 237 | } 238 | ], 239 | "source": [ 240 | "engine.run(ng=50)" 241 | ] 242 | }, 243 | { 244 | "cell_type": "markdown", 245 | "metadata": {}, 246 | "source": [ 247 | "## After engine running, we can do something more..." 248 | ] 249 | }, 250 | { 251 | "cell_type": "markdown", 252 | "metadata": {}, 253 | "source": [ 254 | "### Get the best individual" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": 10, 260 | "metadata": {}, 261 | "outputs": [], 262 | "source": [ 263 | "best_indv = engine.population.best_indv(engine.fitness)" 264 | ] 265 | }, 266 | { 267 | "cell_type": "markdown", 268 | "metadata": {}, 269 | "source": [ 270 | "### Get the solution" 271 | ] 272 | }, 273 | { 274 | "cell_type": "code", 275 | "execution_count": 11, 276 | "metadata": {}, 277 | "outputs": [ 278 | { 279 | "data": { 280 | "text/plain": [ 281 | "[0.892333984375]" 282 | ] 283 | }, 284 | "execution_count": 11, 285 | "metadata": {}, 286 | "output_type": "execute_result" 287 | } 288 | ], 289 | "source": [ 290 | "best_indv.solution" 291 | ] 292 | }, 293 | { 294 | "cell_type": "markdown", 295 | "metadata": {}, 296 | "source": [ 297 | "### And the fitness value" 298 | ] 299 | }, 300 | { 301 | "cell_type": "code", 302 | "execution_count": 12, 303 | "metadata": {}, 304 | "outputs": [ 305 | { 306 | "data": { 307 | "text/plain": [ 308 | "15.164338102247847" 309 | ] 310 | }, 311 | "execution_count": 12, 312 | "metadata": {}, 313 | "output_type": "execute_result" 314 | } 315 | ], 316 | "source": [ 317 | "engine.fitness(best_indv)" 318 | ] 319 | } 320 | ], 321 | "metadata": { 322 | "kernelspec": { 323 | "display_name": "Python 3", 324 | "language": "python", 325 | "name": "python3" 326 | }, 327 | "language_info": { 328 | "codemirror_mode": { 329 | "name": "ipython", 330 | "version": 3 331 | }, 332 | "file_extension": ".py", 333 | "mimetype": "text/x-python", 334 | "name": "python", 335 | "nbconvert_exporter": "python", 336 | "pygments_lexer": "ipython3", 337 | "version": "3.5.3" 338 | } 339 | }, 340 | "nbformat": 4, 341 | "nbformat_minor": 2 342 | } 343 | -------------------------------------------------------------------------------- /examples/ex01/animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PytLab/gaft/0fb7547752d443a3722916db02cf0a2cfb03026d/examples/ex01/animation.gif -------------------------------------------------------------------------------- /examples/ex01/best_fit.py: -------------------------------------------------------------------------------- 1 | best_fit = [ 2 | (0, [7.799249618590482], 24.260047195545532), 3 | (1, [7.906238554511049], 24.414459891004864), 4 | (2, [7.826883164448785], 24.694150555683564), 5 | (3, [7.883042363569771], 24.730421991621732), 6 | (4, [7.856183616164082], 24.855306004714947), 7 | (5, [7.856183616164082], 24.855306004714947), 8 | (6, [7.856183616164082], 24.855306004714947), 9 | (7, [7.856183616164082], 24.855306004714947), 10 | (8, [7.856183616164082], 24.855306004714947), 11 | (9, [7.856183616164082], 24.855306004714947), 12 | (10, [7.856183616164082], 24.855306004714947), 13 | (11, [7.856183616164082], 24.855306004714947), 14 | (12, [7.856183616164082], 24.855306004714947), 15 | (13, [7.856183616164082], 24.855306004714947), 16 | (14, [7.856183616164082], 24.855306004714947), 17 | (15, [7.856183616164082], 24.855306004714947), 18 | (16, [7.856183616164082], 24.855306004714947), 19 | (17, [7.856183616164082], 24.855306004714947), 20 | (18, [7.856183616164082], 24.855306004714947), 21 | (19, [7.856183616164082], 24.855306004714947), 22 | (20, [7.856183616164082], 24.855306004714947), 23 | (21, [7.856183616164082], 24.855306004714947), 24 | (22, [7.856183616164082], 24.855306004714947), 25 | (23, [7.856183616164082], 24.855306004714947), 26 | (24, [7.856183616164082], 24.855306004714947), 27 | (25, [7.856183616164082], 24.855306004714947), 28 | (26, [7.856183616164082], 24.855306004714947), 29 | (27, [7.856183616164082], 24.855306004714947), 30 | (28, [7.856183616164082], 24.855306004714947), 31 | (29, [7.856183616164082], 24.855306004714947), 32 | (30, [7.856183616164082], 24.855306004714947), 33 | (31, [7.856183616164082], 24.855306004714947), 34 | (32, [7.856183616164082], 24.855306004714947), 35 | (33, [7.856183616164082], 24.855306004714947), 36 | (34, [7.856183616164082], 24.855306004714947), 37 | (35, [7.856183616164082], 24.855306004714947), 38 | (36, [7.856183616164082], 24.855306004714947), 39 | (37, [7.856183616164082], 24.855306004714947), 40 | (38, [7.856183616164082], 24.855306004714947), 41 | (39, [7.856183616164082], 24.855306004714947), 42 | (40, [7.856183616164082], 24.855306004714947), 43 | (41, [7.856183616164082], 24.855306004714947), 44 | (42, [7.856183616164082], 24.855306004714947), 45 | (43, [7.856183616164082], 24.855306004714947), 46 | (44, [7.856183616164082], 24.855306004714947), 47 | (45, [7.856183616164082], 24.855306004714947), 48 | (46, [7.856183616164082], 24.855306004714947), 49 | (47, [7.856183616164082], 24.855306004714947), 50 | (48, [7.856183616164082], 24.855306004714947), 51 | (49, [7.856183616164082], 24.855306004714947), 52 | (50, [7.856183616164082], 24.855306004714947), 53 | (51, [7.856183616164082], 24.855306004714947), 54 | (52, [7.856183616164082], 24.855306004714947), 55 | (53, [7.856183616164082], 24.855306004714947), 56 | (54, [7.856183616164082], 24.855306004714947), 57 | (55, [7.856183616164082], 24.855306004714947), 58 | (56, [7.856183616164082], 24.855306004714947), 59 | (57, [7.856183616164082], 24.855306004714947), 60 | (58, [7.856183616164082], 24.855306004714947), 61 | (59, [7.856183616164082], 24.855306004714947), 62 | (60, [7.856183616164082], 24.855306004714947), 63 | (61, [7.856183616164082], 24.855306004714947), 64 | (62, [7.856183616164082], 24.855306004714947), 65 | (63, [7.856183616164082], 24.855306004714947), 66 | (64, [7.856183616164082], 24.855306004714947), 67 | (65, [7.856183616164082], 24.855306004714947), 68 | (66, [7.856183616164082], 24.855306004714947), 69 | (67, [7.856183616164082], 24.855306004714947), 70 | (68, [7.856183616164082], 24.855306004714947), 71 | (69, [7.856183616164082], 24.855306004714947), 72 | (70, [7.856183616164082], 24.855306004714947), 73 | (71, [7.856183616164082], 24.855306004714947), 74 | (72, [7.856183616164082], 24.855306004714947), 75 | (73, [7.856183616164082], 24.855306004714947), 76 | (74, [7.856183616164082], 24.855306004714947), 77 | (75, [7.856183616164082], 24.855306004714947), 78 | (76, [7.856183616164082], 24.855306004714947), 79 | (77, [7.856183616164082], 24.855306004714947), 80 | (78, [7.856183616164082], 24.855306004714947), 81 | (79, [7.856183616164082], 24.855306004714947), 82 | (80, [7.856183616164082], 24.855306004714947), 83 | (81, [7.856183616164082], 24.855306004714947), 84 | (82, [7.856183616164082], 24.855306004714947), 85 | (83, [7.856183616164082], 24.855306004714947), 86 | (84, [7.856183616164082], 24.855306004714947), 87 | (85, [7.856183616164082], 24.855306004714947), 88 | (86, [7.856183616164082], 24.855306004714947), 89 | (87, [7.856183616164082], 24.855306004714947), 90 | (88, [7.856183616164082], 24.855306004714947), 91 | (89, [7.856183616164082], 24.855306004714947), 92 | (90, [7.856183616164082], 24.855306004714947), 93 | (91, [7.856183616164082], 24.855306004714947), 94 | (92, [7.856183616164082], 24.855306004714947), 95 | (93, [7.856183616164082], 24.855306004714947), 96 | (94, [7.856183616164082], 24.855306004714947), 97 | (95, [7.856183616164082], 24.855306004714947), 98 | (96, [7.856183616164082], 24.855306004714947), 99 | (97, [7.856183616164082], 24.855306004714947), 100 | (98, [7.856183616164082], 24.855306004714947), 101 | (99, [7.856183616164082], 24.855306004714947), 102 | ] 103 | 104 | -------------------------------------------------------------------------------- /examples/ex01/envolution_curve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PytLab/gaft/0fb7547752d443a3722916db02cf0a2cfb03026d/examples/ex01/envolution_curve.png -------------------------------------------------------------------------------- /examples/ex01/ex01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Find the global maximum for function: f(x) = x + 10sin(5x) + 7cos(4x) 6 | ''' 7 | 8 | from math import sin, cos 9 | 10 | from gaft import GAEngine 11 | from gaft.components import BinaryIndividual 12 | from gaft.components import Population 13 | from gaft.operators import TournamentSelection 14 | from gaft.operators import UniformCrossover 15 | from gaft.operators import FlipBitMutation 16 | 17 | # Analysis plugin base class. 18 | from gaft.plugin_interfaces.analysis import OnTheFlyAnalysis 19 | 20 | # Built-in best fitness analysis. 21 | from gaft.analysis.fitness_store import FitnessStore 22 | 23 | # Define population. 24 | indv_template = BinaryIndividual(ranges=[(0, 10)], eps=0.001) 25 | population = Population(indv_template=indv_template, size=30).init() 26 | 27 | # Create genetic operators. 28 | selection = TournamentSelection() 29 | crossover = UniformCrossover(pc=0.8, pe=0.5) 30 | mutation = FlipBitMutation(pm=0.1) 31 | 32 | # Create genetic algorithm engine. 33 | engine = GAEngine(population=population, selection=selection, 34 | crossover=crossover, mutation=mutation, 35 | analysis=[FitnessStore]) 36 | 37 | # Define fitness function. 38 | @engine.fitness_register 39 | def fitness(indv): 40 | x, = indv.solution 41 | return x + 10*sin(5*x) + 7*cos(4*x) 42 | 43 | # Define on-the-fly analysis. 44 | @engine.analysis_register 45 | class ConsoleOutputAnalysis(OnTheFlyAnalysis): 46 | interval = 1 47 | master_only = True 48 | 49 | def register_step(self, g, population, engine): 50 | best_indv = population.best_indv(engine.fitness) 51 | msg = 'Generation: {}, best fitness: {:.3f}'.format(g, engine.ori_fmax) 52 | self.logger.info(msg) 53 | 54 | def finalize(self, population, engine): 55 | best_indv = population.best_indv(engine.fitness) 56 | x = best_indv.solution 57 | y = engine.ori_fmax 58 | msg = 'Optimal solution: ({}, {})'.format(x, y) 59 | self.logger.info(msg) 60 | 61 | if '__main__' == __name__: 62 | # Run the GA engine. 63 | engine.run(ng=100) 64 | 65 | -------------------------------------------------------------------------------- /examples/ex01/plot_animation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from math import sin, cos 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | from best_fit import best_fit 8 | 9 | 10 | for i, (x,), y in best_fit: 11 | fig = plt.figure(figsize=(12, 8)) 12 | 13 | ax = fig.add_subplot(111) 14 | f = lambda x: x + 10*sin(5*x) + 7*cos(4*x) 15 | xs = np.linspace(0, 10, 1000) 16 | ys = [f(i) for i in xs] 17 | ax.plot(xs, ys) 18 | ax.scatter([x], [y], facecolor='r', s=100) 19 | ax.set_xlabel('x') 20 | ax.set_ylabel('y') 21 | 22 | fig.savefig('{}.png'.format(i)) 23 | print('save {}.png'.format(i)) 24 | plt.close(fig) 25 | 26 | -------------------------------------------------------------------------------- /examples/ex01/plot_envolution_curve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from math import sin, cos 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | from best_fit import best_fit 7 | 8 | steps, variants, fits = list(zip(*best_fit)) 9 | best_step, best_v, best_f = steps[-1], variants[-1][0], fits[-1] 10 | 11 | fig = plt.figure() 12 | 13 | ax = fig.add_subplot(211) 14 | f = lambda x: x + 10*sin(5*x) + 7*cos(4*x) 15 | x = np.linspace(0, 10, 1000) 16 | y = [f(i) for i in x] 17 | ax.plot(x, y) 18 | ax.scatter([best_v], [best_f], facecolor='r') 19 | ax.set_xlabel('x') 20 | ax.set_ylabel('y') 21 | 22 | ax = fig.add_subplot(212) 23 | ax.plot(steps, fits) 24 | ax.set_xlabel('Generation') 25 | ax.set_ylabel('Fitness') 26 | 27 | # Plot the maximum. 28 | ax.scatter([best_step], [best_f], facecolor='r') 29 | ax.annotate(s='x: {:.2f}\ny:{:.2f}'.format(best_v, best_f), 30 | xy=(best_step, best_f), 31 | xytext=(best_step-0.3, best_f-0.3)) 32 | 33 | 34 | plt.show() 35 | 36 | -------------------------------------------------------------------------------- /examples/ex02/best_fit.py: -------------------------------------------------------------------------------- 1 | best_fit = [ 2 | (0, [1.8429604063899117, -1.900314824692881], 3.07839853201709), 3 | (1, [1.8417195896433807, -1.9296531509526136], 3.282718548280143), 4 | (2, [1.8417195896433807, -1.9921836834391793], 3.5099239225606356), 5 | (3, [1.8260869565217392, -1.9921836834391793], 3.592715650486718), 6 | (4, [1.8260869565217392, -1.9921836834391793], 3.592715650486718), 7 | (5, [1.7713727405959938, -1.9687347337567171], 3.688312954032613), 8 | (6, [1.7713727405959938, -2.0], 3.753366319028496), 9 | (7, [1.7557401074743524, -2.0], 3.7544394806469743), 10 | (8, [1.7557401074743524, -2.0], 3.7544394806469743), 11 | (9, [1.7635564240351735, -2.0], 3.7563055988327214), 12 | (10, [1.7635564240351735, -2.0], 3.7563055988327214), 13 | (11, [1.7635564240351735, -2.0], 3.7563055988327214), 14 | (12, [1.7635564240351735, -2.0], 3.7563055988327214), 15 | (13, [1.7635564240351735, -2.0], 3.7563055988327214), 16 | (14, [1.7635564240351735, -2.0], 3.7563055988327214), 17 | (15, [1.7635564240351735, -2.0], 3.7563055988327214), 18 | (16, [1.7635564240351735, -2.0], 3.7563055988327214), 19 | (17, [1.7635564240351735, -2.0], 3.7563055988327214), 20 | (18, [1.7635564240351735, -2.0], 3.7563055988327214), 21 | (19, [1.7635564240351735, -2.0], 3.7563055988327214), 22 | (20, [1.7635564240351735, -2.0], 3.7563055988327214), 23 | (21, [1.7635564240351735, -2.0], 3.7563055988327214), 24 | (22, [1.7635564240351735, -2.0], 3.7563055988327214), 25 | (23, [1.7635564240351735, -2.0], 3.7563055988327214), 26 | (24, [1.7635564240351735, -2.0], 3.7563055988327214), 27 | (25, [1.7635564240351735, -2.0], 3.7563055988327214), 28 | (26, [1.7635564240351735, -2.0], 3.7563055988327214), 29 | (27, [1.7635564240351735, -2.0], 3.7563055988327214), 30 | (28, [1.7635564240351735, -2.0], 3.7563055988327214), 31 | (29, [1.7635564240351735, -2.0], 3.7563055988327214), 32 | (30, [1.7635564240351735, -2.0], 3.7563055988327214), 33 | (31, [1.7635564240351735, -2.0], 3.7563055988327214), 34 | (32, [1.7635564240351735, -2.0], 3.7563055988327214), 35 | (33, [1.7635564240351735, -2.0], 3.7563055988327214), 36 | (34, [1.7635564240351735, -2.0], 3.7563055988327214), 37 | (35, [1.7635564240351735, -2.0], 3.7563055988327214), 38 | (36, [1.7635564240351735, -2.0], 3.7563055988327214), 39 | (37, [1.7635564240351735, -2.0], 3.7563055988327214), 40 | (38, [1.7635564240351735, -2.0], 3.7563055988327214), 41 | (39, [1.7635564240351735, -2.0], 3.7563055988327214), 42 | (40, [1.7635564240351735, -2.0], 3.7563055988327214), 43 | (41, [1.7635564240351735, -2.0], 3.7563055988327214), 44 | (42, [1.7635564240351735, -2.0], 3.7563055988327214), 45 | (43, [1.7635564240351735, -2.0], 3.7563055988327214), 46 | (44, [1.7635564240351735, -2.0], 3.7563055988327214), 47 | (45, [1.7635564240351735, -2.0], 3.7563055988327214), 48 | (46, [1.7635564240351735, -2.0], 3.7563055988327214), 49 | (47, [1.7635564240351735, -2.0], 3.7563055988327214), 50 | (48, [1.7635564240351735, -2.0], 3.7563055988327214), 51 | (49, [1.7635564240351735, -2.0], 3.7563055988327214), 52 | (50, [1.7635564240351735, -2.0], 3.7563055988327214), 53 | (51, [1.7635564240351735, -2.0], 3.7563055988327214), 54 | (52, [1.7635564240351735, -2.0], 3.7563055988327214), 55 | (53, [1.7635564240351735, -2.0], 3.7563055988327214), 56 | (54, [1.7635564240351735, -2.0], 3.7563055988327214), 57 | (55, [1.7635564240351735, -2.0], 3.7563055988327214), 58 | (56, [1.7635564240351735, -2.0], 3.7563055988327214), 59 | (57, [1.7635564240351735, -2.0], 3.7563055988327214), 60 | (58, [1.7635564240351735, -2.0], 3.7563055988327214), 61 | (59, [1.7635564240351735, -2.0], 3.7563055988327214), 62 | (60, [1.7635564240351735, -2.0], 3.7563055988327214), 63 | (61, [1.7635564240351735, -2.0], 3.7563055988327214), 64 | (62, [1.7635564240351735, -2.0], 3.7563055988327214), 65 | (63, [1.7635564240351735, -2.0], 3.7563055988327214), 66 | (64, [1.7635564240351735, -2.0], 3.7563055988327214), 67 | (65, [1.7635564240351735, -2.0], 3.7563055988327214), 68 | (66, [1.7635564240351735, -2.0], 3.7563055988327214), 69 | (67, [1.7635564240351735, -2.0], 3.7563055988327214), 70 | (68, [1.7635564240351735, -2.0], 3.7563055988327214), 71 | (69, [1.7635564240351735, -2.0], 3.7563055988327214), 72 | (70, [1.7635564240351735, -2.0], 3.7563055988327214), 73 | (71, [1.7635564240351735, -2.0], 3.7563055988327214), 74 | (72, [1.7635564240351735, -2.0], 3.7563055988327214), 75 | (73, [1.7635564240351735, -2.0], 3.7563055988327214), 76 | (74, [1.7635564240351735, -2.0], 3.7563055988327214), 77 | (75, [1.7635564240351735, -2.0], 3.7563055988327214), 78 | (76, [1.7635564240351735, -2.0], 3.7563055988327214), 79 | (77, [1.7635564240351735, -2.0], 3.7563055988327214), 80 | (78, [1.7635564240351735, -2.0], 3.7563055988327214), 81 | (79, [1.7635564240351735, -2.0], 3.7563055988327214), 82 | (80, [1.7635564240351735, -2.0], 3.7563055988327214), 83 | (81, [1.7635564240351735, -2.0], 3.7563055988327214), 84 | (82, [1.7635564240351735, -2.0], 3.7563055988327214), 85 | (83, [1.7635564240351735, -2.0], 3.7563055988327214), 86 | (84, [1.7635564240351735, -2.0], 3.7563055988327214), 87 | (85, [1.7635564240351735, -2.0], 3.7563055988327214), 88 | (86, [1.7635564240351735, -2.0], 3.7563055988327214), 89 | (87, [1.7635564240351735, -2.0], 3.7563055988327214), 90 | (88, [1.7635564240351735, -2.0], 3.7563055988327214), 91 | (89, [1.7635564240351735, -2.0], 3.7563055988327214), 92 | (90, [1.7635564240351735, -2.0], 3.7563055988327214), 93 | (91, [1.7635564240351735, -2.0], 3.7563055988327214), 94 | (92, [1.7635564240351735, -2.0], 3.7563055988327214), 95 | (93, [1.7635564240351735, -2.0], 3.7563055988327214), 96 | (94, [1.7635564240351735, -2.0], 3.7563055988327214), 97 | (95, [1.7635564240351735, -2.0], 3.7563055988327214), 98 | (96, [1.7635564240351735, -2.0], 3.7563055988327214), 99 | (97, [1.7635564240351735, -2.0], 3.7563055988327214), 100 | (98, [1.7635564240351735, -2.0], 3.7563055988327214), 101 | (99, [1.7635564240351735, -2.0], 3.7563055988327214), 102 | ] 103 | 104 | -------------------------------------------------------------------------------- /examples/ex02/envolution_curve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PytLab/gaft/0fb7547752d443a3722916db02cf0a2cfb03026d/examples/ex02/envolution_curve.png -------------------------------------------------------------------------------- /examples/ex02/ex02.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Find the global maximum for binary function: f(x) = y*sim(2*pi*x) + x*cos(2*pi*y) 6 | ''' 7 | 8 | from math import sin, cos, pi 9 | 10 | from gaft import GAEngine 11 | from gaft.components import BinaryIndividual 12 | from gaft.components import Population 13 | from gaft.operators import TournamentSelection 14 | from gaft.operators import UniformCrossover 15 | from gaft.operators import FlipBitBigMutation 16 | 17 | # Built-in best fitness analysis. 18 | from gaft.analysis.fitness_store import FitnessStore 19 | from gaft.analysis.console_output import ConsoleOutput 20 | 21 | # Define population. 22 | indv_template = BinaryIndividual(ranges=[(-2, 2), (-2, 2)], eps=0.001) 23 | population = Population(indv_template=indv_template, size=50).init() 24 | 25 | # Create genetic operators. 26 | #selection = RouletteWheelSelection() 27 | selection = TournamentSelection() 28 | crossover = UniformCrossover(pc=0.8, pe=0.5) 29 | mutation = FlipBitBigMutation(pm=0.1, pbm=0.55, alpha=0.6) 30 | 31 | # Create genetic algorithm engine. 32 | # Here we pass all built-in analysis to engine constructor. 33 | engine = GAEngine(population=population, selection=selection, 34 | crossover=crossover, mutation=mutation, 35 | analysis=[ConsoleOutput, FitnessStore]) 36 | 37 | # Define fitness function. 38 | @engine.fitness_register 39 | def fitness(indv): 40 | x, y = indv.solution 41 | return y*sin(2*pi*x) + x*cos(2*pi*y) 42 | 43 | if '__main__' == __name__: 44 | engine.run(ng=100) 45 | 46 | -------------------------------------------------------------------------------- /examples/ex02/plot_envolution_curve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import matplotlib.pyplot as plt 4 | 5 | from best_fit import best_fit 6 | 7 | steps, variants, fits = list(zip(*best_fit)) 8 | best_step, best_v, best_f = steps[-1], variants[-1], fits[-1] 9 | 10 | fig = plt.figure() 11 | 12 | ax = fig.add_subplot(111) 13 | ax.plot(steps, fits) 14 | ax.set_xlabel('Generation') 15 | ax.set_ylabel('Fitness') 16 | 17 | # Plot the maximum. 18 | ax.scatter([best_step], [best_f], facecolor='r') 19 | ax.annotate(s='x: [{:.2f}, {:.2f}]\ny:{:.2f}'.format(*best_v, best_f), 20 | xy=(best_step, best_f), 21 | xytext=(best_step, best_f-0.1)) 22 | 23 | 24 | plt.show() 25 | 26 | -------------------------------------------------------------------------------- /examples/ex02/plot_surf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | 6 | import numpy as np 7 | import mpl_toolkits.mplot3d 8 | import matplotlib.pyplot as plt 9 | 10 | from best_fit import best_fit 11 | 12 | for i, (x, y), z in best_fit: 13 | fig = plt.figure(figsize=(12, 8)) 14 | ax = fig.add_subplot(111, projection='3d') 15 | 16 | ax.scatter([x], [y], [z], zorder=99, c='r', s=100) 17 | 18 | x, y = np.mgrid[-2:2:100j, -2:2:100j] 19 | z = y*np.sin(2*np.pi*x) + x*np.cos(2*np.pi*y) 20 | ax.plot_surface(x, y, z, rstride=2, cstride=1, cmap=plt.cm.bone_r) 21 | 22 | ax.set_xlabel('X') 23 | ax.set_ylabel('Y') 24 | ax.set_zlabel('Z') 25 | 26 | if not os.path.exists('./surfaces'): 27 | os.mkdir('./surfaces') 28 | fig.savefig('./surfaces/{}.png'.format(i)) 29 | print('save ./surfaces/{}.png'.format(i)) 30 | plt.close(fig) 31 | 32 | -------------------------------------------------------------------------------- /examples/ex02/surface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PytLab/gaft/0fb7547752d443a3722916db02cf0a2cfb03026d/examples/ex02/surface.png -------------------------------------------------------------------------------- /examples/ex02/surface_animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PytLab/gaft/0fb7547752d443a3722916db02cf0a2cfb03026d/examples/ex02/surface_animation.gif -------------------------------------------------------------------------------- /gaft/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | from .engine import GAEngine 5 | 6 | __version__ = '0.5.7' 7 | __author__ = 'ShaoZhengjiang ' 8 | 9 | # Set root logger. 10 | logger = logging.getLogger('gaft') 11 | logger.setLevel(logging.INFO) 12 | console_hdlr = logging.StreamHandler(sys.stdout) 13 | console_hdlr.setLevel(logging.INFO) 14 | formatter = logging.Formatter('%(name)s %(levelname)-8s %(message)s') 15 | console_hdlr.setFormatter(formatter) 16 | logger.addHandler(console_hdlr) 17 | 18 | -------------------------------------------------------------------------------- /gaft/analysis/__init__.py: -------------------------------------------------------------------------------- 1 | from .console_output import ConsoleOutput 2 | from .fitness_store import FitnessStore 3 | 4 | -------------------------------------------------------------------------------- /gaft/analysis/console_output.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from ..plugin_interfaces.analysis import OnTheFlyAnalysis 5 | 6 | 7 | class ConsoleOutput(OnTheFlyAnalysis): 8 | ''' Built-in on-the-fly analysis plugin class for outputing log on console. 9 | 10 | Attribute: 11 | 12 | interval(:obj:`int`): The analysis interval in evolution iteration, default 13 | value is 1 meaning analyze every step. 14 | 15 | master_only(:obj:`bool`): Flag for if the analysis plugin is only effective 16 | in master process. Default is True. 17 | ''' 18 | 19 | # Analysis interval. 20 | interval = 1 21 | 22 | # Only analyze in master process? 23 | master_only = True 24 | 25 | def setup(self, ng, engine): 26 | generation_info = 'Generation number: {}'.format(ng) 27 | population_info = 'Population number: {}'.format(engine.population.size) 28 | self.logger.info('{} {}'.format(generation_info, population_info)) 29 | 30 | def register_step(self, g, population, engine): 31 | best_indv = population.best_indv(engine.fitness) 32 | ng_info = 'Generation: {}, '.format(g+1) 33 | fit_info = 'best fitness: {:.3f}, '.format(engine.ori_fmax) 34 | scaled_info = 'scaled fitness: {:.3f}'.format(engine.fmax) 35 | msg = ng_info + fit_info + scaled_info 36 | self.logger.info(msg) 37 | 38 | def finalize(self, population, engine): 39 | best_indv = population.best_indv(engine.fitness) 40 | x = best_indv.solution 41 | y = engine.ori_fmax 42 | msg = 'Optimal solution: ({}, {})'.format(x, y) 43 | self.logger.info(msg) 44 | 45 | -------------------------------------------------------------------------------- /gaft/analysis/fitness_store.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from ..plugin_interfaces.analysis import OnTheFlyAnalysis 5 | 6 | class FitnessStore(OnTheFlyAnalysis): 7 | ''' Built-in on-the-fly analysis plugin class for storing fitness related data during iteration. 8 | 9 | Attribute: 10 | interval(:obj:`int`): The analysis interval in evolution iteration, default 11 | value is 1 meaning analyze every step. 12 | master_only(:obj:`bool`): Flag for if the analysis plugin is only effective 13 | in master process. Default is True. 14 | ''' 15 | 16 | # Analysis interval. 17 | interval = 1 18 | 19 | # Only analyze in master process? 20 | master_only = True 21 | 22 | def setup(self, ng, engine): 23 | # Generation numbers. 24 | self.ngs = [] 25 | 26 | # Best fitness in each generation. 27 | self.fitness_values = [] 28 | 29 | # Best solution. 30 | self.solution = [] 31 | 32 | def register_step(self, g, population, engine): 33 | # Collect data. 34 | best_indv = population.best_indv(engine.fitness) 35 | best_fit = engine.ori_fmax 36 | 37 | self.ngs.append(g) 38 | self.solution.append(best_indv.solution) 39 | self.fitness_values.append(best_fit) 40 | 41 | def finalize(self, population, engine): 42 | with open('best_fit.py', 'w', encoding='utf-8') as f: 43 | f.write('best_fit = [\n') 44 | for ng, x, y in zip(self.ngs, self.solution, self.fitness_values): 45 | f.write(' ({}, {}, {}),\n'.format(ng, x, y)) 46 | f.write(']\n\n') 47 | 48 | self.logger.info('Best fitness values are written to best_fit.py') 49 | 50 | -------------------------------------------------------------------------------- /gaft/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .individual import IndividualBase 2 | from .binary_individual import BinaryIndividual 3 | from .decimal_individual import DecimalIndividual 4 | from .population import Population 5 | 6 | -------------------------------------------------------------------------------- /gaft/components/binary_individual.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Module for Individual with binary encoding. 5 | ''' 6 | from math import log2 7 | from itertools import accumulate 8 | import logging 9 | 10 | from .individual import IndividualBase 11 | from ..mpiutil import MPIUtil 12 | 13 | mpi = MPIUtil() 14 | 15 | 16 | class BinaryIndividual(IndividualBase): 17 | ''' 18 | Class for individual in population. Random solution will be initialized 19 | by default. 20 | 21 | :param ranges: value ranges for all entries in solution. 22 | :type ranges: tuple list 23 | 24 | :param eps: decrete precisions for binary encoding, default is 0.001. 25 | :type eps: float or float list (with the same length with ranges) 26 | 27 | .. Note: 28 | 29 | The decrete precisions for different components in varants may be 30 | adjusted automatically (possible precision loss) if eps and ranges 31 | are not appropriate. 32 | ''' 33 | def __init__(self, ranges, eps=0.001): 34 | super(self.__class__, self).__init__(ranges, eps) 35 | 36 | # Lengths for all binary sequence in chromsome and adjusted decrete precisions. 37 | self.lengths = [] 38 | 39 | for i, ((a, b), eps) in enumerate(zip(self.ranges, self.eps)): 40 | length = int(log2((b - a)/eps)) 41 | precision = (b - a)/(2**length) 42 | self.lengths.append(length) 43 | self.precisions[i] = precision 44 | 45 | # The start and end indices for each gene segment for entries in solution. 46 | self.gene_indices = self._get_gene_indices() 47 | 48 | # Initialize individual randomly. 49 | self.init() 50 | 51 | def encode(self): 52 | ''' Encode solution to gene sequence in individual using different encoding. 53 | ''' 54 | chromsome = [] 55 | for var, (a, _), length, eps in zip(self.solution, self.ranges, 56 | self.lengths, self.precisions): 57 | chromsome.extend(self.binarize(var-a, eps, length)) 58 | 59 | return chromsome 60 | 61 | def decode(self): 62 | ''' Decode gene sequence to solution of target function. 63 | ''' 64 | solution = [self.decimalize(self.chromsome[start: end], eps, lower_bound) 65 | for (start, end), (lower_bound, _), eps in 66 | zip(self.gene_indices, self.ranges, self.precisions)] 67 | return solution 68 | 69 | def _get_gene_indices(self): 70 | ''' 71 | Helper function to get gene slice indices. 72 | ''' 73 | end_indices = list(accumulate(self.lengths)) 74 | start_indices = [0] + end_indices[: -1] 75 | return list(zip(start_indices, end_indices)) 76 | 77 | @staticmethod 78 | def binarize(decimal, eps, length): 79 | ''' Helper function to convert a float to binary sequence. 80 | 81 | :param decimal: the decimal number to be converted 82 | :type decimal: float 83 | 84 | :param eps: the decrete precision of binary sequence 85 | :type eps: float 86 | 87 | :param length: the length of binary sequence. 88 | :type length: int 89 | ''' 90 | n = int(decimal/eps) 91 | bin_str = '{:0>{}b}'.format(n, length) 92 | return [int(i) for i in bin_str] 93 | 94 | @staticmethod 95 | def decimalize(binary, eps, lower_bound): 96 | ''' Helper function to convert a binary sequence back to decimal number. 97 | 98 | :param binary: The binary list to be converted 99 | :type binary: list of int 100 | 101 | :param eps: the decrete precision of binary sequence 102 | :type eps: float 103 | 104 | :param lower_bound: the lower bound for decimal number 105 | :type lower_bound: float 106 | ''' 107 | bin_str = ''.join([str(bit) for bit in binary]) 108 | return lower_bound + int(bin_str, 2)*eps 109 | 110 | -------------------------------------------------------------------------------- /gaft/components/decimal_individual.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ''' Definition of individual class with decimal encoding. 4 | ''' 5 | 6 | from .individual import IndividualBase 7 | 8 | 9 | class DecimalIndividual(IndividualBase): 10 | ''' Individual with decimal encoding. 11 | 12 | :param ranges: value ranges for all entries in solution. 13 | :type ranges: tuple list 14 | 15 | :param eps: decrete precisions for binary encoding, default is 0.001. 16 | :type eps: float or float list (with the same length with ranges) 17 | ''' 18 | def __init__(self, ranges, eps=0.001): 19 | super(self.__class__, self).__init__(ranges, eps) 20 | # Initialize it randomly. 21 | self.init() 22 | 23 | def encode(self): 24 | ''' Encode solution to gene sequence 25 | ''' 26 | return self.solution 27 | 28 | def decode(self): 29 | ''' Decode gene sequence to decimal solution 30 | ''' 31 | return self.solution 32 | 33 | -------------------------------------------------------------------------------- /gaft/components/individual.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from random import uniform 5 | from copy import deepcopy 6 | 7 | 8 | class SolutionRanges(object): 9 | ''' Descriptor for solution ranges. 10 | ''' 11 | def __init__(self): 12 | self.__ranges = [] 13 | 14 | def __get__(self, obj, owner): 15 | return self.__ranges 16 | 17 | def __set__(self, obj, ranges): 18 | # Check. 19 | if type(ranges) not in [tuple, list]: 20 | raise TypeError('solution ranges must be a list of range tuples') 21 | for rng in ranges: 22 | if type(rng) not in [tuple, list]: 23 | raise TypeError('range({}) is not a tuple containing two numbers'.format(rng)) 24 | if len(rng) != 2: 25 | raise ValueError('length of range({}) not equal to 2') 26 | a, b = rng 27 | if a >= b: 28 | raise ValueError('Wrong range value {}'.format(rng)) 29 | # Assignment. 30 | self.__ranges = ranges 31 | 32 | 33 | class DecretePrecision(object): 34 | ''' Descriptor for individual decrete precisions. 35 | ''' 36 | def __init__(self): 37 | self.__precisions = [] 38 | 39 | def __get__(self, obj, owner): 40 | return self.__precisions 41 | 42 | def __set__(self, obj, precisions): 43 | if type(precisions) in [int, float]: 44 | precisions = [precisions]*len(obj.ranges) 45 | # Check. 46 | if type(precisions) not in [tuple, list]: 47 | raise TypeError('precisions must be a list of numbers') 48 | if len(precisions) != len(obj.ranges): 49 | raise ValueError('Lengths of eps and ranges should be the same') 50 | for (a, b), eps in zip(obj.ranges, precisions): 51 | if eps > (b - a): 52 | msg = 'Invalid precision {} in range ({}, {})'.format(eps, a, b) 53 | raise ValueError(msg) 54 | self.__precisions = precisions 55 | 56 | 57 | class IndividualBase(object): 58 | ''' Base class for individuals. 59 | 60 | :param ranges: value ranges for all entries in solution. 61 | :type ranges: tuple list 62 | 63 | :param eps: decrete precisions for binary encoding, default is 0.001. 64 | :type eps: float or float list (with the same length with ranges) 65 | ''' 66 | # Solution ranges. 67 | ranges = SolutionRanges() 68 | 69 | # Orginal decrete precisions (provided by users). 70 | eps = DecretePrecision() 71 | 72 | # Actual decrete precisions used in GA. 73 | precisions = DecretePrecision() 74 | 75 | def __init__(self, ranges, eps): 76 | self.ranges = ranges 77 | self.eps = eps 78 | self.precisions = eps 79 | 80 | self.solution, self.chromsome = [], [] 81 | 82 | def init(self, chromsome=None, solution=None): 83 | ''' Initialize the individual by providing chromsome or solution. 84 | 85 | :param chromsome: chromesome sequence for the individual 86 | :type chromsome: list of (float / int) 87 | 88 | :param solution: the variable vector of the target function. 89 | :type solution: list of float 90 | 91 | .. Note:: 92 | If both chromsome and solution are provided, only the chromsome would 93 | be used. If neither is provided, individual would be initialized randomly. 94 | ''' 95 | if not any([chromsome, solution]): 96 | self.solution = self._rand_solution() 97 | self.chromsome = self.encode() 98 | elif chromsome: 99 | self.chromsome = chromsome 100 | self.solution = self.decode() 101 | else: 102 | self.solution = solution 103 | self.chromsome = self.encode() 104 | 105 | return self 106 | 107 | def clone(self): 108 | ''' Clone a new individual from current one. 109 | ''' 110 | indv = self.__class__(deepcopy(self.ranges), eps=deepcopy(self.eps)) 111 | indv.init(chromsome=deepcopy(self.chromsome)) 112 | return indv 113 | 114 | 115 | def encode(self): 116 | ''' **NEED IMPLIMENTATION** 117 | 118 | Convert solution to chromsome sequence. 119 | 120 | :return: The chromsome sequence 121 | :rtype: list of float 122 | ''' 123 | raise NotImplementedError 124 | 125 | def decode(self): 126 | ''' **NEED IMPLIMENTATION** 127 | 128 | Convert chromsome sequence to solution. 129 | 130 | :return: The solution vector 131 | :rtype: list of float 132 | ''' 133 | raise NotImplementedError 134 | 135 | def _rand_solution(self): 136 | ''' Initialize individual solution randomly. 137 | ''' 138 | solution = [] 139 | for eps, (a, b) in zip(self.precisions, self.ranges): 140 | n_intervals = (b - a)//eps 141 | n = int(uniform(0, n_intervals + 1)) 142 | solution.append(a + n*eps) 143 | return solution 144 | 145 | 146 | -------------------------------------------------------------------------------- /gaft/components/population.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .individual import IndividualBase 5 | 6 | 7 | class Memoized(object): 8 | ''' Descriptor for population statistical varibles caching. 9 | ''' 10 | def __init__(self, func): 11 | self.func = func 12 | self.result = None 13 | self.fitness = None 14 | 15 | def __get__(self, instance, cls): 16 | self.instance = instance 17 | return self 18 | 19 | def __call__(self, fitness): 20 | if ((not self.instance.updated) # population not changed 21 | and (self.result is not None) # result already cached 22 | and (fitness == self.fitness)): # fitness not changed 23 | # Return cached result directly. 24 | return self.result 25 | else: 26 | # Update fitness function. 27 | self.fitness = fitness 28 | # Update and memoize result. 29 | self.result = self.func(self.instance, fitness) 30 | # Recover flag. 31 | self.instance._updated = False 32 | return self.result 33 | 34 | 35 | class Individuals(object): 36 | ''' Descriptor for all individuals in population. 37 | 38 | .. Note:: 39 | Use this descriptor to ensure the individual related flags can be updated 40 | when the population indivduals are changed. 41 | ''' 42 | def __init__(self, name): 43 | self.name = '_{}'.format(name) 44 | 45 | def __get__(self, instance, owner): 46 | return instance.__dict__[self.name] 47 | 48 | def __set__(self, instance, value): 49 | instance.__dict__[self.name] = value 50 | # Update flag. 51 | instance.update_flag() 52 | 53 | 54 | class Population(object): 55 | ''' Class for representing population in genetic algorithm. 56 | 57 | :param indv_template: A template individual to clone all the other 58 | individuals in current population. 59 | :type indv_template: :obj:`gaft.components.IndividualBase` 60 | 61 | :param size: The size of population, number of individuals in population. 62 | :type size: int 63 | ''' 64 | # All individuals. 65 | individuals = Individuals('individuals') 66 | 67 | def __init__(self, indv_template, size=100): 68 | # Population size. 69 | if size % 2 != 0: 70 | raise ValueError('Population size must be an even number') 71 | self.size = size 72 | 73 | # Template individual. 74 | self.indv_template = indv_template 75 | 76 | # Flag for monitoring changes of population. 77 | self._updated = False 78 | 79 | # Container for all individuals. 80 | class IndvList(list): 81 | ''' A proxy class inherited from built-in list to contain all 82 | individuals which can update the population._updated flag 83 | automatically when its content is changed. 84 | ''' 85 | # {{{ 86 | # NOTE: Use 'this' here to avoid name conflict. 87 | def __init__(this, *args): 88 | super(this.__class__, this).__init__(*args) 89 | 90 | def __setitem__(this, key, value): 91 | ''' 92 | Override __setitem__ in built-in list type. 93 | ''' 94 | old_value = this[key] 95 | if old_value == value: 96 | return 97 | super(this.__class__, self).__setitem__(key, value) 98 | # Update population flag. 99 | self.update_flag() 100 | 101 | def append(this, item): 102 | ''' 103 | Override append method of built-in list type. 104 | ''' 105 | super(this.__class__, this).append(item) 106 | # Update population flag. 107 | self.update_flag() 108 | 109 | def extend(this, iterable_item): 110 | if not iterable_item: 111 | return 112 | super(this.__class__, this).extend(iterable_item) 113 | # Update population flag. 114 | self.update_flag() 115 | # }}} 116 | 117 | self._individuals = IndvList() 118 | 119 | def init(self, indvs=None): 120 | ''' Initialize current population with individuals. 121 | 122 | :param indvs: Initial individuals in population, randomly initialized 123 | individuals are created if not provided. 124 | :type indvs: list of Individual object 125 | ''' 126 | IndvType = self.indv_template.__class__ 127 | 128 | if indvs is None: 129 | for _ in range(self.size): 130 | indv = IndvType(ranges=self.indv_template.ranges, 131 | eps=self.indv_template.eps) 132 | self.individuals.append(indv) 133 | else: 134 | # Check individuals. 135 | if len(indvs) != self.size: 136 | raise ValueError('Invalid individuals number') 137 | for indv in indvs: 138 | if not isinstance(indv, IndividualBase): 139 | raise ValueError('individual class must be subclass of IndividualBase') 140 | self.individuals = indvs 141 | 142 | self._updated = True 143 | 144 | return self 145 | 146 | def update_flag(self): 147 | ''' Interface for updating individual update flag to True. 148 | ''' 149 | self._updated = True 150 | 151 | @property 152 | def updated(self): 153 | ''' Query function for population updating flag. 154 | ''' 155 | return self._updated 156 | 157 | def new(self): 158 | ''' Create a new emtpy population. 159 | ''' 160 | return self.__class__(indv_template=self.indv_template, 161 | size=self.size) 162 | 163 | def __getitem__(self, key): 164 | ''' 165 | Get individual by index. 166 | ''' 167 | if key < 0 or key >= self.size: 168 | raise IndexError('Individual index({}) out of range'.format(key)) 169 | return self.individuals[key] 170 | 171 | def __len__(self): 172 | ''' 173 | Get length of population. 174 | ''' 175 | return len(self.individuals) 176 | 177 | def best_indv(self, fitness): 178 | ''' The individual with the best fitness. 179 | 180 | :param fitness: Fitness function to calculate fitness value 181 | :type fitness: function 182 | 183 | :return: the best individual in current population 184 | :rtype: :obj:`gaft.components.IndividualBase` 185 | ''' 186 | all_fits = self.all_fits(fitness) 187 | return max(self.individuals, 188 | key=lambda indv: all_fits[self.individuals.index(indv)]) 189 | 190 | def worst_indv(self, fitness): 191 | ''' The individual with the worst fitness. 192 | 193 | :param fitness: Fitness function to calculate fitness value 194 | :type fitness: function 195 | 196 | :return: the worst individual in current population 197 | :rtype: :obj:`gaft.components.IndividualBase` 198 | ''' 199 | all_fits = self.all_fits(fitness) 200 | return min(self.individuals, 201 | key=lambda indv: all_fits[self.individuals.index(indv)]) 202 | 203 | def max(self, fitness): 204 | ''' Get the maximum fitness value in population. 205 | 206 | :param fitness: Fitness function to calculate fitness value 207 | :type fitness: function 208 | 209 | :return: The maximum fitness value 210 | :rtype: float 211 | ''' 212 | return max(self.all_fits(fitness)) 213 | 214 | def min(self, fitness): 215 | ''' Get the minimum value of fitness in population. 216 | 217 | :param fitness: Fitness function to calculate fitness value 218 | :type fitness: function 219 | 220 | :return: The minimum fitness value 221 | :rtype: float 222 | ''' 223 | return min(self.all_fits(fitness)) 224 | 225 | def mean(self, fitness): 226 | ''' Get the average fitness value in population. 227 | 228 | :param fitness: Fitness function to calculate fitness value 229 | :type fitness: function 230 | 231 | :return: The average fitness value 232 | :rtype: float 233 | ''' 234 | all_fits = self.all_fits(fitness) 235 | return sum(all_fits)/len(all_fits) 236 | 237 | @Memoized 238 | def all_fits(self, fitness): 239 | ''' Get all fitness values in population. 240 | 241 | :param fitness: Fitness function to calculate fitness value 242 | :type fitness: function 243 | ''' 244 | return [fitness(indv) for indv in self.individuals] 245 | 246 | -------------------------------------------------------------------------------- /gaft/engine.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Genetic Algorithm engine definition 5 | ''' 6 | 7 | import logging 8 | import math 9 | from functools import wraps 10 | 11 | # Imports for profiling. 12 | import cProfile 13 | import pstats 14 | import os 15 | 16 | from .components import IndividualBase, Population 17 | from .plugin_interfaces.operators import Selection, Crossover, Mutation 18 | from .plugin_interfaces.analysis import OnTheFlyAnalysis 19 | from .mpiutil import MPIUtil 20 | 21 | mpi = MPIUtil() 22 | 23 | def do_profile(filename, sortby='tottime'): 24 | ''' Constructor for function profiling decorator. 25 | ''' 26 | def _do_profile(func): 27 | ''' Function profiling decorator. 28 | ''' 29 | @wraps(func) 30 | def profiled_func(*args, **kwargs): 31 | ''' 32 | Decorated function. 33 | ''' 34 | # Flag for doing profiling or not. 35 | DO_PROF = os.getenv('PROFILING') 36 | 37 | if DO_PROF: 38 | profile = cProfile.Profile() 39 | profile.enable() 40 | result = func(*args, **kwargs) 41 | profile.disable() 42 | ps = pstats.Stats(profile).sort_stats(sortby) 43 | ps.dump_stats(filename) 44 | else: 45 | result = func(*args, **kwargs) 46 | return result 47 | return profiled_func 48 | 49 | return _do_profile 50 | 51 | 52 | class StatVar(object): 53 | def __init__(self, name): 54 | ''' Descriptor for statistical variables which need to be memoized when 55 | engine running. 56 | ''' 57 | # Protected. 58 | self.name = '_{}'.format(name) 59 | 60 | def __get__(self, engine, cls): 61 | ''' 62 | Getter. 63 | ''' 64 | stat_var = getattr(engine, self.name) 65 | if stat_var is None: 66 | if 'min' in self.name and 'ori' in self.name: 67 | stat_var = engine.population.min(engine.ori_fitness) 68 | elif 'min' in self.name: 69 | stat_var = engine.population.min(engine.fitness) 70 | elif 'max' in self.name and 'ori' in self.name: 71 | stat_var = engine.population.max(engine.ori_fitness) 72 | elif 'max' in self.name: 73 | stat_var = engine.population.max(engine.fitness) 74 | elif 'mean' in self.name and 'ori' in self.name: 75 | stat_var = engine.population.mean(engine.ori_fitness) 76 | elif 'mean' in self.name: 77 | stat_var = engine.population.mean(engine.fitness) 78 | setattr(engine, self.name, stat_var) 79 | return stat_var 80 | 81 | def __set__(self, engine, value): 82 | ''' 83 | Setter. 84 | ''' 85 | setattr(engine, self.name, value) 86 | 87 | 88 | class GAEngine(object): 89 | ''' Class for representing a Genetic Algorithm engine. The class is the 90 | central object in GAFT framework for running a genetic algorithm optimization. 91 | Once the population with individuals, a set of genetic operators and fitness 92 | function are setup, the engine object unites these informations and provide 93 | means for running a genetic algorthm optimization. 94 | 95 | :param population: The Population to be reproduced in evolution iteration. 96 | :type population: :obj:`gaft.components.Population` 97 | 98 | :param selection: The Selection to be used for individual seleciton. 99 | :type selection: :obj:`gaft.plugin_interfaces.operators.Selection` 100 | 101 | :param crossover: The Crossover to be used for individual crossover. 102 | :type crossover: :obj:`gaft.plugin_interfaces.operators.Crossover` 103 | 104 | :param mutation: The Mutation to be used for individual mutation. 105 | :type mutation: :obj:`gaft.plugin_interfaces.operators.Mutation` 106 | 107 | :param fitness: The fitness calculation function for an individual in population. 108 | :type fitness: function 109 | 110 | :param analysis: All analysis class for on-the-fly analysis. 111 | :type analysis: :obj:`OnTheFlyAnalysis` list 112 | ''' 113 | # Statistical attributes for population. 114 | fmax, fmin, fmean = StatVar('fmax'), StatVar('fmin'), StatVar('fmean') 115 | ori_fmax, ori_fmin, ori_fmean = (StatVar('ori_fmax'), 116 | StatVar('ori_fmin'), 117 | StatVar('ori_fmean')) 118 | 119 | def __init__(self, population, selection, crossover, mutation, 120 | fitness=None, analysis=None): 121 | # Set logger. 122 | logger_name = 'gaft.{}'.format(self.__class__.__name__) 123 | self.logger = logging.getLogger(logger_name) 124 | 125 | # Attributes assignment. 126 | self.population = population 127 | self.fitness = fitness 128 | self.selection = selection 129 | self.crossover = crossover 130 | self.mutation = mutation 131 | self.analysis = [] if analysis is None else [a() for a in analysis] 132 | 133 | # Maxima and minima in population. 134 | self._fmax, self._fmin, self._fmean = None, None, None 135 | self._ori_fmax, self._ori_fmin, self._ori_fmean = None, None, None 136 | 137 | # Default fitness functions. 138 | self.ori_fitness = None if self.fitness is None else self.fitness 139 | 140 | # Store current generation number. 141 | self.current_generation = -1 # Starts from 0. 142 | 143 | # Check parameters validity. 144 | self._check_parameters() 145 | 146 | @do_profile(filename='gaft_run.prof') 147 | def run(self, ng=100): 148 | ''' Run the Genetic Algorithm optimization iteration with specified parameters. 149 | 150 | :param ng: Evolution iteration steps (generation number) 151 | :type ng: int 152 | ''' 153 | if self.fitness is None: 154 | raise AttributeError('No fitness function in GA engine') 155 | 156 | self._update_statvars() 157 | 158 | # Setup analysis objects. 159 | for a in self.analysis: 160 | a.setup(ng=ng, engine=self) 161 | 162 | # Enter evolution iteration. 163 | try: 164 | for g in range(ng): 165 | self.current_generation = g 166 | 167 | # The best individual in current population. 168 | if mpi.is_master: 169 | best_indv = self.population.best_indv(self.fitness) 170 | else: 171 | best_indv = None 172 | best_indv = mpi.bcast(best_indv) 173 | 174 | # Scatter jobs to all processes. 175 | local_indvs = [] 176 | # NOTE: One series of genetic operation generates 2 new individuals. 177 | local_size = mpi.split_size(self.population.size // 2) 178 | 179 | # Fill the new population. 180 | for _ in range(local_size): 181 | # Select father and mother. 182 | parents = self.selection.select(self.population, fitness=self.fitness) 183 | # Crossover. 184 | children = self.crossover.cross(*parents) 185 | # Mutation. 186 | children = [self.mutation.mutate(child, self) for child in children] 187 | # Collect children. 188 | local_indvs.extend(children) 189 | 190 | # Gather individuals from all processes. 191 | indvs = mpi.merge_seq(local_indvs) 192 | # Retain the previous best individual. 193 | indvs[0] = best_indv 194 | # The next generation. 195 | self.population.individuals = indvs 196 | 197 | # Update statistic variables. 198 | self._update_statvars() 199 | 200 | # Run all analysis if needed. 201 | for a in self.analysis: 202 | if g % a.interval == 0: 203 | a.register_step(g=g, population=self.population, engine=self) 204 | except Exception as e: 205 | # Log exception info. 206 | if mpi.is_master: 207 | msg = '{} exception is catched'.format(type(e).__name__) 208 | self.logger.exception(msg) 209 | raise e 210 | finally: 211 | # Recover current generation number. 212 | self.current_generation = -1 213 | # Perform the analysis post processing. 214 | for a in self.analysis: 215 | a.finalize(population=self.population, engine=self) 216 | 217 | def _update_statvars(self): 218 | ''' 219 | Private helper function to update statistic variables in GA engine, like 220 | maximum, minimum and mean values. 221 | ''' 222 | # Wrt original fitness. 223 | self.ori_fmax = self.population.max(self.ori_fitness) 224 | self.ori_fmin = self.population.min(self.ori_fitness) 225 | self.ori_fmean = self.population.mean(self.ori_fitness) 226 | 227 | # Wrt decorated fitness. 228 | self.fmax = self.population.max(self.fitness) 229 | self.fmin = self.population.min(self.fitness) 230 | self.fmean = self.population.mean(self.fitness) 231 | 232 | def _check_parameters(self): 233 | ''' 234 | Helper function to check parameters of engine. 235 | ''' 236 | if not isinstance(self.population, Population): 237 | raise TypeError('population must be a Population object') 238 | if not isinstance(self.selection, Selection): 239 | raise TypeError('selection operator must be a Selection instance') 240 | if not isinstance(self.crossover, Crossover): 241 | raise TypeError('crossover operator must be a Crossover instance') 242 | if not isinstance(self.mutation, Mutation): 243 | raise TypeError('mutation operator must be a Mutation instance') 244 | 245 | for ap in self.analysis: 246 | if not isinstance(ap, OnTheFlyAnalysis): 247 | msg = '{} is not subclass of OnTheFlyAnalysis'.format(ap.__name__) 248 | raise TypeError(msg) 249 | 250 | # Decorators. 251 | 252 | def fitness_register(self, fn): 253 | ''' A decorator for fitness function register. 254 | 255 | :param fn: Fitness function to be registered 256 | :type fn: function 257 | ''' 258 | @wraps(fn) 259 | def _fn_with_fitness_check(indv): 260 | ''' 261 | A wrapper function for fitness function with fitness value check. 262 | ''' 263 | # Check indv type. 264 | if not isinstance(indv, IndividualBase): 265 | raise TypeError('indv\'s class must be subclass of IndividualBase') 266 | 267 | # Check fitness. 268 | fitness = fn(indv) 269 | is_invalid = (type(fitness) is not float) or (math.isnan(fitness)) 270 | if is_invalid: 271 | msg = 'Fitness value(value: {}, type: {}) is invalid' 272 | msg = msg.format(fitness, type(fitness)) 273 | raise ValueError(msg) 274 | return fitness 275 | 276 | self.fitness = _fn_with_fitness_check 277 | if self.ori_fitness is None: 278 | self.ori_fitness = _fn_with_fitness_check 279 | 280 | def analysis_register(self, analysis_cls): 281 | ''' A decorator for analysis regsiter. 282 | 283 | :param analysis_cls: The analysis to be registered 284 | :type analysis_cls: :obj:`gaft.plugin_interfaces.OnTheFlyAnalysis` 285 | ''' 286 | if not issubclass(analysis_cls, OnTheFlyAnalysis): 287 | raise TypeError('analysis class must be subclass of OnTheFlyAnalysis') 288 | 289 | # Add analysis instance to engine. 290 | analysis = analysis_cls() 291 | self.analysis.append(analysis) 292 | 293 | # Functions for fitness scaling. 294 | 295 | def linear_scaling(self, target='max', ksi=0.5): 296 | ''' 297 | A decorator constructor for fitness function linear scaling. 298 | 299 | :param target: The optimization target, maximization or minimization, 300 | possible value: 'max', 'min' 301 | :type target: str 302 | 303 | :param ksi: Selective pressure adjustment value. 304 | :type ksi: float 305 | 306 | .. Note:: 307 | 308 | Linear Scaling: 309 | 1. :math:`arg \max f(x)`, then the scaled fitness would be :math:`f - \min f(x) + {\\xi}` 310 | 2. :math:`arg \min f(x)`, then the scaled fitness would be :math:`\max f(x) - f(x) + {\\xi}` 311 | 312 | ''' 313 | def _linear_scaling(fn): 314 | # For original fitness calculation. 315 | self.ori_fitness = fn 316 | 317 | @wraps(fn) 318 | def _fn_with_linear_scaling(indv): 319 | # Original fitness value. 320 | f = fn(indv) 321 | 322 | # Determine the value of a and b. 323 | if target == 'max': 324 | f_prime = f - self.ori_fmin + ksi 325 | elif target == 'min': 326 | f_prime = self.ori_fmax - f + ksi 327 | else: 328 | raise ValueError('Invalid target type({})'.format(target)) 329 | return f_prime 330 | 331 | return _fn_with_linear_scaling 332 | 333 | return _linear_scaling 334 | 335 | def dynamic_linear_scaling(self, target='max', ksi0=2, r=0.9): 336 | ''' 337 | A decorator constructor for fitness dynamic linear scaling. 338 | 339 | :param target: The optimization target, maximization or minimization 340 | possible value: 'min' or 'max' 341 | :type target: str 342 | 343 | :param ksi0: Initial selective pressure adjustment value, default value is 2 344 | :type ksi0: float 345 | 346 | :param r: The reduction factor for selective pressure adjustment value, 347 | ksi^(k-1)*r is the adjustment value for generation k, default 348 | value is 0.9 349 | :type r: float in range [0.9, 0.999] 350 | 351 | .. Note:: 352 | Dynamic Linear Scaling: 353 | 354 | For maximizaiton, :math:`f' = f(x) - \min f(x) + {\\xi}^{k}`, :math:`k` is generation number. 355 | ''' 356 | def _dynamic_linear_scaling(fn): 357 | # For original fitness calculation. 358 | self.ori_fitness = fn 359 | 360 | @wraps(fn) 361 | def _fn_with_dynamic_linear_scaling(indv): 362 | f = fn(indv) 363 | k = self.current_generation + 1 364 | 365 | if target == 'max': 366 | f_prime = f - self.ori_fmin + ksi0*(r**k) 367 | elif target == 'min': 368 | f_prime = self.ori_fmax - f + ksi0*(r**k) 369 | else: 370 | raise ValueError('Invalid target type({})'.format(target)) 371 | return f_prime 372 | 373 | return _fn_with_dynamic_linear_scaling 374 | 375 | return _dynamic_linear_scaling 376 | 377 | def minimize(self, fn): 378 | ''' A decorator for minimizing the fitness function. 379 | 380 | :param fn: Original fitness function 381 | :type fn: function 382 | ''' 383 | @wraps(fn) 384 | def _minimize(indv): 385 | return -fn(indv) 386 | return _minimize 387 | 388 | -------------------------------------------------------------------------------- /gaft/mpiutil.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ''' 4 | A high-level utility class for parallelizing Genetic Algorithm by using MPI interfaces 5 | in distributed MPI environment. 6 | ''' 7 | 8 | import logging 9 | from itertools import chain 10 | from functools import wraps 11 | 12 | try: 13 | from mpi4py import MPI 14 | MPI_INSTALLED = True 15 | except ImportError: 16 | MPI_INSTALLED = False 17 | 18 | 19 | class Singleton(type): 20 | def __call__(cls, *args, **kwargs): 21 | if not hasattr(cls, '_instance'): 22 | cls._instance = super(Singleton, cls).__call__(*args, **kwargs) 23 | return cls._instance 24 | 25 | 26 | class MPIUtil(object): 27 | 28 | __metaclass__ = Singleton 29 | 30 | def __init__(self): 31 | ''' Wrapper class for higher level of MPI interfaces that will create a 32 | singleton for parallelization. 33 | ''' 34 | logger_name = 'gaft.{}'.format(self.__class__.__name__) 35 | self._logger = logging.getLogger(logger_name) 36 | 37 | def bcast(self, data): 38 | ''' Broadcast data to MPI processes 39 | 40 | :param data: Data to be broadcasted 41 | :type data: any Python object 42 | ''' 43 | if MPI_INSTALLED: 44 | mpi_comm = MPI.COMM_WORLD 45 | bdata = mpi_comm.bcast(data, root=0) 46 | else: 47 | bdata = data 48 | return bdata 49 | 50 | # Wrapper for common MPI interfaces. 51 | def barrier(self): 52 | ''' Block until all processes in the communicator have reached this routine 53 | ''' 54 | if MPI_INSTALLED: 55 | mpi_comm = MPI.COMM_WORLD 56 | mpi_comm.barrier() 57 | 58 | @property 59 | def rank(self): 60 | ''' Get the rank of the calling process in the communicator 61 | ''' 62 | if MPI_INSTALLED: 63 | mpi_comm = MPI.COMM_WORLD 64 | return mpi_comm.Get_rank() 65 | else: 66 | return 0 67 | 68 | @property 69 | def size(self): 70 | ''' Get the size of the group associated with a communicator 71 | ''' 72 | if MPI_INSTALLED: 73 | mpi_comm = MPI.COMM_WORLD 74 | return mpi_comm.Get_size() 75 | else: 76 | return 1 77 | 78 | @property 79 | def is_master(self): 80 | ''' If current process is the master process 81 | ''' 82 | return self.rank == 0 83 | 84 | # Utility methods. 85 | def split_seq(self, sequence): 86 | ''' Split the sequence according to rank and processor number. 87 | 88 | :param sequence: Data sequence to be splitted 89 | :type sequence: any Python object list 90 | 91 | :return: Sub data sequence for current process 92 | :rtype: any Python object list 93 | ''' 94 | starts = [i for i in range(0, len(sequence), len(sequence)//self.size)] 95 | ends = starts[1: ] + [len(sequence)] 96 | start, end = list(zip(starts, ends))[self.rank] 97 | 98 | return sequence[start: end] 99 | 100 | def split_size(self, size): 101 | ''' Split a size number(int) to sub-size number. 102 | 103 | :param size: The size number to be splitted. 104 | :type size: int 105 | 106 | :return: Sub-size for current process 107 | :rtype: int 108 | ''' 109 | if size < self.size: 110 | warn_msg = ('Splitting size({}) is smaller than process ' + 111 | 'number({}), more processor would be ' + 112 | 'superflous').format(size, self.size) 113 | self._logger.warning(warn_msg) 114 | splited_sizes = [1]*size + [0]*(self.size - size) 115 | elif size % self.size != 0: 116 | residual = size % self.size 117 | splited_sizes = [size // self.size]*self.size 118 | for i in range(residual): 119 | splited_sizes[i] += 1 120 | else: 121 | splited_sizes = [size // self.size]*self.size 122 | 123 | return splited_sizes[self.rank] 124 | 125 | def merge_seq(self, seq): 126 | ''' Gather data in sub-process to root process. 127 | 128 | :param seq: Sub data sequence for current process 129 | :type seq: any Python object list 130 | 131 | :return: Merged data sequence from all processes in a communicator 132 | :rtype: any Python object list 133 | ''' 134 | if self.size == 1: 135 | return seq 136 | 137 | mpi_comm = MPI.COMM_WORLD 138 | merged_seq= mpi_comm.allgather(seq) 139 | return list(chain(*merged_seq)) 140 | 141 | 142 | def master_only(func): 143 | ''' Decorator to limit a function to be called only in master process in MPI env. 144 | ''' 145 | @wraps(func) 146 | def _call_in_master_proc(*args, **kwargs): 147 | mpi = MPIUtil() 148 | if mpi.is_master: 149 | return func(*args, **kwargs) 150 | 151 | return _call_in_master_proc 152 | 153 | -------------------------------------------------------------------------------- /gaft/operators/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Package for built-in genetic operators ''' 5 | 6 | from .crossover.uniform_crossover import UniformCrossover 7 | from .selection.roulette_wheel_selection import RouletteWheelSelection 8 | from .selection.tournament_selection import TournamentSelection 9 | from .selection.linear_ranking_selection import LinearRankingSelection 10 | from .selection.exponential_ranking_selection import ExponentialRankingSelection 11 | from .mutation.flip_bit_mutation import FlipBitMutation 12 | from .mutation.flip_bit_mutation import FlipBitBigMutation 13 | 14 | -------------------------------------------------------------------------------- /gaft/operators/crossover/__init__.py: -------------------------------------------------------------------------------- 1 | ''' Package for built-in crossover operators ''' 2 | 3 | from .uniform_crossover import UniformCrossover 4 | 5 | -------------------------------------------------------------------------------- /gaft/operators/crossover/uniform_crossover.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Uniform Crossover operator implementation. ''' 5 | 6 | from random import random 7 | from copy import deepcopy 8 | 9 | from ...plugin_interfaces.operators.crossover import Crossover 10 | 11 | 12 | class UniformCrossover(Crossover): 13 | ''' Crossover operator with uniform crossover algorithm, 14 | see https://en.wikipedia.org/wiki/Crossover_(genetic_algorithm) 15 | 16 | :param pc: The probability of crossover (usaully between 0.25 ~ 1.0) 17 | :type pc: float in (0.0, 1.0] 18 | 19 | :param pe: Gene exchange probability. 20 | :type pe: float in range (0.0, 1.0] 21 | ''' 22 | def __init__(self, pc, pe=0.5): 23 | if pc <= 0.0 or pc > 1.0: 24 | raise ValueError('Invalid crossover probability') 25 | self.pc = pc 26 | 27 | if pe <= 0.0 or pe > 1.0: 28 | raise ValueError('Invalid genome exchange probability') 29 | self.pe = pe 30 | 31 | def cross(self, father, mother): 32 | ''' Cross chromsomes of parent using uniform crossover method. 33 | 34 | :param population: Population where the selection operation occurs. 35 | :type population: :obj:`gaft.components.Population` 36 | 37 | :return: Selected parents (a father and a mother) 38 | :rtype: list of :obj:`gaft.components.IndividualBase` 39 | ''' 40 | do_cross = True if random() <= self.pc else False 41 | 42 | if not do_cross: 43 | return father.clone(), mother.clone() 44 | 45 | # Chromsomes for two children. 46 | chrom1 = deepcopy(father.chromsome) 47 | chrom2 = deepcopy(mother.chromsome) 48 | 49 | for i, (g1, g2) in enumerate(zip(chrom1, chrom2)): 50 | do_exchange = True if random() < self.pe else False 51 | if do_exchange: 52 | chrom1[i], chrom2[i] = g2, g1 53 | 54 | child1, child2 = father.clone(), father.clone() 55 | child1.init(chromsome=chrom1) 56 | child2.init(chromsome=chrom2) 57 | 58 | return child1, child2 59 | 60 | -------------------------------------------------------------------------------- /gaft/operators/mutation/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | ''' Package for built-in mutation operators ''' 3 | 4 | from .flip_bit_mutation import FlipBitMutation, FlipBitBigMutation 5 | 6 | -------------------------------------------------------------------------------- /gaft/operators/mutation/flip_bit_mutation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Flip Bit mutation implementation. ''' 5 | 6 | from random import random, uniform 7 | 8 | from ...mpiutil import MPIUtil 9 | from ...plugin_interfaces.operators.mutation import Mutation 10 | from ...components.binary_individual import BinaryIndividual 11 | from ...components.decimal_individual import DecimalIndividual 12 | 13 | mpi = MPIUtil() 14 | 15 | 16 | class FlipBitMutation(Mutation): 17 | ''' Mutation operator with Flip Bit mutation implementation. 18 | 19 | :param pm: The probability of mutation (usually between 0.001 ~ 0.1) 20 | :type pm: float in range (0.0, 1.0] 21 | ''' 22 | def __init__(self, pm): 23 | if pm <= 0.0 or pm > 1.0: 24 | raise ValueError('Invalid mutation probability') 25 | 26 | self.pm = pm 27 | 28 | def mutate(self, individual, engine): 29 | ''' Mutate the individual. 30 | 31 | :param individual: The individual on which crossover operation occurs 32 | :type individual: :obj:`gaft.components.IndividualBase` 33 | 34 | :param engine: Current genetic algorithm engine 35 | :type engine: :obj:`gaft.engine.GAEngine` 36 | 37 | :return: A mutated individual 38 | :rtype: :obj:`gaft.components.IndividualBase` 39 | ''' 40 | do_mutation = True if random() <= self.pm else False 41 | 42 | if do_mutation: 43 | for i, genome in enumerate(individual.chromsome): 44 | no_flip = True if random() > self.pm else False 45 | if no_flip: 46 | continue 47 | 48 | if type(individual) is BinaryIndividual: 49 | individual.chromsome[i] = genome^1 50 | elif type(individual) is DecimalIndividual: 51 | a, b = individual.ranges[i] 52 | eps = individual.precisions[i] 53 | n_intervals = (b - a)//eps 54 | n = int(uniform(0, n_intervals + 1)) 55 | individual.chromsome[i] = a + n*eps 56 | else: 57 | raise TypeError('Wrong individual type: {}'.format(type(individual))) 58 | 59 | # Update solution. 60 | individual.solution = individual.decode() 61 | 62 | return individual 63 | 64 | 65 | class FlipBitBigMutation(FlipBitMutation): 66 | ''' Mutation operator using Flip Bit mutation implementation with adaptive 67 | big mutation rate to overcome premature or local-best solution. 68 | 69 | :param pm: The probability of mutation (usually between 0.001 ~ 0.1) 70 | :type pm: float in (0.0, 1.0] 71 | 72 | :param pbm: The probability of big mutation, usually more than 5 times 73 | bigger than pm. 74 | :type pbm: float 75 | 76 | :param alpha: intensive factor 77 | :type alpha: float, in range (0.5, 1) 78 | ''' 79 | def __init__(self, pm, pbm, alpha): 80 | super(self.__class__, self).__init__(pm) 81 | 82 | if not (0.0 < pbm < 1.0): 83 | raise ValueError('Invalid big mutation probability') 84 | if pbm < 5*pm and mpi.is_master: 85 | self.logger.warning('Relative low probability for big mutation') 86 | self.pbm = pbm 87 | 88 | # Intensive factor. 89 | if not (0.5 < alpha < 1.0): 90 | raise ValueError('Invalid intensive factor, should be in (0.5, 1.0)') 91 | self.alpha = alpha 92 | 93 | def mutate(self, individual, engine): 94 | ''' Mutate the individual with adaptive big mutation rate. 95 | 96 | :param individual: The individual on which crossover operation occurs 97 | :type individual: :obj:`gaft.components.IndividualBase` 98 | 99 | :param engine: Current genetic algorithm engine 100 | :type engine: :obj:`gaft.engine.GAEngine` 101 | 102 | :return: A mutated individual 103 | :rtype: :obj:`gaft.components.IndividualBase` 104 | ''' 105 | pm = self.pm 106 | 107 | if engine.fmax*self.alpha < engine.fmean: 108 | self.pm = self.pbm 109 | 110 | # Mutate with big probability. 111 | individual = super(self.__class__, self).mutate(individual, engine) 112 | 113 | # Recover probability. 114 | self.pm = pm 115 | 116 | return individual 117 | 118 | -------------------------------------------------------------------------------- /gaft/operators/selection/__init__.py: -------------------------------------------------------------------------------- 1 | ''' Package for built-in selection operators ''' 2 | 3 | from .exponential_ranking_selection import ExponentialRankingSelection 4 | from .linear_ranking_selection import LinearRankingSelection 5 | from .roulette_wheel_selection import RouletteWheelSelection 6 | from .tournament_selection import TournamentSelection 7 | 8 | -------------------------------------------------------------------------------- /gaft/operators/selection/exponential_ranking_selection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Exponential Ranking Selection implemention. ''' 5 | 6 | from random import random 7 | from itertools import accumulate 8 | from bisect import bisect_right 9 | 10 | from ...plugin_interfaces.operators.selection import Selection 11 | 12 | 13 | class ExponentialRankingSelection(Selection): 14 | ''' Selection operator using Exponential Ranking selection method. 15 | 16 | :param base: The base of exponent 17 | :type base: float in range (0.0, 1.0) 18 | ''' 19 | def __init__(self, base=0.5): 20 | if not (0.0 < base < 1.0): 21 | raise ValueError('The base of exponent c must in range (0.0, 1.0)') 22 | 23 | self.base = base 24 | 25 | def select(self, population, fitness): 26 | ''' Select a pair of parent individuals using exponential ranking method. 27 | 28 | :param population: Population where the selection operation occurs. 29 | :type population: :obj:`gaft.components.Population` 30 | 31 | :return: Selected parents (a father and a mother) 32 | :rtype: list of :obj:`gaft.components.IndividualBase` 33 | ''' 34 | # Individual number. 35 | NP = len(population) 36 | 37 | # Add rank to all individuals in population. 38 | all_fits = population.all_fits(fitness) 39 | indvs = population.individuals 40 | sorted_indvs = sorted(indvs, 41 | key=lambda indv: all_fits[indvs.index(indv)]) 42 | 43 | # NOTE: Here the rank i belongs to {1, ..., N} 44 | p = lambda i: self.base**(NP - i) 45 | probabilities = [p(i) for i in range(1, NP + 1)] 46 | 47 | # Normalize probabilities. 48 | psum = sum(probabilities) 49 | wheel = list(accumulate([p/psum for p in probabilities])) 50 | 51 | # Select parents. 52 | father_idx = bisect_right(wheel, random()) 53 | father = sorted_indvs[father_idx] 54 | mother_idx = (father_idx + 1) % len(wheel) 55 | mother = sorted_indvs[mother_idx] 56 | 57 | return father, mother 58 | 59 | -------------------------------------------------------------------------------- /gaft/operators/selection/linear_ranking_selection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Linear Ranking Selection implementation. ''' 5 | 6 | from random import random 7 | from itertools import accumulate 8 | from bisect import bisect_right 9 | 10 | from ...plugin_interfaces.operators.selection import Selection 11 | 12 | 13 | class LinearRankingSelection(Selection): 14 | ''' Selection operator using Linear Ranking selection method. 15 | 16 | Reference: Baker J E. Adaptive selection methods for genetic 17 | algorithms[C]//Proceedings of an International Conference on Genetic 18 | Algorithms and their applications. 1985: 101-111. 19 | ''' 20 | def __init__(self, pmin=0.1, pmax=0.9): 21 | # Selection probabilities for the worst and best individuals. 22 | self.pmin, self.pmax = pmin, pmax 23 | 24 | def select(self, population, fitness): 25 | ''' Select a pair of parent individuals using linear ranking method. 26 | 27 | :param population: Population where the selection operation occurs. 28 | :type population: :obj:`gaft.components.Population` 29 | 30 | :return: Selected parents (a father and a mother) 31 | :rtype: list of :obj:`gaft.components.IndividualBase` 32 | ''' 33 | # Individual number. 34 | NP = len(population) 35 | 36 | # Add rank to all individuals in population. 37 | all_fits = population.all_fits(fitness) 38 | indvs = population.individuals 39 | sorted_indvs = sorted(indvs, 40 | key=lambda indv: all_fits[indvs.index(indv)]) 41 | 42 | # Assign selection probabilities linearly. 43 | # NOTE: Here the rank i belongs to {1, ..., N} 44 | p = lambda i: (self.pmin + (self.pmax - self.pmin)*(i-1)/(NP-1)) 45 | probabilities = [self.pmin] + [p(i) for i in range(2, NP)] + [self.pmax] 46 | 47 | # Normalize probabilities. 48 | psum = sum(probabilities) 49 | wheel = list(accumulate([p/psum for p in probabilities])) 50 | 51 | # Select parents. 52 | father_idx = bisect_right(wheel, random()) 53 | father = sorted_indvs[father_idx] 54 | mother_idx = (father_idx + 1) % len(wheel) 55 | mother = sorted_indvs[mother_idx] 56 | 57 | return father, mother 58 | 59 | -------------------------------------------------------------------------------- /gaft/operators/selection/roulette_wheel_selection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Roulette Wheel Selection implementation. ''' 5 | 6 | from random import random 7 | from bisect import bisect_right 8 | from itertools import accumulate 9 | 10 | from ...plugin_interfaces.operators.selection import Selection 11 | 12 | class RouletteWheelSelection(Selection): 13 | ''' Selection operator with fitness proportionate selection(FPS) or 14 | so-called roulette-wheel selection implementation. 15 | ''' 16 | def __init__(self): 17 | pass 18 | 19 | def select(self, population, fitness): 20 | ''' Select a pair of parent using FPS algorithm. 21 | 22 | :param population: Population where the selection operation occurs. 23 | :type population: :obj:`gaft.components.Population` 24 | 25 | :return: Selected parents (a father and a mother) 26 | :rtype: list of :obj:`gaft.components.IndividualBase` 27 | ''' 28 | # Normalize fitness values for all individuals. 29 | fit = population.all_fits(fitness) 30 | min_fit = min(fit) 31 | fit = [(i - min_fit) for i in fit] 32 | 33 | # Create roulette wheel. 34 | sum_fit = sum(fit) 35 | wheel = list(accumulate([i/sum_fit for i in fit])) 36 | 37 | # Select a father and a mother. 38 | father_idx = bisect_right(wheel, random()) 39 | father = population[father_idx] 40 | mother_idx = (father_idx + 1) % len(wheel) 41 | mother = population[mother_idx] 42 | 43 | return father, mother 44 | 45 | -------------------------------------------------------------------------------- /gaft/operators/selection/tournament_selection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Tournament Selection implementation. ''' 5 | 6 | from random import sample 7 | 8 | from ...plugin_interfaces.operators.selection import Selection 9 | 10 | 11 | class TournamentSelection(Selection): 12 | ''' Selection operator using Tournament Strategy with tournament size equals 13 | to two by default. 14 | 15 | :param tournament_size: Individual number in one tournament 16 | :type tournament_size: int 17 | ''' 18 | def __init__(self, tournament_size=2): 19 | self.tournament_size = tournament_size 20 | 21 | def select(self, population, fitness): 22 | ''' Select a pair of parent using Tournament strategy. 23 | 24 | :param population: Population where the selection operation occurs. 25 | :type population: :obj:`gaft.components.Population` 26 | 27 | :return: Selected parents (a father and a mother) 28 | :rtype: list of :obj:`gaft.components.IndividualBase` 29 | ''' 30 | # Competition function. 31 | all_fits = population.all_fits(fitness) 32 | def complete(competitors): 33 | ''' 34 | Competition function. 35 | ''' 36 | key = lambda indv: all_fits[population.individuals.index(indv)] 37 | return max(competitors, key=key) 38 | 39 | # Check validity of tournament size. 40 | if self.tournament_size >= len(population): 41 | msg = 'Tournament size({}) is larger than population size({})' 42 | raise ValueError(msg.format(self.tournament_size, len(population))) 43 | 44 | # Pick winners of two groups as parent. 45 | competitors_1 = sample(population.individuals, self.tournament_size) 46 | competitors_2 = sample(population.individuals, self.tournament_size) 47 | father, mother = complete(competitors_1), complete(competitors_2) 48 | 49 | return father, mother 50 | 51 | -------------------------------------------------------------------------------- /gaft/plugin_interfaces/__init__.py: -------------------------------------------------------------------------------- 1 | from ..plugin_interfaces.operators import * 2 | from ..plugin_interfaces.analysis import OnTheFlyAnalysis 3 | 4 | -------------------------------------------------------------------------------- /gaft/plugin_interfaces/analysis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .metaclasses import AnalysisMeta 5 | 6 | 7 | class OnTheFlyAnalysis(metaclass=AnalysisMeta): 8 | ''' Class for providing an interface to easily extend and customize the behavior 9 | of the on-the-fly analysis functionality of gaft. 10 | 11 | Attribute: 12 | 13 | interval(:obj:`int`): The analysis interval in evolution iteration, default 14 | value is 1 meaning analyze every step. 15 | 16 | master_only(:obj:`bool`): Flag for if the analysis plugin is only effective 17 | in master process. Default is True. 18 | ''' 19 | # Only used in master process? 20 | master_only = False 21 | 22 | # Analysis interval. 23 | interval = 1 24 | 25 | def setup(self, ng, engine): 26 | ''' Function called right before the start of genetic algorithm main iteration 27 | to allow for custom setup of the analysis object. 28 | 29 | :param ng: The number of generation. 30 | :type ng: int 31 | 32 | :param engine: The current GAEngine where the analysis is running. 33 | :type engine: gaft.engine.GAEngine 34 | ''' 35 | raise NotImplementedError 36 | 37 | def register_step(self, g, population, engine): 38 | ''' 39 | Function called in each iteration step. 40 | 41 | :param g: Current generation number. 42 | :type g: int 43 | 44 | :param population: The up to date population of the iteration. 45 | :type population: Population 46 | 47 | :param engine: The current GAEngine where the analysis is running. 48 | :type engine: gaft.engine.GAEngine 49 | ''' 50 | raise NotImplementedError 51 | 52 | def finalize(self, population, engine): 53 | ''' 54 | Called after the iteration to allow for custom finalization and 55 | post-processing of the collected data. 56 | 57 | :param population: The up to date population of the iteration. 58 | :type population: Population 59 | 60 | :param engine: The current GAEngine where the analysis is running. 61 | :type engine: gaft.engine.GAEngine 62 | ''' 63 | raise NotImplementedError 64 | 65 | -------------------------------------------------------------------------------- /gaft/plugin_interfaces/metaclasses.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | import inspect 6 | from functools import wraps 7 | 8 | from ..components.individual import IndividualBase 9 | from ..components.population import Population 10 | from ..mpiutil import master_only 11 | 12 | 13 | class AnalysisMeta(type): 14 | ''' Metaclass for analysis plugin class 15 | ''' 16 | def __new__(cls, name, bases, attrs): 17 | # Check interval type. 18 | if 'interval' in attrs: 19 | interval = attrs['interval'] 20 | if type(interval) is not int or interval <= 0: 21 | raise TypeError('analysis interval must be a positive integer') 22 | 23 | for method_name in ['setup', 'register_step', 'finalize']: 24 | method = attrs.get(method_name, None) 25 | if method is not None and not callable(method): 26 | msg = "{} must be a callable object".format(method) 27 | raise AttributeError(msg) 28 | # Set default interface methods. 29 | elif method is None: 30 | if method_name == 'setup': 31 | attrs[method_name] = lambda self, ng, engine: None 32 | elif method_name == 'register_step': 33 | attrs[method_name] = lambda self, g, population, engine: None 34 | elif method_name == 'finalize': 35 | attrs[method_name] = lambda self, population, engine: None 36 | 37 | # Check if the plugin is only used in master process. 38 | called_in_master = attrs['master_only'] if 'master_only' in attrs else False 39 | 40 | # Wrap all interfaces. 41 | if called_in_master: 42 | for method_name in ['setup', 'register_step', 'finalize']: 43 | attrs[method_name] = master_only(attrs[method_name]) 44 | 45 | # Set logger. 46 | logger_name = 'gaft.{}'.format(name) 47 | attrs['logger'] = logging.getLogger(logger_name) 48 | 49 | return type.__new__(cls, name, bases, attrs) 50 | 51 | 52 | class CrossoverMeta(type): 53 | ''' Metaclass for crossover operator class. 54 | ''' 55 | def __new__(cls, name, bases, attrs): 56 | if 'cross' not in attrs: 57 | raise AttributeError('crossover operator class must have cross method') 58 | 59 | if 'pc' in attrs and (attrs['pc'] <= 0.0 or attrs['pc'] > 1.0): 60 | raise ValueError('Invalid crossover probability') 61 | 62 | cross = attrs['cross'] 63 | 64 | # Check parameter of cross method. 65 | sig = inspect.signature(cross) 66 | if 'father' not in sig.parameters: 67 | raise NameError('cross method must have father parameter') 68 | if 'mother' not in sig.parameters: 69 | raise NameError('cross method must have mother parameter') 70 | 71 | # Add parameter check to user-defined method. 72 | @wraps(cross) 73 | def _wrapped_cross(self, father, mother): 74 | ''' Wrapper to add parameters type checking. 75 | ''' 76 | # Check parameter types. 77 | if not (isinstance(father, IndividualBase) and 78 | isinstance(mother, IndividualBase)): 79 | raise TypeError('father and mother\'s type must be subclass of IndividualBase') 80 | 81 | return cross(self, father, mother) 82 | 83 | attrs['cross'] = _wrapped_cross 84 | 85 | # Set logger. 86 | logger_name = 'gaft.{}'.format(name) 87 | attrs['logger'] = logging.getLogger(logger_name) 88 | 89 | return type.__new__(cls, name, bases, attrs) 90 | 91 | 92 | class MutationMeta(type): 93 | ''' Metaclass for mutation operator class. 94 | ''' 95 | def __new__(cls, name, bases, attrs): 96 | if 'mutate' not in attrs: 97 | raise AttributeError('mutation operator class must have mutate method') 98 | 99 | if 'pm' in attrs and (attrs['pm'] <= 0.0 or attrs['pm'] > 1.0): 100 | raise ValueError('Invalid mutation probability') 101 | 102 | mutate = attrs['mutate'] 103 | 104 | # Check parameters of mutate method. 105 | sig = inspect.signature(mutate) 106 | if 'individual' not in sig.parameters: 107 | raise NameError('mutate method must have individual parameter') 108 | 109 | # Add parameter check to user-defined method. 110 | @wraps(mutate) 111 | def _wrapped_mutate(self, individual, engine): 112 | ''' Wrapper to add parameters type checking. 113 | ''' 114 | # Check parameter types. 115 | if not isinstance(individual, IndividualBase): 116 | raise TypeError('individual\' type must be subclass of IndividualBase') 117 | 118 | return mutate(self, individual, engine) 119 | 120 | attrs['mutate'] = _wrapped_mutate 121 | 122 | # Set logger. 123 | logger_name = 'gaft.{}'.format(name) 124 | attrs['logger'] = logging.getLogger(logger_name) 125 | 126 | return type.__new__(cls, name, bases, attrs) 127 | 128 | 129 | class SelectionMeta(type): 130 | ''' Metaclass for selection operator class. 131 | ''' 132 | def __new__(cls, name, bases, attrs): 133 | # Check select method. 134 | if 'select' not in attrs: 135 | raise AttributeError('selection operator class must have select method') 136 | 137 | select = attrs['select'] 138 | 139 | # Check select arguments. 140 | sig = inspect.signature(select) 141 | if 'population' not in sig.parameters: 142 | raise NameError('select method must have population parameter') 143 | if 'fitness' not in sig.parameters: 144 | raise NameError('select method must have fitness parameter') 145 | 146 | # Add parameter check to user-defined method. 147 | @wraps(select) 148 | def _wrapped_select(self, population, fitness): 149 | ''' Wrapper to add parameters type checking. 150 | ''' 151 | # Check parameter types. 152 | if not isinstance(population, Population): 153 | raise TypeError('population must be Population object') 154 | if not callable(fitness): 155 | raise TypeError('fitness must be a callable object') 156 | 157 | return select(self, population, fitness) 158 | 159 | attrs['select'] = _wrapped_select 160 | 161 | # Set logger. 162 | logger_name = 'gaft.{}'.format(name) 163 | attrs['logger'] = logging.getLogger(logger_name) 164 | 165 | return type.__new__(cls, name, bases, attrs) 166 | 167 | -------------------------------------------------------------------------------- /gaft/plugin_interfaces/operators/__init__.py: -------------------------------------------------------------------------------- 1 | from .selection import Selection 2 | from .crossover import Crossover 3 | from .mutation import Mutation 4 | 5 | -------------------------------------------------------------------------------- /gaft/plugin_interfaces/operators/crossover.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Module for Genetic Algorithm crossover operator class ''' 5 | 6 | from ..metaclasses import CrossoverMeta 7 | 8 | 9 | class Crossover(metaclass=CrossoverMeta): 10 | ''' Class for providing an interface to easily extend the behavior of crossover 11 | operation between two individuals for children breeding. 12 | 13 | Attributes: 14 | 15 | pc(:obj:`float`): The probability of crossover (usaully between 0.25 ~ 1.0) 16 | ''' 17 | 18 | # The probability of crossover (usaully between 0.25 ~ 1.0) 19 | pc = 0.8 20 | 21 | def cross(self, father, mother): 22 | ''' Called when we need to cross parents to generate children. 23 | 24 | :param father: The parent individual to be crossed 25 | :type father: gaft.components.IndividualBase 26 | 27 | :param mother: The parent individual to be crossed 28 | :type mother: gaft.components.IndividualBase 29 | 30 | :return children: Two new children individuals 31 | :type children: tuple of gaft.components.IndividualBase 32 | ''' 33 | raise NotImplementedError 34 | 35 | -------------------------------------------------------------------------------- /gaft/plugin_interfaces/operators/mutation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Module for Genetic Algorithm mutation operator class ''' 5 | 6 | from ..metaclasses import MutationMeta 7 | 8 | 9 | class Mutation(metaclass=MutationMeta): 10 | ''' Class for providing an interface to easily extend the behavior of selection 11 | operation. 12 | 13 | Attributes: 14 | 15 | pm(float): Default mutation probability, default is 0.1 16 | ''' 17 | # Default mutation probability. 18 | pm = 0.1 19 | 20 | def mutate(self, individual, engine): 21 | ''' Called when an individual to be mutated. 22 | 23 | :param individual: The individual to be mutated 24 | :type individual: gaft.components.IndividualBase 25 | 26 | :param engine: The GA engine where the mutation operator belongs. 27 | :type engine: gaft.engine.GAEngine 28 | ''' 29 | raise NotImplementedError 30 | 31 | -------------------------------------------------------------------------------- /gaft/plugin_interfaces/operators/selection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Module for Genetic Algorithm selection operator class ''' 5 | 6 | from ..metaclasses import SelectionMeta 7 | 8 | class Selection(metaclass=SelectionMeta): 9 | ''' Class for providing an interface to easily extend the behavior of selection 10 | operation. 11 | ''' 12 | 13 | def select(self, population, fitness): 14 | ''' Called when we need to select parents from a population to later breeding. 15 | 16 | :param population: The current population 17 | :type population: gaft.compoenents.Population 18 | 19 | :return parents: Two selected individuals for crossover 20 | :type parents: tuple of gaft.components.IndividualBase 21 | ''' 22 | raise NotImplementedError 23 | 24 | -------------------------------------------------------------------------------- /gaft/tests/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | -------------------------------------------------------------------------------- /gaft/tests/dynamic_linear_scaling_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Test the dynmaic linear scaling decorator of GA engine. 5 | ''' 6 | 7 | import unittest 8 | from math import sin, cos 9 | 10 | from .. import GAEngine 11 | from ..components import BinaryIndividual 12 | from ..components import Population 13 | from ..operators import RouletteWheelSelection 14 | from ..operators import UniformCrossover 15 | from ..operators import FlipBitMutation 16 | 17 | 18 | class DynamicLinearScalingTest(unittest.TestCase): 19 | 20 | def setUp(self): 21 | self.maxDiff = True 22 | 23 | def test_run(self): 24 | ''' 25 | Make sure GA engine can run correctly. 26 | ''' 27 | indv_template = BinaryIndividual(ranges=[(0, 10)], eps=0.001) 28 | population = Population(indv_template=indv_template, size=50).init() 29 | 30 | # Create genetic operators. 31 | selection = RouletteWheelSelection() 32 | crossover = UniformCrossover(pc=0.8, pe=0.5) 33 | mutation = FlipBitMutation(pm=0.1) 34 | 35 | # Create genetic algorithm engine. 36 | engine = GAEngine(population=population, selection=selection, 37 | crossover=crossover, mutation=mutation) 38 | 39 | @engine.fitness_register 40 | @engine.dynamic_linear_scaling() 41 | def fitness(indv): 42 | x, = indv.solution 43 | return x + 10*sin(5*x) + 7*cos(4*x) 44 | 45 | engine.run(50) 46 | 47 | if '__main__' == __name__: 48 | suite = unittest.TestLoader().loadTestsFromTestCase(DynamicLinearScalingTest) 49 | unittest.TextTestRunner(verbosity=2).run(suite) 50 | 51 | -------------------------------------------------------------------------------- /gaft/tests/engine_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Test case for engine run. 5 | ''' 6 | 7 | import unittest 8 | from math import sin, cos 9 | 10 | from .. import GAEngine 11 | from ..components import BinaryIndividual, DecimalIndividual 12 | from ..components import Population 13 | from ..operators import RouletteWheelSelection 14 | from ..operators import UniformCrossover 15 | from ..operators import FlipBitMutation 16 | 17 | 18 | class GAEngineTest(unittest.TestCase): 19 | 20 | def setUp(self): 21 | self.maxDiff = True 22 | 23 | def test_binary_run(self): 24 | ''' 25 | Make sure GA engine can run correctly. 26 | ''' 27 | indv_template = BinaryIndividual(ranges=[(0, 10)], eps=0.001) 28 | population = Population(indv_template=indv_template, size=50).init() 29 | 30 | # Create genetic operators. 31 | selection = RouletteWheelSelection() 32 | crossover = UniformCrossover(pc=0.8, pe=0.5) 33 | mutation = FlipBitMutation(pm=0.1) 34 | 35 | # Create genetic algorithm engine. 36 | engine = GAEngine(population=population, selection=selection, 37 | crossover=crossover, mutation=mutation) 38 | 39 | @engine.fitness_register 40 | @engine.minimize 41 | def fitness(indv): 42 | x, = indv.solution 43 | return x + 10*sin(5*x) + 7*cos(4*x) 44 | 45 | engine.run(50) 46 | 47 | def test_decimal_run(self): 48 | ''' 49 | Make sure GA engine can run correctly with decimal encoding individuals. 50 | ''' 51 | indv_template = DecimalIndividual(ranges=[(0, 10)], eps=0.001) 52 | population = Population(indv_template=indv_template, size=50).init() 53 | 54 | # Create genetic operators. 55 | selection = RouletteWheelSelection() 56 | crossover = UniformCrossover(pc=0.8, pe=0.5) 57 | mutation = FlipBitMutation(pm=0.1) 58 | 59 | # Create genetic algorithm engine. 60 | engine = GAEngine(population=population, selection=selection, 61 | crossover=crossover, mutation=mutation) 62 | 63 | @engine.fitness_register 64 | @engine.minimize 65 | def fitness(indv): 66 | x, = indv.solution 67 | return x + 10*sin(5*x) + 7*cos(4*x) 68 | 69 | engine.run(50) 70 | 71 | if '__main__' == __name__: 72 | suite = unittest.TestLoader().loadTestsFromTestCase(GAEngineTest) 73 | unittest.TextTestRunner(verbosity=2).run(suite) 74 | 75 | -------------------------------------------------------------------------------- /gaft/tests/exponential_ranking_selection_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Test case for built-in Exponential Ranking selection 5 | ''' 6 | 7 | import unittest 8 | 9 | from ..components import Population, BinaryIndividual 10 | from ..operators import ExponentialRankingSelection 11 | 12 | class ExponentialRankingSelectionTest(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.maxDiff 16 | def fitness(indv): 17 | x, = indv.solution 18 | return x**3 - 60*x**2 + 900*x + 100 19 | self.fitness = fitness 20 | 21 | def test_selection(self): 22 | indv = BinaryIndividual(ranges=[(0, 30)]) 23 | p = Population(indv) 24 | p.init() 25 | 26 | selection = ExponentialRankingSelection() 27 | father, mother = selection.select(p, fitness=self.fitness) 28 | 29 | self.assertTrue(isinstance(father, BinaryIndividual)) 30 | self.assertTrue(isinstance(mother, BinaryIndividual)) 31 | self.assertNotEqual(father.chromsome, mother.chromsome) 32 | 33 | if '__main__' == __name__: 34 | suite = unittest.TestLoader().loadTestsFromTestCase(ExponentialRankingSelectionTest) 35 | unittest.TextTestRunner(verbosity=2).run(suite) 36 | 37 | -------------------------------------------------------------------------------- /gaft/tests/flip_bit_big_mutation_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Test case for built-in Flip Bit mutation operator with big mutation rate. 5 | ''' 6 | 7 | import unittest 8 | from math import sin, cos 9 | 10 | from .. import GAEngine 11 | from ..components import BinaryIndividual 12 | from ..components import Population 13 | from ..operators import RouletteWheelSelection 14 | from ..operators import UniformCrossover 15 | from ..operators.mutation.flip_bit_mutation import FlipBitBigMutation 16 | 17 | class FlipBitBigMutationTest(unittest.TestCase): 18 | 19 | def setUp(self): 20 | self.maxDiff 21 | 22 | def test_mutate(self): 23 | ''' Make sure the individual can be mutated correctly. 24 | ''' 25 | indv_template = BinaryIndividual(ranges=[(0, 10)], eps=0.001) 26 | population = Population(indv_template=indv_template, size=50).init() 27 | 28 | # Create genetic operators. 29 | selection = RouletteWheelSelection() 30 | crossover = UniformCrossover(pc=0.8, pe=0.5) 31 | mutation = FlipBitBigMutation(pm=0.03, pbm=0.2, alpha=0.6) 32 | 33 | # Create genetic algorithm engine. 34 | engine = GAEngine(population=population, selection=selection, 35 | crossover=crossover, mutation=mutation) 36 | 37 | @engine.fitness_register 38 | def fitness(indv): 39 | x, = indv.solution 40 | return x + 10*sin(5*x) + 7*cos(4*x) 41 | 42 | mutation.mutate(indv_template, engine) 43 | 44 | if '__main__' == __name__: 45 | suite = unittest.TestLoader().loadTestsFromTestCase(FlipBitBigMutationTest) 46 | unittest.TextTestRunner(verbosity=2).run(suite) 47 | 48 | -------------------------------------------------------------------------------- /gaft/tests/flip_bit_mutation_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Test case for built-in Flip Bit mutation operator. 5 | ''' 6 | 7 | import unittest 8 | 9 | from ..components import BinaryIndividual, DecimalIndividual 10 | from ..operators.mutation.flip_bit_mutation import FlipBitMutation 11 | 12 | class FlipBitMutationTest(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.maxDiff 16 | 17 | def test_mutate_binary_indv(self): 18 | ''' Make sure the individual can be mutated correctly. 19 | ''' 20 | indv = BinaryIndividual(ranges=[(0, 1)]).init(solution=[0.398]) 21 | mutation = FlipBitMutation(pm=1.0) 22 | chromsome_before = [0, 1, 1, 0, 0, 1, 0, 1, 1] 23 | chromsome_after = [1, 0, 0, 1, 1, 0, 1, 0, 0] 24 | self.assertListEqual(indv.chromsome, chromsome_before) 25 | mutation.mutate(indv, engine=None) 26 | self.assertListEqual(indv.chromsome, chromsome_after) 27 | 28 | def test_mutate_decimal_indv(self): 29 | ''' Make sure individual with decimal encoding can be mutated correctly. 30 | ''' 31 | indv = DecimalIndividual(ranges=[(0, 1), (0, 2)]).init(solution=[0.5, 1.5]) 32 | mutation = FlipBitMutation(pm=1.0) 33 | chromsome_before = [0.5, 1.5] 34 | self.assertListEqual(indv.chromsome, chromsome_before) 35 | mutation.mutate(indv, engine=None) 36 | for a, b in zip(indv.chromsome, chromsome_before): 37 | self.assertNotEqual(a, b) 38 | 39 | if '__main__' == __name__: 40 | suite = unittest.TestLoader().loadTestsFromTestCase(FlipBitMutationTest) 41 | unittest.TextTestRunner(verbosity=2).run(suite) 42 | 43 | -------------------------------------------------------------------------------- /gaft/tests/individual_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Test case for Individual. 5 | ''' 6 | 7 | import unittest 8 | 9 | from ..components import BinaryIndividual 10 | 11 | class IndividualTest(unittest.TestCase): 12 | 13 | def setUp(self): 14 | self.maxDiff = True 15 | 16 | def test_binary_encoding(self): 17 | ''' Make sure individual can decode and encode binary gene correctly. 18 | ''' 19 | indv = BinaryIndividual(ranges=[(0, 1)], eps=0.001) 20 | indv.init(solution=[0.398]) 21 | 22 | # Test binary chromsome. 23 | ref_chromsome = [0, 1, 1, 0, 0, 1, 0, 1, 1] 24 | self.assertListEqual(indv.chromsome, ref_chromsome) 25 | 26 | # Test decode. 27 | self.assertListEqual(indv.decode(), [0.396484375]) 28 | 29 | indv = BinaryIndividual(ranges=[(0, 1), (-1, 1)], eps=0.001) 30 | indv.init(solution=[0.398, 0.66]) 31 | 32 | # Test binary chromsome. 33 | ref_chromsome = [0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1] 34 | self.assertListEqual(indv.chromsome, ref_chromsome) 35 | 36 | # Test decode. 37 | self.assertListEqual(indv.decode(), [0.396484375, 0.658203125]) 38 | 39 | def test_descriptors(self): 40 | ''' Make sure descriptors can check the parameters correctly. 41 | ''' 42 | self.assertRaises(TypeError, BinaryIndividual, ranges=0.1, eps=0.001) 43 | self.assertRaises(TypeError, BinaryIndividual, ranges=[(0, 1)], eps='asdf') 44 | self.assertRaises(ValueError, BinaryIndividual, ranges=[(0, 1)], eps=10.0) 45 | self.assertRaises(ValueError, BinaryIndividual, ranges=[(0, 1)], eps=[1, 2]) 46 | 47 | def test_init(self): 48 | ''' Make sure the individual can be initialized correctly. 49 | ''' 50 | indv = BinaryIndividual(ranges=[(0, 1)], eps=0.001) 51 | 52 | # Check chromsome initialization. 53 | indv.init(chromsome=[0, 1, 1, 0, 0, 0, 1, 1, 1, 0]) 54 | 55 | self.assertListEqual([0, 1, 1, 0, 0, 0, 1, 1, 1, 0], indv.chromsome) 56 | self.assertListEqual(indv.solution, [0.388671875]) 57 | 58 | # Check solution initialization. 59 | indv.init(solution=[0.398]) 60 | 61 | self.assertListEqual(indv.solution, [0.398]) 62 | self.assertListEqual(indv.chromsome, [0, 1, 1, 0, 0, 1, 0, 1, 1]) 63 | 64 | def test_clone(self): 65 | ''' Make sure individual can be cloned correctly. 66 | ''' 67 | indv = BinaryIndividual(ranges=[(0, 1)], 68 | eps=0.001).init(solution=[0.398]) 69 | indv_clone = indv.clone() 70 | 71 | self.assertListEqual(indv.chromsome, indv_clone.chromsome) 72 | self.assertAlmostEqual(indv.solution[0], indv_clone.solution[0], places=2) 73 | self.assertEqual(indv.ranges, indv_clone.ranges) 74 | self.assertEqual(indv.eps, indv_clone.eps) 75 | 76 | def test_multi_precisions(self): 77 | ''' Make sure we can construct individual using different decrete precisions. 78 | ''' 79 | indv = BinaryIndividual(ranges=[(0, 1), (0, 10)], eps=[0.01, 1.0]).init(solution=[0.3, 0.5]) 80 | self.assertNotEqual(indv.precisions[0], indv.precisions[1]) 81 | 82 | if '__main__' == __name__: 83 | suite = unittest.TestLoader().loadTestsFromTestCase(IndividualTest) 84 | unittest.TextTestRunner(verbosity=2).run(suite) 85 | 86 | -------------------------------------------------------------------------------- /gaft/tests/linear_ranking_selection_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Test case for built-in Linear Ranking selection 5 | ''' 6 | 7 | import unittest 8 | 9 | from gaft.components import Population, BinaryIndividual 10 | from gaft.operators import LinearRankingSelection 11 | 12 | class LinearRankingSelectionTest(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.maxDiff 16 | def fitness(indv): 17 | x, = indv.solution 18 | return x**3 - 60*x**2 + 900*x + 100 19 | self.fitness = fitness 20 | 21 | def test_selection(self): 22 | indv = BinaryIndividual(ranges=[(0, 30)]) 23 | p = Population(indv) 24 | p.init() 25 | 26 | selection = LinearRankingSelection() 27 | father, mother = selection.select(p, fitness=self.fitness) 28 | 29 | self.assertTrue(isinstance(father, BinaryIndividual)) 30 | self.assertTrue(isinstance(mother, BinaryIndividual)) 31 | self.assertNotEqual(father.chromsome, mother.chromsome) 32 | 33 | if '__main__' == __name__: 34 | suite = unittest.TestLoader().loadTestsFromTestCase(LinearRankingSelectionTest) 35 | unittest.TextTestRunner(verbosity=2).run(suite) 36 | 37 | -------------------------------------------------------------------------------- /gaft/tests/linear_scaling_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Test the linear scaling decorator of GA engine. 5 | ''' 6 | 7 | import unittest 8 | from math import sin, cos 9 | 10 | from gaft import GAEngine 11 | from gaft.components import BinaryIndividual 12 | from gaft.components import Population 13 | from gaft.operators import RouletteWheelSelection 14 | from gaft.operators import UniformCrossover 15 | from gaft.operators import FlipBitMutation 16 | 17 | 18 | class LinearScalingTest(unittest.TestCase): 19 | 20 | def setUp(self): 21 | self.maxDiff = True 22 | 23 | def test_run(self): 24 | ''' 25 | Make sure GA engine can run correctly. 26 | ''' 27 | indv_template = BinaryIndividual(ranges=[(0, 10)], eps=0.001) 28 | population = Population(indv_template=indv_template, size=50).init() 29 | 30 | # Create genetic operators. 31 | selection = RouletteWheelSelection() 32 | crossover = UniformCrossover(pc=0.8, pe=0.5) 33 | mutation = FlipBitMutation(pm=0.1) 34 | 35 | # Create genetic algorithm engine. 36 | engine = GAEngine(population=population, selection=selection, 37 | crossover=crossover, mutation=mutation) 38 | 39 | @engine.fitness_register 40 | @engine.linear_scaling(target='max', ksi=0.5) 41 | def fitness(indv): 42 | x, = indv.solution 43 | return x + 10*sin(5*x) + 7*cos(4*x) 44 | 45 | engine.run(50) 46 | 47 | if '__main__' == __name__: 48 | suite = unittest.TestLoader().loadTestsFromTestCase(LinearScalingTest) 49 | unittest.TextTestRunner(verbosity=2).run(suite) 50 | 51 | -------------------------------------------------------------------------------- /gaft/tests/mpiutil_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Test case for MPI utility class. 5 | ''' 6 | 7 | import unittest 8 | 9 | from gaft.mpiutil import MPIUtil 10 | 11 | mpi = MPIUtil() 12 | 13 | 14 | class MPIUtilTest(unittest.TestCase): 15 | 16 | def setUp(self): 17 | self.maxDiff = True 18 | 19 | def test_split_seq(self): 20 | ''' 21 | Make sure sequence can be splited according to process number correctly. 22 | ''' 23 | seq = list(range(10)) 24 | 25 | if mpi.size == 1: 26 | self.assertListEqual(mpi.split_seq(seq), seq) 27 | elif mpi.size == 2: 28 | if mpi.rank == 0: 29 | self.assertListEqual(mpi.split_seq(seq), [0, 1, 2, 3, 4]) 30 | elif mpi.rank == 1: 31 | self.assertListEqual(mpi.split_seq(seq), [5, 6, 7, 8, 9]) 32 | 33 | def test_merge_seq(self): 34 | ''' 35 | Make sure sequence in different processes can be merged correctly. 36 | ''' 37 | if mpi.size == 1: 38 | self.assertListEqual(mpi.merge_seq([1, 2, 3]), [1, 2, 3]) 39 | if mpi.size == 2: 40 | if mpi.rank == 0: 41 | recv_data = mpi.merge_seq([1, 2, 3]) 42 | elif mpi.rank == 1: 43 | recv_data = mpi.merge_seq([2, 3, 4]) 44 | self.assertListEqual(recv_data, [1, 2, 3, 2, 3, 4]) 45 | 46 | def test_split_size(self): 47 | ''' 48 | Make sure a size number can be splited correctly. 49 | ''' 50 | if mpi.size == 1: 51 | self.assertEqual(mpi.split_size(50), 50) 52 | if mpi.size == 2: 53 | if mpi.rank == 0: 54 | self.assertEqual(mpi.split_size(49), 25) 55 | self.assertEqual(mpi.split_size(1), 1) 56 | elif mpi.rank == 1: 57 | self.assertEqual(mpi.split_size(49), 24) 58 | self.assertEqual(mpi.split_size(1), 0) 59 | 60 | if '__main__' == __name__: 61 | suite = unittest.TestLoader().loadTestsFromTestCase(MPIUtilTest) 62 | unittest.TextTestRunner(verbosity=2).run(suite) 63 | 64 | -------------------------------------------------------------------------------- /gaft/tests/population_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Test case for Population 5 | ''' 6 | 7 | import unittest 8 | 9 | from gaft.components import Population, BinaryIndividual 10 | 11 | class PopulationTest(unittest.TestCase): 12 | 13 | def setUp(self): 14 | self.maxDiff = True 15 | self.indv_template = BinaryIndividual(ranges=[(0, 1)]) 16 | def fitness(indv): 17 | x, = indv.solution 18 | return x**3 - 60*x**2 + 900*x + 100 19 | self.fitness = fitness 20 | 21 | def test_initialization(self): 22 | ''' Make sure a population can be initialized correctly. ''' 23 | population = Population(indv_template=self.indv_template, size=10) 24 | 25 | self.assertListEqual(population.individuals, []) 26 | 27 | population.init() 28 | self.assertEqual(len(population.individuals), 10) 29 | 30 | # Check individual. 31 | self.assertTrue(isinstance(population[0], BinaryIndividual)) 32 | 33 | def test_new_population(self): 34 | ''' Make sure population can clone a new population. ''' 35 | population = Population(indv_template=self.indv_template, size=10) 36 | population.init() 37 | new_population = population.new() 38 | self.assertEqual(new_population.size, 10) 39 | self.assertListEqual(new_population.individuals, []) 40 | 41 | def test_all_fits(self): 42 | population = Population(indv_template=self.indv_template, size=10) 43 | population.init() 44 | all_fits = population.all_fits(fitness=self.fitness) 45 | 46 | self.assertEqual(len(all_fits), 10) 47 | 48 | for fit in all_fits: 49 | self.assertTrue(type(fit) is float) 50 | 51 | if '__main__' == __name__: 52 | suite = unittest.TestLoader().loadTestsFromTestCase(PopulationTest) 53 | unittest.TextTestRunner(verbosity=2).run(suite) 54 | 55 | -------------------------------------------------------------------------------- /gaft/tests/roulette_wheel_selection_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Test case for built-in Roulette Wheel selection 5 | ''' 6 | 7 | import unittest 8 | 9 | from gaft.components import Population, BinaryIndividual 10 | from gaft.operators.selection.roulette_wheel_selection import RouletteWheelSelection 11 | 12 | class RouletteWheelSelectionTest(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.maxDiff 16 | def fitness(indv): 17 | x, = indv.solution 18 | return x**3 - 60*x**2 + 900*x + 100 19 | self.fitness = fitness 20 | 21 | def test_selection(self): 22 | indv = BinaryIndividual(ranges=[(0, 30)]) 23 | p = Population(indv) 24 | p.init() 25 | 26 | selection = RouletteWheelSelection() 27 | father, mother = selection.select(p, fitness=self.fitness) 28 | 29 | self.assertTrue(isinstance(father, BinaryIndividual)) 30 | self.assertTrue(isinstance(mother, BinaryIndividual)) 31 | self.assertNotEqual(father.chromsome, mother.chromsome) 32 | 33 | if '__main__' == __name__: 34 | suite = unittest.TestLoader().loadTestsFromTestCase(RouletteWheelSelectionTest) 35 | unittest.TextTestRunner(verbosity=2).run(suite) 36 | 37 | -------------------------------------------------------------------------------- /gaft/tests/test_all.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Module for all test in ga.py 5 | ''' 6 | 7 | import unittest 8 | 9 | from .individual_test import IndividualTest 10 | from .population_test import PopulationTest 11 | from .roulette_wheel_selection_test import RouletteWheelSelectionTest 12 | from .uniform_crossover_test import UniformCrossoverTest 13 | from .flip_bit_mutation_test import FlipBitMutationTest 14 | from .mpiutil_test import MPIUtilTest 15 | from .engine_test import GAEngineTest 16 | from .tournament_selection_test import TournamentSelectionTest 17 | from .linear_ranking_selection_test import LinearRankingSelectionTest 18 | from .exponential_ranking_selection_test import ExponentialRankingSelectionTest 19 | from .linear_scaling_test import LinearScalingTest 20 | from .dynamic_linear_scaling_test import DynamicLinearScalingTest 21 | from .flip_bit_big_mutation_test import FlipBitBigMutationTest 22 | 23 | def suite(): 24 | ''' Generate test suite for all test cases in GAFT 25 | ''' 26 | test_cases = [ 27 | IndividualTest, 28 | PopulationTest, 29 | RouletteWheelSelectionTest, 30 | UniformCrossoverTest, 31 | FlipBitMutationTest, 32 | MPIUtilTest, 33 | GAEngineTest, 34 | TournamentSelectionTest, 35 | LinearRankingSelectionTest, 36 | ExponentialRankingSelectionTest, 37 | LinearScalingTest, 38 | DynamicLinearScalingTest, 39 | FlipBitBigMutationTest 40 | ] 41 | 42 | test_suite = unittest.TestSuite([ 43 | unittest.TestLoader().loadTestsFromTestCase(tc) for tc in test_cases 44 | ]) 45 | 46 | return test_suite 47 | 48 | if '__main__' == __name__: 49 | result = unittest.TextTestRunner(verbosity=2).run(suite()) 50 | 51 | if result.errors or result.failures: 52 | raise ValueError('Get erros and failures') 53 | 54 | -------------------------------------------------------------------------------- /gaft/tests/tournament_selection_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Test case for built-in Tournament Selection operator. 5 | ''' 6 | 7 | import unittest 8 | 9 | from gaft.components import Population, BinaryIndividual 10 | from gaft.operators import TournamentSelection 11 | 12 | class TournamentSelectionTest(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.maxDiff 16 | def fitness(indv): 17 | x, = indv.solution 18 | return x**3 - 60*x**2 + 900*x + 100 19 | self.fitness = fitness 20 | 21 | def test_selection(self): 22 | indv = BinaryIndividual(ranges=[(0, 30)]) 23 | p = Population(indv) 24 | p.init() 25 | 26 | selection = TournamentSelection() 27 | father, mother = selection.select(p, fitness=self.fitness) 28 | 29 | self.assertTrue(isinstance(father, BinaryIndividual)) 30 | self.assertTrue(isinstance(mother, BinaryIndividual)) 31 | 32 | if '__main__' == __name__: 33 | suite = unittest.TestLoader().loadTestsFromTestCase(TournamentSelectionTest) 34 | unittest.TextTestRunner(verbosity=2).run(suite) 35 | 36 | -------------------------------------------------------------------------------- /gaft/tests/uniform_crossover_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Test case for built-in Uniform Crossover operator. 5 | ''' 6 | 7 | import unittest 8 | 9 | from gaft.components import BinaryIndividual 10 | from gaft.operators.crossover.uniform_crossover import UniformCrossover 11 | 12 | class UniformCrossoverTest(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.maxDiff 16 | 17 | def test_cross(self): 18 | ''' Make sure individuals can be crossed correctly. 19 | ''' 20 | father = BinaryIndividual(ranges=[(0, 1)]).init(solution=[0.398]) 21 | mother = BinaryIndividual(ranges=[(0, 1)]).init(solution=[0.298]) 22 | crossover = UniformCrossover(pc=1.0, pe=0.5) 23 | child1, child2 = crossover.cross(father, mother) 24 | 25 | if '__main__' == __name__: 26 | suite = unittest.TestLoader().loadTestsFromTestCase(UniformCrossoverTest) 27 | unittest.TextTestRunner(verbosity=2).run(suite) 28 | 29 | -------------------------------------------------------------------------------- /gasol/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMake script to build GASol 2 | 3 | cmake_minimum_required(VERSION 3.0) 4 | 5 | project(GASol) 6 | 7 | # Enable verbose output. 8 | #set(CMAKE_VERBOSE_MAKEFILE ON) 9 | 10 | # Use C++11 standard. 11 | set(CMAKE_CXX_STANDARD 11) 12 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 13 | set(CMAKE_CXX_EXTENSIONS OFF) 14 | 15 | # Find OpenMP. 16 | find_package(OpenMP) 17 | if (OPENMP_FOUND) 18 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS} -fPIC -Wall -Wextra -Werror -pedantic") 19 | else() 20 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wall -Wextra -Werror -pedantic") 21 | endif() 22 | 23 | if (COV) 24 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage") 25 | endif() 26 | 27 | # MPI. 28 | if (MPI) 29 | message(STATUS "-- This is a parallel MPI build --") 30 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MPI_CXX_COMPILER_FLAGS}") 31 | include_directories(${MPI_CXX_INCLUDE_DIRS}) 32 | file(WRITE ${PROJECT_SOURCE_DIR}/src/mpiflag.h "#define RUNMPI true\n") 33 | else() 34 | message(STATUS "-- This is a serial build --") 35 | file(WRITE ${PROJECT_SOURCE_DIR}/src/mpiflag.h "#define RUNMPI false\n") 36 | endif() 37 | 38 | # GASol source dir. 39 | add_subdirectory(src) 40 | 41 | # GASol test dir. 42 | add_subdirectory(unittest) 43 | 44 | # GASol run dir. 45 | add_subdirectory(example) 46 | 47 | -------------------------------------------------------------------------------- /gasol/README.md: -------------------------------------------------------------------------------- 1 | # GASol 2 | 3 | A general **G**enetic **A**lgorithm **Sol**ver in C++ 4 | 5 | ## Build 6 | 7 | #### Clone GASol 8 | 9 | ``` shell 10 | git clone --recursive git@github.com:PytLab/GASol.git 11 | ``` 12 | 13 | #### Create dir for building. 14 | 15 | ``` shell 16 | cd GASol 17 | mkdir build 18 | cd build 19 | ``` 20 | #### Build GASol 21 | 22 | - Serial version 23 | ``` shell 24 | cmake .. 25 | make 26 | ``` 27 | 28 | - MPI parallel version 29 | ``` 30 | export CXX=//mpicxx 31 | cmake -DMPI=true .. 32 | make 33 | ``` 34 | 35 | #### Run test 36 | ``` shell 37 | make unittest 38 | ./unittest/unittest 39 | ``` 40 | #### Run example 41 | 42 | ``` shell 43 | make example 44 | ./example/example 45 | ``` 46 | 47 | ## Quick start 48 | 49 | #### Find the maxima of f(x) = x + 10sin(5x) + 7cos(4x) 50 | 51 | ``` cpp 52 | #include "mpiutils.h" 53 | #include "engine.h" 54 | 55 | #include 56 | #include 57 | #include 58 | 59 | using namespace gasol; 60 | 61 | // Define fitness function. 62 | double fitness(const Individual & indv) 63 | { 64 | double x = indv.solution()[0]; 65 | return x + 10*std::sin(5*x) + 7*std::cos(4*x); 66 | } 67 | 68 | int main() 69 | { 70 | // Initialize MPI environment. 71 | MPIUtils::init(); 72 | // Variable range. 73 | std::vector> ranges {{0.0, 10.0}}; 74 | // Decrete precision. 75 | std::vector precisions {0.001}; 76 | 77 | // Create population. 78 | size_t size = 50; 79 | std::vector individuals; 80 | for (size_t i = 0; i < size; i++) 81 | { 82 | gasol::Individual indv(ranges, precisions); 83 | individuals.push_back(indv); 84 | } 85 | Population population(individuals, &fitness); 86 | 87 | // Genetic operators. 88 | RouletteWheelSelection selection; 89 | UniformCrossover crossover(0.8, 0.5); 90 | FlipBitMutation mutation(0.1); 91 | 92 | // Create engine. 93 | Engine engine(population, selection, crossover, mutation); 94 | 95 | // Run 1000 generations. 96 | engine.run(1000); 97 | 98 | // Finalize MPI env. 99 | MPIUtils::finalize(); 100 | 101 | return 0; 102 | } 103 | ``` 104 | 105 | -------------------------------------------------------------------------------- /gasol/example/1d_search.cpp: -------------------------------------------------------------------------------- 1 | /*! \brief GASol example for searching the maxima of f(x) = x + 10sin(5x) + 7cos(4x) 2 | */ 3 | 4 | #include "mpiutils.h" 5 | #include "engine.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace gasol; 14 | 15 | // Define fitness function. 16 | double fitness(const Individual & indv) 17 | { 18 | double x = indv.solution()[0]; 19 | return x + 10*std::sin(5*x) + 7*std::cos(4*x); 20 | } 21 | 22 | int main() 23 | { 24 | MPIUtils::init(); 25 | if (MPIUtils::isMaster()) 26 | { 27 | std::cout << "Process number: " << MPIUtils::size() << std::endl; 28 | } 29 | // Variable range. 30 | std::vector> ranges {{0.0, 100.0}}; 31 | // Decrete precision. 32 | std::vector precisions {0.001}; 33 | 34 | // Create population. 35 | size_t size = 1000; 36 | std::vector individuals; 37 | for (size_t i = 0; i < size; i++) 38 | { 39 | gasol::Individual indv(ranges, precisions); 40 | individuals.push_back(indv); 41 | } 42 | Population population(individuals, &fitness); 43 | 44 | // Genetic operators. 45 | RouletteWheelSelection selection; 46 | UniformCrossover crossover(0.8, 0.5); 47 | FlipBitMutation mutation(0.1); 48 | 49 | // Create engine. 50 | Engine engine(population, selection, crossover, mutation); 51 | 52 | // Run 100 generations. 53 | time_t start = time(NULL); 54 | engine.run(100); 55 | time_t end = time(NULL); 56 | 57 | const Individual & best_indv = engine.population().bestIndv(); 58 | double best_fitness = engine.population().fitness()(best_indv); 59 | double solution = best_indv.solution()[0]; 60 | 61 | if (MPIUtils::isMaster()) 62 | { 63 | std::cout << "Solution: " << solution << ", fitness: " << best_fitness << std::endl; 64 | std::cout << "Time used: " << end - start << "s" << std::endl; 65 | } 66 | 67 | MPIUtils::finalize(); 68 | 69 | return 0; 70 | } 71 | 72 | -------------------------------------------------------------------------------- /gasol/example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMake script for gasol run executable program building. 2 | 3 | include_directories(${PROJECT_SOURCE_DIR}/src) 4 | link_directories(${CMAKE_BINARY_DIR}/../src/) 5 | file(GLOB RUN_SRC *.cpp) 6 | add_executable(example EXCLUDE_FROM_ALL ${RUN_SRC}) 7 | target_link_libraries(example GASol) 8 | 9 | -------------------------------------------------------------------------------- /gasol/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB GASol_SRC *.cpp) 2 | add_library(GASol STATIC ${GASol_SRC}) 3 | 4 | -------------------------------------------------------------------------------- /gasol/src/crossover.cpp: -------------------------------------------------------------------------------- 1 | /*! \brief Implementations of uniform crossover operator. 2 | */ 3 | 4 | #include "individual.h" 5 | #include "crossover.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace gasol { 11 | 12 | //-------------------------------------------------------------------------- 13 | // 14 | std::pair UniformCrossover::cross(Parents & parents) const 15 | { 16 | const Individual *father = parents.first; 17 | const Individual *mother = parents.second; 18 | 19 | // Children to return. 20 | Individual child1(*father); 21 | Individual child2(*mother); 22 | 23 | // Create a random number generator. 24 | std::mt19937 gen(seed()); 25 | std::uniform_real_distribution dis(0.0, 1.0); 26 | 27 | double rnd = dis(gen); 28 | if (rnd > pc()) 29 | { 30 | return std::pair(child1, child2); 31 | } 32 | else 33 | { 34 | for (size_t i = 0; i < child1.chromsome().size(); i++) 35 | { 36 | if (child1.chromsome()[i] != child2.chromsome()[i]) 37 | { 38 | child1.flipGeneBit(i); 39 | child2.flipGeneBit(i); 40 | } 41 | } 42 | return std::pair(child1, child2); 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /gasol/src/crossover.h: -------------------------------------------------------------------------------- 1 | /*! brief File for crossover operators definitions. 2 | */ 3 | 4 | #ifndef __CROSSOVER__ 5 | #define __CROSSOVER__ 6 | 7 | #include 8 | #include 9 | 10 | namespace gasol { 11 | 12 | // Forward declarations. 13 | class Individual; 14 | 15 | // Type alias. 16 | using Parents = std::pair; 17 | 18 | /*! \brief Abstract base class for crossover operator. 19 | */ 20 | class Crossover { 21 | public: 22 | /* \brief Constructor. 23 | * \param pc: Crossover probability. 24 | * \param seed: Random seed for the crossover operator. If seed < 0, then 25 | * the random function would be used to generate a seed. 26 | */ 27 | Crossover(double pc, int seed = -1) : pc_(pc) 28 | { 29 | if (seed < 0) 30 | { 31 | std::random_device rd; 32 | seed_ = rd(); 33 | } 34 | else 35 | { 36 | seed_ = seed; 37 | } 38 | } 39 | 40 | /*! \brief Pure virtual funciton for crossover operation on parents. 41 | * \param parents: A pair of Individual pointers pointing to parents. 42 | */ 43 | virtual std::pair cross(Parents & parents) const = 0; 44 | 45 | /*! \brief Query function for random seed. 46 | */ 47 | int seed() const { return seed_; } 48 | 49 | /*! \brief Query function for crossover probability. 50 | */ 51 | int pc() const { return pc_; } 52 | 53 | protected: 54 | 55 | private: 56 | /// Random seed. 57 | int seed_; 58 | 59 | /// The probability of crossover. 60 | double pc_; 61 | }; 62 | 63 | 64 | /* \brief Crossover operator implementation using uniform crossover algorithm. 65 | * https://en.wikipedia.org/wiki/Crossover_(genetic_algorithm) 66 | */ 67 | class UniformCrossover : public Crossover { 68 | 69 | public: 70 | /* \brief Constructor for uniform crossover operator. 71 | * \param pc: The crossover probability, usually between 0.25 ~ 1.0 72 | * \param pe: The gene exchange probability. 73 | */ 74 | UniformCrossover(double pc, double pe = 0.5, int seed = -1) : 75 | Crossover(pc, seed), 76 | pe_(pe) 77 | {} 78 | 79 | /*! \brief Crossover operation. 80 | * \param parents: A pair of individual pointers as parent. 81 | */ 82 | std::pair cross(Parents & parents) const; 83 | 84 | /*! \brief Query function for gene exchange probability. 85 | */ 86 | double pe() const { return pe_; } 87 | 88 | private: 89 | /// The probability of gene exchanging. 90 | double pe_; 91 | }; 92 | } 93 | 94 | #endif // __CROSSOVER__ 95 | 96 | -------------------------------------------------------------------------------- /gasol/src/engine.cpp: -------------------------------------------------------------------------------- 1 | /*! \file engine.cpp 2 | * \brief Implementations for genetic algorithm engine. 3 | */ 4 | 5 | #include "mpiutils.h" 6 | #include "engine.h" 7 | 8 | #include 9 | 10 | namespace gasol { 11 | 12 | //-------------------------------------------------------------------------- 13 | // 14 | double ** Engine::__getSolutionMatrix(int nrows, int ncols) 15 | { 16 | double * data = new double[nrows*ncols](); 17 | double ** matrix = new double*[nrows]; 18 | 19 | for (int i = 0; i < nrows; i++) 20 | { 21 | matrix[i] = &(data[ncols*i]); 22 | } 23 | 24 | return matrix; 25 | } 26 | 27 | //-------------------------------------------------------------------------- 28 | // 29 | void Engine::run(int ng) 30 | { 31 | // Individuals collector in genetic algorithm generation. 32 | const Individual & indv_template = population_.indvs()[0]; 33 | std::vector individuals(population_.size(), indv_template); 34 | 35 | for (int g = 0; g < ng; g++) 36 | { 37 | #if RUNMPI == true 38 | // Initialize solution component vector for MPI passing. 39 | int nrows = population_.size(); 40 | int ncols = indv_template.solution().size(); 41 | double **local_solutions = __getSolutionMatrix(nrows, ncols); 42 | double **global_solutions = __getSolutionMatrix(nrows, ncols); 43 | 44 | // Endpoints for population size spliting. 45 | std::pair && endpts = MPIUtils::splitOverProcesses(population_.size()/2, 46 | MPI_COMM_WORLD); 47 | #else 48 | std::pair endpts(0, population_.size()/2); 49 | #endif 50 | #pragma omp parallel for schedule(static) 51 | for (int idx = endpts.first; idx < endpts.second; idx++) 52 | { 53 | // Indices for individuals in new individual list. 54 | int i = 2*idx, j = 2*idx + 1; 55 | 56 | // Select father and mother. 57 | Parents parents = selection_.select(population_); 58 | 59 | // Crossover. 60 | std::pair && children = crossover_.cross(parents); 61 | 62 | // Mutation. 63 | mutation_.mutate(children.first); 64 | mutation_.mutate(children.second); 65 | 66 | // Add to individuals. 67 | individuals[i] = children.first; 68 | individuals[j] = children.second; 69 | } 70 | #if RUNMPI == true 71 | // Fill the local solutions. 72 | int start = 2*endpts.first; 73 | int end = 2*endpts.second; 74 | for (int i = start; i < end; i++) 75 | { 76 | for (int j = 0; j < ncols; j++) 77 | { 78 | local_solutions[i][j] = individuals[i].solution()[j]; 79 | } 80 | } 81 | 82 | // Merge solutions from all processes. 83 | MPIUtils::joinOverProcesses(local_solutions, 84 | global_solutions, 85 | nrows, ncols, 86 | MPI_COMM_WORLD); 87 | 88 | // Initialize new individuals from gathered solutions. 89 | std::vector solution(ncols, 0.0); 90 | for (int i = 0; i < nrows; i++) 91 | { 92 | for (int j = 0; j < ncols; j++) 93 | { 94 | solution[j] = global_solutions[i][j]; 95 | } 96 | individuals[i] = Individual(solution, 97 | indv_template.ranges(), 98 | indv_template.precisions()); 99 | } 100 | 101 | // Free memories. 102 | delete [] *local_solutions; 103 | delete [] local_solutions; 104 | delete [] *global_solutions; 105 | delete [] global_solutions; 106 | #endif 107 | // Reserve the best indv. 108 | individuals[0] = population_.bestIndv(); 109 | 110 | // Update population. 111 | population_.updateIndividuals(individuals); 112 | } 113 | } 114 | } 115 | 116 | -------------------------------------------------------------------------------- /gasol/src/engine.h: -------------------------------------------------------------------------------- 1 | /*! \file engine.h 2 | * \brief File for GA engine definition. 3 | */ 4 | 5 | #ifndef __ENGINE__ 6 | #define __ENGINE__ 7 | 8 | #include "population.h" 9 | #include "individual.h" 10 | #include "selection.h" 11 | #include "crossover.h" 12 | #include "mutation.h" 13 | 14 | namespace gasol { 15 | 16 | /*! \brief The Genetic Algorithm engine class is the central object in GASol 17 | * for running a genetic algorithm optimization. Once the poputlation 18 | * with individuals and a set of genetic operators are setup, the engine 19 | * unites them all and provides means for runing genetic algorithm 20 | * optimization. 21 | */ 22 | class Engine { 23 | 24 | public: 25 | /*! \brief Constructor. 26 | * \param population: Population object contains different individuals. 27 | * \param selection: The selection operator to select two candidates for 28 | * producing offsprings. 29 | * \param crossover: The crossover operator to cross two selected individuals. 30 | * \param mutation: The mutation operator to make an individual mutate. 31 | */ 32 | Engine(Population & population, Selection & selection, 33 | Crossover & crossover, Mutation & mutation) : 34 | population_(population), 35 | selection_(selection), 36 | crossover_(crossover), 37 | mutation_(mutation) 38 | {} 39 | 40 | /*! \brief Bootup the engine to run genetic algorithm optimization. 41 | * \param ng: Generation number for genetic algorithm iteration. 42 | */ 43 | void run(int ng = 100); 44 | 45 | /*! \brief Query function for population. 46 | */ 47 | Population & population() { return population_; } 48 | 49 | /*! \brief Query function for selection operator. 50 | */ 51 | const Selection & selection() const { return selection_; } 52 | 53 | /*! \brief Query function for crossover operator. 54 | */ 55 | const Crossover & crossover() const { return crossover_; } 56 | 57 | /*! \brief Query function for crossover operator. 58 | */ 59 | const Mutation & mutation() const { return mutation_; } 60 | 61 | protected: 62 | 63 | private: 64 | /// Population in genetic algorithm engine. 65 | Population & population_; 66 | 67 | /// Selection operator. 68 | Selection & selection_; 69 | 70 | /// Crossover operator. 71 | Crossover & crossover_; 72 | 73 | /// Mutation operator. 74 | Mutation & mutation_; 75 | 76 | /// Private helper function to create a 2D solution matrix which is passed 77 | /// within MPI communicator. 78 | double ** __getSolutionMatrix(int nrows, int ncols); 79 | 80 | }; 81 | } 82 | 83 | #endif // __ENGINE__ 84 | 85 | -------------------------------------------------------------------------------- /gasol/src/individual.cpp: -------------------------------------------------------------------------------- 1 | /*! \file individual.cpp 2 | * \brief File for implementations of GAIndividual class. 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "individual.h" 10 | 11 | namespace gasol { 12 | //-------------------------------------------------------------------------- 13 | // 14 | Individual::Individual(std::vector & solution, 15 | const RangePairs & ranges, 16 | const std::vector & precisions) : 17 | ori_solution_(solution), 18 | solution_(solution.size(), 0.0), 19 | ranges_(ranges), 20 | ori_precisions_(precisions) 21 | { 22 | // Calculate lengths of all gene fragments. 23 | __calcGeneLengths(); 24 | // Get actual precisions which is used in GA engine. 25 | __adjustPrecisions(); 26 | // Create chromsome. 27 | __createChromsome(); 28 | // Update solution candiate according to chromsome. 29 | updateSolution(); 30 | } 31 | 32 | //-------------------------------------------------------------------------- 33 | // 34 | Individual::Individual(std::vector & solution, 35 | const std::pair & range, 36 | double precision) : 37 | Individual(solution, 38 | RangePairs(solution.size(), range), 39 | std::vector(solution.size(), precision)) 40 | {} 41 | 42 | //-------------------------------------------------------------------------- 43 | // 44 | Individual::Individual(const RangePairs & ranges, 45 | const std::vector & precisions) : 46 | ori_solution_(ranges.size(), 0.0), 47 | solution_(ranges.size(), 0.0), 48 | ranges_(ranges), 49 | ori_precisions_(precisions) 50 | { 51 | // Generate original solution randomly. 52 | std::random_device rd; 53 | std::mt19937 gen(rd()); 54 | for (size_t i = 0; i < ori_solution_.size(); i++) 55 | { 56 | std::uniform_real_distribution dis(ranges[i].first, ranges[i].second); 57 | ori_solution_[i] = dis(gen); 58 | } 59 | 60 | // Calculate lengths of all gene fragments. 61 | __calcGeneLengths(); 62 | // Get actual precisions which is used in GA engine. 63 | __adjustPrecisions(); 64 | // Create chromsome. 65 | __createChromsome(); 66 | // Update solution candiate according to chromsome. 67 | updateSolution(); 68 | } 69 | 70 | //-------------------------------------------------------------------------- 71 | // 72 | Individual::Individual(size_t ndim, 73 | const std::pair & range, 74 | double precision) : 75 | Individual(RangePairs(ndim, range), 76 | std::vector(ndim, precision)) 77 | {} 78 | 79 | //-------------------------------------------------------------------------- 80 | // 81 | void Individual::__calcGeneLengths() 82 | { 83 | // Function to check an integer is the power of 2. 84 | auto power_of_2 = [](int n) { return !(n & (n - 1)); }; 85 | 86 | // Calculate gene fragment lengths for all component in solution. 87 | auto range_it = ranges_.cbegin(); 88 | auto prec_it = ori_precisions_.cbegin(); 89 | 90 | // Gene fragment index cursor. 91 | int cursor = 0; 92 | 93 | for (; range_it != ranges_.end() && prec_it != ori_precisions_.end(); 94 | range_it++, prec_it++) 95 | { 96 | double span = range_it->second - range_it->first; 97 | 98 | // The number of possible values for this component. 99 | // ***************************************************************** 100 | // NOTE: EXCEPT the start point. For example, if precision is 0.2 101 | // and range is [0.0, 1.0], then the n_info equals to 5 (5 numbers): 102 | // 0.2, 0.4, 0.6, 0.8, 1.0 not include 0.0 103 | // ***************************************************************** 104 | int n_info = (int)(span/(*prec_it)); 105 | 106 | if (!power_of_2(n_info)) 107 | { 108 | precision_loss_ = true; 109 | } 110 | 111 | int length = (int)(std::log2(n_info)); 112 | gene_lengths_.push_back(length); 113 | 114 | // Get gene fragment break points. 115 | int start = cursor; 116 | int end = cursor + length - 1; 117 | cursor += length; 118 | gene_break_pts_.push_back(std::pair(start, end)); 119 | } 120 | } 121 | 122 | //-------------------------------------------------------------------------- 123 | // 124 | void Individual::__adjustPrecisions() 125 | { 126 | if (precision_loss_) 127 | { 128 | auto range_it = ranges_.cbegin(); 129 | auto len_it = gene_lengths_.cbegin(); 130 | 131 | for (; range_it != ranges_.end() && len_it != gene_lengths_.end(); 132 | range_it++, len_it++) 133 | { 134 | // Ajusted precision. 135 | double precision = (range_it->second - range_it->first)/std::pow(2, *len_it); 136 | precisions_.push_back(precision); 137 | } 138 | } 139 | else 140 | { 141 | precisions_ = ori_precisions_; 142 | } 143 | } 144 | 145 | //-------------------------------------------------------------------------- 146 | // 147 | void Individual::__createChromsome() 148 | { 149 | for (size_t i = 0; i < ori_solution_.size(); i++) 150 | { 151 | // Get gene fragment. 152 | double decimal = ori_solution_[i]; 153 | double floor = ranges_[i].first; 154 | double precision = precisions_[i]; 155 | int length = gene_lengths_[i]; 156 | std::vector && gene_fragment = __decToBin(decimal, floor, precision, length); 157 | 158 | // Append new gene fragment to chromsome. 159 | chromsome_.reserve(chromsome_.size() + gene_fragment.size()); 160 | chromsome_.insert(chromsome_.end(), 161 | gene_fragment.begin(), 162 | gene_fragment.end()); 163 | } 164 | } 165 | 166 | //-------------------------------------------------------------------------- 167 | // 168 | std::vector Individual::__decToBin(double decimal, 169 | double floor, 170 | double precision, 171 | int length) const 172 | { 173 | std::vector binary; 174 | 175 | int ncount = (decimal - floor)/precision - 1; 176 | for (int i = length-1; i >= 0; i--) 177 | { 178 | binary.push_back( (ncount >> i) & 1 ); 179 | } 180 | 181 | return binary; 182 | } 183 | 184 | //-------------------------------------------------------------------------- 185 | // 186 | double Individual::__binToDec(const std::vector & binary, 187 | double floor, 188 | double precision, 189 | int length) const 190 | { 191 | int ncount = 0; 192 | for (int i = 0; i < length; i++) 193 | { 194 | int idx = length - i - 1; 195 | ncount += int(binary[idx])*std::pow(2, i); 196 | } 197 | 198 | return floor + precision*(ncount + 1); 199 | } 200 | 201 | //-------------------------------------------------------------------------- 202 | // 203 | void Individual::updateSolution() 204 | { 205 | for (size_t i = 0; i < solution_.size(); i++) 206 | { 207 | // Get gene fragment. 208 | int start = gene_break_pts_[i].first; 209 | int end = gene_break_pts_[i].second; 210 | std::vector gene_fragment; 211 | for (int j = start; j <= end; j++) 212 | { 213 | gene_fragment.push_back(chromsome_[j]); 214 | } 215 | 216 | // Convert to decimal number. 217 | double floor = ranges_[i].first; 218 | double precision = precisions_[i]; 219 | int length = gene_lengths_[i]; 220 | 221 | double component = __binToDec(gene_fragment, floor, precision, length); 222 | 223 | // Update. 224 | solution_[i] = component; 225 | } 226 | } 227 | } // namespace gasol 228 | 229 | -------------------------------------------------------------------------------- /gasol/src/individual.h: -------------------------------------------------------------------------------- 1 | /*! \file individual.h 2 | * \brief File for the GAIndividual class definition 3 | */ 4 | 5 | #ifndef __INDIVIDUAL__ 6 | #define __INDIVIDUAL__ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace gasol { 13 | 14 | // Type alias. 15 | using RangePairs = std::vector>; 16 | using GeneBreakPts = std::vector>; 17 | 18 | /*! \brief Class for defining an individual in genetic algorithm engine. 19 | */ 20 | class Individual { 21 | 22 | public: 23 | /*! \brief Constructor for the genetic algorithm individual. 24 | * 25 | * \param solution: A possible solution vector in the solution space 26 | * where genetic algorithm runs. 27 | * \param ranges: The value ranges for all components in solution candidate vector. 28 | * \param precisions: The discrete precisions for all components in solution 29 | * candidate vector 30 | */ 31 | Individual(std::vector & solution, 32 | const RangePairs & ranges, 33 | const std::vector & precisions); 34 | 35 | /*! \brief Another constructor for the genetic algorithm individual. 36 | * 37 | * \param solution: A possible solution vector in the solution space 38 | * where genetic algorithm runs. 39 | * \param range: The value range for all components in solution candidate vector. 40 | * \param precision: The discrete precision for all components in solution 41 | * candidate vector 42 | */ 43 | Individual(std::vector & solution, 44 | const std::pair & range, 45 | double precision); 46 | 47 | /*! \brief Constructor without solution provided explicitly (solution is 48 | * generated randomly). 49 | * \param ranges: The value ranges for all components in solution candidate vector. 50 | * \param precisions: The discrete precisions for all components in solution 51 | * candidate vector 52 | */ 53 | Individual(const RangePairs & ranges, 54 | const std::vector & precisions); 55 | 56 | /*! \brief Another constructor without solution provided explicitly 57 | * (solution is generated randomly). 58 | * \param ndim: The dimension number of solution vector. 59 | * \param range: The value range for all components in solution candidate vector. 60 | * \param precision: The discrete precision for all components in solution 61 | * candidate vector 62 | */ 63 | Individual(size_t ndim, 64 | const std::pair & range, 65 | double precision); 66 | 67 | /*! \brief Flip a bit in chromsome bit sequence. 68 | */ 69 | void flipGeneBit(int index) 70 | { chromsome_.at(index) = !chromsome_.at(index); } 71 | 72 | /*! \brief Helper funciton to update solution vector according to chromsome. 73 | */ 74 | void updateSolution(); 75 | 76 | /*! \brief Query function for original solution candidate. 77 | */ 78 | const std::vector & oriSolution() const 79 | { return ori_solution_; } 80 | 81 | /*! \brief Query function for solution candidate. 82 | */ 83 | const std::vector & solution() const { return solution_; } 84 | 85 | /*! \brief Query function for ranges. 86 | */ 87 | const RangePairs & ranges() const 88 | { return ranges_; } 89 | 90 | /*! \brief Query function for original discrete precisions. 91 | */ 92 | const std::vector & originalPrecisions() const 93 | { return ori_precisions_; } 94 | 95 | /*! \brief Query function for precision loss flag. 96 | */ 97 | bool precisionLoss() const 98 | { return precision_loss_; } 99 | 100 | /*! \brief Query function for gene lengths. 101 | */ 102 | const std::vector & geneLengths() const 103 | { return gene_lengths_; } 104 | 105 | /*! \brief Query function for gene fragment break points. 106 | */ 107 | const GeneBreakPts & geneBreakPts() const 108 | { return gene_break_pts_; } 109 | 110 | /*! \brief Query function for discrete precisions. 111 | */ 112 | const std::vector & precisions() const 113 | { return precisions_; } 114 | 115 | /*! \brief Query function for chromsome sequence. 116 | */ 117 | const std::vector & chromsome() const 118 | { return chromsome_; } 119 | 120 | protected: 121 | 122 | private: 123 | /// Original solution candidate provided by user. 124 | std::vector ori_solution_; 125 | 126 | /// Solution candidate vector. 127 | std::vector solution_; 128 | 129 | /// Ranges for all components in solution vector. 130 | RangePairs ranges_; 131 | 132 | /// Original discrete precisions for all components in solution. 133 | std::vector ori_precisions_; 134 | 135 | /// Actual dsicrete precisions used in GA engine. 136 | std::vector precisions_; 137 | 138 | /// Lengths of gene fragments. 139 | std::vector gene_lengths_; 140 | 141 | /// The break points in gene sequence. 142 | GeneBreakPts gene_break_pts_; 143 | 144 | /// The chromsome contains gene sequence. 145 | std::vector chromsome_; 146 | 147 | /// Flag for precision loss. 148 | bool precision_loss_ = false; 149 | 150 | // --------------------------------------------------------------------- 151 | // Private functions 152 | // --------------------------------------------------------------------- 153 | 154 | /*! \brief Helper function to calculate gene fragment lengths for all 155 | * components in solution. 156 | */ 157 | void __calcGeneLengths(); 158 | 159 | /*! \brief Helper function to adjust discrete precisions according to 160 | * user-provided precisions and ranges. 161 | */ 162 | void __adjustPrecisions(); 163 | 164 | /*! \brief Helper function to create chromsome. 165 | */ 166 | void __createChromsome(); 167 | 168 | /*! \brief Function to convert a decimal number to binary one. 169 | * \param decimal: The decimal number to be converted. 170 | * \param floor: The floor of this decimal number. 171 | * \param precision: Precision for this component. 172 | * \param length: The length of the gene fragement. 173 | */ 174 | std::vector __decToBin(double decimal, 175 | double floor, 176 | double precision, 177 | int length) const; 178 | 179 | /*! \brief Function to convert a binary number to decimal number. 180 | * \param binary: The binary number to be converted. 181 | * \param floor: The floor of this component. 182 | * \param precision: Precision for this component. 183 | * \param length: The length of the gene fragement. 184 | */ 185 | double __binToDec(const std::vector & binary, 186 | double floor, 187 | double precision, 188 | int length) const; 189 | }; 190 | 191 | } 192 | 193 | #endif // __INDIVIDUAL__ 194 | 195 | -------------------------------------------------------------------------------- /gasol/src/mpih.h: -------------------------------------------------------------------------------- 1 | /*! \file mpih.h 2 | * \brief Common MPI header wrapper for serial or parallel builds. 3 | */ 4 | 5 | 6 | #ifndef __MPIH__ 7 | #define __MPIH__ 8 | 9 | 10 | // NOTE: The RUNMPI flag is set from CMake. It is defined in the 11 | // CMake generated file mpiflag.h 12 | #include "mpiflag.h" 13 | 14 | 15 | #if RUNMPI == true 16 | #include 17 | #else 18 | using MPI_Comm = int; 19 | #define MPI_COMM_WORLD 0 20 | #endif // RUNMPI 21 | 22 | #endif // __MPIH__ 23 | 24 | -------------------------------------------------------------------------------- /gasol/src/mpiutils.cpp: -------------------------------------------------------------------------------- 1 | /*! \file mpiutils.cpp 2 | * \brief File for the implementation code of the common MPI routines. 3 | */ 4 | 5 | 6 | #include "mpiutils.h" 7 | #include 8 | 9 | namespace gasol { 10 | 11 | // ----------------------------------------------------------------------------- 12 | // 13 | void MPIUtils::init() 14 | { 15 | if (initialized()) 16 | { 17 | return; 18 | } 19 | 20 | #if RUNMPI == true 21 | int argc = 0; 22 | char **argv; 23 | MPI_Init(&argc, &argv); 24 | #endif 25 | } 26 | 27 | 28 | // ----------------------------------------------------------------------------- 29 | // 30 | bool MPIUtils::finalized() 31 | { 32 | #if RUNMPI == true 33 | int flag = 0; 34 | return MPI_Finalized(&flag); 35 | #else 36 | return true; 37 | #endif 38 | } 39 | 40 | 41 | // ----------------------------------------------------------------------------- 42 | // 43 | bool MPIUtils::initialized() 44 | { 45 | #if RUNMPI == true 46 | int flag = 0; 47 | return MPI_Initialized(&flag); 48 | #else 49 | return false; 50 | #endif 51 | } 52 | 53 | 54 | // ----------------------------------------------------------------------------- 55 | // 56 | void MPIUtils::finalize() 57 | { 58 | if (finalized()) 59 | { 60 | return; 61 | } 62 | 63 | #if RUNMPI == true 64 | MPI_Finalize(); 65 | #endif 66 | } 67 | 68 | 69 | // ----------------------------------------------------------------------------- 70 | // 71 | int MPIUtils::myRank(MPI_Comm comm) 72 | { 73 | #if RUNMPI == true 74 | int rank; 75 | MPI_Comm_rank(comm, &rank); 76 | return rank; 77 | #else 78 | return comm = 0; 79 | #endif 80 | } 81 | 82 | 83 | // ----------------------------------------------------------------------------- 84 | // 85 | int MPIUtils::size(MPI_Comm comm) 86 | { 87 | #if RUNMPI == true 88 | int size; 89 | MPI_Comm_size(comm, &size); 90 | return size; 91 | #else 92 | return comm = 1; 93 | #endif 94 | } 95 | 96 | 97 | // ----------------------------------------------------------------------------- 98 | // 99 | void MPIUtils::barrier(MPI_Comm comm) 100 | { 101 | #if RUNMPI == true 102 | MPI_Barrier(comm); 103 | #else 104 | comm += 0; 105 | #endif 106 | } 107 | 108 | 109 | // ----------------------------------------------------------------------------- 110 | // 111 | std::pair MPIUtils::splitOverProcesses(int n, MPI_Comm comm) 112 | { 113 | int start = 0, end = n; 114 | 115 | #if RUNMPI == true 116 | int nprocs = size(comm); 117 | int rank = myRank(comm); 118 | int residual = n % nprocs; 119 | std::vector chunk_sizes(nprocs, n/nprocs); 120 | 121 | for (int i = 0; i < residual; i++) 122 | { 123 | chunk_sizes[i] += 1; 124 | } 125 | 126 | for (int i = 0; i < rank; i++) 127 | { 128 | start += chunk_sizes[i]; 129 | } 130 | end = start + chunk_sizes[rank]; 131 | #else 132 | comm += 0; 133 | #endif 134 | 135 | return std::pair(start, end); 136 | } 137 | 138 | void MPIUtils::joinOverProcesses(double **send, 139 | double **recv, 140 | int nrows, 141 | int ncols, 142 | MPI_Comm comm) 143 | { 144 | #if RUNMPI == true 145 | MPI_Allreduce(*send, *recv, nrows*ncols, MPI_DOUBLE, MPI_SUM, comm); 146 | #else 147 | comm += 0; 148 | for (int i = 0; i < nrows*ncols; i++) 149 | { 150 | (*recv)[i] = (*send)[i]; 151 | } 152 | #endif 153 | } 154 | 155 | } 156 | 157 | -------------------------------------------------------------------------------- /gasol/src/mpiutils.h: -------------------------------------------------------------------------------- 1 | /*! \file mpiutils.h 2 | * \brief Intrerfaces of the common MPI routines. 3 | */ 4 | 5 | 6 | #ifndef __MPIUTILS__ 7 | #define __MPIUTILS__ 8 | 9 | #include "mpih.h" 10 | 11 | #include 12 | 13 | namespace gasol { 14 | /// Struct for handling MPI functions to be wrapped. 15 | struct MPIUtils { 16 | 17 | /*! \brief Wrapps MPI_INIT 18 | */ 19 | static void init(); 20 | 21 | /*! \brief Wrap MPI::Is_initialized 22 | */ 23 | static bool initialized(); 24 | 25 | /*! \brief Wrapps MPI_FINALIZE 26 | */ 27 | static void finalize(); 28 | 29 | /*! \brief Wrap MPI::Is_finialized 30 | */ 31 | static bool finalized(); 32 | 33 | /*! \brief Wrapps MPI_COMM_RANK 34 | * \param comm: The communicator to use. 35 | * \return: The rank of this process withing the given communicator. 36 | */ 37 | static int myRank(MPI_Comm comm = MPI_COMM_WORLD); 38 | 39 | /*! \brief Wrapps MPI_COMM_SIZE 40 | * \param comm: The communicator to use. 41 | * \return: The sise of the communicator (the total number of processes). 42 | */ 43 | static int size(MPI_Comm comm = MPI_COMM_WORLD); 44 | 45 | /*! \brief Wrapps MPI_BARRIER, syncronizing processes. 46 | * \param comm: The communicator to use. 47 | */ 48 | static void barrier(MPI_Comm comm = MPI_COMM_WORLD); 49 | 50 | /*! \brief Returns true if the calling process is the master. 51 | * \param comm: The communicator to use. 52 | */ 53 | static bool isMaster(MPI_Comm comm = MPI_COMM_WORLD) 54 | { return (myRank(comm) == 0); } 55 | 56 | /*! \brief Split a size to different chunks over all processes in communicator. 57 | * \param size: Size to be splitted. 58 | * \param comm: The communicator to use. 59 | */ 60 | static std::pair splitOverProcesses(int size, 61 | MPI_Comm comm = MPI_COMM_WORLD); 62 | 63 | /*! \brief Join all solutions together from all processes in communicator. 64 | * \param send: local data in each process. 65 | * \param recv: global data in each process. 66 | * \param nrows: column number, population size. 67 | * \param ncols: row number, solution dimension. 68 | * \param comm: The communicator to use. 69 | */ 70 | static void joinOverProcesses(double **send, 71 | double **recv, 72 | int nrows, 73 | int ncols, 74 | MPI_Comm comm = MPI_COMM_WORLD); 75 | 76 | }; 77 | 78 | } 79 | 80 | #endif // __MPIUTILS__ 81 | 82 | -------------------------------------------------------------------------------- /gasol/src/mutation.cpp: -------------------------------------------------------------------------------- 1 | /*! brief File for implementations of mutation operators. 2 | */ 3 | 4 | #include "mutation.h" 5 | #include "individual.h" 6 | 7 | #include 8 | 9 | namespace gasol { 10 | 11 | //-------------------------------------------------------------------------- 12 | // 13 | void FlipBitMutation::mutate(Individual & indv) const 14 | { 15 | // Create a random number generator. 16 | std::mt19937 gen(seed()); 17 | std::uniform_real_distribution dis(0.0, 1.0); 18 | 19 | if (dis(gen) < pm()) 20 | { 21 | for (size_t i = 0; i < indv.chromsome().size(); i++) 22 | { 23 | if (dis(gen) < pm()) 24 | { 25 | indv.flipGeneBit(i); 26 | } 27 | } 28 | 29 | // Update solution vector. 30 | indv.updateSolution(); 31 | } 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /gasol/src/mutation.h: -------------------------------------------------------------------------------- 1 | /*! \brief File for mutation operators definition. 2 | */ 3 | 4 | #ifndef __MUTATION__ 5 | #define __MUTATION__ 6 | 7 | #include 8 | 9 | namespace gasol { 10 | 11 | // Forward declarations. 12 | class Individual; 13 | 14 | /*! brief Abstract base class for mutation operator. 15 | */ 16 | class Mutation { 17 | 18 | public: 19 | /*! \brief Constructor. 20 | * \param pm: The mutation probability. 21 | * \param seed: Random seed for the crossover operator. If seed < 0, then 22 | * the random function would be used to generate a seed. 23 | */ 24 | Mutation(double pm, int seed = -1) : pm_(pm) 25 | { 26 | if (seed < 0) 27 | { 28 | std::random_device rd; 29 | seed_ = rd(); 30 | } 31 | else 32 | { 33 | seed_ = seed; 34 | } 35 | } 36 | 37 | /*! \brief A pure virtual function to mutate an individual. 38 | * \param indv: An individual to be mutated. 39 | */ 40 | virtual void mutate(Individual & indv) const = 0; 41 | 42 | /*! \brief Query function for random seed. 43 | */ 44 | int seed() const { return seed_; } 45 | 46 | /*! \brief Query function for mutation probability. 47 | */ 48 | int pm() const { return pm_; } 49 | 50 | protected: 51 | 52 | private: 53 | /// Ranom seed. 54 | int seed_; 55 | 56 | /// Mutation probability. 57 | double pm_; 58 | }; 59 | 60 | 61 | /*! \brief Mutation operator with flip bit mutation implementation. 62 | */ 63 | class FlipBitMutation : public Mutation { 64 | 65 | public: 66 | /*! \brief Constructor. 67 | */ 68 | FlipBitMutation(double pm, int seed = -1) : Mutation(pm, seed) {} 69 | 70 | /*! \brief Mutate an individual with flip bit method. 71 | */ 72 | void mutate(Individual & indv) const; 73 | 74 | protected: 75 | 76 | private: 77 | 78 | }; 79 | } 80 | 81 | #endif 82 | 83 | -------------------------------------------------------------------------------- /gasol/src/population.cpp: -------------------------------------------------------------------------------- 1 | /*! \file population.cpp 2 | * \brief File for implementation of Population class. 3 | */ 4 | 5 | #include "population.h" 6 | #include "individual.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace gasol { 12 | 13 | //-------------------------------------------------------------------------- 14 | // 15 | Population::Population(std::vector & individuals, Fitness *pfit) : 16 | indvs_(individuals), 17 | pfit_(pfit), 18 | size_(indvs_.size()) 19 | { 20 | // Initialize individual pointers. 21 | for (auto & indv: indvs_) 22 | { 23 | indv_ptrs_.push_back(&indv); 24 | } 25 | } 26 | 27 | //-------------------------------------------------------------------------- 28 | // 29 | Population::Population(const Individual & indv_template, size_t size, Fitness *pfit) 30 | { 31 | for (size_t i = 0; i < size; i++) 32 | { 33 | Individual indv(indv_template.ranges(), indv_template.precisions()); 34 | indvs_.push_back(indv); 35 | indv_ptrs_.push_back(&indv); 36 | } 37 | size_ = size; 38 | pfit_ = pfit; 39 | } 40 | 41 | //-------------------------------------------------------------------------- 42 | // 43 | void Population::updateIndividuals(const std::vector & indvs) 44 | { 45 | // Update individuals. 46 | auto it1 = indvs_.begin(); 47 | auto it2 = indvs.begin(); 48 | for ( ; it1 != indvs_.cend(); it1++, it2++) 49 | { 50 | *it1 = *it2; 51 | } 52 | 53 | // Reset best and worst individual pointers. 54 | worst_indv_ = best_indv_ = nullptr; 55 | } 56 | 57 | //-------------------------------------------------------------------------- 58 | // 59 | const Individual & Population::bestIndv() 60 | { 61 | if (best_indv_ == nullptr) 62 | { 63 | // comparation function. 64 | auto comp = [this](Individual *indv_ptr1, Individual *indv_ptr2) 65 | { return (*pfit_)(*indv_ptr1) < (*pfit_)(*indv_ptr2); }; 66 | 67 | best_indv_ = *std::max_element(indv_ptrs_.begin(), 68 | indv_ptrs_.end(), 69 | comp); 70 | } 71 | 72 | return *best_indv_; 73 | } 74 | 75 | //-------------------------------------------------------------------------- 76 | // 77 | const Individual & Population::worstIndv() 78 | { 79 | if (worst_indv_ == nullptr) 80 | { 81 | // Comparation function. 82 | auto comp = [this](Individual *indv_ptr1, Individual *indv_ptr2) 83 | { return (*pfit_)(*indv_ptr1) < (*pfit_)(*indv_ptr2); }; 84 | 85 | worst_indv_ = *std::min_element(indv_ptrs_.begin(), 86 | indv_ptrs_.end(), 87 | comp); 88 | } 89 | 90 | return *worst_indv_; 91 | } 92 | 93 | //-------------------------------------------------------------------------- 94 | // 95 | std::vector Population::allFitVals() const 96 | { 97 | std::vector all_fits; 98 | 99 | for (auto it = indv_ptrs_.begin(); it != indv_ptrs_.end(); it++) 100 | { 101 | all_fits.push_back((*pfit_)(**it)); 102 | } 103 | 104 | return all_fits; 105 | } 106 | 107 | } // namespace gasol 108 | 109 | -------------------------------------------------------------------------------- /gasol/src/population.h: -------------------------------------------------------------------------------- 1 | /*! \file population.h 2 | * \brief File for Population class definition. 3 | */ 4 | 5 | #ifndef __POPULATION__ 6 | #define __POPULATION__ 7 | 8 | 9 | #include 10 | #include 11 | 12 | namespace gasol { 13 | 14 | // Forward declaration. 15 | class Individual; 16 | 17 | // Type alias of a fitness function. 18 | using Fitness = double (const Individual &); 19 | 20 | /*! \brief Class for population iterated in genetic algorithm engine. 21 | */ 22 | class Population { 23 | 24 | public: 25 | /*! \brief Constructor for population with multiple individuals. 26 | * \param individuals: List of individuals in population. 27 | * \param pfit: Pointer of fitness function. 28 | */ 29 | Population(std::vector & individuals, Fitness *pfit); 30 | 31 | /*! \brief Constructor with an individual template proviede. 32 | */ 33 | Population(const Individual & indv_template, size_t size, Fitness *pfit); 34 | 35 | /*! \brief Update individuals in population. 36 | * \param indvs: New individuals. 37 | */ 38 | void updateIndividuals(const std::vector & indvs); 39 | 40 | /*! \brief Query function for population size. 41 | */ 42 | int size() const { return size_; } 43 | 44 | /*! \brief Query function for fitness function. 45 | */ 46 | Fitness *fitness() const { return pfit_; } 47 | 48 | /*! \brief Query function for all individuals. 49 | */ 50 | const std::vector & indvs() const { return indvs_; } 51 | 52 | /*! \brief Const query function for individual pointers. 53 | */ 54 | const std::vector & indvPtrs() const { return indv_ptrs_; } 55 | 56 | /*! \brief Query function for individual pointers. 57 | */ 58 | std::vector & indvPtrs() { return indv_ptrs_; } 59 | 60 | /*! \brief Return reference of the individual with max fitness value. 61 | */ 62 | const Individual & bestIndv(); 63 | 64 | /*! \brief Query function for the individual with min fitness value. 65 | */ 66 | const Individual & worstIndv(); 67 | 68 | /*! \brief Get all fitness values of individuals in population. 69 | * NOTE: The order of returned fitness values is the same with that 70 | * of individuals pointer in population. 71 | */ 72 | std::vector allFitVals() const; 73 | 74 | private: 75 | 76 | /// Individuals in population. 77 | std::vector indvs_; 78 | 79 | /// Pointers pointing to all individuals. 80 | std::vector indv_ptrs_; 81 | 82 | /// Fitness function pointer. 83 | Fitness *pfit_ = nullptr; 84 | 85 | /// Population size. 86 | size_t size_; 87 | 88 | /// Pointer of the best individual. 89 | const Individual *best_indv_ = nullptr; 90 | 91 | /// Pointer of the worst individual. 92 | const Individual *worst_indv_ = nullptr; 93 | }; 94 | } 95 | 96 | #endif // __POPULATION__ 97 | 98 | -------------------------------------------------------------------------------- /gasol/src/selection.cpp: -------------------------------------------------------------------------------- 1 | /* File for implementations of selection operators functions. 2 | */ 3 | 4 | #include "individual.h" 5 | #include "population.h" 6 | #include "selection.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace gasol { 12 | 13 | //-------------------------------------------------------------------------- 14 | // 15 | Parents RouletteWheelSelection::select(Population & population) 16 | { 17 | // Create a fitness wheel roulette. 18 | std::vector all_fits = population.allFitVals(); 19 | const Individual & worst_indv = population.worstIndv(); 20 | double min_fit = population.fitness()(worst_indv); 21 | 22 | std::vector wheel; 23 | double accum = 0.0; 24 | for (auto fit: all_fits) 25 | { 26 | accum += (fit - min_fit); 27 | wheel.push_back(accum); 28 | } 29 | 30 | // Create a random number generator. 31 | std::mt19937 gen(seed()); 32 | std::uniform_real_distribution dis(0.0, accum); 33 | 34 | // Select parents. 35 | int father_idx = (std::upper_bound(wheel.cbegin(), 36 | wheel.cend(), 37 | dis(gen)) - wheel.cbegin()) % wheel.size(); 38 | 39 | int mother_idx = (std::upper_bound(wheel.cbegin(), 40 | wheel.cend(), 41 | dis(gen)) - wheel.cbegin()) % wheel.size(); 42 | 43 | return Parents(population.indvPtrs()[father_idx], 44 | population.indvPtrs()[mother_idx]); 45 | } 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /gasol/src/selection.h: -------------------------------------------------------------------------------- 1 | /*! brief File for selection operator classes definitions. 2 | */ 3 | 4 | #ifndef __SELECTION__ 5 | #define __SELECTION__ 6 | 7 | #include 8 | #include 9 | 10 | namespace gasol { 11 | 12 | // Forward declaration. 13 | class Individual; 14 | class Population; 15 | 16 | // Type alias. 17 | using Parents = std::pair; 18 | 19 | /*! \brief Abstract base class for selection operator. 20 | */ 21 | class Selection { 22 | 23 | public: 24 | /* \brief Constructor. 25 | */ 26 | Selection(int seed = -1) 27 | { 28 | if (seed < 0) 29 | { 30 | std::random_device rd; 31 | seed_ = rd(); 32 | } 33 | else 34 | { 35 | seed_ = seed; 36 | } 37 | } 38 | 39 | /*! \brief Pure virtual function for selection operation on populaiton. 40 | * \param population: Population from which parents are choosen. 41 | */ 42 | virtual Parents select(Population & population) = 0; 43 | 44 | /* \brief Query function for random seed. 45 | */ 46 | int seed() const { return seed_; } 47 | 48 | protected: 49 | 50 | private: 51 | /// Random number seed. 52 | int seed_; 53 | }; 54 | 55 | 56 | /*! \brief Selection operator implementation using roulette wheel. 57 | */ 58 | class RouletteWheelSelection : public Selection { 59 | 60 | public: 61 | /*! \brief Constructor for roulette wheel selection operator. 62 | * \param seed: Seed for random number generator, if seed < 0, then random 63 | * function would be used to generate seed. 64 | */ 65 | RouletteWheelSelection(int seed=-1) : Selection(seed) {} 66 | 67 | /*! \brief Selection operation. 68 | */ 69 | Parents select(Population & population); 70 | 71 | private: 72 | }; 73 | } 74 | 75 | #endif // __SELECTION__ 76 | 77 | -------------------------------------------------------------------------------- /gasol/unittest/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMake build script for GASol test. 2 | 3 | # Compile google test. 4 | set(GTEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/googletest/googletest) 5 | message(STATUS "Google test path: ${GTEST_DIR}") 6 | add_subdirectory(${GTEST_DIR}) 7 | 8 | # Build test executable file. 9 | 10 | # Include from GASol source. 11 | include_directories(AFTER SYSTEM ${GTEST_DIR}/include/) 12 | include_directories(${PROJECT_SOURCE_DIR}/src) 13 | 14 | # Link directories. 15 | link_directories(${CMAKE_CURRENT_BINARY_DIR}/../src/) 16 | link_directories(${CMAKE_CURRENT_BINARY_DIR}/googletest/googletest) 17 | 18 | # Compile unittest. 19 | file(GLOB TEST_SRCS *.cpp) 20 | add_executable(unittest EXCLUDE_FROM_ALL ${TEST_SRCS}) 21 | target_link_libraries(unittest GASol gtest) 22 | 23 | -------------------------------------------------------------------------------- /gasol/unittest/crossover_test.cpp: -------------------------------------------------------------------------------- 1 | /*! \brief Unittest for crossover operatiors. 2 | */ 3 | #include "individual.h" 4 | #include "crossover.h" 5 | #include "gtest/gtest.h" 6 | 7 | 8 | namespace { 9 | 10 | class CrossoverTest : public ::testing::Test { 11 | 12 | protected: 13 | virtual void SetUp() {} 14 | 15 | virtual void TearDown() {} 16 | 17 | // Individuals used in tests. 18 | gasol::RangePairs ranges_ {{0.0, 1.0}, {1.0, 2.0}}; 19 | std::vector precisions_ {0.125, 0.25}; 20 | 21 | std::vector solution1_ {1.0, 1.75}; 22 | gasol::Individual indv1_ = gasol::Individual(solution1_, ranges_, precisions_); 23 | 24 | std::vector solution2_ {0.875, 1.5}; 25 | gasol::Individual indv2_ = gasol::Individual(solution2_, ranges_, precisions_); 26 | }; 27 | 28 | TEST_F(CrossoverTest, UniformCrossover) 29 | { 30 | gasol::Parents parents(&indv1_, &indv2_); 31 | 32 | // If crossover probability is 0.0, children should be the same with parents. 33 | gasol::UniformCrossover c1(0.0, 0.0, 1); 34 | std::pair && children1 = c1.cross(parents); 35 | for (size_t i = 0; i < children1.first.chromsome().size(); i++) 36 | { 37 | EXPECT_EQ(children1.first.chromsome()[i], parents.first->chromsome()[i]); 38 | } 39 | for (size_t i = 0; i < children1.second.chromsome().size(); i++) 40 | { 41 | EXPECT_EQ(children1.second.chromsome()[i], parents.second->chromsome()[i]); 42 | } 43 | 44 | gasol::UniformCrossover c2(1.0, 1.0, 1); 45 | std::pair && children2 = c2.cross(parents); 46 | 47 | // If crossover probability is 1.0, then there must be difference between children and parents. 48 | bool bit_different = false; 49 | for (size_t i = 0; i < children2.first.chromsome().size(); i++) 50 | { 51 | if (children2.first.chromsome()[i] != parents.first->chromsome()[i]) 52 | { 53 | bit_different = true; 54 | break; 55 | } 56 | } 57 | EXPECT_TRUE(bit_different); 58 | 59 | bit_different = false; 60 | for (size_t i = 0; i < children2.second.chromsome().size(); i++) 61 | { 62 | if (children2.second.chromsome()[i] != parents.second->chromsome()[i]) 63 | { 64 | bit_different = true; 65 | break; 66 | } 67 | } 68 | EXPECT_TRUE(bit_different); 69 | 70 | // Test crossover with no custom random seed. 71 | gasol::UniformCrossover c3(0.5, 0.5); 72 | c3.cross(parents); 73 | } 74 | 75 | } // namespace 76 | 77 | -------------------------------------------------------------------------------- /gasol/unittest/engine_test.cpp: -------------------------------------------------------------------------------- 1 | /*! \brief Unittest for GA engine. 2 | */ 3 | 4 | #include "engine.h" 5 | #include "mpiutils.h" 6 | #include "gtest/gtest.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | namespace { 14 | 15 | double fitness(const gasol::Individual & indv) 16 | { 17 | double x = indv.solution()[0]; 18 | return x + 10*std::sin(5*x) + 7*std::cos(4*x); 19 | } 20 | 21 | class EngineTest : public ::testing::Test { 22 | 23 | protected: 24 | virtual void SetUp() 25 | { 26 | int size = 50; 27 | for (int i = 0; i < size; i++) 28 | { 29 | gasol::Individual indv(ranges_, precisions_); 30 | individuals_.push_back(indv); 31 | } 32 | } 33 | 34 | virtual void TearDown() {} 35 | 36 | // Individuals used in tests. 37 | std::vector> ranges_ {{0.0, 10.0}}; 38 | std::vector precisions_ = {0.001}; 39 | std::vector individuals_; 40 | }; 41 | 42 | TEST_F(EngineTest, Run) 43 | { 44 | gasol::Population population(individuals_, &fitness); 45 | gasol::RouletteWheelSelection selection; 46 | gasol::UniformCrossover crossover(0.8, 0.5); 47 | gasol::FlipBitMutation mutation(0.1); 48 | 49 | gasol::Engine engine(population, selection, crossover, mutation); 50 | gasol::MPIUtils::init(); 51 | engine.run(100); 52 | gasol::MPIUtils::finalize(); 53 | 54 | // std::cout << "best fitness: " 55 | // << engine.population().fitness()(engine.population().bestIndv()) 56 | // << std::endl; 57 | } 58 | 59 | } // namespace 60 | 61 | -------------------------------------------------------------------------------- /gasol/unittest/gasol_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gtest/gtest.h" 4 | 5 | GTEST_API_ int main(int argc, char **argv) { 6 | std::cout << "Running all tests for GASol (powered by Google Test)..." << std::endl; 7 | testing::InitGoogleTest(&argc, argv); 8 | return RUN_ALL_TESTS(); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /gasol/unittest/individual_test.cpp: -------------------------------------------------------------------------------- 1 | /*! \brief File unittest for Individual class 2 | */ 3 | 4 | #include "individual.h" 5 | #include "gtest/gtest.h" 6 | 7 | namespace { 8 | 9 | class IndividualTest : public ::testing::Test { 10 | protected: 11 | virtual void SetUp() 12 | { 13 | solution_ = {1.0, 1.75}; 14 | ranges_ = {{0.0, 1.0}, {1.0, 2.0}}; 15 | precisions_ = {0.1, 0.2}; 16 | } 17 | 18 | virtual void TearDown() {} 19 | 20 | // Data used in fixture. 21 | std::vector solution_; 22 | gasol::RangePairs ranges_; 23 | std::vector precisions_; 24 | }; 25 | 26 | TEST_F(IndividualTest, ConstructionWithMultiVals) 27 | { 28 | // {{{ 29 | // Construct an individual. 30 | gasol::Individual indv(solution_, ranges_, precisions_); 31 | 32 | // Check solution candidate. 33 | for (size_t i = 0; i < solution_.size(); ++i) 34 | { 35 | double ref_component = solution_[i]; 36 | double ret_component = indv.oriSolution()[i]; 37 | EXPECT_DOUBLE_EQ(ref_component, ret_component); 38 | } 39 | 40 | // Check ranges. 41 | for (size_t i = 0; i < ranges_.size(); i++) 42 | { 43 | double ref_floor = ranges_[i].first; 44 | double ref_ceiling = ranges_[i].second; 45 | double ret_floor = indv.ranges()[i].first; 46 | double ret_ceiling = indv.ranges()[i].second; 47 | EXPECT_DOUBLE_EQ(ref_floor, ret_floor); 48 | EXPECT_DOUBLE_EQ(ref_ceiling, ret_ceiling); 49 | } 50 | 51 | // Check original precisions. 52 | for (size_t i = 0; i < precisions_.size(); i++) 53 | { 54 | double ref_prec = precisions_[i]; 55 | double ret_prec = indv.originalPrecisions()[i]; 56 | EXPECT_DOUBLE_EQ(ref_prec, ret_prec); 57 | } 58 | 59 | // Check gene fragment lengths. 60 | std::vector ref_lengths = {3, 2}; 61 | for (size_t i = 0; i < ref_lengths.size(); i++) 62 | { 63 | EXPECT_EQ(ref_lengths[i], indv.geneLengths()[i]); 64 | } 65 | 66 | // Check precision loss. 67 | EXPECT_TRUE(indv.precisionLoss()); 68 | 69 | // Check actual precisions. 70 | std::vector ref_precisions = {0.125, 0.25}; 71 | for (size_t i = 0; i < ref_precisions.size(); i++) 72 | { 73 | EXPECT_DOUBLE_EQ(ref_precisions[i], indv.precisions()[i]); 74 | } 75 | 76 | // Check chromsome. 77 | std::vector ref_chromsome {1, 1, 1, 1, 0}; 78 | for (size_t i = 0; i < ref_chromsome.size(); i++) 79 | { 80 | EXPECT_EQ(ref_chromsome[i], indv.chromsome()[i]); 81 | } 82 | 83 | // Check gene fragment break points. 84 | gasol::GeneBreakPts ref_break_pts = {{0, 2}, {3, 4}}; 85 | for (size_t i = 0; i < ref_break_pts.size(); i++) 86 | { 87 | EXPECT_EQ(ref_break_pts[i].first, indv.geneBreakPts()[i].first); 88 | EXPECT_EQ(ref_break_pts[i].second, indv.geneBreakPts()[i].second); 89 | } 90 | 91 | // Check adjusted solution vector. 92 | std::vector ref_solution {1.0, 1.75}; 93 | for (size_t i = 0; i < ref_solution.size(); i++) 94 | { 95 | EXPECT_DOUBLE_EQ(ref_solution[i], indv.solution()[i]); 96 | } 97 | // }}} 98 | } 99 | 100 | TEST_F(IndividualTest, ConstructionWithSingleVal) 101 | { 102 | // {{{ 103 | // Construct an individual. 104 | std::vector solution {1.0, 2.0}; 105 | gasol::RangePairs ref_ranges {{0.0, 2.0}, {0.0, 2.0}}; 106 | std::vector ref_precisions {0.001, 0.001}; 107 | std::pair range {0.0, 2.0}; 108 | double precision = 0.001; 109 | gasol::Individual indv(solution, range, precision); 110 | 111 | // Check solution candidate. 112 | for (size_t i = 0; i < solution.size(); ++i) 113 | { 114 | double ref_component = solution[i]; 115 | double ret_component = indv.oriSolution()[i]; 116 | EXPECT_DOUBLE_EQ(ref_component, ret_component); 117 | } 118 | 119 | // Check ranges. 120 | for (size_t i = 0; i < ref_ranges.size(); i++) 121 | { 122 | double ref_floor = ref_ranges[i].first; 123 | double ref_ceiling = ref_ranges[i].second; 124 | double ret_floor = indv.ranges()[i].first; 125 | double ret_ceiling = indv.ranges()[i].second; 126 | EXPECT_DOUBLE_EQ(ref_floor, ret_floor); 127 | EXPECT_DOUBLE_EQ(ref_ceiling, ret_ceiling); 128 | } 129 | 130 | // Check precisions. 131 | for (size_t i = 0; i < ref_precisions.size(); i++) 132 | { 133 | double ref_prec = ref_precisions[i]; 134 | double ret_prec = indv.originalPrecisions()[i]; 135 | EXPECT_DOUBLE_EQ(ref_prec, ret_prec); 136 | } 137 | // }}} 138 | } 139 | 140 | TEST_F(IndividualTest, ConstructionWithRandomSolution) 141 | { 142 | gasol::Individual indv1(ranges_, precisions_); 143 | gasol::Individual indv2(ranges_, precisions_); 144 | 145 | // Two individuals should be different. 146 | // bool different = false; 147 | // for (size_t i = 0; i < indv1.chromsome().size(); i++) 148 | // { 149 | // if (indv1.chromsome()[i] != indv2.chromsome()[i]) 150 | // { 151 | // different = true; 152 | // break; 153 | // } 154 | // } 155 | // 156 | // EXPECT_TRUE(different); 157 | } 158 | 159 | TEST_F(IndividualTest, GeneBitFlip) 160 | { 161 | // {{{ 162 | gasol::Individual indv(solution_, ranges_, precisions_); 163 | std::vector chromsome_before = {1, 1, 1, 1, 0}; 164 | for (size_t i = 0; i < chromsome_before.size(); i++) 165 | { 166 | EXPECT_EQ(chromsome_before[i], indv.chromsome()[i]); 167 | } 168 | 169 | // Flip one bit in chromsome. 170 | indv.flipGeneBit(2); 171 | std::vector chromsome_after = {1, 1, 0, 1, 0}; 172 | for (size_t i = 0; i < chromsome_after.size(); i++) 173 | { 174 | EXPECT_EQ(chromsome_after[i], indv.chromsome()[i]); 175 | } 176 | // }}} 177 | } 178 | 179 | } // namespace 180 | 181 | -------------------------------------------------------------------------------- /gasol/unittest/mpiutils_test.cpp: -------------------------------------------------------------------------------- 1 | /*! \brief Unittest for GA engine. 2 | */ 3 | 4 | #include "engine.h" 5 | #include "mpiutils.h" 6 | #include "gtest/gtest.h" 7 | 8 | #include 9 | 10 | 11 | namespace { 12 | 13 | #define M 2 14 | #define N 3 15 | 16 | using namespace gasol; 17 | 18 | class MPIUtilsTest : public ::testing::Test { 19 | 20 | protected: 21 | virtual void SetUp() {} 22 | 23 | virtual void TearDown() {} 24 | 25 | double ** getMatrix(int m, int n) 26 | { 27 | double *data = new double[m*n](); 28 | double **matrix = new double*[m](); 29 | 30 | for (int i = 0; i < m; i++) 31 | { 32 | matrix[i] = &data[i*n]; 33 | } 34 | 35 | return matrix; 36 | } 37 | }; 38 | 39 | TEST_F(MPIUtilsTest, MPIInterfaces) 40 | { 41 | MPIUtils::init(); 42 | #if RUNMPI == true 43 | EXPECT_TRUE(MPIUtils::initialized()); 44 | #else 45 | EXPECT_FALSE(MPIUtils::initialized()); 46 | #endif 47 | EXPECT_GE(MPIUtils::size(), 1); 48 | 49 | MPIUtils::finalize(); 50 | EXPECT_TRUE(MPIUtils::finalized()); 51 | } 52 | 53 | TEST_F(MPIUtilsTest, SplitOverProcesses) 54 | { 55 | MPIUtils::init(); 56 | int size = 50; 57 | #if RUNMPI == true 58 | #else 59 | auto ret_break_pts = MPIUtils::splitOverProcesses(size); 60 | std::pair ref_break_pts = {0, size}; 61 | EXPECT_EQ(ref_break_pts.first, ret_break_pts.first); 62 | EXPECT_EQ(ref_break_pts.second, ret_break_pts.second); 63 | #endif 64 | MPIUtils::finalize(); 65 | } 66 | 67 | TEST_F(MPIUtilsTest, JoinOverProcesses) 68 | { 69 | MPIUtils::init(); 70 | 71 | double **send = getMatrix(3, 2); 72 | double **recv = getMatrix(3, 2); 73 | 74 | for (int i = 0; i < 3; i++) 75 | { 76 | for (int j = 0; j < 2; j++) 77 | { 78 | send[i][j] = i + j + 1; 79 | } 80 | } 81 | 82 | for (int i = 0; i < 3; i++) 83 | { 84 | for (int j = 0; j < 2; j++) 85 | { 86 | EXPECT_DOUBLE_EQ(recv[i][j], 0.0); 87 | } 88 | } 89 | 90 | #if RUNMPI == true 91 | #else 92 | MPIUtils::joinOverProcesses(send, recv, M, N); 93 | 94 | for (int i = 0; i < 3; i++) 95 | { 96 | for (int j = 0; j < 2; j++) 97 | { 98 | EXPECT_DOUBLE_EQ(recv[i][j], send[i][j]); 99 | } 100 | } 101 | #endif 102 | 103 | delete [] send[0]; 104 | delete [] send; 105 | delete [] recv[0]; 106 | delete [] recv; 107 | 108 | MPIUtils::finalize(); 109 | } 110 | 111 | } // namespace 112 | 113 | -------------------------------------------------------------------------------- /gasol/unittest/mutation_test.cpp: -------------------------------------------------------------------------------- 1 | /*! \brief Unittest for mutation operatiors. 2 | */ 3 | #include "individual.h" 4 | #include "mutation.h" 5 | #include "gtest/gtest.h" 6 | 7 | 8 | namespace { 9 | 10 | class MutationTest : public ::testing::Test { 11 | 12 | protected: 13 | virtual void SetUp() {} 14 | 15 | virtual void TearDown() {} 16 | 17 | // Individuals used in tests. 18 | gasol::RangePairs ranges_ {{0.0, 1.0}, {1.0, 2.0}}; 19 | std::vector precisions_ {0.125, 0.25}; 20 | 21 | std::vector solution_ {1.0, 1.75}; 22 | gasol::Individual ref_indv_ = gasol::Individual(solution_, ranges_, precisions_); 23 | }; 24 | 25 | TEST_F(MutationTest, FlipBigMuatation) 26 | { 27 | // Test mutation with probability 1.0 28 | gasol::FlipBitMutation m1(1.0); 29 | gasol::Individual indv(ref_indv_); 30 | m1.mutate(indv); 31 | 32 | for (size_t i = 0; i < indv.chromsome().size(); i++) 33 | { 34 | EXPECT_NE(ref_indv_.chromsome()[i], indv.chromsome()[i]); 35 | } 36 | 37 | // Test mutation with mutation probability with 0.0 38 | gasol::FlipBitMutation m2(0.0); 39 | indv = ref_indv_; 40 | m2.mutate(indv); 41 | 42 | for (size_t i = 0; i < indv.chromsome().size(); i++) 43 | { 44 | EXPECT_EQ(ref_indv_.chromsome()[i], indv.chromsome()[i]); 45 | } 46 | } 47 | 48 | } // namespace 49 | 50 | -------------------------------------------------------------------------------- /gasol/unittest/population_test.cpp: -------------------------------------------------------------------------------- 1 | /*! \brief Unittest for Population class 2 | */ 3 | #include "population.h" 4 | #include "individual.h" 5 | #include "gtest/gtest.h" 6 | 7 | 8 | namespace { 9 | 10 | double fitness(const gasol::Individual & indv) 11 | { return indv.solution()[0]*indv.solution()[1]; } 12 | 13 | class PopulationTest : public ::testing::Test { 14 | 15 | protected: 16 | virtual void SetUp() {} 17 | 18 | virtual void TearDown() {} 19 | 20 | // Individuals used in tests. 21 | gasol::RangePairs ranges_ {{0.0, 1.0}, {1.0, 2.0}}; 22 | std::vector precisions_ {0.125, 0.25}; 23 | 24 | std::vector solution1_ {1.0, 1.75}; 25 | gasol::Individual indv1_ = gasol::Individual(solution1_, ranges_, precisions_); 26 | 27 | std::vector solution2_ {0.875, 1.5}; 28 | gasol::Individual indv2_ = gasol::Individual(solution2_, ranges_, precisions_); 29 | 30 | std::vector solution3_ {0.5, 2.0}; 31 | gasol::Individual indv3_ = gasol::Individual(solution3_, ranges_, precisions_); 32 | 33 | // Fitness function pointer. 34 | gasol::Fitness *pfit_ = &fitness; 35 | }; 36 | 37 | TEST_F(PopulationTest, Construction) 38 | { 39 | std::vector indvs {indv1_, indv2_, indv3_}; 40 | gasol::Population population(indvs, pfit_); 41 | 42 | // Check size. 43 | EXPECT_EQ(population.size(), 3); 44 | 45 | // Check one pointer. 46 | const auto indv_ptr = population.indvPtrs()[0]; 47 | std::vector ref_chromsome {1, 1, 1, 1, 0}; 48 | for (size_t i = 0; i < ref_chromsome.size(); i++) 49 | { 50 | EXPECT_EQ(ref_chromsome[i], indv_ptr->chromsome()[i]); 51 | } 52 | 53 | // Check fitness function. 54 | EXPECT_DOUBLE_EQ(population.fitness()(indv3_), 1.0); 55 | } 56 | 57 | TEST_F(PopulationTest, ConstructionWithTemplate) 58 | { 59 | gasol::Population population(indv1_, 10, pfit_); 60 | 61 | EXPECT_EQ(population.indvs().size(), 10); 62 | EXPECT_EQ(population.indvPtrs().size(), 10); 63 | EXPECT_EQ(population.size(), 10); 64 | EXPECT_EQ(population.fitness(), pfit_); 65 | } 66 | 67 | TEST_F(PopulationTest, UpdateIndividuals) 68 | { 69 | std::vector indvs {indv1_, indv2_}; 70 | gasol::Population population(indvs, pfit_); 71 | 72 | for (size_t i = 0; i < indv1_.oriSolution().size(); i++) 73 | { 74 | EXPECT_DOUBLE_EQ(population.indvPtrs()[0]->oriSolution()[i], 75 | indv1_.oriSolution()[i]); 76 | EXPECT_DOUBLE_EQ(population.indvPtrs()[1]->oriSolution()[i], 77 | indv2_.oriSolution()[i]); 78 | } 79 | std::vector indvs2 {indv3_, indv1_}; 80 | population.updateIndividuals(indvs2); 81 | for (size_t i = 0; i < indv1_.oriSolution().size(); i++) 82 | { 83 | EXPECT_DOUBLE_EQ(population.indvPtrs()[0]->oriSolution()[i], 84 | indv3_.oriSolution()[i]); 85 | EXPECT_DOUBLE_EQ(population.indvPtrs()[1]->oriSolution()[i], 86 | indv1_.oriSolution()[i]); 87 | } 88 | } 89 | 90 | TEST_F(PopulationTest, BestIndividual) 91 | { 92 | std::vector indvs {indv1_, indv2_, indv3_}; 93 | gasol::Population population(indvs, pfit_); 94 | 95 | const gasol::Individual & best_indv = population.bestIndv(); 96 | 97 | EXPECT_DOUBLE_EQ(best_indv.solution()[0], indv1_.solution()[0]); 98 | EXPECT_DOUBLE_EQ(best_indv.solution()[1], indv1_.solution()[1]); 99 | } 100 | 101 | TEST_F(PopulationTest, AllFitValues) 102 | { 103 | std::vector indvs {indv1_, indv2_, indv3_}; 104 | gasol::Population population(indvs, pfit_); 105 | 106 | std::vector && all_fits = population.allFitVals(); 107 | std::vector ref_all_fits = {1.75, 1.3125, 1.0}; 108 | for (size_t i = 0; i < all_fits.size(); i++) 109 | { 110 | EXPECT_DOUBLE_EQ(ref_all_fits[i], all_fits[i]); 111 | } 112 | } 113 | 114 | TEST_F(PopulationTest, WorstIndividual) 115 | { 116 | std::vector indvs {indv1_, indv2_, indv3_}; 117 | gasol::Population population(indvs, pfit_); 118 | 119 | const gasol::Individual & worst_indv = population.worstIndv(); 120 | 121 | EXPECT_DOUBLE_EQ(worst_indv.solution()[0], indv3_.solution()[0]); 122 | EXPECT_DOUBLE_EQ(worst_indv.solution()[1], indv3_.solution()[1]); 123 | } 124 | 125 | } // namespace 126 | 127 | -------------------------------------------------------------------------------- /gasol/unittest/selection_test.cpp: -------------------------------------------------------------------------------- 1 | /*! \brief Unittest for Population class 2 | */ 3 | #include "population.h" 4 | #include "individual.h" 5 | #include "selection.h" 6 | #include "gtest/gtest.h" 7 | 8 | 9 | namespace { 10 | 11 | double fitness(const gasol::Individual & indv) 12 | { return indv.solution()[0]*indv.solution()[1]; } 13 | 14 | class SelectionTest : public ::testing::Test { 15 | 16 | protected: 17 | virtual void SetUp() {} 18 | 19 | virtual void TearDown() {} 20 | 21 | // Individuals used in tests. 22 | gasol::RangePairs ranges_ {{0.0, 1.0}, {1.0, 2.0}}; 23 | std::vector precisions_ {0.125, 0.25}; 24 | 25 | std::vector solution1_ {1.0, 1.75}; 26 | gasol::Individual indv1_ = gasol::Individual(solution1_, ranges_, precisions_); 27 | 28 | std::vector solution2_ {0.875, 1.5}; 29 | gasol::Individual indv2_ = gasol::Individual(solution2_, ranges_, precisions_); 30 | 31 | std::vector solution3_ {0.5, 2.0}; 32 | gasol::Individual indv3_ = gasol::Individual(solution3_, ranges_, precisions_); 33 | 34 | // Fitness function pointer. 35 | gasol::Fitness *pfit_ = &fitness; 36 | }; 37 | 38 | TEST_F(SelectionTest, RouletteWheelSelection) 39 | { 40 | std::vector indvs {indv1_, indv2_, indv3_}; 41 | gasol::Population population(indvs, pfit_); 42 | 43 | // Select parents. 44 | gasol::RouletteWheelSelection selection(0); 45 | gasol::Parents parents1 = selection.select(population); 46 | const gasol::Individual *father1 = parents1.first; 47 | const gasol::Individual *mother1 = parents1.second; 48 | EXPECT_DOUBLE_EQ(fitness(*father1), 1.75); 49 | EXPECT_DOUBLE_EQ(fitness(*mother1), 1.3125); 50 | 51 | // Select again, should be the same. 52 | gasol::Parents parents2 = selection.select(population); 53 | const gasol::Individual *father2 = parents2.first; 54 | const gasol::Individual *mother2 = parents2.second; 55 | EXPECT_DOUBLE_EQ(fitness(*father2), 1.75); 56 | EXPECT_DOUBLE_EQ(fitness(*mother2), 1.3125); 57 | 58 | // Select with no seed provided. 59 | gasol::RouletteWheelSelection selection2(-1); 60 | selection2.select(population); 61 | } 62 | 63 | } // namespace 64 | 65 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | #universal=1 3 | 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from setuptools import setup, find_packages 5 | from gaft import __version__ as version 6 | 7 | maintainer = 'Shao-Zheng-Jiang' 8 | maintainer_email = 'shaozhengjiang@gmail.com' 9 | author = maintainer 10 | author_email = maintainer_email 11 | description = "A Genetic Algorithm Framework in Python" 12 | long_description = ''' 13 | ==== 14 | GAFT 15 | ==== 16 | 17 | A **G**\ enetic **A**\ lgorithm **F**\ ramework in py\ **T**\ hon 18 | 19 | .. image:: https://travis-ci.org/PytLab/gaft.svg?branch=master 20 | :target: https://travis-ci.org/PytLab/gaft 21 | :alt: Build Status 22 | 23 | .. image:: https://img.shields.io/codecov/c/github/PytLab/gaft/master.svg 24 | :target: https://codecov.io/gh/PytLab/gaft 25 | :alt: Codecov 26 | 27 | .. image:: https://landscape.io/github/PytLab/gaft/master/landscape.svg?style=flat 28 | :target: https://landscape.io/github/PytLab/gaft/master 29 | :alt: Code Health 30 | 31 | .. image:: https://img.shields.io/badge/python-3.5-green.svg 32 | :target: https://www.python.org/downloads/release/python-351/ 33 | :alt: platform 34 | 35 | .. image:: https://img.shields.io/badge/pypi-v0.5.7-blue.svg 36 | :target: https://pypi.python.org/pypi/gaft/ 37 | :alt: versions 38 | 39 | .. image:: https://readthedocs.org/projects/gaft/badge/?version=latest 40 | :target: https://gaft.readthedocs.io/en/latest/?badge=latest 41 | :alt: Documentation Status 42 | 43 | 44 | Introduction 45 | ------------ 46 | 47 | **GAFT** is a general Python Framework for genetic algorithm computation. It provides built-in genetic operators for target optimization and plugin interfaces for users to define your own genetic operators and on-the-fly analysis for algorithm testing. 48 | 49 | **GAFT** is now accelerated using MPI parallelization interfaces. You can run it on your cluster in parallel with MPI environment. 50 | 51 | Python Support 52 | -------------- 53 | 54 | **GAFT** requires Python version 3.x (Python 2.x is not supported). 55 | 56 | Installation 57 | ------------ 58 | 59 | 1. Via pip:: 60 | 61 | pip install gaft 62 | 63 | 2. From source:: 64 | 65 | python setup.py install 66 | 67 | If you want GAFT to run in MPI env, please install mpi4py explicitly:: 68 | 69 | pip install mpi4py 70 | 71 | See `INSTALL.md `_ for more installation details. 72 | 73 | Test 74 | ---- 75 | 76 | Run unit test:: 77 | 78 | python setup.py test 79 | 80 | 81 | ''' 82 | 83 | install_requires = [ 84 | ] 85 | 86 | license = 'LICENSE' 87 | 88 | name = 'gaft' 89 | platforms = ['linux', 'windows', 'macos'] 90 | url = 'https://github.com/PytLab/gaft' 91 | download_url = 'https://github.com/PytLab/gaft/releases' 92 | 93 | classifiers = [ 94 | 'Development Status :: 3 - Alpha', 95 | 'Topic :: Utilities', 96 | 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)' 97 | 'Programming Language :: Python :: 3', 98 | 'Programming Language :: Python :: 3.5', 99 | ] 100 | 101 | test_suite = 'gaft.tests.test_all' 102 | 103 | setup( 104 | author=author, 105 | author_email=author_email, 106 | description=description, 107 | license=license, 108 | long_description=long_description, 109 | install_requires=install_requires, 110 | maintainer=maintainer, 111 | name=name, 112 | packages=find_packages(), 113 | platforms=platforms, 114 | url=url, 115 | download_url=download_url, 116 | version=version, 117 | test_suite=test_suite 118 | ) 119 | 120 | --------------------------------------------------------------------------------