diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..d9ad0b40 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +ignore = E203, E266, E501, W503, F403, F401 +max-line-length = 79 +max-complexity = 18 +select = B,C,E,F,W,T4,B9 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..52edb2ac --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,37 @@ + + +## Description + + +## Related Issue + + + + + +## Motivation and Context + + +## How Has This Been Tested? + + + + +## Screenshots (if appropriate): + +## Types of changes + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) + +## Checklist: + + +- [ ] My code follows the code style of this project. +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. +- [ ] I have read the **CONTRIBUTING** document. +- [ ] I have added tests to cover my changes. +- [ ] All new and existing tests passed. + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..4bdcc6fc --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: +- repo: https://github.com/ambv/black + rev: stable + hooks: + - id: black + language_version: python3.6 +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v1.2.3 + hooks: + - id: flake8 diff --git a/AUTHORS.rst b/AUTHORS.rst index 168deb4a..9cf73d1d 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -9,20 +9,23 @@ The package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypacka .. _Cookiecutter: https://github.com/audreyr/cookiecutter .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage -Development Lead ----------------- +Maintainers +----------- * Lester James V. Miranda (`@ljvmiranda921`_) +* Aaron Moser (`@whzup`_) +* Siobhán K. Cronin (`@SioKCronin`_) Contributors ------------ * Carl-K (`@Carl-K`_) -* Siobhán K Cronin (`@SioKCronin`_) * Andrew Jarcho (`@jazcap53`_) * Charalampos Papadimitriou (`@CPapadim`_) * Mamady Nabé (`@mamadyonline`_) * Erik (`@slek120`_) +* Jay Speidell (`@jayspeidell`_) +* Bradahoward (`@bradahoward`_) * Thomas (`@ThomasCES`_) .. _`@ljvmiranda921`: https://github.com/ljvmiranda921 @@ -32,4 +35,7 @@ Contributors .. _`@CPapadim`: https://github.com/CPapadim .. _`@mamadyonline`: https://github.com/mamadyonline .. _`@slek120`: https://github.com/slek120 +.. _`@whzup`: https://github.com/whzup +.. _`@jayspeidell`: https://github.com/jayspeidell +.. _`@bradahoward`: https://github.com/bradahoward .. _`@ThomasCES`: https://github.com/ThomasCES diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index ac5a56aa..2f58b7f7 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -77,13 +77,15 @@ Ready to contribute? Here's how to set up `pyswarms` for local development. Now you can make your changes locally. -5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: +5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox. In addition, ensure that your code is formatted using black:: $ flake8 pyswarms tests + $ black pyswarms tests $ python setup.py test or py.test $ tox - To get flake8 and tox, just pip install them into your virtualenv. + To get flake8, black, and tox, just pip install them into your virtualenv. If you wish, + you can add pre-commit hooks for both flake8 and black to make all formatting easier. 6. Commit your changes and push your branch to GitHub:: diff --git a/HISTORY.rst b/HISTORY.rst index 332144af..7914592f 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -6,32 +6,32 @@ History ------------------ * First release on PyPI. -* Includes primary optimization techniques such as global-best PSO and local-best PSO (# 1_) (# 3_). +* Includes primary optimization techniques such as global-best PSO and local-best PSO - `#1`_, `#3`_ -.. _1: https://github.com/ljvmiranda921/pyswarms/issues/1 -.. _3: https://github.com/ljvmiranda921/pyswarmsissues/3 +.. _#1: https://github.com/ljvmiranda921/pyswarms/issues/1 +.. _#3: https://github.com/ljvmiranda921/pyswarmsissues/3 0.1.1 (2017-07-25) ~~~~~~~~~~~~~~~~~~ * Patch on LocalBestPSO implementation. It seems that it's not returning the best value of the neighbors, this fixes the problem . -* **New feature:** Test functions for single-objective problems (# 6_) (# 10_) (PR# 14_). Contributed by `@Carl-K `_. Thank you! +* **New feature:** Test functions for single-objective problems - `#6`_, `#10`_, `#14`_. Contributed by `@Carl-K `_. Thank you! -.. _6: https://github.com/ljvmiranda921/pyswarms/issues/6 -.. _10: https://github.com/ljvmiranda921/pyswarms/pull/10 -.. _14: https://github.com/ljvmiranda921/pyswarms/pull/14 +.. _#6: https://github.com/ljvmiranda921/pyswarms/issues/6 +.. _#10: https://github.com/ljvmiranda921/pyswarms/pull/10 +.. _#14: https://github.com/ljvmiranda921/pyswarms/pull/14 0.1.2 (2017-08-02) ~~~~~~~~~~~~~~~~~~ -* **New feature:** Binary Particle Swarm Optimization (# 7_) (# 17_). -* Patch on Ackley function return error (# 22_). -* Improved documentation and unit tests (# 16_). +* **New feature:** Binary Particle Swarm Optimization - `#7`_, `#17`_ +* Patch on Ackley function return error - `#22`_ +* Improved documentation and unit tests - `#16`_ -.. _7: https://github.com/ljvmiranda921/pyswarms/issues/7 -.. _16: https://github.com/ljvmiranda921/pyswarms/issues/16 -.. _17: https://github.com/ljvmiranda921/pyswarms/issues/17 -.. _22: https://github.com/ljvmiranda921/pyswarms/issues/22 +.. _#7: https://github.com/ljvmiranda921/pyswarms/issues/7 +.. _#16: https://github.com/ljvmiranda921/pyswarms/issues/16 +.. _#17: https://github.com/ljvmiranda921/pyswarms/issues/17 +.. _#22: https://github.com/ljvmiranda921/pyswarms/issues/22 0.1.4 (2017-08-03) @@ -42,33 +42,33 @@ History 0.1.5 (2017-08-11) ~~~~~~~~~~~~~~~~~~ -* **New feature:** easy graphics environment. This new plotting environment makes it easier to plot the costs and swarm movement in 2-d or 3-d planes (# 30_) (PR# 31_). +* **New feature:** easy graphics environment. This new plotting environment makes it easier to plot the costs and swarm movement in 2-d or 3-d planes - `#30`_, `#31`_ -.. _30: https://github.com/ljvmiranda921/pyswarms/issues/30 -.. _31: https://github.com/ljvmiranda921/pyswarms/pull/31 +.. _#30: https://github.com/ljvmiranda921/pyswarms/issues/30 +.. _#31: https://github.com/ljvmiranda921/pyswarms/pull/31 0.1.6 (2017-09-24) ~~~~~~~~~~~~~~~~~~ -* **New feature:** Native GridSearch and RandomSearch implementations for finding the best hyperparameters in controlling swarm behaviour (# 4_) (PR# 20_) (PR# 25_). Contributed by `@SioKCronin `_. Thanks a lot! -* Added tests for hyperparameter search techniques (# 27_) (PR# 28_) (PR# 40_). Contributed by `@jazcap53 `_. Thank you so much! +* **New feature:** Native GridSearch and RandomSearch implementations for finding the best hyperparameters in controlling swarm behaviour - `#4`_, `#20`_, `#25`_. Contributed by `@SioKCronin `_. Thanks a lot! +* Added tests for hyperparameter search techniques - `#27`_, `#28`_, `#40`_. Contributed by `@jazcap53 `_. Thank you so much! * Updated structure of Base classes for higher extensibility -.. _4: https://github.com/ljvmiranda921/pyswarms/issues/4 -.. _20: https://github.com/ljvmiranda921/pyswarms/pull/20 -.. _25: https://github.com/ljvmiranda921/pyswarms/pull/25 -.. _27: https://github.com/ljvmiranda921/pyswarms/issues/27 -.. _28: https://github.com/ljvmiranda921/pyswarms/pull/28 -.. _40: https://github.com/ljvmiranda921/pyswarms/pull/40 +.. _#4: https://github.com/ljvmiranda921/pyswarms/issues/4 +.. _#20: https://github.com/ljvmiranda921/pyswarms/pull/20 +.. _#25: https://github.com/ljvmiranda921/pyswarms/pull/25 +.. _#27: https://github.com/ljvmiranda921/pyswarms/issues/27 +.. _#28: https://github.com/ljvmiranda921/pyswarms/pull/28 +.. _#40: https://github.com/ljvmiranda921/pyswarms/pull/40 0.1.7 (2017-09-25) ~~~~~~~~~~~~~~~~~~ -* Fixed patch on :code:`local_best.py` and :code:`binary.py` (# 33_) (PR# 34_). Thanks for the awesome fix, `@CPapadim `_! +* Fixed patch on :code:`local_best.py` and :code:`binary.py` - `#33`_, `#34`_. Thanks for the awesome fix, `@CPapadim `_! * Git now ignores IPython notebook checkpoints -.. _33: https://github.com/ljvmiranda921/pyswarms/issues/33 -.. _34: https://github.com/ljvmiranda921/pyswarms/pull/34 +.. _#33: https://github.com/ljvmiranda921/pyswarms/issues/33 +.. _#34: https://github.com/ljvmiranda921/pyswarms/pull/34 0.1.8 (2018-01-11) ~~~~~~~~~~~~~~~~~~ @@ -81,34 +81,56 @@ History 0.1.9 (2018-04-20) ~~~~~~~~~~~~~~~~~~ -* You can now set the initial position wherever you want (PR# 93_). -* Quick-fix for the rosenbrock function (PR# 98_). -* Tolerance can now be set to break during iteration (PR# 100_). +* You can now set the initial position wherever you want - `#93`_ +* Quick-fix for the Rosenbrock function - `#98`_ +* Tolerance can now be set to break during iteration - `#100`_ Thanks for all the wonderful Pull Requests, `@mamadyonline `_! -.. _93: https://github.com/ljvmiranda921/pyswarms/pull/93 -.. _98: https://github.com/ljvmiranda921/pyswarms/pull/98 -.. _100: https://github.com/ljvmiranda921/pyswarms/pull/100 +.. _#93: https://github.com/ljvmiranda921/pyswarms/pull/93 +.. _#98: https://github.com/ljvmiranda921/pyswarms/pull/98 +.. _#100: https://github.com/ljvmiranda921/pyswarms/pull/100 0.2.0 (2018-06-11) ------------------ -* New PySwarms backend. You can now build native swarm implementations using this module! (PR# 115_) (PR# 116_) (PR# 117_) -* Drop Python 2.7 version support. This package now supports Python 3.4 and up (PR# 114_). -* All tests were ported into pytest (PR# 113_). +* New PySwarms backend. You can now build native swarm implementations using this module! - `#115`_, `#116`_, `#117`_ +* Drop Python 2.7 version support. This package now supports Python 3.4 and up - `#113`_ +* All tests were ported into pytest - `#114`_ -.. _113: https://github.com/ljvmiranda921/pyswarms/pull/113 -.. _114: https://github.com/ljvmiranda921/pyswarms/pull/114 -.. _115: https://github.com/ljvmiranda921/pyswarms/pull/115 -.. _116: https://github.com/ljvmiranda921/pyswarms/pull/116 -.. _117: https://github.com/ljvmiranda921/pyswarms/pull/117 +.. _#113: https://github.com/ljvmiranda921/pyswarms/pull/113 +.. _#114: https://github.com/ljvmiranda921/pyswarms/pull/114 +.. _#115: https://github.com/ljvmiranda921/pyswarms/pull/115 +.. _#116: https://github.com/ljvmiranda921/pyswarms/pull/116 +.. _#117: https://github.com/ljvmiranda921/pyswarms/pull/117 0.2.1 (2018-06-27) ------------------- +~~~~~~~~~~~~~~~~~~ + +* Fix sigmoid function in BinaryPSO - `#145`_. Thanks a lot `@ThomasCES `_! -* Fix sigmoid function in BinaryPSO. Thanks a lot `@ThomasCES `_! +* New GeneralOptimizer algorithm that allows you to switch-out topologies for your optimization needs - `#151`_. Thanks a lot `@whzup `_! +* All topologies now have a static attribute. Neigbors can now be set initially or computed dynamically - `#164`_. Thanks a lot `@whzup `_! +* New single-objective functions - `#168`_. Awesome work, `@jayspeidell `_! +* New tutorial on Inverse Kinematics using Particle Swarm Optimization - `#141`_. Thanks a lot `@whzup `_! +* New plotters module for visualization. The environment module is now deprecated - `#135`_ +* Keyword arguments can now be passed in the :code:`optimize()` method for your custom objective functions - `#144`_. Great job, `@bradahoward `_ + +.. _#135: https://github.com/ljvmiranda921/pyswarms/pull/135 +.. _#141: https://github.com/ljvmiranda921/pyswarms/pull/141 +.. _#142: https://github.com/ljvmiranda921/pyswarms/pull/142 +.. _#144: https://github.com/ljvmiranda921/pyswarms/pull/144 +.. _#151: https://github.com/ljvmiranda921/pyswarms/pull/151 +.. _#155: https://github.com/ljvmiranda921/pyswarms/pull/155 +.. _#164: https://github.com/ljvmiranda921/pyswarms/pull/164 +.. _#168: https://github.com/ljvmiranda921/pyswarms/pull/168 +.. _#176: https://github.com/ljvmiranda921/pyswarms/pull/176 +.. _#177: https://github.com/ljvmiranda921/pyswarms/pull/177 diff --git a/README.md b/README.md index b3d44870..aa2cdc69 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -![PySwarms Logo](docs/pyswarms-header.png) - +![PySwarms Logo](https://i.imgur.com/eX8oqPQ.png) --- @@ -8,6 +7,7 @@ [![Documentation Status](https://readthedocs.org/projects/pyswarms/badge/?version=master)](https://pyswarms.readthedocs.io/en/master/?badge=development) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg )](https://raw.githubusercontent.com/ljvmiranda921/pyswarms/master/LICENSE) [![DOI](http://joss.theoj.org/papers/10.21105/joss.00433/status.svg)](https://doi.org/10.21105/joss.00433) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) [![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/pyswarms/Issues) PySwarms is an extensible research toolkit for particle swarm optimization @@ -174,51 +174,77 @@ hyperparameter options that enables it. 9.504769054771 ``` -### Plotting environments +### Swarm visualization It is also possible to plot optimizer performance for the sake of formatting. -The plotting environment is built on top of `matplotlib`, making it +The plotters moule is built on top of `matplotlib`, making it highly-customizable. -The environment takes in the optimizer and its parameters, then performs a -fresh run to plot the cost and create animation. ```python import pyswarms as ps from pyswarms.utils.functions import single_obj as fx -from pyswarms.utils.environments import PlotEnvironment +from pyswarms.utils.plotters import plot_cost_history # Set-up optimizer options = {'c1':0.5, 'c2':0.3, 'w':0.9} -optimizer = ps.single.GlobalBestPSO(n_particles=10, dimensions=3, options=options) -# Initialize plot environment -plt_env = PlotEnvironment(optimizer, fx.sphere_func, 1000) +optimizer = ps.single.GlobalBestPSO(n_particles=50, dimensions=2, options=options) +optimizer.optimize(fx.sphere_func, iters=100) # Plot the cost -plt_env.plot_cost(figsize=(8,6)); +plot_cost_history(optimizer.cost_history) plt.show() ``` - +![CostHistory](https://i.imgur.com/19Iuz4B.png) + +We can also plot the animation... + +```python +from pyswarms.utils.plotters.formatters import Mesher +from pyswarms.utils.plotters.formatters import Designer +# Plot the sphere function's mesh for better plots +m = Mesher(func=fx.sphere_func) +# Adjust figure limits +d = Designer(limits=[(-1,1), (-1,1), (-0.1,1)], + label=['x-axis', 'y-axis', 'z-axis']) +``` -We can also plot the animation, +In 2D, ```python -plt_env.plot_particles2D(limits=((-1.2,1.2),(-1.2,1.2)) +plot_contour(pos_history=optimizer.pos_history, mesher=m, mark=(0,0)) ``` - +![Contour](https://i.imgur.com/H3YofJ6.gif) +Or in 3D! + +```python +pos_history_3d = m.compute_history_3d(optimizer.pos_history) # preprocessing +animation3d = plot_surface(pos_history=pos_history_3d, + mesher=m, designer=d, + mark=(0,0,0)) +``` + +![Surface](https://i.imgur.com/kRb61Hx.gif) ## Contributing -PySwarms is currently maintained by a single person (me!) with the aid of a -few but very helpful contributors. We would appreciate it if you can lend a -hand with the following: +PySwarms is currently maintained by a small yet dedicated team: +- Lester James V. Miranda ([@ljvmiranda921](https://github.com/ljvmiranda921)) +- Siobhán K. Cronin ([@SioKCronin](https://github.com/SioKCronin)) +- Aaron Moser ([@whzup](https://github.com/whzup)) + +And we would appreciate it if you can lend a hand with the following: * Find bugs and fix them * Update documentation in docstrings * Implement new optimizers to our collection * Make utility functions more robust. +We would also like to acknowledge [all our +contributors](http://pyswarms.readthedocs.io/en/latest/authors.html), past and +present, for making this project successful! + If you wish to contribute, check out our [contributing guide]. Moreover, you can also see the list of features that need some help in our [Issues] page. diff --git a/docs/api/_pyswarms.backend.rst b/docs/api/_pyswarms.backend.rst index 93c01ad3..ddae95c1 100644 --- a/docs/api/_pyswarms.backend.rst +++ b/docs/api/_pyswarms.backend.rst @@ -4,7 +4,7 @@ Backend The main workhorse of PySwarms is the backend module. It contains various primitive methods and classes to help you create your own custom swarm implementation. The high-level PSO implementations in this library such -as GlobalBestPSO and LocalBestPSo were built using the backend module. +as GlobalBestPSO and LocalBestPSO were built using the backend module. .. toctree:: diff --git a/docs/api/_pyswarms.utils.rst b/docs/api/_pyswarms.utils.rst index 6091ad53..a7e81225 100644 --- a/docs/api/_pyswarms.utils.rst +++ b/docs/api/_pyswarms.utils.rst @@ -1,12 +1,13 @@ Utilities ========= -This includes various utilities to help in optimization. In the future, -parameter search and plotting techniques will be incoroporated in this -module. +This includes various utilities to help in optimization. Some utilities +include benchmark objective functions, hyperparameter search, and plotting +functionalities. .. toctree:: pyswarms.utils.functions pyswarms.utils.search - pyswarms.utils.environments \ No newline at end of file + pyswarms.utils.plotters + pyswarms.utils.environments diff --git a/docs/api/pyswarms.single.rst b/docs/api/pyswarms.single.rst index 7c1ea34e..ae8a17b0 100644 --- a/docs/api/pyswarms.single.rst +++ b/docs/api/pyswarms.single.rst @@ -21,4 +21,14 @@ pyswarms.single.local_best module :undoc-members: :show-inheritance: :private-members: - :special-members: __init__ \ No newline at end of file + :special-members: __init__ + +pyswarms.single.general_optimizer module +--------------------------------- + +.. automodule:: pyswarms.single.general_optimizer + :members: + :undoc-members: + :show-inheritance: + :private-members: + :special-members: __init__ diff --git a/docs/api/pyswarms.topology.rst b/docs/api/pyswarms.topology.rst index da49d34b..fa8cf608 100644 --- a/docs/api/pyswarms.topology.rst +++ b/docs/api/pyswarms.topology.rst @@ -34,4 +34,31 @@ pyswarms.backend.topology.ring module :members: :undoc-members: :show-inheritance: - :special-members: __init__ \ No newline at end of file + :special-members: __init__ + +pyswarms.backend.topology.von_neumann module +-------------------------------------- + +.. automodule:: pyswarms.backend.topology.von_neumann + :members: + :undoc-members: + :show-inheritance: + :special-members: __init__ + +pyswarms.backend.topology.pyramid module +-------------------------------------- + +.. automodule:: pyswarms.backend.topology.pyramid + :members: + :undoc-members: + :show-inheritance: + :special-members: __init__ + +pyswarms.backend.topology.random module +-------------------------------------- + +.. automodule:: pyswarms.backend.topology.random + :members: + :undoc-members: + :show-inheritance: + :special-members: __init__ diff --git a/docs/api/pyswarms.utils.environments.rst b/docs/api/pyswarms.utils.environments.rst index 5e4a658b..907e20b4 100644 --- a/docs/api/pyswarms.utils.environments.rst +++ b/docs/api/pyswarms.utils.environments.rst @@ -3,6 +3,10 @@ pyswarms.utils.environments package .. automodule:: pyswarms.utils.environments +.. deprecated:: 0.2.1 + This module will be deprecated in the next release. Please use + :mod:`pyswarms.utils.plotters` instead. + pyswarms.utils.environments.plot_environment module ---------------------------------------------------- diff --git a/docs/api/pyswarms.utils.plotters.rst b/docs/api/pyswarms.utils.plotters.rst new file mode 100644 index 00000000..a95795d3 --- /dev/null +++ b/docs/api/pyswarms.utils.plotters.rst @@ -0,0 +1,20 @@ +pyswarms.utils.plotters package +================================ + +.. automodule:: pyswarms.utils.plotters + +pyswarms.utils.plotters.plotters module +---------------------------------------- + +.. automodule:: pyswarms.utils.plotters.plotters + :members: + :undoc-members: + :show-inheritance: + +pyswarms.utils.plotters.formatters module +------------------------------------------ + +.. automodule:: pyswarms.utils.plotters.formatters + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/docs/assets/inheritance.aux b/docs/assets/inheritance.aux new file mode 100644 index 00000000..7c14129a --- /dev/null +++ b/docs/assets/inheritance.aux @@ -0,0 +1,2 @@ +\relax +\gdef \sa@multi@numpages {0} diff --git a/docs/assets/inheritance.fdb_latexmk b/docs/assets/inheritance.fdb_latexmk new file mode 100644 index 00000000..1b01ed13 --- /dev/null +++ b/docs/assets/inheritance.fdb_latexmk @@ -0,0 +1,155 @@ +# Fdb version 3 +["pdflatex"] 1528613815 "inheritance.tex" "inheritance.pdf" "inheritance" 1528613816 + "/dev/null" 1528595442 0 d41d8cd98f00b204e9800998ecf8427e "" + "/etc/texmf/web2c/texmf.cnf" 1515379388 475 c0e671620eb5563b2130f56340a5fde8 "" + "/home/ljvm/.texmf-var/fonts/map/pdftex/updmap/pdftex.map" 1515762570 182396 f7dec8867b39ce26f1cba3e9650c3aa6 "" + "/home/ljvm/texmf/tex/generic/oberdiek/etexcmds.sty" 1335995445 7612 c47308d923ec19888707b0f1792b326a "" + "/home/ljvm/texmf/tex/generic/oberdiek/ifpdf.sty" 1303254447 7140 ece2cc23d9f20e1f53975ac167f42d3e "" + "/home/ljvm/texmf/tex/generic/oberdiek/infwarerr.sty" 1335995445 8253 3bdedc8409aa5d290a2339be6f09af03 "" + "/home/ljvm/texmf/tex/generic/oberdiek/kvdefinekeys.sty" 1335995445 5152 387d9200f396b498d5fd679ae44ed898 "" + "/home/ljvm/texmf/tex/generic/oberdiek/kvsetkeys.sty" 1335995445 14040 8de9f47fabc4ca3bd69b6d795e32751c "" + "/home/ljvm/texmf/tex/generic/oberdiek/ltxcmds.sty" 1335995445 18425 775b341047ce304520cc7c11ca41392e "" + "/home/ljvm/texmf/tex/generic/oberdiek/pdftexcmds.sty" 1335995445 19987 01cb2f3c1d21e5f05711b7fd50b17f2a "" + "/home/ljvm/texmf/tex/generic/xstring/xstring.sty" 1269905706 144 0ca8d67b000b795a4d9ec000e0fd09c7 "" + "/home/ljvm/texmf/tex/generic/xstring/xstring.tex" 1381789620 54373 fd4487ae3e45d4074bc89aea1d2b6807 "" + "/home/ljvm/texmf/tex/latex/graphics/dvipsnam.def" 1454284088 4945 3b3a8face751255856839a3489e83341 "" + "/home/ljvm/texmf/tex/latex/graphics/graphics.sty" 1454284088 14337 b66dff1d80f6c21e70858a2b3c2d327d "" + "/home/ljvm/texmf/tex/latex/graphics/graphicx.sty" 1428932888 8125 557ab9f1bfa80d369fb45a914aa8a3b4 "" + "/home/ljvm/texmf/tex/latex/graphics/trig.sty" 1454284088 3980 0a268fbfda01e381fa95821ab13b6aee "" + "/home/ljvm/texmf/tex/latex/oberdiek/epstopdf-base.sty" 1303254447 12029 04d7fdf76e0464c23b5aa3a727952d7c "" + "/home/ljvm/texmf/tex/latex/oberdiek/grfext.sty" 1335995445 7075 bd0c34fbf1ae8fd1debd2a554e41b2d5 "" + "/home/ljvm/texmf/tex/latex/oberdiek/kvoptions.sty" 1335995445 22417 c74ff4af6a1aa2b65d1924020edbbe11 "" + "/home/ljvm/texmf/tex/latex/preview/preview.sty" 1447630789 13687 0cb5888b46d12f19ed3b85a16d43470e "" + "/home/ljvm/texmf/tex/latex/preview/prtightpage.def" 1266794019 4841 763a1efd128d3821c07232f7b2638b7b "" + "/home/ljvm/texmf/tex/latex/standalone/standalone.cfg" 1437172902 902 f9a15737aea33182ec1f3542ca20dbfe "" + "/home/ljvm/texmf/tex/latex/standalone/standalone.cls" 1437172902 27291 b798d344bcba4430e7f7dc7561c517d5 "" + "/home/ljvm/texmf/tex/latex/tools/calc.sty" 1454284088 10214 d03d065f799d54f6b7e9b175f8d84279 "" + "/home/ljvm/texmf/tex/latex/xcolor/xcolor.sty" 1169481954 55224 a43bab84e0ac5e6efcaf9a98bde73a94 "" + "/usr/share/texlive/texmf-dist/fonts/map/fontname/texfonts.map" 1272929888 3287 e6b82fe08f5336d4d5ebc73fb1152e87 "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmmi6.tfm" 1136768653 1512 f21f83efb36853c0b70002322c1ab3ad "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmmi9.tfm" 1136768653 1524 d89e2d087a9828407a196f428428ef4a "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmr6.tfm" 1136768653 1300 b62933e007d01cfd073f79b963c01526 "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmr9.tfm" 1136768653 1292 6b21b9c2c7bebb38aa2273f7ca0fb3af "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmss10.tfm" 1136768653 1316 b636689f1933f24d1294acdf6041daaa "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmss9.tfm" 1136768653 1320 49357c421c0d469f88b867dd0c3d10e8 "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmssbx10.tfm" 1136768653 1272 e2d13f0df30bf3ad990bb9d028e37f34 "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmsy6.tfm" 1136768653 1116 933a60c408fc0a863a92debe84b2d294 "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmsy9.tfm" 1136768653 1116 25a7bf822c58caf309a702ef79f4afbb "" + "/usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmss9.pfb" 1248133631 24373 a91d375736817a75026663adcb2190c1 "" + "/usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmssbx10.pfb" 1248133631 28902 2f5c04fd2884d1878057baa5aad22765 "" + "/usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy9.pfb" 1248133631 32442 c975af247b6702f7ca0c299af3616b80 "" + "/usr/share/texlive/texmf-dist/tex/context/base/supp-pdf.mkii" 1337017135 71627 94eb9990bed73c364d7f53f960cc8c5b "" + "/usr/share/texlive/texmf-dist/tex/generic/ifxetex/ifxetex.sty" 1284331290 1458 43ab4710dc82f3edeabecd0d099626b2 "" + "/usr/share/texlive/texmf-dist/tex/generic/oberdiek/ifluatex.sty" 1303254447 7324 11d14f318d865f420e692d4e6c9c18c3 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex" 1288312291 1006 b103be0bfc8c1682ff1fa9760697a329 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorearrows.code.tex" 1439074469 43226 167a99346bfe2676e3efcdde2d81fe45 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreexternal.code.tex" 1439074469 19302 4f089dc590e71f7331e6d5b5ea85273b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoregraphicstate.code.tex" 1439074469 6068 edae1e768a7d8d8f0f00e953d2b0153e "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreimage.code.tex" 1393459310 7041 a891ad72049e17c4e366c40ca37b0ccb "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorelayers.code.tex" 1393459310 4625 40c07e9f6f2f7c674704b3f2055560ce "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreobjects.code.tex" 1203877327 2631 7eefa6cdbefd8d4e2bad7262cf1094cd "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathconstruct.code.tex" 1393459310 43477 81143b33d9ebafdeead07ede13372427 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathprocessing.code.tex" 1393459310 17436 8d99d4113be311daf23deff86991ee7d "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex" 1439074469 20772 c57e34db4aa7b1da013169d04b743eac "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepatterns.code.tex" 1393459310 9641 711f0edc22c180a5caf168b6e8970057 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepoints.code.tex" 1393459310 34516 658a71478d21df554bce9d9cd436203a "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorequick.code.tex" 1288312291 3052 e5672c657232fd63b0a9853b0746297c "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorescopes.code.tex" 1439074469 16669 4ec6e40088fc6de6334b443fe2dc59f0 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreshade.code.tex" 1393459310 21541 4cd19f8ff7dd74d5aa7d803a6397af84 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransformations.code.tex" 1439074469 19998 d77fef95c7369827753d17fd11be19c4 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransparency.code.tex" 1393459310 8943 2e2495b057f8f0035b5568394d489963 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryarrows.code.tex" 1203727794 437 cf40f841f40822be6cb995f8b47112fd "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarybackgrounds.code.tex" 1393459310 4611 b858a4e5bd5442802c91a13027dc25bb "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarycalc.code.tex" 1393459310 15934 b941bd3ae7b33179029513707d1f0ff6 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.code.tex" 1393459310 5484 4bb4a5cbbd05d6f17a261b59dbd014f1 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.markings.code.tex" 1203727794 782 2479083eef1ef47450770d40ad81f937 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfadings.code.tex" 1288312291 1298 83d7449064b0f0f089f1898a244b6d16 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfit.code.tex" 1393459310 3725 36db4c06798413d051778705f3255eea "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarypositioning.code.tex" 1288312291 4034 0a8cd33cf30d262ec971380666acb2d0 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshadows.code.tex" 1203727794 3001 d54bab2f783098ed890fabbeb437b04f "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.arrows.code.tex" 1203727794 527 a8d3e34fbab3dc317cf9b06aa5cdc2e4 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.callouts.code.tex" 1203727794 1158 d6338189706f4587fbc6175c0fb41f17 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.code.tex" 1203727794 607 40dc15d3efcf10f095866a94bd544bc1 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.geometric.code.tex" 1203727794 457 ffe9f8b9d108b5f729fd86c78c63589a "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.misc.code.tex" 1203727794 447 e87a0add254801e837fa6c18f61f340f "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.multipart.code.tex" 1203727794 1004 86af66805a9d0b62bd41ea0796a64d50 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.symbols.code.tex" 1203727794 590 7e11000a24bbee9ae2a4cd0e5d88e58c "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarytopaths.code.tex" 1288312291 11599 d694704a88e2f9007c996d3a6a4d629c "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex" 1439074469 176652 1c2926908e2b356d454795c35385d580 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/decorations/pgflibrarydecorations.markings.code.tex" 1393459310 5181 c2b736d254ec36204f8fffd5a45bbd41 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryarrows.code.tex" 1393459310 31927 7acd27f90dd95ce67ad32166cd0b95ec "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryfadings.code.tex" 1203727794 2647 defb4a59c2a1d36127a1ac6eebb4a5c1 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex" 1393459310 32969 dbcfd5a7de6a0f7255c333ef60287d59 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.arrows.code.tex" 1288312291 69900 cbd9fafb795a493fb2a3b73713994b78 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.callouts.code.tex" 1393459310 28333 0189c4cfb5044e700e6ba65a32295f01 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.geometric.code.tex" 1288312291 132566 291d42c3b23fdb5c47e51b36a5fea0c4 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.misc.code.tex" 1393459310 37737 ea6cb0b4e615f6048f20ee7153b3cc78 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.multipart.code.tex" 1288312291 49891 e74f8181c57d9359c941b6bee48fccc2 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.symbols.code.tex" 1393459310 90791 0f3e73cae9286c96d9fcb2161cc223bc "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex" 1393459310 454 9e9e7c99f4da4f41698be21eaef4938e "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathcalc.code.tex" 1393459310 13416 940ea6971d7a65dc440d3479939c66ae "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfloat.code.tex" 1439074469 94097 62ac62cda46eb715560dc27f9ed6e8b1 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.base.code.tex" 1393459310 9375 5adc70f722abd29fc250d59e0694b548 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.basic.code.tex" 1439074469 22069 7c21c42b15718ce922f36235be360490 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.code.tex" 1439074469 8210 a7be5b52ef3d2c087b7dc3d52898b67e "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.comparison.code.tex" 1288312291 3534 c7f28fbac13616513e513efe93b8569b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.integerarithmetics.code.tex" 1393459310 3167 7c9394e79aac27db96a92f9b2792b858 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.misc.code.tex" 1439074469 9289 261407875b9dbb0194691c3eb893610f "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.random.code.tex" 1439074469 7078 946ddf4a7e57219b6afdbad98eb6731b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.round.code.tex" 1288312291 2688 139c6abc86761a6190c2f4bef5d752be "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.trigonometric.code.tex" 1439074469 92284 dcf023dbaa84e6c50e11c2f79fe8cfa6 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathparser.code.tex" 1439074469 35430 046e15fbb65e74d8f0e7945f99741fdb "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathutil.code.tex" 1393459310 7099 f44d505bae6c7c2b933cdd63441db4b9 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduledecorations.code.tex" 1393459310 71902 658cc1e13f73daec4225b8fc1c27600b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmodulematrix.code.tex" 1393459310 20934 2328bd2e04520e1ab077ac4ee13b8935 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduleplot.code.tex" 1439074469 16203 83cbe1220e389eeee283a6168f9a567b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduleshapes.code.tex" 1439074469 42906 d54376d96df1a2ae2d33fb722236d8e9 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgf.cfg" 1288312291 978 15af626ebd3d4d790aac19170dac04f2 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-common-pdf.def" 1393459310 5437 d91f93ed61ecdc57e119849b2d784a0b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-pdftex.def" 1439074469 13507 809d848d9262638e1b1705a68a73c566 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex" 1439074469 35113 2ccc50c1c9573e4bac9230d030f9c67c "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.code.tex" 1203877327 1983 b5994ebbcee17f1ba3d29bb1bd696fcf "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.code.tex" 1393459310 7881 d459d6057e13d10ce7a227ae44b7295e "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex" 1393459310 22211 d696ef78c12269178882d218b2cf191d "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex" 1393459310 36194 e194ef4e0b396b531a3891feb4b1cc22 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeysfiltered.code.tex" 1393459310 33377 af391d6ad1bfcbe2278e191f48e43ba8 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex" 1440888734 2536 a3b0529d815a2759ba157b56610a6377 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-common-lists.tex" 1393459310 6833 114eda2cf1d348e0e7e477a1a4dc1941 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-common.tex" 1439074469 16501 ab0135765e27b6b8dae047831fe84818 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-latex.def" 1439074469 5544 294baac9629ba59f675b1f2027ad7136 "" + "/usr/share/texlive/texmf-dist/tex/generic/xkeyval/keyval.tex" 1403829539 2725 fc34ef3ccb37ba15a640e8fca6190bca "" + "/usr/share/texlive/texmf-dist/tex/generic/xkeyval/xkeyval.tex" 1417732693 19231 26434a5656c684f5ffb1f26f98006baa "" + "/usr/share/texlive/texmf-dist/tex/generic/xkeyval/xkvutils.tex" 1403829539 7677 6f5ce7c1124cad7ec57d05b2562bd8fe "" + "/usr/share/texlive/texmf-dist/tex/latex/base/article.cls" 1454284088 20708 39fdf9e2fb65617012fa7382a351f485 "" + "/usr/share/texlive/texmf-dist/tex/latex/base/ifthen.sty" 1454284088 5159 a08c9bbd48fc492f15b22e458bef961f "" + "/usr/share/texlive/texmf-dist/tex/latex/base/size10.clo" 1454284088 9179 4cd3c5f593e63512893b8ac0123f1bd7 "" + "/usr/share/texlive/texmf-dist/tex/latex/latexconfig/color.cfg" 1254097189 802 7b8c8d72c24d795ed7720e4dfd29bff3 "" + "/usr/share/texlive/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg" 1279039959 678 4792914a8f45be57bb98413425e4c7af "" + "/usr/share/texlive/texmf-dist/tex/latex/latexconfig/graphics.cfg" 1278958963 3563 d35e897cae3b8c6848f6677b73370b54 "" + "/usr/share/texlive/texmf-dist/tex/latex/ms/everyshi.sty" 1177890616 3878 6aa7c08ff2621006e0603349e40a30a8 "" + "/usr/share/texlive/texmf-dist/tex/latex/pdftex-def/pdftex.def" 1306616590 55368 3c8a0d99822330f2dfabc0dfb09ce897 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf-blur/tikzlibraryshadows.blur.code.tex" 1335333685 7525 063a37c856ae2c38332d93e3d457a299 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty" 1439074469 1197 8a80cdde14696a9198f1793a55dcf332 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty" 1288312291 410 5bf12ea7330e5f12c445332a4fe9a263 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-0-65.sty" 1203877327 21115 facf03b7dbe5ea2f5f1dce1ac84b5d05 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-1-18.sty" 1203727794 1091 d9163d29def82ee90370c8a63667742c "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty" 1203877327 339 592cf35cba3d400082b8a9a5d0199d70 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/math/pgfmath.sty" 1393459310 306 0796eafca5e159e6ec2167a6d22d81b1 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty" 1393459310 443 0b2e781830192df35c0fd357cf13e26e "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgffor.sty" 1393459310 348 8927fde343487e003b01a4c2ca34073b "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfkeys.sty" 1203727794 274 4cad6e665cc93ac2ac979039a94fa1e1 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty" 1203877327 325 2bcd023400636339210573e2b3ee298b "" + "/usr/share/texlive/texmf-dist/tex/latex/pgfopts/pgfopts.sty" 1405118212 5540 d5c60cf09c59da351aa4023ed084e4eb "" + "/usr/share/texlive/texmf-dist/tex/latex/xkeyval/xkeyval.sty" 1417732693 4962 9c1069474ff71dbc47d5006555e352d3 "" + "/usr/share/texlive/texmf-dist/web2c/texmf.cnf" 1503343927 31343 93828589fb0cea665e553ee5a17ad2d4 "" + "/usr/share/texmf/web2c/texmf.cnf" 1503343927 31343 93828589fb0cea665e553ee5a17ad2d4 "" + "/var/lib/texmf/web2c/pdftex/pdflatex.fmt" 1528507587 718486 55e051f478d994e1e3e5b41625d996af "" + "inheritance.aux" 1528613815 8 a94a2480d3289e625eea47cd1b285758 "" + "inheritance.pdf" 1528613815 33726 755c6ba1b7e08ba25d4c4bf070e74723 "pdflatex" + "inheritance.png" 1528606148 15031 54872dfbf68cfa63dcdcc9264b34daae "" + "inheritance.tex" 1528613800 2092 8e81f1f5803589f754bd70c58b0c6450 "" + "tikz-uml.sty" 1526185697 302703 ed2f9b5dd419a64f254ecbf6a6653c7f "" + (generated) + "inheritance.pdf" + "inheritance.log" + "inheritance.aux" diff --git a/docs/assets/inheritance.fls b/docs/assets/inheritance.fls new file mode 100644 index 00000000..8e82d2d4 --- /dev/null +++ b/docs/assets/inheritance.fls @@ -0,0 +1,250 @@ +PWD /home/ljvm/Documents/Dev/pyswarms/docs/assets +INPUT /etc/texmf/web2c/texmf.cnf +INPUT /usr/share/texmf/web2c/texmf.cnf +INPUT /usr/share/texlive/texmf-dist/web2c/texmf.cnf +INPUT /var/lib/texmf/web2c/pdftex/pdflatex.fmt +INPUT inheritance.tex +OUTPUT inheritance.log +INPUT /home/ljvm/texmf/tex/latex/standalone/standalone.cls +INPUT /home/ljvm/texmf/tex/latex/standalone/standalone.cls +INPUT /usr/share/texlive/texmf-dist/tex/generic/oberdiek/ifluatex.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/oberdiek/ifluatex.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/oberdiek/ifluatex.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/ifpdf.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/ifpdf.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/ifpdf.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/ifxetex/ifxetex.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/ifxetex/ifxetex.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/ifxetex/ifxetex.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/xkeyval/xkeyval.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/xkeyval/xkeyval.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/xkeyval/xkeyval.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/xkeyval/xkvutils.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/xkeyval/keyval.tex +INPUT /dev/null +INPUT /home/ljvm/texmf/tex/latex/standalone/standalone.cfg +INPUT /home/ljvm/texmf/tex/latex/standalone/standalone.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/article.cls +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/article.cls +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/size10.clo +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/size10.clo +INPUT /home/ljvm/texmf/tex/latex/preview/preview.sty +INPUT /home/ljvm/texmf/tex/latex/preview/preview.sty +INPUT /home/ljvm/texmf/tex/latex/preview/prtightpage.def +INPUT /home/ljvm/texmf/tex/latex/preview/prtightpage.def +INPUT inheritance.png +INPUT ./inheritance.png +INPUT ./inheritance.pdf +INPUT inheritance.aux +INPUT inheritance.aux +INPUT ./inheritance.pdf +INPUT inheritance.png +INPUT ./inheritance.png +INPUT /home/ljvm/texmf/tex/latex/xcolor/xcolor.sty +INPUT /home/ljvm/texmf/tex/latex/xcolor/xcolor.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/color.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/color.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/pdftex-def/pdftex.def +INPUT /usr/share/texlive/texmf-dist/tex/latex/pdftex-def/pdftex.def +INPUT /home/ljvm/texmf/tex/generic/oberdiek/infwarerr.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/infwarerr.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/ltxcmds.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/ltxcmds.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/dvipsnam.def +INPUT /home/ljvm/texmf/tex/latex/graphics/dvipsnam.def +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-common.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-common-lists.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-latex.def +INPUT /usr/share/texlive/texmf-dist/tex/latex/ms/everyshi.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/ms/everyshi.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/graphicx.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/graphicx.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/graphics.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/graphics.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/trig.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/trig.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/graphics.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/graphics.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeysfiltered.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgf.cfg +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-pdftex.def +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-pdftex.def +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-common-pdf.def +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathcalc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathutil.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathparser.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.basic.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.trigonometric.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.random.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.comparison.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.base.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.round.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.misc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.integerarithmetics.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfloat.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepoints.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathconstruct.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorescopes.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoregraphicstate.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransformations.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorequick.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreobjects.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathprocessing.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorearrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreshade.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreimage.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreexternal.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorelayers.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransparency.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepatterns.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduleshapes.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduleplot.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-0-65.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-0-65.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-1-18.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-1-18.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgffor.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgffor.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfkeys.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfkeys.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/math/pgfmath.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/math/pgfmath.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmodulematrix.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarytopaths.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarytopaths.code.tex +INPUT tikz-uml.sty +INPUT tikz-uml.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/ifthen.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/ifthen.sty +INPUT /home/ljvm/texmf/tex/generic/xstring/xstring.sty +INPUT /home/ljvm/texmf/tex/generic/xstring/xstring.sty +INPUT /home/ljvm/texmf/tex/generic/xstring/xstring.tex +INPUT /home/ljvm/texmf/tex/latex/tools/calc.sty +INPUT /home/ljvm/texmf/tex/latex/tools/calc.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgfopts/pgfopts.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgfopts/pgfopts.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarybackgrounds.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarybackgrounds.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryarrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryarrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryarrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryarrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.geometric.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.geometric.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.geometric.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.geometric.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.misc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.misc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.misc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.misc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.symbols.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.symbols.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.symbols.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.symbols.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.arrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.arrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.arrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.arrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.callouts.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.callouts.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.callouts.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.callouts.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.multipart.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.multipart.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.multipart.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.multipart.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfit.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfit.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshadows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshadows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfadings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfadings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryfadings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryfadings.code.tex +OUTPUT inheritance.pdf +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.markings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.markings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduledecorations.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/decorations/pgflibrarydecorations.markings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/decorations/pgflibrarydecorations.markings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarypositioning.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarypositioning.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarycalc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarycalc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf-blur/tikzlibraryshadows.blur.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf-blur/tikzlibraryshadows.blur.code.tex +INPUT inheritance.aux +INPUT inheritance.aux +OUTPUT inheritance.aux +INPUT /usr/share/texlive/texmf-dist/fonts/map/fontname/texfonts.map +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmss10.tfm +INPUT /usr/share/texlive/texmf-dist/tex/context/base/supp-pdf.mkii +INPUT /usr/share/texlive/texmf-dist/tex/context/base/supp-pdf.mkii +INPUT /home/ljvm/texmf/tex/generic/oberdiek/pdftexcmds.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/pdftexcmds.sty +INPUT /home/ljvm/texmf/tex/latex/oberdiek/epstopdf-base.sty +INPUT /home/ljvm/texmf/tex/latex/oberdiek/epstopdf-base.sty +INPUT /home/ljvm/texmf/tex/latex/oberdiek/grfext.sty +INPUT /home/ljvm/texmf/tex/latex/oberdiek/grfext.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/kvdefinekeys.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/kvdefinekeys.sty +INPUT /home/ljvm/texmf/tex/latex/oberdiek/kvoptions.sty +INPUT /home/ljvm/texmf/tex/latex/oberdiek/kvoptions.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/kvsetkeys.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/kvsetkeys.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/etexcmds.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/etexcmds.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmss9.tfm +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmr9.tfm +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmr6.tfm +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmmi9.tfm +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmmi6.tfm +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmsy9.tfm +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmsy6.tfm +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmssbx10.tfm +INPUT /home/ljvm/.texmf-var/fonts/map/pdftex/updmap/pdftex.map +INPUT inheritance.aux +INPUT /usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmss9.pfb +INPUT /usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmssbx10.pfb +INPUT /usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy9.pfb diff --git a/docs/assets/optimization_loop.aux b/docs/assets/optimization_loop.aux new file mode 100644 index 00000000..7c14129a --- /dev/null +++ b/docs/assets/optimization_loop.aux @@ -0,0 +1,2 @@ +\relax +\gdef \sa@multi@numpages {0} diff --git a/docs/assets/optimization_loop.fdb_latexmk b/docs/assets/optimization_loop.fdb_latexmk new file mode 100644 index 00000000..77e1eb7d --- /dev/null +++ b/docs/assets/optimization_loop.fdb_latexmk @@ -0,0 +1,156 @@ +# Fdb version 3 +["pdflatex"] 1528617882 "optimization_loop.tex" "optimization_loop.pdf" "optimization_loop" 1528617883 + "/dev/null" 1528595442 0 d41d8cd98f00b204e9800998ecf8427e "" + "/etc/texmf/web2c/texmf.cnf" 1515379388 475 c0e671620eb5563b2130f56340a5fde8 "" + "/home/ljvm/.texmf-var/fonts/map/pdftex/updmap/pdftex.map" 1515762570 182396 f7dec8867b39ce26f1cba3e9650c3aa6 "" + "/home/ljvm/texmf/tex/generic/oberdiek/etexcmds.sty" 1335995445 7612 c47308d923ec19888707b0f1792b326a "" + "/home/ljvm/texmf/tex/generic/oberdiek/ifpdf.sty" 1303254447 7140 ece2cc23d9f20e1f53975ac167f42d3e "" + "/home/ljvm/texmf/tex/generic/oberdiek/infwarerr.sty" 1335995445 8253 3bdedc8409aa5d290a2339be6f09af03 "" + "/home/ljvm/texmf/tex/generic/oberdiek/kvdefinekeys.sty" 1335995445 5152 387d9200f396b498d5fd679ae44ed898 "" + "/home/ljvm/texmf/tex/generic/oberdiek/kvsetkeys.sty" 1335995445 14040 8de9f47fabc4ca3bd69b6d795e32751c "" + "/home/ljvm/texmf/tex/generic/oberdiek/ltxcmds.sty" 1335995445 18425 775b341047ce304520cc7c11ca41392e "" + "/home/ljvm/texmf/tex/generic/oberdiek/pdftexcmds.sty" 1335995445 19987 01cb2f3c1d21e5f05711b7fd50b17f2a "" + "/home/ljvm/texmf/tex/generic/xstring/xstring.sty" 1269905706 144 0ca8d67b000b795a4d9ec000e0fd09c7 "" + "/home/ljvm/texmf/tex/generic/xstring/xstring.tex" 1381789620 54373 fd4487ae3e45d4074bc89aea1d2b6807 "" + "/home/ljvm/texmf/tex/latex/graphics/dvipsnam.def" 1454284088 4945 3b3a8face751255856839a3489e83341 "" + "/home/ljvm/texmf/tex/latex/graphics/graphics.sty" 1454284088 14337 b66dff1d80f6c21e70858a2b3c2d327d "" + "/home/ljvm/texmf/tex/latex/graphics/graphicx.sty" 1428932888 8125 557ab9f1bfa80d369fb45a914aa8a3b4 "" + "/home/ljvm/texmf/tex/latex/graphics/trig.sty" 1454284088 3980 0a268fbfda01e381fa95821ab13b6aee "" + "/home/ljvm/texmf/tex/latex/oberdiek/epstopdf-base.sty" 1303254447 12029 04d7fdf76e0464c23b5aa3a727952d7c "" + "/home/ljvm/texmf/tex/latex/oberdiek/grfext.sty" 1335995445 7075 bd0c34fbf1ae8fd1debd2a554e41b2d5 "" + "/home/ljvm/texmf/tex/latex/oberdiek/kvoptions.sty" 1335995445 22417 c74ff4af6a1aa2b65d1924020edbbe11 "" + "/home/ljvm/texmf/tex/latex/preview/preview.sty" 1447630789 13687 0cb5888b46d12f19ed3b85a16d43470e "" + "/home/ljvm/texmf/tex/latex/preview/prtightpage.def" 1266794019 4841 763a1efd128d3821c07232f7b2638b7b "" + "/home/ljvm/texmf/tex/latex/standalone/standalone.cfg" 1437172902 902 f9a15737aea33182ec1f3542ca20dbfe "" + "/home/ljvm/texmf/tex/latex/standalone/standalone.cls" 1437172902 27291 b798d344bcba4430e7f7dc7561c517d5 "" + "/home/ljvm/texmf/tex/latex/tools/calc.sty" 1454284088 10214 d03d065f799d54f6b7e9b175f8d84279 "" + "/home/ljvm/texmf/tex/latex/xcolor/xcolor.sty" 1169481954 55224 a43bab84e0ac5e6efcaf9a98bde73a94 "" + "/usr/share/texlive/texmf-dist/fonts/map/fontname/texfonts.map" 1272929888 3287 e6b82fe08f5336d4d5ebc73fb1152e87 "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmmi6.tfm" 1136768653 1512 f21f83efb36853c0b70002322c1ab3ad "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmmi9.tfm" 1136768653 1524 d89e2d087a9828407a196f428428ef4a "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmr6.tfm" 1136768653 1300 b62933e007d01cfd073f79b963c01526 "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmr9.tfm" 1136768653 1292 6b21b9c2c7bebb38aa2273f7ca0fb3af "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmss10.tfm" 1136768653 1316 b636689f1933f24d1294acdf6041daaa "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmss9.tfm" 1136768653 1320 49357c421c0d469f88b867dd0c3d10e8 "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmssbx10.tfm" 1136768653 1272 e2d13f0df30bf3ad990bb9d028e37f34 "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmsy6.tfm" 1136768653 1116 933a60c408fc0a863a92debe84b2d294 "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmsy9.tfm" 1136768653 1116 25a7bf822c58caf309a702ef79f4afbb "" + "/usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmtt10.tfm" 1136768653 768 1321e9409b4137d6fb428ac9dc956269 "" + "/usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmss10.pfb" 1248133631 24457 5cbb7bdf209d5d1ce9892a9b80a307cc "" + "/usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmss9.pfb" 1248133631 24373 a91d375736817a75026663adcb2190c1 "" + "/usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmssbx10.pfb" 1248133631 28902 2f5c04fd2884d1878057baa5aad22765 "" + "/usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt10.pfb" 1248133631 31099 c85edf1dd5b9e826d67c9c7293b6786c "" + "/usr/share/texlive/texmf-dist/tex/context/base/supp-pdf.mkii" 1337017135 71627 94eb9990bed73c364d7f53f960cc8c5b "" + "/usr/share/texlive/texmf-dist/tex/generic/ifxetex/ifxetex.sty" 1284331290 1458 43ab4710dc82f3edeabecd0d099626b2 "" + "/usr/share/texlive/texmf-dist/tex/generic/oberdiek/ifluatex.sty" 1303254447 7324 11d14f318d865f420e692d4e6c9c18c3 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex" 1288312291 1006 b103be0bfc8c1682ff1fa9760697a329 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorearrows.code.tex" 1439074469 43226 167a99346bfe2676e3efcdde2d81fe45 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreexternal.code.tex" 1439074469 19302 4f089dc590e71f7331e6d5b5ea85273b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoregraphicstate.code.tex" 1439074469 6068 edae1e768a7d8d8f0f00e953d2b0153e "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreimage.code.tex" 1393459310 7041 a891ad72049e17c4e366c40ca37b0ccb "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorelayers.code.tex" 1393459310 4625 40c07e9f6f2f7c674704b3f2055560ce "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreobjects.code.tex" 1203877327 2631 7eefa6cdbefd8d4e2bad7262cf1094cd "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathconstruct.code.tex" 1393459310 43477 81143b33d9ebafdeead07ede13372427 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathprocessing.code.tex" 1393459310 17436 8d99d4113be311daf23deff86991ee7d "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex" 1439074469 20772 c57e34db4aa7b1da013169d04b743eac "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepatterns.code.tex" 1393459310 9641 711f0edc22c180a5caf168b6e8970057 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepoints.code.tex" 1393459310 34516 658a71478d21df554bce9d9cd436203a "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorequick.code.tex" 1288312291 3052 e5672c657232fd63b0a9853b0746297c "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorescopes.code.tex" 1439074469 16669 4ec6e40088fc6de6334b443fe2dc59f0 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreshade.code.tex" 1393459310 21541 4cd19f8ff7dd74d5aa7d803a6397af84 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransformations.code.tex" 1439074469 19998 d77fef95c7369827753d17fd11be19c4 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransparency.code.tex" 1393459310 8943 2e2495b057f8f0035b5568394d489963 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryarrows.code.tex" 1203727794 437 cf40f841f40822be6cb995f8b47112fd "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarybackgrounds.code.tex" 1393459310 4611 b858a4e5bd5442802c91a13027dc25bb "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarycalc.code.tex" 1393459310 15934 b941bd3ae7b33179029513707d1f0ff6 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.code.tex" 1393459310 5484 4bb4a5cbbd05d6f17a261b59dbd014f1 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.markings.code.tex" 1203727794 782 2479083eef1ef47450770d40ad81f937 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfadings.code.tex" 1288312291 1298 83d7449064b0f0f089f1898a244b6d16 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfit.code.tex" 1393459310 3725 36db4c06798413d051778705f3255eea "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarypositioning.code.tex" 1288312291 4034 0a8cd33cf30d262ec971380666acb2d0 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshadows.code.tex" 1203727794 3001 d54bab2f783098ed890fabbeb437b04f "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.arrows.code.tex" 1203727794 527 a8d3e34fbab3dc317cf9b06aa5cdc2e4 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.callouts.code.tex" 1203727794 1158 d6338189706f4587fbc6175c0fb41f17 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.code.tex" 1203727794 607 40dc15d3efcf10f095866a94bd544bc1 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.geometric.code.tex" 1203727794 457 ffe9f8b9d108b5f729fd86c78c63589a "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.misc.code.tex" 1203727794 447 e87a0add254801e837fa6c18f61f340f "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.multipart.code.tex" 1203727794 1004 86af66805a9d0b62bd41ea0796a64d50 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.symbols.code.tex" 1203727794 590 7e11000a24bbee9ae2a4cd0e5d88e58c "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarytopaths.code.tex" 1288312291 11599 d694704a88e2f9007c996d3a6a4d629c "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex" 1439074469 176652 1c2926908e2b356d454795c35385d580 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/decorations/pgflibrarydecorations.markings.code.tex" 1393459310 5181 c2b736d254ec36204f8fffd5a45bbd41 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryarrows.code.tex" 1393459310 31927 7acd27f90dd95ce67ad32166cd0b95ec "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryfadings.code.tex" 1203727794 2647 defb4a59c2a1d36127a1ac6eebb4a5c1 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex" 1393459310 32969 dbcfd5a7de6a0f7255c333ef60287d59 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.arrows.code.tex" 1288312291 69900 cbd9fafb795a493fb2a3b73713994b78 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.callouts.code.tex" 1393459310 28333 0189c4cfb5044e700e6ba65a32295f01 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.geometric.code.tex" 1288312291 132566 291d42c3b23fdb5c47e51b36a5fea0c4 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.misc.code.tex" 1393459310 37737 ea6cb0b4e615f6048f20ee7153b3cc78 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.multipart.code.tex" 1288312291 49891 e74f8181c57d9359c941b6bee48fccc2 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.symbols.code.tex" 1393459310 90791 0f3e73cae9286c96d9fcb2161cc223bc "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex" 1393459310 454 9e9e7c99f4da4f41698be21eaef4938e "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathcalc.code.tex" 1393459310 13416 940ea6971d7a65dc440d3479939c66ae "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfloat.code.tex" 1439074469 94097 62ac62cda46eb715560dc27f9ed6e8b1 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.base.code.tex" 1393459310 9375 5adc70f722abd29fc250d59e0694b548 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.basic.code.tex" 1439074469 22069 7c21c42b15718ce922f36235be360490 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.code.tex" 1439074469 8210 a7be5b52ef3d2c087b7dc3d52898b67e "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.comparison.code.tex" 1288312291 3534 c7f28fbac13616513e513efe93b8569b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.integerarithmetics.code.tex" 1393459310 3167 7c9394e79aac27db96a92f9b2792b858 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.misc.code.tex" 1439074469 9289 261407875b9dbb0194691c3eb893610f "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.random.code.tex" 1439074469 7078 946ddf4a7e57219b6afdbad98eb6731b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.round.code.tex" 1288312291 2688 139c6abc86761a6190c2f4bef5d752be "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.trigonometric.code.tex" 1439074469 92284 dcf023dbaa84e6c50e11c2f79fe8cfa6 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathparser.code.tex" 1439074469 35430 046e15fbb65e74d8f0e7945f99741fdb "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathutil.code.tex" 1393459310 7099 f44d505bae6c7c2b933cdd63441db4b9 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduledecorations.code.tex" 1393459310 71902 658cc1e13f73daec4225b8fc1c27600b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmodulematrix.code.tex" 1393459310 20934 2328bd2e04520e1ab077ac4ee13b8935 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduleplot.code.tex" 1439074469 16203 83cbe1220e389eeee283a6168f9a567b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduleshapes.code.tex" 1439074469 42906 d54376d96df1a2ae2d33fb722236d8e9 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgf.cfg" 1288312291 978 15af626ebd3d4d790aac19170dac04f2 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-common-pdf.def" 1393459310 5437 d91f93ed61ecdc57e119849b2d784a0b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-pdftex.def" 1439074469 13507 809d848d9262638e1b1705a68a73c566 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex" 1439074469 35113 2ccc50c1c9573e4bac9230d030f9c67c "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.code.tex" 1203877327 1983 b5994ebbcee17f1ba3d29bb1bd696fcf "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.code.tex" 1393459310 7881 d459d6057e13d10ce7a227ae44b7295e "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex" 1393459310 22211 d696ef78c12269178882d218b2cf191d "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex" 1393459310 36194 e194ef4e0b396b531a3891feb4b1cc22 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeysfiltered.code.tex" 1393459310 33377 af391d6ad1bfcbe2278e191f48e43ba8 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex" 1440888734 2536 a3b0529d815a2759ba157b56610a6377 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-common-lists.tex" 1393459310 6833 114eda2cf1d348e0e7e477a1a4dc1941 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-common.tex" 1439074469 16501 ab0135765e27b6b8dae047831fe84818 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-latex.def" 1439074469 5544 294baac9629ba59f675b1f2027ad7136 "" + "/usr/share/texlive/texmf-dist/tex/generic/xkeyval/keyval.tex" 1403829539 2725 fc34ef3ccb37ba15a640e8fca6190bca "" + "/usr/share/texlive/texmf-dist/tex/generic/xkeyval/xkeyval.tex" 1417732693 19231 26434a5656c684f5ffb1f26f98006baa "" + "/usr/share/texlive/texmf-dist/tex/generic/xkeyval/xkvutils.tex" 1403829539 7677 6f5ce7c1124cad7ec57d05b2562bd8fe "" + "/usr/share/texlive/texmf-dist/tex/latex/base/article.cls" 1454284088 20708 39fdf9e2fb65617012fa7382a351f485 "" + "/usr/share/texlive/texmf-dist/tex/latex/base/ifthen.sty" 1454284088 5159 a08c9bbd48fc492f15b22e458bef961f "" + "/usr/share/texlive/texmf-dist/tex/latex/base/size10.clo" 1454284088 9179 4cd3c5f593e63512893b8ac0123f1bd7 "" + "/usr/share/texlive/texmf-dist/tex/latex/latexconfig/color.cfg" 1254097189 802 7b8c8d72c24d795ed7720e4dfd29bff3 "" + "/usr/share/texlive/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg" 1279039959 678 4792914a8f45be57bb98413425e4c7af "" + "/usr/share/texlive/texmf-dist/tex/latex/latexconfig/graphics.cfg" 1278958963 3563 d35e897cae3b8c6848f6677b73370b54 "" + "/usr/share/texlive/texmf-dist/tex/latex/ms/everyshi.sty" 1177890616 3878 6aa7c08ff2621006e0603349e40a30a8 "" + "/usr/share/texlive/texmf-dist/tex/latex/pdftex-def/pdftex.def" 1306616590 55368 3c8a0d99822330f2dfabc0dfb09ce897 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf-blur/tikzlibraryshadows.blur.code.tex" 1335333685 7525 063a37c856ae2c38332d93e3d457a299 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty" 1439074469 1197 8a80cdde14696a9198f1793a55dcf332 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty" 1288312291 410 5bf12ea7330e5f12c445332a4fe9a263 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-0-65.sty" 1203877327 21115 facf03b7dbe5ea2f5f1dce1ac84b5d05 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-1-18.sty" 1203727794 1091 d9163d29def82ee90370c8a63667742c "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty" 1203877327 339 592cf35cba3d400082b8a9a5d0199d70 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/math/pgfmath.sty" 1393459310 306 0796eafca5e159e6ec2167a6d22d81b1 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty" 1393459310 443 0b2e781830192df35c0fd357cf13e26e "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgffor.sty" 1393459310 348 8927fde343487e003b01a4c2ca34073b "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfkeys.sty" 1203727794 274 4cad6e665cc93ac2ac979039a94fa1e1 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty" 1203877327 325 2bcd023400636339210573e2b3ee298b "" + "/usr/share/texlive/texmf-dist/tex/latex/pgfopts/pgfopts.sty" 1405118212 5540 d5c60cf09c59da351aa4023ed084e4eb "" + "/usr/share/texlive/texmf-dist/tex/latex/xkeyval/xkeyval.sty" 1417732693 4962 9c1069474ff71dbc47d5006555e352d3 "" + "/usr/share/texlive/texmf-dist/web2c/texmf.cnf" 1503343927 31343 93828589fb0cea665e553ee5a17ad2d4 "" + "/usr/share/texmf/web2c/texmf.cnf" 1503343927 31343 93828589fb0cea665e553ee5a17ad2d4 "" + "/var/lib/texmf/web2c/pdftex/pdflatex.fmt" 1528507587 718486 55e051f478d994e1e3e5b41625d996af "" + "optimization_loop.aux" 1528617882 8 a94a2480d3289e625eea47cd1b285758 "" + "optimization_loop.pdf" 1528617882 47217 3dc1b25bb3d586f2b98197f14580d0eb "pdflatex" + "optimization_loop.tex" 1528617876 3644 72cc6263136c7d13e98a24348e978a53 "" + "tikz-uml.sty" 1526185697 302703 ed2f9b5dd419a64f254ecbf6a6653c7f "" + (generated) + "optimization_loop.pdf" + "optimization_loop.log" + "optimization_loop.aux" diff --git a/docs/assets/optimization_loop.fls b/docs/assets/optimization_loop.fls new file mode 100644 index 00000000..6f8c18ec --- /dev/null +++ b/docs/assets/optimization_loop.fls @@ -0,0 +1,248 @@ +PWD /home/ljvm/Documents/Dev/pyswarms/docs/assets +INPUT /etc/texmf/web2c/texmf.cnf +INPUT /usr/share/texmf/web2c/texmf.cnf +INPUT /usr/share/texlive/texmf-dist/web2c/texmf.cnf +INPUT /var/lib/texmf/web2c/pdftex/pdflatex.fmt +INPUT optimization_loop.tex +OUTPUT optimization_loop.log +INPUT /home/ljvm/texmf/tex/latex/standalone/standalone.cls +INPUT /home/ljvm/texmf/tex/latex/standalone/standalone.cls +INPUT /usr/share/texlive/texmf-dist/tex/generic/oberdiek/ifluatex.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/oberdiek/ifluatex.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/oberdiek/ifluatex.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/ifpdf.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/ifpdf.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/ifpdf.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/ifxetex/ifxetex.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/ifxetex/ifxetex.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/ifxetex/ifxetex.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/xkeyval/xkeyval.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/xkeyval/xkeyval.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/xkeyval/xkeyval.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/xkeyval/xkvutils.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/xkeyval/keyval.tex +INPUT /dev/null +INPUT /home/ljvm/texmf/tex/latex/standalone/standalone.cfg +INPUT /home/ljvm/texmf/tex/latex/standalone/standalone.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/article.cls +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/article.cls +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/size10.clo +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/size10.clo +INPUT /home/ljvm/texmf/tex/latex/preview/preview.sty +INPUT /home/ljvm/texmf/tex/latex/preview/preview.sty +INPUT /home/ljvm/texmf/tex/latex/preview/prtightpage.def +INPUT /home/ljvm/texmf/tex/latex/preview/prtightpage.def +INPUT ./optimization_loop.pdf +INPUT optimization_loop.aux +INPUT optimization_loop.aux +INPUT ./optimization_loop.pdf +INPUT /home/ljvm/texmf/tex/latex/xcolor/xcolor.sty +INPUT /home/ljvm/texmf/tex/latex/xcolor/xcolor.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/color.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/color.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/pdftex-def/pdftex.def +INPUT /usr/share/texlive/texmf-dist/tex/latex/pdftex-def/pdftex.def +INPUT /home/ljvm/texmf/tex/generic/oberdiek/infwarerr.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/infwarerr.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/ltxcmds.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/ltxcmds.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/dvipsnam.def +INPUT /home/ljvm/texmf/tex/latex/graphics/dvipsnam.def +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-common.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-common-lists.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-latex.def +INPUT /usr/share/texlive/texmf-dist/tex/latex/ms/everyshi.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/ms/everyshi.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/graphicx.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/graphicx.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/graphics.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/graphics.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/trig.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/trig.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/graphics.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/graphics.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeysfiltered.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgf.cfg +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-pdftex.def +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-pdftex.def +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-common-pdf.def +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathcalc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathutil.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathparser.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.basic.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.trigonometric.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.random.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.comparison.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.base.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.round.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.misc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.integerarithmetics.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfloat.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepoints.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathconstruct.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorescopes.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoregraphicstate.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransformations.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorequick.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreobjects.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathprocessing.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorearrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreshade.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreimage.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreexternal.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorelayers.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransparency.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepatterns.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduleshapes.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduleplot.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-0-65.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-0-65.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-1-18.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-1-18.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgffor.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgffor.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfkeys.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfkeys.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/math/pgfmath.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/math/pgfmath.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmodulematrix.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarytopaths.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarytopaths.code.tex +INPUT tikz-uml.sty +INPUT tikz-uml.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/ifthen.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/ifthen.sty +INPUT /home/ljvm/texmf/tex/generic/xstring/xstring.sty +INPUT /home/ljvm/texmf/tex/generic/xstring/xstring.sty +INPUT /home/ljvm/texmf/tex/generic/xstring/xstring.tex +INPUT /home/ljvm/texmf/tex/latex/tools/calc.sty +INPUT /home/ljvm/texmf/tex/latex/tools/calc.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgfopts/pgfopts.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgfopts/pgfopts.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarybackgrounds.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarybackgrounds.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryarrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryarrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryarrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryarrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.geometric.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.geometric.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.geometric.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.geometric.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.misc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.misc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.misc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.misc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.symbols.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.symbols.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.symbols.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.symbols.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.arrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.arrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.arrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.arrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.callouts.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.callouts.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.callouts.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.callouts.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.multipart.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.multipart.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.multipart.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.multipart.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfit.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfit.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshadows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshadows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfadings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfadings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryfadings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryfadings.code.tex +OUTPUT optimization_loop.pdf +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.markings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.markings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduledecorations.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/decorations/pgflibrarydecorations.markings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/decorations/pgflibrarydecorations.markings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarypositioning.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarypositioning.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarycalc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarycalc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf-blur/tikzlibraryshadows.blur.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf-blur/tikzlibraryshadows.blur.code.tex +INPUT optimization_loop.aux +INPUT optimization_loop.aux +OUTPUT optimization_loop.aux +INPUT /usr/share/texlive/texmf-dist/fonts/map/fontname/texfonts.map +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmss10.tfm +INPUT /usr/share/texlive/texmf-dist/tex/context/base/supp-pdf.mkii +INPUT /usr/share/texlive/texmf-dist/tex/context/base/supp-pdf.mkii +INPUT /home/ljvm/texmf/tex/generic/oberdiek/pdftexcmds.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/pdftexcmds.sty +INPUT /home/ljvm/texmf/tex/latex/oberdiek/epstopdf-base.sty +INPUT /home/ljvm/texmf/tex/latex/oberdiek/epstopdf-base.sty +INPUT /home/ljvm/texmf/tex/latex/oberdiek/grfext.sty +INPUT /home/ljvm/texmf/tex/latex/oberdiek/grfext.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/kvdefinekeys.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/kvdefinekeys.sty +INPUT /home/ljvm/texmf/tex/latex/oberdiek/kvoptions.sty +INPUT /home/ljvm/texmf/tex/latex/oberdiek/kvoptions.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/kvsetkeys.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/kvsetkeys.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/etexcmds.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/etexcmds.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmtt10.tfm +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmss9.tfm +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmr9.tfm +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmr6.tfm +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmmi9.tfm +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmmi6.tfm +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmsy9.tfm +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmsy6.tfm +INPUT /usr/share/texlive/texmf-dist/fonts/tfm/public/cm/cmssbx10.tfm +INPUT /home/ljvm/.texmf-var/fonts/map/pdftex/updmap/pdftex.map +INPUT optimization_loop.aux +INPUT /usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmss10.pfb +INPUT /usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmss9.pfb +INPUT /usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmssbx10.pfb +INPUT /usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt10.pfb diff --git a/docs/assets/pyswarms_api.aux b/docs/assets/pyswarms_api.aux new file mode 100644 index 00000000..7c14129a --- /dev/null +++ b/docs/assets/pyswarms_api.aux @@ -0,0 +1,2 @@ +\relax +\gdef \sa@multi@numpages {0} diff --git a/docs/assets/pyswarms_api.fdb_latexmk b/docs/assets/pyswarms_api.fdb_latexmk new file mode 100644 index 00000000..447b804c --- /dev/null +++ b/docs/assets/pyswarms_api.fdb_latexmk @@ -0,0 +1,128 @@ +# Fdb version 3 +["pdflatex"] 1528611763 "pyswarms_api.tex" "pyswarms_api.pdf" "pyswarms_api" 1528611763 + "/dev/null" 1528595442 0 d41d8cd98f00b204e9800998ecf8427e "" + "/etc/texmf/web2c/texmf.cnf" 1515379388 475 c0e671620eb5563b2130f56340a5fde8 "" + "/home/ljvm/texmf/tex/generic/oberdiek/ifpdf.sty" 1303254447 7140 ece2cc23d9f20e1f53975ac167f42d3e "" + "/home/ljvm/texmf/tex/generic/oberdiek/infwarerr.sty" 1335995445 8253 3bdedc8409aa5d290a2339be6f09af03 "" + "/home/ljvm/texmf/tex/generic/oberdiek/ltxcmds.sty" 1335995445 18425 775b341047ce304520cc7c11ca41392e "" + "/home/ljvm/texmf/tex/generic/xstring/xstring.sty" 1269905706 144 0ca8d67b000b795a4d9ec000e0fd09c7 "" + "/home/ljvm/texmf/tex/generic/xstring/xstring.tex" 1381789620 54373 fd4487ae3e45d4074bc89aea1d2b6807 "" + "/home/ljvm/texmf/tex/latex/graphics/dvipsnam.def" 1454284088 4945 3b3a8face751255856839a3489e83341 "" + "/home/ljvm/texmf/tex/latex/graphics/graphics.sty" 1454284088 14337 b66dff1d80f6c21e70858a2b3c2d327d "" + "/home/ljvm/texmf/tex/latex/graphics/graphicx.sty" 1428932888 8125 557ab9f1bfa80d369fb45a914aa8a3b4 "" + "/home/ljvm/texmf/tex/latex/graphics/trig.sty" 1454284088 3980 0a268fbfda01e381fa95821ab13b6aee "" + "/home/ljvm/texmf/tex/latex/preview/preview.sty" 1447630789 13687 0cb5888b46d12f19ed3b85a16d43470e "" + "/home/ljvm/texmf/tex/latex/preview/prtightpage.def" 1266794019 4841 763a1efd128d3821c07232f7b2638b7b "" + "/home/ljvm/texmf/tex/latex/standalone/standalone.cfg" 1437172902 902 f9a15737aea33182ec1f3542ca20dbfe "" + "/home/ljvm/texmf/tex/latex/standalone/standalone.cls" 1437172902 27291 b798d344bcba4430e7f7dc7561c517d5 "" + "/home/ljvm/texmf/tex/latex/tools/calc.sty" 1454284088 10214 d03d065f799d54f6b7e9b175f8d84279 "" + "/home/ljvm/texmf/tex/latex/xcolor/xcolor.sty" 1169481954 55224 a43bab84e0ac5e6efcaf9a98bde73a94 "" + "/usr/share/texlive/texmf-dist/tex/generic/ifxetex/ifxetex.sty" 1284331290 1458 43ab4710dc82f3edeabecd0d099626b2 "" + "/usr/share/texlive/texmf-dist/tex/generic/oberdiek/ifluatex.sty" 1303254447 7324 11d14f318d865f420e692d4e6c9c18c3 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex" 1288312291 1006 b103be0bfc8c1682ff1fa9760697a329 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorearrows.code.tex" 1439074469 43226 167a99346bfe2676e3efcdde2d81fe45 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreexternal.code.tex" 1439074469 19302 4f089dc590e71f7331e6d5b5ea85273b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoregraphicstate.code.tex" 1439074469 6068 edae1e768a7d8d8f0f00e953d2b0153e "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreimage.code.tex" 1393459310 7041 a891ad72049e17c4e366c40ca37b0ccb "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorelayers.code.tex" 1393459310 4625 40c07e9f6f2f7c674704b3f2055560ce "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreobjects.code.tex" 1203877327 2631 7eefa6cdbefd8d4e2bad7262cf1094cd "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathconstruct.code.tex" 1393459310 43477 81143b33d9ebafdeead07ede13372427 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathprocessing.code.tex" 1393459310 17436 8d99d4113be311daf23deff86991ee7d "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex" 1439074469 20772 c57e34db4aa7b1da013169d04b743eac "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepatterns.code.tex" 1393459310 9641 711f0edc22c180a5caf168b6e8970057 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepoints.code.tex" 1393459310 34516 658a71478d21df554bce9d9cd436203a "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorequick.code.tex" 1288312291 3052 e5672c657232fd63b0a9853b0746297c "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorescopes.code.tex" 1439074469 16669 4ec6e40088fc6de6334b443fe2dc59f0 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreshade.code.tex" 1393459310 21541 4cd19f8ff7dd74d5aa7d803a6397af84 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransformations.code.tex" 1439074469 19998 d77fef95c7369827753d17fd11be19c4 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransparency.code.tex" 1393459310 8943 2e2495b057f8f0035b5568394d489963 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryarrows.code.tex" 1203727794 437 cf40f841f40822be6cb995f8b47112fd "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarybackgrounds.code.tex" 1393459310 4611 b858a4e5bd5442802c91a13027dc25bb "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.code.tex" 1393459310 5484 4bb4a5cbbd05d6f17a261b59dbd014f1 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.markings.code.tex" 1203727794 782 2479083eef1ef47450770d40ad81f937 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfadings.code.tex" 1288312291 1298 83d7449064b0f0f089f1898a244b6d16 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfit.code.tex" 1393459310 3725 36db4c06798413d051778705f3255eea "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshadows.code.tex" 1203727794 3001 d54bab2f783098ed890fabbeb437b04f "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.arrows.code.tex" 1203727794 527 a8d3e34fbab3dc317cf9b06aa5cdc2e4 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.callouts.code.tex" 1203727794 1158 d6338189706f4587fbc6175c0fb41f17 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.code.tex" 1203727794 607 40dc15d3efcf10f095866a94bd544bc1 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.geometric.code.tex" 1203727794 457 ffe9f8b9d108b5f729fd86c78c63589a "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.misc.code.tex" 1203727794 447 e87a0add254801e837fa6c18f61f340f "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.multipart.code.tex" 1203727794 1004 86af66805a9d0b62bd41ea0796a64d50 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.symbols.code.tex" 1203727794 590 7e11000a24bbee9ae2a4cd0e5d88e58c "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarytopaths.code.tex" 1288312291 11599 d694704a88e2f9007c996d3a6a4d629c "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex" 1439074469 176652 1c2926908e2b356d454795c35385d580 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/decorations/pgflibrarydecorations.markings.code.tex" 1393459310 5181 c2b736d254ec36204f8fffd5a45bbd41 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryarrows.code.tex" 1393459310 31927 7acd27f90dd95ce67ad32166cd0b95ec "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryfadings.code.tex" 1203727794 2647 defb4a59c2a1d36127a1ac6eebb4a5c1 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex" 1393459310 32969 dbcfd5a7de6a0f7255c333ef60287d59 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.arrows.code.tex" 1288312291 69900 cbd9fafb795a493fb2a3b73713994b78 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.callouts.code.tex" 1393459310 28333 0189c4cfb5044e700e6ba65a32295f01 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.geometric.code.tex" 1288312291 132566 291d42c3b23fdb5c47e51b36a5fea0c4 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.misc.code.tex" 1393459310 37737 ea6cb0b4e615f6048f20ee7153b3cc78 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.multipart.code.tex" 1288312291 49891 e74f8181c57d9359c941b6bee48fccc2 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.symbols.code.tex" 1393459310 90791 0f3e73cae9286c96d9fcb2161cc223bc "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex" 1393459310 454 9e9e7c99f4da4f41698be21eaef4938e "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathcalc.code.tex" 1393459310 13416 940ea6971d7a65dc440d3479939c66ae "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfloat.code.tex" 1439074469 94097 62ac62cda46eb715560dc27f9ed6e8b1 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.base.code.tex" 1393459310 9375 5adc70f722abd29fc250d59e0694b548 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.basic.code.tex" 1439074469 22069 7c21c42b15718ce922f36235be360490 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.code.tex" 1439074469 8210 a7be5b52ef3d2c087b7dc3d52898b67e "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.comparison.code.tex" 1288312291 3534 c7f28fbac13616513e513efe93b8569b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.integerarithmetics.code.tex" 1393459310 3167 7c9394e79aac27db96a92f9b2792b858 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.misc.code.tex" 1439074469 9289 261407875b9dbb0194691c3eb893610f "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.random.code.tex" 1439074469 7078 946ddf4a7e57219b6afdbad98eb6731b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.round.code.tex" 1288312291 2688 139c6abc86761a6190c2f4bef5d752be "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.trigonometric.code.tex" 1439074469 92284 dcf023dbaa84e6c50e11c2f79fe8cfa6 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathparser.code.tex" 1439074469 35430 046e15fbb65e74d8f0e7945f99741fdb "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathutil.code.tex" 1393459310 7099 f44d505bae6c7c2b933cdd63441db4b9 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduledecorations.code.tex" 1393459310 71902 658cc1e13f73daec4225b8fc1c27600b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmodulematrix.code.tex" 1393459310 20934 2328bd2e04520e1ab077ac4ee13b8935 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduleplot.code.tex" 1439074469 16203 83cbe1220e389eeee283a6168f9a567b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduleshapes.code.tex" 1439074469 42906 d54376d96df1a2ae2d33fb722236d8e9 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgf.cfg" 1288312291 978 15af626ebd3d4d790aac19170dac04f2 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-common-pdf.def" 1393459310 5437 d91f93ed61ecdc57e119849b2d784a0b "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-pdftex.def" 1439074469 13507 809d848d9262638e1b1705a68a73c566 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex" 1439074469 35113 2ccc50c1c9573e4bac9230d030f9c67c "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.code.tex" 1203877327 1983 b5994ebbcee17f1ba3d29bb1bd696fcf "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.code.tex" 1393459310 7881 d459d6057e13d10ce7a227ae44b7295e "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex" 1393459310 22211 d696ef78c12269178882d218b2cf191d "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex" 1393459310 36194 e194ef4e0b396b531a3891feb4b1cc22 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeysfiltered.code.tex" 1393459310 33377 af391d6ad1bfcbe2278e191f48e43ba8 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex" 1440888734 2536 a3b0529d815a2759ba157b56610a6377 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-common-lists.tex" 1393459310 6833 114eda2cf1d348e0e7e477a1a4dc1941 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-common.tex" 1439074469 16501 ab0135765e27b6b8dae047831fe84818 "" + "/usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-latex.def" 1439074469 5544 294baac9629ba59f675b1f2027ad7136 "" + "/usr/share/texlive/texmf-dist/tex/generic/xkeyval/keyval.tex" 1403829539 2725 fc34ef3ccb37ba15a640e8fca6190bca "" + "/usr/share/texlive/texmf-dist/tex/generic/xkeyval/xkeyval.tex" 1417732693 19231 26434a5656c684f5ffb1f26f98006baa "" + "/usr/share/texlive/texmf-dist/tex/generic/xkeyval/xkvutils.tex" 1403829539 7677 6f5ce7c1124cad7ec57d05b2562bd8fe "" + "/usr/share/texlive/texmf-dist/tex/latex/base/article.cls" 1454284088 20708 39fdf9e2fb65617012fa7382a351f485 "" + "/usr/share/texlive/texmf-dist/tex/latex/base/ifthen.sty" 1454284088 5159 a08c9bbd48fc492f15b22e458bef961f "" + "/usr/share/texlive/texmf-dist/tex/latex/base/size10.clo" 1454284088 9179 4cd3c5f593e63512893b8ac0123f1bd7 "" + "/usr/share/texlive/texmf-dist/tex/latex/latexconfig/color.cfg" 1254097189 802 7b8c8d72c24d795ed7720e4dfd29bff3 "" + "/usr/share/texlive/texmf-dist/tex/latex/latexconfig/graphics.cfg" 1278958963 3563 d35e897cae3b8c6848f6677b73370b54 "" + "/usr/share/texlive/texmf-dist/tex/latex/ms/everyshi.sty" 1177890616 3878 6aa7c08ff2621006e0603349e40a30a8 "" + "/usr/share/texlive/texmf-dist/tex/latex/pdftex-def/pdftex.def" 1306616590 55368 3c8a0d99822330f2dfabc0dfb09ce897 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty" 1439074469 1197 8a80cdde14696a9198f1793a55dcf332 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty" 1288312291 410 5bf12ea7330e5f12c445332a4fe9a263 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-0-65.sty" 1203877327 21115 facf03b7dbe5ea2f5f1dce1ac84b5d05 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-1-18.sty" 1203727794 1091 d9163d29def82ee90370c8a63667742c "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty" 1203877327 339 592cf35cba3d400082b8a9a5d0199d70 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/math/pgfmath.sty" 1393459310 306 0796eafca5e159e6ec2167a6d22d81b1 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty" 1393459310 443 0b2e781830192df35c0fd357cf13e26e "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgffor.sty" 1393459310 348 8927fde343487e003b01a4c2ca34073b "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfkeys.sty" 1203727794 274 4cad6e665cc93ac2ac979039a94fa1e1 "" + "/usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty" 1203877327 325 2bcd023400636339210573e2b3ee298b "" + "/usr/share/texlive/texmf-dist/tex/latex/pgfopts/pgfopts.sty" 1405118212 5540 d5c60cf09c59da351aa4023ed084e4eb "" + "/usr/share/texlive/texmf-dist/tex/latex/xkeyval/xkeyval.sty" 1417732693 4962 9c1069474ff71dbc47d5006555e352d3 "" + "/usr/share/texlive/texmf-dist/web2c/texmf.cnf" 1503343927 31343 93828589fb0cea665e553ee5a17ad2d4 "" + "/usr/share/texmf/web2c/texmf.cnf" 1503343927 31343 93828589fb0cea665e553ee5a17ad2d4 "" + "/var/lib/texmf/web2c/pdftex/pdflatex.fmt" 1528507587 718486 55e051f478d994e1e3e5b41625d996af "" + "pyswarms_api.aux" 1528611763 8 a94a2480d3289e625eea47cd1b285758 "" + "pyswarms_api.pdf" 1528611763 28449 bf8aa2f620d27c4141ae07f6a16f679b "pdflatex" + "pyswarms_api.tex" 1528611725 3224 d99518edfd37f8be7c602d2ec9643998 "" + "tikz-uml.sty" 1526185697 302703 ed2f9b5dd419a64f254ecbf6a6653c7f "" + (generated) + "pyswarms_api.log" + "pyswarms_api.pdf" + "pyswarms_api.aux" diff --git a/docs/assets/pyswarms_api.fls b/docs/assets/pyswarms_api.fls new file mode 100644 index 00000000..8b96c082 --- /dev/null +++ b/docs/assets/pyswarms_api.fls @@ -0,0 +1,204 @@ +PWD /home/ljvm/Documents/Dev/pyswarms/docs/assets +INPUT /etc/texmf/web2c/texmf.cnf +INPUT /usr/share/texmf/web2c/texmf.cnf +INPUT /usr/share/texlive/texmf-dist/web2c/texmf.cnf +INPUT /var/lib/texmf/web2c/pdftex/pdflatex.fmt +INPUT pyswarms_api.tex +OUTPUT pyswarms_api.log +INPUT /home/ljvm/texmf/tex/latex/standalone/standalone.cls +INPUT /home/ljvm/texmf/tex/latex/standalone/standalone.cls +INPUT /usr/share/texlive/texmf-dist/tex/generic/oberdiek/ifluatex.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/oberdiek/ifluatex.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/oberdiek/ifluatex.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/ifpdf.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/ifpdf.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/ifpdf.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/ifxetex/ifxetex.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/ifxetex/ifxetex.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/ifxetex/ifxetex.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/xkeyval/xkeyval.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/xkeyval/xkeyval.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/xkeyval/xkeyval.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/xkeyval/xkvutils.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/xkeyval/keyval.tex +INPUT /dev/null +INPUT /home/ljvm/texmf/tex/latex/standalone/standalone.cfg +INPUT /home/ljvm/texmf/tex/latex/standalone/standalone.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/article.cls +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/article.cls +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/size10.clo +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/size10.clo +INPUT /home/ljvm/texmf/tex/latex/preview/preview.sty +INPUT /home/ljvm/texmf/tex/latex/preview/preview.sty +INPUT /home/ljvm/texmf/tex/latex/preview/prtightpage.def +INPUT /home/ljvm/texmf/tex/latex/preview/prtightpage.def +INPUT ./pyswarms_api.pdf +INPUT pyswarms_api.aux +INPUT pyswarms_api.aux +INPUT ./pyswarms_api.pdf +INPUT /home/ljvm/texmf/tex/latex/xcolor/xcolor.sty +INPUT /home/ljvm/texmf/tex/latex/xcolor/xcolor.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/color.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/color.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/pdftex-def/pdftex.def +INPUT /usr/share/texlive/texmf-dist/tex/latex/pdftex-def/pdftex.def +INPUT /home/ljvm/texmf/tex/generic/oberdiek/infwarerr.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/infwarerr.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/ltxcmds.sty +INPUT /home/ljvm/texmf/tex/generic/oberdiek/ltxcmds.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/dvipsnam.def +INPUT /home/ljvm/texmf/tex/latex/graphics/dvipsnam.def +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-common.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-common-lists.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfutil-latex.def +INPUT /usr/share/texlive/texmf-dist/tex/latex/ms/everyshi.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/ms/everyshi.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/graphicx.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/graphicx.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/graphics.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/graphics.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/trig.sty +INPUT /home/ljvm/texmf/tex/latex/graphics/trig.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/graphics.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/latexconfig/graphics.cfg +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeysfiltered.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgf.cfg +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-pdftex.def +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-pdftex.def +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-common-pdf.def +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathcalc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathutil.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathparser.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.basic.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.trigonometric.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.random.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.comparison.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.base.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.round.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.misc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.integerarithmetics.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmathfloat.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepoints.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathconstruct.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorescopes.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoregraphicstate.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransformations.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorequick.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreobjects.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathprocessing.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorearrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreshade.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreimage.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreexternal.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorelayers.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransparency.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepatterns.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduleshapes.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduleplot.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-0-65.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-0-65.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-1-18.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-1-18.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgffor.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgffor.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfkeys.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/utilities/pgfkeys.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/math/pgfmath.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgf/math/pgfmath.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmodulematrix.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarytopaths.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarytopaths.code.tex +INPUT tikz-uml.sty +INPUT tikz-uml.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/ifthen.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/base/ifthen.sty +INPUT /home/ljvm/texmf/tex/generic/xstring/xstring.sty +INPUT /home/ljvm/texmf/tex/generic/xstring/xstring.sty +INPUT /home/ljvm/texmf/tex/generic/xstring/xstring.tex +INPUT /home/ljvm/texmf/tex/latex/tools/calc.sty +INPUT /home/ljvm/texmf/tex/latex/tools/calc.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgfopts/pgfopts.sty +INPUT /usr/share/texlive/texmf-dist/tex/latex/pgfopts/pgfopts.sty +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarybackgrounds.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarybackgrounds.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryarrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryarrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryarrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryarrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.geometric.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.geometric.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.geometric.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.geometric.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.misc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.misc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.misc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.misc.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.symbols.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.symbols.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.symbols.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.symbols.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.arrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.arrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.arrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.arrows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.callouts.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.callouts.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.callouts.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.callouts.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.multipart.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshapes.multipart.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.multipart.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/shapes/pgflibraryshapes.multipart.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfit.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfit.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshadows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryshadows.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfadings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibraryfadings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryfadings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/pgflibraryfadings.code.tex +OUTPUT pyswarms_api.pdf +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.markings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.markings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarydecorations.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/modules/pgfmoduledecorations.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/decorations/pgflibrarydecorations.markings.code.tex +INPUT /usr/share/texlive/texmf-dist/tex/generic/pgf/libraries/decorations/pgflibrarydecorations.markings.code.tex diff --git a/docs/dev.optimizer.rst b/docs/dev.optimizer.rst index fa8f609a..47365c59 100644 --- a/docs/dev.optimizer.rst +++ b/docs/dev.optimizer.rst @@ -94,14 +94,19 @@ This ensures that it will be automatically initialized when the whole library is Writing unit tests ------------------ -Testing is an important element of developing PySwarms, and we wanted -everything to be as smooth as possible, especially when doing the build and -integrating. In this case, we provide the :code:`tests` module in the -package. In case you add a test for your optimizer, simply name them with the -same convention as in those tests. +Testing is an important element of developing PySwarms and we want +everything to be as smooth as possible. Especially, when working on +the build and integrating new features. In this case, we provide the +:code:`tests` module in the package. For writing the test, we use the +:code:`pytest` module. In case you add a test for your optimizer, +use the same naming conventions that were used in the existing ones. You can perform separate checks by .. code-block:: shell - $ python -m unittest tests.optimizers. + $ python -m pytest tests.optimizers. + +For more details on running the tests `see here`_. + +.. _see here: https://docs.pytest.org/en/latest/usage.html diff --git a/docs/examples/basic_optimization.rst b/docs/examples/basic_optimization.rst index 2ff04523..6de5d997 100644 --- a/docs/examples/basic_optimization.rst +++ b/docs/examples/basic_optimization.rst @@ -2,12 +2,29 @@ Basic Optimization ================== -In this example, we'll be performing a simple optimization of +In this example, we’ll be performing a simple optimization of single-objective functions using the global-best optimizer in ``pyswarms.single.GBestPSO`` and the local-best optimizer in ``pyswarms.single.LBestPSO``. This aims to demonstrate the basic capabilities of the library when applied to benchmark problems. +.. code-block:: python + + import sys + # Change directory to access the pyswarms module + sys.path.append('../') + +.. code-block:: python + + print('Running on Python version: {}'.format(sys.version)) + + +.. parsed-literal:: + + Running on Python version: 3.6.3 |Anaconda custom (64-bit)| (default, Oct 13 2017, 12:02:49) + [GCC 7.2.0] + + .. code-block:: python # Import modules @@ -25,12 +42,12 @@ capabilities of the library when applied to benchmark problems. Optimizing a function --------------------- -First, let's start by optimizing the sphere function. Recall that the +First, let’s start by optimizing the sphere function. Recall that the minima of this function can be located at ``f(0,0..,0)`` with a value of -``0``. In case you don't remember the characteristics of a given +``0``. In case you don’t remember the characteristics of a given function, simply call ``help()``. -For now let's just set some arbitrary parameters in our optimizers. +For now let’s just set some arbitrary parameters in our optimizers. There are, at minimum, three steps to perform optimization: 1. Set the hyperparameters to configure the swarm as a ``dict``. @@ -46,6 +63,7 @@ several variables at once. .. code-block:: python + %%time # Set-up hyperparameters options = {'c1': 0.5, 'c2': 0.3, 'w':0.9} @@ -58,21 +76,27 @@ several variables at once. .. parsed-literal:: - Iteration 1/1000, cost: 0.215476174296 - Iteration 101/1000, cost: 5.26998280059e-07 - Iteration 201/1000, cost: 1.31313801471e-11 - Iteration 301/1000, cost: 1.63948780036e-15 - Iteration 401/1000, cost: 2.72294062778e-19 - Iteration 501/1000, cost: 3.69002488955e-22 - Iteration 601/1000, cost: 3.13387805277e-27 - Iteration 701/1000, cost: 1.65106278625e-30 - Iteration 801/1000, cost: 6.95403958989e-35 - Iteration 901/1000, cost: 1.33520105208e-41 - ================================ + INFO:pyswarms.single.global_best:Iteration 1/1000, cost: 0.11075768527574707 + INFO:pyswarms.single.global_best:Iteration 101/1000, cost: 7.521863508083004e-08 + INFO:pyswarms.single.global_best:Iteration 201/1000, cost: 2.8159915186067273e-11 + INFO:pyswarms.single.global_best:Iteration 301/1000, cost: 8.794923638889175e-17 + INFO:pyswarms.single.global_best:Iteration 401/1000, cost: 1.4699516547190895e-21 + INFO:pyswarms.single.global_best:Iteration 501/1000, cost: 5.111264897313781e-23 + INFO:pyswarms.single.global_best:Iteration 601/1000, cost: 8.329697430155943e-27 + INFO:pyswarms.single.global_best:Iteration 701/1000, cost: 1.662161785541961e-30 + INFO:pyswarms.single.global_best:Iteration 801/1000, cost: 6.140424420222279e-34 + INFO:pyswarms.single.global_best:Iteration 901/1000, cost: 2.0523902169204634e-39 + INFO:pyswarms.single.global_best:================================ Optimization finished! Final cost: 0.0000 - Best value: [9.4634973546019334e-23, 1.7011045174312443e-22] + Best value: [-2.431421462417008e-22, -9.502018378214418e-23] + + +.. parsed-literal:: + + CPU times: user 144 ms, sys: 14.8 ms, total: 159 ms + Wall time: 151 ms We can see that the optimizer was able to find a good minima as shown @@ -80,10 +104,11 @@ above. You can control the verbosity of the output using the ``verbose`` argument, and the number of steps to be printed out using the ``print_step`` argument. -Now, let's try this one using local-best PSO: +Now, let’s try this one using local-best PSO: .. code-block:: python + %%time # Set-up hyperparameters options = {'c1': 0.5, 'c2': 0.3, 'w':0.9, 'k': 2, 'p': 2} @@ -96,36 +121,42 @@ Now, let's try this one using local-best PSO: .. parsed-literal:: - Iteration 1/1000, cost: 0.0573032190292 - Iteration 101/1000, cost: 8.92699853837e-07 - Iteration 201/1000, cost: 4.56513550671e-10 - Iteration 301/1000, cost: 2.35083665314e-16 - Iteration 401/1000, cost: 8.09981989467e-20 - Iteration 501/1000, cost: 2.58846774519e-22 - Iteration 601/1000, cost: 3.33919326611e-26 - Iteration 701/1000, cost: 2.15052800954e-30 - Iteration 801/1000, cost: 1.09638832057e-33 - Iteration 901/1000, cost: 3.92671836329e-38 - ================================ + INFO:pyswarms.single.local_best:Iteration 1/1000, cost: 0.01379181672220725 + INFO:pyswarms.single.local_best:Iteration 101/1000, cost: 2.084056061999154e-07 + INFO:pyswarms.single.local_best:Iteration 201/1000, cost: 9.44588224259351e-10 + INFO:pyswarms.single.local_best:Iteration 301/1000, cost: 1.5414149511766008e-13 + INFO:pyswarms.single.local_best:Iteration 401/1000, cost: 3.283944854760787e-16 + INFO:pyswarms.single.local_best:Iteration 501/1000, cost: 2.093366830537641e-20 + INFO:pyswarms.single.local_best:Iteration 601/1000, cost: 5.0279508047072096e-24 + INFO:pyswarms.single.local_best:Iteration 701/1000, cost: 1.0492646748670006e-27 + INFO:pyswarms.single.local_best:Iteration 801/1000, cost: 2.2616819643931453e-29 + INFO:pyswarms.single.local_best:Iteration 901/1000, cost: 8.48269618909152e-35 + INFO:pyswarms.single.local_best:================================ Optimization finished! Final cost: 0.0000 - Best value: [1.4149803165668767e-21, -9.9189063589743749e-24] + Best value: [2.122881378865588e-18, -5.35447408455737e-19] +.. parsed-literal:: + + CPU times: user 355 ms, sys: 4.36 ms, total: 359 ms + Wall time: 353 ms + + Optimizing a function with bounds --------------------------------- Another thing that we can do is to set some bounds into our solution, so as to contain our candidate solutions within a specific range. We can do this simply by passing a ``bounds`` parameter, of type ``tuple``, when -creating an instance of our swarm. Let's try this using the global-best +creating an instance of our swarm. Let’s try this using the global-best PSO with the Rastrigin function (``rastrigin_func`` in ``pyswarms.utils.functions.single_obj``). Recall that the Rastrigin function is bounded within ``[-5.12, 5.12]``. If we pass an unbounded swarm into this function, then a ``ValueError`` -might be raised. So what we'll do is to create a bound within the +might be raised. So what we’ll do is to create a bound within the specified range. There are some things to remember when specifying a bound: @@ -136,7 +167,7 @@ bound: than the ``min_bound``. Their shapes should match the dimensions of the swarm. -What we'll do now is to create a 10-particle, 2-dimensional swarm. This +What we’ll do now is to create a 10-particle, 2-dimensional swarm. This means that we have to set our maximum and minimum boundaries with the shape of 2. In case we want to initialize an n-dimensional swarm, we then have to set our bounds with the same shape n. A fast workaround for @@ -152,6 +183,7 @@ constant. .. code-block:: python + %%time # Initialize swarm options = {'c1': 0.5, 'c2': 0.3, 'w':0.9} @@ -164,19 +196,162 @@ constant. .. parsed-literal:: - Iteration 1/1000, cost: 6.93571097813 - Iteration 101/1000, cost: 0.00614705911661 - Iteration 201/1000, cost: 7.22876336567e-09 - Iteration 301/1000, cost: 5.89750470681e-13 - Iteration 401/1000, cost: 0.0 - Iteration 501/1000, cost: 0.0 - Iteration 601/1000, cost: 0.0 - Iteration 701/1000, cost: 0.0 - Iteration 801/1000, cost: 0.0 - Iteration 901/1000, cost: 0.0 - ================================ + INFO:pyswarms.single.global_best:Iteration 1/1000, cost: 12.243865048066269 + INFO:pyswarms.single.global_best:Iteration 101/1000, cost: 1.1759164022634394 + INFO:pyswarms.single.global_best:Iteration 201/1000, cost: 0.9949603350768896 + INFO:pyswarms.single.global_best:Iteration 301/1000, cost: 0.9949590581556009 + INFO:pyswarms.single.global_best:Iteration 401/1000, cost: 0.9949590570934177 + INFO:pyswarms.single.global_best:Iteration 501/1000, cost: 0.9949590570932898 + INFO:pyswarms.single.global_best:Iteration 601/1000, cost: 0.9949590570932898 + INFO:pyswarms.single.global_best:Iteration 701/1000, cost: 0.9949590570932898 + INFO:pyswarms.single.global_best:Iteration 801/1000, cost: 0.9949590570932898 + INFO:pyswarms.single.global_best:Iteration 901/1000, cost: 0.9949590570932898 + INFO:pyswarms.single.global_best:================================ + Optimization finished! + Final cost: 0.9950 + Best value: [3.5850411183743393e-09, -0.9949586379966202] + + + +.. parsed-literal:: + + CPU times: user 213 ms, sys: 7.55 ms, total: 221 ms + Wall time: 210 ms + + +Basic Optimization with Arguments +--------------------------------- + +Here, we will run a basic optimization using an objective function that +needs parameterization. We will use the ``single.GBestPSO`` and a +version of the rosenbrock function to demonstrate + +.. code-block:: python + + import sys + # change directory to access pyswarms + sys.path.append('../') + + print("Running Python {}".format(sys.version)) + + +.. parsed-literal:: + + Running Python 3.5.2 |Anaconda custom (64-bit)| (default, Jul 2 2016, 17:53:06) + [GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] + + +.. code-block:: python + + # import modules + import numpy as np + + # create a parameterized version of the classic Rosenbrock unconstrained optimzation function + def rosenbrock_with_args(x, a, b, c=0): + + f = (a - x[:, 0]) ** 2 + b * (x[:, 1] - x[:, 0] ** 2) ** 2 + c + return f + +Using Arguments +~~~~~~~~~~~~~~~ + +Arguments can either be passed in using a tuple or a dictionary, using +the ``kwargs={}`` paradigm. First lets optimize the Rosenbrock function +using keyword arguments. Note in the definition of the Rosenbrock +function above, there were two arguments that need to be passed other +than the design variables, and one optional keyword argument, ``a``, +``b``, and ``c``, respectively + +.. code-block:: python + + from pyswarms.single.global_best import GlobalBestPSO + + # instatiate the optimizer + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9} + optimizer = GlobalBestPSO(n_particles=10, dimensions=2, options=options, bounds=bounds) + + # now run the optimization, pass a=1 and b=100 as a tuple assigned to args + + cost, pos = optimizer.optimize(rosenbrock_with_args, 1000, print_step=100, verbose=3, a=1, b=100, c=0) + + +.. parsed-literal:: + + INFO:pyswarms.single.global_best:Arguments Passed to Objective Function: {'c': 0, 'b': 100, 'a': 1} + INFO:pyswarms.single.global_best:Iteration 1/1000, cost: 1022.9667801907804 + INFO:pyswarms.single.global_best:Iteration 101/1000, cost: 0.0011172801146408992 + INFO:pyswarms.single.global_best:Iteration 201/1000, cost: 7.845605970774126e-07 + INFO:pyswarms.single.global_best:Iteration 301/1000, cost: 1.313503109901238e-09 + INFO:pyswarms.single.global_best:Iteration 401/1000, cost: 5.187079604907219e-10 + INFO:pyswarms.single.global_best:Iteration 501/1000, cost: 1.0115283486088853e-10 + INFO:pyswarms.single.global_best:Iteration 601/1000, cost: 2.329870757208421e-13 + INFO:pyswarms.single.global_best:Iteration 701/1000, cost: 4.826176894160183e-15 + INFO:pyswarms.single.global_best:Iteration 801/1000, cost: 3.125715456651088e-17 + INFO:pyswarms.single.global_best:Iteration 901/1000, cost: 1.4236768129666014e-19 + INFO:pyswarms.single.global_best:================================ + Optimization finished! + Final cost: 0.0000 + Best value: [0.99999999996210465, 0.9999999999218413] + + + +It is also possible to pass a dictionary of key word arguments by using +``**`` decorator when passing the dict + +.. code-block:: python + + kwargs={"a": 1.0, "b": 100.0, 'c':0} + cost, pos = optimizer.optimize(rosenbrock_with_args, 1000, print_step=100, verbose=3, **kwargs) + + +.. parsed-literal:: + + INFO:pyswarms.single.global_best:Arguments Passed to Objective Function: {'c': 0, 'b': 100.0, 'a': 1.0} + INFO:pyswarms.single.global_best:Iteration 1/1000, cost: 1.996797703363527e-21 + INFO:pyswarms.single.global_best:Iteration 101/1000, cost: 1.0061676299213387e-24 + INFO:pyswarms.single.global_best:Iteration 201/1000, cost: 4.8140236741112245e-28 + INFO:pyswarms.single.global_best:Iteration 301/1000, cost: 2.879342304056693e-29 + INFO:pyswarms.single.global_best:Iteration 401/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 501/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 601/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 701/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 801/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 901/1000, cost: 0.0 + INFO:pyswarms.single.global_best:================================ Optimization finished! Final cost: 0.0000 - Best value: [-6.763954278218746e-11, 2.4565912679296225e-09] + Best value: [1.0, 1.0] + + +Any key word arguments in the objective function can be left out as they +will be passed the default as defined in the prototype. Note here, ``c`` +is not passed into the function. +.. code-block:: python + + cost, pos = optimizer.optimize(rosenbrock_with_args, 1000, print_step=100, verbose=3, a=1, b=100) + + +.. parsed-literal:: + + INFO:pyswarms.single.global_best:Arguments Passed to Objective Function: {'b': 100, 'a': 1} + INFO:pyswarms.single.global_best:Iteration 1/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 101/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 201/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 301/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 401/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 501/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 601/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 701/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 801/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 901/1000, cost: 0.0 + INFO:pyswarms.single.global_best:================================ + Optimization finished! + Final cost: 0.0000 + Best value: [1.0, 1.0] + + diff --git a/docs/examples/inverse_kinematics.rst b/docs/examples/inverse_kinematics.rst new file mode 100644 index 00000000..f5bea9dc --- /dev/null +++ b/docs/examples/inverse_kinematics.rst @@ -0,0 +1,322 @@ +Solving the Inverse Kinematics problem using Particle Swarm Optimization +======================================================================== + +In this example, we are going to use the ``pyswarms`` library to solve a +6-DOF (Degrees of Freedom) Inverse Kinematics (IK) problem by treating +it as an optimization problem. We will use the ``pyswarms`` library to +find an *optimal* solution from a set of candidate solutions. + +.. code:: python + + import sys + # Change directory to access the pyswarms module + sys.path.append('../') + +.. code:: python + + print('Running on Python version: {}'.format(sys.version)) + + +.. parsed-literal:: + + Running on Python version: 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 07:18:10) [MSC v.1900 32 bit (Intel)] + + +.. code:: python + + # Import modules + import numpy as np + + # Import PySwarms + import pyswarms as ps + + # Some more magic so that the notebook will reload external python modules; + # see http://stackoverflow.com/questions/1907993/autoreload-of-modules-in-ipython + %load_ext autoreload + %autoreload 2 + +.. code:: python + + %%html + + # Styling for the text below + + + +.. raw:: html + + + # Styling for the text below + + +Introduction +============ + +Inverse Kinematics is one of the most challenging problems in robotics. +The problem involves finding an optimal *pose* for a manipulator given +the position of the end-tip effector as opposed to forward kinematics, +where the end-tip position is sought given the pose or joint +configuration. Normally, this position is expressed as a point in a +coordinate system (e.g., in a Cartesian system with :math:`x`, :math:`y` +and :math:`z` coordinates). However, the pose of the manipulator can +also be expressed as the collection of joint variables that describe the +angle of bending or twist (in revolute joints) or length of extension +(in prismatic joints). + +IK is particularly difficult because an abundance of solutions can +arise. Intuitively, one can imagine that a robotic arm can have multiple +ways of reaching through a certain point. It’s the same when you touch +the table and move your arm without moving the point you’re touching the +table at. Moreover, the calculation of these positions can be very +difficult. Simple solutions can be found for 3-DOF manipulators but +trying to solve the problem for 6 or even more DOF can lead to +challenging algebraic problems. + +IK as an Optimization Problem +============================= + +In this implementation, we are going to use a *6-DOF Stanford +Manipulator* with 5 revolute joints and 1 prismatic joint. Furthermore, +the constraints of the joints are going to be as follows: + ++------------------+--------------------------+-------------------------+ +| Parameters | Lower Boundary | Upper Boundary | ++==================+==========================+=========================+ +| :math:`\theta_1` | :math:`-\pi` | :math:`\pi` | ++------------------+--------------------------+-------------------------+ +| :math:`\theta_2` | :math:`-\frac{\pi}{2}` | :math:`\frac{\pi}{2}` | ++------------------+--------------------------+-------------------------+ +| :math:`d_3` | :math:`1` | :math:`3` | ++------------------+--------------------------+-------------------------+ +| :math:`\theta_4` | :math:`-\pi` | :math:`\pi` | ++------------------+--------------------------+-------------------------+ +| :math:`\theta_5` | :math:`-\frac{5\pi}{36}` | :math:`\frac{5\pi}{36}` | ++------------------+--------------------------+-------------------------+ +| :math:`\theta_6` | :math:`-\pi` | :math:`\pi` | ++------------------+--------------------------+-------------------------+ + +**Table 1**: *Physical constraints for the joint variables* + +Now, if we are given an *end-tip position* (in this case a :math:`xyz` +coordinate) we need to find the optimal parameters with the constraints +imposed in **Table 1**. These conditions are then sufficient in order to +treat this problem as an optimization problem. We define our parameter +vector :math:`\mathbf{X}` as follows: + +.. math:: \mathbf{X}\,:=\, [ \, \theta_1 \quad \theta_2 \quad d_3\ \quad \theta_4 \quad \theta_5 \, ] + +And for our end-tip position we define the target vector +:math:`\mathbf{T}` as: + +.. math:: \mathbf{T}\,:=\, [\, T_x \quad T_y \quad T_z \,] + +We can then start implementing our optimization algorithm. + +Initializing the Swarm +====================== + +The main idea for PSO is that we set a swarm :math:`\mathbf{S}` composed +of particles :math:`\mathbf{P}_n` into a search space in order to find +the optimal solution. The movement of the swarm depends on the cognitive +(:math:`c_1`) and social (:math:`c_2`) of all the particles. The +cognitive component speaks of the particle’s bias towards its personal +best from its past experience (i.e., how attracted it is to its own best +position). The social component controls how the particles are attracted +to the best score found by the swarm (i.e., the global best). High +:math:`c_1` paired with low :math:`c_2` values can often cause the swarm +to stagnate. The inverse can cause the swarm to converge too fast, +resulting in suboptimal solutions. + +We define our particle :math:`\mathbf{P}` as: + +.. math:: \mathbf{P}\,:=\,\mathbf{X} + +And the swarm as being composed of :math:`N` particles with certain +positions at a timestep :math:`t`: + +.. math:: \mathbf{S}_t\,:=\,[\,\mathbf{P}_1\quad\mathbf{P}_2\quad ... \quad\mathbf{P}_N\,] + +In this implementation, we designate :math:`\mathbf{P}_1` as the initial +configuration of the manipulator at the zero-position. This means that +the angles are equal to 0 and the link offset is also zero. We then +generate the :math:`N-1` particles using a uniform distribution which is +controlled by the hyperparameter :math:`\epsilon`. + +Finding the global optimum +========================== + +In order to find the global optimum, the swarm must be moved. This +movement is then translated by an update of the current position given +the swarm’s velocity :math:`\mathbf{V}`. That is: + +.. math:: \mathbf{S}_{t+1} = \mathbf{S}_t + \mathbf{V}_{t+1} + +The velocity is then computed as follows: + +.. math:: \mathbf{V}_{t+1} = w\mathbf{V}_t + c_1 r_1 (\mathbf{p}_{best} - \mathbf{p}) + c_2 r_2(\mathbf{g}_{best} - \mathbf{p}) + +Where :math:`r_1` and :math:`r_2` denote random values in the intervall +:math:`[0,1]`, :math:`\mathbf{p}_{best}` is the best and +:math:`\mathbf{p}` is the current personal position and +:math:`\mathbf{g}_{best}` is the best position of all the particles. +Moreover, :math:`w` is the inertia weight that controls the “memory” of +the swarm’s previous position. + +Preparations +------------ + +Let us now see how this works with the ``pyswarms`` library. We use the +point :math:`[-2,2,3]` as our target for which we want to find an +optimal pose of the manipulator. We start by defining a function to get +the distance from the current position to the target position: + +.. code:: python + + def distance(query, target): + x_dist = (target[0] - query[0])**2 + y_dist = (target[1] - query[1])**2 + z_dist = (target[2] - query[2])**2 + dist = np.sqrt(x_dist + y_dist + z_dist) + return dist + +We are going to use the distance function to compute the cost, the +further away the more costly the position is. + +The optimization algorithm needs some parameters (the swarm size, +:math:`c_1`, :math:`c_2` and :math:`\epsilon`). For the *options* +(:math:`c_1`,\ :math:`c_2` and :math:`w`) we have to create a dictionary +and for the constraints a tuple with a list of the respective minimal +values and a list of the respective maximal values. The rest can be +handled with variables. Additionally, we define the joint lengths to be +3 units long: + +.. code:: python + + swarm_size = 20 + dim = 6 # Dimension of X + epsilon = 1.0 + options = {'c1': 1.5, 'c2':1.5, 'w':0.5} + + constraints = (np.array([-np.pi , -np.pi/2 , 1 , -np.pi , -5*np.pi/36 , -np.pi]), + np.array([np.pi , np.pi/2 , 3 , np.pi , 5*np.pi/36 , np.pi])) + + d1 = d2 = d3 = d4 = d5 = d6 = 3 + +In order to obtain the current position, we need to calculate the +matrices of rotation and translation for every joint. Here we use the +`Denvait-Hartenberg +parameters `__ +for that. So we define a function that calculates these. The function +uses the rotation angle and the extension :math:`d` of a prismatic joint +as input: + +.. code:: python + + def getTransformMatrix(theta, d, a, alpha): + T = np.array([[np.cos(theta) , -np.sin(theta)*np.cos(alpha) , np.sin(theta)*np.sin(alpha) , a*np.cos(theta)], + [np.sin(theta) , np.cos(theta)*np.cos(alpha) , -np.cos(theta)*np.sin(alpha) , a*np.sin(theta)], + [0 , np.sin(alpha) , np.cos(alpha) , d ], + [0 , 0 , 0 , 1 ] + ]) + return T + +Now we can calculate the transformation matrix to obtain the end tip +position. For this we create another function that takes our vector +:math:`\mathbf{X}` with the joint variables as input: + +.. code:: python + + def get_end_tip_position(params): + # Create the transformation matrices for the respective joints + t_00 = np.array([[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]) + t_01 = getTransformMatrix(params[0] , d2 , 0 , -np.pi/2) + t_12 = getTransformMatrix(params[1] , d2 , 0 , -np.pi/2) + t_23 = getTransformMatrix(0 , params[2] , 0 , -np.pi/2) + t_34 = getTransformMatrix(params[3] , d4 , 0 , -np.pi/2) + t_45 = getTransformMatrix(params[4] , 0 , 0 , np.pi/2) + t_56 = getTransformMatrix(params[5] , d6 ,0 , 0) + + # Get the overall transformation matrix + end_tip_m = t_00.dot(t_01).dot(t_12).dot(t_23).dot(t_34).dot(t_45).dot(t_56) + + # The coordinates of the end tip are the 3 upper entries in the 4th column + pos = np.array([end_tip_m[0,3],end_tip_m[1,3],end_tip_m[2,3]]) + return pos + +The last thing we need to prepare in order to run the algorithm is the +actual function that we want to optimize. We just need to calculate the +distance between the position of each swarm particle and the target +point: + +.. code:: python + + def opt_func(X): + n_particles = X.shape[0] # number of particles + target = np.array([-2,2,3]) + dist = [distance(get_end_tip_position(X[i]), target) for i in range(n_particles)] + return np.array(dist) + +Running the algorithm +--------------------- + +Braced with these preparations we can finally start using the algorithm: + +.. code:: python + + %%time + # Call an instance of PSO + optimizer = ps.single.GlobalBestPSO(n_particles=swarm_size, + dimensions=dim, + options=options, + bounds=constraints) + + # Perform optimization + cost, joint_vars = optimizer.optimize(opt_func, print_step=100, iters=1000, verbose=3) + + +.. parsed-literal:: + + INFO:pyswarms.single.global_best:Iteration 1/1000, cost: 0.9638223076369133 + INFO:pyswarms.single.global_best:Iteration 101/1000, cost: 2.5258875519324167e-07 + INFO:pyswarms.single.global_best:Iteration 201/1000, cost: 4.7236564972673785e-14 + INFO:pyswarms.single.global_best:Iteration 301/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 401/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 501/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 601/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 701/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 801/1000, cost: 0.0 + INFO:pyswarms.single.global_best:Iteration 901/1000, cost: 0.0 + INFO:pyswarms.single.global_best:================================ + Optimization finished! + Final cost: 0.0000 + Best value: [ -2.182725 1.323111 1.579636 ...] + + + +.. parsed-literal:: + + Wall time: 13.6 s + + +Now let’s see if the algorithm really worked and test the output for +``joint_vars``: + +.. code:: python + + print(get_end_tip_position(joint_vars)) + + +.. parsed-literal:: + + [-2. 2. 3.] + + +Hooray! That’s exactly the position we wanted the tip to be in. Of +course this example is quite primitive. Some extensions of this idea +could involve the consideration of the current position of the +manipulator and the amount of rotation and extension in the optimization +function such that the result is the path with the least movement. diff --git a/docs/examples/output_8_0.png b/docs/examples/output_8_0.png new file mode 100644 index 00000000..8f902cd3 Binary files /dev/null and b/docs/examples/output_8_0.png differ diff --git a/docs/examples/output_9_0.png b/docs/examples/output_9_0.png deleted file mode 100644 index 64d75cdf..00000000 Binary files a/docs/examples/output_9_0.png and /dev/null differ diff --git a/docs/examples/visualization.rst b/docs/examples/visualization.rst index 5dbfa015..a43c11af 100644 --- a/docs/examples/visualization.rst +++ b/docs/examples/visualization.rst @@ -16,11 +16,29 @@ and Windows users, it can be installed via: $ conda install -c conda-forge ffmpeg -First, we need to import the -``pyswarms.utils.environments.PlotEnvironment`` class. This enables us -to use various methods to create animations or plot costs. +.. code:: ipython3 + + import sys + # Change directory to access the pyswarms module + sys.path.append('../') + +.. code:: ipython3 + + print('Running on Python version: {}'.format(sys.version)) + + +.. code:: shell + + Running on Python version: 3.6.3 |Anaconda custom (64-bit)| (default, Oct 13 2017, 12:02:49) [GCC 7.2.0] -.. code-block:: ipython + +In this example, we will demonstrate three plotting methods available on +PySwarms: - ``plot_cost_history``: for plotting the cost history of a +swarm given a matrix - ``plot_contour``: for plotting swarm trajectories +of a 2D-swarm in two-dimensional space - ``plot_surface``: for plotting +swarm trajectories of a 2D-swarm in three-dimensional space + +.. code:: ipython3 # Import modules import matplotlib.pyplot as plt @@ -31,7 +49,7 @@ to use various methods to create animations or plot costs. # Import PySwarms import pyswarms as ps from pyswarms.utils.functions import single_obj as fx - from pyswarms.utils.environments import PlotEnvironment + from pyswarms.utils.plotters import (plot_cost_history, plot_contour, plot_surface) # Some more magic so that the notebook will reload external python modules; # see http://stackoverflow.com/questions/1907993/autoreload-of-modules-in-ipython @@ -41,58 +59,52 @@ to use various methods to create animations or plot costs. The first step is to create an optimizer. Here, we're going to use Global-best PSO to find the minima of a sphere function. As usual, we simply create an instance of its class ``pyswarms.single.GlobalBestPSO`` -by passing the required parameters that we will use. +by passing the required parameters that we will use. Then, we'll call +the ``optimize()`` method for 100 iterations. -.. code-block:: ipython +.. code:: ipython3 options = {'c1':0.5, 'c2':0.3, 'w':0.9} - optimizer = ps.single.GlobalBestPSO(n_particles=10, dimensions=3, options=options) - -Initializing the ``PlotEnvironment`` ------------------------------------- + optimizer = ps.single.GlobalBestPSO(n_particles=50, dimensions=2, options=options) + cost, pos = optimizer.optimize(fx.sphere_func, iters=100) -Think of the ``PlotEnvironment`` as a container in which various -plotting methods can be called. In order to create an instance of this -class, we need to pass the optimizer object, the objective function, and -the number of iterations needed. The ``PlotEnvironment`` will then -simulate these parameters so as to build the plots. -.. code-block:: ipython +.. parsed-literal:: - plt_env = PlotEnvironment(optimizer, fx.sphere_func, 1000) + INFO:pyswarms.single.global_best:================================ + Optimization finished! + Final cost: 0.0000 + Best value: [0.00012738838189254011, -0.00011284635020703156] + -Plotting the cost ------------------ -To plot the cost, we simply need to call the ``plot_cost()`` function. -There are pre-set defaults in this method already, but we can customize -by passing various arguments into it such as figure size, title, x- and -y-labels and etc. Furthermore, this method also accepts a keyword -argument ``**kwargs`` similar to ``matplotlib``. This enables us to -further customize various artists and elements in the plot. +Plotting the cost history +------------------------- -For now, let's stick with the default one. We'll just call the -``plot_cost()`` and ``show()`` it. +To plot the cost history, we simply obtain the ``cost_history`` from the +``optimizer`` class and pass it to the ``cost_history`` function. +Furthermore, this method also accepts a keyword argument ``**kwargs`` +similar to ``matplotlib``. This enables us to further customize various +artists and elements in the plot. In addition, we can obtain the +following histories from the same class: - mean\_neighbor\_history: +average local best history of all neighbors throughout optimization - +mean\_pbest\_history: average personal best of the particles throughout +optimization -.. code-block:: ipython +.. code:: ipython3 - plt_env.plot_cost(figsize=(8,6)); + plot_cost_history(cost_history=optimizer.cost_history) plt.show() - - -.. image:: output_9_0.png +.. image:: output_8_0.png Animating swarms ---------------- -The ``PlotEnvironment()`` offers two methods to perform animation, -``plot_particles2D()`` and ``plot_particles3D()``. As its name suggests, -these methods plot the particles in a 2-D or 3-D space. You can choose -which dimensions will be plotted using the ``index`` argument, but the -default takes the first 2 (or first three in 3D) indices of your swarm -dimension. +The ``plotters`` module offers two methods to perform animation, +``plot_contour()`` and ``plot_surface()``. As its name suggests, these +methods plot the particles in a 2-D or 3-D space. Each animation method returns a ``matplotlib.animation.Animation`` class that still needs to be animated by a ``Writer`` class (thus @@ -100,30 +112,83 @@ necessitating the installation of a writer module). For the proceeding examples, we will convert the animations into an HTML5 video. In such case, we need to invoke some extra methods to do just that. -.. code-block:: ipython +.. code:: ipython3 # equivalent to rcParams['animation.html'] = 'html5' # See http://louistiao.me/posts/notebooks/save-matplotlib-animations-as-gifs/ rc('animation', html='html5') +Lastly, it would be nice to add meshes in our swarm to plot the sphere +function. This enables us to visually recognize where the particles are +with respect to our objective function. We can accomplish that using the +``Mesher`` class. + +.. code:: ipython3 + + from pyswarms.utils.plotters.formatters import Mesher + +.. code:: ipython3 + + # Initialize mesher with sphere function + m = Mesher(func=fx.sphere_func) + +There are different formatters available in the +``pyswarms.utils.plotters.formatters`` module to customize your plots +and visualizations. Aside from ``Mesher``, there is a ``Designer`` class +for customizing font sizes, figure sizes, etc. and an ``Animator`` class +to set delays and repeats during animation. + Plotting in 2-D space ~~~~~~~~~~~~~~~~~~~~~ -.. code-block:: ipython +We can obtain the swarm's position history using the ``pos_history`` +attribute from the ``optimizer`` instance. To plot a 2D-contour, simply +pass this together with the ``Mesher`` to the ``plot_contour()`` +function. In addition, we can also mark the global minima of the sphere +function, ``(0,0)``, to visualize the swarm's "target". - HTML(plt_env.plot_particles2D(limits=((-1.2,1.2),(-1.2,1.2))).to_html5_video()) +.. code:: ipython3 -.. image:: output_2d.gif + # Make animation + animation = plot_contour(pos_history=optimizer.pos_history, + mesher=m, + mark=(0,0)) + + # Enables us to view it in a Jupyter notebook + HTML(animation.to_html5_video()) +.. image:: https://i.imgur.com/g7UcOgU.gif Plotting in 3-D space ~~~~~~~~~~~~~~~~~~~~~ +To plot in 3D space, we need a position-fitness matrix with shape +``(iterations, n_particles, 3)``. The first two columns indicate the x-y +position of the particles, while the third column is the fitness of that +given position. You need to set this up on your own, but we have +provided a helper function to compute this automatically + +.. code:: ipython3 + + # Obtain a position-fitness matrix using the Mesher.compute_history_3d() + # method. It requires a cost history obtainable from the optimizer class + pos_history_3d = m.compute_history_3d(optimizer.pos_history) + .. code:: ipython3 - HTML(plt_env.plot_particles3D(limits=((-1.2,1.2),(-1.2,1.2),(-1.2,1.2))).to_html5_video()) + # Make a designer and set the x,y,z limits to (-1,1), (-1,1) and (-0.1,1) respectively + from pyswarms.utils.plotters.formatters import Designer + d = Designer(limits=[(-1,1), (-1,1), (-0.1,1)], label=['x-axis', 'y-axis', 'z-axis']) +.. code:: ipython3 -.. image:: output_3d.gif + # Make animation + animation3d = plot_surface(pos_history=pos_history_3d, # Use the cost_history we computed + mesher=m, designer=d, # Customizations + mark=(0,0,0)) # Mark minima + + # Enables us to view it in a Jupyter notebook + HTML(animation3d.to_html5_video()) +.. image:: https://i.imgur.com/IhbKTIE.gif \ No newline at end of file diff --git a/docs/features.rst b/docs/features.rst index b3a7af53..bb09f74b 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -19,6 +19,8 @@ for optimizing various common functions. * :mod:`pyswarms.single.local_best` - classic local-best Particle Swarm Optimization algorithm with a ring-topology. Every particle compares itself only with its nearest-neighbours as computed by a distance metric. +* :mod:`pyswarms.single.general_optimizer` - alterable but still classic Particle Swarm Optimization algorithm with a custom topology. Every topology in the :mod:`pyswarms.backend` module can be passed as an argument. + Discrete ~~~~~~~~ @@ -30,7 +32,7 @@ job-scheduling, traveling salesman, or any other sequence-based problems. Utilities --------- -Test Functions +Benchmark Functions ~~~~~~~~~~~~~~ These functions can be used as benchmarks for assessing the performance of @@ -48,11 +50,20 @@ hyperparameter value combinations in reducing a specified objective function. * :mod:`pyswarms.utils.search.random_search` - search for optimal performance on selected objective function over combinations of randomly selected hyperparameter values within specified bounds for specified number of selection iterations +Plotters +~~~~~~~~ + +A quick and easy to use tool for the visualization of optimizations. It allows you to easily create animations and +to visually check your optimization! + +* :mod:`pyswarms.utils.plotters` + Environment ~~~~~~~~~~~~ +.. deprecated:: 0.4.0 + Use :mod:`pyswarms.utils.plotters` instead! Various environments that allow you to analyze your swarm performance and make visualizations! * :mod:`pyswarms.utils.environments.plot_environment` - an environment for plotting the cost history and animating particles in a 2D or 3D space. - \ No newline at end of file diff --git a/examples/basic_optimization.ipynb b/examples/basic_optimization.ipynb index a6b41e04..43136d3c 100644 --- a/examples/basic_optimization.ipynb +++ b/examples/basic_optimization.ipynb @@ -278,9 +278,211 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Basic Optimization with Arguments" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, we will run a basic optimization using an objective function that needs parameterization. We will use the ``single.GBestPSO`` and a version of the rosenbrock function to demonstrate" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running Python 3.5.2 |Anaconda custom (64-bit)| (default, Jul 2 2016, 17:53:06) \n", + "[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)]\n" + ] + } + ], + "source": [ + "import sys\n", + "# change directory to access pyswarms\n", + "sys.path.append('../')\n", + "\n", + "print(\"Running Python {}\".format(sys.version))" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# import modules\n", + "import numpy as np\n", + "\n", + "# create a parameterized version of the classic Rosenbrock unconstrained optimzation function\n", + "def rosenbrock_with_args(x, a, b, c=0):\n", + "\n", + " f = (a - x[:, 0]) ** 2 + b * (x[:, 1] - x[:, 0] ** 2) ** 2 + c\n", + " return f" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using Arguments" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Arguments can either be passed in using a tuple or a dictionary, using the ``kwargs={}`` paradigm. First lets optimize the Rosenbrock function using keyword arguments. Note in the definition of the Rosenbrock function above, there were two arguments that need to be passed other than the design variables, and one optional keyword argument, ``a``, ``b``, and ``c``, respectively" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:pyswarms.single.global_best:Arguments Passed to Objective Function: {'c': 0, 'b': 100, 'a': 1}\n", + "INFO:pyswarms.single.global_best:Iteration 1/1000, cost: 1022.9667801907804\n", + "INFO:pyswarms.single.global_best:Iteration 101/1000, cost: 0.0011172801146408992\n", + "INFO:pyswarms.single.global_best:Iteration 201/1000, cost: 7.845605970774126e-07\n", + "INFO:pyswarms.single.global_best:Iteration 301/1000, cost: 1.313503109901238e-09\n", + "INFO:pyswarms.single.global_best:Iteration 401/1000, cost: 5.187079604907219e-10\n", + "INFO:pyswarms.single.global_best:Iteration 501/1000, cost: 1.0115283486088853e-10\n", + "INFO:pyswarms.single.global_best:Iteration 601/1000, cost: 2.329870757208421e-13\n", + "INFO:pyswarms.single.global_best:Iteration 701/1000, cost: 4.826176894160183e-15\n", + "INFO:pyswarms.single.global_best:Iteration 801/1000, cost: 3.125715456651088e-17\n", + "INFO:pyswarms.single.global_best:Iteration 901/1000, cost: 1.4236768129666014e-19\n", + "INFO:pyswarms.single.global_best:================================\n", + "Optimization finished!\n", + "Final cost: 0.0000\n", + "Best value: [0.99999999996210465, 0.9999999999218413]\n", + "\n" + ] + } + ], + "source": [ + "from pyswarms.single.global_best import GlobalBestPSO\n", + "\n", + "# instatiate the optimizer\n", + "x_max = 10 * np.ones(2)\n", + "x_min = -1 * x_max\n", + "bounds = (x_min, x_max)\n", + "options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9}\n", + "optimizer = GlobalBestPSO(n_particles=10, dimensions=2, options=options, bounds=bounds)\n", + "\n", + "# now run the optimization, pass a=1 and b=100 as a tuple assigned to args\n", + "\n", + "cost, pos = optimizer.optimize(rosenbrock_with_args, 1000, print_step=100, verbose=3, a=1, b=100, c=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is also possible to pass a dictionary of key word arguments by using ``**`` decorator when passing the dict" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:pyswarms.single.global_best:Arguments Passed to Objective Function: {'c': 0, 'b': 100.0, 'a': 1.0}\n", + "INFO:pyswarms.single.global_best:Iteration 1/1000, cost: 1.996797703363527e-21\n", + "INFO:pyswarms.single.global_best:Iteration 101/1000, cost: 1.0061676299213387e-24\n", + "INFO:pyswarms.single.global_best:Iteration 201/1000, cost: 4.8140236741112245e-28\n", + "INFO:pyswarms.single.global_best:Iteration 301/1000, cost: 2.879342304056693e-29\n", + "INFO:pyswarms.single.global_best:Iteration 401/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 501/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 601/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 701/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 801/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 901/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:================================\n", + "Optimization finished!\n", + "Final cost: 0.0000\n", + "Best value: [1.0, 1.0]\n", + "\n" + ] + } + ], + "source": [ + "kwargs={\"a\": 1.0, \"b\": 100.0, 'c':0}\n", + "cost, pos = optimizer.optimize(rosenbrock_with_args, 1000, print_step=100, verbose=3, **kwargs)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "Any key word arguments in the objective function can be left out as they will be passed the default as defined in the prototype. Note here, ``c`` is not passed into the function." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:pyswarms.single.global_best:Arguments Passed to Objective Function: {'b': 100, 'a': 1}\n", + "INFO:pyswarms.single.global_best:Iteration 1/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 101/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 201/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 301/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 401/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 501/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 601/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 701/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 801/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 901/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:================================\n", + "Optimization finished!\n", + "Final cost: 0.0000\n", + "Best value: [1.0, 1.0]\n", + "\n" + ] + } + ], + "source": [ + "cost, pos = optimizer.optimize(rosenbrock_with_args, 1000, print_step=100, verbose=3, a=1, b=100)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { + "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", @@ -296,9 +498,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.3" + "version": "3.6.0" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 1 } diff --git a/examples/inverse_kinematics.ipynb b/examples/inverse_kinematics.ipynb new file mode 100644 index 00000000..f46cd5ea --- /dev/null +++ b/examples/inverse_kinematics.ipynb @@ -0,0 +1,381 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Solving the Inverse Kinematics problem using Particle Swarm Optimization\n", + "====================================\n", + "\n", + "In this example, we are going to use the `pyswarms` library to solve a 6-DOF (Degrees of Freedom) Inverse Kinematics (IK) problem by treating it as an optimization problem. We will use the `pyswarms` library to find an *optimal* solution from a set of candidate solutions." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "# Change directory to access the pyswarms module\n", + "sys.path.append('../')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running on Python version: 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 07:18:10) [MSC v.1900 32 bit (Intel)]\n" + ] + } + ], + "source": [ + "print('Running on Python version: {}'.format(sys.version))" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Import modules\n", + "import numpy as np\n", + "\n", + "# Import PySwarms\n", + "import pyswarms as ps\n", + "\n", + "# Some more magic so that the notebook will reload external python modules;\n", + "# see http://stackoverflow.com/questions/1907993/autoreload-of-modules-in-ipython\n", + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "# Styling for the text below" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%html\n", + "\n", + "# Styling for the text below" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Introduction\n", + "======\n", + "\n", + "Inverse Kinematics is one of the most challenging problems in robotics. The problem involves finding an optimal *pose* for a manipulator given the position of the end-tip effector as opposed to forward kinematics, where the end-tip position is sought given the pose or joint configuration. Normally, this position is expressed as a point in a coordinate system (e.g., in a Cartesian system with $x$, $y$ and $z$ coordinates). However, the pose of the manipulator can also be expressed as the collection of joint variables that describe the angle of bending or twist (in revolute joints) or length of extension (in prismatic joints).\n", + "\n", + "IK is particularly difficult because an abundance of solutions can arise. Intuitively, one can imagine that a robotic arm can have multiple ways of reaching through a certain point. It's the same when you touch the table and move your arm without moving the point you're touching the table at. Moreover, the calculation of these positions can be very difficult. Simple solutions can be found for 3-DOF manipulators but trying to solve the problem for 6 or even more DOF can lead to challenging algebraic problems.\n", + "\n", + "IK as an Optimization Problem\n", + "===============\n", + "\n", + "In this implementation, we are going to use a *6-DOF Stanford Manipulator* with 5 revolute joints and 1 prismatic joint. Furthermore, the constraints of the joints are going to be as follows:\n", + "\n", + "| Parameters | Lower Boundary | Upper Boundary |\n", + "|:---:|:----------------:|:----------------:|\n", + "|$\\theta_1$ | $-\\pi$ | $\\pi$ |\n", + "|$\\theta_2$ |$-\\frac{\\pi}{2}$| $\\frac{\\pi}{2}$|\n", + "|$d_3$ | $1$ | $3$ |\n", + "|$\\theta_4$ | $-\\pi$ | $\\pi$ |\n", + "|$\\theta_5$ |$-\\frac{5\\pi}{36}$|$\\frac{5\\pi}{36}$|\n", + "|$\\theta_6$ | $-\\pi$ | $\\pi$ |\n", + "\n", + "**Table 1**: *Physical constraints for the joint variables*\n", + "\n", + "Now, if we are given an *end-tip position* (in this case a $xyz$ coordinate) we need to find the optimal parameters with the constraints imposed in **Table 1**. These conditions are then sufficient in order to treat this problem as an optimization problem. We define our parameter vector $\\mathbf{X}$ as follows:\n", + "\n", + "$$\\mathbf{X}\\,:=\\, [ \\, \\theta_1 \\quad \\theta_2 \\quad d_3\\ \\quad \\theta_4 \\quad \\theta_5 \\, ]$$\n", + "\n", + "And for our end-tip position we define the target vector $\\mathbf{T}$ as:\n", + "\n", + "$$\\mathbf{T}\\,:=\\, [\\, T_x \\quad T_y \\quad T_z \\,]$$\n", + "\n", + "We can then start implementing our optimization algorithm.\n", + "\n", + "Initializing the Swarm\n", + "===========\n", + "\n", + "The main idea for PSO is that we set a swarm $\\mathbf{S}$ composed of particles $\\mathbf{P}_n$ into a search space in order to find the optimal solution. The movement of the swarm depends on the cognitive ($c_1$) and social ($c_2$) of all the particles. The cognitive component speaks of the particle's bias towards its personal best from its past experience (i.e., how attracted it is to its own best position). The social component controls how the particles are attracted to the best score found by the swarm (i.e., the global best). High $c_1$ paired with low $c_2$ values can often cause the swarm to stagnate. The inverse can cause the swarm to converge too fast, resulting in suboptimal solutions.\n", + "\n", + "We define our particle $\\mathbf{P}$ as:\n", + "\n", + "$$\\mathbf{P}\\,:=\\,\\mathbf{X}$$\n", + "\n", + "And the swarm as being composed of $N$ particles with certain positions at a timestep $t$:\n", + "\n", + "$$\\mathbf{S}_t\\,:=\\,[\\,\\mathbf{P}_1\\quad\\mathbf{P}_2\\quad ... \\quad\\mathbf{P}_N\\,]$$\n", + "\n", + "In this implementation, we designate $\\mathbf{P}_1$ as the initial configuration of the manipulator at the zero-position. This means that the angles are equal to 0 and the link offset is also zero. We then generate the $N-1$ particles using a uniform distribution which is controlled by the hyperparameter $\\epsilon$.\n", + "\n", + "Finding the global optimum\n", + "=============\n", + "\n", + "In order to find the global optimum, the swarm must be moved. This movement is then translated by an update of the current position given the swarm's velocity $\\mathbf{V}$. That is:\n", + "\n", + "$$\\mathbf{S}_{t+1} = \\mathbf{S}_t + \\mathbf{V}_{t+1}$$\n", + "\n", + "The velocity is then computed as follows:\n", + "\n", + "$$\\mathbf{V}_{t+1} = w\\mathbf{V}_t + c_1 r_1 (\\mathbf{p}_{best} - \\mathbf{p}) + c_2 r_2(\\mathbf{g}_{best} - \\mathbf{p})$$\n", + "\n", + "Where $r_1$ and $r_2$ denote random values in the intervall $[0,1]$, $\\mathbf{p}_{best}$ is the best and $\\mathbf{p}$ is the current personal position and $\\mathbf{g}_{best}$ is the best position of all the particles. Moreover, $w$ is the inertia weight that controls the \"memory\" of the swarm's previous position.\n", + "\n", + "Preparations\n", + "------------\n", + "\n", + "Let us now see how this works with the `pyswarms` library. We use the point $[-2,2,3]$ as our target for which we want to find an optimal pose of the manipulator. We start by defining a function to get the distance from the current position to the target position:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "def distance(query, target):\n", + " x_dist = (target[0] - query[0])**2\n", + " y_dist = (target[1] - query[1])**2\n", + " z_dist = (target[2] - query[2])**2\n", + " dist = np.sqrt(x_dist + y_dist + z_dist)\n", + " return dist" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are going to use the distance function to compute the cost, the further away the more costly the position is. \n", + "\n", + "The optimization algorithm needs some parameters (the swarm size, $c_1$, $c_2$ and $\\epsilon$). For the *options* ($c_1$,$c_2$ and $w$) we have to create a dictionary and for the constraints a tuple with a list of the respective minimal values and a list of the respective maximal values. The rest can be handled with variables. Additionally, we define the joint lengths to be 3 units long:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "swarm_size = 20\n", + "dim = 6 # Dimension of X\n", + "epsilon = 1.0\n", + "options = {'c1': 1.5, 'c2':1.5, 'w':0.5}\n", + "\n", + "constraints = (np.array([-np.pi , -np.pi/2 , 1 , -np.pi , -5*np.pi/36 , -np.pi]),\n", + " np.array([np.pi , np.pi/2 , 3 , np.pi , 5*np.pi/36 , np.pi]))\n", + "\n", + "d1 = d2 = d3 = d4 = d5 = d6 = 3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In order to obtain the current position, we need to calculate the matrices of rotation and translation for every joint. Here we use the [Denvait-Hartenberg parameters](https://en.wikipedia.org/wiki/Denavit–Hartenberg_parameters) for that. So we define a function that calculates these. The function uses the rotation angle and the extension $d$ of a prismatic joint as input:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def getTransformMatrix(theta, d, a, alpha):\n", + " T = np.array([[np.cos(theta) , -np.sin(theta)*np.cos(alpha) , np.sin(theta)*np.sin(alpha) , a*np.cos(theta)],\n", + " [np.sin(theta) , np.cos(theta)*np.cos(alpha) , -np.cos(theta)*np.sin(alpha) , a*np.sin(theta)],\n", + " [0 , np.sin(alpha) , np.cos(alpha) , d ],\n", + " [0 , 0 , 0 , 1 ]\n", + " ])\n", + " return T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can calculate the transformation matrix to obtain the end tip position. For this we create another function that takes our vector $\\mathbf{X}$ with the joint variables as input:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "def get_end_tip_position(params):\n", + " # Create the transformation matrices for the respective joints\n", + " t_00 = np.array([[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]])\n", + " t_01 = getTransformMatrix(params[0] , d2 , 0 , -np.pi/2)\n", + " t_12 = getTransformMatrix(params[1] , d2 , 0 , -np.pi/2)\n", + " t_23 = getTransformMatrix(0 , params[2] , 0 , -np.pi/2)\n", + " t_34 = getTransformMatrix(params[3] , d4 , 0 , -np.pi/2)\n", + " t_45 = getTransformMatrix(params[4] , 0 , 0 , np.pi/2)\n", + " t_56 = getTransformMatrix(params[5] , d6 ,0 , 0)\n", + "\n", + " # Get the overall transformation matrix\n", + " end_tip_m = t_00.dot(t_01).dot(t_12).dot(t_23).dot(t_34).dot(t_45).dot(t_56)\n", + " \n", + " # The coordinates of the end tip are the 3 upper entries in the 4th column\n", + " pos = np.array([end_tip_m[0,3],end_tip_m[1,3],end_tip_m[2,3]])\n", + " return pos" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The last thing we need to prepare in order to run the algorithm is the actual function that we want to optimize. We just need to calculate the distance between the position of each swarm particle and the target point:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "def opt_func(X):\n", + " n_particles = X.shape[0] # number of particles\n", + " target = np.array([-2,2,3])\n", + " dist = [distance(get_end_tip_position(X[i]), target) for i in range(n_particles)]\n", + " return np.array(dist)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Running the algorithm\n", + "----------------------\n", + "\n", + "Braced with these preparations we can finally start using the algorithm:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:pyswarms.single.global_best:Iteration 1/1000, cost: 0.9638223076369133\n", + "INFO:pyswarms.single.global_best:Iteration 101/1000, cost: 2.5258875519324167e-07\n", + "INFO:pyswarms.single.global_best:Iteration 201/1000, cost: 4.7236564972673785e-14\n", + "INFO:pyswarms.single.global_best:Iteration 301/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 401/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 501/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 601/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 701/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 801/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:Iteration 901/1000, cost: 0.0\n", + "INFO:pyswarms.single.global_best:================================\n", + "Optimization finished!\n", + "Final cost: 0.0000\n", + "Best value: [ -2.182725 1.323111 1.579636 ...]\n", + "\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Wall time: 13.6 s\n" + ] + } + ], + "source": [ + "%%time\n", + "# Call an instance of PSO\n", + "optimizer = ps.single.GlobalBestPSO(n_particles=swarm_size,\n", + " dimensions=dim,\n", + " options=options,\n", + " bounds=constraints)\n", + "\n", + "# Perform optimization\n", + "cost, joint_vars = optimizer.optimize(opt_func, print_step=100, iters=1000, verbose=3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's see if the algorithm really worked and test the output for `joint_vars`:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[-2. 2. 3.]\n" + ] + } + ], + "source": [ + "print(get_end_tip_position(joint_vars))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Hooray! That's exactly the position we wanted the tip to be in. Of course this example is quite primitive. Some extensions of this idea could involve the consideration of the current position of the manipulator and the amount of rotation and extension in the optimization function such that the result is the path with the least movement." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/visualization.ipynb b/examples/visualization.ipynb index 37d4abfd..1565520e 100644 --- a/examples/visualization.ipynb +++ b/examples/visualization.ipynb @@ -49,7 +49,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "First, we need to import the `pyswarms.utils.environments.PlotEnvironment` class. This enables us to use various methods to create animations or plot costs." + "In this example, we will demonstrate three plotting methods available on PySwarms:\n", + "- `plot_cost_history`: for plotting the cost history of a swarm given a matrix\n", + "- `plot_contour`: for plotting swarm trajectories of a 2D-swarm in two-dimensional space\n", + "- `plot_surface`: for plotting swarm trajectories of a 2D-swarm in three-dimensional space" ] }, { @@ -67,7 +70,7 @@ "# Import PySwarms\n", "import pyswarms as ps\n", "from pyswarms.utils.functions import single_obj as fx\n", - "from pyswarms.utils.environments import PlotEnvironment\n", + "from pyswarms.utils.plotters import (plot_cost_history, plot_contour, plot_surface)\n", "\n", "# Some more magic so that the notebook will reload external python modules;\n", "# see http://stackoverflow.com/questions/1907993/autoreload-of-modules-in-ipython\n", @@ -79,56 +82,41 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The first step is to create an optimizer. Here, we're going to use Global-best PSO to find the minima of a sphere function. As usual, we simply create an instance of its class `pyswarms.single.GlobalBestPSO` by passing the required parameters that we will use." + "The first step is to create an optimizer. Here, we're going to use Global-best PSO to find the minima of a sphere function. As usual, we simply create an instance of its class `pyswarms.single.GlobalBestPSO` by passing the required parameters that we will use. Then, we'll call the `optimize()` method for 100 iterations." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, - "outputs": [], - "source": [ - "options = {'c1':0.5, 'c2':0.3, 'w':0.9}\n", - "optimizer = ps.single.GlobalBestPSO(n_particles=10, dimensions=3, options=options)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Initializing the `PlotEnvironment`\n", - "\n", - "Think of the `PlotEnvironment` as a container in which various plotting methods can be called. In order to create an instance of this class, we need to pass the optimizer object, the objective function, and the number of iterations needed. The `PlotEnvironment` will then simulate these parameters so as to build the plots." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, "outputs": [ { - "name": "stdout", + "name": "stderr", "output_type": "stream", "text": [ - "CPU times: user 91.1 ms, sys: 786 µs, total: 91.9 ms\n", - "Wall time: 88.9 ms\n" + "INFO:pyswarms.single.global_best:================================\n", + "Optimization finished!\n", + "Final cost: 0.0000\n", + "Best value: [-0.00016964270606917298, -1.1965240885493259e-05]\n", + "\n" ] } ], "source": [ - "%%time\n", - "plt_env = PlotEnvironment(optimizer, fx.sphere_func, 1000)" + "options = {'c1':0.5, 'c2':0.3, 'w':0.9}\n", + "optimizer = ps.single.GlobalBestPSO(n_particles=50, dimensions=2, options=options)\n", + "cost, pos = optimizer.optimize(fx.sphere_func, iters=100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Plotting the cost\n", - "\n", - "To plot the cost, we simply need to call the `plot_cost()` function. There are pre-set defaults in this method already, but we can customize by passing various arguments into it such as figure size, title, x- and y-labels and etc. Furthermore, this method also accepts a keyword argument `**kwargs` similar to `matplotlib`. This enables us to further customize various artists and elements in the plot. \n", + "## Plotting the cost history\n", "\n", - "For now, let's stick with the default one. We'll just call the `plot_cost()` and `show()` it." + "To plot the cost history, we simply obtain the `cost_history` from the `optimizer` class and pass it to the `cost_history` function. Furthermore, this method also accepts a keyword argument `**kwargs` similar to `matplotlib`. This enables us to further customize various artists and elements in the plot. In addition, we can obtain the following histories from the same class:\n", + "- mean_neighbor_history: average local best history of all neighbors throughout optimization\n", + "- mean_pbest_history: average personal best of the particles throughout optimization" ] }, { @@ -138,9 +126,9 @@ "outputs": [ { "data": { - "image/png": "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\nTkSuQmCC\n", + "image/png": "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\nQ5wkSVIF/f8+cXIHcAmX+QAAAABJRU5ErkJggg==\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -148,7 +136,7 @@ } ], "source": [ - "plt_env.plot_cost(figsize=(8,6));\n", + "plot_cost_history(cost_history=optimizer.cost_history)\n", "plt.show()" ] }, @@ -157,14 +145,14 @@ "metadata": {}, "source": [ "## Animating swarms\n", - "The `PlotEnvironment()` offers two methods to perform animation, `plot_particles2D()` and `plot_particles3D()`. As its name suggests, these methods plot the particles in a 2-D or 3-D space. You can choose which dimensions will be plotted using the `index` argument, but the default takes the first 2 (or first three in 3D) indices of your swarm dimension. \n", + "The `plotters` module offers two methods to perform animation, `plot_contour()` and `plot_surface()`. As its name suggests, these methods plot the particles in a 2-D or 3-D space.\n", "\n", "Each animation method returns a `matplotlib.animation.Animation` class that still needs to be animated by a `Writer` class (thus necessitating the installation of a writer module). For the proceeding examples, we will convert the animations into an HTML5 video. In such case, we need to invoke some extra methods to do just that." ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -177,19 +165,54 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Plotting in 2-D space\n" + "Lastly, it would be nice to add meshes in our swarm to plot the sphere function. This enables us to visually recognize where the particles are with respect to our objective function. We can accomplish that using the `Mesher` class." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "from pyswarms.utils.plotters.formatters import Mesher" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, + "outputs": [], + "source": [ + "# Initialize mesher with sphere function\n", + "m = Mesher(func=fx.sphere_func)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are different formatters available in the `pyswarms.utils.plotters.formatters` module to customize your plots and visualizations. Aside from `Mesher`, there is a `Designer` class for customizing font sizes, figure sizes, etc. and an `Animator` class to set delays and repeats during animation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plotting in 2-D space\n", + "\n", + "We can obtain the swarm's position history using the `pos_history` attribute from the `optimizer` instance. To plot a 2D-contour, simply pass this together with the `Mesher` to the `plot_contour()` function. In addition, we can also mark the global minima of the sphere function, `(0,0)`, to visualize the swarm's \"target\"." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, "outputs": [ { "data": { "text/html": [ - "" @@ -1632,32 +3760,72 @@ "" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "HTML(plt_env.plot_particles2D(limits=((-1.2,1.2),(-1.2,1.2))).to_html5_video())" + "# Make animation\n", + "animation = plot_contour(pos_history=optimizer.pos_history,\n", + " mesher=m,\n", + " mark=(0,0))\n", + "\n", + "# Enables us to view it in a Jupyter notebook\n", + "HTML(animation.to_html5_video())" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "# Save as GIF\n", + "animation.save('plot_contour.gif', writer='imagemagick', dpi=96)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Plotting in 3-D space" + "### Plotting in 3-D space\n", + "\n", + "To plot in 3D space, we need a position-fitness matrix with shape `(iterations, n_particles, 3)`. The first two columns indicate the x-y position of the particles, while the third column is the fitness of that given position. You need to set this up on your own, but we have provided a helper function to compute this automatically" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "# Obtain a position-fitness matrix using the Mesher.compute_history_3d()\n", + "# method. It requires a cost history obtainable from the optimizer class\n", + "pos_history_3d = m.compute_history_3d(optimizer.pos_history)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "# Make a designer and set the x,y,z limits to (-1,1), (-1,1) and (-0.1,1) respectively\n", + "from pyswarms.utils.plotters.formatters import Designer\n", + "d = Designer(limits=[(-1,1), (-1,1), (-0.1,1)], label=['x-axis', 'y-axis', 'z-axis'])" + ] + }, + { + "cell_type": "code", + "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "" @@ -3940,23 +5719,30 @@ "" ] }, - "execution_count": 9, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "HTML(plt_env.plot_particles3D(limits=((-1.2,1.2),(-1.2,1.2),(-1.2,1.2))).to_html5_video())" + "# Make animation\n", + "animation3d = plot_surface(pos_history=pos_history_3d, # Use the cost_history we computed\n", + " mesher=m, designer=d, # Customizations\n", + " mark=(0,0,0)) # Mark minima\n", + "\n", + "# Enables us to view it in a Jupyter notebook\n", + "HTML(animation3d.to_html5_video())" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 16, + "metadata": {}, "outputs": [], - "source": [] + "source": [ + "# Save as GIF\n", + "animation3d.save('plot_surface.gif', writer='imagemagick', dpi=96)" + ] } ], "metadata": { diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..dfab0250 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.black] +line-length = 79 +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist +)/ +''' diff --git a/pyswarms/__init__.py b/pyswarms/__init__.py index f8c0790c..98b853bb 100644 --- a/pyswarms/__init__.py +++ b/pyswarms/__init__.py @@ -11,14 +11,10 @@ """ __author__ = """Lester James V. Miranda""" -__email__ = 'ljvmiranda@gmail.com' -__version__ = '0.2.1' +__email__ = "ljvmiranda@gmail.com" +__version__ = "0.3.0" -from .single import global_best, local_best +from .single import global_best, local_best, general_optimizer from .discrete import binary -__all__ = [ - 'global_best', - 'local_best', - 'binary' - ] +__all__ = ["global_best", "local_best", "general_optimizer", "binary"] diff --git a/pyswarms/backend/__init__.py b/pyswarms/backend/__init__.py index 2173eaaf..425d3450 100644 --- a/pyswarms/backend/__init__.py +++ b/pyswarms/backend/__init__.py @@ -6,4 +6,6 @@ from .generators import * from .operators import * -from .swarms import * \ No newline at end of file +from .swarms import * + +__all__ = ["generators", "operators", "swarms"] diff --git a/pyswarms/backend/generators.py b/pyswarms/backend/generators.py index b5b1c44a..d475fbab 100644 --- a/pyswarms/backend/generators.py +++ b/pyswarms/backend/generators.py @@ -16,8 +16,10 @@ from .swarms import Swarm -def generate_swarm(n_particles, dimensions, bounds=None, center=1.00, init_pos=None): - """Generates a swarm +def generate_swarm( + n_particles, dimensions, bounds=None, center=1.00, init_pos=None +): + """Generate a swarm Parameters ---------- @@ -41,32 +43,40 @@ def generate_swarm(n_particles, dimensions, bounds=None, center=1.00, init_pos=N swarm matrix of shape (n_particles, n_dimensions) """ try: - if init_pos is not None: - # There is user-defined initial position - if bounds is None: - pos = init_pos - else: - if not (np.all(bounds[0] <= init_pos) and np.all(init_pos <= bounds[1])): - raise ValueError('User-defined init_pos is out of bounds.') - pos = init_pos + if (init_pos is not None) and (bounds is None): + pos = init_pos + elif (init_pos is not None) and (bounds is not None): + if not ( + np.all(bounds[0] <= init_pos) and np.all(init_pos <= bounds[1]) + ): + raise ValueError("User-defined init_pos is out of bounds.") + pos = init_pos + elif (init_pos is None) and (bounds is None): + pos = center * np.random.uniform( + low=0.0, high=1.0, size=(n_particles, dimensions) + ) else: - # There is no user-defined initial position - if bounds is None: - pos = center * np.random.uniform(low=0.0, high=1.0, size=(n_particles, dimensions)) - else: - lb, ub = bounds - min_bounds = np.repeat(np.array(lb)[np.newaxis, :], n_particles, axis=0) - max_bounds = np.repeat(np.array(ub)[np.newaxis, :], n_particles, axis=0) - pos = center * np.random.uniform(low=min_bounds, high=max_bounds, - size=(n_particles, dimensions)) + lb, ub = bounds + min_bounds = np.repeat( + np.array(lb)[np.newaxis, :], n_particles, axis=0 + ) + max_bounds = np.repeat( + np.array(ub)[np.newaxis, :], n_particles, axis=0 + ) + pos = center * np.random.uniform( + low=min_bounds, high=max_bounds, size=(n_particles, dimensions) + ) except ValueError: raise else: return pos -def generate_discrete_swarm(n_particles, dimensions, binary=False, init_pos=None): - """Generates a discrete swarm - + +def generate_discrete_swarm( + n_particles, dimensions, binary=False, init_pos=None +): + """Generate a discrete swarm + Parameters ---------- n_particles : int @@ -80,28 +90,26 @@ def generate_discrete_swarm(n_particles, dimensions, binary=False, init_pos=None :code:`None` if you wish to generate the particles randomly. """ try: - if init_pos is not None: - # There is user-defined initial position - if binary: - # Check if the initialized position is binary - if not len(np.unique(init_pos)) == 2: - raise ValueError('User-defined init_pos is not binary!') - pos = init_pos - else: - pos = init_pos + if (init_pos is not None) and binary: + if not len(np.unique(init_pos)) == 2: + raise ValueError("User-defined init_pos is not binary!") + pos = init_pos + elif (init_pos is not None) and not binary: + pos = init_pos + elif (init_pos is None) and binary: + pos = np.random.randint(2, size=(n_particles, dimensions)) else: - # There is no user-defined initial position - if binary: - pos = np.random.randint(2, size=(n_particles, dimensions)) - else: - pos = np.random.random_sample(size=(n_particles, dimensions)).argsort(axis=1) + pos = np.random.random_sample( + size=(n_particles, dimensions) + ).argsort(axis=1) except ValueError: raise else: return pos + def generate_velocity(n_particles, dimensions, clamp=None): - """Initializes a velocity vector + """Initialize a velocity vector Parameters ---------- @@ -120,19 +128,29 @@ def generate_velocity(n_particles, dimensions, clamp=None): velocity matrix of shape (n_particles, dimensions) """ try: - min_velocity, max_velocity = (0,1) if clamp==None else clamp - velocity = ((max_velocity - min_velocity) - * np.random.random_sample(size=(n_particles, dimensions)) - + min_velocity) + min_velocity, max_velocity = (0, 1) if clamp is None else clamp + velocity = (max_velocity - min_velocity) * np.random.random_sample( + size=(n_particles, dimensions) + ) + min_velocity except (ValueError, TypeError): raise else: return velocity -def create_swarm(n_particles, dimensions, discrete=False, binary=False, - options={}, bounds=None, center=1.0, init_pos=None, clamp=None): - """Abstracts the generate_swarm() and generate_velocity() methods - + +def create_swarm( + n_particles, + dimensions, + discrete=False, + binary=False, + options={}, + bounds=None, + center=1.0, + init_pos=None, + clamp=None, +): + """Abstract the generate_swarm() and generate_velocity() methods + Parameters ---------- n_particles : int @@ -165,10 +183,17 @@ def create_swarm(n_particles, dimensions, discrete=False, binary=False, a Swarm class """ if discrete: - position = generate_discrete_swarm(n_particles, dimensions, binary=binary) + position = generate_discrete_swarm( + n_particles, dimensions, binary=binary + ) else: - position = generate_swarm(n_particles, dimensions, bounds=bounds, - center=center, init_pos=init_pos) + position = generate_swarm( + n_particles, + dimensions, + bounds=bounds, + center=center, + init_pos=init_pos, + ) velocity = generate_velocity(n_particles, dimensions, clamp=clamp) - return Swarm(position, velocity, options=options) \ No newline at end of file + return Swarm(position, velocity, options=options) diff --git a/pyswarms/backend/operators.py b/pyswarms/backend/operators.py index e2f01ed0..fb42ecd2 100644 --- a/pyswarms/backend/operators.py +++ b/pyswarms/backend/operators.py @@ -17,9 +17,10 @@ # Create a logger logger = logging.getLogger(__name__) + def compute_pbest(swarm): - """Takes a swarm instance and updates the personal best scores - + """Update the personal best score of a swarm instance + You can use this method to update your personal best positions. .. code-block:: python @@ -37,7 +38,7 @@ def compute_pbest(swarm): It updates your :code:`current_pbest` with the personal bests acquired by comparing the (1) cost of the current positions and the (2) personal bests your swarm has attained. - + If the cost of the current position is less than the cost of the personal best, then the current position replaces the previous personal best position. @@ -58,25 +59,28 @@ def compute_pbest(swarm): # Infer dimensions from positions dimensions = swarm.dimensions # Create a 1-D and 2-D mask based from comparisons - mask_cost = (swarm.current_cost < swarm.pbest_cost) - mask_pos = np.repeat(mask_cost[:, np.newaxis], swarm.dimensions, axis=1) + mask_cost = swarm.current_cost < swarm.pbest_cost + mask_pos = np.repeat(mask_cost[:, np.newaxis], dimensions, axis=1) # Apply masks new_pbest_pos = np.where(~mask_pos, swarm.pbest_pos, swarm.position) - new_pbest_cost = np.where(~mask_cost, swarm.pbest_cost, swarm.current_cost) + new_pbest_cost = np.where( + ~mask_cost, swarm.pbest_cost, swarm.current_cost + ) except AttributeError: - msg = 'Please pass a Swarm class. You passed {}'.format(type(swarm)) + msg = "Please pass a Swarm class. You passed {}".format(type(swarm)) logger.error(msg) raise else: return (new_pbest_pos, new_pbest_cost) + def compute_velocity(swarm, clamp): - """Updates the velocity matrix + """Update the velocity matrix This method updates the velocity matrix using the best and current positions of the swarm. The velocity matrix is computed using the cognitive and social terms of the swarm. - + A sample usage can be seen with the following: .. code-block :: python @@ -107,12 +111,20 @@ def compute_velocity(swarm, clamp): try: # Prepare parameters swarm_size = swarm.position.shape - c1 = swarm.options['c1'] - c2 = swarm.options['c2'] - w = swarm.options['w'] + c1 = swarm.options["c1"] + c2 = swarm.options["c2"] + w = swarm.options["w"] # Compute for cognitive and social terms - cognitive = (c1 * np.random.uniform(0,1, swarm_size) * (swarm.pbest_pos - swarm.position)) - social = (c2 * np.random.uniform(0, 1, swarm_size) * (swarm.best_pos - swarm.position)) + cognitive = ( + c1 + * np.random.uniform(0, 1, swarm_size) + * (swarm.pbest_pos - swarm.position) + ) + social = ( + c2 + * np.random.uniform(0, 1, swarm_size) + * (swarm.best_pos - swarm.position) + ) # Compute temp velocity (subject to clamping if possible) temp_velocity = (w * swarm.velocity) + cognitive + social @@ -120,22 +132,24 @@ def compute_velocity(swarm, clamp): updated_velocity = temp_velocity else: min_velocity, max_velocity = clamp - mask = np.logical_and(temp_velocity >= min_velocity, - temp_velocity <= max_velocity) + mask = np.logical_and( + temp_velocity >= min_velocity, temp_velocity <= max_velocity + ) updated_velocity = np.where(~mask, swarm.velocity, temp_velocity) except AttributeError: - msg = 'Please pass a Swarm class. You passed {}'.format(type(swarm)) + msg = "Please pass a Swarm class. You passed {}".format(type(swarm)) logger.error(msg) raise except KeyError: - msg = 'Missing keyword in swarm.options' + msg = "Missing keyword in swarm.options" logger.error(msg) raise else: return updated_velocity + def compute_position(swarm, bounds): - """Updates the position matrix + """Update the position matrix This method updates the position matrix given the current position and the velocity. If bounded, it waives updating the position. @@ -160,15 +174,20 @@ def compute_position(swarm, bounds): if bounds is not None: lb, ub = bounds - min_bounds = np.repeat(np.array(lb)[np.newaxis, :], swarm.n_particles, axis=0) - max_bounds = np.repeat(np.array(ub)[np.newaxis, :], swarm.n_particles, axis=0) - mask = (np.all(min_bounds <= temp_position, axis=1) - * np.all(temp_position <= max_bounds, axis=1)) + min_bounds = np.repeat( + np.array(lb)[np.newaxis, :], swarm.n_particles, axis=0 + ) + max_bounds = np.repeat( + np.array(ub)[np.newaxis, :], swarm.n_particles, axis=0 + ) + mask = np.all(min_bounds <= temp_position, axis=1) * np.all( + temp_position <= max_bounds, axis=1 + ) mask = np.repeat(mask[:, np.newaxis], swarm.dimensions, axis=1) temp_position = np.where(~mask, swarm.position, temp_position) position = temp_position except AttributeError: - msg = 'Please pass a Swarm class. You passed {}'.format(type(swarm)) + msg = "Please pass a Swarm class. You passed {}".format(type(swarm)) logger.error(msg) raise else: diff --git a/pyswarms/backend/swarms.py b/pyswarms/backend/swarms.py index 6f1c8f27..a6d4db06 100644 --- a/pyswarms/backend/swarms.py +++ b/pyswarms/backend/swarms.py @@ -5,28 +5,29 @@ This module implements a Swarm class that holds various attributes in the swarm such as position, velocity, options, etc. You can use this -as input to most backend cases +as input to most backend cases. """ # Import modules import numpy as np -from attr import (attrs, attrib) +from attr import attrs, attrib from attr.validators import instance_of + @attrs class Swarm(object): """A Swarm Class - + This class offers a generic swarm that can be used in most use-cases such as single-objective optimization, etc. It contains various attributes that are commonly-used in most swarm implementations. To initialize this class, **simply supply values for the position and velocity matrix**. The other attributes are automatically filled. If you want to - initialize random values, take a look at: + initialize random values, take a look at: * :func:`pyswarms.backend.generators.generate_swarm`: for generating positions randomly. - * :func:`pyswarms.backend.generators.generate_velocity`: for generating velocities randomly. + * :func:`pyswarms.backend.generators.generate_velocity`: for generating velocities randomly. If your swarm requires additional parameters (say c1, c2, and w in gbest PSO), simply pass them to the :code:`options` dictionary. @@ -72,10 +73,11 @@ class Swarm(object): pbest_cost : numpy.ndarray (default is empty array) personal best costs of each particle of shape :code:`(n_particles, )` best_cost : float (default is :code:`np.inf`) - best cost found by the swarm + best cost found by the swarm current_cost : numpy.ndarray (default is empty array) the current cost found by the swarm of shape :code:`(n_particles, dimensions)` """ + # Required attributes position = attrib(type=np.ndarray, validator=instance_of(np.ndarray)) velocity = attrib(type=np.ndarray, validator=instance_of(np.ndarray)) @@ -84,10 +86,24 @@ class Swarm(object): dimensions = attrib(type=int, validator=instance_of(int)) options = attrib(type=dict, default={}, validator=instance_of(dict)) pbest_pos = attrib(type=np.ndarray, validator=instance_of(np.ndarray)) - best_pos = attrib(type=np.ndarray, default=np.array([]), validator=instance_of(np.ndarray)) - pbest_cost = attrib(type=np.ndarray, default=np.array([]), validator=instance_of(np.ndarray)) - best_cost = attrib(type=float, default=np.inf, validator=instance_of((int, float))) - current_cost = attrib(type=np.ndarray, default=np.array([]), validator=instance_of(np.ndarray)) + best_pos = attrib( + type=np.ndarray, + default=np.array([]), + validator=instance_of(np.ndarray), + ) + pbest_cost = attrib( + type=np.ndarray, + default=np.array([]), + validator=instance_of(np.ndarray), + ) + best_cost = attrib( + type=float, default=np.inf, validator=instance_of((int, float)) + ) + current_cost = attrib( + type=np.ndarray, + default=np.array([]), + validator=instance_of(np.ndarray), + ) @n_particles.default def n_particles_default(self): @@ -99,4 +115,4 @@ def dimensions_default(self): @pbest_pos.default def pbest_pos_default(self): - return self.position \ No newline at end of file + return self.position diff --git a/pyswarms/backend/topology/__init__.py b/pyswarms/backend/topology/__init__.py index eef1b585..c5c96039 100644 --- a/pyswarms/backend/topology/__init__.py +++ b/pyswarms/backend/topology/__init__.py @@ -1,16 +1,17 @@ """ -The :code:`pyswarms.backend.topology` contains various topologies that dictate -particle behavior. These topologies implement three methods: +The :code:`pyswarms.backend.topology` contains various topologies. They dictate +the behavior of the particles and implement three methods: - compute_best_particle(): gets the position and cost of the best particle in the swarm - update_velocity(): updates the velocity-matrix depending on the topology. - update_position(): updates the position-matrix depending on the topology. """ +from .base import Topology from .star import Star from .ring import Ring +from .pyramid import Pyramid +from .random import Random +from .von_neumann import VonNeumann -__all__ = [ - "Star", - "Ring" -] \ No newline at end of file +__all__ = ["Topology", "Star", "Ring", "Pyramid", "Random", "VonNeumann"] diff --git a/pyswarms/backend/topology/base.py b/pyswarms/backend/topology/base.py index a9d7af44..760d038e 100644 --- a/pyswarms/backend/topology/base.py +++ b/pyswarms/backend/topology/base.py @@ -12,21 +12,40 @@ :mod:`pyswarms.backend.swarms.Swarm` module. """ -class Topology(object): +# Import from stdlib +import logging + +# Import from package +from ...utils.console_utils import cli_print - def __init__(self, **kwargs): + +class Topology(object): + def __init__(self, static, **kwargs): """Initializes the class""" - pass + + # Initialize logger + self.logger = logging.getLogger(__name__) + + # Initialize attributes + self.static = static + self.neighbor_idx = None + + if self.static: + cli_print("Running on `dynamic` topology, neighbors are updated regularly." + "Set `static=True` for fixed neighbors.", + 1, + 0, + self.logger) def compute_gbest(self, swarm): - """Computes the best particle of the swarm and returns the cost and + """Compute the best particle of the swarm and return the cost and position""" raise NotImplementedError("Topology::compute_gbest()") def compute_position(self, swarm): - """Updates the swarm's position-matrix""" + """Update the swarm's position-matrix""" raise NotImplementedError("Topology::compute_position()") def compute_velocity(self, swarm): - """Updates the swarm's velocity-matrix""" - raise NotImplementedError("Topology::compute_velocity()") \ No newline at end of file + """Update the swarm's velocity-matrix""" + raise NotImplementedError("Topology::compute_velocity()") diff --git a/pyswarms/backend/topology/pyramid.py b/pyswarms/backend/topology/pyramid.py new file mode 100644 index 00000000..1f209502 --- /dev/null +++ b/pyswarms/backend/topology/pyramid.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- + +""" +A Pyramid Network Topology + +This class implements a pyramid topology. In this topology, the particles are connected by N-dimensional simplices. +""" + +# Import from stdlib +import logging + +# Import modules +import numpy as np +from scipy.spatial import Delaunay + +# Import from package +from .. import operators as ops +from .base import Topology + +# Create a logger +logger = logging.getLogger(__name__) + + +class Pyramid(Topology): + def __init__(self, static=False): + """Initialize the class + + Parameters + ---------- + static : bool (Default is :code:`False`) + a boolean that decides whether the topology + is static or dynamic + """ + super(Pyramid, self).__init__(static) + + def compute_gbest(self, swarm): + """Update the global best using a pyramid neighborhood approach + + This topology uses the :code:`Delaunay` class from :code:`scipy`. To prevent precision errors in the Delaunay + class, custom :code:`qhull_options` were added. Namely, :code:`QJ0.001 Qbb Qc Qx`. The meaning of those options + is explained in [qhull]. This method is used to triangulate N-dimensional space into simplices. The vertices of + the simplicies consist of swarm particles. This method is adapted from the work of Lane et al.[SIS2008] + + [SIS2008] J. Lane, A. Engelbrecht and J. Gain, "Particle swarm optimization with spatially + meaningful neighbours," 2008 IEEE Swarm Intelligence Symposium, St. Louis, MO, 2008, + pp. 1-8. doi: 10.1109/SIS.2008.4668281 + [qhull] http://www.qhull.org/html/qh-optq.htm + + Parameters + ---------- + swarm : pyswarms.backend.swarms.Swarm + a Swarm instance + + Returns + ------- + numpy.ndarray + Best position of shape :code:`(n_dimensions, )` + float + Best cost + """ + try: + # If there are less than (swarm.dimensions + 1) particles they are all connected + if swarm.n_particles < swarm.dimensions + 1: + self.neighbor_idx = np.tile(np.arange(swarm.n_particles), (swarm.n_particles, 1)) + best_pos = swarm.pbest_pos[np.argmin(swarm.pbest_cost)] + best_cost = np.min(swarm.pbest_cost) + else: + # Check if the topology is static or dynamic and assign neighbors + if (self.static and self.neighbor_idx is None) or not self.static: + pyramid = Delaunay(swarm.position, qhull_options="QJ0.001 Qbb Qc Qx") + indices, index_pointer = pyramid.vertex_neighbor_vertices + # Insert all the neighbors for each particle in the idx array + self.neighbor_idx = np.array( + [index_pointer[indices[i]:indices[i + 1]] for i in range(swarm.n_particles)] + ) + + idx_min = np.array( + [swarm.pbest_cost[self.neighbor_idx[i]].argmin() for i in range(len(self.neighbor_idx))] + ) + best_neighbor = np.array( + [self.neighbor_idx[i][idx_min[i]] for i in range(len(self.neighbor_idx))] + ).astype(int) + + # Obtain best cost and position + best_cost = np.min(swarm.pbest_cost[best_neighbor]) + best_pos = swarm.pbest_pos[ + best_neighbor[np.argmin(swarm.pbest_cost[best_neighbor])] + ] + except AttributeError: + msg = "Please pass a Swarm class. You passed {}".format( + type(swarm) + ) + logger.error(msg) + raise + else: + return (best_pos, best_cost) + + def compute_velocity(self, swarm, clamp=None): + """Compute the velocity matrix + + This method updates the velocity matrix using the best and current + positions of the swarm. The velocity matrix is computed using the + cognitive and social terms of the swarm. + + A sample usage can be seen with the following: + + .. code-block :: python + + import pyswarms.backend as P + from pyswarms.swarms.backend import Swarm + from pyswarms.backend.topology import Pyramid + + my_swarm = P.create_swarm(n_particles, dimensions) + my_topology = Pyramid(static=False) + + for i in range(iters): + # Inside the for-loop + my_swarm.velocity = my_topology.update_velocity(my_swarm, clamp) + + Parameters + ---------- + swarm : pyswarms.backend.swarms.Swarm + a Swarm instance + clamp : tuple of floats (default is :code:`None`) + a tuple of size 2 where the first entry is the minimum velocity + and the second entry is the maximum velocity. It + sets the limits for velocity clamping. + + Returns + ------- + numpy.ndarray + Updated velocity matrix + """ + return ops.compute_velocity(swarm, clamp) + + def compute_position(self, swarm, bounds=None): + """Update the position matrix + + This method updates the position matrix given the current position and + the velocity. If bounded, it waives updating the position. + + Parameters + ---------- + swarm : pyswarms.backend.swarms.Swarm + a Swarm instance + bounds : tuple of :code:`np.ndarray` or list (default is :code:`None`) + a tuple of size 2 where the first entry is the minimum bound while + the second entry is the maximum bound. Each array must be of shape + :code:`(dimensions,)`. + + Returns + ------- + numpy.ndarray + New position-matrix + """ + return ops.compute_position(swarm, bounds) diff --git a/pyswarms/backend/topology/random.py b/pyswarms/backend/topology/random.py new file mode 100644 index 00000000..17439621 --- /dev/null +++ b/pyswarms/backend/topology/random.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- + +""" +A Random Network Topology + +This class implements a random topology. All particles are connected in a random fashion. +""" + +# Import from stdlib +import logging +import itertools + +# Import modules +import numpy as np +from scipy.sparse.csgraph import connected_components, dijkstra + +# Import from package +from ..import operators as ops +from .base import Topology + +# Create a logger +logger = logging.getLogger(__name__) + + +class Random(Topology): + def __init__(self, static=False): + """Initializes the class + + Parameters + ---------- + static : bool (Default is :code:`False`) + a boolean that decides whether the topology + is static or dynamic""" + super(Random, self).__init__(static) + + def compute_gbest(self, swarm, k): + """Update the global best using a random neighborhood approach + + This uses random class from :code:`numpy` to give every particle k + randomly distributed, non-equal neighbors. The resulting topology + is a connected graph. The algorithm to obtain the neighbors was adapted + from [TSWJ2013]. + + [TSWJ2013] Qingjian Ni and Jianming Deng, “A New Logistic Dynamic + Particle Swarm Optimization Algorithm Based on Random Topology,” + The Scientific World Journal, vol. 2013, Article ID 409167, 8 pages, 2013. + https://doi.org/10.1155/2013/409167. + + Parameters + ---------- + swarm : pyswarms.backend.swarms.Swarm + a Swarm instance + k : int + number of neighbors to be considered. Must be a + positive integer less than :code:`n_particles-1` + + Returns + ------- + numpy.ndarray + Best position of shape :code:`(n_dimensions, )` + float + Best cost + """ + try: + # Check if the topology is static or dynamic and assign neighbors + if (self.static and self.neighbor_idx is None) or not self.static: + adj_matrix = self.__compute_neighbors(swarm, k) + self.neighbor_idx = np.array([adj_matrix[i].nonzero()[0] for i in range(swarm.n_particles)]) + idx_min = np.array([swarm.pbest_cost[self.neighbor_idx[i]].argmin() for i in range(len(self.neighbor_idx))]) + best_neighbor = np.array( + [self.neighbor_idx[i][idx_min[i]] for i in range(len(self.neighbor_idx))] + ).astype(int) + + # Obtain best cost and position + best_cost = np.min(swarm.pbest_cost[best_neighbor]) + best_pos = swarm.pbest_pos[ + best_neighbor[np.argmin(swarm.pbest_cost[best_neighbor])] + ] + + except AttributeError: + msg = "Please pass a Swarm class. You passed {}".format( + type(swarm) + ) + logger.error(msg) + raise + else: + return (best_pos, best_cost) + + def compute_velocity(self, swarm, clamp=None): + """Compute the velocity matrix + + This method updates the velocity matrix using the best and current + positions of the swarm. The velocity matrix is computed using the + cognitive and social terms of the swarm. + + A sample usage can be seen with the following: + + .. code-block :: python + + import pyswarms.backend as P + from pyswarms.swarms.backend import Swarm + from pyswarms.backend.topology import Random + + my_swarm = P.create_swarm(n_particles, dimensions) + my_topology = Random(static=False) + + for i in range(iters): + # Inside the for-loop + my_swarm.velocity = my_topology.update_velocity(my_swarm, clamp) + + Parameters + ---------- + swarm : pyswarms.backend.swarms.Swarm + a Swarm instance + clamp : tuple of floats (default is :code:`None`) + a tuple of size 2 where the first entry is the minimum velocity + and the second entry is the maximum velocity. It + sets the limits for velocity clamping. + + Returns + ------- + numpy.ndarray + Updated velocity matrix + """ + return ops.compute_velocity(swarm, clamp) + + def compute_position(self, swarm, bounds=None): + """Update the position matrix + + This method updates the position matrix given the current position and + the velocity. If bounded, it waives updating the position. + + Parameters + ---------- + swarm : pyswarms.backend.swarms.Swarm + a Swarm instance + bounds : tuple of :code:`np.ndarray` or list (default is :code:`None`) + a tuple of size 2 where the first entry is the minimum bound while + the second entry is the maximum bound. Each array must be of shape + :code:`(dimensions,)`. + + Returns + ------- + numpy.ndarray + New position-matrix + """ + return ops.compute_position(swarm, bounds) + + def __compute_neighbors(self, swarm, k): + """Helper method to compute the adjacency matrix of the topology + + This method computes the adjacency matrix of the topology using + the randomized algorithm proposed in [TSWJ2013]. The resulting + topology is a connected graph. This is achieved by creating three + matrices: + + * adj_matrix : The adjacency matrix of the generated graph. + It's initialized as an identity matrix to + make sure that every particle has itself as + a neighbour. This matrix is the return + value of the method. + * neighbor_matrix : The matrix of randomly generated neighbors. + This matrix is a matrix of shape + :code:`(swarm.n_particles, k)`: + with randomly generated elements. It's used + to create connections in the adj_matrix. + * dist_matrix : The distance matrix computed with Dijkstra's + algorithm. It is used to determine where the + graph needs edges to change it to a connected + graph. + + Parameters + ---------- + swarm : pyswarms.backend.swarms.Swarm + a Swarm instance + k : int + number of neighbors to be considered. Must be a + positive integer less than :code:`n_particles-1` + + Returns + ------- + numpy.ndarray + Adjacency matrix of the topology + """ + + adj_matrix = np.identity(swarm.n_particles, dtype=int) + + neighbor_matrix = np.array( + [np.random.choice( + # Exclude i from the array + np.setdiff1d( + np.arange(swarm.n_particles), np.array([i]) + ), k, replace=False + ) for i in range(swarm.n_particles)]) + + # Set random elements to one using the neighbor matrix + adj_matrix[np.arange(swarm.n_particles).reshape(swarm.n_particles, 1), neighbor_matrix] = 1 + adj_matrix[neighbor_matrix, np.arange(swarm.n_particles).reshape(swarm.n_particles, 1)] = 1 + + dist_matrix = dijkstra(adj_matrix, directed=False, return_predecessors=False, unweighted=True) + + # Generate connected graph. + while connected_components(adj_matrix, directed=False, return_labels=False) != 1: + for i, j in itertools.product(range(swarm.n_particles), repeat=2): + if dist_matrix[i][j] == np.inf: + adj_matrix[i][j] = 1 + + return adj_matrix diff --git a/pyswarms/backend/topology/ring.py b/pyswarms/backend/topology/ring.py index 2e7034d7..bef98219 100644 --- a/pyswarms/backend/topology/ring.py +++ b/pyswarms/backend/topology/ring.py @@ -3,8 +3,9 @@ """ A Ring Network Topology -This class implements a star topology where all particles are connected in a -ring-like fashion. This social behavior is often found in LocalBest PSO +This class implements a ring topology. In this topology, +the particles are connected with their k nearest neighbors. +This social behavior is often found in LocalBest PSO optimizers. """ @@ -22,16 +23,23 @@ # Create a logger logger = logging.getLogger(__name__) + class Ring(Topology): + def __init__(self, static=False): + """Initializes the class - def __init__(self): - super(Ring, self).__init__() + Parameters + ---------- + static : bool (Default is :code:`False`) + a boolean that decides whether the topology + is static or dynamic""" + super(Ring, self).__init__(static) def compute_gbest(self, swarm, p, k): - """Updates the global best using a neighborhood approach + """Update the global best using a ring-like neighborhood approach This uses the cKDTree method from :code:`scipy` to obtain the nearest - neighbours + neighbors. Parameters ---------- @@ -53,46 +61,54 @@ def compute_gbest(self, swarm, p, k): Best cost """ try: - # Obtain the nearest-neighbors for each particle - tree = cKDTree(swarm.position) - _, idx = tree.query(swarm.position, p=p, k=k) + # Check if the topology is static or not and assign neighbors + if (self.static and self.neighbor_idx is None) or not self.static: + # Obtain the nearest-neighbors for each particle + tree = cKDTree(swarm.position) + _, self.neighbor_idx = tree.query(swarm.position, p=p, k=k) # Map the computed costs to the neighbour indices and take the # argmin. If k-neighbors is equal to 1, then the swarm acts # independently of each other. if k == 1: # The minimum index is itself, no mapping needed. - best_neighbor = swarm.pbest_cost[idx][:, np.newaxis].argmin(axis=1) + best_neighbor = swarm.pbest_cost[self.neighbor_idx][:, np.newaxis].argmin( + axis=0 + ) else: - idx_min = swarm.pbest_cost[idx].argmin(axis=1) - best_neighbor = idx[np.arange(len(idx)), idx_min] + idx_min = swarm.pbest_cost[self.neighbor_idx].argmin(axis=1) + best_neighbor = self.neighbor_idx[np.arange(len(self.neighbor_idx)), idx_min] # Obtain best cost and position best_cost = np.min(swarm.pbest_cost[best_neighbor]) - best_pos = swarm.pbest_pos[np.argmin(swarm.pbest_cost[best_neighbor])] + best_pos = swarm.pbest_pos[ + best_neighbor[np.argmin(swarm.pbest_cost[best_neighbor])] + ] except AttributeError: - msg = 'Please pass a Swarm class. You passed {}'.format(type(swarm)) + msg = "Please pass a Swarm class. You passed {}".format( + type(swarm) + ) logger.error(msg) raise else: return (best_pos, best_cost) def compute_velocity(self, swarm, clamp=None): - """Computes the velocity matrix + """Compute the velocity matrix This method updates the velocity matrix using the best and current positions of the swarm. The velocity matrix is computed using the cognitive and social terms of the swarm. - + A sample usage can be seen with the following: .. code-block :: python import pyswarms.backend as P from pyswarms.swarms.backend import Swarm - from pyswarms.backend.topology import Star + from pyswarms.backend.topology import Ring my_swarm = P.create_swarm(n_particles, dimensions) - my_topology = Ring() + my_topology = Ring(static=False) for i in range(iters): # Inside the for-loop @@ -115,7 +131,7 @@ def compute_velocity(self, swarm, clamp=None): return ops.compute_velocity(swarm, clamp) def compute_position(self, swarm, bounds=None): - """Updates the position matrix + """Update the position matrix This method updates the position matrix given the current position and the velocity. If bounded, it waives updating the position. @@ -134,4 +150,4 @@ def compute_position(self, swarm, bounds=None): numpy.ndarray New position-matrix """ - return ops.compute_position(swarm, bounds) \ No newline at end of file + return ops.compute_position(swarm, bounds) diff --git a/pyswarms/backend/topology/star.py b/pyswarms/backend/topology/star.py index f4203340..8c666b2d 100644 --- a/pyswarms/backend/topology/star.py +++ b/pyswarms/backend/topology/star.py @@ -3,8 +3,9 @@ """ A Star Network Topology -This class implements a star topology where all particles are connected to -one another. This social behavior is often found in GlobalBest PSO +This class implements a star topology. In this topology, +all particles are connected to one another. This social +behavior is often found in GlobalBest PSO optimizers. """ @@ -21,13 +22,13 @@ # Create a logger logger = logging.getLogger(__name__) -class Star(Topology): +class Star(Topology): def __init__(self): - super(Star, self).__init__() + super(Star, self).__init__(static=True) def compute_gbest(self, swarm): - """Obtains the global best cost and position based on a star topology + """Update the global best using a star topology This method takes the current pbest_pos and pbest_cost, then returns the minimum cost and position from the matrix. It should be used in @@ -60,22 +61,26 @@ def compute_gbest(self, swarm): Best cost """ try: + if self.neighbor_idx is None: + self.neighbor_idx = np.tile(np.arange(swarm.n_particles), (swarm.n_particles, 1)) best_pos = swarm.pbest_pos[np.argmin(swarm.pbest_cost)] best_cost = np.min(swarm.pbest_cost) except AttributeError: - msg = 'Please pass a Swarm class. You passed {}'.format(type(swarm)) + msg = "Please pass a Swarm class. You passed {}".format( + type(swarm) + ) logger.error(msg) raise else: return (best_pos, best_cost) def compute_velocity(self, swarm, clamp=None): - """Computes the velocity matrix + """Compute the velocity matrix This method updates the velocity matrix using the best and current positions of the swarm. The velocity matrix is computed using the cognitive and social terms of the swarm. - + A sample usage can be seen with the following: .. code-block :: python @@ -108,7 +113,7 @@ def compute_velocity(self, swarm, clamp=None): return ops.compute_velocity(swarm, clamp) def compute_position(self, swarm, bounds=None): - """Updates the position matrix + """Update the position matrix This method updates the position matrix given the current position and the velocity. If bounded, it waives updating the position. @@ -127,4 +132,4 @@ def compute_position(self, swarm, bounds=None): numpy.ndarray New position-matrix """ - return ops.compute_position(swarm, bounds) \ No newline at end of file + return ops.compute_position(swarm, bounds) diff --git a/pyswarms/backend/topology/von_neumann.py b/pyswarms/backend/topology/von_neumann.py new file mode 100644 index 00000000..ad44cb85 --- /dev/null +++ b/pyswarms/backend/topology/von_neumann.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- + +""" +A Von Neumann Network Topology + +This class implements a Von Neumann topology. +""" + +# Import from stdlib +import logging + +from .ring import Ring + +# Create a logger +logger = logging.getLogger(__name__) + + +class VonNeumann(Ring): + def __init__(self): + super(VonNeumann, self).__init__(static=True) + + def compute_gbest(self, swarm, p, r): + """Updates the global best using a neighborhood approach + + The Von Neumann topology inherits from the Ring topology and uses + the same approach to calculate the global best. The number of + neighbors is determined by the dimension and the range. This + topology is always a :code:`static` topology. + + Parameters + ---------- + swarm : pyswarms.backend.swarms.Swarm + a Swarm instance + r : int + range of the Von Neumann topology + p: int {1,2} + the Minkowski p-norm to use. 1 is the + sum-of-absolute values (or L1 distance) while 2 is + the Euclidean (or L2) distance. + + Returns + ------- + numpy.ndarray + Best position of shape :code:`(n_dimensions, )` + float + Best cost + """ + neighbors = VonNeumann.delannoy(swarm.dimensions, r) + return super(VonNeumann, self).compute_gbest(swarm, p, neighbors) + + @staticmethod + def delannoy(d, r): + """Static helper method to compute Delannoy numbers + + This method computes the number of neighbours of a Von Neumann + topology, i.e. a Delannoy number, dependent on the range and the + dimension of the search space. The Delannoy numbers are computed + recursively. + + Parameters + ---------- + d : int + dimension of the search space + r : int + range of the Von Neumann topology + + Returns + ------- + int + Delannoy number""" + if d == 0 or r == 0: + return 1 + else: + del_number = (VonNeumann.delannoy(d - 1, r) + + VonNeumann.delannoy(d - 1, r - 1) + + VonNeumann.delannoy(d, r - 1) + ) + return del_number diff --git a/pyswarms/base/__init__.py b/pyswarms/base/__init__.py index a225d367..6c0d4b79 100644 --- a/pyswarms/base/__init__.py +++ b/pyswarms/base/__init__.py @@ -6,7 +6,4 @@ from .base_single import SwarmOptimizer from .base_discrete import DiscreteSwarmOptimizer -__all__ = [ - "SwarmOptimizer" - "DiscreteSwarmOptimizer", - ] +__all__ = ["SwarmOptimizer" "DiscreteSwarmOptimizer"] diff --git a/pyswarms/base/base_discrete.py b/pyswarms/base/base_discrete.py index ec473c2b..382e972f 100644 --- a/pyswarms/base/base_discrete.py +++ b/pyswarms/base/base_discrete.py @@ -38,10 +38,10 @@ # Import from package from ..backend import create_swarm -class DiscreteSwarmOptimizer(object): +class DiscreteSwarmOptimizer(object): def assertions(self): - """Assertion method to check various inputs. + """Check inputs and throw assertions Raises ------ @@ -59,20 +59,27 @@ def assertions(self): # Check clamp settings if self.velocity_clamp is not None: if not isinstance(self.velocity_clamp, tuple): - raise TypeError('Parameter `velocity_clamp` must be a tuple') + raise TypeError("Parameter `velocity_clamp` must be a tuple") if not len(self.velocity_clamp) == 2: - raise IndexError('Parameter `velocity_clamp` must be of ' - 'size 2') + raise IndexError( + "Parameter `velocity_clamp` must be of " "size 2" + ) if not self.velocity_clamp[0] < self.velocity_clamp[1]: - raise ValueError('Make sure that velocity_clamp is in the ' - 'form (v_min, v_max)') + raise ValueError( + "Make sure that velocity_clamp is in the " + "form (v_min, v_max)" + ) # Required keys in options argument - if not all(key in self.options for key in ('c1', 'c2', 'w')): - raise KeyError('Missing either c1, c2, or w in options') - - def setup_logging(self, default_path='./config/logging.yaml', - default_level=logging.INFO, env_key='LOG_CFG'): + if not all(key in self.options for key in ("c1", "c2", "w")): + raise KeyError("Missing either c1, c2, or w in options") + + def setup_logging( + self, + default_path="./config/logging.yaml", + default_level=logging.INFO, + env_key="LOG_CFG", + ): """Setup logging configuration Parameters @@ -89,15 +96,23 @@ def setup_logging(self, default_path='./config/logging.yaml', if value: path = value if os.path.exists(path): - with open(path, 'rt') as f: + with open(path, "rt") as f: config = yaml.safe_load(f.read()) logging.config.dictConfig(config) else: logging.basicConfig(level=default_level) - def __init__(self, n_particles, dimensions, binary, options, - velocity_clamp=None, init_pos=None, ftol=-np.inf): - """Initializes the swarm. + def __init__( + self, + n_particles, + dimensions, + binary, + options, + velocity_clamp=None, + init_pos=None, + ftol=-np.inf, + ): + """Initialize the swarm. Creates a :code:`numpy.ndarray` of positions depending on the number of particles needed and the number of dimensions. @@ -115,7 +130,7 @@ def __init__(self, n_particles, dimensions, binary, options, initial positions. When passed with a :code:`False` value, random integers from 0 to :code:`dimensions` are generated. options : dict with keys :code:`{'c1', 'c2', 'w'}` - a dictionary containing the parameters for the specific + a dictionary containing the parameters for the specific optimization technique * c1 : float cognitive parameter @@ -142,10 +157,16 @@ def __init__(self, n_particles, dimensions, binary, options, self.init_pos = init_pos self.ftol = ftol # Initialize named tuple for populating the history list - self.ToHistory = namedtuple('ToHistory', - ['best_cost', 'mean_pbest_cost', - 'mean_neighbor_cost', 'position', - 'velocity']) + self.ToHistory = namedtuple( + "ToHistory", + [ + "best_cost", + "mean_pbest_cost", + "mean_neighbor_cost", + "position", + "velocity", + ], + ) # Invoke assertions self.assertions() @@ -153,7 +174,7 @@ def __init__(self, n_particles, dimensions, binary, options, self.reset() def _populate_history(self, hist): - """Populates all history lists + """Populate all history lists The :code:`cost_history`, :code:`mean_pbest_history`, and :code:`neighborhood_best` is expected to have a shape of @@ -172,37 +193,12 @@ def _populate_history(self, hist): self.pos_history.append(hist.position) self.velocity_history.append(hist.velocity) - @property - def get_cost_history(self): - """Get cost history""" - return np.array(self.cost_history) - - @property - def get_mean_pbest_history(self): - """Get mean personal best history""" - return np.array(self.mean_pbest_history) - - @property - def get_mean_neighbor_history(self): - """Get mean neighborhood cost history""" - return np.array(self.mean_neighbor_history) - - @property - def get_pos_history(self): - """Get position history""" - return np.array(self.pos_history) - - @property - def get_velocity_history(self): - """Get velocity history""" - return np.array(self.velocity_history) - - def optimize(self, objective_func, iters, print_step=1, verbose=1): - """Optimizes the swarm for a number of iterations. + def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): + """Optimize the swarm for a number of iterations Performs the optimization to evaluate the objective function :code:`objective_func` for a number of iterations - :code:`iter.` + :code:`iter.` Parameters ---------- @@ -214,6 +210,8 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1): amount of steps for printing into console. verbose : int (the default is 1) verbosity setting. + kwargs : dict + arguments for objective function Raises ------ @@ -223,7 +221,7 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1): raise NotImplementedError("SwarmBase::optimize()") def reset(self): - """Resets the attributes of the optimizer. + """Reset the attributes of the optimizer All variables/atributes that will be re-initialized when this method is defined here. Note that this method @@ -253,9 +251,12 @@ def reset(self): self.velocity_history = [] # Initialize the swarm - self.swarm = create_swarm(n_particles=self.n_particles, - dimensions=self.dimensions, - discrete=True, - init_pos=self.init_pos, - binary=self.binary, - clamp=self.velocity_clamp, options=self.options) \ No newline at end of file + self.swarm = create_swarm( + n_particles=self.n_particles, + dimensions=self.dimensions, + discrete=True, + init_pos=self.init_pos, + binary=self.binary, + clamp=self.velocity_clamp, + options=self.options, + ) diff --git a/pyswarms/base/base_single.py b/pyswarms/base/base_single.py index 5aa79bd3..ce1cbacc 100644 --- a/pyswarms/base/base_single.py +++ b/pyswarms/base/base_single.py @@ -4,7 +4,7 @@ Base class for single-objective Particle Swarm Optimization implementations. -All methods here are abstract and raises a :code:`NotImplementedError` +All methods here are abstract and raise a :code:`NotImplementedError` when not used. When defining your own swarm implementation, create another class, @@ -27,6 +27,7 @@ -------- :mod:`pyswarms.single.global_best`: global-best PSO implementation :mod:`pyswarms.single.local_best`: local-best PSO implementation +:mod:`pyswarms.single.general_optimizer`: a more general PSO implementation with a custom topology """ import os @@ -39,10 +40,10 @@ # Import from package from ..backend import create_swarm -class SwarmOptimizer(object): +class SwarmOptimizer(object): def assertions(self): - """Assertion method to check various inputs. + """Check inputs and throw assertions Raises ------ @@ -60,43 +61,58 @@ def assertions(self): # Check setting of bounds if self.bounds is not None: if not isinstance(self.bounds, tuple): - raise TypeError('Parameter `bound` must be a tuple.') + raise TypeError("Parameter `bound` must be a tuple.") if not len(self.bounds) == 2: - raise IndexError('Parameter `bound` must be of size 2.') + raise IndexError("Parameter `bound` must be of size 2.") if not self.bounds[0].shape == self.bounds[1].shape: - raise IndexError('Arrays in `bound` must be of equal shapes') - if not self.bounds[0].shape[0] == self.bounds[1].shape[0] == \ - self.dimensions: - raise IndexError('Parameter `bound` must be the same shape ' - 'as dimensions.') + raise IndexError("Arrays in `bound` must be of equal shapes") + if ( + not self.bounds[0].shape[0] + == self.bounds[1].shape[0] + == self.dimensions + ): + raise IndexError( + "Parameter `bound` must be the same shape " + "as dimensions." + ) if not (self.bounds[1] > self.bounds[0]).all(): - raise ValueError('Values of `bounds[1]` must be greater than ' - '`bounds[0]`.') + raise ValueError( + "Values of `bounds[1]` must be greater than " + "`bounds[0]`." + ) # Check clamp settings - if hasattr(self.velocity_clamp, '__iter__'): + if hasattr(self.velocity_clamp, "__iter__"): if not len(self.velocity_clamp) == 2: - raise IndexError('Parameter `velocity_clamp` must be of ' - 'size 2') + raise IndexError( + "Parameter `velocity_clamp` must be of " "size 2" + ) if not self.velocity_clamp[0] < self.velocity_clamp[1]: - raise ValueError('Make sure that velocity_clamp is in the ' - 'form (min, max)') + raise ValueError( + "Make sure that velocity_clamp is in the " + "form (min, max)" + ) # Check setting of center if isinstance(self.center, (list, np.ndarray)): if not len(self.center) == self.dimensions: - raise IndexError('Parameter `center` must be the same shape ' - 'as dimensions.') + raise IndexError( + "Parameter `center` must be the same shape " + "as dimensions." + ) if isinstance(self.center, np.ndarray) and self.center.ndim != 1: - raise ValueError('Parameter `center` must have a 1d array') - + raise ValueError("Parameter `center` must have a 1d array") # Required keys in options argument - if not all(key in self.options for key in ('c1', 'c2', 'w')): - raise KeyError('Missing either c1, c2, or w in options') - - def setup_logging(self, default_path='./config/logging.yaml', - default_level=logging.INFO, env_key='LOG_CFG'): + if not all(key in self.options for key in ("c1", "c2", "w")): + raise KeyError("Missing either c1, c2, or w in options") + + def setup_logging( + self, + default_path="./config/logging.yaml", + default_level=logging.INFO, + env_key="LOG_CFG", + ): """Setup logging configuration Parameters @@ -113,15 +129,24 @@ def setup_logging(self, default_path='./config/logging.yaml', if value: path = value if os.path.exists(path): - with open(path, 'rt') as f: + with open(path, "rt") as f: config = yaml.safe_load(f.read()) logging.config.dictConfig(config) else: logging.basicConfig(level=default_level) - def __init__(self, n_particles, dimensions, options, bounds=None, - velocity_clamp=None, center=1.0, ftol=-np.inf, init_pos=None): - """Initializes the swarm. + def __init__( + self, + n_particles, + dimensions, + options, + bounds=None, + velocity_clamp=None, + center=1.0, + ftol=-np.inf, + init_pos=None, + ): + """Initialize the swarm Creates a Swarm class depending on the values initialized @@ -165,17 +190,23 @@ def __init__(self, n_particles, dimensions, options, bounds=None, self.ftol = ftol self.init_pos = init_pos # Initialize named tuple for populating the history list - self.ToHistory = namedtuple('ToHistory', - ['best_cost', 'mean_pbest_cost', - 'mean_neighbor_cost', 'position', - 'velocity']) + self.ToHistory = namedtuple( + "ToHistory", + [ + "best_cost", + "mean_pbest_cost", + "mean_neighbor_cost", + "position", + "velocity", + ], + ) # Invoke assertions self.assertions() # Initialize resettable attributes self.reset() def _populate_history(self, hist): - """Populates all history lists + """Populate all history lists The :code:`cost_history`, :code:`mean_pbest_history`, and :code:`neighborhood_best` is expected to have a shape of @@ -194,33 +225,8 @@ def _populate_history(self, hist): self.pos_history.append(hist.position) self.velocity_history.append(hist.velocity) - @property - def get_cost_history(self): - """Get cost history""" - return np.array(self.cost_history) - - @property - def get_mean_pbest_history(self): - """Get mean personal best history""" - return np.array(self.mean_pbest_history) - - @property - def get_mean_neighbor_history(self): - """Get mean neighborhood cost history""" - return np.array(self.mean_neighbor_history) - - @property - def get_pos_history(self): - """Get position history""" - return np.array(self.pos_history) - - @property - def get_velocity_history(self): - """Get velocity history""" - return np.array(self.velocity_history) - - def optimize(self, objective_func, iters, print_step=1, verbose=1): - """Optimizes the swarm for a number of iterations. + def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): + """Optimize the swarm for a number of iterations Performs the optimization to evaluate the objective function :code:`objective_func` for a number of iterations @@ -236,6 +242,8 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1): amount of steps for printing into console. verbose : int (the default is 1) verbosity setting. + kwargs : dict + arguments for objective function Raises ------ @@ -245,7 +253,7 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1): raise NotImplementedError("SwarmOptimizer::optimize()") def reset(self): - """Resets the attributes of the optimizer. + """Reset the attributes of the optimizer All variables/atributes that will be re-initialized when this method is defined here. Note that this method @@ -275,7 +283,12 @@ def reset(self): self.velocity_history = [] # Initialize the swarm - self.swarm = create_swarm(n_particles=self.n_particles, - dimensions=self.dimensions, - bounds=self.bounds, center=self.center, init_pos=self.init_pos, - clamp=self.velocity_clamp, options=self.options) + self.swarm = create_swarm( + n_particles=self.n_particles, + dimensions=self.dimensions, + bounds=self.bounds, + center=self.center, + init_pos=self.init_pos, + clamp=self.velocity_clamp, + options=self.options, + ) diff --git a/pyswarms/discrete/__init__.py b/pyswarms/discrete/__init__.py index 8610851b..a7120805 100644 --- a/pyswarms/discrete/__init__.py +++ b/pyswarms/discrete/__init__.py @@ -6,6 +6,4 @@ from .binary import BinaryPSO -__all__ = [ - "BinaryPSO" - ] +__all__ = ["BinaryPSO"] diff --git a/pyswarms/discrete/binary.py b/pyswarms/discrete/binary.py index a3072b74..72d8d536 100644 --- a/pyswarms/discrete/binary.py +++ b/pyswarms/discrete/binary.py @@ -19,10 +19,10 @@ For the velocity update rule, a particle compares its current position with respect to its neighbours. The nearest neighbours are being determined by a kD-tree given a distance metric, similar to local-best -PSO. However, this whole behavior can be modified into a global-best PSO -by changing the nearest neighbours equal to the number of particles in -the swarm. In this case, all particles see each other, and thus a global -best particle can be established. +PSO. The neighbours are computed for every iteration. However, this whole +behavior can be modified into a global-best PSO by changing the nearest +neighbours equal to the number of particles in the swarm. In this case, +all particles see each other, and thus a global best particle can be established. In addition, one notable change for binary PSO is that the position update rule is now decided upon by the following case expression: @@ -63,10 +63,10 @@ from ..backend.topology import Ring from ..utils.console_utils import cli_print, end_report -class BinaryPSO(DiscreteSwarmOptimizer): +class BinaryPSO(DiscreteSwarmOptimizer): def assertions(self): - """Assertion method to check various inputs. + """Check inputs and throw assertions Raises ------ @@ -78,18 +78,28 @@ def assertions(self): """ super(BinaryPSO, self).assertions() - if not all(key in self.options for key in ('k', 'p')): - raise KeyError('Missing either k or p in options') + if not all(key in self.options for key in ("k", "p")): + raise KeyError("Missing either k or p in options") if not 0 <= self.k <= self.n_particles: - raise ValueError('No. of neighbors must be between 0 and no. of' - 'particles.') + raise ValueError( + "No. of neighbors must be between 0 and no. of" "particles." + ) if self.p not in [1, 2]: - raise ValueError('p-value should either be 1 (for L1/Minkowski)' - 'or 2 (for L2/Euclidean).') - - def __init__(self, n_particles, dimensions, options, init_pos=None, - velocity_clamp=None, ftol=-np.inf): - """Initializes the swarm. + raise ValueError( + "p-value should either be 1 (for L1/Minkowski)" + "or 2 (for L2/Euclidean)." + ) + + def __init__( + self, + n_particles, + dimensions, + options, + init_pos=None, + velocity_clamp=None, + ftol=-np.inf, + ): + """Initialize the swarm Attributes ---------- @@ -121,20 +131,26 @@ def __init__(self, n_particles, dimensions, options, init_pos=None, # Initialize logger self.logger = logging.getLogger(__name__) # Assign k-neighbors and p-value as attributes - self.k, self.p = options['k'], options['p'] + self.k, self.p = options["k"], options["p"] # Initialize parent class - super(BinaryPSO, self).__init__(n_particles=n_particles, dimensions=dimensions, - binary=True, options=options, init_pos=init_pos, - velocity_clamp=velocity_clamp, ftol=ftol) + super(BinaryPSO, self).__init__( + n_particles=n_particles, + dimensions=dimensions, + binary=True, + options=options, + init_pos=init_pos, + velocity_clamp=velocity_clamp, + ftol=ftol, + ) # Invoke assertions self.assertions() # Initialize the resettable attributes self.reset() # Initialize the topology - self.top = Ring() + self.top = Ring(static=False) - def optimize(self, objective_func, iters, print_step=1, verbose=1): - """Optimizes the swarm for a number of iterations. + def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): + """Optimize the swarm for a number of iterations Performs the optimization to evaluate the objective function :code:`f` for a number of iterations :code:`iter.` @@ -149,6 +165,8 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1): amount of steps for printing into console. verbose : int (the default is 1) verbosity setting. + kwargs : dict + arguments for objective function Returns ------- @@ -156,43 +174,60 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1): the local best cost and the local best position among the swarm. """ + cli_print("Arguments Passed to Objective Function: {}".format(kwargs), + verbose, 2, logger=self.logger) + for i in range(iters): # Compute cost for current position and personal best - self.swarm.current_cost = objective_func(self.swarm.position) - self.swarm.pbest_cost = objective_func(self.swarm.pbest_pos) - self.swarm.pbest_pos, self.swarm.pbest_cost = compute_pbest(self.swarm) + self.swarm.current_cost = objective_func(self.swarm.position, **kwargs) + self.swarm.pbest_cost = objective_func(self.swarm.pbest_pos, **kwargs) + self.swarm.pbest_pos, self.swarm.pbest_cost = compute_pbest( + self.swarm + ) best_cost_yet_found = np.min(self.swarm.best_cost) # Update gbest from neighborhood - self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest(self.swarm, - self.p, - self.k) + self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest( + self.swarm, self.p, self.k + ) # Print to console if i % print_step == 0: - cli_print('Iteration %s/%s, cost: %s' % - (i+1, iters, np.min(self.swarm.best_cost)), verbose, 2, - logger=self.logger) + cli_print( + "Iteration {}/{}, cost: {}".format(i + 1, iters, np.min(self.swarm.best_cost)), + verbose, + 2, + logger=self.logger, + ) # Save to history - hist = self.ToHistory(best_cost=self.swarm.best_cost, - mean_pbest_cost=np.mean(self.swarm.pbest_cost), - mean_neighbor_cost=np.mean(self.swarm.best_cost), - position=self.swarm.position, - velocity=self.swarm.velocity) + hist = self.ToHistory( + best_cost=self.swarm.best_cost, + mean_pbest_cost=np.mean(self.swarm.pbest_cost), + mean_neighbor_cost=np.mean(self.swarm.best_cost), + position=self.swarm.position, + velocity=self.swarm.velocity, + ) self._populate_history(hist) # Verify stop criteria based on the relative acceptable cost ftol - relative_measure = self.ftol*(1 + np.abs(best_cost_yet_found)) - if np.abs(self.swarm.best_cost - best_cost_yet_found) < relative_measure: + relative_measure = self.ftol * (1 + np.abs(best_cost_yet_found)) + if ( + np.abs(self.swarm.best_cost - best_cost_yet_found) + < relative_measure + ): break # Perform position velocity update - self.swarm.velocity = self.top.compute_velocity(self.swarm, self.velocity_clamp) + self.swarm.velocity = self.top.compute_velocity( + self.swarm, self.velocity_clamp + ) self.swarm.position = self._compute_position(self.swarm) # Obtain the final best_cost and the final best_position final_best_cost = self.swarm.best_cost.copy() final_best_pos = self.swarm.best_pos.copy() - end_report(final_best_cost, final_best_pos, verbose, logger=self.logger) + end_report( + final_best_cost, final_best_pos, verbose, logger=self.logger + ) return (final_best_cost, final_best_pos) def _compute_position(self, swarm): - """Updates the position matrix of the swarm. + """Update the position matrix of the swarm This computes the next position in a binary swarm. It compares the sigmoid output of the velocity-matrix and compares it with a randomly @@ -203,11 +238,13 @@ def _compute_position(self, swarm): swarm: pyswarms.backend.swarms.Swarm a Swarm class """ - return (np.random.random_sample(size=swarm.dimensions) < self._sigmoid - (swarm.velocity)) * 1 + return ( + np.random.random_sample(size=swarm.dimensions) + < self._sigmoid(swarm.velocity) + ) * 1 def _sigmoid(self, x): - """Helper method for the sigmoid function. + """Helper method for the sigmoid function Parameters ---------- @@ -220,4 +257,3 @@ def _sigmoid(self, x): Output sigmoid computation """ return 1 / (1 + np.exp(-x)) - diff --git a/pyswarms/single/__init__.py b/pyswarms/single/__init__.py index 2e7a96a1..8666dcca 100644 --- a/pyswarms/single/__init__.py +++ b/pyswarms/single/__init__.py @@ -2,12 +2,20 @@ The :mod:`pyswarms.single` module implements various techniques in continuous single-objective optimization. These require only one objective function that can be optimized in a continuous space. + +.. note:: + PSO algorithms scale with the search space. This means that, by + using larger boundaries, the final results are getting larger + as well. + +.. note:: + Please keep in mind that Python has a biggest float number. + So using large boundaries in combination with exponentiation or + multiplication can lead to an :code:`OverflowError`. """ from .global_best import GlobalBestPSO from .local_best import LocalBestPSO +from .general_optimizer import GeneralOptimizerPSO -__all__ = [ - "GlobalBestPSO", - "LocalBestPSO" - ] +__all__ = ["GlobalBestPSO", "LocalBestPSO", "GeneralOptimizerPSO"] diff --git a/pyswarms/single/general_optimizer.py b/pyswarms/single/general_optimizer.py new file mode 100644 index 00000000..33114327 --- /dev/null +++ b/pyswarms/single/general_optimizer.py @@ -0,0 +1,317 @@ +# -*- coding: utf-8 -*- + +r""" +A general Particle Swarm Optimization (general PSO) algorithm. + +It takes a set of candidate solutions, and tries to find the best +solution using a position-velocity update method. Uses a user specified +topology. + +The position update can be defined as: + +.. math:: + + x_{i}(t+1) = x_{i}(t) + v_{i}(t+1) + +Where the position at the current timestep :math:`t` is updated using +the computed velocity at :math:`t+1`. Furthermore, the velocity update +is defined as: + +.. math:: + + v_{ij}(t + 1) = m * v_{ij}(t) + c_{1}r_{1j}(t)[y_{ij}(t) − x_{ij}(t)] + + c_{2}r_{2j}(t)[\hat{y}_{j}(t) − x_{ij}(t)] + +Here, :math:`c1` and :math:`c2` are the cognitive and social parameters +respectively. They control the particle's behavior given two choices: (1) to +follow its *personal best* or (2) follow the swarm's *global best* position. +Overall, this dictates if the swarm is explorative or exploitative in nature. +In addition, a parameter :math:`w` controls the inertia of the swarm's +movement. + +An example usage is as follows: + +.. code-block:: python + + import pyswarms as ps + from pyswarms.backend.topology import Pyramid + from pyswarms.utils.functions import single_obj as fx + + # Set-up hyperparameters and topology + options = {'c1': 0.5, 'c2': 0.3, 'w':0.9} + my_topology = Pyramid(static=False) + + # Call instance of GlobalBestPSO + optimizer = ps.single.GeneralOptimizerPSO(n_particles=10, dimensions=2, + options=options, topology=my_topology) + + # Perform optimization + stats = optimizer.optimize(fx.sphere_func, iters=100) + +This algorithm was adapted from the earlier works of J. Kennedy and +R.C. Eberhart in Particle Swarm Optimization [IJCNN1995]_. + +.. [IJCNN1995] J. Kennedy and R.C. Eberhart, "Particle Swarm Optimization," + Proceedings of the IEEE International Joint Conference on Neural + Networks, 1995, pp. 1942-1948. +""" +# Import from stdlib +import logging + +# Import modules +import numpy as np + +# Import from package +from ..base import SwarmOptimizer +from ..backend.operators import compute_pbest +from ..backend.topology import Topology, Ring, Random, VonNeumann +from ..utils.console_utils import cli_print, end_report + + +class GeneralOptimizerPSO(SwarmOptimizer): + def __init__( + self, + n_particles, + dimensions, + options, + topology, + bounds=None, + velocity_clamp=None, + center=1.00, + ftol=-np.inf, + init_pos=None, + ): + """Initialize the swarm + + Attributes + ---------- + n_particles : int + number of particles in the swarm. + dimensions : int + number of dimensions in the space. + options : dict with keys :code:`{'c1', 'c2', 'w'}` or :code:`{'c1', 'c2', 'w', 'k', 'p'}` + a dictionary containing the parameters for the specific + optimization technique. + * c1 : float + cognitive parameter + * c2 : float + social parameter + * w : float + inertia parameter + if used with the :code:`Ring`, :code:`VonNeumann` or :code:`Random` topology the additional + parameter k must be included + * k : int + number of neighbors to be considered. Must be a + positive integer less than :code:`n_particles` + if used with the :code:`Ring` topology the additional + parameters k and p must be included + * p: int {1,2} + the Minkowski p-norm to use. 1 is the + sum-of-absolute values (or L1 distance) while 2 is + the Euclidean (or L2) distance. + if used with the :code:`VonNeumann` topology the additional + parameters p and r must be included + * r: int + the range of the VonNeumann topology. + This is used to determine the number of + neighbours in the topology. + topology : pyswarms.backend.topology.Topology + a :code:`Topology` object that defines the topology to use + in the optimization process. The currently available topologies + are: + * Star + All particles are connected + * Ring (static and dynamic) + Particles are connected to the k nearest neighbours + * VonNeumann + Particles are connected in a VonNeumann topology + * Pyramid (static and dynamic) + Particles are connected in N-dimensional simplices + * Random (static and dynamic) + Particles are connected to k random particles + Static variants of the topologies remain with the same neighbours + over the course of the optimization. Dynamic variants calculate + new neighbours every time step. + bounds : tuple of :code:`np.ndarray` (default is :code:`None`) + a tuple of size 2 where the first entry is the minimum bound + while the second entry is the maximum bound. Each array must + be of shape :code:`(dimensions,)`. + velocity_clamp : tuple (default is :code:`None`) + a tuple of size 2 where the first entry is the minimum velocity + and the second entry is the maximum velocity. It + sets the limits for velocity clamping. + center : list (default is :code:`None`) + an array of size :code:`dimensions` + ftol : float + relative error in objective_func(best_pos) acceptable for + convergence + """ + super(GeneralOptimizerPSO, self).__init__( + n_particles, + dimensions=dimensions, + options=options, + bounds=bounds, + velocity_clamp=velocity_clamp, + center=center, + ftol=ftol, + init_pos=init_pos, + ) + + # Initialize logger + self.logger = logging.getLogger(__name__) + # Invoke assertions + self.assertions() + # Initialize the resettable attributes + self.reset() + # Initialize the topology and check for type + if not isinstance(topology, Topology): + raise TypeError("Parameter `topology` must be a Topology object") + else: + self.top = topology + + # Case for the Ring topology + if isinstance(topology, (Ring, VonNeumann)): + # Assign p-value as attributes + self.p = options["p"] + # Exceptions for the p value + if "p" not in self.options: + raise KeyError("Missing p in options") + if self.p not in [1, 2]: + raise ValueError( + "p-value should either be 1 (for L1/Minkowski) " + "or 2 (for L2/Euclidean)." + ) + + # Case for Random, VonNeumann and Ring topologies + if isinstance(topology, (Random, Ring, VonNeumann)): + if not isinstance(topology, VonNeumann): + self.k = options["k"] + if not isinstance(self.k, int): + raise ValueError( + "No. of neighbors must be an integer between" + "0 and no. of particles." + ) + if not 0 <= self.k <= self.n_particles - 1: + raise ValueError( + "No. of neighbors must be between 0 and no. " + "of particles." + ) + if "k" not in self.options: + raise KeyError("Missing k in options") + else: + # Assign range r as attribute + self.r = options["r"] + if not isinstance(self.r, int): + raise ValueError("The range must be a positive integer") + if ( + self.r <= 0 + or not 0 + <= VonNeumann.delannoy(self.swarm.dimensions, self.r) + <= self.n_particles - 1 + ): + raise ValueError( + "The range must be set such that the computed" + "Delannoy number (number of neighbours) is" + "between 0 and the no. of particles." + ) + + def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): + """Optimize the swarm for a number of iterations + + Performs the optimization to evaluate the objective + function :code:`f` for a number of iterations :code:`iter.` + + Parameters + ---------- + objective_func : function + objective function to be evaluated + iters : int + number of iterations + print_step : int (default is 1) + amount of steps for printing into console. + verbose : int (default is 1) + verbosity setting. + kwargs : dict + arguments for the objective function + + Returns + ------- + tuple + the global best cost and the global best position. + """ + + cli_print("Arguments Passed to Objective Function: {}".format(kwargs), + verbose, 2, logger=self.logger) + + for i in range(iters): + # Compute cost for current position and personal best + self.swarm.current_cost = objective_func(self.swarm.position, **kwargs) + self.swarm.pbest_cost = objective_func(self.swarm.pbest_pos, **kwargs) + self.swarm.pbest_pos, self.swarm.pbest_cost = compute_pbest( + self.swarm + ) + best_cost_yet_found = self.swarm.best_cost + # If the topology is a ring topology just use the local minimum + if isinstance(self.top, Ring) and not isinstance(self.top, VonNeumann): + # Update gbest from neighborhood + self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest( + self.swarm, self.p, self.k + ) + # If the topology is a VonNeumann topology pass the neighbour and range attribute to compute_gbest() + if isinstance(self.top, VonNeumann): + # Update gbest from neighborhood + self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest( + self.swarm, self.p, self.r + ) + # If the topology is a random topology pass the neighbor attribute to compute_gbest() + elif isinstance(self.top, Random): + # Get minima of pbest and check if it's less than gbest + if np.min(self.swarm.pbest_cost) < self.swarm.best_cost: + self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest( + self.swarm, self.k + ) + else: + # Get minima of pbest and check if it's less than gbest + if np.min(self.swarm.pbest_cost) < self.swarm.best_cost: + self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest( + self.swarm + ) + # Print to console + if i % print_step == 0: + cli_print( + "Iteration {}/{}, cost: {}".format(i + 1, iters, self.swarm.best_cost), + verbose, + 2, + logger=self.logger + ) + # Save to history + hist = self.ToHistory( + best_cost=self.swarm.best_cost, + mean_pbest_cost=np.mean(self.swarm.pbest_cost), + mean_neighbor_cost=self.swarm.best_cost, + position=self.swarm.position, + velocity=self.swarm.velocity + ) + self._populate_history(hist) + # Verify stop criteria based on the relative acceptable cost ftol + relative_measure = self.ftol * (1 + np.abs(best_cost_yet_found)) + if ( + np.abs(self.swarm.best_cost - best_cost_yet_found) + < relative_measure + ): + break + # Perform velocity and position updates + self.swarm.velocity = self.top.compute_velocity( + self.swarm, self.velocity_clamp + ) + self.swarm.position = self.top.compute_position( + self.swarm, self.bounds + ) + # Obtain the final best_cost and the final best_position + final_best_cost = self.swarm.best_cost.copy() + final_best_pos = self.swarm.best_pos.copy() + # Write report in log and return final cost and position + end_report( + final_best_cost, final_best_pos, verbose, logger=self.logger + ) + return(final_best_cost, final_best_pos) diff --git a/pyswarms/single/global_best.py b/pyswarms/single/global_best.py index c21e35b7..28f06439 100644 --- a/pyswarms/single/global_best.py +++ b/pyswarms/single/global_best.py @@ -20,15 +20,15 @@ .. math:: - v_{ij}(t + 1) = m * v_{ij}(t) + c_{1}r_{1j}(t)[y_{ij}(t) − x_{ij}(t)] + v_{ij}(t + 1) = m * v_{ij}(t) + c_{1}r_{1j}(t)[y_{ij}(t) − x_{ij}(t)] + c_{2}r_{2j}(t)[\hat{y}_{j}(t) − x_{ij}(t)] Here, :math:`c1` and :math:`c2` are the cognitive and social parameters -respectively. They control the particle's options in choosing how to -react given two choices: (1) to follow its *personal best* or (2) follow -the swarm's *global best* position. Overall, this dictates if the swarm -is explorative or exploitative in nature. In addition, a parameter -:math:`w` controls the inertia of the swarm's movement. +respectively. They control the particle's behavior given two choices: (1) to +follow its *personal best* or (2) follow the swarm's *global best* position. +Overall, this dictates if the swarm is explorative or exploitative in nature. +In addition, a parameter :math:`w` controls the inertia of the swarm's +movement. An example usage is as follows: @@ -69,11 +69,18 @@ class GlobalBestPSO(SwarmOptimizer): - - def __init__(self, n_particles, dimensions, options, - bounds=None, velocity_clamp=None, center=1.00, ftol=-np.inf, - init_pos=None): - """Initializes the swarm. + def __init__( + self, + n_particles, + dimensions, + options, + bounds=None, + velocity_clamp=None, + center=1.00, + ftol=-np.inf, + init_pos=None, + ): + """Initialize the swarm Attributes ---------- @@ -104,10 +111,16 @@ def __init__(self, n_particles, dimensions, options, relative error in objective_func(best_pos) acceptable for convergence """ - super(GlobalBestPSO, self).__init__(n_particles=n_particles, dimensions=dimensions, - options=options, bounds=bounds, - velocity_clamp=velocity_clamp, - center=center, ftol=ftol, init_pos=init_pos) + super(GlobalBestPSO, self).__init__( + n_particles=n_particles, + dimensions=dimensions, + options=options, + bounds=bounds, + velocity_clamp=velocity_clamp, + center=center, + ftol=ftol, + init_pos=init_pos, + ) # Initialize logger self.logger = logging.getLogger(__name__) @@ -118,8 +131,8 @@ def __init__(self, n_particles, dimensions, options, # Initialize the topology self.top = Star() - def optimize(self, objective_func, iters, print_step=1, verbose=1): - """Optimizes the swarm for a number of iterations. + def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): + """Optimize the swarm for a number of iterations Performs the optimization to evaluate the objective function :code:`f` for a number of iterations :code:`iter.` @@ -134,44 +147,67 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1): amount of steps for printing into console. verbose : int (default is 1) verbosity setting. + kwargs : dict + arguments for the objective function Returns ------- tuple the global best cost and the global best position. """ + + cli_print("Arguments Passed to Objective Function: {}".format(kwargs), + verbose, 2, logger=self.logger) + for i in range(iters): # Compute cost for current position and personal best - self.swarm.current_cost = objective_func(self.swarm.position) - self.swarm.pbest_cost = objective_func(self.swarm.pbest_pos) - self.swarm.pbest_pos, self.swarm.pbest_cost = compute_pbest(self.swarm) + self.swarm.current_cost = objective_func(self.swarm.position, **kwargs) + self.swarm.pbest_cost = objective_func(self.swarm.pbest_pos, **kwargs) + self.swarm.pbest_pos, self.swarm.pbest_cost = compute_pbest( + self.swarm + ) best_cost_yet_found = self.swarm.best_cost # Get minima of pbest and check if it's less than gbest if np.min(self.swarm.pbest_cost) < self.swarm.best_cost: - self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest(self.swarm) + self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest( + self.swarm + ) # Print to console if i % print_step == 0: - cli_print('Iteration %s/%s, cost: %s' % - (i+1, iters, self.swarm.best_cost), verbose, 2, - logger=self.logger) + cli_print( + "Iteration {}/{}, cost: {}".format(i + 1, iters, self.swarm.best_cost), + verbose, + 2, + logger=self.logger, + ) # Save to history - hist = self.ToHistory(best_cost=self.swarm.best_cost, - mean_pbest_cost=np.mean(self.swarm.pbest_cost), - mean_neighbor_cost=self.swarm.best_cost, - position=self.swarm.position, - velocity=self.swarm.velocity) + hist = self.ToHistory( + best_cost=self.swarm.best_cost, + mean_pbest_cost=np.mean(self.swarm.pbest_cost), + mean_neighbor_cost=self.swarm.best_cost, + position=self.swarm.position, + velocity=self.swarm.velocity, + ) self._populate_history(hist) # Verify stop criteria based on the relative acceptable cost ftol - relative_measure = self.ftol*(1 + np.abs(best_cost_yet_found)) - if np.abs(self.swarm.best_cost - best_cost_yet_found) < relative_measure: + relative_measure = self.ftol * (1 + np.abs(best_cost_yet_found)) + if ( + np.abs(self.swarm.best_cost - best_cost_yet_found) + < relative_measure + ): break # Perform velocity and position updates - self.swarm.velocity = self.top.compute_velocity(self.swarm, self.velocity_clamp) - self.swarm.position = self.top.compute_position(self.swarm, self.bounds) + self.swarm.velocity = self.top.compute_velocity( + self.swarm, self.velocity_clamp + ) + self.swarm.position = self.top.compute_position( + self.swarm, self.bounds + ) # Obtain the final best_cost and the final best_position final_best_cost = self.swarm.best_cost.copy() final_best_pos = self.swarm.best_pos.copy() # Write report in log and return final cost and position - end_report(final_best_cost, final_best_pos, verbose, logger=self.logger) + end_report( + final_best_cost, final_best_pos, verbose, logger=self.logger + ) return (final_best_cost, final_best_pos) - diff --git a/pyswarms/single/local_best.py b/pyswarms/single/local_best.py index e4bb7897..aada1609 100644 --- a/pyswarms/single/local_best.py +++ b/pyswarms/single/local_best.py @@ -31,7 +31,7 @@ In this implementation, a neighbor is selected via a k-D tree imported from :code:`scipy`. Distance are computed with either the L1 or L2 distance. The nearest-neighbours are then queried from -this k-D tree. +this k-D tree. They are computed for every iteration. An example usage is as follows: @@ -78,9 +78,8 @@ class LocalBestPSO(SwarmOptimizer): - def assertions(self): - """Assertion method to check various inputs. + """Check inputs and throw assertions Raises ------ @@ -92,18 +91,31 @@ def assertions(self): """ super(LocalBestPSO, self).assertions() - if not all(key in self.options for key in ('k', 'p')): - raise KeyError('Missing either k or p in options') + if not all(key in self.options for key in ("k", "p")): + raise KeyError("Missing either k or p in options") if not 0 <= self.k <= self.n_particles: - raise ValueError('No. of neighbors must be between 0 and no. ' - 'of particles.') + raise ValueError( + "No. of neighbors must be between 0 and no. " "of particles." + ) if self.p not in [1, 2]: - raise ValueError('p-value should either be 1 (for L1/Minkowski) ' - 'or 2 (for L2/Euclidean).') - - def __init__(self, n_particles, dimensions, options, bounds=None, - velocity_clamp=None, center=1.00, ftol=-np.inf, init_pos=None): - """Initializes the swarm. + raise ValueError( + "p-value should either be 1 (for L1/Minkowski) " + "or 2 (for L2/Euclidean)." + ) + + def __init__( + self, + n_particles, + dimensions, + options, + bounds=None, + velocity_clamp=None, + center=1.00, + ftol=-np.inf, + init_pos=None, + static=False + ): + """Initialize the swarm Attributes ---------- @@ -140,26 +152,34 @@ def __init__(self, n_particles, dimensions, options, bounds=None, the Minkowski p-norm to use. 1 is the sum-of-absolute values (or L1 distance) while 2 is the Euclidean (or L2) distance. + static: bool (Default is :code:`False`) + a boolean that decides whether the Ring topology + used is static or dynamic """ # Initialize logger self.logger = logging.getLogger(__name__) # Assign k-neighbors and p-value as attributes - self.k, self.p = options['k'], options['p'] + self.k, self.p = options["k"], options["p"] # Initialize parent class - super(LocalBestPSO, self).__init__(n_particles=n_particles, dimensions=dimensions, - options=options, bounds=bounds, - velocity_clamp=velocity_clamp, - center=center, ftol=ftol, - init_pos=init_pos) + super(LocalBestPSO, self).__init__( + n_particles=n_particles, + dimensions=dimensions, + options=options, + bounds=bounds, + velocity_clamp=velocity_clamp, + center=center, + ftol=ftol, + init_pos=init_pos, + ) # Invoke assertions self.assertions() # Initialize the resettable attributes self.reset() # Initialize the topology - self.top = Ring() + self.top = Ring(static=static) - def optimize(self, objective_func, iters, print_step=1, verbose=1): - """Optimizes the swarm for a number of iterations. + def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): + """Optimize the swarm for a number of iterations Performs the optimization to evaluate the objective function :code:`f` for a number of iterations :code:`iter.` @@ -174,6 +194,8 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1): amount of steps for printing into console. verbose : int (default is 1) verbosity setting. + kwargs : dict + arguments for the objective function Returns ------- @@ -181,37 +203,56 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1): the local best cost and the local best position among the swarm. """ + cli_print("Arguments Passed to Objective Function: {}".format(kwargs), + verbose, 2, logger=self.logger) + for i in range(iters): # Compute cost for current position and personal best - self.swarm.current_cost = objective_func(self.swarm.position) - self.swarm.pbest_cost = objective_func(self.swarm.pbest_pos) - self.swarm.pbest_pos, self.swarm.pbest_cost = compute_pbest(self.swarm) + self.swarm.current_cost = objective_func(self.swarm.position, **kwargs) + self.swarm.pbest_cost = objective_func(self.swarm.pbest_pos, **kwargs) + self.swarm.pbest_pos, self.swarm.pbest_cost = compute_pbest( + self.swarm + ) best_cost_yet_found = np.min(self.swarm.best_cost) # Update gbest from neighborhood - self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest(self.swarm, - self.p, - self.k) + self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest( + self.swarm, self.p, self.k + ) # Print to console if i % print_step == 0: - cli_print('Iteration %s/%s, cost: %s' % - (i+1, iters, np.min(self.swarm.best_cost)), verbose, 2, - logger=self.logger) + cli_print( + "Iteration {}/{}, cost: {}".format(i + 1, iters, np.min(self.swarm.best_cost)), + verbose, + 2, + logger=self.logger, + ) # Save to history - hist = self.ToHistory(best_cost=self.swarm.best_cost, - mean_pbest_cost=np.mean(self.swarm.pbest_cost), - mean_neighbor_cost=np.mean(self.swarm.best_cost), - position=self.swarm.position, - velocity=self.swarm.velocity) + hist = self.ToHistory( + best_cost=self.swarm.best_cost, + mean_pbest_cost=np.mean(self.swarm.pbest_cost), + mean_neighbor_cost=np.mean(self.swarm.best_cost), + position=self.swarm.position, + velocity=self.swarm.velocity, + ) self._populate_history(hist) # Verify stop criteria based on the relative acceptable cost ftol - relative_measure = self.ftol*(1 + np.abs(best_cost_yet_found)) - if np.abs(self.swarm.best_cost - best_cost_yet_found) < relative_measure: + relative_measure = self.ftol * (1 + np.abs(best_cost_yet_found)) + if ( + np.abs(self.swarm.best_cost - best_cost_yet_found) + < relative_measure + ): break # Perform position velocity update - self.swarm.velocity = self.top.compute_velocity(self.swarm, self.velocity_clamp) - self.swarm.position = self.top.compute_position(self.swarm, self.bounds) + self.swarm.velocity = self.top.compute_velocity( + self.swarm, self.velocity_clamp + ) + self.swarm.position = self.top.compute_position( + self.swarm, self.bounds + ) # Obtain the final best_cost and the final best_position final_best_cost = self.swarm.best_cost.copy() final_best_pos = self.swarm.best_pos.copy() - end_report(final_best_cost, final_best_pos, verbose, logger=self.logger) + end_report( + final_best_cost, final_best_pos, verbose, logger=self.logger + ) return (final_best_cost, final_best_pos) diff --git a/pyswarms/utils/console_utils.py b/pyswarms/utils/console_utils.py index c282b3e9..22ae118d 100644 --- a/pyswarms/utils/console_utils.py +++ b/pyswarms/utils/console_utils.py @@ -49,13 +49,15 @@ def end_report(cost, pos, verbosity, logger): # Cuts the length of the best position if it's too long if len(list(pos)) > 3: - out = ('[ ' + 3 * '{:3f} ' + '...]').format(*list(pos)) + out = ("[ " + 3 * "{:3f} " + "...]").format(*list(pos)) else: out = list(pos) - template = ('================================\n' - 'Optimization finished!\n' - 'Final cost: {:06.4f}\n' - 'Best value: {}\n').format(cost, out) + template = ( + "================================\n" + "Optimization finished!\n" + "Final cost: {:06.4f}\n" + "Best value: {}\n" + ).format(cost, out) if verbosity >= 1: logger.info(template) diff --git a/pyswarms/utils/environments/__init__.py b/pyswarms/utils/environments/__init__.py index bd5d791e..2207de52 100644 --- a/pyswarms/utils/environments/__init__.py +++ b/pyswarms/utils/environments/__init__.py @@ -6,6 +6,4 @@ from .plot_environment import PlotEnvironment -__all__ = [ - "PlotEnvironment" - ] +__all__ = ["PlotEnvironment"] diff --git a/pyswarms/utils/environments/plot_environment.py b/pyswarms/utils/environments/plot_environment.py index bf703357..6e9321d3 100644 --- a/pyswarms/utils/environments/plot_environment.py +++ b/pyswarms/utils/environments/plot_environment.py @@ -3,6 +3,10 @@ r""" Plot environment for Optimizer Analysis +.. deprecated:: 0.2.1 + This module will be deprecated in the next release. Please use + :mod:`pyswarms.utils.plotters` instead. + The class PlotEnvironment is built on top of :code:`matplotlib` in order to render quick and easy plots for your optimizer. It can plot the best cost for each iteration, and show animations of the particles in 2-D and @@ -58,6 +62,7 @@ # Import modules import logging +import warnings import numpy as np import matplotlib.pyplot as plt from past.builtins import xrange @@ -65,30 +70,42 @@ from collections import namedtuple from mpl_toolkits.mplot3d import Axes3D +warnings.simplefilter("default") +warnings.warn( + "The pyswarms.environments module is deprecated and will be removed in v.0.2.5. For visualization, please use pyswarms.plotters", + DeprecationWarning, + stacklevel=2, +) -class PlotEnvironment(object): +class PlotEnvironment(object): def assertions(self): - """Assertion check""" + """Check inputs and throw assertions""" # Check if the objective_func is a callable if not callable(self.objective_func): - raise TypeError('Must pass a callable') + raise TypeError("Must pass a callable") # Check if getters exist in the optimizer - if not (hasattr(self.optimizer, 'get_cost_history') - & hasattr(self.optimizer, 'get_pos_history') - & hasattr(self.optimizer, 'get_velocity_history')): - raise AttributeError('Missing getters in optimizer, check ' - 'pyswarms.base module') + if not ( + hasattr(self.optimizer, "get_cost_history") + & hasattr(self.optimizer, "get_pos_history") + & hasattr(self.optimizer, "get_velocity_history") + ): + raise AttributeError( + "Missing getters in optimizer, check " "pyswarms.base module" + ) # Check if important methods exist in the optimizer - if not (hasattr(self.optimizer, 'optimize') - & hasattr(self.optimizer, 'reset')): - raise AttributeError('Missing methods in optimizer, check ' - 'pyswarms.base module') + if not ( + hasattr(self.optimizer, "optimize") + & hasattr(self.optimizer, "reset") + ): + raise AttributeError( + "Missing methods in optimizer, check " "pyswarms.base module" + ) def __init__(self, optimizer, objective_func, iters): - """Runs the optimizer against an objective function for a number + """Run the optimizer against an objective function for a number of iterations Upon initialization, the :code:`optimize` method of the optimizer @@ -120,13 +137,20 @@ def __init__(self, optimizer, objective_func, iters): self.optimizer.reset() self.status = self.optimizer.optimize(objective_func, iters, 1, 0) # Initialize tuples for particle plotting - self.Index = namedtuple('Index', ['x', 'y', 'z']) - self.Limit = namedtuple('Limit', ['x', 'y', 'z']) - self.Label = namedtuple('Label', ['x', 'y', 'z']) - - def plot_cost(self, title='Cost History', ax=None, figsize=None, - title_fontsize="large", text_fontsize="medium", **kwargs): - """Creates a simple line plot with the cost in the y-axis and + self.Index = namedtuple("Index", ["x", "y", "z"]) + self.Limit = namedtuple("Limit", ["x", "y", "z"]) + self.Label = namedtuple("Label", ["x", "y", "z"]) + + def plot_cost( + self, + title="Cost History", + ax=None, + figsize=None, + title_fontsize="large", + text_fontsize="medium", + **kwargs + ): + """Create a simple line plot with the cost in the y-axis and the iteration at the x-axis Parameters @@ -167,28 +191,46 @@ def plot_cost(self, title='Cost History', ax=None, figsize=None, # Plot with self.iters as x-axis and cost_history as # y-axis. - ax.plot(np.arange(self.iters), cost_history, 'k', lw=2, - label='Best cost') - ax.plot(np.arange(self.iters), mean_pbest_history, 'k--', lw=2, - label='Avg. personal best cost') - ax.plot(np.arange(self.iters), mean_neighbor_history, 'k:', lw=2, - label='Avg. neighborhood cost') + ax.plot( + np.arange(self.iters), cost_history, "k", lw=2, label="Best cost" + ) + ax.plot( + np.arange(self.iters), + mean_pbest_history, + "k--", + lw=2, + label="Avg. personal best cost", + ) + ax.plot( + np.arange(self.iters), + mean_neighbor_history, + "k:", + lw=2, + label="Avg. neighborhood cost", + ) # Customize plot depending on parameters ax.set_title(title, fontsize=title_fontsize) ax.legend(fontsize=text_fontsize) - ax.set_xlabel('Iterations', fontsize=text_fontsize) - ax.set_ylabel('Cost', fontsize=text_fontsize) + ax.set_xlabel("Iterations", fontsize=text_fontsize) + ax.set_ylabel("Cost", fontsize=text_fontsize) ax.tick_params(labelsize=text_fontsize) return ax - def plot_particles2D(self, index=(0, 1), limits=((-1, 1), (-1, 1)), - labels=('x-axis', 'y-axis'), interval=80, - title='Particle Movement in 2D space', - ax=None, figsize=None, title_fontsize="large", - text_fontsize="medium"): - """Creates an animation of particle movement in 2D-space + def plot_particles2D( + self, + index=(0, 1), + limits=((-1, 1), (-1, 1)), + labels=("x-axis", "y-axis"), + interval=80, + title="Particle Movement in 2D space", + ax=None, + figsize=None, + title_fontsize="large", + text_fontsize="medium", + ): + """Create an animation of particle movement in 2D-space Parameters ---------- @@ -231,7 +273,7 @@ def plot_particles2D(self, index=(0, 1), limits=((-1, 1), (-1, 1)), """ # Check inconsistencies with input if not (len(index) == len(limits) == 2): - raise ValueError('The index and limits should be of length 2') + raise ValueError("The index and limits should be of length 2") # Set-up tuples for plotting environment idx = self.Index(x=index[0], y=index[1], z=None) @@ -255,27 +297,36 @@ def plot_particles2D(self, index=(0, 1), limits=((-1, 1), (-1, 1)), ax.set_ylim(lmt.y) # Plot data - plot = ax.scatter(x=[], y=[], c='red') + plot = ax.scatter(x=[], y=[], c="red") data = self.optimizer.get_pos_history # Get the number of iterations n_iters = self.optimizer.get_pos_history.shape[0] # Perform animation - anim = animation.FuncAnimation(fig, func=self._animate2D, - frames=xrange(n_iters), - fargs=(data, plot, idx), - interval=interval, blit=True) + anim = animation.FuncAnimation( + fig, + func=self._animate2D, + frames=xrange(n_iters), + fargs=(data, plot, idx), + interval=interval, + blit=True, + ) return anim - def plot_particles3D(self, index=(0, 1, 2), - limits=((-1, 1), (-1, 1), (-1, 1)), - labels=('x-axis', 'y-axis', 'z-axis'), - interval=80, - title='Particle Movement in 3D space', ax=None, - figsize=None, title_fontsize="large", - text_fontsize="medium"): - """Creates an animation of particle movement in 2D-space + def plot_particles3D( + self, + index=(0, 1, 2), + limits=((-1, 1), (-1, 1), (-1, 1)), + labels=("x-axis", "y-axis", "z-axis"), + interval=80, + title="Particle Movement in 3D space", + ax=None, + figsize=None, + title_fontsize="large", + text_fontsize="medium", + ): + """Create an animation of particle movement in 3D-space Parameters ---------- @@ -319,7 +370,7 @@ def plot_particles3D(self, index=(0, 1, 2), """ # Check inconsistencies with input if not (len(index) == len(limits) == 3): - raise ValueError('The index and limits should be of length 3') + raise ValueError("The index and limits should be of length 3") # Set-up tuples for plotting environment idx = self.Index(x=index[0], y=index[1], z=index[2]) @@ -346,21 +397,24 @@ def plot_particles3D(self, index=(0, 1, 2), ax.set_zlim(lmt.z) # Plot data - plot = ax.scatter(xs=[], ys=[], zs=[], c='red') + plot = ax.scatter(xs=[], ys=[], zs=[], c="red") data = self.optimizer.get_pos_history # Get the number of iterations n_iters = self.optimizer.get_pos_history.shape[0] # Perform animation - anim = animation.FuncAnimation(fig, func=self._animate3D, - frames=xrange(n_iters), - fargs=(data, plot, idx), - interval=interval) + anim = animation.FuncAnimation( + fig, + func=self._animate3D, + frames=xrange(n_iters), + fargs=(data, plot, idx), + interval=interval, + ) return anim def _animate2D(self, i, data, plot, idx): - """Helper animation function that is called seqentially + """Helper animation function that is called sequentially :class:`matplotlib.animation.FuncAnimation` Parameters @@ -384,10 +438,10 @@ def _animate2D(self, i, data, plot, idx): current_pos = data[i] xy = current_pos[:, (idx.x, idx.y)] plot.set_offsets(xy) - return plot, + return (plot,) def _animate3D(self, i, data, plot, idx): - """Helper animation function that is called seqentially + """Helper animation function that is called sequentially :class:`matplotlib.animation.FuncAnimation` Parameters @@ -409,7 +463,10 @@ def _animate3D(self, i, data, plot, idx): iterable of artists """ current_pos = data[i] - x, y, z = current_pos[:, idx.x], current_pos[:, idx.y], \ - current_pos[:, idx.z] + x, y, z = ( + current_pos[:, idx.x], + current_pos[:, idx.y], + current_pos[:, idx.z], + ) plot._offsets3d = (x, y, z) - return plot, + return (plot,) diff --git a/pyswarms/utils/functions/single_obj.py b/pyswarms/utils/functions/single_obj.py index ad1206ff..a4dce495 100644 --- a/pyswarms/utils/functions/single_obj.py +++ b/pyswarms/utils/functions/single_obj.py @@ -15,6 +15,25 @@ of a new objective function, be sure to perform unittesting in order to check if all functions implemented adheres to the design pattern stated above. + +Function list: +- Ackley's, ackley_func +- Beale, beale_func +- Booth, booth_func +- Bukin's No 6, bukin6_func +- Cross-in-Tray, crossintray_func +- Easom, easom_func +- Eggholder, eggholder_func +- Goldstein, goldstein_func +- Himmelblau's, himmelblau_func +- Holder Table, holdertable_func +- Levi, levi_func +- Matyas, matyas_func +- Rastrigin, rastrigin_func +- Rosenbrock, rosenbrock_func +- Schaffer No 2, schaffer2_func +- Sphere, sphere_func +- Three Hump Camel, threehump_func """ # Import from __future__ @@ -26,11 +45,11 @@ import numpy as np -def sphere_func(x): - """Sphere objective function. +def ackley_func(x): + """Ackley's objective function. - Has a global minimum at :code:`0` and with a search domain of - :code:`[-inf, inf]` + Has a global minimum of `0` at :code:`f(0,0,...,0)` with a search + domain of [-32, 32] Parameters ---------- @@ -41,17 +60,32 @@ def sphere_func(x): ------- numpy.ndarray computed cost of size :code:`(n_particles, )` + + + ------ + ValueError + When the input is out of bounds with respect to the function + domain """ - j = (x**2.0).sum(axis=1) + if not np.logical_and(x >= -32, x <= 32).all(): + raise ValueError("Input for Ackley function must be within [-32, 32].") + + d = x.shape[1] + j = ( + -20.0 * np.exp(-0.2 * np.sqrt((1 / d) * (x ** 2).sum(axis=1))) + - np.exp((1 / float(d)) * np.cos(2 * np.pi * x).sum(axis=1)) + + 20.0 + + np.exp(1) + ) return j -def rastrigin_func(x): - """Rastrigin objective function. +def beale_func(x): + """Beale objective function. - Has a global minimum at :code:`f(0,0,...,0)` with a search - domain of :code:`[-5.12, 5.12]` + Only takes two dimensions and has a global minimum of `0` at + :code:`f([3,0.5])` Its domain is bounded between :code:`[-4.5, 4.5]` Parameters ---------- @@ -65,25 +99,36 @@ def rastrigin_func(x): Raises ------ + IndexError + When the input dimensions is greater than what the function + allows ValueError When the input is out of bounds with respect to the function domain """ - if not np.logical_and(x >= -5.12, x <= 5.12).all(): - raise ValueError('Input for Rastrigin function must be within ' - '[-5.12, 5.12].') + if not x.shape[1] == 2: + raise IndexError("Beale function only takes two-dimensional input.") + if not np.logical_and(x >= -4.5, x <= 4.5).all(): + raise ValueError( + "Input for Beale function must be within " "[-4.5, 4.5]." + ) - d = x.shape[1] - j = 10.0 * d + (x**2.0 - 10.0 * np.cos(2.0 * np.pi * x)).sum(axis=1) + x_ = x[:, 0] + y_ = x[:, 1] + j = ( + (1.5 - x_ + x_ * y_) ** 2.0 + + (2.25 - x_ + x_ * y_ ** 2.0) ** 2.0 + + (2.625 - x_ + x_ * y_ ** 3.0) ** 2.0 + ) return j -def ackley_func(x): - """Ackley's objective function. +def booth_func(x): + """Booth's objective function. - Has a global minimum at :code:`f(0,0,...,0)` with a search - domain of [-32, 32] + Only takes two dimensions and has a global minimum of `0` at + :code:`f([1,3])`. Its domain is bounded between :code:`[-10, 10]` Parameters ---------- @@ -97,29 +142,32 @@ def ackley_func(x): Raises ------ + IndexError + When the input dimensions is greater than what the function + allows ValueError When the input is out of bounds with respect to the function domain """ - if not np.logical_and(x >= -32, x <= 32).all(): - raise ValueError('Input for Ackley function must be within [-32, 32].') + if not x.shape[1] == 2: + raise IndexError("Booth function only takes two-dimensional input.") + if not np.logical_and(x >= -10, x <= 10).all(): + raise ValueError("Input for Booth function must be within [-10, 10].") - d = x.shape[1] - j = (-20.0 * np.exp(-0.2 * np.sqrt((1/d) * (x**2).sum(axis=1))) - - np.exp((1/float(d)) * np.cos(2 * np.pi * x).sum(axis=1)) - + 20.0 - + np.exp(1)) + x_ = x[:, 0] + y_ = x[:, 1] + j = (x_ + 2 * y_ - 7) ** 2.0 + (2 * x_ + y_ - 5) ** 2.0 return j -def rosenbrock_func(x): - """Rosenbrock objective function. +def bukin6_func(x): + """Bukin N. 6 Objective Function - Also known as the Rosenbrock's valley or Rosenbrock's banana - function. Has a global minimum of :code:`np.ones(dimensions)` where - :code:`dimensions` is :code:`x.shape[1]`. The search domain is - :code:`[-inf, inf]`. + Only takes two dimensions and has a global minimum of `0` at + :code:`f([-10,1])`. Its coordinates are bounded by: + * x[:,0] must be within [-15, -5] + * x[:,1] must be within [-3, 3] Parameters ---------- @@ -130,18 +178,47 @@ def rosenbrock_func(x): ------- numpy.ndarray computed cost of size :code:`(n_particles, )` + + Raises + ------ + IndexError + When the input dimensions is greater than what the function + allows + ValueError + When the input is out of bounds with respect to the function + domain """ + if not x.shape[1] == 2: + raise IndexError( + "Bukin N. 6 function only takes two-dimensional " "input." + ) + if not np.logical_and(x[:, 0] >= -15, x[:, 0] <= -5).all(): + raise ValueError( + "x-coord for Bukin N. 6 function must be within " "[-15, -5]." + ) + if not np.logical_and(x[:, 1] >= -3, x[:, 1] <= 3).all(): + raise ValueError( + "y-coord for Bukin N. 6 function must be within " "[-3, 3]." + ) - r = np.sum(100*(x.T[1:] - x.T[:-1]**2.0)**2 + (1-x.T[:-1])**2.0, axis=0) + x_ = x[:, 0] + y_ = x[:, 1] + j = 100 * np.sqrt(np.absolute(y_ - 0.01 * x_ ** 2.0)) + 0.01 * np.absolute( + x_ + 10 + ) - return r + return j -def beale_func(x): - """Beale objective function. +def crossintray_func(x): + """Cross-in-tray objective function. - Only takes two dimensions and has a global minimum at - :code:`f([3,0.5])` Its domain is bounded between :code:`[-4.5, 4.5]` + Only takes two dimensions and has a four equal global minimums + of `-2.06261` at :code:`f([1.34941, -1.34941])`, :code:`f([1.34941, 1.34941])`, + :code:`f([-1.34941, 1.34941])`, and :code:`f([-1.34941, -1.34941])`. + Its coordinates are bounded within :code:`[-10,10]`. + + Best visualized in the full domain and a range of :code:`[-2.0, -0.5]`. Parameters ---------- @@ -163,26 +240,38 @@ def beale_func(x): domain """ if not x.shape[1] == 2: - raise IndexError('Beale function only takes two-dimensional input.') - if not np.logical_and(x >= -4.5, x <= 4.5).all(): - raise ValueError('Input for Beale function must be within ' - '[-4.5, 4.5].') + raise IndexError( + "Cross-in-tray function only takes two-dimensional input." + ) + if not np.logical_and(x >= -10, x <= 10).all(): + raise ValueError( + "Input for cross-in-tray function must be within [-10, 10]." + ) x_ = x[:, 0] y_ = x[:, 1] - j = ((1.5 - x_ + x_ * y_)**2.0 - + (2.25 - x_ + x_ * y_**2.0)**2.0 - + (2.625 - x_ + x_ * y_**3.0)**2.0) + + j = -0.0001 * np.power( + np.abs( + np.sin(x_) + * np.sin(y_) + * np.exp(np.abs(100 - (np.sqrt(x_ ** 2 + y_ ** 2) / np.pi))) + ) + + 1, + 0.1, + ) return j -# TODO: Implement Goldstein-Price's Function -def goldstein_func(x): - """Goldstein-Price's objective function. +def easom_func(x): + """Easom objective function. - Only takes two dimensions and has a global minimum at - :code:`f([0,-1])`. Its domain is bounded between :code:`[-2, 2]` + Only takes two dimensions and has a global minimum of + `-1` at :code:`f([pi, pi])`. + Its coordinates are bounded within :code:`[-100,100]`. + + Best visualized in the domain of :code:`[-5, 5]` and a range of :code:`[-1, 0.2]`. Parameters ---------- @@ -204,28 +293,33 @@ def goldstein_func(x): domain """ if not x.shape[1] == 2: - raise IndexError('Goldstein function only takes two-dimensional ' - 'input.') - if not np.logical_and(x >= -2, x <= 2).all(): - raise ValueError('Input for Goldstein-Price function must be within ' - '[-2, 2].') + raise IndexError("Easom function only takes two-dimensional input.") + if not np.logical_and(x >= -100, x <= 100).all(): + raise ValueError( + "Input for Easom function must be within [-100, 100]." + ) x_ = x[:, 0] y_ = x[:, 1] - j = ((1 + (x_ + y_ + 1)**2.0 - * (19 - 14*x_ + 3*x_**2.0 - 14*y_ + 6*x_*y_ + 3*y_**2.0)) - * (30 + (2*x_ - 3 * y_)**2.0 - * (18 - 32*x_ + 12*x_**2.0 + 48*y_ - 36*x_*y_ + 27*y_**2.0))) + + j = ( + -1 + * np.cos(x_) + * np.cos(y_) + * np.exp(-1 * ((x_ - np.pi) ** 2 + (y_ - np.pi) ** 2)) + ) return j -# TODO: Implement Booth's Function -def booth_func(x): - """Booth's objective function. +def eggholder_func(x): + """Eggholder objective function. - Only takes two dimensions and has a global minimum at - :code:`f([1,3])`. Its domain is bounded between :code:`[-10, 10]` + Only takes two dimensions and has a global minimum of + `-959.6407` at :code:`f([512, 404.3219])`. + Its coordinates are bounded within :code:`[-512, 512]`. + + Best visualized in the full domain and a range of :code:`[-1000, 1000]`. Parameters ---------- @@ -247,25 +341,31 @@ def booth_func(x): domain """ if not x.shape[1] == 2: - raise IndexError('Booth function only takes two-dimensional input.') - if not np.logical_and(x >= -10, x <= 10).all(): - raise ValueError('Input for Booth function must be within [-10, 10].') + raise IndexError( + "Eggholder function only takes two-dimensional input." + ) + if not np.logical_and(x >= -512, x <= 512).all(): + raise ValueError( + "Input for Eggholder function must be within [-512, 512]." + ) x_ = x[:, 0] y_ = x[:, 1] - j = (x_ + 2 * y_ - 7)**2.0 + (2 * x_ + y_ - 5)**2.0 + + j = -(y_ + 47) * np.sin(np.sqrt(np.abs((x_ / 2) + y_ + 47))) - x_ * np.sin( + np.sqrt(np.abs(x_ - (y_ + 47))) + ) return j -# TODO: Implement Bukin Function no. 6 -def bukin6_func(x): - """Bukin N. 6 Objective Function +def goldstein_func(x): + """Goldstein-Price's objective function. Only takes two dimensions and has a global minimum at - :code:`f([-10,1])`. Its coordinates are bounded by: - * x[:,0] must be within [-15, -5] - * x[:,1] must be within [-3, 3] + :code:`f([0,-1])`. Its domain is bounded between :code:`[-2, 2]` + + Best visualized in the domain of :code:`[-1.3,1.3]` and range :code:`[-1,8000]` Parameters ---------- @@ -287,29 +387,52 @@ def bukin6_func(x): domain """ if not x.shape[1] == 2: - raise IndexError('Bukin N. 6 function only takes two-dimensional ' - 'input.') - if not np.logical_and(x[:, 0] >= -15, x[:, 0] <= -5).all(): - raise ValueError('x-coord for Bukin N. 6 function must be within ' - '[-15, -5].') - if not np.logical_and(x[:, 1] >= -3, x[:, 1] <= 3).all(): - raise ValueError('y-coord for Bukin N. 6 function must be within ' - '[-3, 3].') + raise IndexError( + "Goldstein function only takes two-dimensional " "input." + ) + if not np.logical_and(x >= -2, x <= 2).all(): + raise ValueError( + "Input for Goldstein-Price function must be within " "[-2, 2]." + ) x_ = x[:, 0] y_ = x[:, 1] - j = 100 * np.sqrt(np.absolute(y_**2.0 - 0.01*x_**2.0)) + 0.01 * \ - np.absolute(x_ + 10) + j = ( + 1 + + (x_ + y_ + 1) ** 2.0 + * ( + 19 + - 14 * x_ + + 3 * x_ ** 2.0 + - 14 * y_ + + 6 * x_ * y_ + + 3 * y_ ** 2.0 + ) + ) * ( + 30 + + (2 * x_ - 3 * y_) ** 2.0 + * ( + 18 + - 32 * x_ + + 12 * x_ ** 2.0 + + 48 * y_ + - 36 * x_ * y_ + + 27 * y_ ** 2.0 + ) + ) return j -def matyas_func(x): - """Matyas objective function +def himmelblau_func(x): + """Himmelblau's objective function - Only takes two dimensions and has a global minimum at - :code:`f([0,0])`. Its coordinates are bounded within - :code:`[-10,10]`. + Only takes two dimensions and has a four equal global minimums + of zero at :code:`f([3.0,2.0])`, :code:`f([-2.805118,3.131312])`, + :code:`f([-3.779310,-3.283186])`, and :code:`f([3.584428,-1.848126])`. + Its coordinates are bounded within :code:`[-5,5]`. + + Best visualized with the full domain and a range of :code:`[0,1000]` Parameters ---------- @@ -319,16 +442,80 @@ def matyas_func(x): Returns ------- numpy.ndarray + computed cost of size :code:`(n_particles, )` + + Raises + ------ + IndexError + When the input dimensions is greater than what the function + allows + ValueError + When the input is out of bounds with respect to the function + domain """ if not x.shape[1] == 2: - raise IndexError('Matyas function only takes two-dimensional input.') + raise IndexError( + "Himmelblau function only takes two-dimensional input." + ) + if not np.logical_and(x >= -5, x <= 5).all(): + raise ValueError( + "Input for Himmelblau function must be within [-5,5]." + ) + + x_ = x[:, 0] + y_ = x[:, 1] + + j = (x_ ** 2 + y_ - 11) ** 2 + (x_ + y_ ** 2 - 7) ** 2 + + return j + + +def holdertable_func(x): + """Holder Table objective function + + Only takes two dimensions and has a four equal global minimums + of `-19.2085` at :code:`f([8.05502, 9.66459])`, :code:`f([-8.05502, 9.66459])`, + :code:`f([8.05502, -9.66459])`, and :code:`f([-8.05502, -9.66459])`. + Its coordinates are bounded within :code:`[-10, 10]`. + + Best visualized with the full domain and a range of :code:`[-20, 0]` + + Parameters + ---------- + x : numpy.ndarray + set of inputs of shape :code:`(n_particles, dimensions)` + + Returns + ------- + numpy.ndarray + computed cost of size :code:`(n_particles, )` + + Raises + ------ + IndexError + When the input dimensions is greater than what the function + allows + ValueError + When the input is out of bounds with respect to the function + domain + """ + if not x.shape[1] == 2: + raise IndexError( + "Holder Table function only takes two-dimensional input." + ) if not np.logical_and(x >= -10, x <= 10).all(): - raise ValueError('Input for Matyas function must be within ' - '[-10, 10].') + raise ValueError( + "Input for Holder Table function must be within [-10,10]." + ) x_ = x[:, 0] y_ = x[:, 1] - j = 0.26 * (x_**2.0 + y_**2.0) - 0.48 * x_ * y_ + + j = -np.abs( + np.sin(x_) + * np.cos(y_) + * np.exp(np.abs(1 - np.sqrt(x_ ** 2 + y_ ** 2) / np.pi)) + ) return j @@ -360,9 +547,9 @@ def levi_func(x): domain """ if not x.shape[1] == 2: - raise IndexError('Levi function only takes two-dimensional input.') + raise IndexError("Levi function only takes two-dimensional input.") if not np.logical_and(x >= -10, x <= 10).all(): - raise ValueError('Input for Levi function must be within [-10, 10].') + raise ValueError("Input for Levi function must be within [-10, 10].") mask = np.full(x.shape, False) mask[:, -1] = True @@ -372,15 +559,105 @@ def levi_func(x): masked_w_ = np.ma.array(w_, mask=mask) d_ = x.shape[1] - 1 - j = (np.sin(np.pi * w_[:, 0])**2.0 - + ((masked_x - 1)**2.0).sum(axis=1) - * (1 + 10 * np.sin(np.pi * (masked_w_).sum(axis=1) + 1)**2.0) - + (w_[:, d_] - 1)**2.0 - * (1 + np.sin(2 * np.pi * w_[:, d_])**2.0)) + j = ( + np.sin(np.pi * w_[:, 0]) ** 2.0 + + ((masked_x - 1) ** 2.0).sum(axis=1) + * (1 + 10 * np.sin(np.pi * (masked_w_).sum(axis=1) + 1) ** 2.0) + + (w_[:, d_] - 1) ** 2.0 * (1 + np.sin(2 * np.pi * w_[:, d_]) ** 2.0) + ) + + return j + + +def matyas_func(x): + """Matyas objective function + + Only takes two dimensions and has a global minimum at + :code:`f([0,0])`. Its coordinates are bounded within + :code:`[-10,10]`. + + Parameters + ---------- + x : numpy.ndarray + set of inputs of shape :code:`(n_particles, dimensions)` + + Returns + ------- + numpy.ndarray + """ + if not x.shape[1] == 2: + raise IndexError("Matyas function only takes two-dimensional input.") + if not np.logical_and(x >= -10, x <= 10).all(): + raise ValueError( + "Input for Matyas function must be within " "[-10, 10]." + ) + + x_ = x[:, 0] + y_ = x[:, 1] + j = 0.26 * (x_ ** 2.0 + y_ ** 2.0) - 0.48 * x_ * y_ + + return j + + +def rastrigin_func(x): + """Rastrigin objective function. + + Has a global minimum at :code:`f(0,0,...,0)` with a search + domain of :code:`[-5.12, 5.12]` + + Parameters + ---------- + x : numpy.ndarray + set of inputs of shape :code:`(n_particles, dimensions)` + + Returns + ------- + numpy.ndarray + computed cost of size :code:`(n_particles, )` + + Raises + ------ + ValueError + When the input is out of bounds with respect to the function + domain + """ + if not np.logical_and(x >= -5.12, x <= 5.12).all(): + raise ValueError( + "Input for Rastrigin function must be within " "[-5.12, 5.12]." + ) + + d = x.shape[1] + j = 10.0 * d + (x ** 2.0 - 10.0 * np.cos(2.0 * np.pi * x)).sum(axis=1) return j +def rosenbrock_func(x): + """Rosenbrock objective function. + + Also known as the Rosenbrock's valley or Rosenbrock's banana + function. Has a global minimum of :code:`np.ones(dimensions)` where + :code:`dimensions` is :code:`x.shape[1]`. The search domain is + :code:`[-inf, inf]`. + + Parameters + ---------- + x : numpy.ndarray + set of inputs of shape :code:`(n_particles, dimensions)` + + Returns + ------- + numpy.ndarray + computed cost of size :code:`(n_particles, )` + """ + + r = np.sum( + 100 * (x.T[1:] - x.T[:-1] ** 2.0) ** 2 + (1 - x.T[:-1]) ** 2.0, axis=0 + ) + + return r + + def schaffer2_func(x): """Schaffer N.2 objective function @@ -408,16 +685,85 @@ def schaffer2_func(x): domain """ if not x.shape[1] == 2: - raise IndexError('Schaffer N. 2 function only takes two-dimensional ' - 'input.') + raise IndexError( + "Schaffer N. 2 function only takes two-dimensional " "input." + ) if not np.logical_and(x >= -100, x <= 100).all(): - raise ValueError('Input for Schaffer function must be within ' - '[-100, 100].') + raise ValueError( + "Input for Schaffer function must be within " "[-100, 100]." + ) x_ = x[:, 0] y_ = x[:, 1] - j = (0.5 - + ((np.sin(x_**2.0 - y_**2.0)**2.0 - 0.5) - / ((1 + 0.001 * (x_**2.0 + y_**2.0))**2.0))) + j = 0.5 + ( + (np.sin(x_ ** 2.0 - y_ ** 2.0) ** 2.0 - 0.5) + / ((1 + 0.001 * (x_ ** 2.0 + y_ ** 2.0)) ** 2.0) + ) + + return j + + +def sphere_func(x): + """Sphere objective function. + + Has a global minimum at :code:`0` and with a search domain of + :code:`[-inf, inf]` + + Parameters + ---------- + x : numpy.ndarray + set of inputs of shape :code:`(n_particles, dimensions)` + + Returns + ------- + numpy.ndarray + computed cost of size :code:`(n_particles, )` + """ + j = (x ** 2.0).sum(axis=1) + + return j + + +def threehump_func(x): + """Three-hump camel objective function + + Only takes two dimensions and has a global minimum of `0` at + :code:`f([0, 0])`. Its coordinates are bounded within + :code:`[-5, 5]`. + + Best visualized in the full domin and a range of :code:`[0, 2000]`. + + Parameters + ---------- + x : numpy.ndarray + set of inputs of shape :code:`(n_particles, dimensions)` + + Returns + ------- + numpy.ndarray + computed cost of size :code:`(n_particles, )` + + Raises + ------ + IndexError + When the input dimensions is greater than what the function + allows + ValueError + When the input is out of bounds with respect to the function + domain + """ + if not x.shape[1] == 2: + raise IndexError( + "Three-hump camel function only takes two-dimensional input." + ) + if not np.logical_and(x >= -5, x <= 5).all(): + raise ValueError( + "Input for Three-hump camel function must be within [-5, 5]." + ) + + x_ = x[:, 0] + y_ = x[:, 1] + + j = 2 * x_ ** 2 - 1.05 * (x_ ** 4) + (x_ ** 6) / 6 + x_ * y_ + y_ ** 2 return j diff --git a/pyswarms/utils/plotters/__init__.py b/pyswarms/utils/plotters/__init__.py new file mode 100644 index 00000000..b961aa10 --- /dev/null +++ b/pyswarms/utils/plotters/__init__.py @@ -0,0 +1,10 @@ +""" +The mod:`pyswarms.utils.plotters` module implements various +visualization capabilities to interact with your swarm. Here, +ou can plot cost history and animate your swarm in both 2D or +3D spaces. +""" + +from .plotters import plot_cost_history, plot_contour, plot_surface + +__all__ = ["plotters", "formatters"] diff --git a/pyswarms/utils/plotters/formatters.py b/pyswarms/utils/plotters/formatters.py new file mode 100644 index 00000000..b986ecca --- /dev/null +++ b/pyswarms/utils/plotters/formatters.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- + +""" +Plot Formatters + +This module implements helpful classes to format your plots or create meshes. +""" + +# Import modules +import numpy as np +from attr import attrs, attrib +from attr.validators import instance_of +from matplotlib import cm, colors + + +@attrs +class Designer(object): + """Designer class for specifying a plot's formatting and design + + You can use this class for specifying design-related customizations to + your plot. This can be passed in various functions found in the + :mod:`pyswarms.utils.plotters` module. + + .. code-block :: python + + from pyswarms.utils.plotters import plot_cost_history + from pyswarms.utils.plotters.formatters import Designer + + # Set title_fontsize into 20 + my_designer = Designer(title_fontsize=20) + + # Assuming we already had an optimizer ready + plot_cost_history(cost_history, designer=my_designer) + + Attributes + ---------- + figsize : tuple (default is :code:`(10,8)`) + Overall figure size. + title_fontsize : str, int, or float (default is :code:`large`) + Size of the plot's title. + text_fontsize : str, int, or float (default is :code:`medium`) + Size of the plot's labels and legend. + legend : str (default is :code:`Cost`) + Label to show in the legend. For cost histories, it states + the label of the line plot. + label : str, list, or tuple (default is :code:`['x-axis', 'y-axis']`) + Label to show in the x, y, or z-axis. For a 3D plot, please pass + an iterable with three elements. + """ + + # Overall plot design + figsize = attrib(type=tuple, validator=instance_of(tuple), default=(10, 8)) + title_fontsize = attrib( + validator=instance_of((str, int, float)), default="large" + ) + text_fontsize = attrib( + validator=instance_of((str, int, float)), default="medium" + ) + legend = attrib(validator=instance_of(str), default="Cost") + label = attrib( + validator=instance_of((str, list, tuple)), + default=["x-axis", "y-axis", "z-axis"], + ) + limits = attrib( + validator=instance_of((list, tuple)), default=[(-1, 1), (-1, 1), (-1, 1)] + ) + colormap = attrib( + validator=instance_of(colors.Colormap), + default=cm.viridis, + ) + + +@attrs +class Animator(object): + """Animator class for specifying animation behavior + + You can use this class to modify options on how the animation will be run + in the :func:`pyswarms.utils.plotters.plot_contour` and + :func:`pyswarms.utils.plotters.plot_surface` methods. + + .. code-block :: python + + from pyswarms.utils.plotters import plot_contour + from pyswarms.utils.plotters.formatters import Animator + + # Do not repeat animation + my_animator = Animator(repeat=False) + + # Assuming we already had an optimizer ready + plot_contour(pos_history, animator=my_animator) + + Attributes + ---------- + interval : int (default is :code:`80`) + Sets the interval or speed into which the animation is played. + repeat_delay : int, float (default is :code:`None`) + Sets the delay before repeating the animation again. + repeat : bool (default is :code:`True`) + Pass :code:`False` if you don't want to repeat the animation. + """ + + interval = attrib(type=int, validator=instance_of(int), default=80) + repeat_delay = attrib(default=None) + repeat = attrib(type=bool, validator=instance_of(bool), default=True) + + +@attrs +class Mesher(object): + """Mesher class for plotting contours of objective functions + + This class enables drawing a surface plot of a given objective function. + You can customize how this plot is drawn with this class. Pass an instance + of this class to enable meshing. + + .. code-block :: python + + from pyswarms.utils.plotters import plot_surface + from pyswarms.utils.plotters.formatters import Mesher + from pyswarms.utils.functions import single_obj as fx + + # Use sphere function + my_mesher = Mesher(func=fx.sphere_func) + + # Assuming we already had an optimizer ready + plot_surface(pos_history, mesher=my_mesher) + + Attributes + ---------- + func : callable + Objective function to plot a surface of. + delta : float (default is :code:`0.001`) + Number of steps when generating the surface plot + limits : list, tuple (default is :code:`[(-1,1), (-1,1)]`) + The range, in each axis, where the mesh will be drawn. + levels : list (default is :code:`np.arange(-2.0, 2.0, 0.070)`) + Levels on which the contours are shown. + alpha : float (default is :code:`0.3`) + Transparency of the surface plot + """ + + func = attrib() + # For mesh creation + delta = attrib(type=float, default=0.001) + limits = attrib( + validator=instance_of((list, tuple)), default=[(-1, 1), (-1, 1)] + ) + levels = attrib(type=list, default=np.arange(-2.0, 2.0, 0.070)) + # Surface transparency + alpha = attrib(type=float, validator=instance_of(float), default=0.3) + + def compute_history_3d(self, pos_history): + """Compute a 3D position matrix + + The first two columns are the 2D position in the x and y axes + respectively, while the third column is the fitness on that given + position. + + Parameters + ---------- + pos_history : numpy.ndarray + Two-dimensional position matrix history of shape + :code:`(iterations, n_particles, 2)` + + Returns + ------- + numpy.ndarray + 3D position matrix of shape :code:`(iterations, n_particles, 3)` + """ + fitness = np.array(list(map(self.func, pos_history))) + return np.dstack((pos_history, fitness)) diff --git a/pyswarms/utils/plotters/plotters.py b/pyswarms/utils/plotters/plotters.py new file mode 100644 index 00000000..28516a65 --- /dev/null +++ b/pyswarms/utils/plotters/plotters.py @@ -0,0 +1,407 @@ +# -*- coding: utf-8 -*- + +""" +Plotting tool for Optimizer Analysis + +This module is built on top of :code:`matplotlib` to render quick and easy +plots for your optimizer. It can plot the best cost for each iteration, and +show animations of the particles in 2-D and 3-D space. Furthermore, because +it has :code:`matplotlib` running under the hood, the plots are easily +customizable. + +For example, if we want to plot the cost, simply run the optimizer, get the +cost history from the optimizer instance, and pass it to the +:code:`plot_cost_history()` method + +.. code-block:: python + + import pyswarms as ps + from pyswarms.utils.functions.single_obj import sphere_func + from pyswarms.utils.plotters import plot_cost_history + + # Set up optimizer + options = {'c1':0.5, 'c2':0.3, 'w':0.9} + optimizer = ps.single.GlobalBestPSO(n_particles=10, dimensions=2, + options=options) + + # Obtain cost history from optimizer instance + cost_history = optimizer.cost_history + + # Plot! + plot_cost_history(cost_history) + plt.show() + +In case you want to plot the particle movement, it is important that either +one of the :code:`matplotlib` animation :code:`Writers` is installed. These +doesn't come out of the box for :code:`pyswarms`, and must be installed +separately. For example, in a Linux or Windows distribution, you can install +:code:`ffmpeg` as + + >>> conda install -c conda-forge ffmpeg + +Now, if you want to plot your particles in a 2-D environment, simply pass +the position history of your swarm (obtainable from swarm instance): + + +.. code-block:: python + + import pyswarms as ps + from pyswarms.utils.functions.single_obj import sphere_func + from pyswarms.utils.plotters import plot_cost_history + + # Set up optimizer + options = {'c1':0.5, 'c2':0.3, 'w':0.9} + optimizer = ps.single.GlobalBestPSO(n_particles=10, dimensions=2, + options=options) + + # Obtain pos history from optimizer instance + pos_history = optimizer.pos_history + + # Plot! + plot_trajectory2D(pos_history) + +You can also supply various arguments in this method: the indices of the +specific dimensions to be used, the limits of the axes, and the interval/ +speed of animation. +""" + +# Import modules +import logging + +import matplotlib.pyplot as plt +import numpy as np +from matplotlib import animation, cm +from mpl_toolkits.mplot3d import Axes3D + +# Import from package +from .formatters import Designer, Animator + +# Initialize logger +logger = logging.getLogger(__name__) + + +def plot_cost_history( + cost_history, ax=None, title="Cost History", designer=None, **kwargs +): + """Create a simple line plot with the cost in the y-axis and + the iteration at the x-axis + + Parameters + ---------- + cost_history : list or numpy.ndarray + Cost history of shape :code:`(iters, )` or length :code:`iters` where + each element contains the cost for the given iteration. + ax : :class:`matplotlib.axes.Axes` (default is :code:`None`) + The axes where the plot is to be drawn. If :code:`None` is + passed, then the plot will be drawn to a new set of axes. + title : str (default is :code:`'Cost History'`) + The title of the plotted graph. + designer : pyswarms.utils.formatters.Designer (default is :code:`None`) + Designer class for custom attributes + **kwargs : dict + Keyword arguments that are passed as a keyword argument to + :class:`matplotlib.axes.Axes` + + Returns + ------- + :class:`matplotlib.axes._subplots.AxesSubplot` + The axes on which the plot was drawn. + """ + try: + # Infer number of iterations based on the length + # of the passed array + iters = len(cost_history) + + # If no Designer class supplied, use defaults + if designer is None: + designer = Designer(legend="Cost", label=["Iterations", "Cost"]) + + # If no ax supplied, create new instance + if ax is None: + _, ax = plt.subplots(1, 1, figsize=designer.figsize) + + # Plot with iters in x-axis and the cost in y-axis + ax.plot( + np.arange(iters), cost_history, "k", lw=2, label=designer.legend + ) + + # Customize plot depending on parameters + ax.set_title(title, fontsize=designer.title_fontsize) + ax.legend(fontsize=designer.text_fontsize) + ax.set_xlabel(designer.label[0], fontsize=designer.text_fontsize) + ax.set_ylabel(designer.label[1], fontsize=designer.text_fontsize) + ax.tick_params(labelsize=designer.text_fontsize) + except TypeError: + raise + else: + return ax + + +def plot_contour( + pos_history, + canvas=None, + title="Trajectory", + mark=None, + designer=None, + mesher=None, + animator=None, + **kwargs +): + """Draw a 2D contour map for particle trajectories + + Here, the space is represented as a flat plane. The contours indicate the + elevation with respect to the objective function. This works best with + 2-dimensional swarms with their fitness in z-space. + + Parameters + ---------- + pos_history : numpy.ndarray or list + Position history of the swarm with shape + :code:`(iteration, n_particles, dimensions)` + canvas : tuple of :class:`matplotlib.figure.Figure` and :class:`matplotlib.axes.Axes` (default is :code:`None`) + The (figure, axis) where all the events will be draw. If :code:`None` is + supplied, then plot will be drawn to a fresh set of canvas. + title : str (default is :code:`'Trajectory'`) + The title of the plotted graph. + mark : tuple (default is :code:`None`) + Marks a particular point with a red crossmark. Useful for marking + the optima. + designer : pyswarms.utils.formatters.Designer (default is :code:`None`) + Designer class for custom attributes + mesher : pyswarms.utils.formatters.Mesher (default is :code:`None`) + Mesher class for mesh plots + animator : pyswarms.utils.formatters.Animator (default is :code:`None`) + Animator class for custom animation + **kwargs : dict + Keyword arguments that are passed as a keyword argument to + :class:`matplotlib.axes.Axes` plotting function + + Returns + ------- + :class:`matplotlib.animation.FuncAnimation` + The drawn animation that can be saved to mp4 or other + third-party tools + """ + + try: + # If no Designer class supplied, use defaults + if designer is None: + designer = Designer( + limits=[(-1, 1), (-1, 1)], label=["x-axis", "y-axis"] + ) + + # If no Animator class supplied, use defaults + if animator is None: + animator = Animator() + + # If ax is default, then create new plot. Set-up the figure, the + # axis, and the plot element that we want to animate + if canvas is None: + fig, ax = plt.subplots(1, 1, figsize=designer.figsize) + else: + fig, ax = canvas + + # Get number of iterations + n_iters = len(pos_history) + + # Customize plot + ax.set_title(title, fontsize=designer.title_fontsize) + ax.set_xlabel(designer.label[0], fontsize=designer.text_fontsize) + ax.set_ylabel(designer.label[1], fontsize=designer.text_fontsize) + ax.set_xlim(designer.limits[0]) + ax.set_ylim(designer.limits[1]) + + # Make a contour map if possible + if mesher is not None: + xx, yy, zz, = _mesh(mesher) + ax.contour(xx, yy, zz, levels=mesher.levels) + + # Mark global best if possible + if mark is not None: + ax.scatter(mark[0], mark[1], color="red", marker="x") + + # Put scatter skeleton + plot = ax.scatter(x=[], y=[], c="black", alpha=0.6, **kwargs) + + # Do animation + anim = animation.FuncAnimation( + fig=fig, + func=_animate, + frames=range(n_iters), + fargs=(pos_history, plot), + interval=animator.interval, + repeat=animator.repeat, + repeat_delay=animator.repeat_delay, + ) + except TypeError: + raise + else: + return anim + + +def plot_surface( + pos_history, + canvas=None, + title="Trajectory", + designer=None, + mesher=None, + animator=None, + mark=None, + **kwargs +): + """Plot a swarm's trajectory in 3D + + This is useful for plotting the swarm's 2-dimensional position with + respect to the objective function. The value in the z-axis is the fitness + of the 2D particle when passed to the objective function. When preparing the + position history, make sure that the: + + * first column is the position in the x-axis, + * second column is the position in the y-axis; and + * third column is the fitness of the 2D particle + + The :class:`pyswarms.utils.plotters.formatters.Mesher` class provides a + method that prepares this history given a 2D pos history from any + optimizer. + + .. code-block:: python + + import pyswarms as ps + from pyswarms.utils.functions.single_obj import sphere_func + from pyswarms.utils.plotters import plot_surface + from pyswarms.utils.plotters.formatters import Mesher + + # Run optimizer + options = {'c1':0.5, 'c2':0.3, 'w':0.9} + optimizer = ps.single.GlobalBestPSO(n_particles=10, dimensions=2, options) + + # Prepare position history + m = Mesher(func=sphere_func) + pos_history_3d = m.compute_history_3d(optimizer.pos_history) + + # Plot! + plot_surface(pos_history_3d) + + Parameters + ---------- + pos_history : numpy.ndarray + Position history of the swarm with shape + :code:`(iteration, n_particles, 3)` + objective_func : callable + The objective function that takes a swarm of shape + :code:`(n_particles, 2)` and returns a fitness array + of :code:`(n_particles, )` + canvas : tuple of :class:`matplotlib.figure.Figure` and + :class:`matplotlib.axes.Axes` (default is :code:`None`) + The (figure, axis) where all the events will be draw. If :code:`None` + is supplied, then plot will be drawn to a fresh set of canvas. + title : str (default is :code:`'Trajectory'`) + The title of the plotted graph. + mark : tuple (default is :code:`None`) + Marks a particular point with a red crossmark. Useful for marking the + optima. + designer : pyswarms.utils.formatters.Designer (default is :code:`None`) + Designer class for custom attributes + mesher : pyswarms.utils.formatters.Mesher (default is :code:`None`) + Mesher class for mesh plots + animator : pyswarms.utils.formatters.Animator (default is :code:`None`) + Animator class for custom animation + **kwargs : dict + Keyword arguments that are passed as a keyword argument to + :class:`matplotlib.axes.Axes` plotting function + + Returns + ------- + :class:`matplotlib.animation.FuncAnimation` + The drawn animation that can be saved to mp4 or other + third-party tools + """ + try: + # If no Designer class supplied, use defaults + if designer is None: + designer = Designer( + limits=[(-1, 1), (-1, 1), (-1, 1)], + label=["x-axis", "y-axis", "z-axis"], + colormap=cm.viridis, + ) + + # If no Animator class supplied, use defaults + if animator is None: + animator = Animator() + + # Get number of iterations + # If ax is default, then create new plot. Set-up the figure, the + # axis, and the plot element that we want to animate + if canvas is None: + fig, ax = plt.subplots(1, 1, figsize=designer.figsize) + else: + fig, ax = canvas + + # Initialize 3D-axis + ax = Axes3D(fig) + + n_iters = len(pos_history) + + # Customize plot + ax.set_title(title, fontsize=designer.title_fontsize) + ax.set_xlabel(designer.label[0], fontsize=designer.text_fontsize) + ax.set_ylabel(designer.label[1], fontsize=designer.text_fontsize) + ax.set_zlabel(designer.label[2], fontsize=designer.text_fontsize) + ax.set_xlim(designer.limits[0]) + ax.set_ylim(designer.limits[1]) + ax.set_zlim(designer.limits[2]) + + # Make a contour map if possible + if mesher is not None: + xx, yy, zz, = _mesh(mesher) + ax.plot_surface( + xx, yy, zz, cmap=designer.colormap, alpha=mesher.alpha + ) + + # Mark global best if possible + if mark is not None: + ax.scatter(mark[0], mark[1], mark[2], color="red", marker="x") + + # Put scatter skeleton + plot = ax.scatter(xs=[], ys=[], zs=[], c="black", alpha=0.6, **kwargs) + + # Do animation + anim = animation.FuncAnimation( + fig=fig, + func=_animate, + frames=range(n_iters), + fargs=(pos_history, plot), + interval=animator.interval, + repeat=animator.repeat, + repeat_delay=animator.repeat_delay, + ) + except TypeError: + raise + else: + return anim + + +def _animate(i, data, plot): + """Helper animation function that is called sequentially + :class:`matplotlib.animation.FuncAnimation` + """ + current_pos = data[i] + if np.array(current_pos).shape[1] == 2: + plot.set_offsets(current_pos) + else: + plot._offsets3d = current_pos.T + return (plot,) + + +def _mesh(mesher): + """Helper function to make a mesh""" + xlim = mesher.limits[0] + ylim = mesher.limits[1] + x = np.arange(xlim[0], xlim[1], mesher.delta) + y = np.arange(ylim[0], ylim[1], mesher.delta) + xx, yy = np.meshgrid(x, y) + xypairs = np.vstack([xx.reshape(-1), yy.reshape(-1)]).T + # Get z-value + z = mesher.func(xypairs) + zz = z.reshape(xx.shape) + return (xx, yy, zz) diff --git a/pyswarms/utils/search/__init__.py b/pyswarms/utils/search/__init__.py index dc1fcce8..7eee30bb 100644 --- a/pyswarms/utils/search/__init__.py +++ b/pyswarms/utils/search/__init__.py @@ -6,7 +6,4 @@ from .grid_search import GridSearch from .random_search import RandomSearch -__all__ = [ - "GridSearch", - "RandomSearch" - ] +__all__ = ["GridSearch", "RandomSearch"] diff --git a/pyswarms/utils/search/base_search.py b/pyswarms/utils/search/base_search.py index 6e3d529f..9bb2cbae 100644 --- a/pyswarms/utils/search/base_search.py +++ b/pyswarms/utils/search/base_search.py @@ -11,9 +11,8 @@ class SearchBase(object): - def assertions(self): - """Assertion method to check :code:`optimizer` input. + """Assertion method to check :code:`optimizer` input Raises ------ @@ -21,14 +20,23 @@ def assertions(self): When :code:`optimizer` does not have an `'optimize'` attribute. """ # Check type of optimizer object - if not hasattr(self.optimizer, 'optimize'): - raise TypeError('Parameter `optimizer` must have an ' - '`\'optimize\'` attribute.') - - def __init__(self, optimizer, n_particles, dimensions, options, - objective_func, iters, - bounds=None, velocity_clamp=(0,1)): - """Initializes the Search. + if not hasattr(self.optimizer, "optimize"): + raise TypeError( + "Parameter `optimizer` must have an " "`'optimize'` attribute." + ) + + def __init__( + self, + optimizer, + n_particles, + dimensions, + options, + objective_func, + iters, + bounds=None, + velocity_clamp=(0, 1), + ): + """Initialize the Search Attributes ---------- @@ -81,7 +89,7 @@ def __init__(self, optimizer, n_particles, dimensions, options, self.assertions() def generate_score(self, options): - """Generates score for optimizer's performance on objective function. + """Generate score for optimizer's performance on objective function Parameters ---------- @@ -91,15 +99,16 @@ def generate_score(self, options): """ # Intialize optimizer - f = self.optimizer(self.n_particles, self.dims, options, - self.bounds, self.vclamp) + f = self.optimizer( + self.n_particles, self.dims, options, self.bounds, self.vclamp + ) # Return score return f.optimize(self.objective_func, self.iters)[0] def search(self, maximum=False): - """Compares optimizer's objective function performance scores - for all combinations of provided parameters. + """Compare optimizer's objective function performance scores + for all combinations of provided parameters Parameters ---------- @@ -121,8 +130,7 @@ def search(self, maximum=False): # Catches the maximum bool flag if maximum: - idx, self.best_score = max(enumerate(scores), - key=op.itemgetter(1)) + idx, self.best_score = max(enumerate(scores), key=op.itemgetter(1)) # Return optimum hyperparameter value property from grid using index self.best_options = op.itemgetter(idx)(grid) diff --git a/pyswarms/utils/search/grid_search.py b/pyswarms/utils/search/grid_search.py index ac2bd1b1..46e2fba4 100644 --- a/pyswarms/utils/search/grid_search.py +++ b/pyswarms/utils/search/grid_search.py @@ -44,25 +44,42 @@ class GridSearch(SearchBase): """Exhaustive search of optimal performance on selected objective function over all combinations of specified hyperparameter values.""" - def __init__(self, optimizer, n_particles, dimensions, options, - objective_func, iters, bounds=None, velocity_clamp=(0,1)): - """Initializes the paramsearch.""" + def __init__( + self, + optimizer, + n_particles, + dimensions, + options, + objective_func, + iters, + bounds=None, + velocity_clamp=(0, 1), + ): + """Initialize the Search""" # Assign attributes - super(GridSearch, self).__init__(optimizer, n_particles, dimensions, - options, objective_func, iters, - bounds=bounds, - velocity_clamp=velocity_clamp) + super(GridSearch, self).__init__( + optimizer, + n_particles, + dimensions, + options, + objective_func, + iters, + bounds=bounds, + velocity_clamp=velocity_clamp, + ) # invoke assertions self.assertions() def generate_grid(self): - """Generates the grid of all hyperparameter value combinations.""" + """Generate the grid of all hyperparameter value combinations""" # Extract keys and values from options dictionary params = self.options.keys() - items = [x if type(x) == list - else [x] for x in list(zip(*self.options.items()))[1]] + items = [ + x if type(x) == list else [x] + for x in list(zip(*self.options.items()))[1] + ] # Create list of cartesian products of hyperparameter values # from options diff --git a/pyswarms/utils/search/random_search.py b/pyswarms/utils/search/random_search.py index 1f1211c1..b5dd5df6 100644 --- a/pyswarms/utils/search/random_search.py +++ b/pyswarms/utils/search/random_search.py @@ -49,7 +49,7 @@ class RandomSearch(SearchBase): within specified bounds for specified number of selection iterations.""" def assertions(self): - """Assertion method to check :code:`n_selection_iters` input. + """Assertion method to check :code:`n_selection_iters` input Raises ------ @@ -60,13 +60,23 @@ def assertions(self): # Check type of n_selection_iters parameter if not isinstance(self.n_selection_iters, int): - raise TypeError('Parameter `n_selection_iters` must be of ' - 'type int') - - def __init__(self, optimizer, n_particles, dimensions, options, - objective_func, iters, n_selection_iters, - bounds=None, velocity_clamp=(0,1)): - """Initializes the paramsearch. + raise TypeError( + "Parameter `n_selection_iters` must be of " "type int" + ) + + def __init__( + self, + optimizer, + n_particles, + dimensions, + options, + objective_func, + iters, + n_selection_iters, + bounds=None, + velocity_clamp=(0, 1), + ): + """Initialize the Search Attributes ---------- @@ -77,40 +87,51 @@ def __init__(self, optimizer, n_particles, dimensions, options, # Assign n_selection_iters as attribute self.n_selection_iters = n_selection_iters # Assign attributes - super(RandomSearch, self).__init__(optimizer, n_particles, dimensions, - options, objective_func, iters, - bounds=bounds, - velocity_clamp=velocity_clamp) + super(RandomSearch, self).__init__( + optimizer, + n_particles, + dimensions, + options, + objective_func, + iters, + bounds=bounds, + velocity_clamp=velocity_clamp, + ) # Invoke assertions self.assertions() def generate_grid(self): - """Generates the grid of hyperparameter value combinations.""" + """Generate the grid of hyperparameter value combinations""" options = dict(self.options) params = {} # Remove 'p' to hold as a constant in the paramater combinations - p = options.pop('p') - params['p'] = [p for _ in xrange(self.n_selection_iters)] + p = options.pop("p") + params["p"] = [p for _ in xrange(self.n_selection_iters)] # Assign generators based on parameter type param_generators = { - 'c1': np.random.uniform, - 'c2': np.random.uniform, - 'w': np.random.uniform, - 'k': np.random.randint + "c1": np.random.uniform, + "c2": np.random.uniform, + "w": np.random.uniform, + "k": np.random.randint, } # Generate random values for hyperparameters 'c1', 'c2', 'w', and 'k' for idx, bounds in options.items(): params[idx] = param_generators[idx]( - *bounds, size=self.n_selection_iters) + *bounds, size=self.n_selection_iters + ) # Return list of dicts of hyperparameter combinations - return [{'c1': params['c1'][i], - 'c2': params['c2'][i], - 'w': params['w'][i], - 'k': params['k'][i], - 'p': params['p'][i]} - for i in xrange(self.n_selection_iters)] + return [ + { + "c1": params["c1"][i], + "c2": params["c2"][i], + "w": params["w"][i], + "k": params["k"][i], + "p": params["p"][i], + } + for i in xrange(self.n_selection_iters) + ] diff --git a/requirements_dev.txt b/requirements_dev.txt index c9ea87e9..f1d2e839 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,17 +1,18 @@ -pip==10.0.1 +pip==18.0 bumpversion==0.5.3 wheel==0.31.1 watchdog==0.8.3 flake8==3.5.0 mock==2.0.0 -tox==3.0.0 +tox==3.1.2 coverage==4.5.1 -Sphinx==1.7.5 -cryptography==2.2.2 +Sphinx==1.7.6 +cryptography==2.3 PyYAML==3.13 # pyup: ignore future==0.16.0 scipy>=0.17.0 numpy>=1.13.0 matplotlib>=1.3.1 -pytest==3.6.3 +pytest==3.6.4 attrs==18.1.0 +pre-commit \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 2c88f882..1f58d7c6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.2.1 +current_version = 0.3.0 commit = True tag = True diff --git a/setup.py b/setup.py index 587f6bc8..acb3c98d 100644 --- a/setup.py +++ b/setup.py @@ -5,18 +5,19 @@ from setuptools import setup, find_packages -with open('README.md') as readme_file: +with open("README.md", encoding="utf8") as readme_file: readme = readme_file.read() requirements = [ - 'PyYAML==3.12', - 'future==0.16.0', - 'scipy>=0.17.0', - 'numpy>=1.13.0', - 'matplotlib>=1.3.1', - 'mock==2.0.0', - 'pytest==3.2.1', - 'attrs==18.1.0' + "PyYAML==3.13", + "future==0.16.0", + "scipy>=0.17.0", + "numpy>=1.13.0", + "matplotlib>=1.3.1", + "mock==2.0.0", + "pytest==3.6.4", + "attrs==18.1.0", + "pre-commit", ] setup_requirements = [ @@ -24,44 +25,45 @@ ] test_requirements = [ - 'PyYAML==3.12', - 'future==0.16.0', - 'scipy>=0.17.0', - 'numpy>=1.13.0', - 'matplotlib>=1.3.1', - 'mock==2.0.0', - 'pytest==3.2.1', - 'attrs==18.1.0' + "PyYAML==3.13", + "future==0.16.0", + "scipy>=0.17.0", + "numpy>=1.13.0", + "matplotlib>=1.3.1", + "mock==2.0.0", + "pytest==3.6.4", + "attrs==18.1.0", + "pre-commit", ] setup( - name='pyswarms', - version='0.2.1', + name="pyswarms", + version="0.3.0", description="A Python-based Particle Swarm Optimization (PSO) library.", long_description=readme, long_description_content_type="text/markdown", author="Lester James V. Miranda", - author_email='ljvmiranda@gmail.com', - url='https://github.com/ljvmiranda921/pyswarms', - packages=find_packages(exclude=['docs', 'tests']), + author_email="ljvmiranda@gmail.com", + url="https://github.com/ljvmiranda921/pyswarms", + packages=find_packages(exclude=["docs", "tests"]), include_package_data=True, install_requires=requirements, license="MIT license", zip_safe=False, - keywords='pyswarms', + keywords="pyswarms", classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Intended Audience :: Education', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', - 'Topic :: Scientific/Engineering', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Topic :: Scientific/Engineering", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", ], - test_suite='tests', + test_suite="tests", tests_require=test_requirements, setup_requires=setup_requirements, ) diff --git a/tests/backend/conftest.py b/tests/backend/conftest.py index 6f7096c1..bccc1047 100644 --- a/tests/backend/conftest.py +++ b/tests/backend/conftest.py @@ -10,17 +10,18 @@ # Import from package from pyswarms.backend.swarms import Swarm + @pytest.fixture def swarm(): """A contrived instance of the Swarm class at a certain timestep""" attrs_at_t = { - 'position' : np.array([[5,5,5], [3,3,3], [1,1,1]]), - 'velocity' : np.array([[1,1,1], [1,1,1], [1,1,1]]), - 'current_cost' : np.array([2,2,2]), - 'pbest_cost' : np.array([1,2,3]), - 'pbest_pos' : np.array([[1,2,3], [4,5,6], [7,8,9]]), - 'best_cost' : 1, - 'best_pos' : np.array([1,1,1]), - 'options' : {'c1' : 0.5, 'c2': 1, 'w': 2} + "position": np.array([[5, 5, 5], [3, 3, 3], [1, 1, 1]]), + "velocity": np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]]), + "current_cost": np.array([2, 2, 2]), + "pbest_cost": np.array([1, 2, 3]), + "pbest_pos": np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), + "best_cost": 1, + "best_pos": np.array([1, 1, 1]), + "options": {"c1": 0.5, "c2": 1, "w": 2}, } return Swarm(**attrs_at_t) diff --git a/tests/backend/test_generators.py b/tests/backend/test_generators.py index 921cabca..6c48c56c 100644 --- a/tests/backend/test_generators.py +++ b/tests/backend/test_generators.py @@ -9,12 +9,15 @@ import pyswarms.backend as P -@pytest.mark.parametrize('bounds', [None, ([2,2,2], [5,5,5]), ([-1,-1,0], [2,2,5])]) -@pytest.mark.parametrize('center', [1, [3,3,3], [0.2,0.2,0.1]]) +@pytest.mark.parametrize( + "bounds", [None, ([2, 2, 2], [5, 5, 5]), ([-1, -1, 0], [2, 2, 5])] +) +@pytest.mark.parametrize("center", [1, [3, 3, 3], [0.2, 0.2, 0.1]]) def test_generate_swarm_return_values(bounds, center): """Tests if generate_swarm() returns expected values""" - pos = P.generate_swarm(n_particles=2, dimensions=3, bounds=bounds, - center=center) + pos = P.generate_swarm( + n_particles=2, dimensions=3, bounds=bounds, center=center + ) if bounds is None: min_bounds, max_bounds = (0.0, 1.00) else: @@ -23,53 +26,46 @@ def test_generate_swarm_return_values(bounds, center): upper_bound = center * np.array(max_bounds) assert (pos <= upper_bound).all() and (pos >= lower_bound).all() -@pytest.mark.parametrize('bounds', [None, ([1,1,1], [10,10,10])]) -@pytest.mark.parametrize('init_pos', [None, np.array([[2,5,6],[7,2,1]])]) -def test_generate_swarm_bounds_init_pos(bounds, init_pos): - """Tests if generate_swarm() returns expected values given init_pos and bounds""" - pos = P.generate_swarm(n_particles=2, dimensions=3, bounds=bounds, - init_pos=init_pos) - if (bounds is None) and (init_pos is None): - min_bounds, max_bounds = (0.0, 1.00) - elif (bounds is None) and (init_pos is not None): - min_bounds, max_bounds = (-np.inf, np.inf) - else: - min_bounds, max_bounds = bounds - lower_bound = np.array(min_bounds) - upper_bound = np.array(max_bounds) - assert (pos <= upper_bound).all() and (pos >= lower_bound).all() def test_generate_swarm_out_of_bounds(): """Tests if generate_swarm() raises ValueError when initialized with the wrong value""" - bounds = ([1,1,1], [5,5,5]) - init_pos = np.array([[-2,3,3], [6,8,1]]) + bounds = ([1, 1, 1], [5, 5, 5]) + init_pos = np.array([[-2, 3, 3], [6, 8, 1]]) with pytest.raises(ValueError): - pos = P.generate_swarm(n_particles=2, dimensions=3, bounds=bounds, - init_pos=init_pos) + pos = P.generate_swarm( + n_particles=2, dimensions=3, bounds=bounds, init_pos=init_pos + ) -@pytest.mark.parametrize('binary', [False, True]) + +@pytest.mark.parametrize("binary", [False, True]) def test_generate_discrete_binary_swarm(binary): """Tests if generate_discrete_swarm(binary=True) returns expected values""" dims = 3 - pos = P.generate_discrete_swarm(n_particles=2, dimensions=dims, binary=binary) + pos = P.generate_discrete_swarm( + n_particles=2, dimensions=dims, binary=binary + ) if binary: - assert len(np.unique(pos)) == 2 + assert len(np.unique(pos)) <= 2 # Might generate pure 0 or 1 else: assert (np.max(pos, axis=1) == dims - 1).all() -@pytest.mark.parametrize('init_pos', [None, np.array([[4,2,1], [1,4,6]])]) + +@pytest.mark.parametrize("init_pos", [None, np.array([[4, 2, 1], [1, 4, 6]])]) def test_generate_discrete_swarm(init_pos): """Tests if init_pos actually sets the position properly""" dims = 3 - pos = P.generate_discrete_swarm(n_particles=2, dimensions=dims, init_pos=init_pos) + pos = P.generate_discrete_swarm( + n_particles=2, dimensions=dims, init_pos=init_pos + ) if init_pos is None: assert (np.max(pos, axis=1) == dims - 1).all() else: assert np.equal(pos, init_pos).all() -@pytest.mark.parametrize('clamp', [None, (0,1), (2,5), (1,6)]) + +@pytest.mark.parametrize("clamp", [None, (0, 1), (2, 5), (1, 6)]) def test_generate_velocity_return_values(clamp): """Tests if generate_velocity() returns expected values""" - min_clamp, max_clamp = (0,1) if clamp == None else clamp + min_clamp, max_clamp = (0, 1) if clamp == None else clamp velocity = P.generate_velocity(n_particles=2, dimensions=3, clamp=clamp) - assert (velocity <= max_clamp).all() and (velocity >= min_clamp).all() \ No newline at end of file + assert (velocity <= max_clamp).all() and (velocity >= min_clamp).all() diff --git a/tests/backend/test_operators.py b/tests/backend/test_operators.py index ccaa8517..7fdbe9aa 100644 --- a/tests/backend/test_operators.py +++ b/tests/backend/test_operators.py @@ -11,13 +11,14 @@ def test_compute_pbest_return_values(swarm): """Test if compute_pbest() gives the expected return values""" - expected_cost = np.array([1,2,2]) - expected_pos = np.array([[1,2,3], [4,5,6], [1,1,1]]) + expected_cost = np.array([1, 2, 2]) + expected_pos = np.array([[1, 2, 3], [4, 5, 6], [1, 1, 1]]) pos, cost = P.compute_pbest(swarm) assert (pos == expected_pos).all() assert (cost == expected_cost).all() -@pytest.mark.parametrize('clamp', [None, (0,1), (-1,1)]) + +@pytest.mark.parametrize("clamp", [None, (0, 1), (-1, 1)]) def test_compute_velocity_return_values(swarm, clamp): """Test if compute_velocity() gives the expected shape and range""" v = P.compute_velocity(swarm, clamp) @@ -25,8 +26,11 @@ def test_compute_velocity_return_values(swarm, clamp): if clamp is not None: assert (clamp[0] <= v).all() and (clamp[1] >= v).all() -@pytest.mark.parametrize('bounds', [None, ([-5,-5,-5],[5,5,5]), - ([-10, -10, -10],[10, 10, 10])]) + +@pytest.mark.parametrize( + "bounds", + [None, ([-5, -5, -5], [5, 5, 5]), ([-10, -10, -10], [10, 10, 10])], +) def test_compute_position_return_values(swarm, bounds): """Test if compute_position() gives the expected shape and range""" p = P.compute_position(swarm, bounds) diff --git a/tests/backend/topology/conftest.py b/tests/backend/topology/conftest.py index fba52129..4aade1b1 100644 --- a/tests/backend/topology/conftest.py +++ b/tests/backend/topology/conftest.py @@ -10,18 +10,47 @@ # Import from package from pyswarms.backend.swarms import Swarm -@pytest.fixture + +@pytest.fixture(scope="module") def swarm(): """A contrived instance of the Swarm class at a certain timestep""" attrs_at_t = { - 'position' : np.array([[5,5,5], [3,3,3], [1,1,1]]), - 'velocity' : np.array([[1,1,1], [1,1,1], [1,1,1]]), - 'current_cost' : np.array([2,2,2]), - 'pbest_cost' : np.array([1,2,3]), - 'pbest_pos' : np.array([[1,2,3], [4,5,6], [7,8,9]]), - 'best_cost' : 1, - 'best_pos' : np.array([1,1,1]), - 'options' : {'c1' : 0.5, 'c2': 1, 'w': 2} + "position": np.array([[9.95838686e-01, 5.87433429e-04, 6.49113772e-03], + [1.00559609e+00, 3.96477697e-02, 7.67205397e-01], + [2.87990950e-01, -3.64932609e-02, 1.89750725e-02], + [1.11646877e+00, 3.12037361e-03, 1.97885369e-01], + [8.96117216e-01, -9.79602053e-03, -1.66139336e-01], + [9.90423669e-01, 1.99307974e-03, -1.23386797e-02], + [2.06800701e-01, -1.67869387e-02, 1.14268810e-01], + [4.21786494e-01, 2.58755510e-02, 6.62254843e-01], + [9.90350831e-01, 3.81575154e-03, 8.80833545e-01], + [9.94353749e-01, -4.85086205e-02, 9.85313500e-03]]), + "velocity": np.array([[2.09076818e-02, 2.04936403e-03, 1.06761248e-02], + [1.64940497e-03, 5.67924469e-03, 9.74902301e-02], + [1.50445516e-01, 9.11699158e-03, 1.51474794e-02], + [2.94238740e-01, 5.71545680e-04, 1.54122294e-02], + [4.10430034e-02, 6.51847479e-04, 6.25109226e-02], + [6.71076116e-06, 1.89615516e-04, 4.65023770e-03], + [4.76081378e-02, 4.24416089e-03, 7.11856172e-02], + [1.33832808e-01, 1.81818698e-02, 1.16947941e-01], + [1.22849955e-03, 1.55685312e-03, 1.67819003e-02], + [5.60617396e-03, 4.31819608e-02, 2.52217220e-02]]), + "current_cost": np.array([1.07818462, 5.5647911, 19.6046078, 14.05300016, 3.72597614, 1.01169386, + 16.51846203, 32.72262829, 3.80274901, 1.05237138]), + "pbest_cost": np.array([1.00362006, 2.39151041, 2.55208424, 5.00176207, 1.04510827, 1.00025284, + 6.31216654, 2.53873121, 2.00530884, 1.05237138]), + "pbest_pos": np.array([[9.98033031e-01, 4.97392619e-03, 3.07726256e-03], + [1.00665809e+00, 4.22504014e-02, 9.84334657e-01], + [1.12159389e-02, 1.11429739e-01, 2.86388193e-02], + [1.64059236e-01, 6.85791237e-03, -2.32137604e-02], + [9.93740665e-01, -6.16501403e-03, -1.46096578e-02], + [9.90438476e-01, 2.50379538e-03, 1.87405987e-05], + [1.12301876e-01, 1.77099784e-03, 1.45382457e-01], + [4.41204876e-02, 4.84059652e-02, 1.05454822e+00], + [9.89348409e-01, -1.31692358e-03, 9.88291764e-01], + [9.99959923e-01, -5.32665972e-03, -1.53685870e-02]]), + "best_cost": 1.0002528364353296, + "best_pos": np.array([9.90438476e-01, 2.50379538e-03, 1.87405987e-05]), + "options": {'c1': 0.5, 'c2': 0.3, 'w': 0.9}, } return Swarm(**attrs_at_t) - diff --git a/tests/backend/topology/test_pyramid.py b/tests/backend/topology/test_pyramid.py new file mode 100644 index 00000000..31583428 --- /dev/null +++ b/tests/backend/topology/test_pyramid.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Import modules +import pytest +import numpy as np + +# Import from package +from pyswarms.backend.topology import Pyramid + + +@pytest.mark.parametrize("static", [True, False]) +def test_compute_gbest_return_values(swarm, static): + """Test if compute_gbest() gives the expected return values""" + topology = Pyramid(static=static) + expected_cost = 1.0002528364353296 + expected_pos = np.array([9.90438476e-01, 2.50379538e-03, 1.87405987e-05]) + pos, cost = topology.compute_gbest(swarm) + assert cost == pytest.approx(expected_cost) + assert (pos == pytest.approx(expected_pos)) + + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize("clamp", [None, (0, 1), (-1, 1)]) +def test_compute_velocity_return_values(swarm, clamp, static): + """Test if compute_velocity() gives the expected shape and range""" + topology = Pyramid(static=static) + v = topology.compute_velocity(swarm, clamp) + assert v.shape == swarm.position.shape + if clamp is not None: + assert (clamp[0] <= v).all() and (clamp[1] >= v).all() + + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize( + "bounds", + [None, ([-5, -5, -5], [5, 5, 5]), ([-10, -10, -10], [10, 10, 10])], +) +def test_compute_position_return_values(swarm, bounds, static): + """Test if compute_position() gives the expected shape and range""" + topology = Pyramid(static=static) + p = topology.compute_position(swarm, bounds) + assert p.shape == swarm.velocity.shape + if bounds is not None: + assert (bounds[0] <= p).all() and (bounds[1] >= p).all() + + +@pytest.mark.parametrize("static", [True, False]) +def test_neighbor_idx(swarm, static): + """Test if the neighbor_idx attribute is assigned""" + topology = Pyramid(static=static) + topology.compute_gbest(swarm) + assert topology.neighbor_idx is not None diff --git a/tests/backend/topology/test_random.py b/tests/backend/topology/test_random.py new file mode 100644 index 00000000..1b12a5dc --- /dev/null +++ b/tests/backend/topology/test_random.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Import modules +import pytest +import numpy as np + +# Import from package +from pyswarms.backend.topology import Random + + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize("k", [1, 2]) +def test_update_gbest_neighborhood(swarm, k, static): + """Test if update_gbest_neighborhood gives the expected return values""" + topology = Random(static=static) + pos, cost = topology.compute_gbest(swarm, k=k) + expected_pos = np.array([9.90438476e-01, 2.50379538e-03, 1.87405987e-05]) + expected_cost = 1.0002528364353296 + assert cost == pytest.approx(expected_cost) + assert (pos == pytest.approx(expected_pos)) + + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize("clamp", [None, (0, 1), (-1, 1)]) +def test_compute_velocity_return_values(swarm, clamp, static): + """Test if compute_velocity() gives the expected shape and range""" + topology = Random(static=static) + v = topology.compute_velocity(swarm, clamp) + assert v.shape == swarm.position.shape + if clamp is not None: + assert (clamp[0] <= v).all() and (clamp[1] >= v).all() + + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize( + "bounds", + [None, ([-5, -5, -5], [5, 5, 5]), ([-10, -10, -10], [10, 10, 10])], +) +def test_compute_position_return_values(swarm, bounds, static): + """Test if compute_position() gives the expected shape and range""" + topology = Random(static=static) + p = topology.compute_position(swarm, bounds) + assert p.shape == swarm.velocity.shape + if bounds is not None: + assert (bounds[0] <= p).all() and (bounds[1] >= p).all() + + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize("k", [1, 2]) +def test_compute_neighbors_return_values(swarm, k, static): + """Test if __compute_neighbors() gives the expected shape and symmetry""" + topology = Random(static=static) + adj_matrix = topology._Random__compute_neighbors(swarm, k) + assert adj_matrix.shape == (swarm.n_particles, swarm.n_particles) + assert np.allclose(adj_matrix, adj_matrix.T, atol=1e-8) # Symmetry test + + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize("k", [1]) +def test_compute_neighbors_adjacency_matrix(swarm, k, static): + """Test if __compute_neighbors() gives the expected matrix""" + np.random.seed(1) + topology = Random(static=static) + adj_matrix = topology._Random__compute_neighbors(swarm, k) + comparison_matrix = np.array([[1, 1, 1, 0, 1, 0, 0, 0, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 0, 0, 0, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 0, 1, 1, 0, 1, 0, 1], + [0, 1, 1, 0, 1, 0, 1, 0, 1, 1], + [0, 1, 1, 0, 1, 1, 0, 1, 1, 0], + [0, 1, 1, 1, 1, 0, 1, 1, 1, 0], + [1, 1, 1, 0, 1, 1, 1, 0, 0, 1]]) + assert np.allclose(adj_matrix, comparison_matrix, atol=1e-8) + + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize("k", [1]) +def test_neighbor_idx(swarm, k, static): + """Test if the neighbor_idx attribute is assigned""" + topology = Random(static=static) + topology.compute_gbest(swarm, k) + assert topology.neighbor_idx is not None diff --git a/tests/backend/topology/test_ring.py b/tests/backend/topology/test_ring.py index bebf71e8..c7723724 100644 --- a/tests/backend/topology/test_ring.py +++ b/tests/backend/topology/test_ring.py @@ -9,32 +9,49 @@ from pyswarms.backend.topology import Ring -@pytest.mark.parametrize('k', [1,2,3]) -@pytest.mark.parametrize('p', [1,2]) -def test_update_gbest_neighborhood(swarm, p, k): +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize("k", [i for i in range(1, 10)]) +@pytest.mark.parametrize("p", [1, 2]) +def test_update_gbest_neighborhood(swarm, p, k, static): """Test if update_gbest_neighborhood gives the expected return values""" - topology = Ring() + topology = Ring(static=static) pos, cost = topology.compute_gbest(swarm, p=p, k=k) - expected_pos = np.array([1,2,3]) - expected_cost = 1 - assert (pos == expected_pos).all() - assert cost == expected_cost + expected_pos = np.array([9.90438476e-01, 2.50379538e-03, 1.87405987e-05]) + expected_cost = 1.0002528364353296 + assert cost == pytest.approx(expected_cost) + assert (pos == pytest.approx(expected_pos)) -@pytest.mark.parametrize('clamp', [None, (0,1), (-1,1)]) -def test_compute_velocity_return_values(swarm, clamp): + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize("clamp", [None, (0, 1), (-1, 1)]) +def test_compute_velocity_return_values(swarm, clamp, static): """Test if compute_velocity() gives the expected shape and range""" - topology = Ring() + topology = Ring(static=static) v = topology.compute_velocity(swarm, clamp) assert v.shape == swarm.position.shape if clamp is not None: assert (clamp[0] <= v).all() and (clamp[1] >= v).all() -@pytest.mark.parametrize('bounds', [None, ([-5,-5,-5],[5,5,5]), - ([-10, -10, -10],[10, 10, 10])]) -def test_compute_position_return_values(swarm, bounds): + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize( + "bounds", + [None, ([-5, -5, -5], [5, 5, 5]), ([-10, -10, -10], [10, 10, 10])], +) +def test_compute_position_return_values(swarm, bounds, static): """Test if compute_position() gives the expected shape and range""" - topology = Ring() + topology = Ring(static=static) p = topology.compute_position(swarm, bounds) assert p.shape == swarm.velocity.shape if bounds is not None: assert (bounds[0] <= p).all() and (bounds[1] >= p).all() + + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize("k", [1, 2, 3]) +@pytest.mark.parametrize("p", [1, 2]) +def test_neighbor_idx(swarm, static, p, k): + """Test if the neighbor_idx attribute is assigned""" + topology = Ring(static=static) + topology.compute_gbest(swarm, p=p, k=k) + assert topology.neighbor_idx is not None diff --git a/tests/backend/topology/test_star.py b/tests/backend/topology/test_star.py index affd6770..1dc72415 100644 --- a/tests/backend/topology/test_star.py +++ b/tests/backend/topology/test_star.py @@ -12,13 +12,14 @@ def test_compute_gbest_return_values(swarm): """Test if compute_gbest() gives the expected return values""" topology = Star() - expected_cost = 1 - expected_pos = np.array([1,2,3]) + expected_cost = 1.0002528364353296 + expected_pos = np.array([9.90438476e-01, 2.50379538e-03, 1.87405987e-05]) pos, cost = topology.compute_gbest(swarm) - assert cost == expected_cost - assert (pos == expected_pos).all() + assert cost == pytest.approx(expected_cost) + assert (pos == pytest.approx(expected_pos)) -@pytest.mark.parametrize('clamp', [None, (0,1), (-1,1)]) + +@pytest.mark.parametrize("clamp", [None, (0, 1), (-1, 1)]) def test_compute_velocity_return_values(swarm, clamp): """Test if compute_velocity() gives the expected shape and range""" topology = Star() @@ -27,12 +28,22 @@ def test_compute_velocity_return_values(swarm, clamp): if clamp is not None: assert (clamp[0] <= v).all() and (clamp[1] >= v).all() -@pytest.mark.parametrize('bounds', [None, ([-5,-5,-5],[5,5,5]), - ([-10, -10, -10],[10, 10, 10])]) + +@pytest.mark.parametrize( + "bounds", + [None, ([-5, -5, -5], [5, 5, 5]), ([-10, -10, -10], [10, 10, 10])], +) def test_compute_position_return_values(swarm, bounds): """Test if compute_position() gives the expected shape and range""" topology = Star() p = topology.compute_position(swarm, bounds) assert p.shape == swarm.velocity.shape if bounds is not None: - assert (bounds[0] <= p).all() and (bounds[1] >= p).all() \ No newline at end of file + assert (bounds[0] <= p).all() and (bounds[1] >= p).all() + + +def test_neighbor_idx(swarm): + """Test if the neighbor_idx attribute is assigned""" + topology = Star() + topology.compute_gbest(swarm) + assert topology.neighbor_idx is not None diff --git a/tests/backend/topology/test_von_neumann.py b/tests/backend/topology/test_von_neumann.py new file mode 100644 index 00000000..e34a9000 --- /dev/null +++ b/tests/backend/topology/test_von_neumann.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Import modules +import pytest +import numpy as np + +# Import from package +from pyswarms.backend.topology import VonNeumann + + +@pytest.mark.parametrize("r", [0, 1]) +@pytest.mark.parametrize("p", [1, 2]) +def test_update_gbest_neighborhood(swarm, p, r): + """Test if update_gbest_neighborhood gives the expected return values""" + topology = VonNeumann() + pos, cost = topology.compute_gbest(swarm, p=p, r=r) + expected_pos = np.array([9.90438476e-01, 2.50379538e-03, 1.87405987e-05]) + expected_cost = 1.0002528364353296 + assert cost == pytest.approx(expected_cost) + assert (pos == pytest.approx(expected_pos)) + + +@pytest.mark.parametrize("clamp", [None, (0, 1), (-1, 1)]) +def test_compute_velocity_return_values(swarm, clamp): + """Test if compute_velocity() gives the expected shape and range""" + topology = VonNeumann() + v = topology.compute_velocity(swarm, clamp) + assert v.shape == swarm.position.shape + if clamp is not None: + assert (clamp[0] <= v).all() and (clamp[1] >= v).all() + + +@pytest.mark.parametrize( + "bounds", + [None, ([-5, -5, -5], [5, 5, 5]), ([-10, -10, -10], [10, 10, 10])], +) +def test_compute_position_return_values(swarm, bounds): + """Test if compute_position() gives the expected shape and range""" + topology = VonNeumann() + p = topology.compute_position(swarm, bounds) + assert p.shape == swarm.velocity.shape + if bounds is not None: + assert (bounds[0] <= p).all() and (bounds[1] >= p).all() + + +@pytest.mark.parametrize("r", [0, 1]) +@pytest.mark.parametrize("p", [1, 2]) +def test_neighbor_idx(swarm, p, r): + """Test if the neighbor_idx attribute is assigned""" + topology = VonNeumann() + topology.compute_gbest(swarm, p=p, r=r) + assert topology.neighbor_idx is not None + + +@pytest.mark.parametrize("m", [i for i in range(9)]) +@pytest.mark.parametrize("n", [i for i in range(10)]) +def test_delannoy_numbers(m, n): + expected_values = np.array([1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 25, + 41, 61, 63, 85, 113, 129, 145, 181, 231, + 321, 377, 575, 681, 833, 1159, 1289, + 1683, 2241, 3649, 3653, 5641, 7183, + 8989, 13073, 19825, 40081, 48639, 75517, + 108545, 22363, 224143, 265729, 598417]) + print(VonNeumann.delannoy(m, n)) + assert VonNeumann.delannoy(m, n) in expected_values diff --git a/tests/optimizers/conftest.py b/tests/optimizers/conftest.py index 250ae3d7..6dfee2c3 100644 --- a/tests/optimizers/conftest.py +++ b/tests/optimizers/conftest.py @@ -8,63 +8,103 @@ import numpy as np # Import from package -from pyswarms.single import (GlobalBestPSO, LocalBestPSO) +from pyswarms.single import GlobalBestPSO, LocalBestPSO, GeneralOptimizerPSO from pyswarms.discrete import BinaryPSO from pyswarms.utils.functions.single_obj import sphere_func +from pyswarms.backend.topology import Star, Ring, Pyramid, Random, VonNeumann -@pytest.fixture(scope='module') + +@pytest.fixture(scope="module") +def general_opt_history(topology): + """Returns a GeneralOptimizerPSO instance run for 1000 iterations for checking + history""" + pso = GeneralOptimizerPSO(10, 2, {"c1": 0.5, "c2": 0.7, "w": 0.5}, topology=topology) + pso.optimize(sphere_func, 1000, verbose=0) + return pso + + +@pytest.fixture(scope="module") +def general_opt_reset(topology): + """Returns a GeneralOptimizerPSO instance that has been run and reset to check + default value""" + pso = GeneralOptimizerPSO(10, 2, {"c1": 0.5, "c2": 0.7, "w": 0.5}, topology=topology) + pso.optimize(sphere_func, 10, verbose=0) + pso.reset() + return pso + + +@pytest.fixture(scope="module") def gbest_history(): """Returns a GlobalBestPSO instance run for 1000 iterations for checking history""" - pso = GlobalBestPSO(10, 2, {'c1': 0.5, 'c2': 0.7, 'w': 0.5}) + pso = GlobalBestPSO(10, 2, {"c1": 0.5, "c2": 0.7, "w": 0.5}) pso.optimize(sphere_func, 1000, verbose=0) return pso -@pytest.fixture(scope='module') + +@pytest.fixture(scope="module") def gbest_reset(): """Returns a GlobalBestPSO instance that has been run and reset to check default value""" - pso = GlobalBestPSO(10, 2, {'c1': 0.5, 'c2': 0.7, 'w': 0.5}) + pso = GlobalBestPSO(10, 2, {"c1": 0.5, "c2": 0.7, "w": 0.5}) pso.optimize(sphere_func, 10, verbose=0) pso.reset() return pso -@pytest.fixture(scope='module') + +@pytest.fixture(scope="module") def lbest_history(): """Returns a LocalBestPSO instance run for 1000 iterations for checking history""" - pso = LocalBestPSO(10, 2, {'c1': 0.5, 'c2': 0.7, 'w': 0.5, 'k':2, 'p':2}) + pso = LocalBestPSO(10, 2, {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 2, "p": 2}) pso.optimize(sphere_func, 1000, verbose=0) return pso -@pytest.fixture(scope='module') + +@pytest.fixture(scope="module") def lbest_reset(): """Returns a LocalBestPSO instance that has been run and reset to check default value""" - pso = LocalBestPSO(10, 2, {'c1': 0.5, 'c2': 0.7, 'w': 0.5, 'k': 2, 'p': 2}) + pso = LocalBestPSO(10, 2, {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 2, "p": 2}) pso.optimize(sphere_func, 10, verbose=0) pso.reset() return pso -@pytest.fixture(scope='module') + +@pytest.fixture(scope="module") def binary_history(): """Returns a BinaryPSO instance run for 1000 iterations for checking history""" - pso = BinaryPSO(10, 2, {'c1': 0.5, 'c2': 0.7, 'w': 0.5, 'k':2, 'p':2}) + pso = BinaryPSO(10, 2, {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 2, "p": 2}) pso.optimize(sphere_func, 1000, verbose=0) return pso -@pytest.fixture(scope='module') + +@pytest.fixture(scope="module") def binary_reset(): """Returns a BinaryPSO instance that has been run and reset to check default value""" - pso = BinaryPSO(10, 2, {'c1': 0.5, 'c2': 0.7, 'w': 0.5, 'k': 2, 'p': 2}) + pso = BinaryPSO(10, 2, {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 2, "p": 2}) pso.optimize(sphere_func, 10, verbose=0) pso.reset() return pso + @pytest.fixture def options(): """Default options dictionary for most PSO use-cases""" - options_ = {'c1':0.5, 'c2':0.7, 'w':0.5, 'k':2, 'p':2} - return options_ \ No newline at end of file + options_ = {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 2, "p": 2, "r": 1} + return options_ + + +@pytest.fixture(params=[ + Star(), + Ring(static=False), Ring(static=True), + Pyramid(static=False), Pyramid(static=True), + Random(static=False), Random(static=True), + VonNeumann() + ]) +def topology(request): + """Parametrized topology parameter""" + topology_ = request.param + return topology_ diff --git a/tests/optimizers/test_binary.py b/tests/optimizers/test_binary.py index 61f22abe..acf533b6 100644 --- a/tests/optimizers/test_binary.py +++ b/tests/optimizers/test_binary.py @@ -7,62 +7,81 @@ # Import from package from pyswarms.discrete import BinaryPSO -from pyswarms.utils.functions.single_obj import sphere_func - -@pytest.mark.parametrize('options', [ - {'c2':0.7, 'w':0.5, 'k': 2, 'p': 2}, - {'c1':0.5, 'w':0.5, 'k': 2, 'p': 2}, - {'c1':0.5, 'c2':0.7, 'k': 2, 'p': 2}, - {'c1':0.5, 'c2':0.7, 'w':0.5, 'p': 2}, - {'c1':0.5, 'c2':0.7, 'w':0.5, 'k': 2} -]) + + +@pytest.mark.parametrize( + "options", + [ + {"c2": 0.7, "w": 0.5, "k": 2, "p": 2}, + {"c1": 0.5, "w": 0.5, "k": 2, "p": 2}, + {"c1": 0.5, "c2": 0.7, "k": 2, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 2}, + ], +) def test_keyword_exception(options): """Tests if exceptions are thrown when keywords are missing""" with pytest.raises(KeyError): BinaryPSO(5, 2, options) -@pytest.mark.parametrize('options', [ - {'c1':0.5, 'c2':0.7, 'w':0.5, 'k':-1, 'p':2}, - {'c1':0.5, 'c2':0.7, 'w':0.5, 'k':6, 'p':2}, - {'c1':0.5, 'c2':0.7, 'w':0.5, 'k':2, 'p':5} -]) + +@pytest.mark.parametrize( + "options", + [ + {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": -1, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 6, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 2, "p": 5}, + ], +) def test_invalid_k_or_p_values(options): """Tests if exception is thrown when passing an invalid value for k or p""" with pytest.raises(ValueError): BinaryPSO(5, 2, options) -@pytest.mark.parametrize('velocity_clamp', [[1, 3],np.array([1, 3])]) + +@pytest.mark.parametrize("velocity_clamp", [[1, 3], np.array([1, 3])]) def test_vclamp_type_exception(velocity_clamp, options): """Tests if exception is raised when velocity_clamp type is not a tuple""" with pytest.raises(TypeError): BinaryPSO(5, 2, velocity_clamp=velocity_clamp, options=options) -@pytest.mark.parametrize('velocity_clamp', [(1,1,1), (2,3,1)]) + +@pytest.mark.parametrize("velocity_clamp", [(1, 1, 1), (2, 3, 1)]) def test_vclamp_shape_exception(velocity_clamp, options): """Tests if exception is raised when velocity_clamp's size is not equal to 2""" with pytest.raises(IndexError): BinaryPSO(5, 2, velocity_clamp=velocity_clamp, options=options) -@pytest.mark.parametrize('velocity_clamp', [(3,2),(10,8)]) + +@pytest.mark.parametrize("velocity_clamp", [(3, 2), (10, 8)]) def test_vclamp_maxmin_exception(velocity_clamp, options): """Tests if the max velocity_clamp is less than min velocity_clamp and vice-versa""" with pytest.raises(ValueError): BinaryPSO(5, 2, velocity_clamp=velocity_clamp, options=options) + def test_reset_default_values(binary_reset): """Tests if best cost and best pos are set properly when the reset() method is called""" assert binary_reset.swarm.best_cost == np.inf assert set(binary_reset.swarm.best_pos) == set(np.array([])) -def test_training_history_shape(binary_history): + +@pytest.mark.parametrize( + "history, expected_shape", + [ + ("cost_history", (1000,)), + ("mean_pbest_history", (1000,)), + ("mean_neighbor_history", (1000,)), + ("pos_history", (1000, 10, 2)), + ("velocity_history", (1000, 10, 2)), + ], +) +def test_training_history_shape(binary_history, history, expected_shape): """Test if training histories are of expected shape""" - assert binary_history.get_cost_history.shape == (1000,) - assert binary_history.get_mean_pbest_history.shape == (1000,) - assert binary_history.get_mean_neighbor_history.shape == (1000,) - assert binary_history.get_pos_history.shape == (1000, 10, 2) - assert binary_history.get_velocity_history.shape == (1000, 10, 2) + pso = vars(binary_history) + assert np.array(pso[history]).shape == expected_shape diff --git a/tests/optimizers/test_general_optimizer.py b/tests/optimizers/test_general_optimizer.py new file mode 100644 index 00000000..970e9116 --- /dev/null +++ b/tests/optimizers/test_general_optimizer.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Import modules +import pytest +import numpy as np + +# Import from package +from pyswarms.single import GeneralOptimizerPSO +from pyswarms.backend.topology import Star, Ring, Pyramid, Random, VonNeumann +from pyswarms.utils.functions.single_obj import sphere_func + + +@pytest.mark.parametrize( + "options", + [{"c2": 0.7, "w": 0.5}, {"c1": 0.5, "w": 0.5}, {"c1": 0.5, "c2": 0.7}], +) +def test_keyword_exception(options, topology): + """Tests if exceptions are thrown when keywords are missing""" + with pytest.raises(KeyError): + GeneralOptimizerPSO(5, 2, options, topology) + + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize( + "options", + [ + {"c2": 0.7, "w": 0.5, "k": 2, "p": 2}, + {"c1": 0.5, "w": 0.5, "k": 2, "p": 2}, + {"c1": 0.5, "c2": 0.7, "k": 2, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 2}, + ], +) +def test_keyword_exception_ring(options, static): + """Tests if exceptions are thrown when keywords are missing and a Ring topology is chosen""" + with pytest.raises(KeyError): + GeneralOptimizerPSO(5, 2, options, Ring(static=static)) + + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize( + "options", + [ + {"c2": 0.7, "w": 0.5, "r": 2, "p": 2}, + {"c1": 0.5, "w": 0.5, "r": 2, "p": 2}, + {"c1": 0.5, "c2": 0.7, "r": 2, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "r": 2}, + ], +) +def test_keyword_exception_vonneumann(options, static): + """Tests if exceptions are thrown when keywords are missing and a VonNeumann topology is chosen""" + with pytest.raises(KeyError): + GeneralOptimizerPSO(5, 2, options, VonNeumann()) + + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize( + "options", + [ + {"c2": 0.7, "w": 0.5, "k": 2}, + {"c1": 0.5, "w": 0.5, "k": 2}, + {"c1": 0.5, "c2": 0.7, "k": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5}, + ], +) +def test_keyword_exception_random(options, static): + """Tests if exceptions are thrown when keywords are missing and a Random topology is chosen""" + with pytest.raises(KeyError): + GeneralOptimizerPSO(5, 2, options, Random(static=static)) + + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize( + "options", + [ + {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": -1, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 6, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 2, "p": 5}, + ], +) +def test_invalid_k_or_p_values(options, static): + """Tests if exception is thrown when passing + an invalid value for k or p when using a Ring topology""" + with pytest.raises(ValueError): + GeneralOptimizerPSO(5, 2, options, Ring(static=static)) + + +@pytest.mark.parametrize( + "options", + [ + {"c1": 0.5, "c2": 0.7, "w": 0.5, "r": -1, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "r": 6, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "r": 2, "p": 5}, + ], +) +def test_invalid_r_or_p_values(options): + """Tests if exception is thrown when passing + an invalid value for r or p when using a Von Neumann topology""" + with pytest.raises(ValueError): + GeneralOptimizerPSO(5, 2, options, VonNeumann()) + + +@pytest.mark.parametrize("static", [True, False]) +@pytest.mark.parametrize( + "options", + [ + {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": -1}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 6}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 0.5} + ], +) +def test_invalid_k_value(options, static): + """Tests if exception is thrown when passing + an invalid value for k when using a Random topology""" + with pytest.raises(ValueError): + GeneralOptimizerPSO(5, 2, options, Random(static=static)) + + +@pytest.mark.parametrize( + "topology", + [object(), int(), dict()] +) +def test_topology_type_exception(options, topology): + """Tests if exceptions are thrown when the topology has the wrong type""" + with pytest.raises(TypeError): + GeneralOptimizerPSO(5, 2, options, topology) + + +@pytest.mark.parametrize( + "bounds", + [ + tuple(np.array([-5, -5])), + (np.array([-5, -5, -5]), np.array([5, 5])), + (np.array([-5, -5, -5]), np.array([5, 5, 5])), + ], +) +def test_bounds_size_exception(bounds, options, topology): + """Tests if exceptions are raised when bound sizes are wrong""" + with pytest.raises(IndexError): + GeneralOptimizerPSO(5, 2, options=options, topology=topology, bounds=bounds) + + +@pytest.mark.parametrize( + "bounds", + [ + (np.array([5, 5]), np.array([-5, -5])), + (np.array([5, -5]), np.array([-5, 5])), + ], +) +def test_bounds_maxmin_exception(bounds, options, topology): + """Tests if the max bounds is less than min bounds and vice-versa""" + with pytest.raises(ValueError): + GeneralOptimizerPSO(5, 2, options=options, topology=topology, bounds=bounds) + + +@pytest.mark.parametrize( + "bounds", + [ + [np.array([-5, -5]), np.array([5, 5])], + np.array([np.array([-5, -5]), np.array([5, 5])]), + ], +) +def test_bound_type_exception(bounds, options, topology): + """Tests if exception is raised when bound type is not a tuple""" + with pytest.raises(TypeError): + GeneralOptimizerPSO(5, 2, options=options, topology=topology, bounds=bounds) + + +@pytest.mark.parametrize("velocity_clamp", [(1, 1, 1), (2, 3, 1)]) +def test_vclamp_shape_exception(velocity_clamp, options, topology): + """Tests if exception is raised when velocity_clamp's size is not equal + to 2""" + with pytest.raises(IndexError): + GeneralOptimizerPSO(5, 2, velocity_clamp=velocity_clamp, options=options, topology=topology) + + +@pytest.mark.parametrize("velocity_clamp", [(3, 2), (10, 8)]) +def test_vclamp_maxmin_exception(velocity_clamp, options, topology): + """Tests if the max velocity_clamp is less than min velocity_clamp and + vice-versa""" + with pytest.raises(ValueError): + GeneralOptimizerPSO(5, 2, velocity_clamp=velocity_clamp, options=options, topology=topology) + + +@pytest.mark.parametrize("err, center", [(IndexError, [1.5, 3.2, 2.5])]) +def test_center_exception(err, center, options, topology): + """Tests if exception is thrown when center is not a list or of different shape""" + with pytest.raises(err): + GeneralOptimizerPSO(5, 2, center=center, options=options, topology=topology) + + +def test_reset_default_values(gbest_reset): + """Tests if best cost and best pos are set properly when the reset() + method is called""" + assert gbest_reset.swarm.best_cost == np.inf + assert set(gbest_reset.swarm.best_pos) == set(np.array([])) + + +@pytest.mark.parametrize( + "history, expected_shape", + [ + ("cost_history", (1000,)), + ("mean_pbest_history", (1000,)), + ("mean_neighbor_history", (1000,)), + ("pos_history", (1000, 10, 2)), + ("velocity_history", (1000, 10, 2)), + ], +) +def test_training_history_shape(gbest_history, history, expected_shape): + """Test if training histories are of expected shape""" + pso = vars(gbest_history) + assert np.array(pso[history]).shape == expected_shape + + +def test_ftol_effect(options, topology): + """Test if setting the ftol breaks the optimization process accordingly""" + pso = GeneralOptimizerPSO(10, 2, options=options, topology=topology, ftol=1e-1) + pso.optimize(sphere_func, 2000, verbose=0) + assert np.array(pso.cost_history).shape != (2000,) diff --git a/tests/optimizers/test_global_best.py b/tests/optimizers/test_global_best.py index 951db947..434f9770 100644 --- a/tests/optimizers/test_global_best.py +++ b/tests/optimizers/test_global_best.py @@ -9,81 +9,105 @@ from pyswarms.single import GlobalBestPSO from pyswarms.utils.functions.single_obj import sphere_func -@pytest.mark.parametrize('options', [ - {'c2':0.7, 'w':0.5}, - {'c1':0.5, 'w':0.5}, - {'c1':0.5, 'c2':0.7} -]) + +@pytest.mark.parametrize( + "options", + [{"c2": 0.7, "w": 0.5}, {"c1": 0.5, "w": 0.5}, {"c1": 0.5, "c2": 0.7}], +) def test_keyword_exception(options): """Tests if exceptions are thrown when keywords are missing""" with pytest.raises(KeyError): GlobalBestPSO(5, 2, options) -@pytest.mark.parametrize('bounds', [ - tuple(np.array([-5,-5])), - (np.array([-5,-5,-5]), np.array([5,5])), - (np.array([-5,-5,-5]), np.array([5,5,5])) -]) + +@pytest.mark.parametrize( + "bounds", + [ + tuple(np.array([-5, -5])), + (np.array([-5, -5, -5]), np.array([5, 5])), + (np.array([-5, -5, -5]), np.array([5, 5, 5])), + ], +) def test_bounds_size_exception(bounds, options): """Tests if exceptions are raised when bound sizes are wrong""" with pytest.raises(IndexError): GlobalBestPSO(5, 2, options=options, bounds=bounds) -@pytest.mark.parametrize('bounds', [ - (np.array([5,5]), np.array([-5,-5])), - (np.array([5,-5]), np.array([-5,5])) -]) + +@pytest.mark.parametrize( + "bounds", + [ + (np.array([5, 5]), np.array([-5, -5])), + (np.array([5, -5]), np.array([-5, 5])), + ], +) def test_bounds_maxmin_exception(bounds, options): """Tests if the max bounds is less than min bounds and vice-versa""" with pytest.raises(ValueError): GlobalBestPSO(5, 2, options=options, bounds=bounds) -@pytest.mark.parametrize('bounds',[ - [np.array([-5, -5]), np.array([5,5])], - np.array([np.array([-5, -5]), np.array([5, 5])]) -]) + +@pytest.mark.parametrize( + "bounds", + [ + [np.array([-5, -5]), np.array([5, 5])], + np.array([np.array([-5, -5]), np.array([5, 5])]), + ], +) def test_bound_type_exception(bounds, options): """Tests if exception is raised when bound type is not a tuple""" with pytest.raises(TypeError): - GlobalBestPSO(5,2, options=options, bounds=bounds) + GlobalBestPSO(5, 2, options=options, bounds=bounds) + -@pytest.mark.parametrize('velocity_clamp', [(1,1,1), (2,3,1)]) +@pytest.mark.parametrize("velocity_clamp", [(1, 1, 1), (2, 3, 1)]) def test_vclamp_shape_exception(velocity_clamp, options): """Tests if exception is raised when velocity_clamp's size is not equal to 2""" with pytest.raises(IndexError): GlobalBestPSO(5, 2, velocity_clamp=velocity_clamp, options=options) -@pytest.mark.parametrize('velocity_clamp', [(3,2),(10,8)]) + +@pytest.mark.parametrize("velocity_clamp", [(3, 2), (10, 8)]) def test_vclamp_maxmin_exception(velocity_clamp, options): """Tests if the max velocity_clamp is less than min velocity_clamp and vice-versa""" with pytest.raises(ValueError): GlobalBestPSO(5, 2, velocity_clamp=velocity_clamp, options=options) -@pytest.mark.parametrize('err, center', - [(IndexError, [1.5, 3.2, 2.5])]) + +@pytest.mark.parametrize("err, center", [(IndexError, [1.5, 3.2, 2.5])]) def test_center_exception(err, center, options): """Tests if exception is thrown when center is not a list or of different shape""" with pytest.raises(err): GlobalBestPSO(5, 2, center=center, options=options) + def test_reset_default_values(gbest_reset): """Tests if best cost and best pos are set properly when the reset() method is called""" assert gbest_reset.swarm.best_cost == np.inf assert set(gbest_reset.swarm.best_pos) == set(np.array([])) -def test_training_history_shape(gbest_history): + +@pytest.mark.parametrize( + "history, expected_shape", + [ + ("cost_history", (1000,)), + ("mean_pbest_history", (1000,)), + ("mean_neighbor_history", (1000,)), + ("pos_history", (1000, 10, 2)), + ("velocity_history", (1000, 10, 2)), + ], +) +def test_training_history_shape(gbest_history, history, expected_shape): """Test if training histories are of expected shape""" - assert gbest_history.get_cost_history.shape == (1000,) - assert gbest_history.get_mean_pbest_history.shape == (1000,) - assert gbest_history.get_mean_neighbor_history.shape == (1000,) - assert gbest_history.get_pos_history.shape == (1000, 10, 2) - assert gbest_history.get_velocity_history.shape == (1000, 10, 2) + pso = vars(gbest_history) + assert np.array(pso[history]).shape == expected_shape + def test_ftol_effect(options): """Test if setting the ftol breaks the optimization process accodingly""" pso = GlobalBestPSO(10, 2, options=options, ftol=1e-1) pso.optimize(sphere_func, 2000, verbose=0) - assert pso.get_cost_history.shape != (2000,) \ No newline at end of file + assert np.array(pso.cost_history).shape != (2000,) diff --git a/tests/optimizers/test_local_best.py b/tests/optimizers/test_local_best.py index 19dc9be1..3d3315fa 100644 --- a/tests/optimizers/test_local_best.py +++ b/tests/optimizers/test_local_best.py @@ -9,94 +9,126 @@ from pyswarms.single import LocalBestPSO from pyswarms.utils.functions.single_obj import sphere_func -@pytest.mark.parametrize('options', [ - {'c2':0.7, 'w':0.5, 'k': 2, 'p': 2}, - {'c1':0.5, 'w':0.5, 'k': 2, 'p': 2}, - {'c1':0.5, 'c2':0.7, 'k': 2, 'p': 2}, - {'c1':0.5, 'c2':0.7, 'w':0.5, 'p': 2}, - {'c1':0.5, 'c2':0.7, 'w':0.5, 'k': 2} -]) + +@pytest.mark.parametrize( + "options", + [ + {"c2": 0.7, "w": 0.5, "k": 2, "p": 2}, + {"c1": 0.5, "w": 0.5, "k": 2, "p": 2}, + {"c1": 0.5, "c2": 0.7, "k": 2, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 2}, + ], +) def test_keyword_exception(options): """Tests if exceptions are thrown when keywords are missing""" with pytest.raises(KeyError): LocalBestPSO(5, 2, options) -@pytest.mark.parametrize('options', [ - {'c1':0.5, 'c2':0.7, 'w':0.5, 'k':-1, 'p':2}, - {'c1':0.5, 'c2':0.7, 'w':0.5, 'k':6, 'p':2}, - {'c1':0.5, 'c2':0.7, 'w':0.5, 'k':2, 'p':5} -]) + +@pytest.mark.parametrize( + "options", + [ + {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": -1, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 6, "p": 2}, + {"c1": 0.5, "c2": 0.7, "w": 0.5, "k": 2, "p": 5}, + ], +) def test_invalid_k_or_p_values(options): """Tests if exception is thrown when passing an invalid value for k or p""" with pytest.raises(ValueError): LocalBestPSO(5, 2, options) -@pytest.mark.parametrize('bounds', [ - tuple(np.array([-5,-5])), - (np.array([-5,-5,-5]), np.array([5,5])), - (np.array([-5,-5,-5]), np.array([5,5,5])) -]) + +@pytest.mark.parametrize( + "bounds", + [ + tuple(np.array([-5, -5])), + (np.array([-5, -5, -5]), np.array([5, 5])), + (np.array([-5, -5, -5]), np.array([5, 5, 5])), + ], +) def test_bounds_size_exception(bounds, options): """Tests if exceptions are raised when bound sizes are wrong""" with pytest.raises(IndexError): LocalBestPSO(5, 2, options=options, bounds=bounds) -@pytest.mark.parametrize('bounds', [ - (np.array([5,5]), np.array([-5,-5])), - (np.array([5,-5]), np.array([-5,5])) -]) + +@pytest.mark.parametrize( + "bounds", + [ + (np.array([5, 5]), np.array([-5, -5])), + (np.array([5, -5]), np.array([-5, 5])), + ], +) def test_bounds_maxmin_exception(bounds, options): """Tests if the max bounds is less than min bounds and vice-versa""" with pytest.raises(ValueError): LocalBestPSO(5, 2, options=options, bounds=bounds) -@pytest.mark.parametrize('bounds',[ - [np.array([-5, -5]), np.array([5,5])], - np.array([np.array([-5, -5]), np.array([5, 5])]) -]) + +@pytest.mark.parametrize( + "bounds", + [ + [np.array([-5, -5]), np.array([5, 5])], + np.array([np.array([-5, -5]), np.array([5, 5])]), + ], +) def test_bound_type_exception(bounds, options): """Tests if exception is raised when bound type is not a tuple""" with pytest.raises(TypeError): - LocalBestPSO(5,2, options=options, bounds=bounds) + LocalBestPSO(5, 2, options=options, bounds=bounds) -@pytest.mark.parametrize('velocity_clamp', [(1,1,1), (2,3,1)]) + +@pytest.mark.parametrize("velocity_clamp", [(1, 1, 1), (2, 3, 1)]) def test_vclamp_shape_exception(velocity_clamp, options): """Tests if exception is raised when velocity_clamp's size is not equal to 2""" with pytest.raises(IndexError): LocalBestPSO(5, 2, velocity_clamp=velocity_clamp, options=options) -@pytest.mark.parametrize('velocity_clamp', [(3,2),(10,8)]) + +@pytest.mark.parametrize("velocity_clamp", [(3, 2), (10, 8)]) def test_vclamp_maxmin_exception(velocity_clamp, options): """Tests if the max velocity_clamp is less than min velocity_clamp and vice-versa""" with pytest.raises(ValueError): LocalBestPSO(5, 2, velocity_clamp=velocity_clamp, options=options) -@pytest.mark.parametrize('err, center', - [(IndexError, [1.5, 3.2, 2.5])]) + +@pytest.mark.parametrize("err, center", [(IndexError, [1.5, 3.2, 2.5])]) def test_center_exception(err, center, options): """Tests if exception is thrown when center is not a list or of different shape""" with pytest.raises(err): LocalBestPSO(5, 2, center=center, options=options) + def test_reset_default_values(lbest_reset): """Tests if best cost and best pos are set properly when the reset() method is called""" assert lbest_reset.swarm.best_cost == np.inf assert set(lbest_reset.swarm.best_pos) == set(np.array([])) -def test_training_history_shape(lbest_history): + +@pytest.mark.parametrize( + "history, expected_shape", + [ + ("cost_history", (1000,)), + ("mean_pbest_history", (1000,)), + ("mean_neighbor_history", (1000,)), + ("pos_history", (1000, 10, 2)), + ("velocity_history", (1000, 10, 2)), + ], +) +def test_training_history_shape(lbest_history, history, expected_shape): """Test if training histories are of expected shape""" - assert lbest_history.get_cost_history.shape == (1000,) - assert lbest_history.get_mean_pbest_history.shape == (1000,) - assert lbest_history.get_mean_neighbor_history.shape == (1000,) - assert lbest_history.get_pos_history.shape == (1000, 10, 2) - assert lbest_history.get_velocity_history.shape == (1000, 10, 2) + pso = vars(lbest_history) + assert np.array(pso[history]).shape == expected_shape + def test_ftol_effect(options): """Test if setting the ftol breaks the optimization process accodingly""" pso = LocalBestPSO(10, 2, options=options, ftol=1e-1) pso.optimize(sphere_func, 2000, verbose=0) - assert pso.get_cost_history.shape != (2000,) + assert np.array(pso.cost_history).shape != (2000,) diff --git a/tests/optimizers/test_objective_func_with_kwargs.py b/tests/optimizers/test_objective_func_with_kwargs.py new file mode 100644 index 00000000..a980044b --- /dev/null +++ b/tests/optimizers/test_objective_func_with_kwargs.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Import modules +import pytest +import numpy as np + +# Import from package +from pyswarms.single import GlobalBestPSO, LocalBestPSO +from pyswarms.utils.functions.single_obj import rosenbrock_func + + +def rosenbrock_with_args(x, a, b): + + f = (a - x[:, 0]) ** 2 + b * (x[:, 1] - x[:, 0] ** 2) ** 2 + return f + + +@pytest.mark.parametrize('func', [ + rosenbrock_with_args +]) +def test_global_kwargs(func): + """Tests if kwargs are passed properly to the objective function for when kwargs are present""" + + # setup optimizer + options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9, 'k': 2, 'p': 2} + + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + opt_ps = GlobalBestPSO(n_particles=100, dimensions=2, options=options, bounds=bounds) + + # run it + cost, pos = opt_ps.optimize(func, 1000, print_step=10, verbose=3, a=1 , b=100) + + assert np.isclose(cost, 0, rtol=1e-03) + assert np.isclose(pos[0], 1.0, rtol=1e-03) + assert np.isclose(pos[1], 1.0, rtol=1e-03) + + +@pytest.mark.parametrize('func', [ + rosenbrock_with_args +]) +def test_global_kwargs_without_named_arguments(func): + """Tests if kwargs are passed properly to the objective function for when kwargs are present and + other named arguments are not passed, such as print_step""" + + # setup optimizer + options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9, 'k': 2, 'p': 2} + + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + opt_ps = GlobalBestPSO(n_particles=100, dimensions=2, options=options, bounds=bounds) + + # run it + cost, pos = opt_ps.optimize(func, 1000, verbose=3, a=1 , b=100) + + assert np.isclose(cost, 0, rtol=1e-03) + assert np.isclose(pos[0], 1.0, rtol=1e-03) + assert np.isclose(pos[1], 1.0, rtol=1e-03) + + +@pytest.mark.parametrize('func', [ + rosenbrock_func +]) +def test_global_no_kwargs(func): + """Tests if args are passed properly to the objective function for when no args are present""" + + # setup optimizer + options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9, 'k': 2, 'p': 2} + + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + opt_ps = GlobalBestPSO(n_particles=100, dimensions=2, options=options, bounds=bounds) + + # run it + cost, pos = opt_ps.optimize(func, 1000, print_step=10, verbose=3) + + assert np.isclose(cost, 0, rtol=1e-03) + assert np.isclose(pos[0], 1.0, rtol=1e-03) + assert np.isclose(pos[1], 1.0, rtol=1e-03) + + +@pytest.mark.parametrize('func', [ + rosenbrock_with_args +]) +def test_local_kwargs(func): + """Tests if kwargs are passed properly to the objective function for when kwargs are present""" + + # setup optimizer + options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9, 'k': 2, 'p': 2} + + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + opt_ps = LocalBestPSO(n_particles=100, dimensions=2, options=options, bounds=bounds) + + # run it + cost, pos = opt_ps.optimize(func, 1000, print_step=10, verbose=3, a=1, b=100) + + assert np.isclose(cost, 0, rtol=1e-03) + assert np.isclose(pos[0], 1.0, rtol=1e-03) + assert np.isclose(pos[1], 1.0, rtol=1e-03) + + +@pytest.mark.parametrize('func', [ + rosenbrock_func +]) +def test_local_no_kwargs(func): + """Tests if no kwargs/args are passed properly to the objective function for when kwargs are present""" + + # setup optimizer + options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9, 'k': 2, 'p': 2} + + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + opt_ps = LocalBestPSO(n_particles=100, dimensions=2, options=options, bounds=bounds) + + # run it + cost, pos = opt_ps.optimize(func, iters=1000, print_step=10, verbose=3) + + assert np.isclose(cost, 0, rtol=1e-03) + assert np.isclose(pos[0], 1.0, rtol=1e-03) + assert np.isclose(pos[1], 1.0, rtol=1e-03) + + +@pytest.mark.parametrize('func', [ + rosenbrock_func +]) +def test_global_uneeded_kwargs(func): + """Tests kwargs are passed the objective function for when kwargs do not exist""" + + # setup optimizer + options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9, 'k': 2, 'p': 2} + + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + opt_ps = GlobalBestPSO(n_particles=100, dimensions=2, options=options, bounds=bounds) + + # run it + with pytest.raises(TypeError) as excinfo: + cost, pos = opt_ps.optimize(func, 1000, print_step=10, verbose=3, a=1) + assert 'unexpected keyword' in str(excinfo.value) + + +@pytest.mark.parametrize('func', [ + rosenbrock_with_args +]) +def test_global_missed_kwargs(func): + """Tests kwargs are passed the objective function for when kwargs do not exist""" + + # setup optimizer + options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9, 'k': 2, 'p': 2} + + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + opt_ps = GlobalBestPSO(n_particles=100, dimensions=2, options=options, bounds=bounds) + + # run it + with pytest.raises(TypeError) as excinfo: + cost, pos = opt_ps.optimize(func, 1000, print_step=10, verbose=3, a=1) + assert 'missing 1 required positional argument' in str(excinfo.value) + + +@pytest.mark.parametrize('func', [ + rosenbrock_func +]) +def test_local_uneeded_kwargs(func): + """Tests kwargs are passed the objective function for when kwargs do not exist""" + + # setup optimizer + options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9, 'k': 2, 'p': 2} + + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + opt_ps = LocalBestPSO(n_particles=100, dimensions=2, options=options, bounds=bounds) + + # run it + with pytest.raises(TypeError) as excinfo: + cost, pos = opt_ps.optimize(func, 1000, print_step=10, verbose=3, a=1) + assert 'unexpected keyword' in str(excinfo.value) + + +@pytest.mark.parametrize('func', [ + rosenbrock_with_args +]) +def test_local_missed_kwargs(func): + """Tests kwargs are passed the objective function for when kwargs do not exist""" + + # setup optimizer + options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9, 'k': 2, 'p': 2} + + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + opt_ps = LocalBestPSO(n_particles=100, dimensions=2, options=options, bounds=bounds) + + # run it + with pytest.raises(TypeError) as excinfo: + cost, pos = opt_ps.optimize(func, 1000, print_step=10, verbose=3, a=1) + assert 'missing 1 required positional argument' in str(excinfo.value) + + +@pytest.mark.parametrize('func', [ + rosenbrock_with_args +]) +def test_local_wrong_kwargs(func): + """Tests kwargs are passed the objective function for when kwargs do not exist""" + + # setup optimizer + options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9, 'k': 2, 'p': 2} + + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + opt_ps = LocalBestPSO(n_particles=100, dimensions=2, options=options, bounds=bounds) + + # run it + with pytest.raises(TypeError) as excinfo: + cost, pos = opt_ps.optimize(func, 1000, print_step=10, verbose=3, c=1, d=100) + assert 'unexpected keyword' in str(excinfo.value) + + +@pytest.mark.parametrize('func', [ + rosenbrock_with_args +]) +def test_global_wrong_kwargs(func): + """Tests kwargs are passed the objective function for when kwargs do not exist""" + + # setup optimizer + options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9, 'k': 2, 'p': 2} + + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + opt_ps = GlobalBestPSO(n_particles=100, dimensions=2, options=options, bounds=bounds) + + # run it + with pytest.raises(TypeError) as excinfo: + cost, pos = opt_ps.optimize(func, 1000, print_step=10, verbose=3, c=1, d=100) + assert 'unexpected keyword' in str(excinfo.value) diff --git a/tests/utils/environments/conftest.py b/tests/utils/environments/conftest.py index 55eb42f4..1f548e9b 100644 --- a/tests/utils/environments/conftest.py +++ b/tests/utils/environments/conftest.py @@ -6,35 +6,40 @@ # Import modules import os import pytest -import numpy as np from mock import Mock import matplotlib as mpl -if os.environ.get('DISPLAY','') == '': - mpl.use('Agg') +if os.environ.get("DISPLAY", "") == "": + mpl.use("Agg") # Import from package from pyswarms.single import GlobalBestPSO from pyswarms.utils.environments import PlotEnvironment from pyswarms.utils.functions.single_obj import sphere_func + @pytest.fixture def mock_pso(): """Returns a function that mocks a PSO class with missing attributes""" + def _mock_pso(index): class_methods = [ - 'get_cost_history', - 'get_pos_history', - 'get_velocity_history', - 'optimize', - 'reset' + "cost_history", + "pos_history", + "velocity_history", + "optimize", + "reset", + ] + get_specs = lambda idx: [ + x for i, x in enumerate(class_methods) if i != idx ] - get_specs = lambda idx: [x for i,x in enumerate(class_methods) if i != idx] return Mock(spec=get_specs(index)) + return _mock_pso + @pytest.fixture def plot_environment(): """Returns a PlotEnvironment instance""" - optimizer = GlobalBestPSO(10, 3, options={'c1': 0.5, 'c2': 0.3, 'w': 0.9}) - return PlotEnvironment(optimizer, sphere_func, 1000) \ No newline at end of file + optimizer = GlobalBestPSO(10, 3, options={"c1": 0.5, "c2": 0.3, "w": 0.9}) + return PlotEnvironment(optimizer, sphere_func, 1000) diff --git a/tests/utils/environments/test_plot_environment.py b/tests/utils/environments/test_plot_environment.py index 1318ab92..600a5039 100644 --- a/tests/utils/environments/test_plot_environment.py +++ b/tests/utils/environments/test_plot_environment.py @@ -4,11 +4,10 @@ # Import modules import os import pytest -import numpy as np import matplotlib as mpl -if os.environ.get('DISPLAY','') == '': - mpl.use('Agg') +if os.environ.get("DISPLAY", "") == "": + mpl.use("Agg") from matplotlib.axes._subplots import SubplotBase from matplotlib.animation import FuncAnimation @@ -18,29 +17,36 @@ from pyswarms.utils.functions.single_obj import sphere_func class_methods = [ - 'get_cost_history', - 'get_pos_history', - 'get_velocity_history', - 'optimize', - 'reset' + "cost_history", + "pos_history", + "velocity_history", + "optimize", + "reset", ] -@pytest.mark.parametrize('attributes', [i for i in enumerate(class_methods)]) + +@pytest.mark.parametrize("attributes", [i for i in enumerate(class_methods)]) def test_getters_pso(mock_pso, attributes): """Tests an instance of the PSO class and should raise an exception when the class has missing attributes""" idx, _ = attributes with pytest.raises(AttributeError): - m = mock_pso(idx) - PlotEnvironment(m, sphere_func, 100) + m = mock_pso(idx) + PlotEnvironment(m, sphere_func, 100) + +@pytest.mark.xfail def test_plot_cost_return_type(plot_environment): """Tests if plot_cost() returns a SubplotBase instance""" - assert isinstance(plot_environment.plot_cost(),SubplotBase) + assert isinstance(plot_environment.plot_cost(), SubplotBase) + +@pytest.mark.xfail def test_plot2D_return_type(plot_environment): """Test if plot_particles2D() returns a FuncAnimation instance""" assert isinstance(plot_environment.plot_particles2D(), FuncAnimation) + +@pytest.mark.xfail def test_plot3D_return_type(plot_environment): """Test if plot_particles3D() returns a FuncAnimation instance""" - assert isinstance(plot_environment.plot_particles3D(), FuncAnimation) \ No newline at end of file + assert isinstance(plot_environment.plot_particles3D(), FuncAnimation) diff --git a/tests/utils/functions/conftest.py b/tests/utils/functions/conftest.py index a369ca25..855f3845 100644 --- a/tests/utils/functions/conftest.py +++ b/tests/utils/functions/conftest.py @@ -7,36 +7,43 @@ import pytest import numpy as np + @pytest.fixture def outbound(): """Returns a function that generates a matrix out of bounds a given range""" - def _outbound(low, high, size, tol=100, nums=100): + + def _outbound(low, high, size, tol=1000, nums=100): """Generates a matrix that is out of bounds""" - low_end = - np.random.uniform(tol, low, (nums,)) + low_end = -np.random.uniform(tol, low, (nums,)) high_end = np.random.uniform(tol, high, (nums,)) choices = np.hstack([low_end, high_end]) return np.random.choice(choices, size=size, replace=True) + return _outbound + @pytest.fixture def outdim(): """Returns a matrix of bad shape (3D matrix)""" - return np.zeros(shape=(3,3)) + return np.zeros(shape=(3, 3)) + @pytest.fixture def common_minima(): """Returns a zero-matrix with a common-minima for most objective functions""" - return np.zeros(shape=(3,2)) + return np.zeros(shape=(3, 2)) + @pytest.fixture def common_minima2(): """Returns a one-matrix with a common-minima for most objective functions""" - return np.ones(shape=(3,2)) + return np.ones(shape=(3, 2)) + @pytest.fixture def targetdim(): """Returns a baseline target dimension for most objective functions""" - return (3, ) \ No newline at end of file + return (3,) diff --git a/tests/utils/functions/test_singleobj_bounds.py b/tests/utils/functions/test_singleobj_bounds.py index 62daa069..dd950106 100644 --- a/tests/utils/functions/test_singleobj_bounds.py +++ b/tests/utils/functions/test_singleobj_bounds.py @@ -11,74 +11,138 @@ # Import from package from pyswarms.utils.functions import single_obj as fx -Bounds = namedtuple('Bounds', 'low high') +Bounds = namedtuple("Bounds", "low high") b = { # Define all bounds here - 'rastrigin' : Bounds(low=-5.12, high=5.12), - 'ackley' : Bounds(low=-32, high=32), - 'beale' : Bounds(low=-4.5, high=4.5), - 'goldstein' : Bounds(low=-2, high=-2), - 'booth' : Bounds(low=-10, high=10), - 'matyas' : Bounds(low=-10, high=10), - 'levi' : Bounds(low=-10, high=10), - 'schaffer2' : Bounds(low=-100, high=100) - + "ackley": Bounds(low=-32, high=32), + "beale": Bounds(low=-4.5, high=4.5), + "booth": Bounds(low=-10, high=10), + # bukin_6 is not symmetrical + "crossintray": Bounds(low=-1, high=10), + "easom": Bounds(low=-100, high=100), + "eggholder": Bounds(low=-512, high=512), + "goldstein": Bounds(low=-2, high=-2), + "himmelblau": Bounds(low=-5, high=5), + "holdertable": Bounds(low=-10, high=10), + "levi": Bounds(low=-10, high=10), + "matyas": Bounds(low=-10, high=10), + "rastrigin": Bounds(low=-5.12, high=5.12), + # rosenbrock has an infinite domain + "schaffer2": Bounds(low=-100, high=100), + "threehump": Bounds(low=-5, high=5), } -def test_rastrigin_bound_fail(outbound): - """Test rastrigin bound exception""" - with pytest.raises(ValueError): - x = outbound(b['rastrigin'].low, b['rastrigin'].high, size=(3,2)) - fx.rastrigin_func(x) + def test_ackley_bound_fail(outbound): """Test ackley bound exception""" with pytest.raises(ValueError): - x = outbound(b['ackley'].low, b['ackley'].high, size=(3,2)) + x = outbound(b["ackley"].low, b["ackley"].high, size=(3, 2)) fx.ackley_func(x) + def test_beale_bound_fail(outbound): """Test beale bound exception""" with pytest.raises(ValueError): - x = outbound(b['beale'].low, b['beale'].high, size=(3,2)) + x = outbound(b["beale"].low, b["beale"].high, size=(3, 2)) fx.beale_func(x) -def test_goldstein_bound_fail(outbound): - """Test goldstein bound exception""" - with pytest.raises(ValueError): - x = outbound(b['goldstein'].low, b['goldstein'].high, size=(3,2)) - fx.goldstein_func(x) def test_booth_bound_fail(outbound): """Test booth bound exception""" with pytest.raises(ValueError): - x = outbound(b['booth'].low, b['booth'].high, size=(3,2)) + x = outbound(b["booth"].low, b["booth"].high, size=(3, 2)) fx.booth_func(x) -@pytest.mark.parametrize("x", [ - -np.random.uniform(15.001, 100, (3,2)), - np.random.uniform(-5.001, -3.001, (3,2)), - np.random.uniform(-3.001, -100, (3,2)) -]) + +@pytest.mark.parametrize( + "x", + [ + -np.random.uniform(15.001, 100, (3, 2)), + np.random.uniform(-5.001, -3.001, (3, 2)), + np.random.uniform(-3.001, -100, (3, 2)), + ], +) def test_bukin6_bound_fail(x): """Test bukin6 bound exception""" with pytest.raises(ValueError): fx.bukin6_func(x) -def test_matyas_bound_fail(outbound): - """Test matyas bound exception""" + +def test_crossintray_bound_fail(outbound): + """Test crossintray bound exception""" with pytest.raises(ValueError): - x = outbound(b['matyas'].low, b['matyas'].high, size=(3,2)) - fx.matyas_func(x) + x = outbound(b["crossintray"].low, b["crossintray"].high, size=(3, 2)) + fx.crossintray_func(x) + + +def test_easom_bound_fail(outbound): + """Test easom bound exception""" + with pytest.raises(ValueError): + x = outbound(b["easom"].low, b["easom"].high, size=(3, 2)) + fx.easom_func(x) + + +def test_eggholder_bound_fail(outbound): + """Test eggholder bound exception""" + with pytest.raises(ValueError): + x = outbound(b["eggholder"].low, b["eggholder"].high, size=(3, 2)) + fx.eggholder_func(x) + + +def test_goldstein_bound_fail(outbound): + """Test goldstein bound exception""" + with pytest.raises(ValueError): + x = outbound(b["goldstein"].low, b["goldstein"].high, size=(3, 2)) + fx.goldstein_func(x) + + +def test_himmelblau_bound_fail(outbound): + """Test himmelblau bound exception""" + with pytest.raises(ValueError): + x = outbound(b["himmelblau"].low, b["himmelblau"].high, size=(3, 2)) + fx.himmelblau_func(x) + + +def test_holdertable_bound_fail(outbound): + """Test holdertable bound exception""" + with pytest.raises(ValueError): + x = outbound(b["holdertable"].low, b["holdertable"].high, size=(3, 2)) + fx.holdertable_func(x) + def test_levi_bound_fail(outbound): """Test levi bound exception""" with pytest.raises(ValueError): - x = outbound(b['levi'].low, b['levi'].high, size=(3,2)) + x = outbound(b["levi"].low, b["levi"].high, size=(3, 2)) fx.levi_func(x) + +def test_matyas_bound_fail(outbound): + """Test matyas bound exception""" + with pytest.raises(ValueError): + x = outbound(b["matyas"].low, b["matyas"].high, size=(3, 2)) + fx.matyas_func(x) + + +def test_rastrigin_bound_fail(outbound): + """Test rastrigin bound exception""" + with pytest.raises(ValueError): + x = outbound(b["rastrigin"].low, b["rastrigin"].high, size=(3, 2)) + fx.rastrigin_func(x) + + def test_schaffer2_bound_fail(outbound): """Test schaffer2 bound exception""" with pytest.raises(ValueError): - x = outbound(b['schaffer2'].low, b['schaffer2'].high, tol=200, size=(3,2)) - fx.schaffer2_func(x) \ No newline at end of file + x = outbound( + b["schaffer2"].low, b["schaffer2"].high, tol=200, size=(3, 2) + ) + fx.schaffer2_func(x) + + +def test_threehump_bound_fail(outbound): + """Test threehump bound exception""" + with pytest.raises(ValueError): + x = outbound(b["threehump"].low, b["threehump"].high, size=(3, 2)) + fx.threehump_func(x) diff --git a/tests/utils/functions/test_singleobj_dims.py b/tests/utils/functions/test_singleobj_dims.py index 9258902f..e1a78e55 100644 --- a/tests/utils/functions/test_singleobj_dims.py +++ b/tests/utils/functions/test_singleobj_dims.py @@ -11,37 +11,79 @@ # Import from package from pyswarms.utils.functions import single_obj as fx + def test_beale_dim_fail(outdim): """Test beale dim exception""" with pytest.raises(IndexError): fx.beale_func(outdim) -def test_goldstein_dim_fail(outdim): - """Test goldstein dim exception""" - with pytest.raises(IndexError): - fx.goldstein_func(outdim) def test_booth_dim_fail(outdim): """Test booth dim exception""" with pytest.raises(IndexError): fx.booth_func(outdim) + def test_bukin6_dim_fail(outdim): """Test bukin6 dim exception""" with pytest.raises(IndexError): fx.bukin6_func(outdim) -def test_matyas_dim_fail(outdim): - """Test matyas dim exception""" + +def test_crossintray_dim_fail(outdim): + """Test crossintray dim exception""" with pytest.raises(IndexError): - fx.matyas_func(outdim) + fx.crossintray_func(outdim) + + +def test_easom_dim_fail(outdim): + """Test easom dim exception""" + with pytest.raises(IndexError): + fx.easom_func(outdim) + +def test_goldstein_dim_fail(outdim): + """Test goldstein dim exception""" + with pytest.raises(IndexError): + fx.goldstein_func(outdim) + + +def test_eggholder_dim_fail(outdim): + """Test eggholder dim exception""" + with pytest.raises(IndexError): + fx.eggholder_func(outdim) + + +def test_himmelblau_dim_fail(outdim): + """Test himmelblau dim exception""" + with pytest.raises(IndexError): + fx.himmelblau_func(outdim) + + +def test_holdertable_dim_fail(outdim): + """Test holdertable dim exception""" + with pytest.raises(IndexError): + fx.holdertable_func(outdim) + def test_levi_dim_fail(outdim): """Test levi dim exception""" with pytest.raises(IndexError): fx.levi_func(outdim) + +def test_matyas_dim_fail(outdim): + """Test matyas dim exception""" + with pytest.raises(IndexError): + fx.matyas_func(outdim) + + def test_schaffer2_dim_fail(outdim): """Test schaffer2 dim exception""" with pytest.raises(IndexError): - fx.schaffer2_func(outdim) \ No newline at end of file + fx.schaffer2_func(outdim) + + +def test_threehump_dim_fail(outdim): + """Test threehump dim exception""" + with pytest.raises(IndexError): + fx.threehump_func(outdim) diff --git a/tests/utils/functions/test_singleobj_return.py b/tests/utils/functions/test_singleobj_return.py index 5fc3b819..5c6fbb36 100644 --- a/tests/utils/functions/test_singleobj_return.py +++ b/tests/utils/functions/test_singleobj_return.py @@ -9,46 +9,149 @@ # Import from package from pyswarms.utils.functions import single_obj as fx -def test_sphere_output(common_minima): - """Tests sphere function output.""" - assert np.array_equal(fx.sphere_func(common_minima), np.zeros((3,))) -def test_rastrigin_output(common_minima): - """Tests rastrigin function output.""" - assert np.array_equal(fx.rastrigin_func(common_minima), np.zeros(3)) + + def test_ackley_output(common_minima): """Tests ackley function output.""" assert np.isclose(fx.ackley_func(common_minima), np.zeros(3)).all() -def test_rosenbrock_output(common_minima2): - """Tests rosenbrock function output.""" - assert np.array_equal(fx.rosenbrock_func(common_minima2).all(),np.zeros(3).all()) def test_beale_output(common_minima2): """Tests beale function output.""" - assert np.isclose(fx.beale_func([3, 0.5] * common_minima2), np.zeros(3)).all() + assert np.isclose( + fx.beale_func([3, 0.5] * common_minima2), np.zeros(3) + ).all() -def test_goldstein_output(common_minima2): - """Tests goldstein-price function output.""" - assert np.isclose(fx.goldstein_func([0, -1] * common_minima2), (3 * np.ones(3))).all() def test_booth_output(common_minima2): """Test booth function output.""" - assert np.isclose(fx.booth_func([1, 3] * common_minima2), np.zeros(3)).all() + assert np.isclose( + fx.booth_func([1, 3] * common_minima2), np.zeros(3) + ).all() + def test_bukin6_output(common_minima2): """Test bukin function output.""" - assert np.isclose(fx.bukin6_func([-10, 1] * common_minima2), np.zeros(3)).all() + assert np.isclose( + fx.bukin6_func([-10, 1] * common_minima2), np.zeros(3) + ).all() + + +@pytest.mark.parametrize( + "x", + [ + np.array([[1.34941, -1.34941], + [1.34941, 1.34941], + [-1.34941, 1.34941], + [-1.34941, -1.34941]]) + ], +) +@pytest.mark.parametrize( + "minima", + [ + np.array([-2.06261, -2.06261, -2.06261, -2.06261]) + ], +) +def test_crossintray_output(x, minima): + """Tests crossintray function output.""" + assert np.isclose( + fx.crossintray_func(x), minima + ).all() + + +def test_easom_output(common_minima2): + """Tests easom function output.""" + assert np.isclose( + fx.easom_func([np.pi, np.pi] * common_minima2), (-1 * np.ones(3)) + ).all() + + +def test_eggholder_output(common_minima2): + """Tests eggholder function output.""" + assert np.isclose( + fx.eggholder_func([512, 404.3219] * common_minima2), (-959.6407 * np.ones(3)) + ).all() + + +def test_goldstein_output(common_minima2): + """Tests goldstein-price function output.""" + assert np.isclose( + fx.goldstein_func([0, -1] * common_minima2), (3 * np.ones(3)) + ).all() + + +@pytest.mark.parametrize( + "x", + [ + np.array([[3.0, 2.0], + [-2.805118, 3.131312], + [-3.779310, -3.283186], + [3.584428, -1.848126]]) + ], +) +def test_himmelblau_output(x): + """Tests himmelblau function output.""" + assert np.isclose( + fx.himmelblau_func(x), np.zeros(4) + ).all() + + +@pytest.mark.parametrize( + "x", + [ + np.array([[8.05502, 9.66459], + [-8.05502, 9.66459], + [8.05502, -9.66459], + [-8.05502, -9.66459]]) + ], +) +@pytest.mark.parametrize( + "minima", + [ + np.array([-19.2085, -19.2085, -19.2085, -19.2085]) + ], +) +def test_holdertable_output(x, minima): + """Tests holdertable function output.""" + assert np.isclose( + fx.holdertable_func(x), minima + ).all() -def test_bukin6_output(common_minima): - """Test bukin function output.""" - assert np.isclose(fx.matyas_func(common_minima), np.zeros(3)).all() def test_levi_output(common_minima2): """Test levi function output.""" assert np.isclose(fx.levi_func(common_minima2), np.zeros(3)).all() + +def test_matyas_output(common_minima): + """Test matyas function output.""" + assert np.isclose(fx.matyas_func(common_minima), np.zeros(3)).all() + + +def test_rastrigin_output(common_minima): + """Tests rastrigin function output.""" + assert np.array_equal(fx.rastrigin_func(common_minima), np.zeros(3)) + + +def test_rosenbrock_output(common_minima2): + """Tests rosenbrock function output.""" + assert np.array_equal( + fx.rosenbrock_func(common_minima2).all(), np.zeros(3).all() + ) + + def test_schaffer2_output(common_minima): """Test schaffer2 function output.""" - assert np.isclose(fx.schaffer2_func(common_minima), np.zeros(3)).all() \ No newline at end of file + assert np.isclose(fx.schaffer2_func(common_minima), np.zeros(3)).all() + + +def test_sphere_output(common_minima): + """Tests sphere function output.""" + assert np.array_equal(fx.sphere_func(common_minima), np.zeros((3,))) + + +def test_threehump_output(common_minima): + """Tests threehump function output.""" + assert np.array_equal(fx.threehump_func(common_minima), np.zeros(3)) diff --git a/tests/utils/functions/test_singleobj_returndims.py b/tests/utils/functions/test_singleobj_returndims.py index cbaa6d17..2e40a2e4 100644 --- a/tests/utils/functions/test_singleobj_returndims.py +++ b/tests/utils/functions/test_singleobj_returndims.py @@ -9,42 +9,82 @@ # Import from package from pyswarms.utils.functions import single_obj as fx -def test_sphere_output_size(common_minima, targetdim): - """Tests sphere output size.""" - assert fx.sphere_func(common_minima).shape == targetdim - -def test_rastrigin_output_size(common_minima, targetdim): - """Tests rastrigin output size.""" - assert fx.rastrigin_func(common_minima).shape == targetdim def test_ackley_output_size(common_minima, targetdim): """Tests ackley output size.""" assert fx.ackley_func(common_minima).shape == targetdim -def test_rosenbrock_output_size(common_minima, targetdim): - """Tests rosenbrock output size.""" - assert fx.rosenbrock_func(common_minima).shape == targetdim def test_beale_output_size(common_minima, targetdim): """Tests beale output size.""" assert fx.beale_func(common_minima).shape == targetdim -def test_goldstein_output_size(common_minima, targetdim): - """Test goldstein output size.""" - assert fx.goldstein_func(common_minima).shape == targetdim def test_booth_output_size(common_minima, targetdim): """Test booth output size.""" assert fx.booth_func(common_minima).shape == targetdim + def test_bukin6_output_size(common_minima2, targetdim): """Test bukin6 output size.""" - assert fx.bukin6_func([-10,0] * common_minima2).shape == targetdim + assert fx.bukin6_func([-10, 0] * common_minima2).shape == targetdim + + +def test_crossintray_output_size(common_minima2, targetdim): + """Test crossintray output size.""" + assert fx.crossintray_func([-10, 0] * common_minima2).shape == targetdim + + +def test_easom_output_size(common_minima2, targetdim): + """Test easom output size.""" + assert fx.easom_func([-10, 0] * common_minima2).shape == targetdim + + +def test_eggholder_output_size(common_minima2, targetdim): + """Test eggholder output size.""" + assert fx.eggholder_func([-10, 0] * common_minima2).shape == targetdim + + +def test_goldstein_output_size(common_minima, targetdim): + """Test goldstein output size.""" + assert fx.goldstein_func(common_minima).shape == targetdim + + +def test_himmelblau_output_size(common_minima, targetdim): + """Test himmelblau output size.""" + assert fx.himmelblau_func(common_minima).shape == targetdim + + +def test_holdertable_output_size(common_minima, targetdim): + """Test holdertable output size.""" + assert fx.holdertable_func(common_minima).shape == targetdim + def test_levi_output_size(common_minima, targetdim): """Test levi output size.""" assert fx.levi_func(common_minima).shape == targetdim + +def test_rastrigin_output_size(common_minima, targetdim): + """Tests rastrigin output size.""" + assert fx.rastrigin_func(common_minima).shape == targetdim + + +def test_rosenbrock_output_size(common_minima, targetdim): + """Tests rosenbrock output size.""" + assert fx.rosenbrock_func(common_minima).shape == targetdim + + def test_schaffer2_output_size(common_minima, targetdim): """Test schaffer2 output size.""" assert fx.schaffer2_func(common_minima).shape == targetdim + + +def test_sphere_output_size(common_minima, targetdim): + """Tests sphere output size.""" + assert fx.sphere_func(common_minima).shape == targetdim + + +def test_threehump_output_size(common_minima, targetdim): + """Test threehump output size.""" + assert fx.threehump_func(common_minima).shape == targetdim diff --git a/tests/utils/plotters/__init__.py b/tests/utils/plotters/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/utils/plotters/conftest.py b/tests/utils/plotters/conftest.py new file mode 100644 index 00000000..6b8da6e4 --- /dev/null +++ b/tests/utils/plotters/conftest.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Fixtures for tests""" + +# Import modules +import os +import pytest +import numpy as np +from mock import Mock +import matplotlib as mpl + +if os.environ.get("DISPLAY", "") == "": + mpl.use("Agg") + +# Import from package +from pyswarms.single import GlobalBestPSO +from pyswarms.utils.functions.single_obj import sphere_func +from pyswarms.utils.plotters.formatters import Mesher + + +@pytest.fixture +def trained_optimizer(): + """Returns a trained optimizer instance with 100 iterations""" + options = {"c1": 0.5, "c2": 0.3, "w": 0.9} + optimizer = GlobalBestPSO(n_particles=10, dimensions=2, options=options) + optimizer.optimize(sphere_func, iters=100) + return optimizer + + +@pytest.fixture +def pos_history(): + """Returns a list containing a swarms' position history""" + return np.random.uniform(size=(10, 5, 2)) + + +@pytest.fixture +def mesher(): + """A Mesher instance with sphere function and delta=0.1""" + return Mesher(func=sphere_func, delta=0.1) diff --git a/tests/utils/plotters/test_plotters.py b/tests/utils/plotters/test_plotters.py new file mode 100644 index 00000000..668129d4 --- /dev/null +++ b/tests/utils/plotters/test_plotters.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Import modules +import os +import pytest +import matplotlib as mpl + +if os.environ.get("DISPLAY", "") == "": + mpl.use("Agg") + +from matplotlib.axes._subplots import SubplotBase +from matplotlib.animation import FuncAnimation + +# Import from package +from pyswarms.utils.plotters import ( + plot_cost_history, + plot_contour, + plot_surface, +) + +from pyswarms.utils.plotters.plotters import _mesh, _animate +from pyswarms.utils.plotters.formatters import Mesher + + +@pytest.mark.parametrize( + "history", ["cost_history", "mean_neighbor_history", "mean_pbest_history"] +) +def test_plot_cost_history_return_type(trained_optimizer, history): + """Tests if plot_cost_history() returns a SubplotBase instance""" + opt_params = vars(trained_optimizer) + plot = plot_cost_history(opt_params[history]) + assert isinstance(plot, SubplotBase) + + +@pytest.mark.parametrize("bad_values", [2, 43.14]) +def test_plot_cost_history_error(bad_values): + """Tests if plot_cost_history() raises an error given bad values""" + with pytest.raises(TypeError): + plot_cost_history(bad_values) + + +def test_plot_contour_return_type(pos_history): + """Tests if the animation function returns the expected type""" + assert isinstance(plot_contour(pos_history), FuncAnimation) + + +def test_plot_surface_return_type(pos_history): + """Tests if the animation function returns the expected type""" + assert isinstance(plot_surface(pos_history), FuncAnimation) + + +def test_mesh_hidden_function_shape(mesher): + """Tests if the hidden _mesh() function returns the expected shape""" + xx, yy, zz = _mesh(mesher) + assert xx.shape == yy.shape == zz.shape == (20, 20) + + +def test_animate_hidden_function_type(pos_history): + """Tests if the hidden _animate() function returns the expected type""" + fig, ax = mpl.pyplot.subplots(1, 1) + ax = mpl.pyplot.scatter(x=[], y=[]) + return_plot = _animate(i=1, data=pos_history, plot=ax) + assert isinstance(return_plot, tuple) diff --git a/tests/utils/search/conftest.py b/tests/utils/search/conftest.py index dcc31b40..8ef81ce9 100644 --- a/tests/utils/search/conftest.py +++ b/tests/utils/search/conftest.py @@ -13,45 +13,83 @@ from pyswarms.single import LocalBestPSO from pyswarms.utils.functions.single_obj import sphere_func + @pytest.fixture def grid(): """Returns a GridSearch instance""" - options = {'c1': [1, 2, 3], - 'c2': [1, 2, 3], - 'k' : [5, 10, 15], - 'w' : [0.9, 0.7, 0.4], - 'p' : [1]} - return GridSearch(LocalBestPSO, n_particles=40, - dimensions=20, options=options, objective_func=sphere_func, iters=10, bounds=None) + options = { + "c1": [1, 2, 3], + "c2": [1, 2, 3], + "k": [5, 10, 15], + "w": [0.9, 0.7, 0.4], + "p": [1], + } + return GridSearch( + LocalBestPSO, + n_particles=40, + dimensions=20, + options=options, + objective_func=sphere_func, + iters=10, + bounds=None, + ) + @pytest.fixture def grid_mini(): """Returns a GridSearch instance with a smaller search-space""" - options = {'c1': [1, 2], - 'c2': 6, - 'k' : 5, - 'w' : 0.9, - 'p' : 0} - return GridSearch(LocalBestPSO, n_particles=40, - dimensions=20, options=options, objective_func=sphere_func, iters=10, bounds=None) + options = {"c1": [1, 2], "c2": 6, "k": 5, "w": 0.9, "p": 0} + return GridSearch( + LocalBestPSO, + n_particles=40, + dimensions=20, + options=options, + objective_func=sphere_func, + iters=10, + bounds=None, + ) + @pytest.fixture def random_unbounded(): """Returns a RandomSearch instance without bounds""" - options = {'c1': [1, 5], - 'c2': [6, 10], - 'k' : [11, 15], - 'w' : [0.4, 0.9], - 'p' : 1} - return RandomSearch(LocalBestPSO, n_particles=40, dimensions=20, options=options, objective_func=sphere_func, iters=10, n_selection_iters=100, bounds=None) + options = { + "c1": [1, 5], + "c2": [6, 10], + "k": [11, 15], + "w": [0.4, 0.9], + "p": 1, + } + return RandomSearch( + LocalBestPSO, + n_particles=40, + dimensions=20, + options=options, + objective_func=sphere_func, + iters=10, + n_selection_iters=100, + bounds=None, + ) + @pytest.fixture def random_bounded(): """Returns a RandomSearch instance with bounds""" - bounds = (np.array([-5,-5]), np.array([5,5])) - options = {'c1': [1, 5], - 'c2': [6, 10], - 'k' : [11, 15], - 'w' : [0.4, 0.9], - 'p' : 1} - return RandomSearch(LocalBestPSO, n_particles=40, dimensions=20, options=options, objective_func=sphere_func, iters=10, n_selection_iters=100, bounds=bounds) \ No newline at end of file + bounds = (np.array([-5, -5]), np.array([5, 5])) + options = { + "c1": [1, 5], + "c2": [6, 10], + "k": [11, 15], + "w": [0.4, 0.9], + "p": 1, + } + return RandomSearch( + LocalBestPSO, + n_particles=40, + dimensions=20, + options=options, + objective_func=sphere_func, + iters=10, + n_selection_iters=100, + bounds=bounds, + ) diff --git a/tests/utils/search/test_gridsearch.py b/tests/utils/search/test_gridsearch.py index 85cc4b86..8a9936f2 100644 --- a/tests/utils/search/test_gridsearch.py +++ b/tests/utils/search/test_gridsearch.py @@ -4,14 +4,18 @@ # Import modules import pytest -@pytest.mark.parametrize('maximum', [True, False]) + +@pytest.mark.parametrize("maximum", [True, False]) def test_search_best_options_return_type(grid, maximum): """Tests if best options returns a dictionary""" _, best_options = grid.search(maximum) assert isinstance(best_options, dict) + def test_grid_output(grid_mini): """Tests if generate_grid function returns expected value""" - expected = [{'c1': 1, 'c2': 6, 'k': 5, 'w': 0.9, 'p': 0}, - {'c1': 2, 'c2': 6, 'k': 5, 'w': 0.9, 'p': 0}] - assert grid_mini.generate_grid() == expected \ No newline at end of file + expected = [ + {"c1": 1, "c2": 6, "k": 5, "w": 0.9, "p": 0}, + {"c1": 2, "c2": 6, "k": 5, "w": 0.9, "p": 0}, + ] + assert grid_mini.generate_grid() == expected diff --git a/tests/utils/search/test_randomsearch.py b/tests/utils/search/test_randomsearch.py index e2677135..7ac99045 100644 --- a/tests/utils/search/test_randomsearch.py +++ b/tests/utils/search/test_randomsearch.py @@ -4,12 +4,14 @@ # Import modules import pytest -@pytest.mark.parametrize('maximum', [True, False]) + +@pytest.mark.parametrize("maximum", [True, False]) def test_search_best_options_return_type(random_unbounded, maximum): """Tests if best options returns a dictionary""" _, best_options = random_unbounded.search(maximum) assert isinstance(best_options, dict) + def test_generate_grid_combinations(random_bounded): """Test that the number of combinations in grid equals the number parameter selection iterations specficied""" @@ -17,7 +19,8 @@ def test_generate_grid_combinations(random_bounded): grid = random_bounded.generate_grid() assert len(grid) == expected -@pytest.mark.parametrize('options', ['c1', 'c2', 'k', 'w']) + +@pytest.mark.parametrize("options", ["c1", "c2", "k", "w"]) def test_generate_grid_parameter_mapping(random_bounded, options): """Test that generated values are correctly mapped to each parameter and are within the specified bounds """ @@ -25,4 +28,4 @@ def test_generate_grid_parameter_mapping(random_bounded, options): values = [x[options] for x in grid] for val in values: assert val >= random_bounded.options[options][0] - assert val <= random_bounded.options[options][1] \ No newline at end of file + assert val <= random_bounded.options[options][1]