diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..cecfe93a --- /dev/null +++ b/.coveragerc @@ -0,0 +1,5 @@ +[report] +exclude_lines = + pragma: no cover + @abc.abstractmethod + pos = init_pos diff --git a/.gitignore b/.gitignore index 71cb3794..b2eaaab1 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ __pycache__/ # Distribution / packaging .Python -env/ +venv/ build/ develop-eggs/ dist/ @@ -68,4 +68,4 @@ target/ .python-version # Vagrant folder -.vagrant \ No newline at end of file +.vagrant diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 00000000..a5b2d0d8 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,5 @@ +[settings] +skip=test_plotters.py +import_heading_stdlib=Import standard library +import_heading_firstparty=Import from pyswarms +import_heading_thirdparty=Import modules diff --git a/.travis.yml b/.travis.yml index 44b7e2c0..1c6a5e5f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,5 +29,4 @@ language: python python: - 3.6 - 3.5 - - 3.4 script: tox diff --git a/HISTORY.rst b/HISTORY.rst index 8ddcf4a9..3fb7cfad 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -6,7 +6,7 @@ History ------------------ * First release on PyPI. -* Includes primary optimization techniques such as global-best PSO and local-best PSO - `#1`_, `#3`_ +* **NEW:** 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 @@ -14,8 +14,8 @@ History 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`_, `#14`_. Contributed by `@Carl-K `_. Thank you! +* **FIX:** Patch on LocalBestPSO implementation. It seems that it's not returning the best value of the neighbors, this fixes the problem . +* **NEW:** 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 @@ -24,9 +24,9 @@ History 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:** Binary Particle Swarm Optimization - `#7`_, `#17`_ +* **FIX:** Fix 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 @@ -37,12 +37,12 @@ History 0.1.4 (2017-08-03) ~~~~~~~~~~~~~~~~~~ -* Added a patch to fix :code:`pip` installation +* **FIX:** Added a patch to fix :code:`pip` installation 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`_, `#31`_ +* **NEW:** 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 @@ -50,9 +50,9 @@ History 0.1.6 (2017-09-24) ~~~~~~~~~~~~~~~~~~ -* **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 +* **NEW:** Native GridSearch and RandomSearch implementations for finding the best hyperparameters in controlling swarm behaviour - `#4`_, `#20`_, `#25`_. Contributed by `@SioKCronin `_. Thanks a lot! +* **NEW:** Added tests for hyperparameter search techniques - `#27`_, `#28`_, `#40`_. Contributed by `@jazcap53 `_. Thank you so much! +* **IMPROVED:** Updated structure of Base classes for higher extensibility .. _#4: https://github.com/ljvmiranda921/pyswarms/issues/4 .. _#20: https://github.com/ljvmiranda921/pyswarms/pull/20 @@ -64,8 +64,8 @@ History 0.1.7 (2017-09-25) ~~~~~~~~~~~~~~~~~~ -* 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 +* **FIX:** Fixed patch on :code:`local_best.py` and :code:`binary.py` - `#33`_, `#34`_. Thanks for the awesome fix, `@CPapadim `_! +* **NEW:** Git now ignores IPython notebook checkpoints .. _#33: https://github.com/ljvmiranda921/pyswarms/issues/33 .. _#34: https://github.com/ljvmiranda921/pyswarms/pull/34 @@ -73,7 +73,7 @@ History 0.1.8 (2018-01-11) ~~~~~~~~~~~~~~~~~~ -* PySwarms is now published on the Journal of Open Source Software (JOSS)! You can check the review here_. In addition, you can also find our paper in this link_. Thanks a lot to `@kyleniemeyer `_ and `@stsievert `_ for the thoughtful reviews and comments. +* **NEW:** PySwarms is now published on the Journal of Open Source Software (JOSS)! You can check the review here_. In addition, you can also find our paper in this link_. Thanks a lot to `@kyleniemeyer `_ and `@stsievert `_ for the thoughtful reviews and comments. .. _here: https://github.com/openjournals/joss-reviews/issues/433 .. _link: http://joss.theoj.org/papers/235299884212b9223bce909631e3938b @@ -81,9 +81,9 @@ History 0.1.9 (2018-04-20) ~~~~~~~~~~~~~~~~~~ -* 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`_ +* **NEW:** You can now set the initial position wherever you want - `#93`_ +* **FIX:** Quick-fix for the Rosenbrock function - `#98`_ +* **NEW:** Tolerance can now be set to break during iteration - `#100`_ Thanks for all the wonderful Pull Requests, `@mamadyonline `_! @@ -95,9 +95,9 @@ Thanks for all the wonderful Pull Requests, `@mamadyonline `_! +* **FIX:** Fix sigmoid function in BinaryPSO - `#145`_. Thanks a lot `@ThomasCES `_! .. _#145: https://github.com/ljvmiranda921/pyswarms/pull/145 0.3.0 (2018-08-10) ------------------ -* New topologies: Pyramid, Random, and Von Neumann. More ways for your particles to interact! - `#176`_, `#177`_, `#155`_, `#142`_. Thanks a lot `@whzup `_! -* 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 `_ +* **NEW:** New topologies: Pyramid, Random, and Von Neumann. More ways for your particles to interact! - `#176`_, `#177`_, `#155`_, `#142`_. Thanks a lot `@whzup `_! +* **NEW:** New GeneralOptimizer algorithm that allows you to switch-out topologies for your optimization needs - `#151`_. Thanks a lot `@whzup `_! +* **NEW:** All topologies now have a static attribute. Neigbors can now be set initially or computed dynamically - `#164`_. Thanks a lot `@whzup `_! +* **NEW:** New single-objective functions - `#168`_. Awesome work, `@jayspeidell `_! +* **NEW:** New tutorial on Inverse Kinematics using Particle Swarm Optimization - `#141`_. Thanks a lot `@whzup `_! +* **NEW:** New plotters module for visualization. The environment module is now deprecated - `#135`_ +* **IMPROVED:** 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 @@ -138,12 +138,35 @@ Thanks for all the wonderful Pull Requests, `@mamadyonline `_! -* Add configuration file for pyup.io - `#210`_ -* Fix incomplete documentation in ReadTheDocs - `#208`_ -* Update dependencies via pyup - `#204`_ +* **NEW:** New collaboration tool using Vagrantfiles - `#193`_. Thanks a lot `@jdbohrman `_! +* **NEW:** Add configuration file for pyup.io - `#210`_ +* **FIX:** Fix incomplete documentation in ReadTheDocs - `#208`_ +* **IMPROVED:** Update dependencies via pyup - `#204`_ .. _#193: https://github.com/ljvmiranda921/pyswarms/pull/193 .. _#204: https://github.com/ljvmiranda921/pyswarms/pull/204 .. _#208: https://github.com/ljvmiranda921/pyswarms/pull/208 -.. _#210: https://github.com/ljvmiranda921/pyswarms/pull/210 \ No newline at end of file +.. _#210: https://github.com/ljvmiranda921/pyswarms/pull/210 + +0.4.0 (2019-01-29) +------------------ + +* **NEW:** The console output is now generated by the :code:`Reporter` module - `#227`_ +* **NEW:** A :code:`@cost` decorator which automatically scales to the whole swarm - `#226`_ +* **FIX:** A bug in the topologies where the best position in some topologies was not calculated using the nearest neighbours - `#253`_ +* **FIX:** Swarm init positions - `#249`_ Thanks `@dfhljf`_! +* **IMPROVED:** Better naming for the benchmark functions - `#222`_ Thanks `@nik1082`_! +* **IMPROVED:** Error handling in the :code:`Optimizers` - `#232`_ +* **IMPROVED:** New management method for dependencies - `#263`_ +* **DEPRECATED:** The `environments` module is now deprecated - `#217`_ + +.. _#217: https://github.com/ljvmiranda921/pyswarms/pull/217 +.. _#222: https://github.com/ljvmiranda921/pyswarms/pull/222 +.. _#226: https://github.com/ljvmiranda921/pyswarms/pull/226 +.. _#227: https://github.com/ljvmiranda921/pyswarms/pull/227 +.. _#232: https://github.com/ljvmiranda921/pyswarms/pull/232 +.. _#249: https://github.com/ljvmiranda921/pyswarms/pull/249 +.. _#253: https://github.com/ljvmiranda921/pyswarms/pull/253 +.. _#263: https://github.com/ljvmiranda921/pyswarms/pull/263 +.. _@nik1082: https://github.com/nik1082 +.. _@dfhljf: https://github.com/dfhljf diff --git a/MANIFEST.in b/MANIFEST.in index 022b2de4..dbff85fe 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,6 +3,8 @@ include CONTRIBUTING.rst include HISTORY.rst include LICENSE include README.md +include requirements.txt +include requirements-dev.txt recursive-include tests * recursive-exclude * __pycache__ diff --git a/Makefile b/Makefile index a2ef4ba7..bef6edcb 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,12 @@ BROWSER := python -c "$$BROWSER_PYSCRIPT" help: @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) +build: venv requirements.txt + venv/bib/pip-sync + +dev: venv requirements-dev.txt + venv/bib/pip-sync requirements-dev.txt + clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts @@ -85,3 +91,13 @@ dist: clean ## builds source and wheel package install: clean ## install the package to the active Python's site-packages python setup.py install + +venv: + python3 -m venv venv + venv/bin/pip3 install pip-tools + +requirements.txt: requirements.in + venv/bin/pip-compile -o requirements.txt --no-header --no-annotate requirements.in + +requirements-dev.txt: requirements-dev.in + venv/bin/pip-compile -o requirements-dev.txt --no-header --no-annotate requirements-dev.in diff --git a/README.md b/README.md index fcd28a46..e63be2d1 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ interaction with swarm optimizations. Check out more features below! * **Free software:** MIT license * **Documentation:** https://pyswarms.readthedocs.io. -* **Python versions:** 3.4 and above +* **Python versions:** 3.5 and above ## Features @@ -101,7 +101,7 @@ import pyswarms as ps Suppose we want to find the minima of `f(x) = x^2` using global best PSO, simply import the built-in sphere function, -`pyswarms.utils.functions.sphere_func()`, and the necessary optimizer: +`pyswarms.utils.functions.sphere()`, and the necessary optimizer: ```python import pyswarms as ps @@ -111,7 +111,7 @@ options = {'c1': 0.5, 'c2': 0.3, 'w':0.9} # Call instance of PSO optimizer = ps.single.GlobalBestPSO(n_particles=10, dimensions=2, options=options) # Perform optimization -best_cost, best_pos = optimizer.optimize(fx.sphere_func, iters=100, verbose=3, print_step=25) +best_cost, best_pos = optimizer.optimize(fx.sphere, iters=100, verbose=3, print_step=25) ``` ```s >>> 2017-10-03 10:12:33,859 - pyswarms.single.global_best - INFO - Iteration 1/100, cost: 0.131244226714 @@ -170,7 +170,7 @@ options = { # n_selection_iters is the number of iterations to run the searcher # iters is the number of iterations to run the optimizer g = RandomSearch(ps.single.LocalBestPSO, n_particles=40, - dimensions=20, options=options, objective_func=fx.sphere_func, + dimensions=20, options=options, objective_func=fx.sphere, iters=10, n_selection_iters=100) best_score, best_options = g.search() @@ -202,7 +202,7 @@ 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=50, dimensions=2, options=options) -optimizer.optimize(fx.sphere_func, iters=100) +optimizer.optimize(fx.sphere, iters=100) # Plot the cost plot_cost_history(optimizer.cost_history) plt.show() @@ -216,7 +216,8 @@ We can also plot the animation... 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) +m = Mesher(func=fx.sphere_func, + limits=[(-1,1), (-1,1)]) # Adjust figure limits d = Designer(limits=[(-1,1), (-1,1), (-0.1,1)], label=['x-axis', 'y-axis', 'z-axis']) @@ -225,7 +226,7 @@ d = Designer(limits=[(-1,1), (-1,1), (-0.1,1)], In 2D, ```python -plot_contour(pos_history=optimizer.pos_history, mesher=m, mark=(0,0)) +plot_contour(pos_history=optimizer.pos_history, mesher=m, designer=d, mark=(0,0)) ``` ![Contour](https://i.imgur.com/H3YofJ6.gif) diff --git a/docs/api/_pyswarms.utils.rst b/docs/api/_pyswarms.utils.rst index a7e81225..70bf7da8 100644 --- a/docs/api/_pyswarms.utils.rst +++ b/docs/api/_pyswarms.utils.rst @@ -7,7 +7,9 @@ functionalities. .. toctree:: + pyswarms.utils.decorators pyswarms.utils.functions - pyswarms.utils.search pyswarms.utils.plotters - pyswarms.utils.environments + pyswarms.utils.reporter + pyswarms.utils.search + \ No newline at end of file diff --git a/docs/api/pyswarms.utils.decorators.rst b/docs/api/pyswarms.utils.decorators.rst new file mode 100644 index 00000000..01a5a295 --- /dev/null +++ b/docs/api/pyswarms.utils.decorators.rst @@ -0,0 +1,7 @@ +pyswarms.utils.decorators package +================================= + +.. automodule:: pyswarms.utils.decorators + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/pyswarms.utils.environments.rst b/docs/api/pyswarms.utils.environments.rst deleted file mode 100644 index 907e20b4..00000000 --- a/docs/api/pyswarms.utils.environments.rst +++ /dev/null @@ -1,17 +0,0 @@ -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 ----------------------------------------------------- - -.. automodule:: pyswarms.utils.environments.plot_environment - :members: - :undoc-members: - :show-inheritance: - :special-members: __init__ \ No newline at end of file diff --git a/docs/api/pyswarms.utils.reporter.rst b/docs/api/pyswarms.utils.reporter.rst new file mode 100644 index 00000000..ba18e082 --- /dev/null +++ b/docs/api/pyswarms.utils.reporter.rst @@ -0,0 +1,10 @@ +pyswarms.utils.reporter package +================================ + +.. automodule:: pyswarms.utils.reporter.reporter + :members: + :undoc-members: + :show-inheritance: + :private-members: + :special-members: __init__ + diff --git a/docs/conf.py b/docs/conf.py index 56e0eceb..ba7be3b2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,57 +13,60 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys +# Import standard library import os +import sys + +# Import from pyswarms +import pyswarms # If extensions (or modules to document with autodoc) are in another # directory, add these directories to sys.path here. If the directory is # relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) # Get the project root dir, which is the parent dir of this -#cwd = os.getcwd() -#project_root = os.path.dirname(cwd) +# cwd = os.getcwd() +# project_root = os.path.dirname(cwd) # Insert the project root dir as the first element in the PYTHONPATH. # This lets us ensure that the source package is imported, and that its # version is used. -#sys.path.insert(0, project_root) -sys.path.insert(0, os.path.abspath('../')) +# sys.path.insert(0, project_root) +sys.path.insert(0, os.path.abspath("../")) -import pyswarms # -- General configuration --------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.viewcode', - 'sphinx.ext.napoleon', - 'sphinx.ext.mathjax' - ] + "sphinx.ext.autodoc", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "sphinx.ext.mathjax", +] -exclude_patterns = ['_build', '**.ipynb_checkpoints'] +exclude_patterns = ["_build", "**.ipynb_checkpoints"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'PySwarms' +project = u"PySwarms" copyright = u"2017, Lester James V. Miranda" # The version info for the project you're documenting, acts as replacement @@ -77,174 +80,177 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to # some non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built # documents. -#keep_warnings = False +# keep_warnings = False # -- Options for HTML output ------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a # theme further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as # html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the # top of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon # of the docs. This file should be a Windows icon file (.ico) being # 16x16 or 32x32 pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) # here, relative to this directory. They are copied after the builtin # static files, so a file named "default.css" will overwrite the builtin # "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] + def setup(app): # overrides for wide tables in RTD theme - app.add_stylesheet('theme_overrides.css') # path relative to static + app.add_stylesheet("theme_overrides.css") # path relative to static # If not '', a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. -html_last_updated_fmt = '%b %d, %Y' +html_last_updated_fmt = "%b %d, %Y" # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names # to template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. # Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. # Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages # will contain a tag referring to it. The value of this option # must be the base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'pyswarmsdoc' +htmlhelp_basename = "pyswarmsdoc" # -- Options for LaTeX output ------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). - 'papersize': 'a4paper', - + "papersize": "a4paper", # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', - + # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. - #'preamble': '', + # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ - ('index', 'pyswarms.tex', - u'PySwarms Documentation', - u'Lester James V. Miranda', 'manual'), + ( + "index", + "pyswarms.tex", + u"PySwarms Documentation", + u"Lester James V. Miranda", + "manual", + ) ] # The name of an image file (relative to this directory) to place at # the top of the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings # are parts, not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output ------------------------------------ @@ -252,13 +258,17 @@ def setup(app): # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'pyswarms', - u'PySwarms Documentation', - [u'Lester James V. Miranda'], 1) + ( + "index", + "pyswarms", + u"PySwarms Documentation", + [u"Lester James V. Miranda"], + 1, + ) ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ---------------------------------------- @@ -267,22 +277,25 @@ def setup(app): # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'pyswarms', - u'PySwarms Documentation', - u'Lester James V. Miranda', - 'pyswarms', - 'PySwarms is a simple, Python-based, Particle Swarm Optimization (PSO) library.', - 'Research'), + ( + "index", + "pyswarms", + u"PySwarms Documentation", + u"Lester James V. Miranda", + "pyswarms", + "PySwarms is a simple, Python-based, Particle Swarm Optimization (PSO) library.", + "Research", + ) ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False diff --git a/docs/examples/basic_optimization.rst b/docs/examples/basic_optimization.rst index 6de5d997..aa5e93a0 100644 --- a/docs/examples/basic_optimization.rst +++ b/docs/examples/basic_optimization.rst @@ -71,7 +71,7 @@ several variables at once. optimizer = ps.single.GlobalBestPSO(n_particles=10, dimensions=2, options=options) # Perform optimization - cost, pos = optimizer.optimize(fx.sphere_func, print_step=100, iters=1000, verbose=3) + cost, pos = optimizer.optimize(fx.sphere, print_step=100, iters=1000, verbose=3) .. parsed-literal:: @@ -116,7 +116,7 @@ Now, let’s try this one using local-best PSO: optimizer = ps.single.LocalBestPSO(n_particles=10, dimensions=2, options=options) # Perform optimization - cost, pos = optimizer.optimize(fx.sphere_func, print_step=100, iters=1000, verbose=3) + cost, pos = optimizer.optimize(fx.sphere, print_step=100, iters=1000, verbose=3) .. parsed-literal:: @@ -151,7 +151,7 @@ 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 -PSO with the Rastrigin function (``rastrigin_func`` in +PSO with the Rastrigin function (``rastrigin`` in ``pyswarms.utils.functions.single_obj``). Recall that the Rastrigin function is bounded within ``[-5.12, 5.12]``. @@ -191,7 +191,7 @@ constant. optimizer = ps.single.GlobalBestPSO(n_particles=10, dimensions=2, options=options, bounds=bounds) # Perform optimization - cost, pos = optimizer.optimize(fx.rastrigin_func, print_step=100, iters=1000, verbose=3) + cost, pos = optimizer.optimize(fx.rastrigin, print_step=100, iters=1000, verbose=3) .. parsed-literal:: diff --git a/docs/examples/custom_optimization_loop.rst b/docs/examples/custom_optimization_loop.rst index fea3a029..2610fed1 100644 --- a/docs/examples/custom_optimization_loop.rst +++ b/docs/examples/custom_optimization_loop.rst @@ -29,7 +29,7 @@ new attributes. import numpy as np # Import sphere function as objective function - from pyswarms.utils.functions.single_obj import sphere_func as f + from pyswarms.utils.functions.single_obj import sphere as f # Import backend modules import pyswarms.backend as P diff --git a/docs/examples/visualization.rst b/docs/examples/visualization.rst index 7e5ec401..9b2f1506 100644 --- a/docs/examples/visualization.rst +++ b/docs/examples/visualization.rst @@ -50,7 +50,7 @@ the ``optimize()`` method for 100 iterations. options = {'c1':0.5, 'c2':0.3, 'w':0.9} optimizer = ps.single.GlobalBestPSO(n_particles=50, dimensions=2, options=options) - cost, pos = optimizer.optimize(fx.sphere_func, iters=100) + cost, pos = optimizer.optimize(fx.sphere, iters=100) .. code-block:: @@ -112,7 +112,7 @@ with respect to our objective function. We can accomplish that using the from pyswarms.utils.plotters.formatters import Mesher # Initialize mesher with sphere function - m = Mesher(func=fx.sphere_func) + m = Mesher(func=fx.sphere) There are different formatters available in the ``pyswarms.utils.plotters.formatters`` module to customize your plots diff --git a/docs/index.rst b/docs/index.rst index c542f96a..0e3cd1e7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -59,7 +59,7 @@ interaction with swarm optimizations. Check out more features below! * **Free software:** MIT license * **Github repository:** https://github.com/ljvmiranda921/pyswarms -* **Python versions:** 3.4, 3.5 and 3.6 +* **Python versions:** 3.5 and 3.6 Launching pad ------------- diff --git a/examples/basic_optimization.ipynb b/examples/basic_optimization.ipynb index 43136d3c..1e395c21 100644 --- a/examples/basic_optimization.ipynb +++ b/examples/basic_optimization.ipynb @@ -121,7 +121,7 @@ "optimizer = ps.single.GlobalBestPSO(n_particles=10, dimensions=2, options=options)\n", "\n", "# Perform optimization\n", - "cost, pos = optimizer.optimize(fx.sphere_func, print_step=100, iters=1000, verbose=3)" + "cost, pos = optimizer.optimize(fx.sphere, print_step=100, iters=1000, verbose=3)" ] }, { @@ -184,7 +184,7 @@ "optimizer = ps.single.LocalBestPSO(n_particles=10, dimensions=2, options=options)\n", "\n", "# Perform optimization\n", - "cost, pos = optimizer.optimize(fx.sphere_func, print_step=100, iters=1000, verbose=3)" + "cost, pos = optimizer.optimize(fx.sphere, print_step=100, iters=1000, verbose=3)" ] }, { @@ -192,7 +192,7 @@ "metadata": {}, "source": [ "## Optimizing a function with bounds\n", - "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 PSO with the Rastrigin function (`rastrigin_func` in `pyswarms.utils.functions.single_obj`)." + "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 PSO with the Rastrigin function (`rastrigin` in `pyswarms.utils.functions.single_obj`)." ] }, { @@ -269,7 +269,7 @@ "optimizer = ps.single.GlobalBestPSO(n_particles=10, dimensions=2, options=options, bounds=bounds)\n", "\n", "# Perform optimization\n", - "cost, pos = optimizer.optimize(fx.rastrigin_func, print_step=100, iters=1000, verbose=3)" + "cost, pos = optimizer.optimize(fx.rastrigin, print_step=100, iters=1000, verbose=3)" ] }, { diff --git a/examples/custom_optimization_loop.ipynb b/examples/custom_optimization_loop.ipynb index 94c22aa4..6368aa50 100644 --- a/examples/custom_optimization_loop.ipynb +++ b/examples/custom_optimization_loop.ipynb @@ -60,7 +60,7 @@ "import numpy as np\n", "\n", "# Import sphere function as objective function\n", - "from pyswarms.utils.functions.single_obj import sphere_func as f\n", + "from pyswarms.utils.functions.single_obj import sphere as f\n", "\n", "# Import backend modules\n", "import pyswarms.backend as P\n", diff --git a/examples/visualization.ipynb b/examples/visualization.ipynb index 1565520e..7c70d6c7 100644 --- a/examples/visualization.ipynb +++ b/examples/visualization.ipynb @@ -105,7 +105,7 @@ "source": [ "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)" + "cost, pos = optimizer.optimize(fx.sphere, iters=100)" ] }, { @@ -184,7 +184,7 @@ "outputs": [], "source": [ "# Initialize mesher with sphere function\n", - "m = Mesher(func=fx.sphere_func)" + "m = Mesher(func=fx.sphere)" ] }, { diff --git a/pyswarms/__init__.py b/pyswarms/__init__.py index edba6707..5bf3f4b5 100644 --- a/pyswarms/__init__.py +++ b/pyswarms/__init__.py @@ -16,5 +16,6 @@ from .single import global_best, local_best, general_optimizer from .discrete import binary +from .utils.decorators import cost -__all__ = ["global_best", "local_best", "general_optimizer", "binary"] +__all__ = ["global_best", "local_best", "general_optimizer", "binary", "cost"] diff --git a/pyswarms/backend/generators.py b/pyswarms/backend/generators.py index d475fbab..c3e9bdc0 100644 --- a/pyswarms/backend/generators.py +++ b/pyswarms/backend/generators.py @@ -9,12 +9,17 @@ """ +# Import standard library +import logging + # Import modules import numpy as np -# Import from package +from ..utils.reporter import Reporter from .swarms import Swarm +rep = Reporter(logger=logging.getLogger(__name__)) + def generate_swarm( n_particles, dimensions, bounds=None, center=1.00, init_pos=None @@ -41,6 +46,14 @@ def generate_swarm( ------- numpy.ndarray swarm matrix of shape (n_particles, n_dimensions) + + Raises + ------ + ValueError + When the shapes and values of bounds, dimensions, and init_pos + are inconsistent. + TypeError + When the argument passed to bounds is not an iterable. """ try: if (init_pos is not None) and (bounds is None): @@ -67,6 +80,12 @@ def generate_swarm( low=min_bounds, high=max_bounds, size=(n_particles, dimensions) ) except ValueError: + msg = "Bounds and/or init_pos should be of size ({},)" + rep.logger.exception(msg.format(dimensions)) + raise + except TypeError: + msg = "generate_swarm() takes an int for n_particles and dimensions and an array for bounds" + rep.logger.exception(msg) raise else: return pos @@ -88,11 +107,24 @@ def generate_discrete_swarm( init_pos : :code:`numpy.ndarray` (default is :code:`None`) option to explicitly set the particles' initial positions. Set to :code:`None` if you wish to generate the particles randomly. + + Returns + ------- + numpy.ndarray + swarm matrix of shape (n_particles, n_dimensions) + + Raises + ------ + ValueError + When init_pos during binary=True does not contain two unique values. + TypeError + When the argument passed to n_particles or dimensions is incorrect. """ try: if (init_pos is not None) and binary: - if not len(np.unique(init_pos)) == 2: + if not len(np.unique(init_pos)) <= 2: raise ValueError("User-defined init_pos is not binary!") + # init_pos maybe ones pos = init_pos elif (init_pos is not None) and not binary: pos = init_pos @@ -103,6 +135,11 @@ def generate_discrete_swarm( size=(n_particles, dimensions) ).argsort(axis=1) except ValueError: + rep.logger.exception("Please check the size and value of dimensions") + raise + except TypeError: + msg = "generate_discrete_swarm() takes an int for n_particles and dimensions" + rep.logger.exception(msg) raise else: return pos @@ -132,7 +169,13 @@ def generate_velocity(n_particles, dimensions, clamp=None): velocity = (max_velocity - min_velocity) * np.random.random_sample( size=(n_particles, dimensions) ) + min_velocity - except (ValueError, TypeError): + except ValueError: + msg = "Please check clamp shape: {} != {}" + rep.logger.exception(msg.format(len(clamp), dimensions)) + raise + except TypeError: + msg = "generate_velocity() takes an int for n_particles and dimensions and an array for clamp" + rep.logger.exception(msg) raise else: return velocity @@ -184,7 +227,7 @@ def create_swarm( """ if discrete: position = generate_discrete_swarm( - n_particles, dimensions, binary=binary + n_particles, dimensions, binary=binary, init_pos=init_pos ) else: position = generate_swarm( diff --git a/pyswarms/backend/operators.py b/pyswarms/backend/operators.py index fb42ecd2..2ff104e3 100644 --- a/pyswarms/backend/operators.py +++ b/pyswarms/backend/operators.py @@ -8,14 +8,15 @@ to specify how the swarm will behave. """ -# Import from stdlib +# Import standard library import logging # Import modules import numpy as np -# Create a logger -logger = logging.getLogger(__name__) +from ..utils.reporter import Reporter + +rep = Reporter(logger=logging.getLogger(__name__)) def compute_pbest(swarm): @@ -67,8 +68,9 @@ def compute_pbest(swarm): ~mask_cost, swarm.pbest_cost, swarm.current_cost ) except AttributeError: - msg = "Please pass a Swarm class. You passed {}".format(type(swarm)) - logger.error(msg) + rep.logger.exception( + "Please pass a Swarm class. You passed {}".format(type(swarm)) + ) raise else: return (new_pbest_pos, new_pbest_cost) @@ -137,12 +139,12 @@ def compute_velocity(swarm, clamp): ) updated_velocity = np.where(~mask, swarm.velocity, temp_velocity) except AttributeError: - msg = "Please pass a Swarm class. You passed {}".format(type(swarm)) - logger.error(msg) + rep.logger.exception( + "Please pass a Swarm class. You passed {}".format(type(swarm)) + ) raise except KeyError: - msg = "Missing keyword in swarm.options" - logger.error(msg) + rep.logger.exception("Missing keyword in swarm.options") raise else: return updated_velocity @@ -187,8 +189,9 @@ def compute_position(swarm, bounds): 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)) - logger.error(msg) + rep.logger.exception( + "Please pass a Swarm class. You passed {}".format(type(swarm)) + ) raise else: return position diff --git a/pyswarms/backend/swarms.py b/pyswarms/backend/swarms.py index a6d4db06..4f77bf40 100644 --- a/pyswarms/backend/swarms.py +++ b/pyswarms/backend/swarms.py @@ -10,7 +10,7 @@ # Import modules import numpy as np -from attr import attrs, attrib +from attr import attrib, attrs from attr.validators import instance_of @@ -69,7 +69,9 @@ class Swarm(object): pbest_pos : numpy.ndarray (default is :code:`None`) personal best positions of each particle of shape :code:`(n_particles, dimensions)` best_pos : numpy.ndarray (default is empty array) - best position found by the swarm of shape :code:`(dimensions, )` + best position found by the swarm of shape :code:`(dimensions, )` for the + :code:`Star`topology and :code:`(dimensions, particles)` for the other + topologies 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`) diff --git a/pyswarms/backend/topology/base.py b/pyswarms/backend/topology/base.py index 760d038e..d1b6a5a0 100644 --- a/pyswarms/backend/topology/base.py +++ b/pyswarms/backend/topology/base.py @@ -12,40 +12,43 @@ :mod:`pyswarms.backend.swarms.Swarm` module. """ -# Import from stdlib +# Import standard library +import abc import logging -# Import from package -from ...utils.console_utils import cli_print +from ...utils.reporter import Reporter -class Topology(object): +class Topology(abc.ABC): def __init__(self, static, **kwargs): """Initializes the class""" # Initialize logger - self.logger = logging.getLogger(__name__) + self.rep = Reporter(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) + self.rep.log( + "Running on `dynamic` topology," + "set `static=True` for fixed neighbors.", + lvl=logging.DEBUG, + ) + @abc.abstractmethod def compute_gbest(self, swarm): """Compute the best particle of the swarm and return the cost and position""" raise NotImplementedError("Topology::compute_gbest()") + @abc.abstractmethod def compute_position(self, swarm): """Update the swarm's position-matrix""" raise NotImplementedError("Topology::compute_position()") + @abc.abstractmethod def compute_velocity(self, swarm): """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 index 1f209502..3fa5e489 100644 --- a/pyswarms/backend/topology/pyramid.py +++ b/pyswarms/backend/topology/pyramid.py @@ -6,20 +6,17 @@ This class implements a pyramid topology. In this topology, the particles are connected by N-dimensional simplices. """ -# Import from stdlib +# Import standard library import logging # Import modules import numpy as np from scipy.spatial import Delaunay -# Import from package from .. import operators as ops +from ...utils.reporter import Reporter from .base import Topology -# Create a logger -logger = logging.getLogger(__name__) - class Pyramid(Topology): def __init__(self, static=False): @@ -32,8 +29,9 @@ def __init__(self, static=False): is static or dynamic """ super(Pyramid, self).__init__(static) + self.rep = Reporter(logger=logging.getLogger(__name__)) - def compute_gbest(self, swarm): + def compute_gbest(self, swarm, **kwargs): """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 @@ -61,36 +59,48 @@ def compute_gbest(self, swarm): 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)) + 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") + 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)] + [ + 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))] + [ + 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))] + [ + 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])] - ] + best_pos = swarm.pbest_pos[best_neighbor] except AttributeError: - msg = "Please pass a Swarm class. You passed {}".format( - type(swarm) + self.rep.logger.exception( + "Please pass a Swarm class. You passed {}".format(type(swarm)) ) - logger.error(msg) raise else: return (best_pos, best_cost) diff --git a/pyswarms/backend/topology/random.py b/pyswarms/backend/topology/random.py index 17439621..adf9a859 100644 --- a/pyswarms/backend/topology/random.py +++ b/pyswarms/backend/topology/random.py @@ -6,21 +6,18 @@ This class implements a random topology. All particles are connected in a random fashion. """ -# Import from stdlib -import logging +# Import standard library import itertools +import logging # Import modules import numpy as np from scipy.sparse.csgraph import connected_components, dijkstra -# Import from package -from ..import operators as ops +from .. import operators as ops +from ...utils.reporter import Reporter from .base import Topology -# Create a logger -logger = logging.getLogger(__name__) - class Random(Topology): def __init__(self, static=False): @@ -30,10 +27,12 @@ def __init__(self, static=False): ---------- static : bool (Default is :code:`False`) a boolean that decides whether the topology - is static or dynamic""" + is static or dynamic + """ super(Random, self).__init__(static) + self.rep = Reporter(logger=logging.getLogger(__name__)) - def compute_gbest(self, swarm, k): + def compute_gbest(self, swarm, k, **kwargs): """Update the global best using a random neighborhood approach This uses random class from :code:`numpy` to give every particle k @@ -65,23 +64,33 @@ def compute_gbest(self, swarm, k): # 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))]) + 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))] + [ + 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])] - ] + best_pos = swarm.pbest_pos[best_neighbor] except AttributeError: - msg = "Please pass a Swarm class. You passed {}".format( - type(swarm) + self.rep.logger.exception( + "Please pass a Swarm class. You passed {}".format(type(swarm)) ) - logger.error(msg) raise else: return (best_pos, best_cost) @@ -186,23 +195,43 @@ def __compute_neighbors(self, swarm, k): 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)]) + [ + 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) + 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: + 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 + 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 bef98219..d81cdde7 100644 --- a/pyswarms/backend/topology/ring.py +++ b/pyswarms/backend/topology/ring.py @@ -9,20 +9,17 @@ optimizers. """ -# Import from stdlib +# Import standard library import logging # Import modules import numpy as np from scipy.spatial import cKDTree -# Import from package from .. import operators as ops +from ...utils.reporter import Reporter from .base import Topology -# Create a logger -logger = logging.getLogger(__name__) - class Ring(Topology): def __init__(self, static=False): @@ -32,10 +29,12 @@ def __init__(self, static=False): ---------- static : bool (Default is :code:`False`) a boolean that decides whether the topology - is static or dynamic""" + is static or dynamic + """ super(Ring, self).__init__(static) + self.rep = Reporter(logger=logging.getLogger(__name__)) - def compute_gbest(self, swarm, p, k): + def compute_gbest(self, swarm, p, k, **kwargs): """Update the global best using a ring-like neighborhood approach This uses the cKDTree method from :code:`scipy` to obtain the nearest @@ -45,13 +44,13 @@ def compute_gbest(self, swarm, p, k): ---------- 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` 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. + k : int + number of neighbors to be considered. Must be a + positive integer less than :code:`n_particles` Returns ------- @@ -65,29 +64,29 @@ def compute_gbest(self, swarm, p, k): 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) + _, 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[self.neighbor_idx][:, np.newaxis].argmin( - axis=0 - ) + self.neighbor_idx = self.neighbor_idx[:, np.newaxis] + best_neighbor = np.arange(swarm.n_particles) else: idx_min = swarm.pbest_cost[self.neighbor_idx].argmin(axis=1) - best_neighbor = self.neighbor_idx[np.arange(len(self.neighbor_idx)), idx_min] + 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[ - best_neighbor[np.argmin(swarm.pbest_cost[best_neighbor])] - ] + best_pos = swarm.pbest_pos[best_neighbor] except AttributeError: - msg = "Please pass a Swarm class. You passed {}".format( - type(swarm) + self.rep.logger.exception( + "Please pass a Swarm class. You passed {}".format(type(swarm)) ) - logger.error(msg) raise else: return (best_pos, best_cost) diff --git a/pyswarms/backend/topology/star.py b/pyswarms/backend/topology/star.py index 8c666b2d..8fc7b094 100644 --- a/pyswarms/backend/topology/star.py +++ b/pyswarms/backend/topology/star.py @@ -9,30 +9,29 @@ optimizers. """ -# Import from stdlib +# Import standard library import logging # Import modules import numpy as np -# Import from package from .. import operators as ops +from ...utils.reporter import Reporter from .base import Topology -# Create a logger -logger = logging.getLogger(__name__) - class Star(Topology): - def __init__(self): + def __init__(self, static=None, **kwargs): + # static = None is just an artifact to make the API consistent + # Setting it will not change swarm behavior super(Star, self).__init__(static=True) + self.rep = Reporter(logger=logging.getLogger(__name__)) - def compute_gbest(self, swarm): + def compute_gbest(self, swarm, **kwargs): """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 - tandem with an if statement + the minimum cost and position from the matrix. .. code-block:: python @@ -43,10 +42,8 @@ def compute_gbest(self, swarm): my_swarm = P.create_swarm(n_particles, dimensions) my_topology = Star() - # If the minima of the pbest_cost is less than the best_cost - if np.min(pbest_cost) < best_cost: - # Update best_cost and position - swarm.best_pos, swarm.best_cost = my_topology.compute_best_particle(my_swarm) + # Update best_cost and position + swarm.best_pos, swarm.best_cost = my_topology.compute_gbest(my_swarm) Parameters ---------- @@ -62,14 +59,21 @@ def compute_gbest(self, swarm): """ 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) + self.neighbor_idx = np.tile( + np.arange(swarm.n_particles), (swarm.n_particles, 1) + ) + if np.min(swarm.pbest_cost) < swarm.best_cost: + # Get the particle position with the lowest pbest_cost + # and assign it to be the best_pos + best_pos = swarm.pbest_pos[np.argmin(swarm.pbest_cost)] + best_cost = np.min(swarm.pbest_cost) + else: + # Just get the previous best_pos and best_cost + best_pos, best_cost = swarm.best_pos, swarm.best_cost except AttributeError: - msg = "Please pass a Swarm class. You passed {}".format( - type(swarm) + self.rep.logger.exception( + "Please pass a Swarm class. You passed {}".format(type(swarm)) ) - logger.error(msg) raise else: return (best_pos, best_cost) diff --git a/pyswarms/backend/topology/von_neumann.py b/pyswarms/backend/topology/von_neumann.py index ad44cb85..d8765b41 100644 --- a/pyswarms/backend/topology/von_neumann.py +++ b/pyswarms/backend/topology/von_neumann.py @@ -6,20 +6,21 @@ This class implements a Von Neumann topology. """ -# Import from stdlib +# Import standard library import logging +from ...utils.reporter import Reporter from .ring import Ring -# Create a logger -logger = logging.getLogger(__name__) - class VonNeumann(Ring): - def __init__(self): + def __init__(self, static=None): + # static = None is just an artifact to make the API consistent + # Setting it will not change swarm behavior super(VonNeumann, self).__init__(static=True) + self.rep = Reporter(logger=logging.getLogger(__name__)) - def compute_gbest(self, swarm, p, r): + def compute_gbest(self, swarm, p, r, **kwargs): """Updates the global best using a neighborhood approach The Von Neumann topology inherits from the Ring topology and uses @@ -45,8 +46,8 @@ def compute_gbest(self, swarm, p, r): float Best cost """ - neighbors = VonNeumann.delannoy(swarm.dimensions, r) - return super(VonNeumann, self).compute_gbest(swarm, p, neighbors) + k = VonNeumann.delannoy(swarm.dimensions, r) + return super(VonNeumann, self).compute_gbest(swarm, p, k) @staticmethod def delannoy(d, r): @@ -71,8 +72,9 @@ def delannoy(d, r): 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) - ) + 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/base_discrete.py b/pyswarms/base/base_discrete.py index 382e972f..9a84c405 100644 --- a/pyswarms/base/base_discrete.py +++ b/pyswarms/base/base_discrete.py @@ -28,80 +28,18 @@ """ -import os -import yaml -import logging -import numpy as np -import logging.config +# Import standard library +import abc from collections import namedtuple +# Import modules +import numpy as np + # Import from package from ..backend import create_swarm -class DiscreteSwarmOptimizer(object): - def assertions(self): - """Check inputs and throw assertions - - Raises - ------ - TypeError - When the :code:`bounds` is not of type tuple - IndexError - When the :code:`bounds` is not of size 2. - When the arrays in :code:`bounds` is not of equal size. - When the shape of :code:`bounds` is not the same as `dimensions`. - ValueError - When the value of :code:`bounds[1]` is less than - :code:`bounds[0]`. - """ - - # 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") - if not len(self.velocity_clamp) == 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)" - ) - - # 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", - ): - """Setup logging configuration - - Parameters - ---------- - default_path : str (default is `./config/logging.yaml`) - the path where the logging configuration is stored - default_level: logging.LEVEL (default is `logging.INFO`) - the default logging level - env_key : str - the environment key for accessing the setup - """ - path = default_path - value = os.getenv(env_key, None) - if value: - path = value - if os.path.exists(path): - with open(path, "rt") as f: - config = yaml.safe_load(f.read()) - logging.config.dictConfig(config) - else: - logging.basicConfig(level=default_level) - +class DiscreteSwarmOptimizer(abc.ABC): def __init__( self, n_particles, @@ -146,7 +84,6 @@ def __init__( a dictionary containing the parameters for a specific optimization technique """ - self.setup_logging() # Initialize primary swarm attributes self.n_particles = n_particles self.dimensions = dimensions @@ -167,8 +104,6 @@ def __init__( "velocity", ], ) - # Invoke assertions - self.assertions() # Initialize resettable attributes self.reset() @@ -193,7 +128,8 @@ def _populate_history(self, hist): self.pos_history.append(hist.position) self.velocity_history.append(hist.velocity) - def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): + @abc.abstractmethod + def optimize(self, objective_func, iters, fast=False, **kwargs): """Optimize the swarm for a number of iterations Performs the optimization to evaluate the objective @@ -206,10 +142,8 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): objective function to be evaluated iters : int number of iterations - print_step : int (the default is 1) - amount of steps for printing into console. - verbose : int (the default is 1) - verbosity setting. + fast : bool (default is False) + if True, time.sleep is not executed kwargs : dict arguments for objective function diff --git a/pyswarms/base/base_single.py b/pyswarms/base/base_single.py index ce1cbacc..862dce8b 100644 --- a/pyswarms/base/base_single.py +++ b/pyswarms/base/base_single.py @@ -30,111 +30,17 @@ :mod:`pyswarms.single.general_optimizer`: a more general PSO implementation with a custom topology """ -import os -import yaml -import logging -import numpy as np -import logging.config +# Import standard library +import abc from collections import namedtuple -# Import from package -from ..backend import create_swarm - - -class SwarmOptimizer(object): - def assertions(self): - """Check inputs and throw assertions - - Raises - ------ - TypeError - When the :code:`bounds` is not of type tuple - IndexError - When the :code:`bounds` is not of size 2. - When the arrays in :code:`bounds` is not of equal size. - When the shape of :code:`bounds` is not the same as :code:`dimensions`. - ValueError - When the value of :code:`bounds[1]` is less than - :code:`bounds[0]`. - """ - - # Check setting of bounds - if self.bounds is not None: - if not isinstance(self.bounds, tuple): - raise TypeError("Parameter `bound` must be a tuple.") - if not len(self.bounds) == 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." - ) - if not (self.bounds[1] > self.bounds[0]).all(): - raise ValueError( - "Values of `bounds[1]` must be greater than " - "`bounds[0]`." - ) - - # Check clamp settings - if hasattr(self.velocity_clamp, "__iter__"): - if not len(self.velocity_clamp) == 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)" - ) - - # 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." - ) - if isinstance(self.center, np.ndarray) and self.center.ndim != 1: - 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") +# Import modules +import numpy as np - def setup_logging( - self, - default_path="./config/logging.yaml", - default_level=logging.INFO, - env_key="LOG_CFG", - ): - """Setup logging configuration +from ..backend import create_swarm - Parameters - ---------- - default_path : str (default is `./config/logging.yaml`) - the path where the logging configuration is stored - default_level: logging.LEVEL (default is `logging.INFO`) - the default logging level - env_key : str - the environment key for accessing the setup - """ - path = default_path - value = os.getenv(env_key, None) - if value: - path = value - if os.path.exists(path): - with open(path, "rt") as f: - config = yaml.safe_load(f.read()) - logging.config.dictConfig(config) - else: - logging.basicConfig(level=default_level) +class SwarmOptimizer(abc.ABC): def __init__( self, n_particles, @@ -178,7 +84,6 @@ def __init__( ftol : float relative error in objective_func(best_pos) acceptable for convergence """ - self.setup_logging() # Initialize primary swarm attributes self.n_particles = n_particles self.dimensions = dimensions @@ -200,8 +105,6 @@ def __init__( "velocity", ], ) - # Invoke assertions - self.assertions() # Initialize resettable attributes self.reset() @@ -225,7 +128,8 @@ def _populate_history(self, hist): self.pos_history.append(hist.position) self.velocity_history.append(hist.velocity) - def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): + @abc.abstractmethod + def optimize(self, objective_func, iters, fast=False, **kwargs): """Optimize the swarm for a number of iterations Performs the optimization to evaluate the objective @@ -238,10 +142,8 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): objective function to be evaluated iters : int number of iterations - print_step : int (the default is 1) - amount of steps for printing into console. - verbose : int (the default is 1) - verbosity setting. + fast : bool (default is False) + if True, time.sleep is not executed kwargs : dict arguments for objective function diff --git a/pyswarms/discrete/binary.py b/pyswarms/discrete/binary.py index 72d8d536..7a1b8ac9 100644 --- a/pyswarms/discrete/binary.py +++ b/pyswarms/discrete/binary.py @@ -51,45 +51,20 @@ Conference on Systems, Man, and Cybernetics, 1997. """ -# Import from stdlib +# Import standard library import logging +from time import sleep # Import modules import numpy as np -# Import from package -from ..base import DiscreteSwarmOptimizer from ..backend.operators import compute_pbest from ..backend.topology import Ring -from ..utils.console_utils import cli_print, end_report +from ..base import DiscreteSwarmOptimizer +from ..utils.reporter import Reporter class BinaryPSO(DiscreteSwarmOptimizer): - def assertions(self): - """Check inputs and throw assertions - - Raises - ------ - KeyError - When one of the required dictionary keys is missing. - ValueError - When the number of neighbors is not within the range :code:`[0, n_particles]`. - When the p-value is not in the list of values :code:`[1,2]`. - """ - 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 0 <= self.k <= self.n_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, @@ -107,10 +82,6 @@ def __init__( number of particles in the swarm. dimensions : int number of dimensions in the space. - 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. options : dict with keys :code:`{'c1', 'c2', 'k', 'p'}` a dictionary containing the parameters for the specific optimization technique @@ -127,9 +98,19 @@ def __init__( the Minkowski p-norm to use. 1 is the sum-of-absolute values (or L1 distance) while 2 is the Euclidean (or L2) distance. + init_pos : :code:`numpy.ndarray` (default is :code:`None`) + option to explicitly set the particles' initial positions. Set to + :code:`None` if you wish to generate the particles randomly. + 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. + ftol : float + relative error in objective_func(best_pos) acceptable for + convergence """ # Initialize logger - self.logger = logging.getLogger(__name__) + self.rep = Reporter(logger=logging.getLogger(__name__)) # Assign k-neighbors and p-value as attributes self.k, self.p = options["k"], options["p"] # Initialize parent class @@ -142,14 +123,13 @@ def __init__( velocity_clamp=velocity_clamp, ftol=ftol, ) - # Invoke assertions - self.assertions() # Initialize the resettable attributes self.reset() # Initialize the topology self.top = Ring(static=False) + self.name = __name__ - def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): + def optimize(self, objective_func, iters, fast=False, **kwargs): """Optimize the swarm for a number of iterations Performs the optimization to evaluate the objective @@ -161,10 +141,8 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): objective function to be evaluated iters : int number of iterations - print_step : int (the default is 1) - amount of steps for printing into console. - verbose : int (the default is 1) - verbosity setting. + fast : bool (default is False) + if True, time.sleep is not executed kwargs : dict arguments for objective function @@ -174,29 +152,32 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): 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) + self.rep.log("Obj. func. args: {}".format(kwargs), lvl=logging.DEBUG) + self.rep.log( + "Optimize for {} iters with {}".format(iters, self.options), + lvl=logging.INFO, + ) - for i in range(iters): + for i in self.rep.pbar(iters, self.name): + if not fast: + sleep(0.01) # 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.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, p=self.p, k=self.k ) # Print to console - if i % print_step == 0: - cli_print( - "Iteration {}/{}, cost: {}".format(i + 1, iters, np.min(self.swarm.best_cost)), - verbose, - 2, - logger=self.logger, - ) + self.rep.hook(best_cost=self.swarm.best_cost) # Save to history hist = self.ToHistory( best_cost=self.swarm.best_cost, @@ -221,8 +202,11 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): # 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 + self.rep.log( + "Optimization finished | best cost: {}, best pos: {}".format( + final_best_cost, final_best_pos + ), + lvl=logging.INFO, ) return (final_best_cost, final_best_pos) diff --git a/pyswarms/single/general_optimizer.py b/pyswarms/single/general_optimizer.py index 33114327..809e28d7 100644 --- a/pyswarms/single/general_optimizer.py +++ b/pyswarms/single/general_optimizer.py @@ -46,7 +46,7 @@ options=options, topology=my_topology) # Perform optimization - stats = optimizer.optimize(fx.sphere_func, iters=100) + stats = optimizer.optimize(fx.sphere, iters=100) This algorithm was adapted from the earlier works of J. Kennedy and R.C. Eberhart in Particle Swarm Optimization [IJCNN1995]_. @@ -55,17 +55,18 @@ Proceedings of the IEEE International Joint Conference on Neural Networks, 1995, pp. 1942-1948. """ -# Import from stdlib + +# Import standard library import logging +from time import sleep # 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 +from ..backend.topology import Topology +from ..base import SwarmOptimizer +from ..utils.reporter import Reporter class GeneralOptimizerPSO(SwarmOptimizer): @@ -89,7 +90,8 @@ def __init__( 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'}` + 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 @@ -98,27 +100,26 @@ def __init__( 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 + 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` + 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. + 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. + 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: + 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) @@ -129,22 +130,25 @@ def __init__( 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. + 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,)`. + 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. + 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 + init_pos : :code:`numpy.ndarray` (default is :code:`None`) + option to explicitly set the particles' initial positions. Set to + :code:`None` if you wish to generate the particles randomly. """ super(GeneralOptimizerPSO, self).__init__( n_particles, @@ -158,9 +162,7 @@ def __init__( ) # Initialize logger - self.logger = logging.getLogger(__name__) - # Invoke assertions - self.assertions() + self.rep = Reporter(logger=logging.getLogger(__name__)) # Initialize the resettable attributes self.reset() # Initialize the topology and check for type @@ -168,54 +170,9 @@ def __init__( raise TypeError("Parameter `topology` must be a Topology object") else: self.top = topology + self.name = __name__ - # 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): + def optimize(self, objective_func, iters, fast=False, **kwargs): """Optimize the swarm for a number of iterations Performs the optimization to evaluate the objective @@ -227,10 +184,8 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): 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. + fast : bool (default is False) + if True, time.sleep is not executed kwargs : dict arguments for the objective function @@ -239,58 +194,35 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): tuple the global best cost and the global best position. """ + if not fast: + sleep(0.01) - cli_print("Arguments Passed to Objective Function: {}".format(kwargs), - verbose, 2, logger=self.logger) + self.rep.log("Obj. func. args: {}".format(kwargs), lvl=logging.DEBUG) + self.rep.log( + "Optimize for {} iters with {}".format(iters, self.options), + lvl=logging.INFO, + ) - for i in range(iters): + for i in self.rep.pbar(iters, self.name): # Compute cost for current position and personal best + # fmt: off 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 - ) + 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 - ) + # fmt: on + # Update swarm + self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest( + self.swarm, **self.options + ) # 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 + self.rep.hook(best_cost=self.swarm.best_cost) 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 + velocity=self.swarm.velocity, ) self._populate_history(hist) # Verify stop criteria based on the relative acceptable cost ftol @@ -309,9 +241,12 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): ) # 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() + final_best_pos = self.swarm.position[self.swarm.pbest_cost.argmin()].copy() # Write report in log and return final cost and position - end_report( - final_best_cost, final_best_pos, verbose, logger=self.logger + self.rep.log( + "Optimization finished | best cost: {}, best pos: {}".format( + final_best_cost, final_best_pos + ), + lvl=logging.INFO, ) - return(final_best_cost, final_best_pos) + return (final_best_cost, final_best_pos) diff --git a/pyswarms/single/global_best.py b/pyswarms/single/global_best.py index 28f06439..94e579b1 100644 --- a/pyswarms/single/global_best.py +++ b/pyswarms/single/global_best.py @@ -45,7 +45,7 @@ options=options) # Perform optimization - stats = optimizer.optimize(fx.sphere_func, iters=100) + stats = optimizer.optimize(fx.sphere, iters=100) This algorithm was adapted from the earlier works of J. Kennedy and R.C. Eberhart in Particle Swarm Optimization [IJCNN1995]_. @@ -55,17 +55,17 @@ Networks, 1995, pp. 1942-1948. """ -# Import from stdlib +# Import standard library import logging +from time import sleep # Import modules import numpy as np -# Import from package -from ..base import SwarmOptimizer from ..backend.operators import compute_pbest from ..backend.topology import Star -from ..utils.console_utils import cli_print, end_report +from ..base import SwarmOptimizer +from ..utils.reporter import Reporter class GlobalBestPSO(SwarmOptimizer): @@ -110,6 +110,9 @@ def __init__( ftol : float relative error in objective_func(best_pos) acceptable for convergence + init_pos : :code:`numpy.ndarray` (default is :code:`None`) + option to explicitly set the particles' initial positions. Set to + :code:`None` if you wish to generate the particles randomly. """ super(GlobalBestPSO, self).__init__( n_particles=n_particles, @@ -123,15 +126,14 @@ def __init__( ) # Initialize logger - self.logger = logging.getLogger(__name__) - # Invoke assertions - self.assertions() + self.rep = Reporter(logger=logging.getLogger(__name__)) # Initialize the resettable attributes self.reset() # Initialize the topology self.top = Star() + self.name = __name__ - def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): + def optimize(self, objective_func, iters, fast=False, **kwargs): """Optimize the swarm for a number of iterations Performs the optimization to evaluate the objective @@ -143,10 +145,8 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): 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. + fast : bool (default is False) + if True, time.sleep is not executed kwargs : dict arguments for the objective function @@ -156,30 +156,25 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): the global best cost and the global best position. """ - cli_print("Arguments Passed to Objective Function: {}".format(kwargs), - verbose, 2, logger=self.logger) + self.rep.log("Obj. func. args: {}".format(kwargs), lvl=logging.DEBUG) + self.rep.log( + "Optimize for {} iters with {}".format(iters, self.options), + lvl=logging.INFO, + ) - for i in range(iters): + for i in self.rep.pbar(iters, self.name): + if not fast: + sleep(0.01) # Compute cost for current position and personal best + # fmt: off 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 - ) + self.swarm.pbest_pos, self.swarm.pbest_cost = compute_pbest(self.swarm) + # Set best_cost_yet_found for ftol 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 - ) - # 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, - ) + self.swarm.best_pos, self.swarm.best_cost = self.top.compute_gbest(self.swarm) + # fmt: on + self.rep.hook(best_cost=self.swarm.best_cost) # Save to history hist = self.ToHistory( best_cost=self.swarm.best_cost, @@ -207,7 +202,10 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): 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 + self.rep.log( + "Optimization finished | best cost: {}, best pos: {}".format( + final_best_cost, final_best_pos + ), + lvl=logging.INFO, ) return (final_best_cost, final_best_pos) diff --git a/pyswarms/single/local_best.py b/pyswarms/single/local_best.py index aada1609..28c3feb4 100644 --- a/pyswarms/single/local_best.py +++ b/pyswarms/single/local_best.py @@ -49,7 +49,7 @@ options=options) # Perform optimization - stats = optimizer.optimize(fx.sphere_func, iters=100) + stats = optimizer.optimize(fx.sphere, iters=100) This algorithm was adapted from one of the earlier works of J. Kennedy and R.C. Eberhart in Particle Swarm Optimization @@ -64,45 +64,20 @@ Symposium on Micromachine and Human Science, 1995, pp. 39–43. """ -# Import from stdlib +# Import standard library import logging +from time import sleep # Import modules import numpy as np -# Import from package -from ..base import SwarmOptimizer from ..backend.operators import compute_pbest from ..backend.topology import Ring -from ..utils.console_utils import cli_print, end_report +from ..base import SwarmOptimizer +from ..utils.reporter import Reporter class LocalBestPSO(SwarmOptimizer): - def assertions(self): - """Check inputs and throw assertions - - Raises - ------ - KeyError - When one of the required dictionary keys is missing. - ValueError - When the number of neighbors is not within the range :code:`[0, n_particles]`. - When the p-value is not in the list of values :code:`[1,2]`. - """ - 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 0 <= self.k <= self.n_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, @@ -113,7 +88,7 @@ def __init__( center=1.00, ftol=-np.inf, init_pos=None, - static=False + static=False, ): """Initialize the swarm @@ -152,6 +127,9 @@ def __init__( the Minkowski p-norm to use. 1 is the sum-of-absolute values (or L1 distance) while 2 is the Euclidean (or L2) distance. + init_pos : :code:`numpy.ndarray` (default is :code:`None`) + option to explicitly set the particles' initial positions. Set to + :code:`None` if you wish to generate the particles randomly. static: bool (Default is :code:`False`) a boolean that decides whether the Ring topology used is static or dynamic @@ -171,14 +149,15 @@ def __init__( ftol=ftol, init_pos=init_pos, ) - # Invoke assertions - self.assertions() + # Initialize logger + self.rep = Reporter(logger=logging.getLogger(__name__)) # Initialize the resettable attributes self.reset() # Initialize the topology self.top = Ring(static=static) + self.name = __name__ - def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): + def optimize(self, objective_func, iters, fast=False, **kwargs): """Optimize the swarm for a number of iterations Performs the optimization to evaluate the objective @@ -190,10 +169,8 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): 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. + fast : bool (default is False) + if True, time.sleep is not executed kwargs : dict arguments for the objective function @@ -203,29 +180,31 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): 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) + self.rep.log("Obj. func. args: {}".format(kwargs), lvl=logging.DEBUG) + self.rep.log( + "Optimize for {} iters with {}".format(iters, self.options), + lvl=logging.INFO, + ) - for i in range(iters): + for i in self.rep.pbar(iters, self.name): + if not fast: + sleep(0.01) # 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.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, p=self.p, k=self.k ) - # Print to console - if i % print_step == 0: - cli_print( - "Iteration {}/{}, cost: {}".format(i + 1, iters, np.min(self.swarm.best_cost)), - verbose, - 2, - logger=self.logger, - ) + self.rep.hook(best_cost=np.min(self.swarm.best_cost)) # Save to history hist = self.ToHistory( best_cost=self.swarm.best_cost, @@ -251,8 +230,12 @@ def optimize(self, objective_func, iters, print_step=1, verbose=1, **kwargs): ) # 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 + final_best_pos = self.swarm.position[self.swarm.pbest_cost.argmin(axis=0)].copy() + # Write report in log and return final cost and position + self.rep.log( + "Optimization finished | best cost: {}, best pos: {}".format( + final_best_cost, final_best_pos + ), + lvl=logging.INFO, ) return (final_best_cost, final_best_pos) diff --git a/pyswarms/utils/__init__.py b/pyswarms/utils/__init__.py index feb2c0bd..ea80fdaf 100644 --- a/pyswarms/utils/__init__.py +++ b/pyswarms/utils/__init__.py @@ -1 +1,5 @@ """ Pacakge for various utilities """ + +from .reporter.reporter import Reporter + +__all__ = ["Reporter"] diff --git a/pyswarms/utils/console_utils.py b/pyswarms/utils/console_utils.py deleted file mode 100644 index 22ae118d..00000000 --- a/pyswarms/utils/console_utils.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- - -""" console_utils.py: various tools for printing into console """ - -# Import from __future__ -from __future__ import with_statement -from __future__ import absolute_import -from __future__ import print_function - -# Import modules - - -def cli_print(message, verbosity, threshold, logger): - """Helper function to print console output - - Parameters - ---------- - message : str - the message to be printed into the console - verbosity : int - verbosity setting of the user - threshold : int - threshold for printing - logger : logging.getLogger - logger instance - - """ - if verbosity >= threshold: - logger.info(message) - else: - pass - - -def end_report(cost, pos, verbosity, logger): - """Helper function to print a simple report at the end of the - run. This always has a threshold of 1. - - Parameters - ---------- - cost : float - final cost from the optimization procedure. - pos : numpy.ndarray or list - best position found - verbosity : int - verbosity setting of the user. - logger : logging.getLogger - logger instance - """ - - # Cuts the length of the best position if it's too long - if len(list(pos)) > 3: - 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) - if verbosity >= 1: - logger.info(template) diff --git a/pyswarms/utils/decorators/__init__.py b/pyswarms/utils/decorators/__init__.py new file mode 100644 index 00000000..4ef5c8b3 --- /dev/null +++ b/pyswarms/utils/decorators/__init__.py @@ -0,0 +1,9 @@ +""" +The :mod:`pyswarms.decorators` module implements a decorator that +can be used to simplify the task of writing the cost function for +an optimization run. The decorator can be directly called by using +:code:`@pyswarms.cost`. +""" +from .decorators import cost + +__all__ = ["cost"] diff --git a/pyswarms/utils/decorators/decorators.py b/pyswarms/utils/decorators/decorators.py new file mode 100644 index 00000000..07d7139b --- /dev/null +++ b/pyswarms/utils/decorators/decorators.py @@ -0,0 +1,53 @@ +# Import modules +import numpy as np + + +def cost(cost_func): + """A decorator for the cost function + + This decorator allows the creation of much simpler cost functions. Instead + of writing a cost function that returns a shape of :code:`(n_particles, 0)` + it enables the usage of shorter and simpler cost functions that directly + return the cost. A simple example might be: + + .. code-block:: python + import pyswarms + import numpy as np + + @pyswarms.cost + def cost_func(x): + cost = np.abs(np.sum(x)) + return cost + + The decorator expects your cost function to use a d-dimensional array + (where d is the number of dimensions for the optimization) as and argument. + + .. note:: + Some :code:`numpy` functions return a :code:`np.ndarray` with single + values in it. Be aware of the fact that without unpacking the value + the optimizer will raise an exception. + + Parameters + ---------- + cost_func : callable + A callable object that can be used as cost function in the optimization + (must return a :code:`float` or an :code:`int`). + + Returns + ------- + callable + The vectorized output for all particles as defined by :code:`cost_func` + """ + + def cost_dec(particles, **kwargs): + n_particles = particles.shape[0] + vector = np.array( + [cost_func(particles[i], **kwargs) for i in range(n_particles)] + ) + if vector.shape != (n_particles,): + msg = "Cost function must return int or float. You passed: {}" + cost_return_type = type(cost_func(particles[0], **kwargs)) + raise ValueError(msg.format(cost_return_type)) + return vector + + return cost_dec diff --git a/pyswarms/utils/environments/__init__.py b/pyswarms/utils/environments/__init__.py deleted file mode 100644 index 2207de52..00000000 --- a/pyswarms/utils/environments/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -The mod:`pyswarms.utils.environments` module implements various -optimization environments to analyze optimizer performance or search -better parameters -""" - -from .plot_environment import PlotEnvironment - -__all__ = ["PlotEnvironment"] diff --git a/pyswarms/utils/environments/plot_environment.py b/pyswarms/utils/environments/plot_environment.py deleted file mode 100644 index 6e9321d3..00000000 --- a/pyswarms/utils/environments/plot_environment.py +++ /dev/null @@ -1,472 +0,0 @@ -# -*- coding: utf-8 -*- - -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 -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 using PlotEnvironment, simply -pass the optimizer object when initializing the class, and the -PlotEnvironment will do a fresh run of your optimizer. After that, -various plotting methods can now be done: - -.. code-block:: python - - import pyswarms as ps - from pyswarms.utils.functions.single_obj import sphere_func - from pyswarms.utils.environments import PlotEnvironment - - # Set up optimizer - options = {'c1':0.5, 'c2':0.3, 'w':0.9} - optimizer = ps.single.GlobalBestPSO(n_particles=10, dimensions=2, - options=options) - - # Pass optimizer inside the environment. You also need to pass some - # of the required arguments on how your optimizer will be evaluated. - plt_env = PlotEnvironment(optimizer, sphere_func, 1000) - - # To plot the cost - plt_env.plot_cost() - 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 call -the following function: - - >>> plt_env.plot_particles2d() - -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 from __future__ -from __future__ import with_statement -from __future__ import absolute_import -from __future__ import print_function - -# Import modules -import logging -import warnings -import numpy as np -import matplotlib.pyplot as plt -from past.builtins import xrange -from matplotlib import animation -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): - def assertions(self): - """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") - - # 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" - ) - - # 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" - ) - - def __init__(self, optimizer, objective_func, iters): - """Run the optimizer against an objective function for a number - of iterations - - Upon initialization, the :code:`optimize` method of the optimizer - will be called, passing the arguments :code:`objective_func` and - :code:`iters`. The results of the optimization scheme is then - stored as attributes of this class. - - Parameters - ---------- - optimizer : object instance - An instance of an optimizer class that was derived from any - of the :mod:`pyswarms.base` classes. - objective_func : method - An objective function to be optimized using the :code:`optimizer`. - This argument is passed to the :code:`optimize` method of the - :code:`optimizer`. - iters : int - The number of iterations to run the optimizer. This argument - is passed to the :code:`optimize` method of the :code:`optimizer`. - """ - self.logger = logging.getLogger(__name__) - # Store the arguments - self.optimizer = optimizer - self.objective_func = objective_func - self.iters = iters - # Check assertions - self.assertions() - # Run the optimizer - 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 - ): - """Create a simple line plot with the cost in the y-axis and - the iteration at the x-axis - - Parameters - ---------- - title : str (default is :code:`'Cost History'`) - The title of the plotted graph. - 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. - figsize : tuple (default is None) - Sets the size of the plot figure. - title_fontsize : str or int (default is :code:`large`) - This is a :class:`matplotlib.axes.Axes` argument that - specifies the size of the title. Available values are - ['small', 'medium', 'large'] or integer values. - text_fontsize : str or int (default is :code:`large`) - This is a :class:`matplotlib.axes.Axes` argument that - specifies the size of various texts around the plot. - Available values are ['small', 'medium', 'large'] or integer - values. - **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. - """ - # Get cost history from the optimizer method - cost_history = self.optimizer.get_cost_history - mean_pbest_history = self.optimizer.get_mean_pbest_history - mean_neighbor_history = self.optimizer.get_mean_neighbor_history - - # If ax is default, then create new plot - if ax is None: - fig, ax = plt.subplots(1, 1, figsize=figsize) - - # 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", - ) - - # 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.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", - ): - """Create an animation of particle movement in 2D-space - - Parameters - ---------- - index : n-tuple (default is :code:`(0,1)`) - The index in which a specific dimension will be plotted. For - example, :code:`(idx_1, idx_2)` for two dimensions. - limits : n-tuple of 2-tuples (default is :code:`((-1,1),(-1,1))`) - The limits of the x-y axes for 2D. For example, - :code:`((xmin, xmax),(ymin, ymax))` - labels : 2-tuple (default is :code:`('x-axis', 'y-axis')` - Sets the x and y labels of the 2D plot. For example, - :code:`('label_x_axis', 'label_y_axis')` - interval : int (default is 80) - The speed of update, in milliseconds - title : str (default is :code:`'Particle Movement in 2D space'`) - The title of the plotted graph. - 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. - figsize : tuple (default is None) - Sets the size of the plot figure. - title_fontsize : str or int (default is :code:`large`) - This is a :class:`matplotlib.axes.Axes` argument that - specifies the size of the title. Available values are - ['small', 'medium', 'large'] or integer values. - text_fontsize : str or int (default is :code:`large`) - This is a :class:`matplotlib.axes.Axes` argument that - specifies the size of various texts around the plot. - Available values are ['small', 'medium', 'large'] or integer - values. - **kwargs : dict - Keyword arguments that are passed as a keyword argument to - :class:`matplotlib.axes.Axes` - - Returns - ------- - :class:`matplotlib.animation.FuncAnimation` - The drawn animation that can be saved to mp4 or other - third-party tools - """ - # Check inconsistencies with input - if not (len(index) == len(limits) == 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) - lmt = self.Limit(x=limits[0], y=limits[1], z=None) - lbl = self.Label(x=labels[0], y=labels[1], z=None) - - # If ax is default, then create new plot. Set-up the figure, the - # acis, and the plot element that we want to animate - if ax is None: - fig, ax = plt.subplots(1, 1, figsize=figsize) - - # Set plot title - ax.set_title(title, fontsize=title_fontsize) - - # Set plot labels - ax.set_xlabel(lbl.x, fontsize=text_fontsize) - ax.set_ylabel(lbl.y, fontsize=text_fontsize) - - # Set plot limits - ax.set_xlim(lmt.x) - ax.set_ylim(lmt.y) - - # Plot data - 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, - ) - 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", - ): - """Create an animation of particle movement in 3D-space - - Parameters - ---------- - index : n-tuple (default is :code:`(0,1,2)`) - The index in which a specific dimension will be plotted. For - example, :code:`(idx_1, idx_2, idx_3)` for three dimensions. - limits : n-tuple of 2-tuples (default is - :code:`((-1,1),(-1,1),(-1,1))`) - The limits of the x-y axes for 3D. For example, - :code:`((xmin, xmax),(ymin, ymax))` - labels : 2-tuple (default is :code:`('x-axis', 'y-axis', 'z-axis')` - Sets the x and y labels of the 2D plot. For example, - :code:`('label_x_axis', 'label_y_axis', 'label_z_axis')` - interval : int (default is 80) - The speed of update, in milliseconds - title : str (default is :code:`'Particle Movement in 3D space'`) - The title of the plotted graph. - 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. - figsize : tuple (default is None) - Sets the size of the plot figure. - title_fontsize : str or int (default is :code:`large`) - This is a :class:`matplotlib.axes.Axes` argument that - specifies the size of the title. Available values are - ['small', 'medium', 'large'] or integer values. - text_fontsize : str or int (default is :code:`large`) - This is a :class:`matplotlib.axes.Axes` argument that - specifies the size of various texts around the plot. - Available values are ['small', 'medium', 'large'] or integer - values. - **kwargs : dict - Keyword arguments that are passed as a keyword argument to - :class:`matplotlib.axes.Axes` - - Returns - ------- - :class:`matplotlib.animation.FuncAnimation` - The drawn animation that can be saved to mp4 or other - third-party tools - """ - # Check inconsistencies with input - if not (len(index) == len(limits) == 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]) - lmt = self.Limit(x=limits[0], y=limits[1], z=limits[2]) - lbl = self.Label(x=labels[0], y=labels[1], z=labels[2]) - - # If ax is default, then create new plot. Set-up the figure, the - # acis, and the plot element that we want to animate - if ax is None: - fig, ax = plt.subplots(1, 1, figsize=figsize) - ax = Axes3D(fig) - - # Set plot title - ax.set_title(title, fontsize=title_fontsize) - - # Set plot axes labels - ax.set_xlabel(lbl.x, fontsize=text_fontsize) - ax.set_ylabel(lbl.y, fontsize=text_fontsize) - ax.set_zlabel(lbl.z, fontsize=text_fontsize) - - # Set plot limits - ax.set_xlim(lmt.x) - ax.set_ylim(lmt.y) - ax.set_zlim(lmt.z) - - # Plot data - 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, - ) - return anim - - def _animate2D(self, i, data, plot, idx): - """Helper animation function that is called sequentially - :class:`matplotlib.animation.FuncAnimation` - - Parameters - ---------- - i : int - Required argument for :code:`matplotlib.animation.FuncAnimation`, - basis for indexing the current position of the swarm. - data : numpy.ndarray - The position matrix where the particles' position - will be taken from. - plot : matplotlib.Axes - The plot environment where the update operations will be drawn - idx : namedtuple - The chosen indices for plotting the dimensions - - Returns - ------- - :class:`matplotlib.artist.Artist` - iterable of artists - """ - current_pos = data[i] - xy = current_pos[:, (idx.x, idx.y)] - plot.set_offsets(xy) - return (plot,) - - def _animate3D(self, i, data, plot, idx): - """Helper animation function that is called sequentially - :class:`matplotlib.animation.FuncAnimation` - - Parameters - ---------- - i : int - Required argument for :code:`matplotlib.animation.FuncAnimation`, - basis for indexing the current position of the swarm. - data : numpy.ndarray - The position matrix where the particles' position - will be taken from. - plot : matplotlib.Axes - The plot environment where the update operations will be drawn - idx : namedtuple - The chosen indices for plotting the dimensions - - Returns - ------- - :class:`matplotlib.artist.Artist` - iterable of artists - """ - current_pos = data[i] - x, y, z = ( - current_pos[:, idx.x], - current_pos[:, idx.y], - current_pos[:, idx.z], - ) - plot._offsets3d = (x, y, z) - return (plot,) diff --git a/pyswarms/utils/functions/single_obj.py b/pyswarms/utils/functions/single_obj.py index a4dce495..aa34a2ca 100644 --- a/pyswarms/utils/functions/single_obj.py +++ b/pyswarms/utils/functions/single_obj.py @@ -17,35 +17,30 @@ 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 +- Ackley's, ackley +- Beale, beale +- Booth, booth +- Bukin's No 6, bukin6 +- Cross-in-Tray, crossintray +- Easom, easom +- Eggholder, eggholder +- Goldstein, goldstein +- Himmelblau's, himmelblau +- Holder Table, holdertable +- Levi, levi +- Matyas, matyas +- Rastrigin, rastrigin +- Rosenbrock, rosenbrock +- Schaffer No 2, schaffer2 +- Sphere, sphere +- Three Hump Camel, threehump """ -# Import from __future__ -from __future__ import with_statement -from __future__ import absolute_import -from __future__ import print_function - # Import modules import numpy as np -def ackley_func(x): +def ackley(x): """Ackley's objective function. Has a global minimum of `0` at :code:`f(0,0,...,0)` with a search @@ -81,7 +76,7 @@ def ackley_func(x): return j -def beale_func(x): +def beale(x): """Beale objective function. Only takes two dimensions and has a global minimum of `0` at @@ -124,7 +119,7 @@ def beale_func(x): return j -def booth_func(x): +def booth(x): """Booth's objective function. Only takes two dimensions and has a global minimum of `0` at @@ -161,7 +156,7 @@ def booth_func(x): return j -def bukin6_func(x): +def bukin6(x): """Bukin N. 6 Objective Function Only takes two dimensions and has a global minimum of `0` at @@ -210,7 +205,7 @@ def bukin6_func(x): return j -def crossintray_func(x): +def crossintray(x): """Cross-in-tray objective function. Only takes two dimensions and has a four equal global minimums @@ -264,7 +259,7 @@ def crossintray_func(x): return j -def easom_func(x): +def easom(x): """Easom objective function. Only takes two dimensions and has a global minimum of @@ -312,7 +307,7 @@ def easom_func(x): return j -def eggholder_func(x): +def eggholder(x): """Eggholder objective function. Only takes two dimensions and has a global minimum of @@ -359,7 +354,7 @@ def eggholder_func(x): return j -def goldstein_func(x): +def goldstein(x): """Goldstein-Price's objective function. Only takes two dimensions and has a global minimum at @@ -424,7 +419,7 @@ def goldstein_func(x): return j -def himmelblau_func(x): +def himmelblau(x): """Himmelblau's objective function Only takes two dimensions and has a four equal global minimums @@ -470,7 +465,7 @@ def himmelblau_func(x): return j -def holdertable_func(x): +def holdertable(x): """Holder Table objective function Only takes two dimensions and has a four equal global minimums @@ -520,7 +515,7 @@ def holdertable_func(x): return j -def levi_func(x): +def levi(x): """Levi objective function Only takes two dimensions and has a global minimum at @@ -569,7 +564,7 @@ def levi_func(x): return j -def matyas_func(x): +def matyas(x): """Matyas objective function Only takes two dimensions and has a global minimum at @@ -599,7 +594,7 @@ def matyas_func(x): return j -def rastrigin_func(x): +def rastrigin(x): """Rastrigin objective function. Has a global minimum at :code:`f(0,0,...,0)` with a search @@ -632,7 +627,7 @@ def rastrigin_func(x): return j -def rosenbrock_func(x): +def rosenbrock(x): """Rosenbrock objective function. Also known as the Rosenbrock's valley or Rosenbrock's banana @@ -658,7 +653,7 @@ def rosenbrock_func(x): return r -def schaffer2_func(x): +def schaffer2(x): """Schaffer N.2 objective function Only takes two dimensions and has a global minimum at @@ -703,7 +698,7 @@ def schaffer2_func(x): return j -def sphere_func(x): +def sphere(x): """Sphere objective function. Has a global minimum at :code:`0` and with a search domain of @@ -724,7 +719,7 @@ def sphere_func(x): return j -def threehump_func(x): +def threehump(x): """Three-hump camel objective function Only takes two dimensions and has a global minimum of `0` at diff --git a/pyswarms/utils/plotters/formatters.py b/pyswarms/utils/plotters/formatters.py index b986ecca..1ca6931f 100644 --- a/pyswarms/utils/plotters/formatters.py +++ b/pyswarms/utils/plotters/formatters.py @@ -8,7 +8,7 @@ # Import modules import numpy as np -from attr import attrs, attrib +from attr import attrib, attrs from attr.validators import instance_of from matplotlib import cm, colors @@ -43,9 +43,14 @@ class Designer(object): 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 : str, list, or tuple (default is :code:`['x-axis', 'y-axis', 'z-axis']`) Label to show in the x, y, or z-axis. For a 3D plot, please pass an iterable with three elements. + limits : list (default is :code:`[(-1, 1), (-1, 1), (-1, 1)]`) + The x-, y-, z- limits of the axes. Pass an iterable with the number of elements + representing the number of axes. + colormap : matplotlib.cm.Colormap (default is :code:`cm.viridis`) + Colormap for contour plots """ # Overall plot design @@ -62,11 +67,11 @@ class Designer(object): default=["x-axis", "y-axis", "z-axis"], ) limits = attrib( - validator=instance_of((list, tuple)), default=[(-1, 1), (-1, 1), (-1, 1)] + validator=instance_of((list, tuple)), + default=[(-1, 1), (-1, 1), (-1, 1)], ) colormap = attrib( - validator=instance_of(colors.Colormap), - default=cm.viridis, + validator=instance_of(colors.Colormap), default=cm.viridis ) @@ -119,7 +124,7 @@ class Mesher(object): from pyswarms.utils.functions import single_obj as fx # Use sphere function - my_mesher = Mesher(func=fx.sphere_func) + my_mesher = Mesher(func=fx.sphere) # Assuming we already had an optimizer ready plot_surface(pos_history, mesher=my_mesher) @@ -132,10 +137,14 @@ class Mesher(object): 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. + levels : list or int (default is :code:`np.arange(-2.0, 2.0, 0.070)`) + Levels on which the contours are shown. If :code:`int` is passed, + then `matplotlib` automatically computes for the level positions. alpha : float (default is :code:`0.3`) Transparency of the surface plot + limits : list (default is :code:`[(-1, 1), (-1, 1)]`) + The x-, y-, z- limits of the axes. Pass an iterable with the number of elements + representing the number of axes. """ func = attrib() diff --git a/pyswarms/utils/plotters/plotters.py b/pyswarms/utils/plotters/plotters.py index 28516a65..98c3e9f1 100644 --- a/pyswarms/utils/plotters/plotters.py +++ b/pyswarms/utils/plotters/plotters.py @@ -16,7 +16,7 @@ .. code-block:: python import pyswarms as ps - from pyswarms.utils.functions.single_obj import sphere_func + from pyswarms.utils.functions.single_obj import sphere from pyswarms.utils.plotters import plot_cost_history # Set up optimizer @@ -46,7 +46,7 @@ .. code-block:: python import pyswarms as ps - from pyswarms.utils.functions.single_obj import sphere_func + from pyswarms.utils.functions.single_obj import sphere from pyswarms.utils.plotters import plot_cost_history # Set up optimizer @@ -58,26 +58,26 @@ pos_history = optimizer.pos_history # Plot! - plot_trajectory2D(pos_history) + plot_contour(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 standard library import logging +# Import modules 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 +from ..reporter import Reporter +from .formatters import Animator, Designer -# Initialize logger -logger = logging.getLogger(__name__) +rep = Reporter(logger=logging.getLogger(__name__)) def plot_cost_history( @@ -132,6 +132,7 @@ def plot_cost_history( ax.set_ylabel(designer.label[1], fontsize=designer.text_fontsize) ax.tick_params(labelsize=designer.text_fontsize) except TypeError: + rep.logger.exception("Please check your input type") raise else: return ax @@ -234,6 +235,7 @@ def plot_contour( repeat_delay=animator.repeat_delay, ) except TypeError: + rep.logger.exception("Please check your input type") raise else: return anim @@ -267,7 +269,7 @@ def plot_surface( .. code-block:: python import pyswarms as ps - from pyswarms.utils.functions.single_obj import sphere_func + from pyswarms.utils.functions.single_obj import sphere from pyswarms.utils.plotters import plot_surface from pyswarms.utils.plotters.formatters import Mesher @@ -276,7 +278,7 @@ def plot_surface( optimizer = ps.single.GlobalBestPSO(n_particles=10, dimensions=2, options) # Prepare position history - m = Mesher(func=sphere_func) + m = Mesher(func=sphere) pos_history_3d = m.compute_history_3d(optimizer.pos_history) # Plot! @@ -376,6 +378,7 @@ def plot_surface( repeat_delay=animator.repeat_delay, ) except TypeError: + rep.logger.exception("Please check your input type") raise else: return anim diff --git a/pyswarms/utils/reporter/__init__.py b/pyswarms/utils/reporter/__init__.py new file mode 100644 index 00000000..15390076 --- /dev/null +++ b/pyswarms/utils/reporter/__init__.py @@ -0,0 +1,5 @@ +"""Reporter module""" + +from .reporter import Reporter + +__all__ = ["Reporter"] diff --git a/pyswarms/utils/reporter/reporter.py b/pyswarms/utils/reporter/reporter.py new file mode 100644 index 00000000..8e02894d --- /dev/null +++ b/pyswarms/utils/reporter/reporter.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- +# Import standard library +import logging +import logging.config +import os +import pprint + +# Import modules +import yaml +from tqdm import trange + + +class Reporter(object): + """A Reporter object that abstracts various logging capabilities + + To set-up a Reporter, simply perform the following tasks: + + .. code-block:: python + + from pyswarms.utils import Reporter + + rep = Reporter() + rep.log("Here's my message", lvl=logging.INFO) + + This will set-up a reporter with a default configuration that + logs to a file, `report.log`, on the current working directory. + You can change the log path by passing a string to the `log_path` + parameter: + + .. code-block:: python + + from pyswarms.utils import Reporter + + rep = Reporter(log_path="/path/to/log/file.log") + rep.log("Here's my message", lvl=logging.INFO) + + If you are working on a module and you have an existing logger, + you can pass that logger instance during initialization: + + .. code-block:: python + + # mymodule.py + from pyswarms.utils import Reporter + + # An existing logger in a module + logger = logging.getLogger(__name__) + rep = Reporter(logger=logger) + + Lastly, if you have your own logger configuration (YAML file), + then simply pass that to the `config_path` parameter. This + overrides the default configuration (including `log_path`): + + .. code-block:: python + + from pyswarms.utils import Reporter + + rep = Reporter(config_path="/path/to/config/file.yml") + rep.log("Here's my message", lvl=logging.INFO) + + """ + + def __init__( + self, log_path=None, config_path=None, logger=None, printer=None + ): + """Initialize the reporter + + Attributes + ---------- + log_path : str (default is :code:`None`) + Sets the default log path (overriden when :code:`path` is given to + :code:`_setup_logger()`) + config_path : str (default is :code:`None`) + Sets the configuration path for custom loggers + logger : logging.Logger (default is :code:`None`) + The logger object. By default, it creates a new :code:`Logger` + instance + printer : pprint.PrettyPrinter (default is :code:`None`) + A printer object. By default, it creates a :code:`PrettyPrinter` + instance with default values + """ + self.logger = logger or logging.getLogger(__name__) + self.printer = printer or pprint.PrettyPrinter() + self.log_path = log_path or (os.getcwd() + "/report.log") + self._bar_fmt = "{l_bar}{bar}|{n_fmt}/{total_fmt}{postfix}" + self._env_key = "LOG_CFG" + self._default_config = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "standard": { + "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + } + }, + "handlers": { + "default": { + "level": "INFO", + "class": "logging.StreamHandler", + "formatter": "standard", + }, + "file_default": { + "level": "INFO", + "formatter": "standard", + "class": "logging.handlers.RotatingFileHandler", + "filename": self.log_path, + "encoding": "utf8", + "maxBytes": 10485760, + "backupCount": 20, + }, + }, + "loggers": { + "": { + "handlers": ["default", "file_default"], + "level": "INFO", + "propagate": True, + } + }, + } + self._setup_logger(config_path) + + def log(self, msg, lvl=logging.INFO, *args, **kwargs): + """Log a message within a set level + + This method abstracts the logging.Logger.log() method. We use this + method during major state changes, errors, or critical events during + the optimization run. + + You can check logging levels on this `link`_. In essence, DEBUG is 10, + INFO is 20, WARNING is 30, ERROR is 40, and CRITICAL is 50. + + .. link: https://docs.python.org/3/library/logging.html#logging-levels + + Parameters + ---------- + msg : str + Message to be logged + lvl : int (default is 20) + Logging level + """ + self.logger.log(lvl, msg, *args, **kwargs) + + def print(self, msg, verbosity, threshold=0): + """Print a message into console + + This method can be called during non-system calls or minor state + changes. In practice, we call this method when reporting the cost + on a given timestep. + + Parameters + ---------- + msg : str + Message to be printed + verbosity : int + Verbosity parameter, prints message when it's greater than the + threshold + threshold : int (default is 0) + Threshold parameer, prints message when it's lesser than the + verbosity + """ + if verbosity > threshold: + self.printer.pprint(msg) + else: + pass + + def _setup_logger(self, path=None): + """Set-up the logger with default values + + This method is called right after initializing the Reporter module. + If no path is supplied, then it loads a default configuration. + You can view the defaults via the Reporter._default_config attribute. + + + Parameters + ---------- + path : str + Path to a YAML configuration. If not supplied, uses + a default config. + """ + value = path or os.getenv(self._env_key, None) + try: + with open(value, "rt") as f: + config = yaml.safe_load(f.read()) + logging.config.dictConfig(config) + except (TypeError, FileNotFoundError): + self._load_defaults() + + def _load_defaults(self): + """Load default logging configuration""" + logging.config.dictConfig(self._default_config) + + def pbar(self, iters, desc=None): + """Create a tqdm iterable + + You can use this method to create progress bars. It uses a set + of abstracted methods from tqdm: + + .. code-block:: python + + from pyswarms.utils import Reporter + + rep = Reporter() + # Create a progress bar + for i in rep.pbar(100, name="Optimizer") + pass + + Parameters + ---------- + iters : int + Maximum range passed to the tqdm instance + desc : str + Name of the progress bar that will be displayed + + Returns + ------- + tqdm._tqdm.tqdm + A tqdm iterable + """ + self.t = trange(iters, desc=desc, bar_format=self._bar_fmt) + return self.t + + def hook(self, *args, **kwargs): + """Set a hook on the progress bar + + Method for creating a postfix in tqdm. In practice we use this + to report the best cost found during an iteration: + + .. code-block:: python + + from pyswarms.utils import Reporter + + rep = Reporter() + # Create a progress bar + for i in rep.pbar(100, name="Optimizer") + best_cost = compute() + rep.hook(best_cost=best_cost) + """ + self.t.set_postfix(*args, **kwargs) diff --git a/pyswarms/utils/search/base_search.py b/pyswarms/utils/search/base_search.py index 9bb2cbae..6533ffad 100644 --- a/pyswarms/utils/search/base_search.py +++ b/pyswarms/utils/search/base_search.py @@ -2,11 +2,9 @@ """Base class for hyperparameter optimization search functions""" # Import from __future__ -from __future__ import with_statement -from __future__ import absolute_import -from __future__ import print_function +from __future__ import absolute_import, print_function, with_statement -# Import modules +# Import standard library import operator as op diff --git a/pyswarms/utils/search/grid_search.py b/pyswarms/utils/search/grid_search.py index 46e2fba4..62fe47ee 100644 --- a/pyswarms/utils/search/grid_search.py +++ b/pyswarms/utils/search/grid_search.py @@ -18,7 +18,7 @@ 'k' : [5, 10, 15], 'p' : 1} >>> g = GridSearch(LocalBestPSO, n_particles=40, dimensions=20, - options=options, objective_func=sphere_func, iters=10) + options=options, objective_func=sphere, iters=10) >>> best_score, best_options = g.search() >>> best_score 0.498641604188 @@ -29,13 +29,12 @@ """ # Import from __future__ -from __future__ import with_statement -from __future__ import absolute_import -from __future__ import print_function +from __future__ import absolute_import, print_function, with_statement -# Import modules +# Import standard library import itertools +# Import from pyswarms # Import from package from pyswarms.utils.search.base_search import SearchBase diff --git a/pyswarms/utils/search/random_search.py b/pyswarms/utils/search/random_search.py index b5dd5df6..879ece7a 100644 --- a/pyswarms/utils/search/random_search.py +++ b/pyswarms/utils/search/random_search.py @@ -20,7 +20,7 @@ 'k' : [11, 15], 'p' : 1} >>> g = RandomSearch(LocalBestPSO, n_particles=40, dimensions=20, - options=options, objective_func=sphere_func, iters=10) + options=options, objective_func=sphere, iters=10) >>> best_score, best_options = g.search() >>> best_score 1.41978545901 @@ -31,14 +31,13 @@ """ # Import from __future__ -from __future__ import with_statement -from __future__ import absolute_import -from __future__ import print_function +from __future__ import absolute_import, print_function, with_statement # Import modules import numpy as np from past.builtins import xrange +# Import from pyswarms # Import from package from pyswarms.utils.search.base_search import SearchBase diff --git a/requirements_dev.txt b/requirements-dev.in similarity index 63% rename from requirements_dev.txt rename to requirements-dev.in index a2d2ed01..c5a769ee 100644 --- a/requirements_dev.txt +++ b/requirements-dev.in @@ -1,18 +1,17 @@ -pip==18.0 +-r requirements.in +# Packaging bumpversion==0.5.3 +PyYAML==3.13 # pyup: ignore wheel==0.31.1 watchdog==0.8.3 +cryptography==2.3 +# Tests +pytest==3.7.1 +coverage==4.5.1 flake8==3.5.0 mock==2.0.0 tox==3.2.1 -coverage==4.5.1 +# Workflow +pre-commit==1.10.5 +# Docs 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.7.1 -attrs==18.1.0 -pre-commit==1.10.5 \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..d57f5949 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,59 @@ +alabaster==0.7.12 +argh==0.26.2 +asn1crypto==0.24.0 +aspy.yaml==1.1.1 +atomicwrites==1.2.1 +attrs==18.1.0 +babel==2.6.0 +bumpversion==0.5.3 +cached-property==1.5.1 +certifi==2018.11.29 +cffi==1.11.5 +cfgv==1.4.0 +chardet==3.0.4 +coverage==4.5.1 +cryptography==2.3 +cycler==0.10.0 +docutils==0.14 +flake8==3.5.0 +future==0.16.0 +identify==1.2.1 +idna==2.8 +imagesize==1.1.0 +jinja2==2.10 +kiwisolver==1.0.1 +markupsafe==1.1.0 +matplotlib==3.0.2 +mccabe==0.6.1 +mock==2.0.0 +more-itertools==5.0.0 +nodeenv==1.3.3 +numpy==1.16.0 +packaging==19.0 +pathtools==0.1.2 +pbr==5.1.1 +pluggy==0.8.1 +pre-commit==1.10.5 +py==1.7.0 +pycodestyle==2.3.1 +pycparser==2.19 +pyflakes==1.6.0 +pygments==2.3.1 +pyparsing==2.3.1 +pytest==3.7.1 +python-dateutil==2.7.5 +pytz==2018.9 +pyyaml==3.13 +requests==2.21.0 +scipy==1.2.0 +six==1.12.0 +snowballstemmer==1.2.1 +sphinx==1.7.6 +sphinxcontrib-websupport==1.1.0 +toml==0.10.0 +tox==3.2.1 +tqdm==4.24.0 +urllib3==1.24.1 +virtualenv==16.3.0 +watchdog==0.8.3 +wheel==0.31.1 diff --git a/requirements.in b/requirements.in new file mode 100644 index 00000000..eaf7021b --- /dev/null +++ b/requirements.in @@ -0,0 +1,6 @@ +scipy>=0.17.0 +numpy>=1.13.0 +matplotlib>=1.3.1 +attrs==18.1.0 +tqdm==4.24.0 +future==0.16.0 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..cb80af65 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +attrs==18.1.0 +cycler==0.10.0 +future==0.16.0 +kiwisolver==1.0.1 +matplotlib==3.0.2 +numpy==1.16.0 +pyparsing==2.3.1 +python-dateutil==2.7.5 +scipy==1.2.0 +six==1.12.0 +tqdm==4.24.0 diff --git a/setup.py b/setup.py index 44fc25c5..04c165cb 100644 --- a/setup.py +++ b/setup.py @@ -3,38 +3,18 @@ """The setup script.""" -from setuptools import setup, find_packages +# Import modules +from setuptools import find_packages, setup with open("README.md", encoding="utf8") as readme_file: readme = readme_file.read() -requirements = [ - "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", -] +with open("requirements.txt") as f: + requirements = f.read().splitlines() -setup_requirements = [ - # TODO(ljvmiranda921): put setup requirements (distutils extensions, etc.) here -] +with open("requirements-dev.txt") as f: + dev_requirements = f.read().splitlines() -test_requirements = [ - "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", @@ -48,6 +28,8 @@ packages=find_packages(exclude=["docs", "tests"]), include_package_data=True, install_requires=requirements, + tests_require=dev_requirements, + extras_require={"test": dev_requirements}, license="MIT license", zip_safe=False, keywords="pyswarms", @@ -64,6 +46,4 @@ "Programming Language :: Python :: 3.6", ], test_suite="tests", - tests_require=test_requirements, - setup_requires=setup_requirements, ) diff --git a/tests/backend/conftest.py b/tests/backend/conftest.py index bccc1047..90b19e6b 100644 --- a/tests/backend/conftest.py +++ b/tests/backend/conftest.py @@ -4,9 +4,10 @@ """Fixtures for tests""" # Import modules -import pytest import numpy as np +import pytest +# Import from pyswarms # Import from package from pyswarms.backend.swarms import Swarm diff --git a/tests/backend/test_generators.py b/tests/backend/test_generators.py index 6c48c56c..89586604 100644 --- a/tests/backend/test_generators.py +++ b/tests/backend/test_generators.py @@ -2,70 +2,115 @@ # -*- coding: utf-8 -*- # Import modules -import pytest import numpy as np +import pytest -# Import from package +# Import from pyswarms 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]]) -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 +class TestGenerateSwarm(object): + """Test suite for generate_swarm() method""" + + @pytest.mark.parametrize( + "bounds", [None, ([2, 2, 2], [5, 5, 5]), ([-1, -1, 0], [2, 2, 5])] ) - if bounds is None: - min_bounds, max_bounds = (0.0, 1.00) - else: - min_bounds, max_bounds = bounds - lower_bound = center * np.array(min_bounds) - upper_bound = center * 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]]) - with pytest.raises(ValueError): + @pytest.mark.parametrize("center", [1, [3, 3, 3], [0.2, 0.2, 0.1]]) + def test_return_values(self, bounds, center): + """Test if method returns expected values""" pos = P.generate_swarm( - n_particles=2, dimensions=3, bounds=bounds, init_pos=init_pos + n_particles=2, dimensions=3, bounds=bounds, center=center ) + if bounds is None: + min_bounds, max_bounds = (0.0, 1.00) + else: + min_bounds, max_bounds = bounds + lower_bound = center * np.array(min_bounds) + upper_bound = center * np.array(max_bounds) + assert (pos <= upper_bound).all() and (pos >= lower_bound).all() + def test_out_of_bounds(self): + """Test if method 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]]) + with pytest.raises(ValueError): + P.generate_swarm( + n_particles=2, dimensions=3, bounds=bounds, init_pos=init_pos + ) -@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 + @pytest.mark.parametrize("bounds", [0.1]) + def test_bounds_wrong_type(self, bounds): + """Test if method raises TypeError when bounds is not an array""" + with pytest.raises(TypeError): + P.generate_swarm(n_particles=2, dimensions=3, bounds=bounds) + + @pytest.mark.parametrize( + "bounds", [(1, 1, 1), ([1, 1, 1]), ([1, 1, 1], [2, 2])] ) - if binary: - 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]])]) -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 + def test_bounds_wrong_size(self, bounds): + """Test if method raises ValueError when bounds is of wrong shape""" + with pytest.raises(ValueError): + P.generate_swarm(n_particles=2, dimensions=3, bounds=bounds) + + +class TestDiscreteSwarm(object): + """Test suite for generate_discrete_swarm() method""" + + @pytest.mark.parametrize("binary", [False, True]) + def test_generate_discrete_binary_swarm(self, binary): + """Test if binary=True returns expected values""" + dims = 3 + pos = P.generate_discrete_swarm( + n_particles=2, dimensions=dims, binary=binary + ) + if binary: + assert len(np.unique(pos)) <= 2 # Might generate pure 0 or 1 + else: + assert (np.max(pos, axis=1) == dims - 1).all() + + def test_not_binary_error_discrete_swarm(self): + """Test if method raises ValueError given wrong init_pos val""" + init_pos = [0, 1, 2] + with pytest.raises(ValueError): + P.generate_discrete_swarm( + n_particles=2, dimensions=3, binary=True, init_pos=init_pos + ) + + @pytest.mark.parametrize( + "init_pos", [None, np.array([[4, 2, 1], [1, 4, 6]])] ) - 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)]) -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 - velocity = P.generate_velocity(n_particles=2, dimensions=3, clamp=clamp) - assert (velocity <= max_clamp).all() and (velocity >= min_clamp).all() + def test_generate_discrete_swarm(self, init_pos): + """Test if init_pos actually sets the position properly""" + dims = 3 + 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() + + +class TestGenerateVelocity(object): + """Test suite for generate_velocity()""" + + @pytest.mark.parametrize("clamp", [None, (0, 1), (2, 5), (1, 6)]) + def test_return_values(self, clamp): + """Test if the method returns expected values""" + min_clamp, max_clamp = (0, 1) if clamp is None else clamp + velocity = P.generate_velocity( + n_particles=2, dimensions=3, clamp=clamp + ) + assert (velocity <= max_clamp).all() and (velocity >= min_clamp).all() + + @pytest.mark.parametrize("clamp", [(0, 2, 5), [1, 3, 5]]) + def test_invalid_clamp_value(self, clamp): + """Test if the method raises a ValueError given invalid clamp size""" + with pytest.raises(ValueError): + P.generate_velocity(n_particles=2, dimensions=3, clamp=clamp) + + @pytest.mark.parametrize("clamp", [0, 1]) + def test_invalid_clamp_type(self, clamp): + """Test if method raises a TypeError given invalid clamp type""" + with pytest.raises(TypeError): + P.generate_velocity(n_particles=2, dimensions=3, clamp=clamp) diff --git a/tests/backend/test_operators.py b/tests/backend/test_operators.py index 7fdbe9aa..224dcc04 100644 --- a/tests/backend/test_operators.py +++ b/tests/backend/test_operators.py @@ -2,38 +2,73 @@ # -*- coding: utf-8 -*- # Import modules -import pytest import numpy as np +import pytest -# Import from package +# Import from pyswarms import pyswarms.backend as P -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]]) - 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)]) -def test_compute_velocity_return_values(swarm, clamp): - """Test if compute_velocity() gives the expected shape and range""" - v = P.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""" - p = P.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() +class TestComputePbest(object): + """Test suite for compute_pbest()""" + + def test_return_values(self, swarm): + """Test if method 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]]) + pos, cost = P.compute_pbest(swarm) + assert (pos == expected_pos).all() + assert (cost == expected_cost).all() + + @pytest.mark.parametrize("swarm", [0, (1, 2, 3)]) + def test_input_swarm(self, swarm): + """Test if method raises AttributeError with wrong swarm""" + with pytest.raises(AttributeError): + P.compute_pbest(swarm) + + +class TestComputeVelocity(object): + """Test suite for compute_velocity()""" + + @pytest.mark.parametrize("clamp", [None, (0, 1), (-1, 1)]) + def test_return_values(self, swarm, clamp): + """Test if method gives the expected shape and range""" + v = P.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("swarm", [0, (1, 2, 3)]) + def test_input_swarm(self, swarm): + """Test if method raises AttributeError with wrong swarm""" + with pytest.raises(AttributeError): + P.compute_velocity(swarm, clamp=(0, 1)) + + @pytest.mark.parametrize("options", [{"c1": 0.5, "c2": 0.3}]) + def test_missing_kwargs(self, swarm, options): + """Test if method raises KeyError with missing kwargs""" + with pytest.raises(KeyError): + swarm.options = options + clamp = (0, 1) + P.compute_velocity(swarm, clamp) + + +class TestComputePosition(object): + """Test suite for compute_position()""" + + @pytest.mark.parametrize( + "bounds", + [None, ([-5, -5, -5], [5, 5, 5]), ([-10, -10, -10], [10, 10, 10])], + ) + def test_return_values(self, swarm, bounds): + """Test if method gives the expected shape and range""" + p = P.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("swarm", [0, (1, 2, 3)]) + def test_input_swarm(self, swarm): + """Test if method raises AttributeError with wrong swarm""" + with pytest.raises(AttributeError): + P.compute_position(swarm, bounds=([-5, -5], [5, 5])) diff --git a/tests/backend/topology/abc_test_topology.py b/tests/backend/topology/abc_test_topology.py new file mode 100644 index 00000000..f91c5701 --- /dev/null +++ b/tests/backend/topology/abc_test_topology.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Import standard library +import abc + +# Import modules +import pytest + + +class ABCTestTopology(abc.ABC): + """Abstract class that defines various tests for topologies + + Whenever a topology inherits from ABCTestTopology, + you don't need to write down all tests anymore. Instead, you can just + specify all required fixtures in the test suite. + """ + + @pytest.fixture + def topology(self): + """Return an instance of the topology""" + raise NotImplementedError("NotImplementedError::topology") + + @pytest.fixture + def options(self): + """Return a dictionary of options""" + raise NotImplementedError("NotImplementedError::options") + + @pytest.mark.parametrize("static", [True, False]) + @pytest.mark.parametrize("clamp", [None, (0, 1), (-1, 1)]) + def test_compute_velocity_return_values( + self, topology, swarm, clamp, static + ): + """Test if compute_velocity() gives the expected shape and range""" + topo = topology(static=static) + v = topo.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( + self, topology, swarm, bounds, static + ): + """Test if compute_position() gives the expected shape and range""" + topo = topology(static=static) + p = topo.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(self, topology, options, swarm, static): + """Test if the neighbor_idx attribute is assigned""" + topo = topology(static=static) + topo.compute_gbest(swarm, **options) + assert topo.neighbor_idx is not None + + @pytest.mark.parametrize("static", [True, False]) + @pytest.mark.parametrize("swarm", [0, (1, 2, 3)]) + def test_input_swarm(self, topology, static, swarm, options): + """Test if AttributeError is raised when passed with a non-Swarm instance""" + with pytest.raises(AttributeError): + topo = topology(static=static) + topo.compute_gbest(swarm, **options) diff --git a/tests/backend/topology/conftest.py b/tests/backend/topology/conftest.py index 4aade1b1..1ddcdeb9 100644 --- a/tests/backend/topology/conftest.py +++ b/tests/backend/topology/conftest.py @@ -4,9 +4,10 @@ """Fixtures for tests""" # Import modules -import pytest import numpy as np +import pytest +# Import from pyswarms # Import from package from pyswarms.backend.swarms import Swarm @@ -14,43 +15,45 @@ @pytest.fixture(scope="module") def swarm(): """A contrived instance of the Swarm class at a certain timestep""" + # fmt: off attrs_at_t = { - "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], + "position": np.array([[40.95838686e-01, 5.87433429e-04, 6.49113772e-03], + [1.00559609e+00, 3.96477697e-02, 7.67205397e-01], + [8.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]), + [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], + "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.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}, } + # fmt: on return Swarm(**attrs_at_t) diff --git a/tests/backend/topology/test_pyramid.py b/tests/backend/topology/test_pyramid.py index 31583428..eb3e0b16 100644 --- a/tests/backend/topology/test_pyramid.py +++ b/tests/backend/topology/test_pyramid.py @@ -2,52 +2,33 @@ # -*- coding: utf-8 -*- # Import modules -import pytest import numpy as np +import pytest -# Import from package +# Import from pyswarms 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 +from .abc_test_topology import ABCTestTopology + +np.random.seed(4135157) + + +class TestPyramidTopology(ABCTestTopology): + @pytest.fixture + def topology(self): + return Pyramid + + @pytest.fixture + def options(self): + return {} + + @pytest.mark.parametrize("static", [True, False]) + def test_compute_gbest_return_values( + self, swarm, topology, options, static + ): + """Test if compute_gbest() gives the expected return values""" + topo = topology(static=static) + expected_cost = 1.0002528364353296 + expected_pos = np.array([9.90438476e-01, 2.50379538e-03, 1.87405987e-05]) + pos, cost = topo.compute_gbest(swarm, **options) + assert cost == pytest.approx(expected_cost) + assert pos[np.argmin(cost)] == pytest.approx(expected_pos) diff --git a/tests/backend/topology/test_random.py b/tests/backend/topology/test_random.py index 1b12a5dc..50ef3235 100644 --- a/tests/backend/topology/test_random.py +++ b/tests/backend/topology/test_random.py @@ -2,84 +2,70 @@ # -*- coding: utf-8 -*- # Import modules -import pytest import numpy as np +import pytest -# Import from package +# Import from pyswarms from pyswarms.backend.topology import Random +from .abc_test_topology import ABCTestTopology - -@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() +np.random.seed(4135157) -@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 +class TestRandomTopology(ABCTestTopology): + @pytest.fixture + def topology(self): + return Random + @pytest.fixture(params=[1, 2, 3]) + def options(self, request): + return {"k": request.param} -@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, 2]) + def test_compute_gbest_return_values( + self, swarm, options, topology, k, static + ): + """Test if update_gbest_neighborhood gives the expected return values""" + topo = topology(static=static) + expected_cost = 1.0002528364353296 + expected_pos = np.array([9.90438476e-01, 2.50379538e-03, 1.87405987e-05]) + expected_pos_2 = np.array([9.98033031e-01, 4.97392619e-03, 3.07726256e-03]) + pos, cost = topo.compute_gbest(swarm, **options) + assert cost == pytest.approx(expected_cost) + assert ((pos[np.argmin(cost)] == pytest.approx(expected_pos)) or + (pos[np.argmin(cost)] == pytest.approx(expected_pos_2))) + @pytest.mark.parametrize("static", [True, False]) + @pytest.mark.parametrize("k", [1, 2]) + def test_compute_neighbors_return_values(self, swarm, topology, k, static): + """Test if __compute_neighbors() gives the expected shape and symmetry""" + topo = topology(static=static) + adj_matrix = topo._Random__compute_neighbors(swarm, k=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_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 + @pytest.mark.parametrize("static", [True, False]) + @pytest.mark.parametrize("k", [1]) + def test_compute_neighbors_adjacency_matrix( + self, swarm, topology, k, static + ): + """Test if __compute_neighbors() gives the expected matrix""" + np.random.seed(1) + topo = topology(static=static) + adj_matrix = topo._Random__compute_neighbors(swarm, k=k) + # fmt: off + 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) + # fmt: on diff --git a/tests/backend/topology/test_ring.py b/tests/backend/topology/test_ring.py index c7723724..03fa8948 100644 --- a/tests/backend/topology/test_ring.py +++ b/tests/backend/topology/test_ring.py @@ -2,56 +2,36 @@ # -*- coding: utf-8 -*- # Import modules -import pytest import numpy as np +import pytest -# Import from package +# Import from pyswarms from pyswarms.backend.topology import Ring - - -@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(static=static) - pos, cost = topology.compute_gbest(swarm, p=p, 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 = 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("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(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 +from .abc_test_topology import ABCTestTopology + +np.random.seed(4135157) + + +class TestRingTopology(ABCTestTopology): + @pytest.fixture + def topology(self): + return Ring + + @pytest.fixture(params=[(1, 2), (2, 3)]) + def options(self, request): + p, k = request.param + return {"p": p, "k": 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_compute_gbest_return_values(self, swarm, topology, p, k, static): + """Test if update_gbest_neighborhood gives the expected return values""" + topo = topology(static=static) + pos, cost = topo.compute_gbest(swarm, p=p, k=k) + expected_cost = 1.0002528364353296 + expected_pos = np.array([9.90438476e-01, 2.50379538e-03, 1.87405987e-05]) + expected_pos_2 = np.array([9.98033031e-01, 4.97392619e-03, 3.07726256e-03]) + assert cost == pytest.approx(expected_cost) + assert ((pos[np.argmin(cost)] == pytest.approx(expected_pos)) or + (pos[np.argmin(cost)] == pytest.approx(expected_pos_2))) diff --git a/tests/backend/topology/test_star.py b/tests/backend/topology/test_star.py index 1dc72415..3f8f5a67 100644 --- a/tests/backend/topology/test_star.py +++ b/tests/backend/topology/test_star.py @@ -2,48 +2,33 @@ # -*- coding: utf-8 -*- # Import modules -import pytest import numpy as np +import pytest -# Import from package +# Import from pyswarms from pyswarms.backend.topology import Star +from .abc_test_topology import ABCTestTopology + +np.random.seed(4135157) + + +class TestStarTopology(ABCTestTopology): + @pytest.fixture + def topology(self): + return Star + + @pytest.fixture + def options(self): + return {} -def test_compute_gbest_return_values(swarm): - """Test if compute_gbest() gives the expected return values""" - topology = Star() - 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("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() - 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 = 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() - - -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 + def test_compute_gbest_return_values(self, swarm, options, topology): + """Test if compute_gbest() gives the expected return values""" + topo = topology() + expected_cost = 1.0002528364353296 + expected_pos = np.array( + [9.90438476e-01, 2.50379538e-03, 1.87405987e-05] + ) + pos, cost = topo.compute_gbest(swarm, **options) + assert cost == pytest.approx(expected_cost) + assert pos == pytest.approx(expected_pos) diff --git a/tests/backend/topology/test_von_neumann.py b/tests/backend/topology/test_von_neumann.py index e34a9000..7e3eed53 100644 --- a/tests/backend/topology/test_von_neumann.py +++ b/tests/backend/topology/test_von_neumann.py @@ -2,65 +2,40 @@ # -*- coding: utf-8 -*- # Import modules -import pytest import numpy as np +import pytest -# Import from package +# Import from pyswarms 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 +from .abc_test_topology import ABCTestTopology + +np.random.seed(4135157) + + +class TestVonNeumannTopology(ABCTestTopology): + @pytest.fixture + def topology(self): + return VonNeumann + + @pytest.fixture + def options(self): + return {"p": 1, "r": 1} + + @pytest.mark.parametrize("r", [0, 1]) + @pytest.mark.parametrize("p", [1, 2]) + def test_update_gbest_neighborhood(self, swarm, topology, p, r): + """Test if update_gbest_neighborhood gives the expected return values""" + topo = topology() + pos, cost = topo.compute_gbest(swarm, p=p, r=r) + expected_pos = np.array([9.90438476e-01, 2.50379538e-03, 1.87405987e-05]) + expected_pos_2 = np.array([9.98033031e-01, 4.97392619e-03, 3.07726256e-03]) + expected_cost = 1.0002528364353296 + assert cost == pytest.approx(expected_cost) + assert ((pos[np.argmin(cost)] == pytest.approx(expected_pos)) or + (pos[np.argmin(cost)] == pytest.approx(expected_pos_2))) + + @pytest.mark.parametrize("m", [i for i in range(3)]) + @pytest.mark.parametrize("n", [i for i in range(3)]) + def test_delannoy_numbers(self, m, n): + expected_values = np.array([1, 3, 5, 7, 9, 11, 13, 15, 17]) + assert VonNeumann.delannoy(m, n) in expected_values diff --git a/tests/optimizers/abc_test_discrete_optimizer.py b/tests/optimizers/abc_test_discrete_optimizer.py new file mode 100644 index 00000000..5e498ea1 --- /dev/null +++ b/tests/optimizers/abc_test_discrete_optimizer.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Import modules +import numpy as np +import pytest + +from .abc_test_optimizer import ABCTestOptimizer + +# from pyswarms.utils.functions.single_obj import sphere + + +class ABCTestDiscreteOptimizer(ABCTestOptimizer): + """Abstract class that defines various tests for high-level optimizers + + Whenever an optimizer implementation inherits from ABCTestOptimizer, + you don't need to write down all tests anymore. Instead, you can just + specify all required fixtures in the test suite. + """ + + @pytest.mark.skip("No way of testing this yet") + def test_obj_with_kwargs(self, obj_with_args, optimizer, options): + """Test if kwargs are passed properly in objfunc""" + opt = optimizer(100, 2, options=options) + cost, pos = opt.optimize(obj_with_args, 1000, 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.skip("No way of testing this yet") + def test_obj_unnecessary_kwargs( + self, obj_without_args, optimizer, options + ): + """Test if error is raised given unnecessary kwargs""" + opt = optimizer(100, 2, options=options) + with pytest.raises(TypeError): + # kwargs `a` should not be supplied + cost, pos = opt.optimize(obj_without_args, 1000, a=1) + + @pytest.mark.skip("No way of testing this yet") + def test_obj_missing_kwargs(self, obj_with_args, optimizer, options): + """Test if error is raised with incomplete kwargs""" + opt = optimizer(100, 2, options=options) + with pytest.raises(TypeError): + # kwargs `b` is missing here + cost, pos = opt.optimize(obj_with_args, 1000, a=1) + + @pytest.mark.skip("No way of testing this yet") + def test_obj_incorrect_kwargs(self, obj_with_args, optimizer, options): + """Test if error is raised with wrong kwargs""" + opt = optimizer(100, 2, options=options) + with pytest.raises(TypeError): + # Wrong kwargs + cost, pos = opt.optimize(obj_with_args, 1000, c=1, d=100) diff --git a/tests/optimizers/abc_test_optimizer.py b/tests/optimizers/abc_test_optimizer.py new file mode 100644 index 00000000..c7474c1e --- /dev/null +++ b/tests/optimizers/abc_test_optimizer.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Import standard library +import abc + +# Import modules +import numpy as np +import pytest + +# Import from pyswarms +from pyswarms.utils.functions.single_obj import rosenbrock, sphere + + +class ABCTestOptimizer(abc.ABC): + """Abstract class that defines various tests for high-level optimizers + + Whenever an optimizer implementation inherits from ABCTestOptimizer, + you don't need to write down all tests anymore. Instead, you can just + specify all required fixtures in the test suite. + """ + + @pytest.fixture + def optimizer(self): + """Return an instance of the optimizer""" + raise NotImplementedError("NotImplementedError::optimizer") + + @pytest.fixture + def optimizer_history(self): + """Run the optimizer for 1000 iterations and return its instance""" + raise NotImplementedError("NotImplementedError::optimizer_history") + + @pytest.fixture + def optimizer_reset(self): + """Reset the optimizer and return its instance""" + raise NotImplementedError("NotImplementedError::optimizer_reset") + + @pytest.fixture + def options(self): + """Default options dictionary for most PSO use-cases""" + return {"c1": 0.3, "c2": 0.7, "w": 0.9, "k": 2, "p": 2, "r": 1} + + @pytest.fixture + def obj_with_args(self): + """Objective function with arguments""" + + def obj_with_args_(x, a, b): + f = (a - x[:, 0]) ** 2 + b * (x[:, 1] - x[:, 0] ** 2) ** 2 + return f + + return obj_with_args_ + + @pytest.fixture + def obj_without_args(self): + """Objective function without arguments""" + return rosenbrock + + @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_train_history(self, optimizer_history, history, expected_shape): + """Test if training histories are of expected shape""" + opt = vars(optimizer_history) + assert np.array(opt[history]).shape == expected_shape + + def test_reset_default_values(self, optimizer_reset): + """Test if best cost and best pos are set properly when the reset() + method is called""" + assert optimizer_reset.swarm.best_cost == np.inf + assert set(optimizer_reset.swarm.best_pos) == set(np.array([])) + + @pytest.mark.skip(reason="The Ring topology converges too slowly") + def test_ftol_effect(self, options, optimizer): + """Test if setting the ftol breaks the optimization process""" + opt = optimizer(10, 2, options=options, ftol=1e-1) + opt.optimize(sphere, 2000) + assert np.array(opt.cost_history).shape != (2000,) + + def test_obj_with_kwargs(self, obj_with_args, optimizer, options): + """Test if kwargs are passed properly in objfunc""" + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + opt = optimizer(100, 2, options=options, bounds=bounds) + cost, pos = opt.optimize(obj_with_args, 1000, 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) + + def test_obj_unnecessary_kwargs( + self, obj_without_args, optimizer, options + ): + """Test if error is raised given unnecessary kwargs""" + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + opt = optimizer(100, 2, options=options, bounds=bounds) + with pytest.raises(TypeError): + # kwargs `a` should not be supplied + cost, pos = opt.optimize(obj_without_args, 1000, a=1) + + def test_obj_missing_kwargs(self, obj_with_args, optimizer, options): + """Test if error is raised with incomplete kwargs""" + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + opt = optimizer(100, 2, options=options, bounds=bounds) + with pytest.raises(TypeError): + # kwargs `b` is missing here + cost, pos = opt.optimize(obj_with_args, 1000, a=1) + + def test_obj_incorrect_kwargs(self, obj_with_args, optimizer, options): + """Test if error is raised with wrong kwargs""" + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + opt = optimizer(100, 2, options=options, bounds=bounds) + with pytest.raises(TypeError): + # Wrong kwargs + cost, pos = opt.optimize(obj_with_args, 1000, c=1, d=100) diff --git a/tests/optimizers/conftest.py b/tests/optimizers/conftest.py deleted file mode 100644 index 6dfee2c3..00000000 --- a/tests/optimizers/conftest.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -"""Fixtures for tests""" - -# Import modules -import pytest -import numpy as np - -# Import from package -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") -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.optimize(sphere_func, 1000, verbose=0) - return pso - - -@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.optimize(sphere_func, 10, verbose=0) - pso.reset() - return pso - - -@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.optimize(sphere_func, 1000, verbose=0) - return pso - - -@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.optimize(sphere_func, 10, verbose=0) - pso.reset() - return pso - - -@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.optimize(sphere_func, 1000, verbose=0) - return pso - - -@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.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, "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 acf533b6..0fca5fc4 100644 --- a/tests/optimizers/test_binary.py +++ b/tests/optimizers/test_binary.py @@ -3,85 +3,28 @@ # Import modules import pytest -import numpy as np -# Import from package +# Import from pyswarms from pyswarms.discrete import BinaryPSO +from pyswarms.utils.functions.single_obj import sphere +from .abc_test_discrete_optimizer import ABCTestDiscreteOptimizer -@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) +class TestDiscreteOptimizer(ABCTestDiscreteOptimizer): + @pytest.fixture + def optimizer(self): + return BinaryPSO -@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.fixture + def optimizer_history(self, options): + opt = BinaryPSO(10, 2, options=options) + opt.optimize(sphere, 1000) + return opt - -@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)]) -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)]) -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([])) - - -@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""" - pso = vars(binary_history) - assert np.array(pso[history]).shape == expected_shape + @pytest.fixture + def optimizer_reset(self, options): + opt = BinaryPSO(10, 2, options=options) + opt.optimize(sphere, 10) + opt.reset() + return opt diff --git a/tests/optimizers/test_general_optimizer.py b/tests/optimizers/test_general_optimizer.py index 970e9116..96cee252 100644 --- a/tests/optimizers/test_general_optimizer.py +++ b/tests/optimizers/test_general_optimizer.py @@ -1,221 +1,97 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# Import standard library +import inspect + # Import modules -import pytest import numpy as np +import pytest -# Import from package +# Import from pyswarms +import pyswarms.backend.topology as t 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,) +from pyswarms.utils.functions.single_obj import sphere + +from .abc_test_optimizer import ABCTestOptimizer + + +def istopology(x): + """Helper predicate to check if it's a subclass""" + return inspect.isclass(x) and not inspect.isabstract(x) + + +# Get all classes in the topology module, then +# Instatiate topologies, no need to suppy static param +topologies = [topo() for _, topo in inspect.getmembers(t, istopology)] + + +class TestGeneralOptimizer(ABCTestOptimizer): + @pytest.fixture(params=topologies) + def optimizer(self, request, options): + x_max = 10 * np.ones(2) + x_min = -1 * x_max + bounds = (x_min, x_max) + return GeneralOptimizerPSO( + n_particles=10, + dimensions=2, + options=options, + bounds=bounds, + topology=request.param, + ) + + @pytest.fixture(params=topologies) + def optimizer_history(self, request, options): + opt = GeneralOptimizerPSO( + n_particles=10, + dimensions=2, + options=options, + topology=request.param, + ) + opt.optimize(sphere, 1000) + return opt + + @pytest.fixture(params=topologies) + def optimizer_reset(self, request, options): + opt = GeneralOptimizerPSO( + n_particles=10, + dimensions=2, + options=options, + topology=request.param, + ) + opt.optimize(sphere, 1000) + opt.reset() + return opt + + def test_ftol_effect(self, optimizer): + """Test if setting the ftol breaks the optimization process""" + # Set optimizer tolerance + optimizer.ftol = 1e-1 + optimizer.optimize(sphere, 2000) + assert np.array(optimizer.cost_history).shape != (2000,) + + @pytest.mark.skip(reason="Some topologies converge too slowly") + def test_obj_with_kwargs(self, obj_with_args, optimizer): + """Test if kwargs are passed properly in objfunc""" + cost, pos = optimizer.optimize(obj_with_args, 1000, 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) + + def test_obj_unnecessary_kwargs(self, obj_without_args, optimizer): + """Test if error is raised given unnecessary kwargs""" + with pytest.raises(TypeError): + # kwargs `a` should not be supplied + cost, pos = optimizer.optimize(obj_without_args, 1000, a=1) + + def test_obj_missing_kwargs(self, obj_with_args, optimizer): + """Test if error is raised with incomplete kwargs""" + with pytest.raises(TypeError): + # kwargs `b` is missing here + cost, pos = optimizer.optimize(obj_with_args, 1000, a=1) + + def test_obj_incorrect_kwargs(self, obj_with_args, optimizer): + """Test if error is raised with wrong kwargs""" + with pytest.raises(TypeError): + # Wrong kwargs + cost, pos = optimizer.optimize(obj_with_args, 1000, c=1, d=100) diff --git a/tests/optimizers/test_global_best.py b/tests/optimizers/test_global_best.py index 434f9770..58187288 100644 --- a/tests/optimizers/test_global_best.py +++ b/tests/optimizers/test_global_best.py @@ -3,111 +3,28 @@ # Import modules import pytest -import numpy as np -# Import from package +# Import from pyswarms from pyswarms.single import GlobalBestPSO -from pyswarms.utils.functions.single_obj import sphere_func +from pyswarms.utils.functions.single_obj import sphere +from .abc_test_optimizer import ABCTestOptimizer -@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) +class TestGlobalBestOptimizer(ABCTestOptimizer): + @pytest.fixture + def optimizer(self): + return GlobalBestPSO -@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.fixture + def optimizer_history(self, options): + opt = GlobalBestPSO(10, 2, options=options) + opt.optimize(sphere, 1000) + return opt - -@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])]), - ], -) -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) - - -@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)]) -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])]) -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([])) - - -@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): - """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 np.array(pso.cost_history).shape != (2000,) + @pytest.fixture + def optimizer_reset(self, options): + opt = GlobalBestPSO(10, 2, options=options) + opt.optimize(sphere, 10) + opt.reset() + return opt diff --git a/tests/optimizers/test_local_best.py b/tests/optimizers/test_local_best.py index 3d3315fa..40df0e47 100644 --- a/tests/optimizers/test_local_best.py +++ b/tests/optimizers/test_local_best.py @@ -3,132 +3,28 @@ # Import modules import pytest -import numpy as np -# Import from package +# Import from pyswarms from pyswarms.single import LocalBestPSO -from pyswarms.utils.functions.single_obj import sphere_func +from pyswarms.utils.functions.single_obj import sphere +from .abc_test_optimizer import ABCTestOptimizer -@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) +class TestLocalBestOptimizer(ABCTestOptimizer): + @pytest.fixture + def optimizer(self): + return LocalBestPSO -@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.fixture + def optimizer_history(self, options): + opt = LocalBestPSO(10, 2, options) + opt.optimize(sphere, 1000) + return opt - -@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])), - ], -) -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])]), - ], -) -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) - - -@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)]) -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])]) -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([])) - - -@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""" - 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 np.array(pso.cost_history).shape != (2000,) + @pytest.fixture + def optimizer_reset(self, options): + opt = LocalBestPSO(10, 2, options) + opt.optimize(sphere, 10) + opt.reset() + return opt diff --git a/tests/optimizers/test_objective_func_with_kwargs.py b/tests/optimizers/test_objective_func_with_kwargs.py deleted file mode 100644 index a980044b..00000000 --- a/tests/optimizers/test_objective_func_with_kwargs.py +++ /dev/null @@ -1,247 +0,0 @@ -#!/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/__init__.py b/tests/utils/decorators/__init__.py similarity index 100% rename from tests/utils/environments/__init__.py rename to tests/utils/decorators/__init__.py diff --git a/tests/utils/decorators/conftest.py b/tests/utils/decorators/conftest.py new file mode 100644 index 00000000..40981b6e --- /dev/null +++ b/tests/utils/decorators/conftest.py @@ -0,0 +1,11 @@ +# Import modules +import numpy as np +import pytest + + +@pytest.fixture() +def particles(): + shape = (np.random.randint(10, 20), np.random.randint(2, 6)) + particles_ = np.random.uniform(0, 10, shape) + print(particles_) + return particles_ diff --git a/tests/utils/decorators/test_decorators.py b/tests/utils/decorators/test_decorators.py new file mode 100644 index 00000000..67e4131e --- /dev/null +++ b/tests/utils/decorators/test_decorators.py @@ -0,0 +1,47 @@ +# Import modules +import numpy as np +import pytest + +# Import from pyswarms +# Import from package +from pyswarms.utils.decorators import cost + + +@pytest.mark.parametrize("objective_func", [np.sum, np.prod]) +def test_cost_decorator(objective_func, particles): + """Test if cost decorator returns the same shape and value as undecorated function""" + n_particles = particles.shape[0] + + def cost_func_without_decorator(x): + n_particles_in_func = x.shape[0] + cost = np.array( + [objective_func(x[i]) for i in range(n_particles_in_func)] + ) + return cost + + @cost + def cost_func_with_decorator(x): + cost = objective_func(x) + return cost + + undecorated = cost_func_without_decorator(particles) + decorated = cost_func_with_decorator(particles) + + assert np.array_equal(decorated, undecorated) + assert decorated.shape == (n_particles,) + + +def test_decorator_invalid_cost_func(particles): + """Test if ValueError is raised whenever an invalid cost function is passed""" + + def objective_func(x): + """Returns a numpy.ndarray instead of int or float""" + return np.array([1, 3]) + + @cost + def cost_func_with_wrong_output_shape_decorated(x): + cost = objective_func(x) + return cost + + with pytest.raises(ValueError): + cost_func_with_wrong_output_shape_decorated(particles) diff --git a/tests/utils/environments/conftest.py b/tests/utils/environments/conftest.py deleted file mode 100644 index 1f548e9b..00000000 --- a/tests/utils/environments/conftest.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -"""Fixtures for tests""" - -# Import modules -import os -import pytest -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.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 = [ - "cost_history", - "pos_history", - "velocity_history", - "optimize", - "reset", - ] - 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) diff --git a/tests/utils/environments/test_plot_environment.py b/tests/utils/environments/test_plot_environment.py deleted file mode 100644 index 600a5039..00000000 --- a/tests/utils/environments/test_plot_environment.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/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.environments import PlotEnvironment -from pyswarms.utils.functions.single_obj import sphere_func - -class_methods = [ - "cost_history", - "pos_history", - "velocity_history", - "optimize", - "reset", -] - - -@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) - - -@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) - - -@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) diff --git a/tests/utils/functions/conftest.py b/tests/utils/functions/conftest.py index 855f3845..e8988ef3 100644 --- a/tests/utils/functions/conftest.py +++ b/tests/utils/functions/conftest.py @@ -4,8 +4,8 @@ """Fixtures for tests""" # Import modules -import pytest import numpy as np +import pytest @pytest.fixture diff --git a/tests/utils/functions/test_singleobj_bounds.py b/tests/utils/functions/test_singleobj_bounds.py index dd950106..1418153e 100644 --- a/tests/utils/functions/test_singleobj_bounds.py +++ b/tests/utils/functions/test_singleobj_bounds.py @@ -3,12 +3,14 @@ """Tests for `pyswarms` package.""" +# Import standard library +from collections import namedtuple + # Import modules -import pytest import numpy as np -from collections import namedtuple +import pytest -# Import from package +# Import from pyswarms from pyswarms.utils.functions import single_obj as fx Bounds = namedtuple("Bounds", "low high") @@ -33,26 +35,25 @@ } - 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)) - fx.ackley_func(x) + fx.ackley(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)) - fx.beale_func(x) + fx.beale(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)) - fx.booth_func(x) + fx.booth(x) @pytest.mark.parametrize( @@ -66,70 +67,70 @@ def test_booth_bound_fail(outbound): def test_bukin6_bound_fail(x): """Test bukin6 bound exception""" with pytest.raises(ValueError): - fx.bukin6_func(x) + fx.bukin6(x) def test_crossintray_bound_fail(outbound): """Test crossintray bound exception""" with pytest.raises(ValueError): x = outbound(b["crossintray"].low, b["crossintray"].high, size=(3, 2)) - fx.crossintray_func(x) + fx.crossintray(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) + fx.easom(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) + fx.eggholder(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) + fx.goldstein(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) + fx.himmelblau(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) + fx.holdertable(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)) - fx.levi_func(x) + fx.levi(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) + fx.matyas(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) + fx.rastrigin(x) def test_schaffer2_bound_fail(outbound): @@ -138,11 +139,11 @@ def test_schaffer2_bound_fail(outbound): x = outbound( b["schaffer2"].low, b["schaffer2"].high, tol=200, size=(3, 2) ) - fx.schaffer2_func(x) + fx.schaffer2(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) + fx.threehump(x) diff --git a/tests/utils/functions/test_singleobj_dims.py b/tests/utils/functions/test_singleobj_dims.py index e1a78e55..d25259bb 100644 --- a/tests/utils/functions/test_singleobj_dims.py +++ b/tests/utils/functions/test_singleobj_dims.py @@ -3,87 +3,90 @@ """Tests for `pyswarms` package.""" +# Import standard library +from collections import namedtuple + # Import modules -import pytest import numpy as np -from collections import namedtuple +import pytest -# Import from package +# Import from pyswarms 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) + fx.beale(outdim) def test_booth_dim_fail(outdim): """Test booth dim exception""" with pytest.raises(IndexError): - fx.booth_func(outdim) + fx.booth(outdim) def test_bukin6_dim_fail(outdim): """Test bukin6 dim exception""" with pytest.raises(IndexError): - fx.bukin6_func(outdim) + fx.bukin6(outdim) def test_crossintray_dim_fail(outdim): """Test crossintray dim exception""" with pytest.raises(IndexError): - fx.crossintray_func(outdim) + fx.crossintray(outdim) def test_easom_dim_fail(outdim): """Test easom dim exception""" with pytest.raises(IndexError): - fx.easom_func(outdim) + fx.easom(outdim) + def test_goldstein_dim_fail(outdim): """Test goldstein dim exception""" with pytest.raises(IndexError): - fx.goldstein_func(outdim) + fx.goldstein(outdim) def test_eggholder_dim_fail(outdim): """Test eggholder dim exception""" with pytest.raises(IndexError): - fx.eggholder_func(outdim) + fx.eggholder(outdim) def test_himmelblau_dim_fail(outdim): """Test himmelblau dim exception""" with pytest.raises(IndexError): - fx.himmelblau_func(outdim) + fx.himmelblau(outdim) def test_holdertable_dim_fail(outdim): """Test holdertable dim exception""" with pytest.raises(IndexError): - fx.holdertable_func(outdim) + fx.holdertable(outdim) def test_levi_dim_fail(outdim): """Test levi dim exception""" with pytest.raises(IndexError): - fx.levi_func(outdim) + fx.levi(outdim) def test_matyas_dim_fail(outdim): """Test matyas dim exception""" with pytest.raises(IndexError): - fx.matyas_func(outdim) + fx.matyas(outdim) def test_schaffer2_dim_fail(outdim): """Test schaffer2 dim exception""" with pytest.raises(IndexError): - fx.schaffer2_func(outdim) + fx.schaffer2(outdim) def test_threehump_dim_fail(outdim): """Test threehump dim exception""" with pytest.raises(IndexError): - fx.threehump_func(outdim) + fx.threehump(outdim) diff --git a/tests/utils/functions/test_singleobj_return.py b/tests/utils/functions/test_singleobj_return.py index 5c6fbb36..a7a1e705 100644 --- a/tests/utils/functions/test_singleobj_return.py +++ b/tests/utils/functions/test_singleobj_return.py @@ -1,157 +1,151 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# Import standard library +from collections import namedtuple + # Import modules -import pytest import numpy as np -from collections import namedtuple +import pytest -# Import from package +# Import from pyswarms from pyswarms.utils.functions import single_obj as fx - - - def test_ackley_output(common_minima): """Tests ackley function output.""" - assert np.isclose(fx.ackley_func(common_minima), np.zeros(3)).all() + assert np.isclose(fx.ackley(common_minima), 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([3, 0.5] * common_minima2), np.zeros(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([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([-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]]) + 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]) - ], + "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() + assert np.isclose(fx.crossintray(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)) + fx.easom([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)) + fx.eggholder([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)) + fx.goldstein([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]]) + 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() + assert np.isclose(fx.himmelblau(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]]) + 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]) - ], + "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() + assert np.isclose(fx.holdertable(x), minima).all() def test_levi_output(common_minima2): """Test levi function output.""" - assert np.isclose(fx.levi_func(common_minima2), np.zeros(3)).all() + assert np.isclose(fx.levi(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() + assert np.isclose(fx.matyas(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)) + assert np.array_equal(fx.rastrigin(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() + fx.rosenbrock(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() + assert np.isclose(fx.schaffer2(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,))) + assert np.array_equal(fx.sphere(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)) + assert np.array_equal(fx.threehump(common_minima), np.zeros(3)) diff --git a/tests/utils/functions/test_singleobj_returndims.py b/tests/utils/functions/test_singleobj_returndims.py index 2e40a2e4..1704d4c4 100644 --- a/tests/utils/functions/test_singleobj_returndims.py +++ b/tests/utils/functions/test_singleobj_returndims.py @@ -1,90 +1,92 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# Import standard library +from collections import namedtuple + # Import modules -import pytest import numpy as np -from collections import namedtuple +import pytest -# Import from package +# Import from pyswarms from pyswarms.utils.functions import single_obj as fx def test_ackley_output_size(common_minima, targetdim): """Tests ackley output size.""" - assert fx.ackley_func(common_minima).shape == targetdim + assert fx.ackley(common_minima).shape == targetdim def test_beale_output_size(common_minima, targetdim): """Tests beale output size.""" - assert fx.beale_func(common_minima).shape == targetdim + assert fx.beale(common_minima).shape == targetdim def test_booth_output_size(common_minima, targetdim): """Test booth output size.""" - assert fx.booth_func(common_minima).shape == targetdim + assert fx.booth(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([-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 + assert fx.crossintray([-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 + assert fx.easom([-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 + assert fx.eggholder([-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 + assert fx.goldstein(common_minima).shape == targetdim def test_himmelblau_output_size(common_minima, targetdim): """Test himmelblau output size.""" - assert fx.himmelblau_func(common_minima).shape == targetdim + assert fx.himmelblau(common_minima).shape == targetdim def test_holdertable_output_size(common_minima, targetdim): """Test holdertable output size.""" - assert fx.holdertable_func(common_minima).shape == targetdim + assert fx.holdertable(common_minima).shape == targetdim def test_levi_output_size(common_minima, targetdim): """Test levi output size.""" - assert fx.levi_func(common_minima).shape == targetdim + assert fx.levi(common_minima).shape == targetdim def test_rastrigin_output_size(common_minima, targetdim): """Tests rastrigin output size.""" - assert fx.rastrigin_func(common_minima).shape == targetdim + assert fx.rastrigin(common_minima).shape == targetdim def test_rosenbrock_output_size(common_minima, targetdim): """Tests rosenbrock output size.""" - assert fx.rosenbrock_func(common_minima).shape == targetdim + assert fx.rosenbrock(common_minima).shape == targetdim def test_schaffer2_output_size(common_minima, targetdim): """Test schaffer2 output size.""" - assert fx.schaffer2_func(common_minima).shape == targetdim + assert fx.schaffer2(common_minima).shape == targetdim def test_sphere_output_size(common_minima, targetdim): """Tests sphere output size.""" - assert fx.sphere_func(common_minima).shape == targetdim + assert fx.sphere(common_minima).shape == targetdim def test_threehump_output_size(common_minima, targetdim): """Test threehump output size.""" - assert fx.threehump_func(common_minima).shape == targetdim + assert fx.threehump(common_minima).shape == targetdim diff --git a/tests/utils/plotters/conftest.py b/tests/utils/plotters/conftest.py index 6b8da6e4..b5289848 100644 --- a/tests/utils/plotters/conftest.py +++ b/tests/utils/plotters/conftest.py @@ -1,21 +1,25 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -"""Fixtures for tests""" +"""Fixtures for tests -# Import modules +isort:skip_file +""" + +# Import standard library import os -import pytest -import numpy as np -from mock import Mock + +# Import modules import matplotlib as mpl +import numpy as np +import pytest if os.environ.get("DISPLAY", "") == "": mpl.use("Agg") -# Import from package +# Import from pyswarms from pyswarms.single import GlobalBestPSO -from pyswarms.utils.functions.single_obj import sphere_func +from pyswarms.utils.functions.single_obj import sphere from pyswarms.utils.plotters.formatters import Mesher @@ -24,7 +28,7 @@ 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) + optimizer.optimize(sphere, iters=100) return optimizer @@ -37,4 +41,4 @@ def pos_history(): @pytest.fixture def mesher(): """A Mesher instance with sphere function and delta=0.1""" - return Mesher(func=sphere_func, delta=0.1) + return Mesher(func=sphere, delta=0.1) diff --git a/tests/utils/plotters/test_plotters.py b/tests/utils/plotters/test_plotters.py index 668129d4..cde9249e 100644 --- a/tests/utils/plotters/test_plotters.py +++ b/tests/utils/plotters/test_plotters.py @@ -1,26 +1,28 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Import modules +# Import from standard library import os + +# Import modules import pytest import matplotlib as mpl +# Set $DISPLAY environmental variable if os.environ.get("DISPLAY", "") == "": + print("No display found. Using non-interactive Agg backend.") mpl.use("Agg") -from matplotlib.axes._subplots import SubplotBase from matplotlib.animation import FuncAnimation +from matplotlib.axes._subplots import SubplotBase -# Import from package +# Import from pyswarms from pyswarms.utils.plotters import ( - plot_cost_history, plot_contour, + plot_cost_history, plot_surface, ) - -from pyswarms.utils.plotters.plotters import _mesh, _animate -from pyswarms.utils.plotters.formatters import Mesher +from pyswarms.utils.plotters.plotters import _animate, _mesh @pytest.mark.parametrize( diff --git a/tests/utils/search/conftest.py b/tests/utils/search/conftest.py index 8ef81ce9..478a4aa2 100644 --- a/tests/utils/search/conftest.py +++ b/tests/utils/search/conftest.py @@ -4,14 +4,16 @@ """Fixtures for tests""" # Import modules -import pytest import numpy as np +import pytest + +# Import from pyswarms +from pyswarms.single import LocalBestPSO +from pyswarms.utils.functions.single_obj import sphere # Import from package from pyswarms.utils.search.grid_search import GridSearch from pyswarms.utils.search.random_search import RandomSearch -from pyswarms.single import LocalBestPSO -from pyswarms.utils.functions.single_obj import sphere_func @pytest.fixture @@ -29,7 +31,7 @@ def grid(): n_particles=40, dimensions=20, options=options, - objective_func=sphere_func, + objective_func=sphere, iters=10, bounds=None, ) @@ -44,7 +46,7 @@ def grid_mini(): n_particles=40, dimensions=20, options=options, - objective_func=sphere_func, + objective_func=sphere, iters=10, bounds=None, ) @@ -65,7 +67,7 @@ def random_unbounded(): n_particles=40, dimensions=20, options=options, - objective_func=sphere_func, + objective_func=sphere, iters=10, n_selection_iters=100, bounds=None, @@ -88,7 +90,7 @@ def random_bounded(): n_particles=40, dimensions=20, options=options, - objective_func=sphere_func, + objective_func=sphere, iters=10, n_selection_iters=100, bounds=bounds, diff --git a/tox.ini b/tox.ini index 43c48ae5..582a5851 100644 --- a/tox.ini +++ b/tox.ini @@ -1,21 +1,20 @@ [tox] -envlist = py34, py35, py36, flake8 +envlist = py35, py36, flake8 [travis] python = - 3.6: py36 + 3.6: py36 3.5: py35 - 3.4: py34 [testenv:flake8] basepython=python -deps=flake8 -commands=flake8 pyswarms +deps=-rrequirements-dev.txt +commands=python -m flake8 pyswarms [testenv] setenv = PYTHONPATH = {toxinidir} - +deps=-rrequirements-dev.txt commands = python -m pytest -v ; If you want to make tox run the tests with the same versions, create a diff --git a/travis_pypi_setup.py b/travis_pypi_setup.py index 844491f9..d972d826 100644 --- a/travis_pypi_setup.py +++ b/travis_pypi_setup.py @@ -4,15 +4,18 @@ from __future__ import print_function + +# Import standard library import base64 import json import os from getpass import getpass + +# Import modules import yaml -from cryptography.hazmat.primitives.serialization import load_pem_public_key from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 - +from cryptography.hazmat.primitives.serialization import load_pem_public_key try: from urllib import urlopen @@ -20,9 +23,10 @@ from urllib.request import urlopen -GITHUB_REPO = 'ljvmiranda921/pyswarms' +GITHUB_REPO = "ljvmiranda921/pyswarms" TRAVIS_CONFIG_FILE = os.path.join( - os.path.dirname(os.path.abspath(__file__)), '.travis.yml') + os.path.dirname(os.path.abspath(__file__)), ".travis.yml" +) def load_key(pubkey): @@ -37,7 +41,7 @@ def load_key(pubkey): return load_pem_public_key(pubkey.encode(), default_backend()) except ValueError: # workaround for https://github.com/travis-ci/travis-api/issues/196 - pubkey = pubkey.replace('BEGIN RSA', 'BEGIN').replace('END RSA', 'END') + pubkey = pubkey.replace("BEGIN RSA", "BEGIN").replace("END RSA", "END") return load_pem_public_key(pubkey.encode(), default_backend()) @@ -57,13 +61,13 @@ def fetch_public_key(repo): Travis API docs: http://docs.travis-ci.com/api/#repository-keys """ - keyurl = 'https://api.travis-ci.org/repos/{0}/key'.format(repo) + keyurl = "https://api.travis-ci.org/repos/{0}/key".format(repo) data = json.loads(urlopen(keyurl).read().decode()) - if 'key' not in data: + if "key" not in data: errmsg = "Could not find public key for repo: {}.\n".format(repo) errmsg += "Have you already added your GitHub repo to Travis?" raise ValueError(errmsg) - return data['key'] + return data["key"] def prepend_line(filepath, line): @@ -73,7 +77,7 @@ def prepend_line(filepath, line): lines.insert(0, line) - with open(filepath, 'w') as f: + with open(filepath, "w") as f: f.writelines(lines) @@ -85,7 +89,7 @@ def load_yaml_config(filepath): def save_yaml_config(filepath, config): """Save yaml config file at the given path.""" - with open(filepath, 'w') as f: + with open(filepath, "w") as f: yaml.dump(config, f, default_flow_style=False) @@ -93,12 +97,14 @@ def update_travis_deploy_password(encrypted_password): """Put `encrypted_password` into the deploy section of .travis.yml.""" config = load_yaml_config(TRAVIS_CONFIG_FILE) - config['deploy']['password'] = dict(secure=encrypted_password) + config["deploy"]["password"] = dict(secure=encrypted_password) save_yaml_config(TRAVIS_CONFIG_FILE, config) - line = ('# This file was autogenerated and will overwrite' - ' each time you run travis_pypi_setup.py\n') + line = ( + "# This file was autogenerated and will overwrite" + " each time you run travis_pypi_setup.py\n" + ) prepend_line(TRAVIS_CONFIG_FILE, line) @@ -110,18 +116,23 @@ def main(args): password. """ public_key = fetch_public_key(args.repo) - password = args.password or getpass('PyPI password: ') + password = args.password or getpass("PyPI password: ") update_travis_deploy_password(encrypt(public_key, password.encode())) print("Wrote encrypted password to .travis.yml -- you're ready to deploy") -if '__main__' == __name__: +if "__main__" == __name__: import argparse + parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--repo', default=GITHUB_REPO, - help='GitHub repo (default: %s)' % GITHUB_REPO) - parser.add_argument('--password', - help='PyPI password (will prompt if not provided)') + parser.add_argument( + "--repo", + default=GITHUB_REPO, + help="GitHub repo (default: %s)" % GITHUB_REPO, + ) + parser.add_argument( + "--password", help="PyPI password (will prompt if not provided)" + ) args = parser.parse_args() main(args)