├── .gitattributes
├── .github
└── workflows
│ ├── deploy.yml
│ └── testing.yml
├── .gitignore
├── .readthedocs.yml
├── LICENSE
├── MANIFEST.in
├── README.md
├── docs
├── Makefile
├── make.bat
├── requirements.txt
└── source
│ ├── Theory
│ ├── DE.ipynb
│ ├── Rank-and-Crowding.ipynb
│ ├── Survival.ipynb
│ └── plots.py
│ ├── Usage
│ ├── Complete-tutorial.ipynb
│ ├── Many-objective.ipynb
│ ├── Multi-objective.ipynb
│ ├── Portfolio.ipynb
│ ├── Single-objective.ipynb
│ └── index.ipynb
│ ├── algorithms.rst
│ ├── conf.py
│ ├── index.rst
│ ├── modules.rst
│ ├── pymoode.algorithms.rst
│ ├── pymoode.cython.rst
│ ├── pymoode.misc.rst
│ ├── pymoode.operators.rst
│ ├── pymoode.performance.rst
│ ├── pymoode.rst
│ ├── pymoode.survival.rst
│ ├── references.bib
│ └── references.rst
├── images
├── gde3mnn_example.gif
├── gde3mnn_example.png
├── general_de.png
├── logo_header_pymoode.png
├── logo_pymoode.png
├── moo_gde3_f_video.gif
├── rastrigin.gif
├── soo_video.gif
└── tnk_gde3.png
├── notebooks
├── Many-objective.ipynb
├── Multi-objective.ipynb
├── Portfolio.ipynb
├── Single-objective.ipynb
├── example_readme.ipynb
└── tutorial.ipynb
├── pymoode
├── __init__.py
├── algorithms
│ ├── __init__.py
│ ├── base
│ │ ├── __init__.py
│ │ ├── differential.py
│ │ ├── evolutionary.py
│ │ └── genetic.py
│ ├── de.py
│ ├── gde3.py
│ ├── nsde.py
│ └── nsder.py
├── backups
│ ├── mnn_arrays.pyx
│ ├── mnn_bak.pyx
│ ├── mnn_bak2.pyx
│ ├── mnn_bounded.pyx
│ ├── mnn_condensed.pyx
│ ├── mnn_naive_sort.pyx
│ └── mnn_precalc.pyx
├── cython
│ ├── __init__.py
│ ├── info.cpp
│ ├── info.pyx
│ ├── mnn.cpp
│ ├── mnn.pyx
│ ├── pruning_cd.cpp
│ ├── pruning_cd.pyx
│ ├── spacing_neighbors.cpp
│ ├── spacing_neighbors.pyx
│ └── utils.pxd
├── misc
│ ├── __init__.py
│ ├── mnn.py
│ └── pruning_cd.py
├── operators
│ ├── __init__.py
│ ├── dem.py
│ ├── deop.py
│ ├── des.py
│ ├── dex.py
│ └── variant.py
├── performance
│ ├── __init__.py
│ └── _spacing.py
└── survival
│ ├── __init__.py
│ ├── fitness.py
│ ├── rank_and_crowding
│ ├── __init__.py
│ ├── metrics.py
│ └── rnc.py
│ └── replacement.py
├── pyproject.toml
├── pytest.ini
├── requirements.txt
├── setup.py
└── tests
├── __init__.py
├── requirements.txt
├── test_many.py
├── test_multi.py
├── test_single.py
└── test_spacing.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.ipynb linguist-detectable=false
2 | pymoode/backups/* linguist-detectable=false
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 | on:
3 | push:
4 | branches:
5 | - deploy
6 |
7 | jobs:
8 |
9 | build:
10 | runs-on: "${{ matrix.os }}"
11 | steps:
12 | - uses: actions/checkout@v3
13 | - name: "Set up Python"
14 | uses: actions/setup-python@v4
15 | with:
16 | python-version: "${{ matrix.python-version }}"
17 | - name: "Install dependencies"
18 | run: |
19 | python -m pip install --upgrade pip
20 | pip install setuptools wheel numpy cython
21 | - uses: knicknic/os-specific-run@v1
22 | with:
23 | linux: "python setup.py sdist"
24 | macos: "python setup.py bdist_wheel"
25 | windows: "python setup.py bdist_wheel"
26 | - uses: actions/upload-artifact@v3
27 | with:
28 | name: dist
29 | path: dist
30 | strategy:
31 | matrix:
32 | os:
33 | - windows-latest
34 | - macos-latest
35 | python-version:
36 | - "3.7"
37 | - "3.8"
38 | - "3.9"
39 | - "3.10"
40 | - "3.11"
41 | - "3.12"
42 | include:
43 | - os: ubuntu-latest
44 | python-version: "3.12"
45 |
46 | build-manylinux:
47 | runs-on: ubuntu-latest
48 | steps:
49 | - uses: actions/checkout@v3
50 | - uses: RalfG/python-wheels-manylinux-build@v0.5.0
51 | with:
52 | build-requirements: "cython numpy"
53 | pip-wheel-args: "-w ./dist --no-deps"
54 | python-versions: "cp37-cp37m cp38-cp38 cp39-cp39 cp310-cp310 cp311-cp311 cp312-cp312"
55 | - name: "Remove non-compatible packages"
56 | run: "sudo rm dist/*linux_x86_64.whl\n"
57 | - uses: actions/upload-artifact@v3
58 | with:
59 | name: dist
60 | path: dist
--------------------------------------------------------------------------------
/.github/workflows/testing.yml:
--------------------------------------------------------------------------------
1 | name: Testing
2 | on:
3 | push:
4 | branches:
5 | - develop
6 | - main
7 |
8 | jobs:
9 |
10 | testing:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: Install Python 3
15 | uses: actions/setup-python@v2
16 | with:
17 | python-version: '3.11'
18 | - name: Install Dependencies
19 | run: |
20 | pip install numpy Cython
21 | pip install -r tests/requirements.txt
22 | pip install pytest
23 | - name: Install pymoode (DEBUG)
24 | run: |
25 | python setup.py install
26 | - name: Install pymoode
27 | run: |
28 | pip install .
29 | rm -rf pymoode
30 | - name: Check Compilation
31 | run: |
32 | python -c "from pymoode.survival.rank_and_crowding.metrics import IS_COMPILED;print('Compiled Extensions: ', IS_COMPILED)"
33 | - name: Run Tests
34 | run: |
35 | pytest -v --maxfail 1 --no-header -m "not long"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | dist
3 | dist.*
4 | experimental
5 | *.egg-info
6 | /venv
7 | */venv
8 | /pymoode/cython/*.html
9 | /pymoode/cython/*.pyd
10 | __pycache__/
11 |
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | # Read the Docs configuration file for Sphinx projects
2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
3 |
4 | # Required
5 | version: 2
6 |
7 | # Set the OS, Python version and other tools you might need
8 | build:
9 | os: ubuntu-22.04
10 | tools:
11 | python: "3.11"
12 | # You can also specify other tool versions:
13 | # nodejs: "20"
14 | # rust: "1.70"
15 | # golang: "1.20"
16 |
17 | # Build documentation in the "docs/" directory with Sphinx
18 | sphinx:
19 | configuration: docs/source/conf.py
20 | # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
21 | # builder: "dirhtml"
22 | # Fail on all warnings to avoid broken references
23 | # fail_on_warning: true
24 |
25 | # Optionally build your docs in additional formats such as PDF and ePub
26 | formats:
27 | - pdf
28 | - epub
29 |
30 | # Optional but recommended, declare the Python requirements required
31 | # to build your documentation
32 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
33 | python:
34 | install:
35 | - requirements: docs/requirements.txt
36 | - method: pip
37 | path: .
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include pymoode *.py *.pyx *.pxd
2 | recursive-exclude pymoode *.so *.cpp *.html
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | # pymoode
8 |
9 | A Python framework for Differential Evolution using [pymoo](https://github.com/anyoptimization/pymoo) (Blank & Deb, 2020).
10 |
11 | [Read the Docs](https://pymoode.readthedocs.io/en/latest/)
12 |
13 | ## Contents
14 |
15 | [Install](#install) | [Algorithms](#algorithms) | [Survival Operators](#survival-operators) | [Crowding Metrics](#crowding-metrics) | [Usage](#usage) | [Structure](#structure) | [Citation](#citation) | [References](#references) | [Contact](#contact) | [Acknowledgements](#acknowledgements)
16 |
17 | ## Install
18 |
19 | First, make sure you have a Python 3 environment installed.
20 |
21 | From PyPi:
22 | ```
23 | pip install pymoode
24 | ```
25 |
26 | From the current version on github:
27 | ```
28 | pip install git+https://github.com/mooscaliaproject/pymoode
29 | ```
30 |
31 | ## Algorithms
32 | - **DE**: Differential Evolution for single-objective problems proposed by Storn & Price (1997). Other features later implemented are also present, such as dither, jitter, selection variants, and crossover strategies. For details see Price et al. (2005).
33 | - **NSDE**: Non-dominated Sorting Differential Evolution, a multi-objective algorithm that combines DE mutation and crossover operators to NSGA-II (Deb et al., 2002) survival.
34 | - **GDE3**: Generalized Differential Evolution 3, a multi-objective algorithm that combines DE mutation and crossover operators to NSGA-II survival with a hybrid type survival strategy. In this algorithm, individuals might be removed in a one-to-one comparison before truncating the population by the multi-objective survival operator. It was proposed by Kukkonen, S. & Lampinen, J. (2005). Variants with M-Nearest Neighbors and 2-Nearest Neighbors survival are also available.
35 | - **NSDE-R**: Non-dominated Sorting Differential Evolution based on Reference directions (Reddy & Dulikravich, 2019). It is an algorithm for many-objective problems that works as an extension of NSDE using NSGA-III (Deb & Jain, 2014) survival strategy.
36 |
37 | ## Survival Operators
38 |
39 | - **RandAndCrowding**: Flexible structure to implement NSGA-II rank and crowding survival with different options for crowding metric and elimination of individuals.
40 | - **ConstrRankAndCrowding**: A survival operator based on rank and crowding with a special constraint handling approach proposed by Kukkonen, S. & Lampinen, J. (2005).
41 |
42 | ## Crowding Metrics
43 |
44 | - **Crowding Distance** (*'cd'*): Proposed by Deb et al. (2002) in NSGA-II. Imported from *pymoo*.
45 | - **Pruning Crowding Distance** (*'pruning-cd'* or *'pcd'*): Proposed by Kukkonen & Deb (2006a), it recursively recalculates crowding distances as removes individuals from a population to improve diversity.
46 | - ***M*-Nearest Neighbors** (*'mnn'*): Proposed by Kukkonen & Deb (2006b) in an extension of GDE3 to many-objective problems.
47 | - **2-Nearest Neighbors** (*'2nn'*): Also proposed by Kukkonen & Deb (2006b), it is a variant of M-Nearest Neighbors in which the number of neighbors is two.
48 | - **Crowding Entropy** (*'ce'*): Proposed by Wang et al. (2010) in MOSADE.
49 |
50 | Metrics *'pcd'*, *'mnn'*, and *'2nn'* are recursively recalculated as individuals are removed, to improve the population diversity. Therefore, they are implemented using cython to reduce computational time. If compilation fails, .py files are used instead, which makes it slightly slower.
51 |
52 | ## Usage
53 | For more examples, [read the docs](https://pymoode.readthedocs.io/en/latest/Usage/index.html)
54 |
55 | ```python
56 | import matplotlib.pyplot as plt
57 | from pymoo.problems import get_problem
58 | from pymoo.optimize import minimize
59 | from pymoode.algorithms import GDE3
60 | from pymoode.survival import RankAndCrowding
61 |
62 | problem = get_problem("tnk")
63 | pf = problem.pareto_front()
64 | ```
65 |
66 | ```python
67 | gde3 = GDE3(
68 | pop_size=50, variant="DE/rand/1/bin", CR=0.5, F=(0.0, 0.9),
69 | survival=RankAndCrowding(crowding_func="pcd")
70 | )
71 |
72 | res = minimize(problem, gde3, ('n_gen', 200), seed=12)
73 | ```
74 |
75 | ```python
76 | fig, ax = plt.subplots(figsize=[6, 5], dpi=100)
77 | ax.scatter(pf[:, 0], pf[:, 1], color="navy", label="True Front")
78 | ax.scatter(res.F[:, 0], res.F[:, 1], color="firebrick", label="GDE3")
79 | ax.set_ylabel("$f_2$")
80 | ax.set_xlabel("$f_1$")
81 | ax.legend()
82 | fig.tight_layout()
83 | plt.show()
84 | ```
85 |
86 |
87 |
91 |
92 |
93 | Alternatively, on the three-objective problem DTLZ2, it would produce amazing results.
94 |
95 | ```python
96 | problem = get_problem("dtlz2")
97 | ```
98 |
99 | ```python
100 | gde3mnn = GDE3(
101 | pop_size=150, variant="DE/rand/1/bin", CR=0.5, F=(0.0, 0.9),
102 | survival=RankAndCrowding(crowding_func="mnn")
103 | )
104 |
105 | res = minimize(problem, gde3mnn, ('n_gen', 250), seed=12)
106 | ```
107 |
108 |
109 |
113 |
114 |
115 | ## Structure
116 |
117 | ```
118 | pymoode
119 | ├───algorithms
120 | │ ├───DE
121 | │ ├───GDE3
122 | │ ├───NSDE
123 | │ └───NSDER
124 | ├───survival
125 | │ ├───RankAndCrowding
126 | │ └───ConstrRankAndCrowding
127 | ├───performance
128 | │ └───SpacingIndicator
129 | └───operators
130 | ├───dem.py
131 | │ └───DEM
132 | ├───dex.py
133 | │ └───DEX
134 | └───des.py
135 | └───DES
136 | ```
137 |
138 |
139 | ## Citation
140 | This package was developed as part of an academic optimization project. Please, if you use it for research purposes, cite it using the published article:
141 |
142 | [Leite, B., Costa, A. O. S., Costa, E. F., 2023. Multi-objective optimization of adiabatic styrene reactors using Generalized Differential Evolution 3 (GDE3). Chem. Eng. Sci., Volume 265, Article 118196. doi:10.1016/j.ces.2022.118196.](https://doi.org/10.1016/j.ces.2022.118196)
143 |
144 | ## References
145 |
146 | [Blank, J. & Deb, K., 2020. pymoo: Multi-Objective Optimization in Python. IEEE Access, Volume 8, pp. 89497-89509. doi:10.1109/ACCESS.2020.2990567.](https://doi.org/10.1109/ACCESS.2020.2990567)
147 |
148 | [Deb, K. & Jain, H., 2014. An evolutionary many-objective optimization algorithm using reference-point-based nondominated sorting approach, part I: solving problems with box constraints. IEEE Transactions on Evolutionary Computation, 18(4), pp. 577–601. doi:10.1109/TEVC.2013.2281535.](https://doi.org/10.1109/TEVC.2013.2281535)
149 |
150 | [Deb, K., Pratap, A., Agarwal, S. & Meyarivan, T. A. M. T., 2002. A Fast and Elitist Multiobjective Genetic Algorithm: NSGA-II. IEEE transactions on evolutionary computation, 6(2), pp. 182-197. doi:10.1109/4235.996017.](https://doi.org/10.1109/4235.996017)
151 |
152 | [Kukkonen, S. & Deb, K., 2006a. Improved Pruning of Non-Dominated Solutions Based on Crowding Distance for Bi-Objective Optimization Problems. Vancouver, s.n., pp. 1179-1186. doi:10.1109/CEC.2006.1688443.](https://doi.org/10.1109/CEC.2006.1688443)
153 |
154 | [Kukkonen, S. & Deb, K., 2006b. A fast and effective method for pruning of non-dominated solutions in many-objective problems. In: Parallel problem solving from nature-PPSN IX. Berlin: Springer, pp. 553-562. doi:10.1007/11844297_56.](https://doi.org/10.1007/11844297_56)
155 |
156 | [Kukkonen, S. & Lampinen, J., 2005. GDE3: The third evolution step of generalized differential evolution. 2005 IEEE congress on evolutionary computation, Volume 1, pp. 443-450. doi:10.1109/CEC.2005.1554717.](https://doi.org/10.1109/CEC.2005.1554717)
157 |
158 | [Reddy, S. R. & Dulikravich, G. S., 2019. Many-objective differential evolution optimization based on reference points: NSDE-R. Struct. Multidisc. Optim., Volume 60, pp. 1455-1473. doi:10.1007/s00158-019-02272-0.](https://doi.org/10.1007/s00158-019-02272-0)
159 |
160 | Price, K. V., Storn, R. M. & Lampinen, J. A., 2005. Differential Evolution: A Practical Approach to Global Optimization. 1st ed. Springer: Berlin.
161 |
162 | [Storn, R. & Price, K., 1997. Differential evolution–a simple and efficient heuristic for global optimization over continuous spaces. J. Glob. Optim., 11(4), pp. 341-359. doi:10.1023/A:1008202821328](https://doi.org/10.1023/A:1008202821328)
163 |
164 | [Wang, Y.-N., Wu, L.-H. & Yuan, X.-F., 2010. Multi-objective self-adaptive differential evolution with elitist archive and crowding entropy-based diversity measure. Soft Comput., 14(3), pp. 193-209. doi:10.1007/s00500-008-0394-9](https://doi.org/10.1007/s00500-008-0394-9)
165 |
166 | ## Contact
167 | e-mail: bruscalia12@gmail.com
168 |
169 | ## Acknowledgements
170 | To Julian Blank, who created the amazing structure of pymoo, making such a project possible.
171 |
172 | To Esly F. da Costa Junior, for the unconditional support all along.
173 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=source
11 | set BUILDDIR=build
12 |
13 | %SPHINXBUILD% >NUL 2>NUL
14 | if errorlevel 9009 (
15 | echo.
16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
17 | echo.installed, then set the SPHINXBUILD environment variable to point
18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
19 | echo.may add the Sphinx directory to PATH.
20 | echo.
21 | echo.If you don't have Sphinx installed, grab it from
22 | echo.https://www.sphinx-doc.org/
23 | exit /b 1
24 | )
25 |
26 | if "%1" == "" goto help
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | ipykernel
2 | pymoode
3 | sphinx
4 | sphinx_rtd_theme
5 | sphinxcontrib-bibtex
6 | pandoc
7 | numpydoc
8 | nbsphinx
9 | matplotlib
10 |
--------------------------------------------------------------------------------
/docs/source/Theory/Survival.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "attachments": {},
5 | "cell_type": "markdown",
6 | "metadata": {},
7 | "source": [
8 | "# Survival\n",
9 | "\n",
10 | "The general theory of pymoode's survival operators is presented throughout this section, although code details should be better addressed in their [corresponding section](../pymoode.survival.rst)."
11 | ]
12 | },
13 | {
14 | "attachments": {},
15 | "cell_type": "markdown",
16 | "metadata": {},
17 | "source": [
18 | "## Single-objective\n",
19 | "\n",
20 | "In single-objective DE, to decide if a trial vector $u_i$ should become a member of the next generation, it is compared to the corresponding target vector $x_i$ using a greedy criterion . If $u_i$ outperforms $x_i$, it proceeds to the next generation and $x_i$ is removed.\n",
21 | "\n",
22 | "Until version 0.2.4, the one-to-one comparison in DE was performed using pymoo's ``ImprovementReplacement`` class. Now the user can parse its own survival operator to single-objective DE via the ``survival`` argument. It is recommended that the new operator is inherited from ``pymoode.survival.replacement.BaseReplacement``.\n",
23 | "\n",
24 | "\n",
25 | "
\n",
27 | "
"
28 | ]
29 | },
30 | {
31 | "attachments": {},
32 | "cell_type": "markdown",
33 | "metadata": {},
34 | "source": [
35 | "## Multi-objective algorithms\n",
36 | "\n",
37 | "The one-to-one comparison might be inadequate in multi-objective problems and is usually replaced by other survival strategies.\n",
38 | "\n",
39 | "GDE3 and NSDE are two multi-objective variants of DE using Nondominated Sorting and crowding metrics in the survival stage. They share the same reproduction operators, although different in their survival strategies.\n",
40 | "\n",
41 | "Whereas NSDE uses a full (µ+λ) survival strategy, which combines the parent population with offspring, GDE3 has a one-to-one comparison between each member in the parent population and its corresponding trial vector before truncating the population into the original size. In this one-to-one comparison, one of the individuals of each pair might be removed in advance from the combined population before (µ+λ) survival if dominated by the other.\n",
42 | "\n",
43 | "\n",
44 | "
\n",
46 | "
"
47 | ]
48 | },
49 | {
50 | "attachments": {},
51 | "cell_type": "markdown",
52 | "metadata": {},
53 | "source": [
54 | "## Many-objective algorithms\n",
55 | "\n",
56 | "As GDE3 and NSDE have been originally designed using NSGA-II Rank and Crowding survival, they perform poorly in problems with more than two objectives. However, simple modifications from a user perspective can solve this issue. Let us in pymoode denote problems with more than two objectives as many-objective problems.\n",
57 | "\n",
58 | "NSDE-R combines the survival operator of NSGA-III with the reproduction operators of DE , which leads to great performance in many-objective problems. GDE3-MNN is a variant of GDE3 proposed by Kukkonen and Deb that replaces original crowding distances of Rank and Crowding survival with an M-Nearest Neighbors based crowding metric with recursive elimination and re-calculation. It has improved a lot the performance of GDE3 in many-objective problems.\n",
59 | "\n",
60 | "\n",
61 | "
\n",
63 | "
"
64 | ]
65 | }
66 | ],
67 | "metadata": {
68 | "kernelspec": {
69 | "display_name": "venv",
70 | "language": "python",
71 | "name": "python3"
72 | },
73 | "language_info": {
74 | "codemirror_mode": {
75 | "name": "ipython",
76 | "version": 3
77 | },
78 | "file_extension": ".py",
79 | "mimetype": "text/x-python",
80 | "name": "python",
81 | "nbconvert_exporter": "python",
82 | "pygments_lexer": "ipython3",
83 | "version": "3.9.7 (tags/v3.9.7:1016ef3, Aug 30 2021, 20:19:38) [MSC v.1929 64 bit (AMD64)]"
84 | },
85 | "orig_nbformat": 4,
86 | "vscode": {
87 | "interpreter": {
88 | "hash": "b918b2b0974b3df50d2cb6c751b8eaae1bb466aaf5007e6a0fab3d6d8f3e624b"
89 | }
90 | }
91 | },
92 | "nbformat": 4,
93 | "nbformat_minor": 2
94 | }
95 |
--------------------------------------------------------------------------------
/docs/source/Theory/plots.py:
--------------------------------------------------------------------------------
1 | import matplotlib.pyplot as plt
2 |
3 |
4 | def plot_pairs_3d(first, second, colors=("indigo", "firebrick"), **kwargs):
5 |
6 | fig, ax = plt.subplots(1, 2, subplot_kw={'projection':'3d'}, **kwargs)
7 |
8 | ax[0].scatter(
9 | *first[1].T,
10 | color=colors[0], label=first[0], marker="o",
11 | )
12 | ax[0].set_ylabel("$f_2$")
13 | ax[0].set_xlabel("$f_1$")
14 | ax[0].set_zlabel("$f_3$")
15 | ax[0].legend()
16 |
17 | ax[1].scatter(
18 | *second[1].T,
19 | color=colors[1], label=second[0], marker="o",
20 | )
21 | ax[1].set_ylabel("$f_2$")
22 | ax[1].set_xlabel("$f_1$")
23 | ax[1].set_zlabel("$f_3$")
24 | ax[1].legend()
25 |
26 | ax[0].view_init(elev=30, azim=30)
27 | ax[1].view_init(elev=30, azim=30)
28 |
29 | fig.tight_layout()
30 | plt.show()
31 |
32 |
33 | def plot_pairs_2d(first, second, colors=("indigo", "firebrick"), **kwargs):
34 |
35 | fig, ax = plt.subplots(1, 2, **kwargs)
36 |
37 | ax[0].scatter(
38 | *first[1].T,
39 | color=colors[0], label=first[0], marker="o",
40 | )
41 | ax[0].set_ylabel("$f_2$")
42 | ax[0].set_xlabel("$f_1$")
43 | ax[0].legend()
44 |
45 | ax[1].scatter(
46 | *second[1].T,
47 | color=colors[1], label=second[0], marker="o",
48 | )
49 | ax[1].set_ylabel("$f_2$")
50 | ax[1].set_xlabel("$f_1$")
51 | ax[1].legend()
52 |
53 | fig.tight_layout()
54 | plt.show()
--------------------------------------------------------------------------------
/docs/source/Usage/Single-objective.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "attachments": {},
5 | "cell_type": "markdown",
6 | "metadata": {},
7 | "source": [
8 | "# Single-objective"
9 | ]
10 | },
11 | {
12 | "attachments": {},
13 | "cell_type": "markdown",
14 | "metadata": {},
15 | "source": [
16 | "Throughout this section, we will solve the classic Rastrigin problem imported from [pymoo](https://github.com/anyoptimization/pymoo/blob/main/pymoo/problems/single/rastrigin.py). Three strategies of how to instantiate it from scratch are also presented in the [complete tutorial](./Complete-tutorial.ipynb).\n",
17 | "\n",
18 | "Besides the original DE , in this example the PSO implementation from pymoo will also be used."
19 | ]
20 | },
21 | {
22 | "attachments": {},
23 | "cell_type": "markdown",
24 | "metadata": {},
25 | "source": [
26 | "**Rastrigin**\n",
27 | "\n",
28 | "$$\n",
29 | "\\begin{align}\n",
30 | " \\text{min} \\; \\; & f(\\boldsymbol{x}) = An + \\sum_{i=1}^{n}[x_i^2 - A \\, \\mathrm{cos}(2 \\pi x_i)]\\\\\n",
31 | " \\text{s.t.} \\; \\; & -5.12 \\leq x_i \\leq 5.12 & \\forall i \\in \\{ 1, 2 \\}\n",
32 | "\\end{align}\n",
33 | "$$"
34 | ]
35 | },
36 | {
37 | "cell_type": "code",
38 | "execution_count": 1,
39 | "metadata": {},
40 | "outputs": [],
41 | "source": [
42 | "from pymoo.algorithms.soo.nonconvex.pso import PSO\n",
43 | "from pymoo.optimize import minimize\n",
44 | "from pymoo.problems import get_problem\n",
45 | "from pymoode.algorithms import GDE3\n",
46 | "from pymoode.algorithms import DE"
47 | ]
48 | },
49 | {
50 | "cell_type": "code",
51 | "execution_count": 2,
52 | "metadata": {},
53 | "outputs": [],
54 | "source": [
55 | "problem = get_problem(\"rastrigin\")"
56 | ]
57 | },
58 | {
59 | "cell_type": "code",
60 | "execution_count": 3,
61 | "metadata": {},
62 | "outputs": [],
63 | "source": [
64 | "NGEN = 100\n",
65 | "POPSIZE = 20\n",
66 | "SEED = 3"
67 | ]
68 | },
69 | {
70 | "cell_type": "code",
71 | "execution_count": 4,
72 | "metadata": {},
73 | "outputs": [],
74 | "source": [
75 | "# DE Parameters\n",
76 | "CR = 0.5\n",
77 | "F = (0.3, 1.0)"
78 | ]
79 | },
80 | {
81 | "cell_type": "code",
82 | "execution_count": 5,
83 | "metadata": {},
84 | "outputs": [
85 | {
86 | "name": "stdout",
87 | "output_type": "stream",
88 | "text": [
89 | "[1.42108547e-14]\n",
90 | "[-9.85467210e-11 8.38205833e-09]\n"
91 | ]
92 | }
93 | ],
94 | "source": [
95 | "de = DE(pop_size=POPSIZE, variant=\"DE/rand/1/bin\", CR=CR, F=F)\n",
96 | "\n",
97 | "res_de = minimize(\n",
98 | " problem,\n",
99 | " de,\n",
100 | " ('n_gen', NGEN),\n",
101 | " seed=SEED,\n",
102 | " save_history=False,\n",
103 | " verbose=False,\n",
104 | ")\n",
105 | "\n",
106 | "print(res_de.F)\n",
107 | "print(res_de.X)"
108 | ]
109 | },
110 | {
111 | "cell_type": "code",
112 | "execution_count": 6,
113 | "metadata": {},
114 | "outputs": [
115 | {
116 | "name": "stdout",
117 | "output_type": "stream",
118 | "text": [
119 | "[2.00159889e-11]\n",
120 | "[-1.28686492e-07 2.90416106e-07]\n"
121 | ]
122 | }
123 | ],
124 | "source": [
125 | "pso = PSO(pop_size=POPSIZE)\n",
126 | "\n",
127 | "res_pso = minimize(\n",
128 | " problem,\n",
129 | " pso,\n",
130 | " ('n_gen', NGEN),\n",
131 | " seed=SEED,\n",
132 | " save_history=False,\n",
133 | " verbose=False,\n",
134 | ")\n",
135 | "\n",
136 | "print(res_pso.F)\n",
137 | "print(res_pso.X)"
138 | ]
139 | }
140 | ],
141 | "metadata": {
142 | "kernelspec": {
143 | "display_name": "venv",
144 | "language": "python",
145 | "name": "python3"
146 | },
147 | "language_info": {
148 | "codemirror_mode": {
149 | "name": "ipython",
150 | "version": 3
151 | },
152 | "file_extension": ".py",
153 | "mimetype": "text/x-python",
154 | "name": "python",
155 | "nbconvert_exporter": "python",
156 | "pygments_lexer": "ipython3",
157 | "version": "3.9.7 (tags/v3.9.7:1016ef3, Aug 30 2021, 20:19:38) [MSC v.1929 64 bit (AMD64)]"
158 | },
159 | "orig_nbformat": 4,
160 | "vscode": {
161 | "interpreter": {
162 | "hash": "6f648821e8b7b21234539644f05b8ed9c4fec624e513ce0931c304e76320dc94"
163 | }
164 | }
165 | },
166 | "nbformat": 4,
167 | "nbformat_minor": 2
168 | }
169 |
--------------------------------------------------------------------------------
/docs/source/Usage/index.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "attachments": {},
5 | "cell_type": "markdown",
6 | "metadata": {},
7 | "source": [
8 | "# Usage\n",
9 | "\n",
10 | "Here are linked examples for:\n",
11 | "\n",
12 | "- [Single-objective problems](./Single-objective.ipynb)\n",
13 | "- [Multi-objective problems](./Multi-objective.ipynb)\n",
14 | "- [Many-objective problems](./Many-objective.ipynb)\n",
15 | "- [Complete tutorial](./Complete-tutorial.ipynb)\n",
16 | "- [Portfolio selection](./Portfolio.ipynb)\n",
17 | "\n",
18 | "Enjoy!"
19 | ]
20 | }
21 | ],
22 | "metadata": {
23 | "kernelspec": {
24 | "display_name": "venv",
25 | "language": "python",
26 | "name": "python3"
27 | },
28 | "language_info": {
29 | "name": "python",
30 | "version": "3.9.7 (tags/v3.9.7:1016ef3, Aug 30 2021, 20:19:38) [MSC v.1929 64 bit (AMD64)]"
31 | },
32 | "orig_nbformat": 4,
33 | "vscode": {
34 | "interpreter": {
35 | "hash": "b918b2b0974b3df50d2cb6c751b8eaae1bb466aaf5007e6a0fab3d6d8f3e624b"
36 | }
37 | }
38 | },
39 | "nbformat": 4,
40 | "nbformat_minor": 2
41 | }
42 |
--------------------------------------------------------------------------------
/docs/source/algorithms.rst:
--------------------------------------------------------------------------------
1 | Algorithms
2 | ==========
3 |
4 | The general overview of algorithms is presented in this section, although for more usage details we
5 | suggest referring to `usage page `_.
6 |
7 |
8 | DE
9 | --
10 |
11 | Differential Evolution for single-objective problems proposed by
12 | Storn & Price [:cite:label:`de_article`].
13 | Other features later implemented are also present, such as dither, jitter, selection variants,
14 | and crossover strategies. For details see Price et al. [:cite:label:`de_book`].
15 |
16 |
17 | GDE3
18 | ----
19 |
20 | Generalized Differential Evolution 3, a multi-objective algorithm that combines
21 | DE mutation and crossover operators to NSGA-II [:cite:label:`nsga2`] survival with a hybrid type survival strategy.
22 | In this algorithm, individuals might be removed in a one-to-one comparison before truncating
23 | the population by the multi-objective survival operator. It was proposed by
24 | Kukkonen, S. & Lampinen, J. [:cite:label:`gde3`].
25 | Variants with M-Nearest Neighbors and 2-Nearest Neighbors survival [:cite:label:`gde3many`]
26 | are also available.
27 |
28 |
29 | NSDE
30 | ----
31 |
32 | Non-dominated Sorting Differential Evolution, a multi-objective algorithm that combines
33 | DE mutation and crossover operators to NSGA-II [:cite:label:`nsga2`] survival.
34 |
35 |
36 | NSDE-R
37 | ------
38 |
39 | Non-dominated Sorting Differential Evolution based on Reference directions [:cite:label:`nsder`].
40 | It is an algorithm for many-objective problems that works as an extension of NSDE
41 | using NSGA-III [:cite:label:`nsga3-part1`] [:cite:label:`nsga3-part2`] survival strategy.
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # For the full list of built-in configuration values, see the documentation:
4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
5 |
6 | # -- Project information -----------------------------------------------------
7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8 |
9 | project = 'pymoode'
10 | copyright = '2023, Bruno Scalia C. F. Leite'
11 | author = 'Bruno Scalia C. F. Leite'
12 | release = '0.2.4.1'
13 |
14 | # -- General configuration ---------------------------------------------------
15 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
16 |
17 | extensions = [
18 | 'sphinx.ext.mathjax',
19 | 'sphinx.ext.autodoc',
20 | 'sphinx.ext.autosummary',
21 | 'numpydoc',
22 | 'nbsphinx',
23 | 'sphinx.ext.intersphinx',
24 | 'sphinx.ext.coverage',
25 | 'matplotlib.sphinxext.plot_directive',
26 | 'IPython.sphinxext.ipython_console_highlighting',
27 | 'sphinxcontrib.bibtex',
28 | ]
29 |
30 | bibtex_bibfiles = ['references.bib']
31 | bibtex_reference_style = 'label'
32 |
33 | templates_path = ['_templates']
34 | exclude_patterns = []
35 | autoclass_content = 'both'
36 |
37 | intersphinx_mapping = {
38 | 'python': ('http://docs.python.org/2', None),
39 | 'numpy': ('http://docs.scipy.org/doc/numpy', "http://docs.scipy.org/doc/numpy/objects.inv"),
40 | 'scipy': ('http://docs.scipy.org/doc/scipy/reference', None),
41 | 'matplotlib': ('http://matplotlib.sourceforge.net', None),
42 | 'pymoo': ('http://pymoo.org', None)
43 | }
44 |
45 |
46 | # -- Options for HTML output -------------------------------------------------
47 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
48 |
49 | html_theme = 'sphinx_rtd_theme'
50 | html_static_path = ['_static']
51 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | .. pymoode documentation master file, created by
2 | sphinx-quickstart on Tue Jan 3 02:07:17 2023.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | .. image:: https://github.com/mooscaliaproject/pymoode/raw/main/images/logo_header_pymoode.png
7 | :alt: header
8 | :align: center
9 | :target: https://github.com/mooscaliaproject/pymoode
10 |
11 |
12 | pymoode: Differential Evolution in Python
13 | =========================================
14 |
15 | A Python framework for Differential Evolution using `pymoo `_ [:cite:label:`pymoo`].
16 |
17 |
18 | Install
19 | -------
20 |
21 | First, make sure you have a Python 3 environment installed.
22 |
23 | From PyPi:
24 |
25 | .. code:: bash
26 |
27 | pip install pymoode
28 |
29 |
30 | From the current version on github:
31 |
32 | .. code:: bash
33 |
34 | pip install -e git+https://github.com/mooscalia/pymoode#egg=pymoode
35 |
36 |
37 | New features
38 | ------------
39 |
40 | This package was written as an extension of pymoo, providing some additional
41 | features for DE algorithms and survival operators. One might refer to the sections
42 | :doc:`Algorithms `, `Survival `_ and
43 | `Rank and Crowding `_ for more details.
44 |
45 | For instance, these solutions for the DTLZ2 problem were obtained using GDE3 with the M-Nearest Neighbors crowding metric.
46 |
47 | .. image:: https://github.com/mooscaliaproject/pymoode/raw/main/images/gde3mnn_example.gif
48 | :alt: header
49 | :align: center
50 | :width: 500
51 | :target: https://github.com/mooscaliaproject/pymoode
52 |
53 |
54 | .. toctree::
55 | :maxdepth: 2
56 | :caption: Algorithms
57 |
58 | algorithms
59 |
60 |
61 | .. toctree::
62 | :maxdepth: 1
63 | :caption: Usage
64 |
65 | Usage/Complete-tutorial.ipynb
66 | Usage/Single-objective.ipynb
67 | Usage/Multi-objective.ipynb
68 | Usage/Many-objective.ipynb
69 | Usage/Portfolio.ipynb
70 |
71 |
72 | .. toctree::
73 | :maxdepth: 1
74 | :caption: Theory
75 |
76 | Theory/DE.ipynb
77 | Theory/Survival.ipynb
78 | Theory/Rank-and-Crowding.ipynb
79 |
80 |
81 | .. toctree::
82 | :maxdepth: 3
83 | :caption: Package
84 |
85 | modules
86 |
87 |
88 | Citation
89 | ========
90 |
91 | This package was developed as part of an academic optimization project [:cite:label:`pymoodestyrene`], as well as pymoo [:cite:label:`pymoo`].
92 | Please, if you use it for research purposes, cite it accordingly:
93 |
94 | `Blank, J. & Deb, K., 2020. pymoo: Multi-Objective Optimization in Python. IEEE Access, Volume 8, pp. 89497-89509. doi:10.1109/ACCESS.2020.2990567. `_
95 |
96 | `Leite, B., Costa, A. O. S., Costa, E. F., 2023. Multi-objective optimization of adiabatic styrene reactors using Generalized Differential Evolution 3 (GDE3). Chem. Eng. Sci., Volume 265, Article 118196. doi:10.1016/j.ces.2022.118196. `_
97 |
98 |
99 | References
100 | ==========
101 |
102 | To the complete reference list, please refer to :doc:`this page `.
103 |
--------------------------------------------------------------------------------
/docs/source/modules.rst:
--------------------------------------------------------------------------------
1 | pymoode
2 | =======
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 |
7 | pymoode.algorithms
8 | pymoode.survival
9 | pymoode.performance
10 | pymoode.operators
11 |
--------------------------------------------------------------------------------
/docs/source/pymoode.algorithms.rst:
--------------------------------------------------------------------------------
1 | Algorithms
2 | ==========
3 |
4 | pymoode.algorithms
5 | ------------------
6 |
7 | .. automodule:: pymoode.algorithms
8 | :members: DE, GDE3, NSDE, NSDER
9 | :undoc-members:
10 | :show-inheritance:
11 |
12 |
13 | pymoode.algorithms.base
14 | -----------------------
15 |
16 | .. autoclass:: pymoode.algorithms.base.evolutionary.EvolutionaryAlgorithm
17 | .. autoclass:: pymoode.algorithms.base.genetic.GeneticAlgorithm
18 |
19 | .. automodule:: pymoode.algorithms.base.genetic
20 | :members:
21 | :show-inheritance:
--------------------------------------------------------------------------------
/docs/source/pymoode.cython.rst:
--------------------------------------------------------------------------------
1 | pymoode.cython package
2 | ======================
3 |
4 | Submodules
5 | ----------
6 |
7 | pymoode.cython.info module
8 | --------------------------
9 |
10 | .. automodule:: pymoode.cython.info
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
15 | pymoode.cython.mnn module
16 | -------------------------
17 |
18 | .. automodule:: pymoode.cython.mnn
19 | :members:
20 | :undoc-members:
21 | :show-inheritance:
22 |
23 | pymoode.cython.pruning\_cd module
24 | ---------------------------------
25 |
26 | .. automodule:: pymoode.cython.pruning_cd
27 | :members:
28 | :undoc-members:
29 | :show-inheritance:
30 |
31 | pymoode.cython.spacing\_neighbors module
32 | ----------------------------------------
33 |
34 | .. automodule:: pymoode.cython.spacing_neighbors
35 | :members:
36 | :undoc-members:
37 | :show-inheritance:
38 |
39 | Module contents
40 | ---------------
41 |
42 | .. automodule:: pymoode.cython
43 | :members:
44 | :undoc-members:
45 | :show-inheritance:
46 |
--------------------------------------------------------------------------------
/docs/source/pymoode.misc.rst:
--------------------------------------------------------------------------------
1 | pymoode.misc package
2 | ====================
3 |
4 | Submodules
5 | ----------
6 |
7 | pymoode.misc.mnn module
8 | -----------------------
9 |
10 | .. automodule:: pymoode.misc.mnn
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
15 | pymoode.misc.pruning\_cd module
16 | -------------------------------
17 |
18 | .. automodule:: pymoode.misc.pruning_cd
19 | :members:
20 | :undoc-members:
21 | :show-inheritance:
22 |
23 | Module contents
24 | ---------------
25 |
26 | .. automodule:: pymoode.misc
27 | :members:
28 | :undoc-members:
29 | :show-inheritance:
30 |
--------------------------------------------------------------------------------
/docs/source/pymoode.operators.rst:
--------------------------------------------------------------------------------
1 | Operators
2 | =========
3 |
4 | pymoode.operators.des
5 | ---------------------
6 |
7 | .. autoclass:: pymoode.operators.des.DES
8 |
9 |
10 | pymoode.operators.dex
11 | ---------------------
12 |
13 | .. autoclass:: pymoode.operators.dex.DEX
14 | .. autoclass:: pymoode.operators.dex.DEM
15 |
16 |
--------------------------------------------------------------------------------
/docs/source/pymoode.performance.rst:
--------------------------------------------------------------------------------
1 | Indicators
2 | ==========
3 |
4 | pymoode.performance
5 | -------------------
6 |
7 | .. autoclass:: pymoode.performance.SpacingIndicator
8 |
--------------------------------------------------------------------------------
/docs/source/pymoode.rst:
--------------------------------------------------------------------------------
1 | pymoode
2 | =======
3 |
4 | Modules
5 | -------
6 |
7 | .. toctree::
8 | :maxdepth: 2
9 |
10 | pymoode.algorithms
11 | pymoode.operators
12 | pymoode.performance
13 | pymoode.survival
14 |
15 |
--------------------------------------------------------------------------------
/docs/source/pymoode.survival.rst:
--------------------------------------------------------------------------------
1 | Survival
2 | ========
3 |
4 | pymoode.survival
5 | ----------------
6 |
7 | Rank and Crowding
8 | ~~~~~~~~~~~~~~~~~
9 |
10 | .. autoclass:: pymoode.survival.RankAndCrowding
11 | .. autoclass:: pymoode.survival.ConstrRankAndCrowding
12 |
13 |
14 | Replacement
15 | ~~~~~~~~~~~
16 |
17 | .. autoclass:: pymoode.survival.replacement.BaseReplacement
18 | .. autoclass:: pymoode.survival.replacement.ImproveReplacement
--------------------------------------------------------------------------------
/docs/source/references.bib:
--------------------------------------------------------------------------------
1 | @article{pymoo,
2 | author={J. {Blank} and K. {Deb}},
3 | journal={IEEE Access},
4 | title={{p}ymoo: Multi-Objective Optimization in Python},
5 | year={2020},
6 | volume={8},
7 | pages={89497-89509},
8 | doi={10.1109/ACCESS.2020.2990567}
9 | }
10 |
11 |
12 | @article{pymoodestyrene,
13 | title={Multi-objective optimization of adiabatic styrene reactors using Generalized Differential Evolution 3 ({GDE3})},
14 | author={Leite, Bruno and da Costa, Andr{\'e}a Oliveira Souza and da Costa Junior, Esly Ferreira},
15 | journal={Chemical Engineering Science},
16 | volume={265},
17 | pages={118196},
18 | year={2023},
19 | publisher={Elsevier},
20 | doi={10.1016/j.ces.2022.118196}
21 | }
22 |
23 |
24 | @article{de_article,
25 | title={Differential evolution--a simple and efficient heuristic for global optimization over continuous spaces},
26 | author={Storn, Rainer and Price, Kenneth},
27 | journal={Journal of global optimization},
28 | volume={11},
29 | number={4},
30 | pages={341--359},
31 | year={1997},
32 | publisher={Springer}
33 | }
34 |
35 |
36 | @book{de_book,
37 | title={Differential evolution: a practical approach to global optimization},
38 | author={Price, Kenneth and Storn, Rainer M and Lampinen, Jouni A},
39 | year={2005},
40 | publisher={Springer Science \& Business Media}
41 | }
42 |
43 |
44 | @book{multi_objective_book,
45 | author={Deb, Kalyanmoy and Kalyanmoy, Deb},
46 | title={Multi-Objective Optimization Using Evolutionary Algorithms},
47 | year={2001},
48 | isbn={047187339X},
49 | publisher={John Wiley \& Sons, Inc.},
50 | address={New York, NY, USA},
51 | }
52 |
53 |
54 | @article{nsga2,
55 | author={Deb, K. and Pratap, A. and Agarwal, S. and Meyarivan, T.},
56 | title={A Fast and Elitist Multiobjective Genetic Algorithm: NSGA-{II}},
57 | journal={Trans. Evol. Comp},
58 | issue_date={April 2002},
59 | volume={6},
60 | number={2},
61 | year={2002},
62 | issn={1089-778X},
63 | pages={182--197},
64 | numpages={16},
65 | url={http://dx.doi.org/10.1109/4235.996017},
66 | doi={10.1109/4235.996017},
67 | acmid={2221582},
68 | publisher={IEEE Press},
69 | address={Piscataway, NJ, USA},
70 | }
71 |
72 |
73 | @article{ref_dirs_blank,
74 | author={J. {Blank} and K. {Deb} and Y. {Dhebar} and S. {Bandaru} and H. {Seada}},
75 | journal={IEEE Transactions on Evolutionary Computation},
76 | title={Generating Well-Spaced Points on a Unit Simplex for Evolutionary Many-Objective Optimization},
77 | year={2021},
78 | volume={25},
79 | number={1},
80 | pages={48-60},
81 | doi={10.1109/TEVC.2020.2992387}
82 | }
83 |
84 |
85 | @inproceedings{gde3,
86 | title={GDE3: The third evolution step of generalized differential evolution},
87 | author={Kukkonen, Saku and Lampinen, Jouni},
88 | booktitle={2005 IEEE congress on evolutionary computation},
89 | volume={1},
90 | pages={443--450},
91 | year={2005},
92 | organization={IEEE}
93 | }
94 |
95 |
96 | @inproceedings{gde3pruning,
97 | title={Improved pruning of non-dominated solutions based on crowding distance for bi-objective optimization problems},
98 | author={Kukkonen, Saku and Deb, Kalyanmoy},
99 | booktitle={2006 IEEE International Conference on Evolutionary Computation},
100 | pages={1179--1186},
101 | year={2006},
102 | organization={IEEE}
103 | }
104 |
105 |
106 | @incollection{gde3many,
107 | title={A fast and effective method for pruning of non-dominated solutions in many-objective problems},
108 | author={Kukkonen, Saku and Deb, Kalyanmoy},
109 | booktitle={Parallel problem solving from nature-PPSN IX},
110 | pages={553--562},
111 | year={2006},
112 | publisher={Springer}
113 | }
114 |
115 |
116 | @article{mosade,
117 | title={Multi-objective self-adaptive differential evolution with elitist archive and crowding entropy-based diversity measure},
118 | author={Wang, Yao-Nan and Wu, Liang-Hong and Yuan, Xiao-Fang},
119 | journal={Soft Computing},
120 | volume={14},
121 | number={3},
122 | pages={193--209},
123 | year={2010},
124 | publisher={Springer}
125 | }
126 |
127 |
128 | @article{nsder,
129 | title={Many-objective differential evolution optimization based on reference points: NSDE-R},
130 | author={Reddy, Sohail R and Dulikravich, George S},
131 | journal={Structural and Multidisciplinary Optimization},
132 | volume={60},
133 | number={4},
134 | pages={1455--1473},
135 | year={2019},
136 | publisher={Springer}
137 | }
138 |
139 |
140 | @article{nsga3-part1,
141 | title={An evolutionary many-objective optimization algorithm using reference-point-based nondominated sorting approach, Part {I}: Solving problems with box constraints},
142 | author={Kalyanmoy Deb and Himanshu Jain},
143 | year={2014},
144 | doi={10.1109/TEVC.2013.2281535},
145 | volume={18},
146 | pages={577-601},
147 | journal={IEEE Transactions on Evolutionary Computation},
148 | issn={1089-778X},
149 | publisher={Institute of Electrical and Electronics Engineers Inc.},
150 | number={4},
151 | }
152 |
153 |
154 | @article{nsga3-part2,
155 | author={H. Jain and K. Deb},
156 | journal={IEEE Transactions on Evolutionary Computation},
157 | title={An Evolutionary Many-Objective Optimization Algorithm Using Reference-Point Based Nondominated Sorting Approach, Part {II}: Handling Constraints and Extending to an Adaptive Approach},
158 | year={2014},
159 | volume={18},
160 | number={4},
161 | pages={602-622},
162 | ISSN={1089-778X},
163 | }
164 |
165 |
166 | @article{de_crossover,
167 | title={Influence of crossover on the behavior of Differential Evolution Algorithms},
168 | journal={Applied Soft Computing},
169 | volume={9},
170 | number={3},
171 | pages={1126-1138},
172 | year={2009},
173 | issn={1568-4946},
174 | doi={https://doi.org/10.1016/j.asoc.2009.02.012},
175 | url={https://www.sciencedirect.com/science/article/pii/S1568494609000325},
176 | author={Daniela Zaharie},
177 | keywords={Differential Evolution, Binomial crossover, Exponential crossover, Parameter control, Self-adaptation},
178 | }
179 |
180 | @article{pso,
181 | title={Particle swarm optimization},
182 | author={Poli, Riccardo and Kennedy, James and Blackwell, Tim},
183 | journal={Swarm intelligence},
184 | volume={1},
185 | number={1},
186 | pages={33-57},
187 | year={2007},
188 | publisher={Springer}
189 | }
190 |
191 |
192 | @article{portfolio,
193 | ISSN={00221082, 15406261},
194 | URL={http://www.jstor.org/stable/2975974},
195 | author={Harry Markowitz},
196 | journal={The Journal of Finance},
197 | number={1},
198 | pages={77-91},
199 | publisher={[American Finance Association, Wiley]},
200 | title={Portfolio Selection},
201 | volume={7},
202 | year={1952},
203 | doi={10.2307/2975974},
204 | }
--------------------------------------------------------------------------------
/docs/source/references.rst:
--------------------------------------------------------------------------------
1 | References
2 | ==========
3 |
4 |
5 | .. only:: html
6 |
7 | .. bibliography::
8 | :style: unsrt
9 | :all:
10 |
--------------------------------------------------------------------------------
/images/gde3mnn_example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooscaliaproject/pymoode/521d591263ceac29d5236b3bc62a482049a18f86/images/gde3mnn_example.gif
--------------------------------------------------------------------------------
/images/gde3mnn_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooscaliaproject/pymoode/521d591263ceac29d5236b3bc62a482049a18f86/images/gde3mnn_example.png
--------------------------------------------------------------------------------
/images/general_de.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooscaliaproject/pymoode/521d591263ceac29d5236b3bc62a482049a18f86/images/general_de.png
--------------------------------------------------------------------------------
/images/logo_header_pymoode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooscaliaproject/pymoode/521d591263ceac29d5236b3bc62a482049a18f86/images/logo_header_pymoode.png
--------------------------------------------------------------------------------
/images/logo_pymoode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooscaliaproject/pymoode/521d591263ceac29d5236b3bc62a482049a18f86/images/logo_pymoode.png
--------------------------------------------------------------------------------
/images/moo_gde3_f_video.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooscaliaproject/pymoode/521d591263ceac29d5236b3bc62a482049a18f86/images/moo_gde3_f_video.gif
--------------------------------------------------------------------------------
/images/rastrigin.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooscaliaproject/pymoode/521d591263ceac29d5236b3bc62a482049a18f86/images/rastrigin.gif
--------------------------------------------------------------------------------
/images/soo_video.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooscaliaproject/pymoode/521d591263ceac29d5236b3bc62a482049a18f86/images/soo_video.gif
--------------------------------------------------------------------------------
/images/tnk_gde3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooscaliaproject/pymoode/521d591263ceac29d5236b3bc62a482049a18f86/images/tnk_gde3.png
--------------------------------------------------------------------------------
/notebooks/Single-objective.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "attachments": {},
5 | "cell_type": "markdown",
6 | "metadata": {},
7 | "source": [
8 | "# Single-objective"
9 | ]
10 | },
11 | {
12 | "attachments": {},
13 | "cell_type": "markdown",
14 | "metadata": {},
15 | "source": [
16 | "Throughout this section, we will solve the classic Rastrigin problem imported from [pymoo](https://github.com/anyoptimization/pymoo/blob/main/pymoo/problems/single/rastrigin.py). Three strategies of how to instantiate it from scratch are also presented in the [complete tutorial](./Complete-tutorial.ipynb).\n",
17 | "\n",
18 | "Besides the original DE , in this example the PSO implementation from pymoo will also be used."
19 | ]
20 | },
21 | {
22 | "attachments": {},
23 | "cell_type": "markdown",
24 | "metadata": {},
25 | "source": [
26 | "**Rastrigin**\n",
27 | "\n",
28 | "$$\n",
29 | "\\begin{align}\n",
30 | " \\text{min} \\; \\; & f(\\boldsymbol{x}) = An + \\sum_{i=1}^{n}[x_i^2 - A \\, \\mathrm{cos}(2 \\pi x_i)]\\\\\n",
31 | " \\text{s.t.} \\; \\; & -5.12 \\leq x_i \\leq 5.12 & \\forall i \\in \\{ 1, 2 \\}\n",
32 | "\\end{align}\n",
33 | "$$"
34 | ]
35 | },
36 | {
37 | "cell_type": "code",
38 | "execution_count": 1,
39 | "metadata": {},
40 | "outputs": [],
41 | "source": [
42 | "from pymoo.algorithms.soo.nonconvex.pso import PSO\n",
43 | "from pymoo.optimize import minimize\n",
44 | "from pymoo.problems import get_problem\n",
45 | "from pymoode.algorithms import DE"
46 | ]
47 | },
48 | {
49 | "cell_type": "code",
50 | "execution_count": 2,
51 | "metadata": {},
52 | "outputs": [],
53 | "source": [
54 | "problem = get_problem(\"rastrigin\")"
55 | ]
56 | },
57 | {
58 | "cell_type": "code",
59 | "execution_count": 3,
60 | "metadata": {},
61 | "outputs": [],
62 | "source": [
63 | "NGEN = 100\n",
64 | "POPSIZE = 20\n",
65 | "SEED = 3"
66 | ]
67 | },
68 | {
69 | "cell_type": "code",
70 | "execution_count": 4,
71 | "metadata": {},
72 | "outputs": [],
73 | "source": [
74 | "# DE Parameters\n",
75 | "CR = 0.5\n",
76 | "F = (0.3, 1.0)"
77 | ]
78 | },
79 | {
80 | "cell_type": "code",
81 | "execution_count": 5,
82 | "metadata": {},
83 | "outputs": [
84 | {
85 | "name": "stdout",
86 | "output_type": "stream",
87 | "text": [
88 | "[1.42108547e-14]\n",
89 | "[-9.85467210e-11 8.38205833e-09]\n"
90 | ]
91 | }
92 | ],
93 | "source": [
94 | "de = DE(pop_size=POPSIZE, variant=\"DE/rand/1/bin\", CR=CR, F=F)\n",
95 | "\n",
96 | "res_de = minimize(\n",
97 | " problem,\n",
98 | " de,\n",
99 | " ('n_gen', NGEN),\n",
100 | " seed=SEED,\n",
101 | " save_history=False,\n",
102 | " verbose=False,\n",
103 | ")\n",
104 | "\n",
105 | "print(res_de.F)\n",
106 | "print(res_de.X)"
107 | ]
108 | },
109 | {
110 | "cell_type": "code",
111 | "execution_count": 6,
112 | "metadata": {},
113 | "outputs": [
114 | {
115 | "name": "stdout",
116 | "output_type": "stream",
117 | "text": [
118 | "[2.00159889e-11]\n",
119 | "[-1.28686492e-07 2.90416106e-07]\n"
120 | ]
121 | }
122 | ],
123 | "source": [
124 | "pso = PSO(pop_size=POPSIZE)\n",
125 | "\n",
126 | "res_pso = minimize(\n",
127 | " problem,\n",
128 | " pso,\n",
129 | " ('n_gen', NGEN),\n",
130 | " seed=SEED,\n",
131 | " save_history=False,\n",
132 | " verbose=False,\n",
133 | ")\n",
134 | "\n",
135 | "print(res_pso.F)\n",
136 | "print(res_pso.X)"
137 | ]
138 | },
139 | {
140 | "cell_type": "code",
141 | "execution_count": null,
142 | "metadata": {},
143 | "outputs": [],
144 | "source": []
145 | }
146 | ],
147 | "metadata": {
148 | "kernelspec": {
149 | "display_name": "venv",
150 | "language": "python",
151 | "name": "python3"
152 | },
153 | "language_info": {
154 | "codemirror_mode": {
155 | "name": "ipython",
156 | "version": 3
157 | },
158 | "file_extension": ".py",
159 | "mimetype": "text/x-python",
160 | "name": "python",
161 | "nbconvert_exporter": "python",
162 | "pygments_lexer": "ipython3",
163 | "version": "3.9.7"
164 | },
165 | "orig_nbformat": 4,
166 | "vscode": {
167 | "interpreter": {
168 | "hash": "6f648821e8b7b21234539644f05b8ed9c4fec624e513ce0931c304e76320dc94"
169 | }
170 | }
171 | },
172 | "nbformat": 4,
173 | "nbformat_minor": 2
174 | }
175 |
--------------------------------------------------------------------------------
/pymoode/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooscaliaproject/pymoode/521d591263ceac29d5236b3bc62a482049a18f86/pymoode/__init__.py
--------------------------------------------------------------------------------
/pymoode/algorithms/__init__.py:
--------------------------------------------------------------------------------
1 | from pymoode.algorithms.de import DifferentialEvolution
2 | from pymoode.algorithms.de import DE
3 | from pymoode.algorithms.gde3 import GDE3, GDE3MNN, GDE32NN, GDE3P
4 | from pymoode.algorithms.nsde import NSDE
5 | from pymoode.algorithms.nsder import NSDER
6 |
--------------------------------------------------------------------------------
/pymoode/algorithms/base/__init__.py:
--------------------------------------------------------------------------------
1 | from pymoode.algorithms.base.evolutionary import EvolutionaryAlgorithm
2 | from pymoode.algorithms.base.differential import DifferentialEvolution
3 | from pymoode.algorithms.base.genetic import GeneticAlgorithm
--------------------------------------------------------------------------------
/pymoode/algorithms/base/differential.py:
--------------------------------------------------------------------------------
1 | # Native
2 | from typing import Optional
3 |
4 | # External
5 | import numpy as np
6 |
7 | # pymoo imports
8 | from pymoo.operators.sampling.lhs import LHS
9 | from pymoo.termination.default import DefaultSingleObjectiveTermination, DefaultMultiObjectiveTermination
10 | from pymoo.util.display.single import SingleObjectiveOutput
11 | from pymoo.util.display.multi import MultiObjectiveOutput
12 | from pymoo.util.misc import has_feasible
13 |
14 | # pymoode imports
15 | from pymoode.operators.variant import DifferentialVariant
16 | from pymoode.algorithms.base.evolutionary import EvolutionaryAlgorithm
17 | from pymoode.survival.replacement import ImprovementReplacement
18 | from pymoode.survival import RankAndCrowding
19 |
20 |
21 | # =========================================================================================================
22 | # Implementation
23 | # =========================================================================================================
24 |
25 | class DifferentialEvolution(EvolutionaryAlgorithm):
26 |
27 | def __init__(self,
28 | pop_size=100,
29 | sampling=LHS(),
30 | variant="DE/rand/1/bin",
31 | CR=0.7,
32 | F=(0.5, 1.0),
33 | gamma=1e-4,
34 | de_repair="bounce-back",
35 | survival=ImprovementReplacement(),
36 | advance_after_initial_infill=True,
37 | output=SingleObjectiveOutput(),
38 | **kwargs):
39 | """
40 | Base class for Differential Evolution algorithms
41 |
42 | Parameters
43 | ----------
44 | pop_size : int, optional
45 | Population size. Defaults to 100.
46 |
47 | sampling : Sampling, optional
48 | Sampling strategy of pymoo. Defaults to LHS().
49 |
50 | variant : str, optional
51 | Differential evolution strategy. Must be a string in the format: "DE/selection/n/crossover", in which, n in an integer of number of difference vectors, and crossover is either 'bin' or 'exp'. Selection variants are:
52 |
53 | - 'ranked'
54 | - 'rand'
55 | - 'best'
56 | - 'current-to-best'
57 | - 'current-to-best'
58 | - 'current-to-rand'
59 | - 'rand-to-best'
60 |
61 | The selection strategy 'ranked' might be helpful to improve convergence speed without much harm to diversity. Defaults to 'DE/rand/1/bin'.
62 |
63 | CR : float, optional
64 | Crossover parameter. Defined in the range [0, 1]
65 | To reinforce mutation, use higher values. To control convergence speed, use lower values.
66 |
67 | F : iterable of float or float, optional
68 | Scale factor or mutation parameter. Defined in the range (0, 2]
69 | To reinforce exploration, use higher values; for exploitation, use lower values.
70 |
71 | gamma : float, optional
72 | Jitter deviation parameter. Should be in the range (0, 2). Defaults to 1e-4.
73 |
74 | de_repair : str, optional
75 | Repair of DE mutant vectors. Is either callable or one of:
76 |
77 | - 'bounce-back'
78 | - 'midway'
79 | - 'rand-init'
80 | - 'to-bounds'
81 |
82 | If callable, has the form fun(X, Xb, xl, xu) in which X contains mutated vectors including violations, Xb contains reference vectors for repair in feasible space, xl is a 1d vector of lower bounds, and xu a 1d vector of upper bounds.
83 | Defaults to 'bounce-back'.
84 |
85 | genetic_mutation, optional
86 | Pymoo's genetic mutation operator after crossover. Defaults to NoMutation().
87 |
88 | survival : Survival, optional
89 | Replacement survival operator. Defaults to ImprovementReplacement().
90 |
91 | repair : Repair, optional
92 | Pymoo's repair operator after mutation. Defaults to NoRepair().
93 | """
94 |
95 | # Mating
96 | mating = DifferentialVariant(
97 | variant=variant, CR=CR, F=F, gamma=gamma,
98 | de_repair=de_repair, **kwargs,
99 | )
100 |
101 | # Number of offsprings at each generation
102 | n_offsprings = pop_size
103 |
104 | super().__init__(
105 | pop_size=pop_size,
106 | sampling=sampling,
107 | mating=mating,
108 | n_offsprings=n_offsprings,
109 | eliminate_duplicates=None,
110 | survival=survival,
111 | output=output,
112 | advance_after_initial_infill=advance_after_initial_infill,
113 | **kwargs,
114 | )
115 |
116 | self.termination = DefaultSingleObjectiveTermination()
117 |
118 | def _initialize_advance(self, infills=None, **kwargs):
119 | self.pop = self.survival.do(self.problem, infills, None, n_survive=self.pop_size)
120 |
121 | def _infill(self):
122 | infills = self.mating(self.problem, self.pop, self.n_offsprings, algorithm=self)
123 | return infills
124 |
125 | def _advance(self, infills=None, **kwargs):
126 |
127 | assert infills is not None, "This algorithms uses the AskAndTell interface thus infills must be provided."
128 |
129 | # One-to-one replacement survival
130 | self.pop = self.survival.do(self.problem, self.pop, infills)
131 |
132 | def _set_optimum(self, **kwargs):
133 | if not has_feasible(self.pop):
134 | self.opt = self.pop[[np.argmin(self.pop.get("CV"))]]
135 | else:
136 | self.opt = self.pop[self.pop.get("rank") == 0]
137 |
138 |
139 | class MODE(DifferentialEvolution):
140 |
141 | def __init__(self,
142 | pop_size=100,
143 | sampling=LHS(),
144 | variant="DE/rand/1/bin",
145 | CR=0.5,
146 | F=None,
147 | gamma=0.0001,
148 | de_repair="bounce-back",
149 | survival=RankAndCrowding(),
150 | output=MultiObjectiveOutput(),
151 | **kwargs):
152 |
153 | super().__init__(
154 | pop_size=pop_size,
155 | sampling=sampling,
156 | variant=variant,
157 | CR=CR,
158 | F=F,
159 | gamma=gamma,
160 | de_repair=de_repair,
161 | survival=survival,
162 | output=output,
163 | **kwargs,
164 | )
165 |
166 | self.termination = DefaultMultiObjectiveTermination()
167 |
168 | def _initialize_advance(self, infills=None, **kwargs):
169 | self.pop = self.survival.do(self.problem, infills, None, n_survive=self.pop_size)
170 |
--------------------------------------------------------------------------------
/pymoode/algorithms/base/evolutionary.py:
--------------------------------------------------------------------------------
1 | # Native
2 | from typing import Optional
3 |
4 | # pymoo imports
5 | from pymoo.core.algorithm import Algorithm
6 | from pymoo.core.duplicate import DefaultDuplicateElimination, NoDuplicateElimination
7 | from pymoo.core.initialization import Initialization
8 | from pymoo.core.infill import InfillCriterion
9 | from pymoo.core.population import Population
10 | from pymoo.core.repair import NoRepair, Repair
11 | from pymoo.core.survival import Survival
12 | from pymoo.core.sampling import Sampling
13 | from pymoo.core.mating import Mating
14 |
15 |
16 | # =========================================================================================================
17 | # Implementation
18 | # =========================================================================================================
19 |
20 | class EvolutionaryAlgorithm(Algorithm):
21 |
22 | def __init__(
23 | self,
24 | pop_size: Optional[int] = None,
25 | sampling: Optional[Sampling] = None,
26 | mating: Optional[Mating] = None,
27 | survival: Optional[Survival] = None,
28 | n_offsprings: Optional[int] = None,
29 | eliminate_duplicates: bool = True,
30 | repair: Optional[Repair] = None,
31 | advance_after_initial_infill: bool = False,
32 | **kwargs
33 | ):
34 | """Base class for Evolutionary Algorithms
35 |
36 | Parameters
37 | ----------
38 | pop_size : int, optional
39 | Population size, by default None
40 |
41 | sampling : Sampling, optional
42 | pymoo Sampling instance, by default None
43 |
44 | mating : InfillCriterion, optional
45 | pymoo mating operator, by default None
46 |
47 | survival : Survival, optional
48 | pymoo survival operator, by default None
49 |
50 | n_offsprings : int, optional
51 | Number of offspring individuals created at each generation, by default None
52 |
53 | eliminate_duplicates : DuplicateElimination | bool | None, optional
54 | Eliminate duplicates in mating, by default True
55 |
56 | repair : Repair, optional
57 | pymoo repair operator. In the algorithm level it should be called when sampling.
58 | It is recommended that mating operators also have some repair associated with. By default None
59 |
60 | advance_after_initial_infill : bool, optional
61 | Either or not apply survival after initialization, by default False
62 | """
63 |
64 | super().__init__(**kwargs)
65 |
66 | # the population size used
67 | self.pop_size = pop_size
68 |
69 | # whether the algorithm should be advanced after initialization of not
70 | self.advance_after_initial_infill = advance_after_initial_infill
71 |
72 | # the survival for the genetic algorithm
73 | self.survival = survival
74 |
75 | # number of offsprings to generate through recombination
76 | self.n_offsprings = n_offsprings
77 |
78 | # if the number of offspring is not set - equal to population size
79 | if self.n_offsprings is None:
80 | self.n_offsprings = pop_size
81 |
82 | # set the duplicate detection class - a boolean value chooses the default duplicate detection
83 | if isinstance(eliminate_duplicates, bool):
84 | if eliminate_duplicates:
85 | self.eliminate_duplicates = DefaultDuplicateElimination()
86 | else:
87 | self.eliminate_duplicates = NoDuplicateElimination()
88 | else:
89 | self.eliminate_duplicates = eliminate_duplicates
90 |
91 | # simply set the no repair object if it is None
92 | self.repair = repair if repair is not None else NoRepair()
93 |
94 | self.initialization = Initialization(
95 | sampling, repair=self.repair,
96 | eliminate_duplicates=self.eliminate_duplicates,
97 | )
98 |
99 | if mating is None:
100 | mating = InfillCriterion(
101 | repair=self.repair,
102 | eliminate_duplicates=self.eliminate_duplicates,
103 | )
104 | self.mating = mating
105 |
106 | # other run specific data updated whenever solve is called - to share them in all algorithms
107 | self.n_gen = None
108 | self.pop = None
109 | self.off = None
110 |
111 | def _initialize_infill(self):
112 | pop = self.initialization.do(self.problem, self.pop_size, algorithm=self)
113 | return pop
114 |
115 | def _initialize_advance(self, infills=None, **kwargs):
116 | if self.advance_after_initial_infill:
117 | self.pop = self.survival.do(self.problem, infills, n_survive=len(infills), algorithm=self, **kwargs)
118 |
119 | def _infill(self):
120 |
121 | # do the mating using the current population
122 | off = self.mating.do(self.problem, self.pop, self.n_offsprings, algorithm=self)
123 |
124 | # if the mating could not generate any new offspring (duplicate elimination might make that happen)
125 | if len(off) == 0:
126 | self.termination.force_termination = True
127 | return
128 |
129 | # if not the desired number of offspring could be created
130 | elif len(off) < self.n_offsprings:
131 | if self.verbose:
132 | print("WARNING: Mating could not produce the required number of (unique) offsprings!")
133 |
134 | return off
135 |
136 | def _advance(self, infills=None, **kwargs):
137 |
138 | # the current population
139 | pop = self.pop
140 |
141 | # merge the offsprings with the current population
142 | if infills is not None:
143 | pop = Population.merge(self.pop, infills)
144 |
145 | # execute the survival to find the fittest solutions
146 | self.pop = self.survival.do(self.problem, pop, n_survive=self.pop_size, algorithm=self, **kwargs)
147 |
--------------------------------------------------------------------------------
/pymoode/algorithms/base/genetic.py:
--------------------------------------------------------------------------------
1 | # pymoo imports
2 | from pymoode.algorithms.base.evolutionary import EvolutionaryAlgorithm
3 | from pymoo.core.mating import Mating
4 | from pymoo.core.duplicate import DefaultDuplicateElimination, NoDuplicateElimination
5 |
6 |
7 | # =========================================================================================================
8 | # Implementation
9 | # =========================================================================================================
10 |
11 | class GeneticAlgorithm(EvolutionaryAlgorithm):
12 |
13 | def __init__(self,
14 | pop_size=None,
15 | sampling=None,
16 | selection=None,
17 | crossover=None,
18 | mutation=None,
19 | survival=None,
20 | n_offsprings=None,
21 | eliminate_duplicates=True,
22 | repair=None,
23 | **kwargs):
24 | """Base class for Genetic Algorithms. A Mating operator is instantiated using
25 | selection, crossover, mutation, repair, and eliminate_duplicates arguments.
26 |
27 | Parameters
28 | ----------
29 | pop_size : int, optional
30 | Population size, by default None
31 |
32 | sampling : Sampling, optional
33 | pymoo Sampling instance, by default None
34 |
35 | selection : Selection, optional
36 | pymoo parent selection operator, by default None
37 |
38 | crossover : Crossover, optional
39 | pymoo crossover operator, by default None
40 |
41 | genetic_mutation, optional
42 | pymoo mutation operator, by default None
43 |
44 | survival : Survival, optional
45 | pymoo survival operator, by default None
46 |
47 | n_offsprings : int, optional
48 | Number of offspring individuals created at each generation, by default None
49 |
50 | eliminate_duplicates : DuplicateElimination | bool | None, optional
51 | Eliminate duplicates in mating, by default True
52 |
53 | repair : Repair, optional
54 | pymoo repair operator which should be passed to Mating and self. By default None
55 |
56 | advance_after_initial_infill : bool, optional
57 | Either or not apply survival after initialization, by default False
58 | """
59 |
60 | # set the duplicate detection class - a boolean value chooses the default duplicate detection
61 | if isinstance(eliminate_duplicates, bool):
62 | if eliminate_duplicates:
63 | eliminate_duplicates = DefaultDuplicateElimination()
64 | else:
65 | eliminate_duplicates = NoDuplicateElimination()
66 |
67 | # Mating operator of genetic algorithms
68 | mating = Mating(selection, crossover, mutation,
69 | repair=repair,
70 | eliminate_duplicates=eliminate_duplicates)
71 |
72 | # Instantiate generic evolutionary algorithm
73 | super().__init__(
74 | pop_size=pop_size,
75 | sampling=sampling,
76 | survival=survival,
77 | mating=mating,
78 | n_offsprings=n_offsprings,
79 | eliminate_duplicates=eliminate_duplicates,
80 | repair=repair,
81 | **kwargs,
82 | )
83 |
--------------------------------------------------------------------------------
/pymoode/algorithms/de.py:
--------------------------------------------------------------------------------
1 | # pymoode imports
2 | from pymoode.survival.replacement import ImprovementReplacement
3 | from pymoode.algorithms.base.differential import DifferentialEvolution
4 |
5 |
6 | # =========================================================================================================
7 | # Implementation
8 | # =========================================================================================================
9 |
10 | class DE(DifferentialEvolution):
11 |
12 | def __init__(self,
13 | pop_size=100,
14 | variant="DE/rand/1/bin",
15 | CR=0.7,
16 | F=(0.5, 1.0),
17 | gamma=1e-4,
18 | de_repair="bounce-back",
19 | survival=ImprovementReplacement(),
20 | **kwargs):
21 | """
22 | Single-objective Differential Evolution proposed by Storn and Price (1997).
23 |
24 | Storn, R. & Price, K., 1997. Differential evolution-a simple and efficient heuristic for global optimization over continuous spaces. J. Glob. Optim., 11(4), pp. 341-359.
25 |
26 | Parameters
27 | ----------
28 | pop_size : int, optional
29 | Population size. Defaults to 100.
30 |
31 | variant : str, optional
32 | Differential evolution strategy. Must be a string in the format: "DE/selection/n/crossover", in which, n in an integer of number of difference vectors, and crossover is either 'bin' or 'exp'. Selection variants are:
33 |
34 | - 'ranked'
35 | - 'rand'
36 | - 'best'
37 | - 'current-to-best'
38 | - 'current-to-best'
39 | - 'current-to-rand'
40 | - 'rand-to-best'
41 |
42 | The selection strategy 'ranked' might be helpful to improve convergence speed without much harm to diversity. Defaults to 'DE/rand/1/bin'.
43 |
44 | CR : float, optional
45 | Crossover parameter. Defined in the range [0, 1]
46 | To reinforce mutation, use higher values. To control convergence speed, use lower values.
47 |
48 | F : iterable of float or float, optional
49 | Scale factor or mutation parameter. Defined in the range (0, 2]
50 | To reinforce exploration, use higher values; for exploitation, use lower values.
51 |
52 | gamma : float, optional
53 | Jitter deviation parameter. Should be in the range (0, 2). Defaults to 1e-4.
54 |
55 | de_repair : str, optional
56 | Repair of DE mutant vectors. Is either callable or one of:
57 |
58 | - 'bounce-back'
59 | - 'midway'
60 | - 'rand-init'
61 | - 'to-bounds'
62 |
63 | If callable, has the form fun(X, Xb, xl, xu) in which X contains mutated vectors including violations, Xb contains reference vectors for repair in feasible space, xl is a 1d vector of lower bounds, and xu a 1d vector of upper bounds.
64 | Defaults to 'bounce-back'.
65 |
66 | genetic_mutation, optional
67 | Pymoo's genetic mutation operator after crossover. Defaults to NoMutation().
68 |
69 | survival : Survival, optional
70 | Replacement survival operator. Defaults to ImprovementReplacement().
71 |
72 | repair : Repair, optional
73 | Pymoo's repair operator after mutation. Defaults to NoRepair().
74 | """
75 |
76 | super().__init__(
77 | pop_size=pop_size,
78 | variant=variant,
79 | CR=CR,
80 | F=F,
81 | gamma=gamma,
82 | de_repair=de_repair,
83 | survival=survival,
84 | **kwargs,
85 | )
86 |
87 |
--------------------------------------------------------------------------------
/pymoode/algorithms/gde3.py:
--------------------------------------------------------------------------------
1 | # pymoo imports
2 | from pymoo.core.population import Population
3 | from pymoo.util.dominator import get_relation
4 |
5 | # pymoode imports
6 | from pymoode.algorithms.base.differential import MODE
7 | from pymoode.survival import RankAndCrowding
8 |
9 |
10 | # =========================================================================================================
11 | # Implementation
12 | # =========================================================================================================
13 |
14 | class GDE3(MODE):
15 |
16 | def __init__(self,
17 | pop_size=100,
18 | variant="DE/rand/1/bin",
19 | CR=0.5,
20 | F=None,
21 | gamma=1e-4,
22 | de_repair="bounce-back",
23 | survival=RankAndCrowding(),
24 | **kwargs):
25 | """
26 | GDE3 is an extension of DE to multi-objective problems using a mixed type survival strategy.
27 | It is implemented in this version with the same constraint handling strategy of NSGA-II by default.
28 |
29 | Derived algorithms GDE3-MNN and GDE3-2NN use by default survival RankAndCrowding with metrics 'mnn' and '2nn'.
30 |
31 | For many-objective problems, try using NSDE-R, GDE3-MNN, or GDE3-2NN.
32 |
33 | For Bi-objective problems, survival = RankAndCrowding(crowding_func='pcd') is very effective.
34 |
35 | Kukkonen, S. & Lampinen, J., 2005. GDE3: The third evolution step of generalized differential evolution. 2005 IEEE congress on evolutionary computation, Volume 1, pp. 443-450.
36 |
37 | Parameters
38 | ----------
39 | pop_size : int, optional
40 | Population size. Defaults to 100.
41 |
42 | variant : str, optional
43 | Differential evolution strategy. Must be a string in the format: "DE/selection/n/crossover", in which, n in an integer of number of difference vectors, and crossover is either 'bin' or 'exp'. Selection variants are:
44 |
45 | - 'ranked'
46 | - 'rand'
47 | - 'best'
48 | - 'current-to-best'
49 | - 'current-to-best'
50 | - 'current-to-rand'
51 | - 'rand-to-best'
52 |
53 | The selection strategy 'ranked' might be helpful to improve convergence speed without much harm to diversity. Defaults to 'DE/rand/1/bin'.
54 |
55 | CR : float, optional
56 | Crossover parameter. Defined in the range [0, 1]
57 | To reinforce mutation, use higher values. To control convergence speed, use lower values.
58 |
59 | F : iterable of float or float, optional
60 | Scale factor or mutation parameter. Defined in the range (0, 2]
61 | To reinforce exploration, use higher values; for exploitation, use lower values.
62 |
63 | gamma : float, optional
64 | Jitter deviation parameter. Should be in the range (0, 2). Defaults to 1e-4.
65 |
66 | de_repair : str, optional
67 | Repair of DE mutant vectors. Is either callable or one of:
68 |
69 | - 'bounce-back'
70 | - 'midway'
71 | - 'rand-init'
72 | - 'to-bounds'
73 |
74 | If callable, has the form fun(X, Xb, xl, xu) in which X contains mutated vectors including violations, Xb contains reference vectors for repair in feasible space, xl is a 1d vector of lower bounds, and xu a 1d vector of upper bounds.
75 | Defaults to 'bounce-back'.
76 |
77 | genetic_mutation, optional
78 | Pymoo's genetic mutation operator after crossover. Defaults to NoMutation().
79 |
80 | repair : Repair, optional
81 | Pymoo's repair operator after mutation. Defaults to NoRepair().
82 |
83 | survival : Survival, optional
84 | Pymoo's survival strategy.
85 | Defaults to RankAndCrowding() with crowding distances ('cd').
86 | In GDE3, the survival strategy is applied after a one-to-one comparison between child vector and corresponding parent when both are non-dominated by the other.
87 | """
88 |
89 | super().__init__(
90 | pop_size=pop_size,
91 | variant=variant,
92 | CR=CR,
93 | F=F,
94 | gamma=gamma,
95 | de_repair=de_repair,
96 | survival=survival,
97 | **kwargs,
98 | )
99 |
100 | def _advance(self, infills=None, **kwargs):
101 |
102 | assert infills is not None, "This algorithms uses the AskAndTell interface thus 'infills' must to be provided."
103 |
104 | # The individuals that are considered for the survival later and final survive
105 | survivors = []
106 |
107 | # now for each of the infill solutions
108 | for k in range(len(self.pop)):
109 |
110 | # Get the offspring an the parent it is coming from
111 | off, parent = infills[k], self.pop[k]
112 |
113 | # Check whether the new solution dominates the parent or not
114 | rel = get_relation(parent, off)
115 |
116 | # If indifferent we add both
117 | if rel == 0:
118 | survivors.extend([parent, off])
119 |
120 | # If offspring dominates parent
121 | elif rel == -1:
122 | survivors.append(off)
123 |
124 | # If parent dominates offspring
125 | else:
126 | survivors.append(parent)
127 |
128 | # Create the population
129 | survivors = Population.create(*survivors)
130 |
131 | # Perform a survival to reduce to pop size
132 | self.pop = self.survival.do(self.problem, survivors, n_survive=self.n_offsprings)
133 |
134 |
135 | class GDE3MNN(GDE3):
136 |
137 | def __init__(self, pop_size=100, variant="DE/rand/1/bin", CR=0.5, F=None, gamma=0.0001, **kwargs):
138 | survival = RankAndCrowding(crowding_func="mnn")
139 | super().__init__(pop_size, variant, CR, F, gamma, survival=survival, **kwargs)
140 |
141 |
142 | class GDE32NN(GDE3):
143 |
144 | def __init__(self, pop_size=100, variant="DE/rand/1/bin", CR=0.5, F=None, gamma=0.0001, **kwargs):
145 | survival = RankAndCrowding(crowding_func="2nn")
146 | super().__init__(pop_size, variant, CR, F, gamma, survival=survival, **kwargs)
147 |
148 |
149 | class GDE3P(GDE3):
150 |
151 | def __init__(self, pop_size=100, variant="DE/rand/1/bin", CR=0.5, F=None, gamma=0.0001, **kwargs):
152 | survival = RankAndCrowding(crowding_func="pcd")
153 | super().__init__(pop_size, variant, CR, F, gamma, survival=survival, **kwargs)
154 |
--------------------------------------------------------------------------------
/pymoode/algorithms/nsde.py:
--------------------------------------------------------------------------------
1 | # pymoo imports
2 | from pymoo.core.population import Population
3 |
4 | # pymoode imports
5 | from pymoode.algorithms.base.differential import MODE
6 | from pymoode.survival import RankAndCrowding
7 |
8 |
9 | # =========================================================================================================
10 | # Implementation
11 | # =========================================================================================================
12 |
13 | class NSDE(MODE):
14 |
15 | def __init__(self,
16 | pop_size=100,
17 | variant="DE/rand/1/bin",
18 | CR=0.7,
19 | F=None,
20 | gamma=1e-4,
21 | de_repair="bounce-back",
22 | survival=RankAndCrowding(),
23 | **kwargs):
24 | """
25 | NSDE is an algorithm that combines that combines NSGA-II sorting and survival strategies
26 | to DE mutation and crossover.
27 |
28 | For many-objective problems, try using NSDE-R, GDE3-MNN, or GDE3-2NN.
29 |
30 | For Bi-objective problems, survival = RankAndCrowding(crowding_func='pcd') is very effective.
31 |
32 | Parameters
33 | ----------
34 | pop_size : int, optional
35 | Population size. Defaults to 100.
36 |
37 | sampling : Sampling, optional
38 | Sampling strategy of pymoo. Defaults to LHS().
39 |
40 | variant : str, optional
41 | Differential evolution strategy. Must be a string in the format: "DE/selection/n/crossover", in which, n in an integer of number of difference vectors, and crossover is either 'bin' or 'exp'. Selection variants are:
42 |
43 | - "ranked'
44 | - 'rand'
45 | - 'best'
46 | - 'current-to-best'
47 | - 'current-to-best'
48 | - 'current-to-rand'
49 | - 'rand-to-best'
50 |
51 | The selection strategy 'ranked' might be helpful to improve convergence speed without much harm to diversity. Defaults to 'DE/rand/1/bin'.
52 |
53 | CR : float, optional
54 | Crossover parameter. Defined in the range [0, 1]
55 | To reinforce mutation, use higher values. To control convergence speed, use lower values.
56 |
57 | F : iterable of float or float, optional
58 | Scale factor or mutation parameter. Defined in the range (0, 2]
59 | To reinforce exploration, use higher values; for exploitation, use lower values.
60 |
61 | gamma : float, optional
62 | Jitter deviation parameter. Should be in the range (0, 2). Defaults to 1e-4.
63 |
64 | de_repair : str, optional
65 | Repair of DE mutant vectors. Is either callable or one of:
66 |
67 | - 'bounce-back'
68 | - 'midway'
69 | - 'rand-init'
70 | - 'to-bounds'
71 |
72 | If callable, has the form fun(X, Xb, xl, xu) in which X contains mutated vectors including violations, Xb contains reference vectors for repair in feasible space, xl is a 1d vector of lower bounds, and xu a 1d vector of upper bounds.
73 | Defaults to 'bounce-back'.
74 |
75 | genetic_mutation, optional
76 | Pymoo's genetic mutation operator after crossover. Defaults to NoMutation().
77 |
78 | repair : Repair, optional
79 | Pymoo's repair operator after mutation. Defaults to NoRepair().
80 |
81 | survival : Survival, optional
82 | Pymoo's survival strategy.
83 | Defaults to RankAndCrowding() with crowding distances ('cd').
84 | In GDE3, the survival strategy is applied after a one-to-one comparison between child vector and corresponding parent when both are non-dominated by the other.
85 | """
86 | super().__init__(
87 | pop_size=pop_size,
88 | variant=variant,
89 | CR=CR,
90 | F=F,
91 | gamma=gamma,
92 | de_repair=de_repair,
93 | survival=survival,
94 | **kwargs,
95 | )
96 |
97 | def _advance(self, infills=None, **kwargs):
98 |
99 | assert infills is not None, "This algorithms uses the AskAndTell interface thus 'infills' must to be provided."
100 |
101 | # Merge in mu + lambda style
102 | pop = Population.merge(self.pop, infills)
103 |
104 | # Perform a survival to reduce to pop size
105 | self.pop = self.survival.do(self.problem, pop, n_survive=self.n_offsprings)
106 |
--------------------------------------------------------------------------------
/pymoode/algorithms/nsder.py:
--------------------------------------------------------------------------------
1 | # External
2 | import numpy as np
3 |
4 | # pymoo imports
5 | from pymoo.algorithms.moo.nsga3 import ReferenceDirectionSurvival
6 | from pymoo.util.misc import has_feasible
7 |
8 | # pymoode imports
9 | from pymoode.algorithms.nsde import NSDE
10 |
11 |
12 | # =========================================================================================================
13 | # Implementation
14 | # =========================================================================================================
15 |
16 | class NSDER(NSDE):
17 |
18 | def __init__(self,
19 | ref_dirs,
20 | pop_size=100,
21 | variant="DE/rand/1/bin",
22 | CR=0.7,
23 | F=None,
24 | gamma=1e-4,
25 | **kwargs):
26 | """
27 | NSDE-R is an extension of NSDE to many-objective problems (Reddy & Dulikravich, 2019) using NSGA-III survival.
28 |
29 | S. R. Reddy and G. S. Dulikravich, "Many-objective differential evolution optimization based on reference points: NSDE-R," Struct. Multidisc. Optim., vol. 60, pp. 1455-1473, 2019.
30 |
31 | Parameters
32 | ----------
33 | ref_dirs : array like
34 | The reference directions that should be used during the optimization.
35 |
36 | pop_size : int, optional
37 | Population size. Defaults to 100.
38 |
39 | sampling : Sampling, optional
40 | Sampling strategy of pymoo. Defaults to LHS().
41 |
42 | variant : str, optional
43 | Differential evolution strategy. Must be a string in the format: "DE/selection/n/crossover", in which, n in an integer of number of difference vectors, and crossover is either 'bin' or 'exp'. Selection variants are:
44 |
45 | - "ranked'
46 | - 'rand'
47 | - 'best'
48 | - 'current-to-best'
49 | - 'current-to-best'
50 | - 'current-to-rand'
51 | - 'rand-to-best'
52 |
53 | The selection strategy 'ranked' might be helpful to improve convergence speed without much harm to diversity. Defaults to 'DE/rand/1/bin'.
54 |
55 | CR : float, optional
56 | Crossover parameter. Defined in the range [0, 1]
57 | To reinforce mutation, use higher values. To control convergence speed, use lower values.
58 |
59 | F : iterable of float or float, optional
60 | Scale factor or mutation parameter. Defined in the range (0, 2]
61 | To reinforce exploration, use higher values; for exploitation, use lower values.
62 |
63 | gamma : float, optional
64 | Jitter deviation parameter. Should be in the range (0, 2). Defaults to 1e-4.
65 |
66 | de_repair : str, optional
67 | Repair of DE mutant vectors. Is either callable or one of:
68 |
69 | - 'bounce-back'
70 | - 'midway'
71 | - 'rand-init'
72 | - 'to-bounds'
73 |
74 | If callable, has the form fun(X, Xb, xl, xu) in which X contains mutated vectors including violations, Xb contains reference vectors for repair in feasible space, xl is a 1d vector of lower bounds, and xu a 1d vector of upper bounds.
75 | Defaults to 'bounce-back'.
76 |
77 | genetic_mutation, optional
78 | Pymoo's genetic mutation operator after crossover. Defaults to NoMutation().
79 |
80 | repair : Repair, optional
81 | Pymoo's repair operator after mutation. Defaults to NoRepair().
82 |
83 | survival : Survival, optional
84 | Pymoo's survival strategy.
85 | Defaults to ReferenceDirectionSurvival().
86 | """
87 |
88 | self.ref_dirs = ref_dirs
89 |
90 | if self.ref_dirs is not None:
91 |
92 | if pop_size is None:
93 | pop_size = len(self.ref_dirs)
94 |
95 | if pop_size < len(self.ref_dirs):
96 | print(
97 | f"WARNING: pop_size={pop_size} is less than the number of reference directions ref_dirs={len(self.ref_dirs)}.\n"
98 | "This might cause unwanted behavior of the algorithm. \n"
99 | "Please make sure pop_size is equal or larger than the number of reference directions. ")
100 |
101 | if 'survival' in kwargs:
102 | survival = kwargs['survival']
103 | del kwargs['survival']
104 | else:
105 | survival = DERSurvival(ref_dirs)
106 |
107 | super().__init__(pop_size=pop_size,
108 | variant=variant,
109 | CR=CR,
110 | F=F,
111 | gamma=gamma,
112 | survival=survival,
113 | **kwargs)
114 |
115 | def _setup(self, problem, **kwargs):
116 |
117 | if self.ref_dirs is not None:
118 | if self.ref_dirs.shape[1] != problem.n_obj:
119 | raise Exception(
120 | "Dimensionality of reference points must be equal to the number of objectives: %s != %s" %
121 | (self.ref_dirs.shape[1], problem.n_obj))
122 |
123 | def _set_optimum(self, **kwargs):
124 | if not has_feasible(self.pop):
125 | self.opt = self.pop[[np.argmin(self.pop.get("CV"))]]
126 | else:
127 | self.opt = self.survival.opt
128 |
129 |
130 | class DERSurvival(ReferenceDirectionSurvival):
131 |
132 | def _do(self, problem, pop, *args, n_survive=None, D=None, **kwargs):
133 | return super()._do(problem, pop, n_survive, D, **kwargs)
134 |
--------------------------------------------------------------------------------
/pymoode/backups/mnn_bak.pyx:
--------------------------------------------------------------------------------
1 | # distutils: language = c++
2 | # cython: language_level=2, boundscheck=False, wraparound=False, cdivision=True
3 |
4 | import numpy as np
5 |
6 | from libcpp cimport bool
7 | from libcpp.vector cimport vector
8 | from libcpp.set cimport set as cpp_set
9 |
10 |
11 | cdef extern from "math.h":
12 | double HUGE_VAL
13 |
14 |
15 | def calc_mnn(double[:, :] X, int n_remove=0):
16 |
17 | cdef:
18 | int N, M, n
19 | cpp_set[int] extremes
20 | vector[int] extremes_min, extremes_max
21 |
22 | N = X.shape[0]
23 | M = X.shape[1]
24 |
25 | if n_remove <= (N - M):
26 | if n_remove < 0:
27 | n_remove = 0
28 | else:
29 | pass
30 | else:
31 | n_remove = N - M
32 |
33 | extremes_min = c_get_argmin(X)
34 | extremes_max = c_get_argmax(X)
35 |
36 | extremes = cpp_set[int]()
37 |
38 | for n in extremes_min:
39 | extremes.insert(n)
40 |
41 | for n in extremes_max:
42 | extremes.insert(n)
43 |
44 | X = c_normalize_array(X, extremes_max, extremes_min)
45 |
46 | return c_calc_mnn(X, n_remove, N, M, extremes)
47 |
48 |
49 | def calc_2nn(double[:, :] X, int n_remove=0):
50 |
51 | cdef:
52 | int N, M, n
53 | cpp_set[int] extremes
54 | vector[int] extremes_min, extremes_max
55 |
56 | N = X.shape[0]
57 | M = X.shape[1]
58 |
59 | if n_remove <= (N - M):
60 | if n_remove < 0:
61 | n_remove = 0
62 | else:
63 | pass
64 | else:
65 | n_remove = N - M
66 |
67 | extremes_min = c_get_argmin(X)
68 | extremes_max = c_get_argmax(X)
69 |
70 | extremes = cpp_set[int]()
71 |
72 | for n in extremes_min:
73 | extremes.insert(n)
74 |
75 | for n in extremes_max:
76 | extremes.insert(n)
77 |
78 | X = c_normalize_array(X, extremes_max, extremes_min)
79 |
80 | M = 2
81 |
82 | return c_calc_mnn(X, n_remove, N, M, extremes)
83 |
84 |
85 | cdef c_calc_mnn(double[:, :] X, int n_remove, int N, int M, cpp_set[int] extremes):
86 |
87 | cdef:
88 | int n, n_removed, k
89 | cpp_set[int] calc_items
90 | cpp_set[int] H
91 | double[:, :] Dnn, Dij, upper_square
92 | double[:] d
93 | int[:, :] Mnn
94 |
95 | #Define items to calculate distances
96 | calc_items = cpp_set[int]()
97 | for n in range(N):
98 | calc_items.insert(n)
99 | for n in extremes:
100 | calc_items.erase(n)
101 |
102 | #Define remaining items to evaluate
103 | H = cpp_set[int]()
104 | for n in range(N):
105 | H.insert(n)
106 |
107 | #Upper limits for squared l2 distances
108 | _Xsum = np.sum(X, axis=1, keepdims=True)
109 | _upper_square = (_Xsum - _Xsum.T) * (_Xsum - _Xsum.T) / M
110 | upper_square = _upper_square[:, :]
111 |
112 | #Initialize
113 | n_removed = 0
114 |
115 | #Initialize neighbors and distances
116 | _Mnn = np.full((N, M), 0, dtype=np.intc)
117 | _Dnn = np.full((N, M), HUGE_VAL, dtype=np.double)
118 | _Dij = np.full((N, N), -1.0, dtype=np.double)
119 | dd = np.full((N,), HUGE_VAL, dtype=np.double)
120 |
121 | Mnn = _Mnn[:, :]
122 | Dnn = _Dnn[:, :]
123 | Dij = _Dij[:, :]
124 | d = dd[:]
125 |
126 | #Fill in neighbors and distance matrix
127 | c_calc_mnn_iter(
128 | X,
129 | Mnn,
130 | Dnn,
131 | Dij,
132 | N, M,
133 | calc_items,
134 | H,
135 | upper_square
136 | )
137 |
138 | #Obtain distance metrics
139 | c_calc_d(d, Dnn, calc_items, M)
140 |
141 | #While n_remove not acheived
142 | while n_removed < n_remove:
143 |
144 | #Obtain element to drop
145 | k = c_get_drop(d, H)
146 | H.erase(k)
147 |
148 | #Update index
149 | n_removed = n_removed + 1
150 | if n_removed == n_remove:
151 | break
152 |
153 | else:
154 |
155 | #Get items to be recalculated
156 | calc_items = c_get_calc_items(Mnn, Dnn, H, k, M)
157 | for n in extremes:
158 | calc_items.erase(n)
159 |
160 | #Fill in neighbors and distance matrix
161 | c_calc_mnn_iter(
162 | X,
163 | Mnn,
164 | Dnn,
165 | Dij,
166 | N, M,
167 | calc_items,
168 | H,
169 | upper_square
170 | )
171 |
172 | #Obtain distance metrics
173 | c_calc_d(d, Dnn, calc_items, M)
174 |
175 | return dd
176 |
177 |
178 | cdef c_calc_mnn_iter(
179 | double[:, :] X,
180 | int[:, :] Mnn,
181 | double[:, :] Dnn,
182 | double[:, :] Dij,
183 | int N, int M,
184 | cpp_set[int] calc_items,
185 | cpp_set[int] H,
186 | double[:, :] upper_square
187 | ):
188 |
189 | cdef:
190 | int i, j, m, MM
191 | double dij
192 |
193 | #MM might be different from M in 2NN
194 | MM = X.shape[1]
195 |
196 | #Iterate over items to calculate
197 | for i in calc_items:
198 |
199 | #Iterate over elements in X
200 | for j in H:
201 |
202 | #Calculate distance if elements satisfy rule
203 | if ((j != i)
204 | and (upper_square[i, j] <= Dnn[i, M-1])
205 | and (Dij[i, j] <= Dnn[i, M-1])):
206 |
207 | #Calculate if different from -1
208 | if Dij[i, j] == -1.0:
209 |
210 | #Squared distance
211 | dij = 0
212 | for m in range(MM):
213 | dij = dij + (X[j, m] - X[i, m]) * (X[j, m] - X[i, m])
214 |
215 | #Fill values
216 | Dij[i, j] = dij
217 | Dij[j, i] = Dij[i, j]
218 |
219 | else:
220 | dij = Dij[i, j]
221 |
222 | #Iterate over current values
223 | for m in range(M):
224 |
225 | #Check is any should be replaced
226 | if (dij <= Dnn[i, m]):
227 |
228 | #Replace higher values
229 | Dnn[i, m + 1:] = Dnn[i, m:-1]
230 | Mnn[i, m + 1:] = Mnn[i, m:-1]
231 |
232 | #Replace current value
233 | Dnn[i, m] = dij
234 | Mnn[i, m] = j
235 |
236 | break
237 |
238 |
239 | #Calculate crowding metric
240 | cdef c_calc_d(double[:] d, double[:, :] Dnn, cpp_set[int] calc_items, int M):
241 |
242 | cdef:
243 | int i, m
244 |
245 | for i in calc_items:
246 |
247 | d[i] = 1
248 | for m in range(M):
249 | d[i] = d[i] * Dnn[i, m]
250 |
251 |
252 | #Returns indexes of items to be recalculated after removal
253 | cdef cpp_set[int] c_get_calc_items(
254 | int[:, :] Mnn,
255 | double[:, :] Dnn,
256 | cpp_set[int] H,
257 | int k, int M):
258 |
259 | cdef:
260 | int i, m
261 | cpp_set[int] calc_items
262 |
263 | calc_items = cpp_set[int]()
264 |
265 | for i in H:
266 |
267 | for m in range(M):
268 |
269 | if Mnn[i, m] == k:
270 |
271 | Dnn[i, :] = HUGE_VAL
272 | calc_items.insert(i)
273 |
274 | return calc_items
275 |
276 |
277 | #Returns elements to remove based on crowding metric d and heap of remaining elements H
278 | cdef int c_get_drop(double[:] d, cpp_set[int] H):
279 |
280 | cdef:
281 | int i, min_i
282 | double min_d
283 |
284 | min_d = HUGE_VAL
285 | min_i = 0
286 |
287 | for i in H:
288 |
289 | if d[i] <= min_d:
290 | min_d = d[i]
291 | min_i = i
292 |
293 | return min_i
294 |
295 |
296 | #Returns vector of positions of minimum values along axis 0 of a 2d memoryview
297 | cdef vector[int] c_get_argmin(double[:, :] X):
298 |
299 | cdef:
300 | int N, M, min_i, n, m
301 | double min_val
302 | vector[int] indexes
303 |
304 | N = X.shape[0]
305 | M = X.shape[1]
306 |
307 | indexes = vector[int]()
308 |
309 | for m in range(M):
310 |
311 | min_i = 0
312 | min_val = X[0, m]
313 |
314 | for n in range(N):
315 |
316 | if X[n, m] < min_val:
317 |
318 | min_i = n
319 | min_val = X[n, m]
320 |
321 | indexes.push_back(min_i)
322 |
323 | return indexes
324 |
325 |
326 | #Returns vector of positions of maximum values along axis 0 of a 2d memoryview
327 | cdef vector[int] c_get_argmax(double[:, :] X):
328 |
329 | cdef:
330 | int N, M, max_i, n, m
331 | double max_val
332 | vector[int] indexes
333 |
334 | N = X.shape[0]
335 | M = X.shape[1]
336 |
337 | indexes = vector[int]()
338 |
339 | for m in range(M):
340 |
341 | max_i = 0
342 | max_val = X[0, m]
343 |
344 | for n in range(N):
345 |
346 | if X[n, m] > max_val:
347 |
348 | max_i = n
349 | max_val = X[n, m]
350 |
351 | indexes.push_back(max_i)
352 |
353 | return indexes
354 |
355 |
356 | #Performs normalization of a 2d memoryview
357 | cdef double[:, :] c_normalize_array(double[:, :] X, vector[int] extremes_max, vector[int] extremes_min):
358 |
359 | cdef:
360 | int N = X.shape[0]
361 | int M = X.shape[1]
362 | int n, m, l, u
363 | double l_val, u_val, diff_val
364 | vector[double] min_vals, max_vals
365 |
366 | min_vals = vector[double]()
367 | max_vals = vector[double]()
368 |
369 | m = 0
370 | for u in extremes_max:
371 | u_val = X[u, m]
372 | max_vals.push_back(u_val)
373 | m = m + 1
374 |
375 | m = 0
376 | for l in extremes_min:
377 | l_val = X[l, m]
378 | min_vals.push_back(l_val)
379 | m = m + 1
380 |
381 | for m in range(M):
382 |
383 | diff_val = max_vals[m] - min_vals[m]
384 | if diff_val == 0.0:
385 | diff_val = 1.0
386 |
387 | for n in range(N):
388 |
389 | X[n, m] = (X[n, m] - min_vals[m]) / diff_val
390 |
391 | return X
--------------------------------------------------------------------------------
/pymoode/backups/mnn_naive_sort.pyx:
--------------------------------------------------------------------------------
1 | # distutils: language = c++
2 | # cython: language_level=2, boundscheck=False, wraparound=False, cdivision=True
3 |
4 | # This was implemented using a naive sorting approach and calculating the full distances matrix
5 | # Other strategies can be more efficient depending on the population size and number of objectives
6 | # This approach was the most promising for N = 3 and a population size < 30
7 | # For population sizes > 30 using numpy quicksort implementation would make the algorithm faster
8 | # I believe for a large number of objectives M, some strategy based on upper bounds for distances would be helpful
9 | # Those interested in contributing please contact me at bruscalia12@gmail.com
10 |
11 |
12 | import numpy as np
13 |
14 | from libcpp cimport bool
15 | from libcpp.vector cimport vector
16 | from libcpp.set cimport set as cpp_set
17 |
18 |
19 | cdef extern from "math.h":
20 | double HUGE_VAL
21 |
22 |
23 | def calc_mnn(double[:, :] X, int n_remove=0):
24 |
25 | cdef:
26 | int N, M, n
27 | cpp_set[int] extremes
28 | vector[int] extremes_min, extremes_max
29 |
30 | N = X.shape[0]
31 | M = X.shape[1]
32 |
33 | if n_remove <= (N - M):
34 | if n_remove < 0:
35 | n_remove = 0
36 | else:
37 | pass
38 | else:
39 | n_remove = N - M
40 |
41 | extremes_min = c_get_argmin(X)
42 | extremes_max = c_get_argmax(X)
43 |
44 | extremes = cpp_set[int]()
45 |
46 | for n in extremes_min:
47 | extremes.insert(n)
48 |
49 | for n in extremes_max:
50 | extremes.insert(n)
51 |
52 | X = c_normalize_array(X, extremes_max, extremes_min)
53 |
54 | return c_calc_mnn(X, n_remove, N, M, extremes)
55 |
56 |
57 | def calc_2nn(double[:, :] X, int n_remove=0):
58 |
59 | cdef:
60 | int N, M, n
61 | cpp_set[int] extremes
62 | vector[int] extremes_min, extremes_max
63 |
64 | N = X.shape[0]
65 | M = X.shape[1]
66 |
67 | if n_remove <= (N - M):
68 | if n_remove < 0:
69 | n_remove = 0
70 | else:
71 | pass
72 | else:
73 | n_remove = N - M
74 |
75 | extremes_min = c_get_argmin(X)
76 | extremes_max = c_get_argmax(X)
77 |
78 | extremes = cpp_set[int]()
79 |
80 | for n in extremes_min:
81 | extremes.insert(n)
82 |
83 | for n in extremes_max:
84 | extremes.insert(n)
85 |
86 | X = c_normalize_array(X, extremes_max, extremes_min)
87 |
88 | M = 2
89 |
90 | return c_calc_mnn(X, n_remove, N, M, extremes)
91 |
92 |
93 | cdef c_calc_mnn(double[:, :] X, int n_remove, int N, int M, cpp_set[int] extremes):
94 |
95 | cdef:
96 | int n, mm, i, j, n_removed, k, MM
97 | double dij
98 | cpp_set[int] calc_items
99 | cpp_set[int] H
100 | double[:, :] D
101 | double[:] d
102 | int[:, :] Mnn
103 |
104 | #Define items to calculate distances
105 | calc_items = cpp_set[int]()
106 | for n in range(N):
107 | calc_items.insert(n)
108 | for n in extremes:
109 | calc_items.erase(n)
110 |
111 | #Define remaining items to evaluate
112 | H = cpp_set[int]()
113 | for n in range(N):
114 | H.insert(n)
115 |
116 | #Instantiate distances array
117 | _D = np.empty((N, N), dtype=np.double)
118 | D = _D[:, :]
119 |
120 | #Shape of X
121 | MM = X.shape[1]
122 |
123 | #Fill values on D
124 | for i in range(N - 1):
125 | D[i, i] = 0.0
126 |
127 | for j in range(i + 1, N):
128 |
129 | dij = 0
130 | for mm in range(MM):
131 | dij = dij + (X[j, mm] - X[i, mm]) * (X[j, mm] - X[i, mm])
132 |
133 | D[i, j] = dij
134 | D[j, i] = D[i, j]
135 |
136 | D[N-1, N-1] = 0.0
137 |
138 | #Initialize
139 | n_removed = 0
140 |
141 | #Initialize neighbors and distances
142 | _Mnn = np.full((N, M), -1, dtype=np.intc)
143 | dd = np.full((N,), HUGE_VAL, dtype=np.double)
144 |
145 | Mnn = _Mnn[:, :]
146 | d = dd[:]
147 |
148 | #Fill in neighbors and distance matrix
149 | c_calc_mnn_iter(
150 | X,
151 | Mnn,
152 | D,
153 | N, M,
154 | calc_items,
155 | H
156 | )
157 |
158 | #Obtain distance metrics
159 | c_calc_d(d, Mnn, D, calc_items, M)
160 |
161 | #While n_remove not acheived (no need to recalculate if only one item should be removed)
162 | while n_removed < (n_remove - 1):
163 |
164 | #Obtain element to drop
165 | k = c_get_drop(d, H)
166 | H.erase(k)
167 |
168 | #Update index
169 | n_removed = n_removed + 1
170 |
171 | #Get items to be recalculated
172 | calc_items = c_get_calc_items(Mnn, H, k, M)
173 | for n in extremes:
174 | calc_items.erase(n)
175 |
176 | #Fill in neighbors and distance matrix
177 | c_calc_mnn_iter(
178 | X,
179 | Mnn,
180 | D,
181 | N, M,
182 | calc_items,
183 | H
184 | )
185 |
186 | #Obtain distance metrics
187 | c_calc_d(d, Mnn, D, calc_items, M)
188 |
189 | return dd
190 |
191 |
192 | cdef c_calc_mnn_iter(
193 | double[:, :] X,
194 | int[:, :] Mnn,
195 | double[:, :] D,
196 | int N, int M,
197 | cpp_set[int] calc_items,
198 | cpp_set[int] H
199 | ):
200 |
201 | cdef:
202 | int i, j, m
203 |
204 | #Iterate over items to calculate
205 | for i in calc_items:
206 |
207 | #Iterate over elements in X
208 | for j in H:
209 |
210 | #Go to next if same element
211 | if (j == i):
212 | continue
213 |
214 | #Replace at least the last neighbor
215 | elif (D[i, j] <= D[i, Mnn[i, M-1]]) or (Mnn[i, M-1] == -1):
216 |
217 | #Iterate over current values
218 | for m in range(M):
219 |
220 | #Set to current if unassigned
221 | if (Mnn[i, m] == -1):
222 |
223 | #Set last neighbor to index
224 | Mnn[i, m] = j
225 | break
226 |
227 | #Break if checking already corresponding index
228 | elif (j == Mnn[i, m]):
229 | break
230 |
231 | #Distance satisfies condition
232 | elif (D[i, j] <= D[i, Mnn[i, m]]):
233 |
234 | #Replace higher values
235 | Mnn[i, m + 1:] = Mnn[i, m:-1]
236 |
237 | #Replace current value
238 | Mnn[i, m] = j
239 | break
240 |
241 |
242 | #Calculate crowding metric
243 | cdef c_calc_d(double[:] d, int[:, :] Mnn, double[:, :] D, cpp_set[int] calc_items, int M):
244 |
245 | cdef:
246 | int i, m
247 |
248 | for i in calc_items:
249 |
250 | d[i] = 1
251 | for m in range(M):
252 | d[i] = d[i] * D[i, Mnn[i, m]]
253 |
254 |
255 | #Returns indexes of items to be recalculated after removal
256 | cdef cpp_set[int] c_get_calc_items(
257 | int[:, :] Mnn,
258 | cpp_set[int] H,
259 | int k, int M):
260 |
261 | cdef:
262 | int i, m
263 | cpp_set[int] calc_items
264 |
265 | calc_items = cpp_set[int]()
266 |
267 | for i in H:
268 |
269 | for m in range(M):
270 |
271 | if Mnn[i, m] == k:
272 |
273 | Mnn[i, m:-1] = Mnn[i, m + 1:]
274 | Mnn[i, M-1] = -1
275 |
276 | calc_items.insert(i)
277 |
278 | return calc_items
279 |
280 |
281 | #Returns elements to remove based on crowding metric d and heap of remaining elements H
282 | cdef int c_get_drop(double[:] d, cpp_set[int] H):
283 |
284 | cdef:
285 | int i, min_i
286 | double min_d
287 |
288 | min_d = HUGE_VAL
289 | min_i = 0
290 |
291 | for i in H:
292 |
293 | if d[i] <= min_d:
294 | min_d = d[i]
295 | min_i = i
296 |
297 | return min_i
298 |
299 |
300 | #Elements in condensed matrix
301 | cdef int c_square_to_condensed(int i, int j, int N):
302 |
303 | cdef int _i = i
304 |
305 | if i < j:
306 | i = j
307 | j = _i
308 |
309 | return N * j - j * (j + 1) // 2 + i - 1 - j
310 |
311 |
312 | #Returns vector of positions of minimum values along axis 0 of a 2d memoryview
313 | cdef vector[int] c_get_argmin(double[:, :] X):
314 |
315 | cdef:
316 | int N, M, min_i, n, m
317 | double min_val
318 | vector[int] indexes
319 |
320 | N = X.shape[0]
321 | M = X.shape[1]
322 |
323 | indexes = vector[int]()
324 |
325 | for m in range(M):
326 |
327 | min_i = 0
328 | min_val = X[0, m]
329 |
330 | for n in range(N):
331 |
332 | if X[n, m] < min_val:
333 |
334 | min_i = n
335 | min_val = X[n, m]
336 |
337 | indexes.push_back(min_i)
338 |
339 | return indexes
340 |
341 |
342 | #Returns vector of positions of maximum values along axis 0 of a 2d memoryview
343 | cdef vector[int] c_get_argmax(double[:, :] X):
344 |
345 | cdef:
346 | int N, M, max_i, n, m
347 | double max_val
348 | vector[int] indexes
349 |
350 | N = X.shape[0]
351 | M = X.shape[1]
352 |
353 | indexes = vector[int]()
354 |
355 | for m in range(M):
356 |
357 | max_i = 0
358 | max_val = X[0, m]
359 |
360 | for n in range(N):
361 |
362 | if X[n, m] > max_val:
363 |
364 | max_i = n
365 | max_val = X[n, m]
366 |
367 | indexes.push_back(max_i)
368 |
369 | return indexes
370 |
371 |
372 | #Performs normalization of a 2d memoryview
373 | cdef double[:, :] c_normalize_array(double[:, :] X, vector[int] extremes_max, vector[int] extremes_min):
374 |
375 | cdef:
376 | int N = X.shape[0]
377 | int M = X.shape[1]
378 | int n, m, l, u
379 | double l_val, u_val, diff_val
380 | vector[double] min_vals, max_vals
381 |
382 | min_vals = vector[double]()
383 | max_vals = vector[double]()
384 |
385 | m = 0
386 | for u in extremes_max:
387 | u_val = X[u, m]
388 | max_vals.push_back(u_val)
389 | m = m + 1
390 |
391 | m = 0
392 | for l in extremes_min:
393 | l_val = X[l, m]
394 | min_vals.push_back(l_val)
395 | m = m + 1
396 |
397 | for m in range(M):
398 |
399 | diff_val = max_vals[m] - min_vals[m]
400 | if diff_val == 0.0:
401 | diff_val = 1.0
402 |
403 | for n in range(N):
404 |
405 | X[n, m] = (X[n, m] - min_vals[m]) / diff_val
406 |
407 | return X
--------------------------------------------------------------------------------
/pymoode/backups/mnn_precalc.pyx:
--------------------------------------------------------------------------------
1 | # distutils: language = c++
2 | # cython: language_level=2, boundscheck=False, wraparound=False, cdivision=True
3 |
4 | import numpy as np
5 | from scipy.spatial.distance import squareform, pdist
6 |
7 | from libcpp cimport bool
8 | from libcpp.vector cimport vector
9 | from libcpp.set cimport set as cpp_set
10 |
11 |
12 | cdef extern from "math.h":
13 | double HUGE_VAL
14 |
15 |
16 | def calc_mnn(double[:, :] X, int n_remove=0):
17 |
18 | cdef:
19 | int N, M, n
20 | cpp_set[int] extremes
21 | vector[int] extremes_min, extremes_max
22 |
23 | N = X.shape[0]
24 | M = X.shape[1]
25 |
26 | if n_remove <= (N - M):
27 | if n_remove < 0:
28 | n_remove = 0
29 | else:
30 | pass
31 | else:
32 | n_remove = N - M
33 |
34 | extremes_min = c_get_argmin(X)
35 | extremes_max = c_get_argmax(X)
36 |
37 | extremes = cpp_set[int]()
38 |
39 | for n in extremes_min:
40 | extremes.insert(n)
41 |
42 | for n in extremes_max:
43 | extremes.insert(n)
44 |
45 | X = c_normalize_array(X, extremes_max, extremes_min)
46 |
47 | return c_calc_mnn(X, n_remove, N, M, extremes)
48 |
49 |
50 | def calc_2nn(double[:, :] X, int n_remove=0):
51 |
52 | cdef:
53 | int N, M, n
54 | cpp_set[int] extremes
55 | vector[int] extremes_min, extremes_max
56 |
57 | N = X.shape[0]
58 | M = X.shape[1]
59 |
60 | if n_remove <= (N - M):
61 | if n_remove < 0:
62 | n_remove = 0
63 | else:
64 | pass
65 | else:
66 | n_remove = N - M
67 |
68 | extremes_min = c_get_argmin(X)
69 | extremes_max = c_get_argmax(X)
70 |
71 | extremes = cpp_set[int]()
72 |
73 | for n in extremes_min:
74 | extremes.insert(n)
75 |
76 | for n in extremes_max:
77 | extremes.insert(n)
78 |
79 | X = c_normalize_array(X, extremes_max, extremes_min)
80 |
81 | M = 2
82 |
83 | return c_calc_mnn(X, n_remove, N, M, extremes)
84 |
85 |
86 | cdef c_calc_mnn(double[:, :] X, int n_remove, int N, int M, cpp_set[int] extremes):
87 |
88 | cdef:
89 | int n, n_removed, k
90 | cpp_set[int] calc_items
91 | cpp_set[int] H
92 | double[:, :] D
93 | double[:] d
94 | int[:, :] Mnn
95 |
96 | #Define items to calculate distances
97 | calc_items = cpp_set[int]()
98 | for n in range(N):
99 | calc_items.insert(n)
100 | for n in extremes:
101 | calc_items.erase(n)
102 |
103 | #Define remaining items to evaluate
104 | H = cpp_set[int]()
105 | for n in range(N):
106 | H.insert(n)
107 |
108 | #Initialize
109 | n_removed = 0
110 |
111 | #Initialize neighbors and distances
112 | _Mnn = np.full((N, M), -1, dtype=np.intc)
113 | _D = squareform(pdist(X, metric="sqeuclidean").astype(np.double))
114 | dd = np.full((N,), HUGE_VAL, dtype=np.double)
115 |
116 | Mnn = _Mnn[:, :]
117 | D = _D[:, :]
118 | d = dd[:]
119 |
120 | #Fill in neighbors and distance matrix
121 | c_calc_mnn_iter(
122 | X,
123 | Mnn,
124 | D,
125 | N, M,
126 | calc_items,
127 | H
128 | )
129 |
130 | #Obtain distance metrics
131 | c_calc_d(d, Mnn, D, calc_items, M)
132 |
133 | #While n_remove not acheived
134 | while n_removed < n_remove:
135 |
136 | #Obtain element to drop
137 | k = c_get_drop(d, H)
138 | H.erase(k)
139 |
140 | #Update index
141 | n_removed = n_removed + 1
142 | if n_removed == n_remove:
143 | break
144 |
145 | else:
146 |
147 | #Get items to be recalculated
148 | calc_items = c_get_calc_items(Mnn, D, H, k, M)
149 | for n in extremes:
150 | calc_items.erase(n)
151 |
152 | #Fill in neighbors and distance matrix
153 | c_calc_mnn_iter(
154 | X,
155 | Mnn,
156 | D,
157 | N, M,
158 | calc_items,
159 | H
160 | )
161 |
162 | #Obtain distance metrics
163 | c_calc_d(d, Mnn, D, calc_items, M)
164 |
165 | return dd
166 |
167 |
168 | cdef c_calc_mnn_iter(
169 | double[:, :] X,
170 | int[:, :] Mnn,
171 | double[:, :] D,
172 | int N, int M,
173 | cpp_set[int] calc_items,
174 | cpp_set[int] H
175 | ):
176 |
177 | cdef:
178 | int i, j, m, last_n, MM
179 | double dij, last_d, dim
180 |
181 | #MM might be different from M in 2NN
182 | MM = X.shape[1]
183 |
184 | #Iterate over items to calculate
185 | for i in calc_items:
186 |
187 | #Set last neighbor to index
188 | last_n = Mnn[i, M-1]
189 |
190 | #Set to huge val if unassinged
191 | if last_n == -1:
192 | last_d = HUGE_VAL
193 | else:
194 | last_d = D[i, last_n]
195 |
196 | #Iterate over elements in X
197 | for j in H:
198 |
199 | #Calculate distance if elements satisfy rule
200 | if ((j != i) and (j != last_n) and (D[i, j] <= last_d)):
201 |
202 | #Iterate over current values
203 | for m in range(M):
204 |
205 | #Break if checking already corresponding index
206 | if (j == Mnn[i, m]):
207 | break
208 |
209 | #Set to current if unassigned
210 | elif (Mnn[i, m] == -1):
211 |
212 | #Set last neighbor to index
213 | Mnn[i, m] = j
214 | last_n = Mnn[i, M-1]
215 |
216 | #Set to huge val if unassinged
217 | if last_n == -1:
218 | last_d = HUGE_VAL
219 | else:
220 | last_d = D[i, last_n]
221 | break
222 |
223 | elif (D[i, j] <= D[i, Mnn[i, m]]):
224 |
225 | #Replace higher values
226 | Mnn[i, m + 1:] = Mnn[i, m:-1]
227 |
228 | #Replace current value
229 | Mnn[i, m] = j
230 |
231 | #Set last neighbor to index
232 | last_n = Mnn[i, M-1]
233 |
234 | #Set to huge val if unassinged
235 | if last_n == -1:
236 | last_d = HUGE_VAL
237 | else:
238 | last_d = D[i, last_n]
239 | break
240 |
241 |
242 | #Calculate crowding metric
243 | cdef c_calc_d(double[:] d, int[:, :] Mnn, double[:, :] D, cpp_set[int] calc_items, int M):
244 |
245 | cdef:
246 | int i, m
247 |
248 | for i in calc_items:
249 |
250 | d[i] = 1
251 | for m in range(M):
252 | d[i] = d[i] * D[i, Mnn[i, m]]
253 |
254 |
255 | #Returns indexes of items to be recalculated after removal
256 | cdef cpp_set[int] c_get_calc_items(
257 | int[:, :] Mnn,
258 | double[:, :] D,
259 | cpp_set[int] H,
260 | int k, int M):
261 |
262 | cdef:
263 | int i, m
264 | cpp_set[int] calc_items
265 |
266 | calc_items = cpp_set[int]()
267 |
268 | for i in H:
269 |
270 | for m in range(M):
271 |
272 | if Mnn[i, m] == k:
273 |
274 | Mnn[i, m:-1] = Mnn[i, m + 1:]
275 | Mnn[i, M-1] = -1
276 |
277 | calc_items.insert(i)
278 |
279 | return calc_items
280 |
281 |
282 | #Returns elements to remove based on crowding metric d and heap of remaining elements H
283 | cdef int c_get_drop(double[:] d, cpp_set[int] H):
284 |
285 | cdef:
286 | int i, min_i
287 | double min_d
288 |
289 | min_d = HUGE_VAL
290 | min_i = 0
291 |
292 | for i in H:
293 |
294 | if d[i] <= min_d:
295 | min_d = d[i]
296 | min_i = i
297 |
298 | return min_i
299 |
300 |
301 | #Returns vector of positions of minimum values along axis 0 of a 2d memoryview
302 | cdef vector[int] c_get_argmin(double[:, :] X):
303 |
304 | cdef:
305 | int N, M, min_i, n, m
306 | double min_val
307 | vector[int] indexes
308 |
309 | N = X.shape[0]
310 | M = X.shape[1]
311 |
312 | indexes = vector[int]()
313 |
314 | for m in range(M):
315 |
316 | min_i = 0
317 | min_val = X[0, m]
318 |
319 | for n in range(N):
320 |
321 | if X[n, m] < min_val:
322 |
323 | min_i = n
324 | min_val = X[n, m]
325 |
326 | indexes.push_back(min_i)
327 |
328 | return indexes
329 |
330 |
331 | #Returns vector of positions of maximum values along axis 0 of a 2d memoryview
332 | cdef vector[int] c_get_argmax(double[:, :] X):
333 |
334 | cdef:
335 | int N, M, max_i, n, m
336 | double max_val
337 | vector[int] indexes
338 |
339 | N = X.shape[0]
340 | M = X.shape[1]
341 |
342 | indexes = vector[int]()
343 |
344 | for m in range(M):
345 |
346 | max_i = 0
347 | max_val = X[0, m]
348 |
349 | for n in range(N):
350 |
351 | if X[n, m] > max_val:
352 |
353 | max_i = n
354 | max_val = X[n, m]
355 |
356 | indexes.push_back(max_i)
357 |
358 | return indexes
359 |
360 |
361 | #Performs normalization of a 2d memoryview
362 | cdef double[:, :] c_normalize_array(double[:, :] X, vector[int] extremes_max, vector[int] extremes_min):
363 |
364 | cdef:
365 | int N = X.shape[0]
366 | int M = X.shape[1]
367 | int n, m, l, u
368 | double l_val, u_val, diff_val
369 | vector[double] min_vals, max_vals
370 |
371 | min_vals = vector[double]()
372 | max_vals = vector[double]()
373 |
374 | m = 0
375 | for u in extremes_max:
376 | u_val = X[u, m]
377 | max_vals.push_back(u_val)
378 | m = m + 1
379 |
380 | m = 0
381 | for l in extremes_min:
382 | l_val = X[l, m]
383 | min_vals.push_back(l_val)
384 | m = m + 1
385 |
386 | for m in range(M):
387 |
388 | diff_val = max_vals[m] - min_vals[m]
389 | if diff_val == 0.0:
390 | diff_val = 1.0
391 |
392 | for n in range(N):
393 |
394 | X[n, m] = (X[n, m] - min_vals[m]) / diff_val
395 |
396 | return X
--------------------------------------------------------------------------------
/pymoode/cython/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooscaliaproject/pymoode/521d591263ceac29d5236b3bc62a482049a18f86/pymoode/cython/__init__.py
--------------------------------------------------------------------------------
/pymoode/cython/info.pyx:
--------------------------------------------------------------------------------
1 | # distutils: language = c++
2 | # cython: language_level=2, boundscheck=False, wraparound=False, cdivision=True
3 |
4 | def info():
5 | return "This file was based on pymoo to check compilation success"
--------------------------------------------------------------------------------
/pymoode/cython/mnn.pyx:
--------------------------------------------------------------------------------
1 | # distutils: language = c++
2 | # cython: language_level=2, boundscheck=False, wraparound=False, cdivision=True
3 |
4 | # This was implemented using the full distances matrix
5 | # Other strategies can be more efficient depending on the population size and number of objectives
6 | # This approach was the most promising for N = 3
7 | # I believe for a large number of objectives M, some strategy based on upper bounds for distances would be helpful
8 | # Those interested in contributing please contact me at bruscalia12@gmail.com
9 |
10 |
11 | import numpy as np
12 |
13 | from pymoode.cython.utils cimport c_get_drop, c_get_argmin, c_get_argmax, c_normalize_array
14 |
15 | from libcpp cimport bool
16 | from libcpp.vector cimport vector
17 | from libcpp.set cimport set as cpp_set
18 |
19 |
20 | cdef extern from "math.h":
21 | double HUGE_VAL
22 |
23 |
24 | def calc_mnn(double[:, :] X, int n_remove=0):
25 |
26 | cdef:
27 | int N, M, n
28 | cpp_set[int] extremes
29 | vector[int] extremes_min, extremes_max
30 |
31 | N = X.shape[0]
32 | M = X.shape[1]
33 |
34 | if n_remove <= (N - M):
35 | if n_remove < 0:
36 | n_remove = 0
37 | else:
38 | n_remove = N - M
39 |
40 | extremes_min = c_get_argmin(X)
41 | extremes_max = c_get_argmax(X)
42 |
43 | extremes = cpp_set[int]()
44 |
45 | for n in extremes_min:
46 | extremes.insert(n)
47 |
48 | for n in extremes_max:
49 | extremes.insert(n)
50 |
51 | X = c_normalize_array(X, extremes_max, extremes_min)
52 |
53 | return c_calc_mnn(X, n_remove, N, M, extremes)
54 |
55 |
56 | def calc_2nn(double[:, :] X, int n_remove=0):
57 |
58 | cdef:
59 | int N, M, n
60 | cpp_set[int] extremes
61 | vector[int] extremes_min, extremes_max
62 |
63 | N = X.shape[0]
64 | M = X.shape[1]
65 |
66 | if n_remove <= (N - M):
67 | if n_remove < 0:
68 | n_remove = 0
69 | else:
70 | n_remove = N - M
71 |
72 | extremes_min = c_get_argmin(X)
73 | extremes_max = c_get_argmax(X)
74 |
75 | extremes = cpp_set[int]()
76 |
77 | for n in extremes_min:
78 | extremes.insert(n)
79 |
80 | for n in extremes_max:
81 | extremes.insert(n)
82 |
83 | X = c_normalize_array(X, extremes_max, extremes_min)
84 |
85 | M = 2
86 |
87 | return c_calc_mnn(X, n_remove, N, M, extremes)
88 |
89 |
90 | cdef double[:] c_calc_mnn(double[:, :] X, int n_remove, int N, int M, cpp_set[int] extremes):
91 |
92 | cdef:
93 | int n, mm, i, j, n_removed, k, MM
94 | double dij
95 | cpp_set[int] calc_items
96 | cpp_set[int] H
97 | double[:, :] D
98 | double[:] d
99 | int[:, :] Mnn
100 |
101 | # Return infinity if not enough points
102 | if N <= M:
103 | return np.full(N, HUGE_VAL, dtype=np.double)
104 |
105 | # Define items to calculate distances
106 | calc_items = cpp_set[int]()
107 | for n in range(N):
108 | calc_items.insert(n)
109 | for n in extremes:
110 | calc_items.erase(n)
111 |
112 | # Define remaining items to evaluate
113 | H = cpp_set[int]()
114 | for n in range(N):
115 | H.insert(n)
116 |
117 | # Instantiate distances array
118 | _D = np.empty((N, N), dtype=np.double)
119 | D = _D[:, :]
120 |
121 | # Shape of X
122 | MM = X.shape[1]
123 |
124 | # Fill values on D
125 | for i in range(N - 1):
126 | D[i, i] = 0.0
127 |
128 | for j in range(i + 1, N):
129 |
130 | dij = 0
131 | for mm in range(MM):
132 | dij = dij + (X[j, mm] - X[i, mm]) * (X[j, mm] - X[i, mm])
133 |
134 | D[i, j] = dij
135 | D[j, i] = D[i, j]
136 |
137 | D[N-1, N-1] = 0.0
138 |
139 | # Initialize
140 | n_removed = 0
141 |
142 | # Initialize neighbors and distances
143 | # _Mnn = np.full((N, M), -1, dtype=np.intc)
144 | _Mnn = np.argpartition(D, range(1, M+1), axis=1)[:, 1:M+1].astype(np.intc)
145 | dd = np.full((N,), HUGE_VAL, dtype=np.double)
146 |
147 | Mnn = _Mnn[:, :]
148 | d = dd[:]
149 |
150 | # Obtain distance metrics
151 | c_calc_d(d, Mnn, D, calc_items, M)
152 |
153 | # While n_remove not acheived (no need to recalculate if only one item should be removed)
154 | while n_removed < (n_remove - 1):
155 |
156 | # Obtain element to drop
157 | k = c_get_drop(d, H)
158 | H.erase(k)
159 |
160 | # Update index
161 | n_removed = n_removed + 1
162 |
163 | # Get items to be recalculated
164 | calc_items = c_get_calc_items(Mnn, H, k, M)
165 | for n in extremes:
166 | calc_items.erase(n)
167 |
168 | # Fill in neighbors and distance matrix
169 | c_calc_mnn_iter(
170 | X,
171 | Mnn,
172 | D,
173 | N, M,
174 | calc_items,
175 | H
176 | )
177 |
178 | # Obtain distance metrics
179 | c_calc_d(d, Mnn, D, calc_items, M)
180 |
181 | return dd
182 |
183 |
184 | cdef c_calc_mnn_iter(
185 | double[:, :] X,
186 | int[:, :] Mnn,
187 | double[:, :] D,
188 | int N, int M,
189 | cpp_set[int] calc_items,
190 | cpp_set[int] H
191 | ):
192 |
193 | cdef:
194 | int i, j, m
195 |
196 | # Iterate over items to calculate
197 | for i in calc_items:
198 |
199 | # Iterate over elements in X
200 | for j in H:
201 |
202 | # Go to next if same element
203 | if (j == i):
204 | continue
205 |
206 | # Replace at least the last neighbor
207 | elif (D[i, j] <= D[i, Mnn[i, M-1]]) or (Mnn[i, M-1] == -1):
208 |
209 | # Iterate over current values
210 | for m in range(M):
211 |
212 | # Set to current if unassigned
213 | if (Mnn[i, m] == -1):
214 |
215 | # Set last neighbor to index
216 | Mnn[i, m] = j
217 | break
218 |
219 | # Break if checking already corresponding index
220 | elif (j == Mnn[i, m]):
221 | break
222 |
223 | # Distance satisfies condition
224 | elif (D[i, j] <= D[i, Mnn[i, m]]):
225 |
226 | # Replace higher values
227 | Mnn[i, m + 1:] = Mnn[i, m:-1]
228 |
229 | # Replace current value
230 | Mnn[i, m] = j
231 | break
232 |
233 |
234 | # Calculate crowding metric
235 | cdef c_calc_d(double[:] d, int[:, :] Mnn, double[:, :] D, cpp_set[int] calc_items, int M):
236 |
237 | cdef:
238 | int i, m
239 |
240 | for i in calc_items:
241 |
242 | d[i] = 1
243 | for m in range(M):
244 | d[i] = d[i] * D[i, Mnn[i, m]]
245 |
246 |
247 | # Returns indexes of items to be recalculated after removal
248 | cdef cpp_set[int] c_get_calc_items(
249 | int[:, :] Mnn,
250 | cpp_set[int] H,
251 | int k, int M):
252 |
253 | cdef:
254 | int i, m
255 | cpp_set[int] calc_items
256 |
257 | calc_items = cpp_set[int]()
258 |
259 | for i in H:
260 |
261 | for m in range(M):
262 |
263 | if Mnn[i, m] == k:
264 |
265 | Mnn[i, m:-1] = Mnn[i, m + 1:]
266 | Mnn[i, M-1] = -1
267 |
268 | calc_items.insert(i)
269 |
270 | return calc_items
271 |
--------------------------------------------------------------------------------
/pymoode/cython/pruning_cd.pyx:
--------------------------------------------------------------------------------
1 | # distutils: language = c++
2 | # cython: language_level=2, boundscheck=False, wraparound=False, cdivision=True
3 |
4 | import numpy as np
5 |
6 | from pymoode.cython.utils cimport c_get_drop, c_get_argmin, c_get_argmax, c_normalize_array
7 |
8 | from libcpp cimport bool
9 | from libcpp.vector cimport vector
10 | from libcpp.set cimport set as cpp_set
11 |
12 |
13 | cdef extern from "math.h":
14 | double HUGE_VAL
15 |
16 |
17 | # Python definition
18 | def calc_pcd(double[:, :] X, int n_remove=0):
19 |
20 | cdef:
21 | int N, M, n
22 | cpp_set[int] extremes
23 | vector[int] extremes_min, extremes_max
24 | int[:, :] I
25 |
26 | N = X.shape[0]
27 | M = X.shape[1]
28 |
29 | if n_remove <= (N - M):
30 | if n_remove < 0:
31 | n_remove = 0
32 | else:
33 | pass
34 | else:
35 | n_remove = N - M
36 |
37 | extremes_min = c_get_argmin(X)
38 | extremes_max = c_get_argmax(X)
39 |
40 | extremes = cpp_set[int]()
41 |
42 | for n in extremes_min:
43 | extremes.insert(n)
44 |
45 | for n in extremes_max:
46 | extremes.insert(n)
47 |
48 | _I = np.argsort(X, axis=0, kind='mergesort').astype(np.intc)
49 | I = _I[:, :]
50 |
51 | X = c_normalize_array(X, extremes_max, extremes_min)
52 |
53 | return c_calc_pcd(X, I, n_remove, N, M, extremes)
54 |
55 |
56 | # Returns crowding metrics with recursive elimination
57 | cdef c_calc_pcd(double[:, :] X, int[:, :] I, int n_remove, int N, int M, cpp_set[int] extremes):
58 |
59 | cdef:
60 | int n, n_removed, k
61 | cpp_set[int] calc_items
62 | cpp_set[int] H
63 | double[:, :] D
64 | double[:] d
65 |
66 | # Define items to calculate distances
67 | calc_items = cpp_set[int]()
68 | for n in range(N):
69 | calc_items.insert(n)
70 | for n in extremes:
71 | calc_items.erase(n)
72 |
73 | # Define remaining items to evaluate
74 | H = cpp_set[int]()
75 | for n in range(N):
76 | H.insert(n)
77 |
78 | # Initialize
79 | n_removed = 0
80 |
81 | # Initialize neighbors and distances
82 | _D = np.full((N, M), HUGE_VAL, dtype=np.double)
83 | dd = np.full((N,), HUGE_VAL, dtype=np.double)
84 |
85 | D = _D[:, :]
86 | d = dd[:]
87 |
88 | # Fill in neighbors and distance matrix
89 | c_calc_pcd_iter(
90 | X,
91 | I,
92 | D,
93 | N, M,
94 | calc_items,
95 | )
96 |
97 | # Obtain distance metrics
98 | c_calc_d(d, D, calc_items, M)
99 |
100 | # While n_remove not acheived
101 | while n_removed < (n_remove - 1):
102 |
103 | # Obtain element to drop
104 | k = c_get_drop(d, H)
105 | H.erase(k)
106 |
107 | # Update index
108 | n_removed = n_removed + 1
109 |
110 | # Get items to be recalculated
111 | calc_items = c_get_calc_items(I, k, M, N)
112 | for n in extremes:
113 | calc_items.erase(n)
114 |
115 | # Fill in neighbors and distance matrix
116 | c_calc_pcd_iter(
117 | X,
118 | I,
119 | D,
120 | N, M,
121 | calc_items,
122 | )
123 |
124 | # Obtain distance metrics
125 | c_calc_d(d, D, calc_items, M)
126 |
127 | return dd
128 |
129 |
130 | # Iterate
131 | cdef c_calc_pcd_iter(
132 | double[:, :] X,
133 | int[:, :] I,
134 | double[:, :] D,
135 | int N, int M,
136 | cpp_set[int] calc_items,
137 | ):
138 |
139 | cdef:
140 | int i, m, n, l, u
141 |
142 | # Iterate over items to calculate
143 | for i in calc_items:
144 |
145 | # Iterate over elements in X
146 | for m in range(M):
147 |
148 | for n in range(N):
149 |
150 | if i == I[n, m]:
151 |
152 | l = I[n - 1, m]
153 | u = I[n + 1, m]
154 |
155 | D[i, m] = (X[u, m] - X[l, m]) / M
156 |
157 |
158 | # Calculate crowding metric
159 | cdef c_calc_d(double[:] d, double[:, :] D, cpp_set[int] calc_items, int M):
160 |
161 | cdef:
162 | int i, m
163 |
164 | for i in calc_items:
165 |
166 | d[i] = 0
167 | for m in range(M):
168 | d[i] = d[i] + D[i, m]
169 |
170 |
171 | # Returns indexes of items to be recalculated after removal
172 | cdef cpp_set[int] c_get_calc_items(
173 | int[:, :] I,
174 | int k, int M, int N
175 | ):
176 |
177 | cdef:
178 | int n, m
179 | cpp_set[int] calc_items
180 |
181 | calc_items = cpp_set[int]()
182 |
183 | # Iterate over all elements in I
184 | for m in range(M):
185 |
186 | for n in range(N):
187 |
188 | if I[n, m] == k:
189 |
190 | # Add to set of items to be recalculated
191 | calc_items.insert(I[n - 1, m])
192 | calc_items.insert(I[n + 1, m])
193 |
194 | # Remove element from sorted array
195 | I[n:-1, m] = I[n + 1:, m]
196 |
197 | return calc_items
198 |
--------------------------------------------------------------------------------
/pymoode/cython/spacing_neighbors.pyx:
--------------------------------------------------------------------------------
1 | # distutils: language = c++
2 | # cython: language_level=2, boundscheck=False, wraparound=False, cdivision=True
3 |
4 | import numpy as np
5 |
6 | from libcpp cimport bool
7 | from libcpp.vector cimport vector
8 | from libcpp.set cimport set as cpp_set
9 |
10 |
11 | cdef extern from "math.h":
12 | double HUGE_VAL
13 |
14 |
15 | def calc_spacing_distances(double[:, :] X):
16 |
17 | return c_calc_spacing_distances(X)
18 |
19 |
20 | cdef c_calc_spacing_distances(double[:, :] X):
21 |
22 | cdef:
23 | int N, M, n, m
24 | cpp_set[int] calc_items
25 | double[:, :] D
26 | double[:] d
27 | double dij, _dijm
28 |
29 | N = X.shape[0]
30 | M = X.shape[1]
31 |
32 | # Initialize neighbors and distances
33 | _D = np.full((N, N), -1.0, dtype=np.double)
34 | dd = np.full((N,), HUGE_VAL, dtype=np.double)
35 |
36 | D = _D[:, :]
37 | d = dd[:]
38 |
39 | # Iterate over items to calculate
40 | for i in range(N):
41 |
42 | # Iterate over elements in X
43 | for j in range(N):
44 |
45 | # Calculate distance if elements satisfy rule
46 | if ((j != i) and (D[i, j] <= d[i])):
47 |
48 | # Calculate if different from -1
49 | if D[i, j] == -1.0:
50 |
51 | # Squared distance
52 | dij = 0
53 | for m in range(M):
54 | _dijm = X[j, m] - X[i, m]
55 |
56 | if _dijm >= 0:
57 | dij = dij + _dijm
58 |
59 | else:
60 | dij = dij - _dijm
61 |
62 | # Fill values
63 | D[i, j] = dij
64 | D[j, i] = D[i, j]
65 |
66 | else:
67 | dij = D[i, j]
68 |
69 | # Check is any should be replaced
70 | if (dij <= d[i]):
71 | d[i] = dij
72 |
73 | return dd
74 |
--------------------------------------------------------------------------------
/pymoode/cython/utils.pxd:
--------------------------------------------------------------------------------
1 | # distutils: language = c++
2 | # cython: language_level=2, boundscheck=False, wraparound=False, cdivision=True
3 |
4 | import numpy as np
5 |
6 | from libcpp cimport bool
7 | from libcpp.vector cimport vector
8 | from libcpp.set cimport set as cpp_set
9 |
10 |
11 | cdef extern from "math.h":
12 | double HUGE_VAL
13 |
14 |
15 | # Returns elements to remove based on crowding metric d and heap of remaining elements H
16 | cdef inline int c_get_drop(double[:] d, cpp_set[int] H):
17 |
18 | cdef:
19 | int i, min_i
20 | double min_d
21 |
22 | min_d = HUGE_VAL
23 | min_i = 0
24 |
25 | for i in H:
26 |
27 | if d[i] <= min_d:
28 | min_d = d[i]
29 | min_i = i
30 |
31 | return min_i
32 |
33 |
34 | # Returns vector of positions of minimum values along axis 0 of a 2d memoryview
35 | cdef inline vector[int] c_get_argmin(double[:, :] X):
36 |
37 | cdef:
38 | int N, M, min_i, n, m
39 | double min_val
40 | vector[int] indexes
41 |
42 | N = X.shape[0]
43 | M = X.shape[1]
44 |
45 | indexes = vector[int]()
46 |
47 | for m in range(M):
48 |
49 | min_i = 0
50 | min_val = X[0, m]
51 |
52 | for n in range(N):
53 |
54 | if X[n, m] < min_val:
55 |
56 | min_i = n
57 | min_val = X[n, m]
58 |
59 | indexes.push_back(min_i)
60 |
61 | return indexes
62 |
63 |
64 | # Returns vector of positions of maximum values along axis 0 of a 2d memoryview
65 | cdef inline vector[int] c_get_argmax(double[:, :] X):
66 |
67 | cdef:
68 | int N, M, max_i, n, m
69 | double max_val
70 | vector[int] indexes
71 |
72 | N = X.shape[0]
73 | M = X.shape[1]
74 |
75 | indexes = vector[int]()
76 |
77 | for m in range(M):
78 |
79 | max_i = 0
80 | max_val = X[0, m]
81 |
82 | for n in range(N):
83 |
84 | if X[n, m] > max_val:
85 |
86 | max_i = n
87 | max_val = X[n, m]
88 |
89 | indexes.push_back(max_i)
90 |
91 | return indexes
92 |
93 |
94 | # Performs normalization of a 2d memoryview
95 | cdef inline double[:, :] c_normalize_array(double[:, :] X, vector[int] extremes_max, vector[int] extremes_min):
96 |
97 | cdef:
98 | int N = X.shape[0]
99 | int M = X.shape[1]
100 | int n, m, l, u
101 | double l_val, u_val, diff_val
102 | vector[double] min_vals, max_vals
103 |
104 | min_vals = vector[double]()
105 | max_vals = vector[double]()
106 |
107 | m = 0
108 | for u in extremes_max:
109 | u_val = X[u, m]
110 | max_vals.push_back(u_val)
111 | m = m + 1
112 |
113 | m = 0
114 | for l in extremes_min:
115 | l_val = X[l, m]
116 | min_vals.push_back(l_val)
117 | m = m + 1
118 |
119 | for m in range(M):
120 |
121 | diff_val = max_vals[m] - min_vals[m]
122 | if diff_val == 0.0:
123 | diff_val = 1.0
124 |
125 | for n in range(N):
126 |
127 | X[n, m] = (X[n, m] - min_vals[m]) / diff_val
128 |
129 | return X
130 |
--------------------------------------------------------------------------------
/pymoode/misc/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooscaliaproject/pymoode/521d591263ceac29d5236b3bc62a482049a18f86/pymoode/misc/__init__.py
--------------------------------------------------------------------------------
/pymoode/misc/mnn.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from scipy.spatial.distance import pdist, squareform
3 |
4 |
5 | def calc_mnn(X, n_remove=0, twonn=False):
6 |
7 | N = X.shape[0]
8 | M = X.shape[1]
9 |
10 | if n_remove <= (N - M):
11 | if n_remove < 0:
12 | n_remove = 0
13 | else:
14 | pass
15 | else:
16 | n_remove = N - M
17 |
18 | if twonn:
19 | M = 2
20 |
21 | if N <= M:
22 | return np.full(N, np.inf)
23 |
24 | extremes_min = np.argmin(X, axis=0)
25 | extremes_max = np.argmax(X, axis=0)
26 |
27 | min_vals = np.min(X, axis=0)
28 | max_vals = np.max(X, axis=0)
29 |
30 | extremes = np.concatenate((extremes_min, extremes_max))
31 |
32 | X = (X - min_vals) / (max_vals - min_vals)
33 |
34 | H = np.arange(N)
35 |
36 | D = squareform(pdist(X, metric="sqeuclidean"))
37 | Dnn = np.partition(D, range(1, M+1), axis=1)[:, 1:M+1]
38 | d = np.prod(Dnn, axis=1)
39 | d[extremes] = np.inf
40 |
41 | n_removed = 0
42 |
43 | #While n_remove not acheived
44 | while n_removed < (n_remove - 1):
45 |
46 | #Obtain element to drop
47 | _d = d[H]
48 | _k = np.argmin(_d)
49 | k = H[_k]
50 | H = H[H != k]
51 |
52 | #Update index
53 | n_removed = n_removed + 1
54 | if n_removed == n_remove:
55 | break
56 |
57 | else:
58 |
59 | D[:, k] = np.inf
60 | Dnn[H] = np.partition(D[H], range(1, M+1), axis=1)[:, 1:M+1]
61 | d[H] = np.prod(Dnn[H], axis=1)
62 | d[extremes] = np.inf
63 |
64 | return d
65 |
--------------------------------------------------------------------------------
/pymoode/misc/pruning_cd.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 |
4 | def calc_pcd(X: np.ndarray, n_remove=0):
5 |
6 | N = X.shape[0]
7 | M = X.shape[1]
8 |
9 | if n_remove <= (N - M):
10 | if n_remove < 0:
11 | n_remove = 0
12 | else:
13 | pass
14 | else:
15 | n_remove = N - M
16 |
17 | extremes_min = np.argmin(X, axis=0)
18 | extremes_max = np.argmax(X, axis=0)
19 |
20 | min_vals = np.min(X, axis=0)
21 | max_vals = np.max(X, axis=0)
22 |
23 | extremes = np.concatenate((extremes_min, extremes_max))
24 |
25 | X = (X - min_vals) / (max_vals - min_vals)
26 |
27 | H = np.arange(N)
28 | d = np.full(N, np.inf)
29 |
30 | I = np.argsort(X, axis=0, kind='mergesort')
31 |
32 | # sort the objective space values for the whole matrix
33 | _X = X[I, np.arange(M)]
34 |
35 | # calculate the distance from each point to the last and next
36 | dist = np.row_stack([_X, np.full(M, np.inf)]) - np.row_stack([np.full(M, -np.inf), _X])
37 |
38 | # prepare the distance to last and next vectors
39 | dist_to_last, dist_to_next = dist, np.copy(dist)
40 | dist_to_last, dist_to_next = dist_to_last[:-1], dist_to_next[1:]
41 |
42 | # if we divide by zero because all values in one columns are equal replace by none
43 | dist_to_last[np.isnan(dist_to_last)] = 0.0
44 | dist_to_next[np.isnan(dist_to_next)] = 0.0
45 |
46 | # sum up the distance to next and last and norm by objectives - also reorder from sorted list
47 | J = np.argsort(I, axis=0)
48 | _d = np.sum(dist_to_last[J, np.arange(M)] + dist_to_next[J, np.arange(M)], axis=1)
49 | d[H] = _d
50 | d[extremes] = np.inf
51 |
52 | n_removed = 0
53 |
54 | #While n_remove not acheived
55 | while n_removed < (n_remove - 1):
56 |
57 | #Obtain element to drop
58 | _d = d[H]
59 | _k = np.argmin(_d)
60 | k = H[_k]
61 |
62 | H = H[H != k]
63 |
64 | #Update index
65 | n_removed = n_removed + 1
66 |
67 | I = np.argsort(X[H].copy(), axis=0, kind='mergesort')
68 |
69 | # sort the objective space values for the whole matrix
70 | _X = X[H].copy()[I, np.arange(M)]
71 |
72 | # calculate the distance from each point to the last and next
73 | dist = np.row_stack([_X, np.full(M, np.inf)]) - np.row_stack([np.full(M, -np.inf), _X])
74 |
75 | # prepare the distance to last and next vectors
76 | dist_to_last, dist_to_next = dist, np.copy(dist)
77 | dist_to_last, dist_to_next = dist_to_last[:-1], dist_to_next[1:]
78 |
79 | # if we divide by zero because all values in one columns are equal replace by none
80 | dist_to_last[np.isnan(dist_to_last)] = 0.0
81 | dist_to_next[np.isnan(dist_to_next)] = 0.0
82 |
83 | # sum up the distance to next and last and norm by objectives - also reorder from sorted list
84 | J = np.argsort(I, axis=0)
85 | _d = np.sum(dist_to_last[J, np.arange(M)] + dist_to_next[J, np.arange(M)], axis=1)
86 | d[H] = _d
87 | d[extremes] = np.inf
88 |
89 | # Divide by number of objectives to obtain correct metric
90 | d = d / M
91 |
92 | return d
93 |
94 |
--------------------------------------------------------------------------------
/pymoode/operators/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooscaliaproject/pymoode/521d591263ceac29d5236b3bc62a482049a18f86/pymoode/operators/__init__.py
--------------------------------------------------------------------------------
/pymoode/operators/deop.py:
--------------------------------------------------------------------------------
1 | # Native
2 | from abc import abstractmethod
3 | from typing import Optional, Union
4 |
5 | # External
6 | import numpy as np
7 |
8 | # pymoo
9 | from pymoo.core.crossover import Crossover
10 | from pymoo.core.population import Population
11 | from pymoo.core.problem import Problem
12 |
13 |
14 | # =========================================================================================================
15 | # Implementation
16 | # =========================================================================================================
17 |
18 | class DifferentialOperator(Crossover):
19 |
20 | def __init__(self, n_parents=None, **kwargs):
21 | """White label for differential evolution operators
22 |
23 | Parameters
24 | ----------
25 | n_parents : int | None, optional
26 | Number of parents necessary in its operations. Useful for compatibility with pymoo.
27 | """
28 | # __init__ operator
29 | super().__init__(n_parents=n_parents, n_offsprings=1, prob=1.0, **kwargs)
30 |
31 | @staticmethod
32 | def default_prepare(pop: Population, parents: Union[Population, np.ndarray]):
33 | """Utility function that converts population and parents from pymoo Selection to pop and X
34 |
35 | Parameters
36 | ----------
37 | pop : Population
38 | pymoo population
39 |
40 | parents : Population | np.ndarray (n_samples, n_parents) | None
41 | Parent population or indices
42 |
43 | Returns
44 | -------
45 | pop, X : Population (n_samples, n_parents), np.ndarray (n_parents, n_samples, n_var)
46 | Population and corresponding decision variables
47 | """
48 | # Convert pop if parents is not None
49 | if parents is not None:
50 | pop = pop[parents]
51 |
52 | # Get all X values for mutation parents
53 | X = np.swapaxes(pop, 0, 1).get("X")
54 | return pop, X
55 |
56 | @abstractmethod
57 | def do(
58 | self,
59 | problem: Problem,
60 | pop: Population,
61 | parents: Optional[Union[Population, np.ndarray]] = None,
62 | **kwargs
63 | ):
64 | pass
65 |
66 | @abstractmethod
67 | def _do(self, problem: Problem, X: np.ndarray, **kwargs):
68 | pass
69 |
--------------------------------------------------------------------------------
/pymoode/operators/des.py:
--------------------------------------------------------------------------------
1 | # Native
2 | import warnings
3 |
4 | # External
5 | import numpy as np
6 |
7 | # pymoo imports
8 | from pymoo.core.selection import Selection
9 | from pymoo.core.population import Population
10 | from pymoo.core.problem import Problem
11 |
12 |
13 | # =========================================================================================================
14 | # Implementation
15 | # =========================================================================================================
16 |
17 |
18 | # This is the core differential evolution selection class
19 | class DES(Selection):
20 |
21 | def __init__(self, variant: str, **kwargs):
22 | """Differential Evolution parent selection class
23 |
24 | Parameters
25 | ----------
26 | variant : str, optional
27 | Differential evolution strategy. Must be a string in the format: "DE/selection/n/crossover", in which, n in an integer of number of difference vectors, and crossover is either 'bin' or 'exp'. Selection variants are:
28 |
29 | - 'ranked'
30 | - 'rand'
31 | - 'best'
32 | - 'current-to-best'
33 | - 'current-to-rand'
34 | - 'rand-to-best'
35 | """
36 |
37 | super().__init__()
38 | self.variant = variant
39 |
40 | def _do(
41 | self,
42 | problem: Problem,
43 | pop: Population,
44 | n_select: int,
45 | n_parents: int,
46 | **kwargs
47 | ):
48 |
49 | # Obtain number of elements in population
50 | n_pop = len(pop)
51 | if n_pop != n_select:
52 | _warn_n_select()
53 |
54 | if self.variant == "ranked":
55 | """Proposed by Zhang et al. (2021). doi.org/10.1016/j.asoc.2021.107317"""
56 | P = self._ranked(pop, n_select, n_parents)
57 |
58 | elif self.variant == "best":
59 | P = self._best(pop, n_select, n_parents)
60 |
61 | elif self.variant == "current-to-best":
62 | P = self._current_to_best(pop, n_select, n_parents)
63 |
64 | elif self.variant == "current-to-rand":
65 | P = self._current_to_rand(pop, n_select, n_parents)
66 |
67 | elif self.variant == "rand-to-best":
68 | P = self._rand_to_best(pop, n_select, n_parents)
69 |
70 | elif self.variant == "rand":
71 | P = self._rand(pop, n_select, n_parents)
72 |
73 | else:
74 | _warn_variant()
75 | P = self._rand(pop, n_select, n_parents)
76 |
77 | return P
78 |
79 | def _rand(self, pop, n_select, n_parents, **kwargs):
80 |
81 | # len of pop
82 | n_pop = len(pop)
83 |
84 | # Base form
85 | P = np.empty([n_select, n_parents], dtype=int)
86 |
87 | # Fill target vector with corresponding parent
88 | target = np.arange(n_pop)[:n_select]
89 |
90 | # Fill next columns in loop
91 | for j in range(n_parents):
92 |
93 | P[:, j] = np.random.choice(n_pop, n_select)
94 | reselect = get_reselect(P, target, j)
95 |
96 | while np.any(reselect):
97 | P[reselect, j] = np.random.choice(n_pop, reselect.sum())
98 | reselect = get_reselect(P, target, j)
99 |
100 | return P
101 |
102 | def _best(self, pop, n_select, n_parents, **kwargs):
103 |
104 | # len of pop
105 | n_pop = len(pop)
106 |
107 | # Base form
108 | P = np.empty([n_select, n_parents], dtype=int)
109 |
110 | # Fill target vector with corresponding parent
111 | target = np.arange(n_pop)[:n_select]
112 |
113 | # Fill first column with best candidate
114 | P[:, 0] = 0
115 |
116 | # Fill next columns in loop
117 | for j in range(1, n_parents):
118 |
119 | P[:, j] = np.random.choice(n_pop, n_select)
120 | reselect = get_reselect(P, target, j)
121 |
122 | while np.any(reselect):
123 | P[reselect, j] = np.random.choice(n_pop, reselect.sum())
124 | reselect = get_reselect(P, target, j)
125 |
126 | return P
127 |
128 | def _current_to_best(self, pop, n_select, n_parents, **kwargs):
129 |
130 | # len of pop
131 | n_pop = len(pop)
132 |
133 | # Base form
134 | P = np.empty([n_select, n_parents], dtype=int)
135 |
136 | # Fill target vector with corresponding parent
137 | target = np.arange(n_pop)[:n_select]
138 |
139 | # Fill first column with current candidate
140 | P[:, 0] = np.arange(n_pop)
141 |
142 | # Fill first direction from current
143 | P[:, 2] = np.arange(n_pop)
144 |
145 | # Towards best
146 | P[:, 1] = 0
147 |
148 | # Fill next columns in loop
149 | for j in range(3, n_parents):
150 |
151 | P[:, j] = np.random.choice(n_pop, n_select)
152 | reselect = get_reselect(P, target, j)
153 |
154 | while np.any(reselect):
155 | P[reselect, j] = np.random.choice(n_pop, reselect.sum())
156 | reselect = get_reselect(P, target, j)
157 |
158 | return P
159 |
160 | def _current_to_rand(self, pop, n_select, n_parents, **kwargs):
161 |
162 | # len of pop
163 | n_pop = len(pop)
164 |
165 | # Base form
166 | P = np.empty([n_select, n_parents], dtype=int)
167 |
168 | # Fill target vector with corresponding parent
169 | target = np.arange(n_pop)[:n_select]
170 |
171 | # Fill first column with current candidate
172 | P[:, 0] = np.arange(n_pop)
173 |
174 | # Fill first direction from current
175 | P[:, 2] = np.arange(n_pop)
176 |
177 | # Towards random
178 | P[:, 1] = np.random.choice(n_pop, n_select)
179 | reselect = get_reselect(P, target, 1)
180 |
181 | while np.any(reselect):
182 | P[reselect, 1] = np.random.choice(n_pop, reselect.sum())
183 | reselect = get_reselect(P, target, 1)
184 |
185 | # Fill next columns in loop
186 | for j in range(3, n_parents):
187 |
188 | P[:, j] = np.random.choice(n_pop, n_select)
189 | reselect = get_reselect(P, target, j)
190 |
191 | while np.any(reselect):
192 | P[reselect, j] = np.random.choice(n_pop, reselect.sum())
193 | reselect = get_reselect(P, target, j)
194 |
195 | return P
196 |
197 | def _rand_to_best(self, pop, n_select, n_parents, **kwargs):
198 |
199 | PB = self._best(pop, n_select, n_parents, **kwargs)
200 | P = PB.copy()
201 |
202 | P[:, 0] = PB[:, 1]
203 | P[:, 1] = PB[:, 0]
204 |
205 | return P
206 |
207 | def _ranked(self, pop, n_select, n_parents, **kwargs):
208 |
209 | P = self._rand(pop, n_select, n_parents, **kwargs)
210 | P[:, 1:] = rank_sort(P[:, 1:], pop)
211 |
212 | return P
213 |
214 |
215 | def _warn_n_select():
216 | warnings.warn(
217 | "DE parent selection is supposed to work with n_select as the population size",
218 | UserWarning
219 | )
220 |
221 |
222 | def _warn_variant():
223 | warnings.warn(
224 | "Unknown selection variant; using 'rand' instead",
225 | UserWarning
226 | )
227 |
228 |
229 | def get_reselect(P: np.ndarray, target: np.ndarray, j: int):
230 | return (P[:, j] == target) | (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1)
231 |
232 |
233 | def ranks_from_cv(pop: Population):
234 |
235 | ranks = pop.get("rank")
236 | cv_elements = ranks == None
237 |
238 | if np.any(cv_elements):
239 | ranks[cv_elements] = np.arange(len(pop))[cv_elements]
240 |
241 | return ranks
242 |
243 |
244 | def rank_sort(P: np.ndarray, pop: Population):
245 |
246 | ranks = ranks_from_cv(pop)
247 |
248 | sorted = np.argsort(ranks[P], axis=1, kind="stable")
249 | S = np.take_along_axis(P, sorted, axis=1)
250 |
251 | P[:, 0] = S[:, 0]
252 |
253 | n_diffs = int((P.shape[1] - 1) / 2)
254 |
255 | for j in range(1, n_diffs + 1):
256 | P[:, 2*j - 1] = S[:, j]
257 | P[:, 2*j] = S[:, -j]
258 |
259 | return P
260 |
261 |
262 | def reiforce_directions(P: np.ndarray, pop: Population):
263 |
264 | ranks = ranks_from_cv(pop)
265 |
266 | ranks = ranks[P]
267 | S = P.copy()
268 |
269 | n_diffs = int(P.shape[1] / 2)
270 |
271 | for j in range(0, n_diffs):
272 | bad_directions = ranks[:, 2*j] > ranks[:, 2*j + 1]
273 | P[bad_directions, 2*j] = S[bad_directions, 2*j + 1]
274 | P[bad_directions, 2*j + 1] = S[bad_directions, 2*j]
275 |
276 | return P
277 |
--------------------------------------------------------------------------------
/pymoode/operators/dex.py:
--------------------------------------------------------------------------------
1 | # Native
2 | from abc import abstractmethod
3 | from typing import Optional, Union
4 |
5 | # External
6 | import numpy as np
7 |
8 | # pymoo imports
9 | from pymoo.core.population import Population
10 | from pymoo.core.problem import Problem
11 |
12 | # pymoode imports
13 | from pymoode.operators.deop import DifferentialOperator
14 |
15 |
16 | # =========================================================================================================
17 | # Implementation
18 | # =========================================================================================================
19 |
20 | class DifferentialCrossover(DifferentialOperator):
21 |
22 | def __init__(self,
23 | variant="bin",
24 | CR=0.7,
25 | at_least_once=True,
26 | **kwargs):
27 | """Differential evolution crossover
28 | (DE mutation is considered a part of this operator)
29 |
30 | Parameters
31 | ----------
32 | variant : str | callable, optional
33 | Crossover variant. Must be either "bin", "exp", or callable. By default "bin".
34 | If callable, it has the form:
35 | ``cross_function(n_matings, n_var, CR, at_least_once=True)``
36 |
37 | CR : float, optional
38 | Crossover parameter. Defined in the range [0, 1]
39 | To reinforce mutation, use higher values. To control convergence speed, use lower values.
40 |
41 | at_least_once : bool, optional
42 | Either or not offsprings must inherit at least one attribute from mutant vectors, by default True
43 | """
44 |
45 | # __init__ operator
46 | super().__init__(n_parents=2, **kwargs)
47 |
48 | self.CR = CR
49 | self.variant = variant
50 | self.at_least_once = at_least_once
51 |
52 | def do(
53 | self,
54 | problem: Problem,
55 | pop: Population,
56 | parents: Optional[Union[Population, np.ndarray]] = None,
57 | **kwargs
58 | ):
59 |
60 | # Convert pop if parents is not None
61 | pop, X = self.default_prepare(pop, parents)
62 |
63 | # Create child vectors
64 | U = self._do(problem, X, **kwargs)
65 |
66 | return Population.new("X", U)
67 |
68 | @abstractmethod
69 | def _do(self, problem: Problem, X: np.ndarray, **kwargs):
70 | pass
71 |
72 |
73 | class DEX(DifferentialCrossover):
74 |
75 | def __init__(self,
76 | variant="bin",
77 | CR=0.7,
78 | at_least_once=True,
79 | **kwargs):
80 |
81 | super().__init__(
82 | variant=variant, CR=CR,
83 | at_least_once=at_least_once,
84 | **kwargs,
85 | )
86 |
87 | if self.variant == "bin":
88 | self.cross_function = cross_binomial
89 | elif self.variant == "exp":
90 | self.cross_function = cross_exp
91 | elif hasattr(self.variant, "__call__"):
92 | self.cross_function = self.variant
93 | else:
94 | raise ValueError("Crossover variant must be either 'bin', 'exp', or callable")
95 |
96 | def _do(self, problem: Problem, X: np.ndarray, **kwargs):
97 |
98 | # Decompose input vector
99 | V = X[1]
100 | X_ = X[0]
101 | U = np.array(X_, copy=True)
102 |
103 | # About X
104 | n_matings, n_var = X_.shape
105 |
106 | # Mask
107 | M = self.cross_function(n_matings, n_var, self.CR, self.at_least_once)
108 | U[M] = V[M]
109 |
110 | return U
111 |
112 |
113 | # =========================================================================================================
114 | # Crossovers
115 | # =========================================================================================================
116 |
117 |
118 | # From version 0.5.0 of pymoo
119 | def row_at_least_once_true(M: np.ndarray):
120 |
121 | _, d = M.shape
122 |
123 | for k in np.where(~np.any(M, axis=1))[0]:
124 | M[k, np.random.randint(d)] = True
125 |
126 | return M
127 |
128 |
129 | def cross_binomial(n_matings, n_var, prob, at_least_once=True):
130 |
131 | M = np.random.random((n_matings, n_var)) < prob
132 |
133 | if at_least_once:
134 | M = row_at_least_once_true(M)
135 |
136 | return M
137 |
138 |
139 | def cross_exp(n_matings, n_var, prob, at_least_once=True):
140 |
141 | # the mask do to the crossover
142 | M = np.full((n_matings, n_var), False)
143 |
144 | # start point of crossover
145 | s = np.random.randint(0, n_var, size=n_matings)
146 |
147 | # create for each individual the crossover range
148 | for i in range(n_matings):
149 |
150 | # the actual index where we start
151 | start = s[i]
152 | for j in range(n_var):
153 |
154 | # the current position where we are pointing to
155 | current = (start + j) % n_var
156 |
157 | # replace only if random value keeps being smaller than CR
158 | if np.random.random() <= prob:
159 | M[i, current] = True
160 | else:
161 | break
162 |
163 | if at_least_once:
164 | M = row_at_least_once_true(M)
165 |
166 | return M
167 |
--------------------------------------------------------------------------------
/pymoode/operators/variant.py:
--------------------------------------------------------------------------------
1 | # Native
2 | import warnings
3 |
4 | # External
5 | import numpy as np
6 |
7 | # pymoo imports
8 | from pymoo.operators.mutation.nom import NoMutation
9 | from pymoo.core.infill import InfillCriterion
10 | from pymoo.core.population import Population
11 | from pymoo.core.problem import Problem
12 |
13 | # pymoode imports
14 | from pymoode.operators.des import DES
15 | from pymoode.operators.dex import DEX
16 | from pymoode.operators.dem import DEM
17 |
18 |
19 | # =========================================================================================================
20 | # Implementation
21 | # =========================================================================================================
22 |
23 | class DifferentialVariant(InfillCriterion):
24 |
25 | def __init__(self,
26 | variant="DE/rand/1/bin",
27 | CR=0.7,
28 | F=(0.5, 1.0),
29 | gamma=1e-4,
30 | de_repair="bounce-back",
31 | genetic_mutation=None,
32 | **kwargs):
33 | """InfillCriterion class for Differential Evolution
34 |
35 | Parameters
36 | ----------
37 | variant : str, optional
38 | Differential evolution strategy. Must be a string in the format: "DE/selection/n/crossover", in which, n in an integer of number of difference vectors, and crossover is either 'bin' or 'exp'. Selection variants are:
39 |
40 | - 'ranked'
41 | - 'rand'
42 | - 'best'
43 | - 'current-to-best'
44 | - 'current-to-rand'
45 | - 'rand-to-best'
46 |
47 | The selection strategy 'ranked' might be helpful to improve convergence speed without much harm to diversity. Defaults to 'DE/rand/1/bin'.
48 |
49 | CR : float, optional
50 | Crossover parameter. Defined in the range [0, 1]
51 | To reinforce mutation, use higher values. To control convergence speed, use lower values.
52 |
53 | F : iterable of float or float, optional
54 | Scale factor or mutation parameter. Defined in the range (0, 2]
55 | To reinforce exploration, use higher values; for exploitation, use lower values.
56 |
57 | gamma : float, optional
58 | Jitter deviation parameter. Should be in the range (0, 2). Defaults to 1e-4.
59 |
60 | de_repair : str, optional
61 | Repair of DE mutant vectors. Is either callable or one of:
62 |
63 | - 'bounce-back'
64 | - 'midway'
65 | - 'rand-init'
66 | - 'to-bounds'
67 |
68 | If callable, has the form fun(X, Xb, xl, xu) in which X contains mutated vectors including violations, Xb contains reference vectors for repair in feasible space, xl is a 1d vector of lower bounds, and xu a 1d vector of upper bounds.
69 | Defaults to 'bounce-back'.
70 |
71 | genetic_mutation : Mutation, optional
72 | Pymoo's genetic algorithm's mutation operator after crossover. Defaults to NoMutation().
73 |
74 | repair : Repair, optional
75 | Pymoo's repair operator after mutation. Defaults to NoRepair().
76 | """
77 |
78 | # Fix deprecated pm kwargs
79 | kwargs, genetic_mutation = _fix_deprecated_pm_kwargs(kwargs, genetic_mutation)
80 |
81 | # Default initialization of InfillCriterion
82 | super().__init__(eliminate_duplicates=None, **kwargs)
83 |
84 | # Parse the information from the string
85 | _, selection_variant, n_diff, crossover_variant, = variant.split("/")
86 | n_diffs = int(n_diff)
87 |
88 | # When "to" in variant there are more than 1 difference vectors
89 | if "-to-" in variant:
90 | n_diffs = n_diffs + 1
91 |
92 | # Define parent selection operator
93 | self.selection = DES(selection_variant)
94 |
95 | # Define differential evolution mutation
96 | self.de_mutation = DEM(F=F, gamma=gamma, de_repair=de_repair, n_diffs=n_diffs)
97 |
98 | # Define crossover strategy (DE mutation is included)
99 | self.crossover = DEX(variant=crossover_variant, CR=CR, at_least_once=True)
100 |
101 | # Define posterior mutation strategy and repair
102 | self.genetic_mutation = genetic_mutation if genetic_mutation is not None else NoMutation()
103 |
104 | def _do(
105 | self,
106 | problem: Problem,
107 | pop: Population,
108 | n_offsprings: int,
109 | **kwargs
110 | ):
111 |
112 | # Select parents including donor vector
113 | parents = self.selection(
114 | problem, pop,
115 | n_offsprings, self.n_parents,
116 | to_pop=False, **kwargs,
117 | )
118 |
119 | # Mutant vectors from DE
120 | mutants = self.de_mutation(problem, pop, parents, **kwargs)
121 |
122 | # Crossover between mutant vector and target
123 | matings = self.merge_columnwise(pop, mutants)
124 | trials = self.crossover(problem, matings, **kwargs)
125 |
126 | # Perform posterior mutation and repair if passed
127 | off = self.genetic_mutation(problem, trials, **kwargs)
128 |
129 | return off
130 |
131 | @property
132 | def n_parents(self):
133 | return self.de_mutation.n_parents
134 |
135 | @staticmethod
136 | def merge_columnwise(*populations):
137 | matings = np.column_stack(populations).view(Population)
138 | return matings
139 |
140 |
141 |
142 | def _fix_deprecated_pm_kwargs(kwargs, genetic_mutation):
143 | if "pm" in kwargs:
144 | warnings.warn(
145 | "pm is deprecated; use 'genetic_mutation'",
146 | DeprecationWarning, 2
147 | )
148 | if genetic_mutation is None:
149 | genetic_mutation = kwargs["pm"]
150 | elif "mutation" in kwargs:
151 | warnings.warn(
152 | "mutation is deprecated; use 'genetic_mutation'",
153 | DeprecationWarning, 2
154 | )
155 | if genetic_mutation is None:
156 | genetic_mutation = kwargs["mutation"]
157 |
158 | return kwargs, genetic_mutation
159 |
--------------------------------------------------------------------------------
/pymoode/performance/__init__.py:
--------------------------------------------------------------------------------
1 | from pymoode.performance._spacing import SpacingIndicator
--------------------------------------------------------------------------------
/pymoode/performance/_spacing.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from scipy.spatial.distance import pdist, squareform
3 | from pymoo.core.indicator import Indicator
4 | from pymoo.indicators.distance_indicator import at_least_2d_array, derive_ideal_and_nadir_from_pf
5 |
6 |
7 | # =========================================================================================================
8 | # Implementation
9 | # =========================================================================================================
10 |
11 | class SpacingIndicator(Indicator):
12 |
13 | def __init__(self,
14 | metric="cityblock",
15 | pf=None,
16 | zero_to_one=False,
17 | ideal=None,
18 | nadir=None):
19 | """Spacing indicator
20 | The smaller the value this indicator assumes, the most uniform is the distribution of elements on the pareto front.
21 |
22 | Parameters
23 | ----------
24 | metric : str, optional
25 | Distance metric parsed to scipy.spatial.distance.pdist, by default "cityblock"
26 |
27 | pf : 2d array, optional
28 | Pareto front, by default None
29 |
30 | zero_to_one : bool, optional
31 | Whether or not the objective values should be normalized in calculations, by default False
32 |
33 | ideal : 1d array, optional
34 | Ideal point, by default None
35 |
36 | nadir : 1d array, optional
37 | Nadir point, by default None
38 | """
39 |
40 | # the pareto front if necessary to calculate the indicator
41 | pf = at_least_2d_array(pf, extend_as="row")
42 | ideal, nadir = derive_ideal_and_nadir_from_pf(pf, ideal=ideal, nadir=nadir)
43 |
44 | super().__init__(pf=pf,
45 | zero_to_one=zero_to_one,
46 | ideal=ideal,
47 | nadir=nadir)
48 |
49 | self.metric = metric
50 |
51 | def do(self, F, *args, **kwargs):
52 | """Obtain the spacing indicator given a Pareto front
53 |
54 | Parameters
55 | ----------
56 | F : numpy.array (n_samples, n_obj)
57 | Pareto front
58 |
59 | Returns
60 | -------
61 | float
62 | Spacing indicator
63 | """
64 | return super().do(F, *args, **kwargs)
65 |
66 | def _do(self, F, *args, **kwargs):
67 |
68 | # Get F dimensions
69 | n_points, n_obj = F.shape
70 |
71 | # knn
72 | D = squareform(pdist(F, metric=self.metric))
73 | d = np.partition(D, 1, axis=1)[:, 1]
74 | dm = np.mean(d)
75 |
76 | # Get spacing
77 | S = np.sqrt(np.sum(np.square(d - dm)) / n_points)
78 |
79 | return S
80 |
--------------------------------------------------------------------------------
/pymoode/survival/__init__.py:
--------------------------------------------------------------------------------
1 | from pymoode.survival.rank_and_crowding.rnc import RankAndCrowding, ConstrRankAndCrowding
--------------------------------------------------------------------------------
/pymoode/survival/fitness.py:
--------------------------------------------------------------------------------
1 | # Native
2 | from typing import Optional
3 |
4 | # External
5 | import numpy as np
6 |
7 | # pymoo imports
8 | from pymoo.core.survival import Survival
9 | from pymoo.core.population import Population
10 | from pymoo.core.problem import Problem
11 |
12 |
13 | # =========================================================================================================
14 | # Implementation
15 | # =========================================================================================================
16 |
17 | class BaseFitnessSurvival(Survival):
18 |
19 | def __init__(self) -> None:
20 | super().__init__(filter_infeasible=False)
21 |
22 | def _do(
23 | self,
24 | problem: Problem,
25 | pop: Population,
26 | n_survive: Optional[int]=None,
27 | **kwargs
28 | ):
29 | return pop[:n_survive]
30 |
31 |
32 | class FitnessSurvival(Survival):
33 |
34 | def __init__(self) -> None:
35 | super().__init__(filter_infeasible=False)
36 |
37 | def _do(
38 | self,
39 | problem: Problem,
40 | pop: Population,
41 | n_survive: Optional[int]=None,
42 | **kwargs
43 | ):
44 | F, cv = pop.get("F", "cv")
45 | assert F.shape[1] == 1, "FitnessSurvival can only used for single objective single!"
46 | S = np.lexsort([F[:, 0], cv])
47 | pop.set("rank", np.argsort(S))
48 | return pop[S[:n_survive]]
49 |
--------------------------------------------------------------------------------
/pymoode/survival/rank_and_crowding/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooscaliaproject/pymoode/521d591263ceac29d5236b3bc62a482049a18f86/pymoode/survival/rank_and_crowding/__init__.py
--------------------------------------------------------------------------------
/pymoode/survival/rank_and_crowding/metrics.py:
--------------------------------------------------------------------------------
1 | # External
2 | import numpy as np
3 | from scipy.spatial.distance import pdist, squareform
4 |
5 | # pymoo imports
6 | from pymoo.util.misc import find_duplicates
7 |
8 |
9 | # =========================================================================================================
10 | # Checks compilation
11 | # =========================================================================================================
12 |
13 | try:
14 | from pymoode.cython.info import info
15 | if info() == "This file was based on pymoo to check compilation success":
16 | IS_COMPILED = True
17 | else:
18 | IS_COMPILED = False
19 | except Exception:
20 | IS_COMPILED = False
21 |
22 |
23 | # =========================================================================================================
24 | # pymoode imports and definitions
25 | # =========================================================================================================
26 |
27 |
28 | if IS_COMPILED:
29 |
30 | from pymoode.cython.mnn import calc_mnn, calc_2nn
31 | from pymoode.cython.pruning_cd import calc_pcd
32 |
33 | def calc_mnn_nds(F, n_remove: int = 0):
34 | return calc_mnn(F, n_remove=n_remove)
35 |
36 | def calc_2nn_nds(F, n_remove: int = 0):
37 | return calc_2nn(F, n_remove=n_remove)
38 |
39 | def calc_pcd_nds(F, n_remove: int = 0):
40 | return calc_pcd(F, n_remove=n_remove)
41 |
42 | else:
43 |
44 | print("WARNING: Failed to import cython modules. Slow implementation on.")
45 | from pymoode.misc.mnn import calc_mnn
46 | from pymoode.misc.pruning_cd import calc_pcd
47 |
48 | def calc_mnn_nds(F, n_remove: int = 0):
49 | return calc_mnn(F, n_remove=n_remove, twonn=False)
50 |
51 | def calc_2nn_nds(F, n_remove: int = 0):
52 | return calc_mnn(F, n_remove=n_remove, twonn=True)
53 |
54 | def calc_pcd_nds(F, n_remove: int = 0):
55 | return calc_pcd(F, n_remove=n_remove)
56 |
57 |
58 | def get_crowding_function(label):
59 |
60 | if label == "cd":
61 | fun = FunctionalDiversity(calc_crowding_distance, filter_out_duplicates=False)
62 | elif (label == "pcd") or (label == "pruning-cd"):
63 | fun = FunctionalDiversity(calc_pcd_nds, filter_out_duplicates=True)
64 | elif label == "ce":
65 | fun = FunctionalDiversity(calc_crowding_entropy, filter_out_duplicates=True)
66 | elif label == "mnn":
67 | fun = FuncionalDiversityMNN(calc_mnn_nds, filter_out_duplicates=True)
68 | elif label == "2nn":
69 | fun = FuncionalDiversityMNN(calc_2nn_nds, filter_out_duplicates=True)
70 | elif hasattr(label, "__call__"):
71 | fun = FunctionalDiversity(label, filter_out_duplicates=True)
72 | elif isinstance(label, CrowdingDiversity):
73 | fun = label
74 | else:
75 | raise KeyError("Crwoding function not defined")
76 | return fun
77 |
78 |
79 | # =========================================================================================================
80 | # Implementation
81 | # =========================================================================================================
82 |
83 |
84 | class CrowdingDiversity:
85 |
86 | def __init__(self, *args, **kwargs) -> None:
87 | """Instantiates an operator that calculates crowding metrics given an array of objectives F
88 | """
89 | pass
90 |
91 | def do(self, F: np.ndarray, n_remove: int = 0, **kwargs) -> np.ndarray:
92 | """Computes diversity metric for a given objective space
93 |
94 | Parameters
95 | ----------
96 | F : 2d array like
97 | Objective space (each row represents an individual and each column an objective)
98 |
99 | n_remove : int, optional
100 | Number of individuals to be removed (in case of recursive elimination), by default 0
101 |
102 | Returns
103 | -------
104 | np.ndarray
105 | Diversity metric (1d)
106 | """
107 | # Converting types Python int to Cython int would fail in some cases converting to long instead
108 | n_remove = np.intc(n_remove)
109 | F = np.array(F, dtype=np.double)
110 | return self._do(F, n_remove=n_remove, **kwargs)
111 |
112 | def _do(self, F, n_remove=None, **kwargs):
113 | pass
114 |
115 |
116 | class FunctionalDiversity(CrowdingDiversity):
117 |
118 | def __init__(self, function=None, filter_out_duplicates=True):
119 | """Operator to evaluate crowding metrics with useful strategies of filtering out duplicates
120 |
121 | Parameters
122 | ----------
123 | function : Callable, optional
124 | Function in the format ``func(F, n_remove=None, **kwargs)``
125 | that should return crowding metrics. By default None
126 |
127 | filter_out_duplicates : bool, optional
128 | Either of not to filter out duplicates before calculating crowding metrics, by default True
129 | """
130 | self.function = function
131 | self.filter_out_duplicates = filter_out_duplicates
132 | super().__init__()
133 |
134 | def _do(self, F, **kwargs):
135 |
136 | n_points, _ = F.shape
137 |
138 | if n_points <= 2:
139 | return np.full(n_points, np.inf)
140 |
141 | else:
142 |
143 | if self.filter_out_duplicates:
144 | # filter out solutions which are duplicates - duplicates get a zero finally
145 | is_unique = np.where(np.logical_not(find_duplicates(F, epsilon=1e-32)))[0]
146 | else:
147 | # set every point to be unique without checking it
148 | is_unique = np.arange(n_points)
149 |
150 | # index the unique points of the array
151 | _F = F[is_unique]
152 |
153 | _d = self.function(_F, **kwargs)
154 |
155 | d = np.zeros(n_points)
156 | d[is_unique] = _d
157 |
158 | return d
159 |
160 |
161 | class FuncionalDiversityMNN(FunctionalDiversity):
162 |
163 | def _do(self, F, **kwargs):
164 |
165 | n_points, n_obj = F.shape
166 |
167 | if n_points <= n_obj:
168 | return np.full(n_points, np.inf)
169 |
170 | else:
171 | return super()._do(F, **kwargs)
172 |
173 |
174 | def calc_crowding_entropy(F, **kwargs) -> np.ndarray:
175 | """Wang, Y.-N., Wu, L.-H. & Yuan, X.-F., 2010. Multi-objective self-adaptive differential
176 | evolution with elitist archive and crowding entropy-based diversity measure.
177 | Soft Comput., 14(3), pp. 193-209.
178 |
179 | Parameters
180 | ----------
181 | F : 2d array like
182 | Objective space (each row represents an individual and each column an objective)
183 |
184 | Returns
185 | -------
186 | crowding_enropies : 1d array
187 | """
188 |
189 | n_points, n_obj = F.shape
190 |
191 | # sort each column and get index
192 | I = np.argsort(F, axis=0, kind='mergesort')
193 |
194 | # sort the objective space values for the whole matrix
195 | F = F[I, np.arange(n_obj)]
196 |
197 | # calculate the distance from each point to the last and next
198 | dist = np.row_stack([F, np.full(n_obj, np.inf)]) - np.row_stack([np.full(n_obj, -np.inf), F])
199 |
200 | # calculate the norm for each objective - set to NaN if all values are equal
201 | norm = np.max(F, axis=0) - np.min(F, axis=0)
202 | norm[norm == 0] = np.nan
203 |
204 | # prepare the distance to last and next vectors
205 | dl = dist.copy()[:-1]
206 | du = dist.copy()[1:]
207 |
208 | # Fix nan
209 | dl[np.isnan(dl)] = 0.0
210 | du[np.isnan(du)] = 0.0
211 |
212 | # Total distance
213 | cd = dl + du
214 |
215 | # Get relative positions
216 | pl = (dl[1:-1] / cd[1:-1])
217 | pu = (du[1:-1] / cd[1:-1])
218 |
219 | # Entropy
220 | entropy = np.row_stack([np.full(n_obj, np.inf),
221 | -(pl * np.log2(pl) + pu * np.log2(pu)),
222 | np.full(n_obj, np.inf)])
223 |
224 | # Crowding entropy
225 | J = np.argsort(I, axis=0)
226 | _cej = cd[J, np.arange(n_obj)] * entropy[J, np.arange(n_obj)] / norm
227 | _cej[np.isnan(_cej)] = 0.0
228 | ce = _cej.sum(axis=1)
229 |
230 | return ce
231 |
232 |
233 | def calc_crowding_distance(F: np.ndarray, **kwargs) -> np.ndarray:
234 | """Pymoo's native function to compute crowding distances
235 |
236 | Parameters
237 | ----------
238 | F : 2d array like
239 | Objective space (each row represents an individual and each column an objective)
240 |
241 | Returns
242 | -------
243 | crwoding_distances : 1d array
244 | """
245 | _, n_obj = F.shape
246 |
247 | # sort each column and get index
248 | I = np.argsort(F, axis=0, kind='mergesort')
249 |
250 | # sort the objective space values for the whole matrix
251 | F = F[I, np.arange(n_obj)]
252 |
253 | # calculate the distance from each point to the last and next
254 | dist = np.row_stack([F, np.full(n_obj, np.inf)]) - np.row_stack([np.full(n_obj, -np.inf), F])
255 |
256 | # calculate the norm for each objective - set to NaN if all values are equal
257 | norm = np.max(F, axis=0) - np.min(F, axis=0)
258 | norm[norm == 0] = np.nan
259 |
260 | # prepare the distance to last and next vectors
261 | dist_to_last, dist_to_next = dist, np.copy(dist)
262 | dist_to_last, dist_to_next = dist_to_last[:-1] / norm, dist_to_next[1:] / norm
263 |
264 | # if we divide by zero because all values in one columns are equal replace by none
265 | dist_to_last[np.isnan(dist_to_last)] = 0.0
266 | dist_to_next[np.isnan(dist_to_next)] = 0.0
267 |
268 | # sum up the distance to next and last and norm by objectives - also reorder from sorted list
269 | J = np.argsort(I, axis=0)
270 | cd = np.sum(dist_to_last[J, np.arange(n_obj)] + dist_to_next[J, np.arange(n_obj)], axis=1) / n_obj
271 |
272 | return cd
273 |
274 |
275 | def calc_mnn_fast(F, **kwargs):
276 | return _calc_mnn_fast(F, F.shape[1], **kwargs)
277 |
278 |
279 | def calc_2nn_fast(F, **kwargs):
280 | return _calc_mnn_fast(F, 2, **kwargs)
281 |
282 |
283 | def _calc_mnn_fast(F, n_neighbors, **kwargs):
284 |
285 | # calculate the norm for each objective - set to NaN if all values are equal
286 | norm = np.max(F, axis=0) - np.min(F, axis=0)
287 | norm[norm == 0] = 1.0
288 |
289 | # F normalized
290 | F = (F - F.min(axis=0)) / norm
291 |
292 | # Distances pairwise (Inefficient)
293 | D = squareform(pdist(F, metric="sqeuclidean"))
294 |
295 | # M neighbors
296 | M = F.shape[1]
297 | _D = np.partition(D, range(1, M+1), axis=1)[:, 1:M+1]
298 |
299 | # Metric d
300 | d = np.prod(_D, axis=1)
301 |
302 | # Set top performers as np.inf
303 | _extremes = np.concatenate((np.argmin(F, axis=0), np.argmax(F, axis=0)))
304 | d[_extremes] = np.inf
305 |
306 | return d
307 |
--------------------------------------------------------------------------------
/pymoode/survival/rank_and_crowding/rnc.py:
--------------------------------------------------------------------------------
1 | # Native
2 | import warnings
3 | from typing import Optional
4 |
5 | # External
6 | import numpy as np
7 |
8 | # pymoo imports
9 | from pymoo.util.randomized_argsort import randomized_argsort
10 | from pymoo.util.nds.non_dominated_sorting import NonDominatedSorting
11 | from pymoo.core.survival import Survival, split_by_feasibility
12 | from pymoo.core.population import Population
13 | from pymoo.core.problem import Problem
14 |
15 | # pymoode imports
16 | from pymoode.survival.rank_and_crowding.metrics import get_crowding_function
17 |
18 |
19 | # =========================================================================================================
20 | # Implementation
21 | # =========================================================================================================
22 |
23 | class RankAndCrowding(Survival):
24 |
25 | def __init__(
26 | self,
27 | nds=None,
28 | crowding_func="cd",
29 | rule=None
30 | ):
31 | """
32 | A generalization of the NSGA-II survival operator that ranks individuals by dominance criteria
33 | and sorts the last front by some crowding metric.
34 |
35 | For many-objective problems, try using 'mnn' or '2nn'.
36 |
37 | For Bi-objective problems, 'pcd' is very effective.
38 |
39 | Parameters
40 | ----------
41 | nds : str or None, optional
42 | Pymoo type of non-dominated sorting. Defaults to None.
43 |
44 | crowding_func : str or callable, optional
45 | Crowding metric. Options are:
46 |
47 | - 'cd': crowding distances
48 | - 'pcd' or 'pruning-cd': improved pruning based on crowding distances
49 | - 'ce': crowding entropy
50 | - 'mnn': M-Neaest Neighbors
51 | - '2nn': 2-Neaest Neighbors
52 |
53 | If callable, it has the form ``fun(F, n_remove=None, **kwargs)``
54 | in which F (n, m) and must return metrics in a (n,) array.
55 |
56 | The options 'pcd', 'cd', and 'ce' are recommended for two-objective problems, whereas 'mnn' and '2nn' for many objective.
57 | When using 'pcd', 'mnn', or '2nn', individuals are already eliminated in a 'single' manner.
58 | Due to Cython implementation, they are as fast as the corresponding 'cd', 'mnn-fast', or '2nn-fast',
59 | although they can singnificantly improve diversity of solutions.
60 | Defaults to 'cd'.
61 | """
62 | if rule is not None:
63 | warnings.warn(
64 | "rule is deprecated; elimination methods are now included in crowding_func",
65 | DeprecationWarning, 2
66 | )
67 |
68 | crowding_func_ = get_crowding_function(crowding_func)
69 |
70 | super().__init__(filter_infeasible=True)
71 | self.nds = nds if nds is not None else NonDominatedSorting()
72 | self.crowding_func = crowding_func_
73 |
74 | def _do(
75 | self,
76 | problem: Problem,
77 | pop: Population,
78 | *args,
79 | n_survive: Optional[int] = None,
80 | **kwargs
81 | ):
82 |
83 | # get the objective space values and objects
84 | F = pop.get("F").astype(float, copy=False)
85 |
86 | # the final indices of surviving individuals
87 | survivors = []
88 |
89 | # do the non-dominated sorting until splitting front
90 | fronts = self.nds.do(F, n_stop_if_ranked=n_survive)
91 |
92 | for k, front in enumerate(fronts):
93 |
94 | I = np.arange(len(front))
95 |
96 | # current front sorted by crowding distance if splitting
97 | if len(survivors) + len(I) > n_survive:
98 |
99 | # Define how many will be removed
100 | n_remove = len(survivors) + len(front) - n_survive
101 |
102 | # re-calculate the crowding distance of the front
103 | crowding_of_front = \
104 | self.crowding_func.do(
105 | F[front, :],
106 | n_remove=n_remove
107 | )
108 |
109 | I = randomized_argsort(crowding_of_front, order='descending', method='numpy')
110 | I = I[:-n_remove]
111 |
112 | # otherwise take the whole front unsorted
113 | else:
114 | # calculate the crowding distance of the front
115 | crowding_of_front = \
116 | self.crowding_func.do(
117 | F[front, :],
118 | n_remove=0
119 | )
120 |
121 | # save rank and crowding in the individual class
122 | for j, i in enumerate(front):
123 | pop[i].set("rank", k)
124 | pop[i].set("crowding", crowding_of_front[j])
125 |
126 | # extend the survivors by all or selected individuals
127 | survivors.extend(front[I])
128 |
129 | return pop[survivors]
130 |
131 |
132 | class ConstrRankAndCrowding(Survival):
133 |
134 | def __init__(self, nds=None, crowding_func="cd", ranking=None):
135 | """
136 | The Rank and Crowding survival approach for handling constraints proposed on
137 | GDE3 by Kukkonen, S. & Lampinen, J. (2005).
138 |
139 | Parameters
140 | ----------
141 | nds : str or None, optional
142 | Pymoo type of non-dominated sorting. Defaults to None.
143 |
144 | crowding_func : str or callable, optional
145 | Crowding metric. Options are:
146 |
147 | - 'cd': crowding distances
148 | - 'pcd' or 'pruning-cd': improved pruning based on crowding distances
149 | - 'ce': crowding entropy
150 | - 'mnn': M-Neaest Neighbors
151 | - '2nn': 2-Neaest Neighbors
152 |
153 | If callable, it has the form ``fun(F, n_remove=None, **kwargs)``
154 | in which F (n, m) and must return metrics in a (n,) array.
155 |
156 | The options 'pcd', 'cd', and 'ce' are recommended for two-objective problems, whereas 'mnn' and '2nn' for many objective.
157 | When using 'pcd', 'mnn', or '2nn', individuals are already eliminated in a 'single' manner.
158 | Due to Cython implementation, they are as fast as the corresponding 'cd', 'mnn-fast', or '2nn-fast',
159 | although they can singnificantly improve diversity of solutions.
160 | Defaults to 'cd'.
161 | """
162 | if ranking is not None:
163 | warnings.warn(
164 | "'ranking' is deprecated; internal survival operators are now constructed from nds and crowding_func",
165 | DeprecationWarning, 2
166 | )
167 |
168 | super().__init__(filter_infeasible=False)
169 | self.nds = nds if nds is not None else NonDominatedSorting()
170 | self.ranking = RankAndCrowding(nds=nds, crowding_func=crowding_func)
171 |
172 | def _do(
173 | self,
174 | problem: Problem,
175 | pop: Population,
176 | *args,
177 | n_survive: Optional[int] = None,
178 | **kwargs
179 | ):
180 |
181 | if n_survive is None:
182 | n_survive = len(pop)
183 |
184 | n_survive = min(n_survive, len(pop))
185 |
186 | # If the split should be done beforehand
187 | if problem.n_constr > 0:
188 |
189 | # Split by feasibility
190 | feas, infeas = split_by_feasibility(pop, sort_infeas_by_cv=True, sort_feas_by_obj=False, return_pop=False)
191 |
192 | # Obtain len of feasible
193 | n_feas = len(feas)
194 |
195 | # Assure there is at least_one survivor
196 | if n_feas == 0:
197 | survivors = Population()
198 | else:
199 | survivors = self.ranking.do(problem, pop[feas], *args, n_survive=min(len(feas), n_survive), **kwargs)
200 |
201 | # Calculate how many individuals are still remaining to be filled up with infeasible ones
202 | n_remaining = n_survive - len(survivors)
203 |
204 | # If infeasible solutions need to be added
205 | if n_remaining > 0:
206 |
207 | # Constraints to new ranking
208 | G = pop[infeas].get("G")
209 | G = np.maximum(G, 0)
210 | H = pop[infeas].get("H")
211 | H = np.absolute(H)
212 | C = np.column_stack((G, H))
213 |
214 | # Fronts in infeasible population
215 | infeas_fronts = self.nds.do(C, n_stop_if_ranked=n_remaining)
216 |
217 | # Iterate over fronts
218 | for k, front in enumerate(infeas_fronts):
219 |
220 | # Save ranks
221 | pop[infeas][front].set("cv_rank", k)
222 |
223 | # Current front sorted by CV
224 | if len(survivors) + len(front) > n_survive:
225 |
226 | # Obtain CV of front
227 | CV = pop[infeas][front].get("CV").flatten()
228 | I = randomized_argsort(CV, order='ascending', method='numpy')
229 | I = I[:(n_survive - len(survivors))]
230 |
231 | # Otherwise take the whole front unsorted
232 | else:
233 | I = np.arange(len(front))
234 |
235 | # extend the survivors by all or selected individuals
236 | survivors = Population.merge(survivors, pop[infeas][front[I]])
237 |
238 | else:
239 | survivors = self.ranking.do(problem, pop, *args, n_survive=n_survive, **kwargs)
240 |
241 | return survivors
242 |
--------------------------------------------------------------------------------
/pymoode/survival/replacement.py:
--------------------------------------------------------------------------------
1 | # Native
2 | from typing import Optional
3 |
4 | # External
5 | import numpy as np
6 |
7 | # pymoo imports
8 | from pymoo.core.duplicate import DefaultDuplicateElimination
9 | from pymoo.core.individual import Individual
10 | from pymoo.core.population import Population
11 | from pymoo.core.problem import Problem
12 | from pymoo.core.survival import Survival
13 | from pymoo.core.individual import Individual
14 |
15 | # pymoode imports
16 | from pymoode.survival.fitness import FitnessSurvival
17 |
18 |
19 | # =========================================================================================================
20 | # Implementation
21 | # =========================================================================================================
22 |
23 | def is_better(_new: Individual, _old: Individual, eps=0.0):
24 | both_infeasible = not _old.feasible[0] and not _new.feasible[0]
25 | both_feasible = _old.feasible[0] and _new.feasible[0]
26 |
27 | if both_infeasible and _old.CV[0] - _new.CV[0] > eps:
28 | return True
29 | elif not _old.feasible and _new.feasible:
30 | return True
31 | elif both_feasible and _old.F[0] - _new.F[0] > eps:
32 | return True
33 |
34 | return False
35 |
36 |
37 | class BaseReplacement(Survival):
38 |
39 | def __init__(
40 | self,
41 | fitness: Optional[Survival] = None
42 | ):
43 | """Base class for Replacement survival
44 |
45 | Parameters
46 | ----------
47 | fitness : Survival, optional
48 | Survival used in ranking and fitness assignement, by default None
49 | """
50 | super().__init__(filter_infeasible=False)
51 | if fitness is None:
52 | fitness = FitnessSurvival()
53 | self.fitness = fitness
54 |
55 | def do(
56 | self,
57 | problem: Problem,
58 | pop: Population,
59 | off: Population,
60 | return_indices=False,
61 | inplace=False,
62 | **kwargs
63 | ):
64 | """Given a problem a parent population and offspring canditates return next generation.
65 | By default, only fitness assigment is performed on parents. To define which ones should be replaced,
66 | define method ``_do`` in child classes that returns a boolean if parent should be replaced.
67 |
68 | Parameters
69 | ----------
70 | problem : Problem
71 | Pymoo problem
72 |
73 | pop : Population
74 | Parent population
75 |
76 | off : Population | None
77 | Offspring candidates. If None, only fitness assigment is performed on parents.
78 |
79 | return_indices : bool, optional
80 | Either or not to just return boolean of positions to be replaced, by default False
81 |
82 | inplace : bool, optional
83 | Change population inplace, by default False
84 |
85 | Returns
86 | -------
87 | Population
88 | Population that proceeds into the next generation
89 | """
90 |
91 | # If no offspring is available just do fitness assignment
92 | if (off is None) or len(off) == 0:
93 | return self.fitness.do(problem, pop, n_survive=None)
94 |
95 | # this makes it usable as a traditional survival
96 | elif isinstance(off, int) or isinstance(off, np.integer):
97 | k = off
98 | off = pop[k:]
99 | pop = pop[:k]
100 |
101 | assert len(pop) == len(off), "For the replacement pop and off must have the same number of individuals."
102 |
103 | pop = Population.create(pop) if isinstance(pop, Individual) else pop
104 | off = Population.create(off) if isinstance(off, Individual) else off
105 |
106 | I = self._do(problem, pop, off, **kwargs)
107 |
108 | if return_indices:
109 | return I
110 | else:
111 | if not inplace:
112 | pop = pop.copy()
113 | pop[I] = off[I]
114 | return self.fitness.do(problem, pop, n_survive=None)
115 |
116 | def _do(
117 | self,
118 | problem: Problem,
119 | pop: Population,
120 | off: Population,
121 | **kwargs
122 | ):
123 | return np.zeros(len(pop), dtype=np.bool_)
124 |
125 |
126 | class ImprovementReplacement(BaseReplacement):
127 |
128 | def do(
129 | self,
130 | problem: Problem,
131 | pop: Population,
132 | off: Population,
133 | return_indices=False,
134 | inplace=False,
135 | **kwargs
136 | ):
137 | """Given a problem a parent population and offspring canditates return next generation,
138 | it selects those that proceed into the next generation via one-to-one comparison.
139 | Feasible solutions are always preferred to infeasible and infeasible solutions are compared by
140 | overall constraint violation.
141 |
142 | Parameters
143 | ----------
144 | problem : Problem
145 | Pymoo problem
146 |
147 | pop : Population
148 | Parent population
149 |
150 | off : Population | None
151 | Offspring candidates. If None, only fitness assigment is performed on parents.
152 |
153 | return_indices : bool, optional
154 | Either or not to just return boolean of positions to be replaced, by default False
155 |
156 | inplace : bool, optional
157 | Change population inplace, by default False
158 |
159 | Returns
160 | -------
161 | Population
162 | Population that proceeds into the next generation
163 | """
164 | return super().do(problem, pop, off, return_indices=return_indices, inplace=inplace, **kwargs)
165 |
166 | def _do(
167 | self,
168 | problem: Problem,
169 | pop: Population,
170 | off: Population,
171 | **kwargs
172 | ):
173 |
174 | ret = np.full((len(pop), 1), False)
175 |
176 | pop_F, pop_CV, pop_feas = pop.get("F", "CV", "feasible")
177 | off_F, off_CV, off_feas = off.get("F", "CV", "feasible")
178 |
179 | if problem.has_constraints() > 0:
180 |
181 | # 1) Both infeasible and constraints have been improved
182 | ret[(~pop_feas & ~off_feas) & (off_CV < pop_CV)] = True
183 |
184 | # 2) A solution became feasible
185 | ret[~pop_feas & off_feas] = True
186 |
187 | # 3) Both feasible but objective space value has improved
188 | ret[(pop_feas & off_feas) & (off_F < pop_F)] = True
189 |
190 | else:
191 | ret[off_F < pop_F] = True
192 |
193 | # never allow duplicates to become part of the population when replacement is used
194 | _, _, is_duplicate = DefaultDuplicateElimination(epsilon=0.0).do(off, pop, return_indices=True)
195 | ret[is_duplicate] = False
196 |
197 | return ret[:, 0]
198 |
199 |
200 | def parameter_less(f, cv):
201 | v = np.copy(f)
202 | infeas = cv > 0
203 | v[infeas] = f.max() + cv[infeas]
204 | return v
205 |
206 |
207 | def hierarchical_sort(f, cv=None):
208 | if cv is not None:
209 | f = parameter_less(f, cv)
210 | return np.argsort(f)
211 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools", "wheel", "numpy>=1.19.0", "Cython>=0.29.21"]
3 | build-backend = "setuptools.build_meta"
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | markers =
3 | long: tests with a longer running time.
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | numpy>=1.19.0
2 | pymoo==0.6.1
3 | scipy>=1.7.0
4 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | from distutils.errors import CompileError
3 | import os
4 | import sys
5 | from setuptools import setup, Extension, find_packages
6 | from setuptools.command.build_ext import build_ext
7 |
8 |
9 | # ---------------------------------------------------------------------------------------------------------
10 | # BASE CONFIGURATION
11 | # ---------------------------------------------------------------------------------------------------------
12 |
13 | with open("README.md", "r", encoding="utf-8") as fh:
14 | long_description = fh.read()
15 |
16 | BASE_PACKAGE = 'pymoode'
17 |
18 | base_kwargs = dict(
19 | name = 'pymoode',
20 | packages = [BASE_PACKAGE] + [f"{BASE_PACKAGE}." + e for e in find_packages(where=BASE_PACKAGE)],
21 | version = '0.3.0',
22 | license='Apache License 2.0',
23 | description = 'A Python optimization package using Differential Evolution.',
24 | long_description=long_description,
25 | long_description_content_type="text/markdown",
26 | author = 'Bruno Scalia C. F. Leite',
27 | author_email = 'mooscaliaproject@gmail.com',
28 | url = 'https://github.com/mooscaliaproject/pymoode',
29 | download_url = 'https://github.com/mooscaliaproject/pymoode',
30 | keywords = [
31 | 'Differential Evolution',
32 | 'Multi-objective optimization',
33 | 'GDE3',
34 | 'NSDE',
35 | 'NSDE-R',
36 | 'NSGA-II',
37 | 'Genetic Algorithm',
38 | 'Crowding Distances',
39 | 'Evolutionary Algorithms',
40 | 'Evolutionary Optimization',
41 | 'Metaheuristics',
42 | ],
43 | install_requires=[
44 | 'numpy>=1.19.0',
45 | 'pymoo==0.6.1',
46 | 'scipy>=1.7.0',
47 | 'future',
48 | ],
49 | )
50 |
51 | # ---------------------------------------------------------------------------------------------------------
52 | # ARGPARSER
53 | # ---------------------------------------------------------------------------------------------------------
54 |
55 | parser = argparse.ArgumentParser()
56 |
57 | parser.add_argument('--nopyx', dest='nopyx', action='store_true', help='Whether the pyx files shall be considered at all.')
58 | parser.add_argument('--nocython', dest='nocython', action='store_true', help='Whether pyx files shall be cythonized.')
59 | parser.add_argument('--nolibs', dest='nolibs', action='store_true', help='Whether the libraries should be compiled.')
60 | parser.add_argument('--markcython', dest='markcython', action='store_true', help='Whether to mark the html cython files.')
61 |
62 | params, _ = parser.parse_known_args()
63 | sys.argv = [arg for arg in sys.argv if not arg.lstrip("-") in params]
64 |
65 | # ---------------------------------------------------------------------------------------------------------
66 | # ADD MARKED HTML FILES FOR CYTHON
67 | # ---------------------------------------------------------------------------------------------------------
68 |
69 | if params.markcython:
70 | import Cython.Compiler.Options
71 | Cython.Compiler.Options.annotate = True
72 |
73 | # ---------------------------------------------------------------------------------------------------------
74 | # CLASS TO BUILD EXTENSIONS
75 | # ---------------------------------------------------------------------------------------------------------
76 |
77 | # exception that is thrown when the build fails
78 | class CompilingFailed(Exception):
79 | pass
80 |
81 |
82 | # try to compile, if not possible throw exception
83 | def construct_build_ext(build_ext):
84 | class WrappedBuildExt(build_ext):
85 | def run(self):
86 | try:
87 | build_ext.run(self)
88 | except BaseException as e:
89 | raise CompilingFailed(e)
90 |
91 | def build_extension(self, ext):
92 | try:
93 | build_ext.build_extension(self, ext)
94 | except BaseException as e:
95 | raise CompilingFailed(e)
96 |
97 | return WrappedBuildExt
98 |
99 | # ---------------------------------------------------------------------------------------------------------
100 | # HANDLING CYTHON FILES
101 | # ---------------------------------------------------------------------------------------------------------
102 |
103 | ROOT = os.path.dirname(os.path.realpath(__file__))
104 |
105 | if params.nopyx:
106 | ext_modules = []
107 |
108 | else:
109 | try:
110 | if params.nocython:
111 | path = os.path.join(ROOT, "pymoode", "cython")
112 | pyx = [os.path.join(path, f) for f in os.listdir() if f.endswith(".pyx")]
113 | ext_modules = [Extension(f"pymoode.cython.{source[:-4]}", [source]) for source in pyx]
114 | else:
115 | try:
116 | from Cython.Build import cythonize
117 | ext_modules = cythonize("pymoode/cython/*.pyx")
118 | except ImportError:
119 | print('*' * 75)
120 | print("No Cython package found to convert .pyx files.")
121 | print("If no compilation occurs, .py files will be used instead, which provide the same results but with worse computational time.")
122 | print('*' * 75)
123 | ext_modules = []
124 | except:
125 | print('*' * 75)
126 | print("Problems compiling .pyx files.")
127 | print("If no compilation occurs, .py files will be used instead, which provide the same results but with worse computational time.")
128 | print('*' * 75)
129 | ext_modules = []
130 |
131 | if not params.nolibs:
132 |
133 | if len(ext_modules) > 0:
134 | base_kwargs['ext_modules'] = ext_modules
135 |
136 | try:
137 | import numpy as np
138 | base_kwargs['include_dirs'] = [np.get_include()]
139 |
140 | except BaseException:
141 | raise CompileError(
142 | "NumPy libraries must be installed for compiled extensions! Speedups are not enabled."
143 | )
144 |
145 | base_kwargs['cmdclass'] = dict(build_ext=construct_build_ext(build_ext))
146 |
147 | else:
148 | print('*' * 75)
149 | print("External cython modules found.")
150 | print("To verify compilation success run:")
151 | print("from pymoode.survival.rank_and_crowding.metrics import IS_COMPILED")
152 | print("This variable will be True to mark compilation success;")
153 | print("If no compilation occurs, .py files will be used instead, which provide the same results but with worse computational time.")
154 | print('*' * 75)
155 |
156 | # ---------------------------------------------------------------------------------------------------------
157 | # RUN SETUP
158 | # ---------------------------------------------------------------------------------------------------------
159 |
160 | compiled_kwargs = base_kwargs.copy()
161 | compiled_kwargs["ext_modules"] = ext_modules
162 |
163 | try:
164 | setup(**compiled_kwargs)
165 | print('*' * 75)
166 | print("Installation successful at the first attempt.")
167 | print("To verify compilation success run:")
168 | print("from pymoode.survival.rank_and_crowding.metrics import IS_COMPILED")
169 | print('*' * 75)
170 | except:
171 | print('*' * 75)
172 | print("Running setup with cython compilation failed.")
173 | print("Attempt to a pure Python setup.")
174 | print("If no compilation occurs, .py files will be used instead, which provide the same results but with worse computational time.")
175 | print('*' * 75)
176 | setup(**base_kwargs)
177 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mooscaliaproject/pymoode/521d591263ceac29d5236b3bc62a482049a18f86/tests/__init__.py
--------------------------------------------------------------------------------
/tests/requirements.txt:
--------------------------------------------------------------------------------
1 | numpy==1.26.*
2 | pymoo==0.6.1
3 | scipy==1.13.*
4 | future
5 | pytest
--------------------------------------------------------------------------------
/tests/test_many.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from pymoo.optimize import minimize
4 | from pymoo.problems import get_problem
5 | from pymoo.indicators.igd import IGD
6 | from pymoode.algorithms import NSDER
7 | from pymoode.algorithms import GDE3
8 | from pymoode.survival import RankAndCrowding
9 | from pymoo.util.ref_dirs import get_reference_directions
10 |
11 | import numpy as np
12 |
13 | @pytest.mark.parametrize('selection', ["rand", "current-to-rand", "ranked"])
14 | @pytest.mark.parametrize('crossover', ["bin", "exp"])
15 | @pytest.mark.parametrize('crowding_func', ["mnn", "2nn"])
16 | def test_many_run(selection, crossover, crowding_func):
17 |
18 | problem = get_problem("dtlz2")
19 |
20 | NGEN = 150
21 | POPSIZE = 136
22 | SEED = 5
23 |
24 | gde3 = GDE3(pop_size=POPSIZE, variant=f"DE/{selection}/1/{crossover}", CR=0.2, F=(0.0, 1.0), gamma=1e-4,
25 | survival=RankAndCrowding(crowding_func=crowding_func))
26 |
27 | res_gde3 = minimize(problem,
28 | gde3,
29 | ('n_gen', NGEN),
30 | seed=SEED,
31 | save_history=False,
32 | verbose=False)
33 |
34 | assert len(res_gde3.opt) > 0
35 |
36 |
37 | def test_many_perf():
38 |
39 | np.random.seed(3)
40 | assert abs(np.random.rand() - 0.5507979025745755) <= 1e-8
41 |
42 | problem = get_problem("dtlz2")
43 | ref_dirs = get_reference_directions("das-dennis", 3, n_partitions=15)
44 | igd = IGD(pf=problem.pareto_front(), zero_to_one=True)
45 |
46 | NGEN = 150
47 | POPSIZE = 136
48 | SEED = 5
49 |
50 | gde3 = GDE3(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=0.2, F=(0.0, 1.0), gamma=1e-4,
51 | survival=RankAndCrowding(crowding_func="mnn"))
52 |
53 | res_gde3 = minimize(problem,
54 | gde3,
55 | ('n_gen', NGEN),
56 | seed=SEED,
57 | save_history=False,
58 | verbose=False)
59 |
60 | igd_gde3 = igd.do(res_gde3.F)
61 | assert abs(igd_gde3 - 0.04011488503871424) <= 1e-8
62 |
63 | nsder = NSDER(ref_dirs=ref_dirs, pop_size=POPSIZE, variant="DE/rand/1/bin", CR=0.5, F=(0.0, 1.0), gamma=1e-4)
64 |
65 | res_nsder = minimize(problem,
66 | nsder,
67 | ('n_gen', NGEN),
68 | seed=SEED,
69 | save_history=False,
70 | verbose=False)
71 |
72 | igd_nsder = igd.do(res_nsder.F)
73 | assert abs(igd_nsder - 0.004877000918527632) <= 1e-8
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/tests/test_multi.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from pymoo.optimize import minimize
4 | from pymoo.problems import get_problem
5 | from pymoo.indicators.igd import IGD
6 | from pymoode.algorithms import GDE3, NSDE
7 | from pymoode.operators.dex import DEX
8 | from pymoode.operators.dem import DEM
9 | from pymoo.algorithms.moo.nsga2 import NSGA2
10 | from pymoo.operators.mutation.pm import PM
11 | from pymoode.survival import RankAndCrowding, ConstrRankAndCrowding
12 |
13 |
14 | @pytest.mark.parametrize('survival', [RankAndCrowding, ConstrRankAndCrowding])
15 | @pytest.mark.parametrize('crowding_func', ["mnn", "2nn", "cd", "pcd", "ce"])
16 | def test_multi_run(survival, crowding_func):
17 |
18 | problem = get_problem("truss2d")
19 |
20 | NGEN = 50
21 | POPSIZE = 50
22 | SEED = 5
23 |
24 | gde3 = GDE3(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=0.5, F=(0.0, 0.9), de_repair="bounce-back",
25 | survival=survival(crowding_func=crowding_func))
26 |
27 | res_gde3 = minimize(problem,
28 | gde3,
29 | ('n_gen', NGEN),
30 | seed=SEED,
31 | save_history=False,
32 | verbose=False)
33 |
34 | assert len(res_gde3.opt) > 0
35 |
36 |
37 | @pytest.mark.parametrize('crossover', [DEX(), DEM()])
38 | def test_multi_frankstein(crossover):
39 |
40 | problem = get_problem("truss2d")
41 |
42 | NGEN = 100
43 | POPSIZE = 100
44 | SEED = 5
45 |
46 | frank = NSGA2(pop_size=POPSIZE, crossover=crossover)
47 |
48 | res_frank = minimize(problem,
49 | frank,
50 | ('n_gen', NGEN),
51 | seed=SEED,
52 | save_history=False,
53 | verbose=False)
54 |
55 | assert len(res_frank.opt) > 0
56 |
57 |
58 | def test_gde3_pm_run():
59 |
60 | problem = get_problem("truss2d")
61 |
62 | NGEN = 50
63 | POPSIZE = 50
64 | SEED = 5
65 |
66 | gde3pm = GDE3(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=0.5, F=(0.0, 0.9), de_repair="bounce-back",
67 | survival=RankAndCrowding(crowding_func="pcd"), genetic_mutation=PM())
68 |
69 | res_gde3pm = minimize(problem,
70 | gde3pm,
71 | ('n_gen', NGEN),
72 | seed=SEED,
73 | save_history=False,
74 | verbose=False)
75 |
76 | assert len(res_gde3pm.opt) > 0
77 |
78 | gde3 = GDE3(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=0.5, F=(0.0, 0.9), de_repair="bounce-back",
79 | survival=RankAndCrowding(crowding_func="pcd"))
80 |
81 | res_gde3 = minimize(problem,
82 | gde3,
83 | ('n_gen', NGEN),
84 | seed=SEED,
85 | save_history=False,
86 | verbose=False)
87 |
88 | assert len(sum(res_gde3pm.F - res_gde3.F)) >= 1e-3
89 |
90 |
91 | def test_multi_perf():
92 |
93 | problem = get_problem("truss2d")
94 | igd = IGD(pf=problem.pareto_front(), zero_to_one=True)
95 |
96 | NGEN = 250
97 | POPSIZE = 100
98 | SEED = 5
99 |
100 | gde3 = GDE3(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=0.5, F=(0.0, 0.9), de_repair="bounce-back",
101 | survival=RankAndCrowding(crowding_func="cd"))
102 |
103 | res_gde3 = minimize(problem,
104 | gde3,
105 | ('n_gen', NGEN),
106 | seed=SEED,
107 | save_history=False,
108 | verbose=False)
109 |
110 | igd_gde3 = igd.do(res_gde3.F)
111 | assert abs(igd_gde3 - 0.005859828655308572) <= 1e-8
112 |
113 | gde3p = GDE3(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=0.5, F=(0.0, 0.9), de_repair="bounce-back",
114 | survival=RankAndCrowding(crowding_func="pcd"))
115 |
116 | res_gde3p = minimize(problem,
117 | gde3p,
118 | ('n_gen', NGEN),
119 | seed=SEED,
120 | save_history=False,
121 | verbose=False)
122 |
123 | igd_gde3p = igd.do(res_gde3p.F)
124 | assert abs(igd_gde3p - 0.004744463013355145) <= 1e-8
125 |
126 | nsde = NSDE(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=0.5, F=(0.0, 0.9), de_repair="bounce-back",
127 | survival=RankAndCrowding(crowding_func="pcd"))
128 |
129 | res_nsde = minimize(problem,
130 | nsde,
131 | ('n_gen', NGEN),
132 | seed=SEED,
133 | save_history=False,
134 | verbose=False)
135 |
136 | igd_nsde = igd.do(res_nsde.F)
137 | assert abs(igd_nsde - 0.004562068055351625) <= 1e-8
--------------------------------------------------------------------------------
/tests/test_single.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from pymoo.optimize import minimize
4 | from pymoo.problems import get_problem
5 | from pymoo.operators.mutation.pm import PM
6 | from pymoode.algorithms import GDE3
7 | from pymoode.algorithms import DE
8 |
9 |
10 | @pytest.mark.parametrize('selection', ["rand", "best", "current-to-best", "current-to-rand", "ranked"])
11 | @pytest.mark.parametrize('crossover', ["bin", "exp"])
12 | @pytest.mark.parametrize('de_repair', ["bounce-back", "midway", "rand-init", "to-bounds"])
13 | def test_de_run(selection, crossover, de_repair):
14 | problem = get_problem("rastrigin")
15 |
16 | NGEN = 50
17 | POPSIZE = 20
18 | SEED = 3
19 |
20 | #DE Parameters
21 | CR = 0.5
22 | F = (0.3, 1.0)
23 |
24 | de = DE(pop_size=POPSIZE, variant=f"DE/{selection}/1/{crossover}", CR=CR, F=F, de_repair=de_repair)
25 |
26 | res_de = minimize(problem,
27 | de,
28 | ('n_gen', NGEN),
29 | seed=SEED,
30 | save_history=False,
31 | verbose=False)
32 |
33 | assert len(res_de.opt) > 0
34 |
35 |
36 | def test_de_pm_run():
37 |
38 | problem = get_problem("rastrigin")
39 |
40 | NGEN = 50
41 | POPSIZE = 20
42 | SEED = 3
43 |
44 | #DE Parameters
45 | CR = 0.5
46 | F = (0.3, 1.0)
47 |
48 | de = DE(pop_size=POPSIZE, variant=f"DE/rand/1/bin", CR=CR, F=F)
49 |
50 | res_de = minimize(problem,
51 | de,
52 | ('n_gen', NGEN),
53 | seed=SEED,
54 | save_history=False,
55 | verbose=False)
56 |
57 | depm = DE(pop_size=POPSIZE, variant=f"DE/rand/1/bin", CR=CR, F=F, genetic_mutation=PM())
58 |
59 | res_pm = minimize(problem,
60 | depm,
61 | ('n_gen', NGEN),
62 | seed=SEED,
63 | save_history=False,
64 | verbose=False)
65 |
66 | assert len(res_pm.opt) > 0
67 | assert len(sum(res_de.pop.get("F") - res_pm.pop.get("F"))) >= 1e-6
68 |
69 |
70 | def test_de_perf():
71 | problem = get_problem("rastrigin")
72 |
73 | NGEN = 100
74 | POPSIZE = 20
75 | SEED = 3
76 |
77 | #DE Parameters
78 | CR = 0.5
79 | F = (0.3, 1.0)
80 |
81 | de = DE(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=CR, F=F)
82 |
83 | res_de = minimize(problem,
84 | de,
85 | ('n_gen', NGEN),
86 | seed=SEED,
87 | save_history=False,
88 | verbose=False)
89 |
90 | assert len(res_de.opt) > 0
91 | assert res_de.F <= 1e-6
92 |
93 | gde3 = GDE3(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=CR, F=F)
94 |
95 | res_gde3 = minimize(problem,
96 | gde3,
97 | ('n_gen', NGEN),
98 | seed=SEED,
99 | save_history=False,
100 | verbose=False)
101 |
102 | assert res_gde3.F <= 1e-6
103 |
104 |
--------------------------------------------------------------------------------
/tests/test_spacing.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | import numpy as np
4 | from pymoo.optimize import minimize
5 | from pymoo.problems import get_problem
6 | from pymoode.performance import SpacingIndicator
7 | from pymoode.algorithms import GDE3
8 | from pymoode.survival import RankAndCrowding
9 |
10 |
11 | def test_spacing_values():
12 |
13 | F = np.array([
14 | [1.2, 7.8],
15 | [2.8, 5.1],
16 | [4.0, 2.8],
17 | [7.0, 2.2],
18 | [8.4, 1.2]
19 | ])
20 |
21 | assert abs(SpacingIndicator().do(F) - 0.73) <= 1e-3
22 |
23 | F = np.array([
24 | [1.0, 7.5],
25 | [1.1, 5.5],
26 | [2.0, 5.0],
27 | [3.0, 4.0],
28 | [4.0, 2.8],
29 | [5.5, 2.5],
30 | [6.8, 2.0],
31 | [8.4, 1.2]
32 | ])
33 |
34 | assert abs(SpacingIndicator().do(F) - 0.316) <= 1e-3
35 |
36 |
37 | def test_spacing_zero_to_one():
38 |
39 | problem = get_problem("truss2d")
40 | sp_from_pf = SpacingIndicator(pf=problem.pareto_front(), zero_to_one=True)
41 | sp_from_ref_points = SpacingIndicator(zero_to_one=True, nadir=problem.nadir_point(), ideal=problem.ideal_point())
42 | sp_no_scale = SpacingIndicator(zero_to_one=False)
43 |
44 | NGEN = 250
45 | POPSIZE = 100
46 | SEED = 5
47 |
48 | gde3 = GDE3(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=0.5, F=(0.0, 0.9), de_repair="bounce-back",
49 | survival=RankAndCrowding(crowding_func="pcd"))
50 |
51 | res_gde3 = minimize(problem,
52 | gde3,
53 | ('n_gen', NGEN),
54 | seed=SEED,
55 | save_history=False,
56 | verbose=False)
57 |
58 | assert sp_from_pf.do(res_gde3.F) > 0
59 | assert sp_from_ref_points.do(res_gde3.F) > 0
60 | assert sp_no_scale.do(res_gde3.F) > 0
61 | assert abs(sp_from_pf.do(res_gde3.F) - sp_from_ref_points.do(res_gde3.F)) <= 1e-6
--------------------------------------------------------------------------------