├── .github └── workflows │ └── python-publish.yml ├── .gitignore ├── .readthedocs.yml ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── conf.py ├── index.rst ├── make.bat ├── ode_example │ ├── example_rob.png │ ├── example_rob_no_noise.png │ ├── fit_after_pso.png │ ├── fit_before_pso.png │ └── ode_example.rst ├── requirements.txt └── training_earm.gif ├── examples ├── __init__.py ├── data │ └── EC-RP_IMS-RP_IC-RP_data_for_models.csv ├── example_earm.py ├── robertson_example.py └── run_schogl_example_ssa.py ├── requirements.txt ├── setup.cfg ├── setup.py └── simplepso ├── __init__.py ├── logging.py ├── pso.py └── tests ├── __init__.py ├── test_pso_construction.py └── test_robertson.py /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | jobs: 16 | deploy: 17 | environment: release 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: '3.x' 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install setuptools wheel twine 30 | pip install -r requirements.txt 31 | - name: Build and publish 32 | env: 33 | TWINE_USERNAME: __token__ 34 | TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} 35 | run: | 36 | python setup.py sdist bdist_wheel 37 | twine upload --repository pypi dist/* 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python template 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | *.pyc 7 | .idea 8 | # C extensions 9 | *.so 10 | docs/_build 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask instance folder 59 | instance/ 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # IPython Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # dotenv 80 | .env 81 | 82 | # virtualenv 83 | venv/ 84 | ENV/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | 92 | # Created by .ignore support plugin (hsz.mobi) 93 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/conf.py 11 | 12 | # Build documentation with MkDocs 13 | #mkdocs: 14 | # configuration: mkdocs.yml 15 | 16 | # Optionally build your docs in additional formats such as PDF 17 | formats: 18 | - pdf 19 | 20 | # Optionally set the version of Python and requirements required to build your docs 21 | python: 22 | version: 3.7 23 | install: 24 | - requirements: docs/requirements.txt -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, SimplePSO Developers. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in 13 | the documentation and/or other materials provided with the 14 | distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 30 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 31 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include examples/data/* 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![DOI](https://zenodo.org/badge/20464524.svg)](https://zenodo.org/badge/latestdoi/20464524) 2 | [![Documentation Status](https://readthedocs.org/projects/simplepso/badge/?version=latest)](https://simplepso.readthedocs.io/en/latest/?badge=latest) 3 | # Simple interface to optimize models using Particle Swarm optimization 4 | 5 | Training stochastic or determinisic models encoded in PySB? Looking for an easy to use, effecient Python package? Look no further. 6 | 7 | PySB examples can be found in simplepso/examples 8 | 9 | 10 | 11 | You can email me at james.ch.pino@gmail.com for any questions or comments. 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help 18 | help: 19 | @echo "Please use \`make ' where is one of" 20 | @echo " html to make standalone HTML files" 21 | @echo " dirhtml to make HTML files named index.html in directories" 22 | @echo " singlehtml to make a single large HTML file" 23 | @echo " pickle to make pickle files" 24 | @echo " json to make JSON files" 25 | @echo " htmlhelp to make HTML files and a HTML help project" 26 | @echo " qthelp to make HTML files and a qthelp project" 27 | @echo " applehelp to make an Apple Help Book" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " epub3 to make an epub3" 31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 32 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 33 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 34 | @echo " text to make text files" 35 | @echo " man to make manual pages" 36 | @echo " texinfo to make Texinfo files" 37 | @echo " info to make Texinfo files and run them through makeinfo" 38 | @echo " gettext to make PO message catalogs" 39 | @echo " changes to make an overview of all changed/added/deprecated items" 40 | @echo " xml to make Docutils-native XML files" 41 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 42 | @echo " linkcheck to check all external links for integrity" 43 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 44 | @echo " coverage to run coverage check of the documentation (if enabled)" 45 | @echo " dummy to check syntax errors of document sources" 46 | 47 | .PHONY: clean 48 | clean: 49 | rm -rf $(BUILDDIR)/* 50 | 51 | .PHONY: html 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | .PHONY: dirhtml 58 | dirhtml: 59 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 60 | @echo 61 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 62 | 63 | .PHONY: singlehtml 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | .PHONY: pickle 70 | pickle: 71 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 72 | @echo 73 | @echo "Build finished; now you can process the pickle files." 74 | 75 | .PHONY: json 76 | json: 77 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 78 | @echo 79 | @echo "Build finished; now you can process the JSON files." 80 | 81 | .PHONY: htmlhelp 82 | htmlhelp: 83 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 84 | @echo 85 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 86 | ".hhp project file in $(BUILDDIR)/htmlhelp." 87 | 88 | .PHONY: qthelp 89 | qthelp: 90 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 91 | @echo 92 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 93 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 94 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/simplepso.qhcp" 95 | @echo "To view the help file:" 96 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/simplepso.qhc" 97 | 98 | .PHONY: applehelp 99 | applehelp: 100 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 101 | @echo 102 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 103 | @echo "N.B. You won't be able to view it unless you put it in" \ 104 | "~/Library/Documentation/Help or install it in your application" \ 105 | "bundle." 106 | 107 | .PHONY: devhelp 108 | devhelp: 109 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 110 | @echo 111 | @echo "Build finished." 112 | @echo "To view the help file:" 113 | @echo "# mkdir -p $$HOME/.local/share/devhelp/simplepso" 114 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/simplepso" 115 | @echo "# devhelp" 116 | 117 | .PHONY: epub 118 | epub: 119 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 120 | @echo 121 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 122 | 123 | .PHONY: epub3 124 | epub3: 125 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 126 | @echo 127 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 128 | 129 | .PHONY: latex 130 | latex: 131 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 132 | @echo 133 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 134 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 135 | "(use \`make latexpdf' here to do that automatically)." 136 | 137 | .PHONY: latexpdf 138 | latexpdf: 139 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 140 | @echo "Running LaTeX files through pdflatex..." 141 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 142 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 143 | 144 | .PHONY: latexpdfja 145 | latexpdfja: 146 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 147 | @echo "Running LaTeX files through platex and dvipdfmx..." 148 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 149 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 150 | 151 | .PHONY: text 152 | text: 153 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 154 | @echo 155 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 156 | 157 | .PHONY: man 158 | man: 159 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 160 | @echo 161 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 162 | 163 | .PHONY: texinfo 164 | texinfo: 165 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 166 | @echo 167 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 168 | @echo "Run \`make' in that directory to run these through makeinfo" \ 169 | "(use \`make info' here to do that automatically)." 170 | 171 | .PHONY: info 172 | info: 173 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 174 | @echo "Running Texinfo files through makeinfo..." 175 | make -C $(BUILDDIR)/texinfo info 176 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 177 | 178 | .PHONY: gettext 179 | gettext: 180 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 181 | @echo 182 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 183 | 184 | .PHONY: changes 185 | changes: 186 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 187 | @echo 188 | @echo "The overview file is in $(BUILDDIR)/changes." 189 | 190 | .PHONY: linkcheck 191 | linkcheck: 192 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 193 | @echo 194 | @echo "Link check complete; look for any errors in the above output " \ 195 | "or in $(BUILDDIR)/linkcheck/output.txt." 196 | 197 | .PHONY: doctest 198 | doctest: 199 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 200 | @echo "Testing of doctests in the sources finished, look at the " \ 201 | "results in $(BUILDDIR)/doctest/output.txt." 202 | 203 | .PHONY: coverage 204 | coverage: 205 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 206 | @echo "Testing of coverage in the sources finished, look at the " \ 207 | "results in $(BUILDDIR)/coverage/python.txt." 208 | 209 | .PHONY: xml 210 | xml: 211 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 212 | @echo 213 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 214 | 215 | .PHONY: pseudoxml 216 | pseudoxml: 217 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 218 | @echo 219 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 220 | 221 | .PHONY: dummy 222 | dummy: 223 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 224 | @echo 225 | @echo "Build finished. Dummy builder generates no files." 226 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # simplepso documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Jun 2 15:57:36 2016. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | import os 20 | import sys 21 | 22 | import sphinx_bootstrap_theme 23 | 24 | sys.path.append(os.path.abspath('..')) 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | needs_sphinx = '3.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | 'sphinx.ext.autodoc', 36 | 'sphinx.ext.doctest', 37 | 'sphinx.ext.coverage', 38 | 'sphinx.ext.imgmath', 39 | 'sphinx.ext.ifconfig', 40 | 'sphinx.ext.viewcode', 41 | 'sphinx.ext.autosummary', 42 | 'sphinx.ext.todo', 43 | 'sphinx.ext.inheritance_diagram', 44 | 45 | 'numpydoc', 46 | 47 | ] 48 | autodoc_default_options = { 49 | 'members': True, 50 | 'member-order': 'groupwise', 51 | 'undoc-members': True, 52 | 'special-members': '__init__', 53 | } 54 | # Napoleon settings 55 | napoleon_google_docstring = False 56 | napoleon_numpy_docstring = True 57 | napoleon_include_init_with_doc = False 58 | napoleon_include_private_with_doc = False 59 | napoleon_include_special_with_doc = False 60 | napoleon_use_admonition_for_examples = False 61 | napoleon_use_admonition_for_notes = False 62 | napoleon_use_admonition_for_references = False 63 | napoleon_use_ivar = False 64 | napoleon_use_param = True 65 | napoleon_use_rtype = True 66 | napoleon_use_keyword = True 67 | 68 | numpydoc_show_class_members = False 69 | # Add any paths that contain templates here, relative to this directory. 70 | templates_path = ['_templates'] 71 | 72 | # The suffix(es) of source filenames. 73 | # You can specify multiple suffix as a list of string: 74 | # 75 | # source_suffix = ['.rst', '.md'] 76 | source_suffix = '.rst' 77 | 78 | # The encoding of source files. 79 | # 80 | # source_encoding = 'utf-8-sig' 81 | 82 | # The master toctree document. 83 | master_doc = 'index' 84 | 85 | # General information about the project. 86 | project = u'simplepso' 87 | copyright = u'2016, James C. Pino' 88 | author = u'James C. Pino' 89 | 90 | # The version info for the project you're documenting, acts as replacement for 91 | # |version| and |release|, also used in various other places throughout the 92 | # built documents. 93 | # 94 | # The short X.Y version. 95 | version = '1.1.5' 96 | # The full version, including alpha/beta/rc tags. 97 | release = '1.1.5' 98 | 99 | # The language for content autogenerated by Sphinx. Refer to documentation 100 | # for a list of supported languages. 101 | # 102 | # This is also used if you do content translation via gettext catalogs. 103 | # Usually you set "language" from the command line for these cases. 104 | language = None 105 | 106 | # There are two options for replacing |today|: either, you set today to some 107 | # non-false value, then it is used: 108 | # 109 | # today = '' 110 | # 111 | # Else, today_fmt is used as the format for a strftime call. 112 | # 113 | # today_fmt = '%B %d, %Y' 114 | 115 | # List of patterns, relative to source directory, that match files and 116 | # directories to ignore when looking for source files. 117 | # This patterns also effect to html_static_path and html_extra_path 118 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 119 | 120 | # The reST default role (used for this markup: `text`) to use for all 121 | # documents. 122 | # 123 | # default_role = None 124 | 125 | # If true, '()' will be appended to :func: etc. cross-reference text. 126 | # 127 | # add_function_parentheses = True 128 | 129 | # If true, the current module name will be prepended to all description 130 | # unit titles (such as .. function::). 131 | # 132 | # add_module_names = True 133 | 134 | # If true, sectionauthor and moduleauthor directives will be shown in the 135 | # output. They are ignored by default. 136 | # 137 | # show_authors = False 138 | 139 | # The name of the Pygments (syntax highlighting) style to use. 140 | pygments_style = 'sphinx' 141 | 142 | # A list of ignored prefixes for module index sorting. 143 | # modindex_common_prefix = [] 144 | 145 | # If true, keep warnings as "system message" paragraphs in the built documents. 146 | # keep_warnings = False 147 | 148 | # If true, `todo` and `todoList` produce output, else they produce nothing. 149 | todo_include_todos = True 150 | 151 | # -- Options for HTML output ---------------------------------------------- 152 | 153 | # The theme to use for HTML and HTML Help pages. See the documentation for 154 | # a list of builtin themes. 155 | # 156 | html_theme = 'default' 157 | html_theme_path = sphinx_bootstrap_theme.get_html_theme_path() 158 | # Theme options are theme-specific and customize the look and feel of a theme 159 | # further. For a list of options available for each theme, see the 160 | # documentation. 161 | # 162 | # html_theme_options = {} 163 | 164 | # Add any paths that contain custom themes here, relative to this directory. 165 | # html_theme_path = [] 166 | 167 | # The name for this set of Sphinx documents. 168 | # " v documentation" by default. 169 | # 170 | # html_title = u'simplepso v0.0.1' 171 | 172 | # A shorter title for the navigation bar. Default is the same as html_title. 173 | # 174 | # html_short_title = None 175 | 176 | # The name of an image file (relative to this directory) to place at the top 177 | # of the sidebar. 178 | # 179 | # html_logo = None 180 | 181 | # The name of an image file (relative to this directory) to use as a favicon of 182 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 183 | # pixels large. 184 | # 185 | # html_favicon = None 186 | 187 | # Add any paths that contain custom static files (such as style sheets) here, 188 | # relative to this directory. They are copied after the builtin static files, 189 | # so a file named "default.css" will overwrite the builtin "default.css". 190 | 191 | # Add any extra paths that contain custom files (such as robots.txt or 192 | # .htaccess) here, relative to this directory. These files are copied 193 | # directly to the root of the documentation. 194 | # 195 | # html_extra_path = [] 196 | 197 | # If not None, a 'Last updated on:' timestamp is inserted at every page 198 | # bottom, using the given strftime format. 199 | # The empty string is equivalent to '%b %d, %Y'. 200 | # 201 | # html_last_updated_fmt = None 202 | 203 | # If true, SmartyPants will be used to convert quotes and dashes to 204 | # typographically correct entities. 205 | # 206 | # html_use_smartypants = True 207 | 208 | # Custom sidebar templates, maps document names to template names. 209 | # 210 | # html_sidebars = {} 211 | 212 | # Additional templates that should be rendered to pages, maps page names to 213 | # template names. 214 | # 215 | # html_additional_pages = {} 216 | 217 | # If false, no module index is generated. 218 | # 219 | # html_domain_indices = True 220 | 221 | # If false, no index is generated. 222 | # 223 | # html_use_index = True 224 | 225 | # If true, the index is split into individual pages for each letter. 226 | # 227 | # html_split_index = False 228 | 229 | # If true, links to the reST sources are added to the pages. 230 | # 231 | # html_show_sourcelink = True 232 | 233 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 234 | # 235 | # html_show_sphinx = True 236 | 237 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 238 | # 239 | # html_show_copyright = True 240 | 241 | # If true, an OpenSearch description file will be output, and all pages will 242 | # contain a tag referring to it. The value of this option must be the 243 | # base URL from which the finished HTML is served. 244 | # 245 | # html_use_opensearch = '' 246 | 247 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 248 | # html_file_suffix = None 249 | 250 | # Language to be used for generating the HTML full-text search index. 251 | # Sphinx supports the following languages: 252 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 253 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' 254 | # 255 | # html_search_language = 'en' 256 | 257 | # A dictionary with options for the search language support, empty by default. 258 | # 'ja' uses this config value. 259 | # 'zh' user can custom change `jieba` dictionary path. 260 | # 261 | # html_search_options = {'type': 'default'} 262 | 263 | # The name of a javascript file (relative to the configuration directory) that 264 | # implements a search results scorer. If empty, the default will be used. 265 | # 266 | # html_search_scorer = 'scorer.js' 267 | 268 | # Output file base name for HTML help builder. 269 | htmlhelp_basename = 'simplepsodoc' 270 | 271 | # -- Options for LaTeX output --------------------------------------------- 272 | 273 | latex_elements = { 274 | # The paper size ('letterpaper' or 'a4paper'). 275 | # 276 | # 'papersize': 'letterpaper', 277 | 278 | # The font size ('10pt', '11pt' or '12pt'). 279 | # 280 | # 'pointsize': '10pt', 281 | 282 | # Additional stuff for the LaTeX preamble. 283 | # 284 | # 'preamble': '', 285 | 286 | # Latex figure (float) alignment 287 | # 288 | # 'figure_align': 'htbp', 289 | } 290 | 291 | # Grouping the document tree into LaTeX files. List of tuples 292 | # (source start file, target name, title, 293 | # author, documentclass [howto, manual, or own class]). 294 | latex_documents = [ 295 | (master_doc, 'simplepso.tex', u'simplepso Documentation', 296 | u'James C. Pino', 'manual'), 297 | ] 298 | 299 | # The name of an image file (relative to this directory) to place at the top of 300 | # the title page. 301 | # 302 | # latex_logo = None 303 | 304 | # For "manual" documents, if this is true, then toplevel headings are parts, 305 | # not chapters. 306 | # 307 | # latex_use_parts = False 308 | 309 | # If true, show page references after internal links. 310 | # 311 | # latex_show_pagerefs = False 312 | 313 | # If true, show URL addresses after external links. 314 | # 315 | # latex_show_urls = False 316 | 317 | # Documents to append as an appendix to all manuals. 318 | # 319 | # latex_appendices = [] 320 | 321 | # If false, no module index is generated. 322 | # 323 | # latex_domain_indices = True 324 | 325 | 326 | # -- Options for manual page output --------------------------------------- 327 | 328 | # One entry per manual page. List of tuples 329 | # (source start file, name, description, authors, manual section). 330 | man_pages = [ 331 | (master_doc, 'simplepso', u'simplepso Documentation', 332 | [author], 1) 333 | ] 334 | 335 | # If true, show URL addresses after external links. 336 | # 337 | # man_show_urls = False 338 | 339 | 340 | # -- Options for Texinfo output ------------------------------------------- 341 | 342 | # Grouping the document tree into Texinfo files. List of tuples 343 | # (source start file, target name, title, author, 344 | # dir menu entry, description, category) 345 | texinfo_documents = [ 346 | (master_doc, 'simplepso', u'simplepso Documentation', 347 | author, 'simplepso', 'One line description of project.', 348 | 'Miscellaneous'), 349 | ] 350 | 351 | # Documents to append as an appendix to all manuals. 352 | # 353 | # texinfo_appendices = [] 354 | 355 | # If false, no module index is generated. 356 | # 357 | # texinfo_domain_indices = True 358 | 359 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 360 | # 361 | # texinfo_show_urls = 'footnote' 362 | 363 | # If true, do not generate a @detailmenu in the "Top" node's menu. 364 | # 365 | # texinfo_no_detailmenu = False 366 | 367 | 368 | # Example configuration for intersphinx: refer to the Python standard library. 369 | intersphinx_mapping = {'https://docs.python.org/': None} 370 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to simplepso's documentation! 2 | ===================================== 3 | 4 | simplePSO is a full implementation of the PSO algorithm. It requires an 5 | objective function to minimize that returns a scalar value, starting position, 6 | bounds on how far you want to search around the starting position, and max 7 | speeds each particle can travel. Then, you set the number of particles in the 8 | swarm, the number of iterations to run, and the number of processors you want 9 | to use. That's it! 10 | 11 | .. code-block:: python 12 | 13 | # Here we initial the class 14 | # We must provide the cost function and a starting value 15 | optimizer = PSO(start=start_position) 16 | 17 | # We also must set bounds of the parameter space 18 | optimizer.set_bounds(parameter_range) 19 | # set speed limits on updating particles 20 | optimizer.set_speed(speed_min, speed_max) 21 | 22 | # Now we run the pso algorithm 23 | optimizer.run(num_particles, num_iterations, num_processors, 24 | cost_function) 25 | # get best parameter fit 26 | best = optimizer.best 27 | # parameter set 28 | best.pos 29 | # fitness of parameter set 30 | best.fitness 31 | 32 | We provide two examples in the examples 33 | `folder `_ training 34 | PySB models to time course data using ODE simulations and 35 | `one `_ 36 | example training a model to a distribution of species concentrations using 37 | stochastic simulations. 38 | 39 | .. image:: training_earm.gif 40 | :width: 400 41 | 42 | .. toctree:: 43 | :maxdepth: 2 44 | 45 | ode_example/ode_example.rst 46 | 47 | .. autoclass:: simplepso.pso.PSO 48 | :members: 49 | 50 | 51 | Indices and tables 52 | ================== 53 | 54 | * :ref:`genindex` 55 | * :ref:`modindex` 56 | * :ref:`search` 57 | 58 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | echo. dummy to check syntax errors of document sources 43 | goto end 44 | ) 45 | 46 | if "%1" == "clean" ( 47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 48 | del /q /s %BUILDDIR%\* 49 | goto end 50 | ) 51 | 52 | 53 | REM Check if sphinx-build is available and fallback to Python version if any 54 | %SPHINXBUILD% 1>NUL 2>NUL 55 | if errorlevel 9009 goto sphinx_python 56 | goto sphinx_ok 57 | 58 | :sphinx_python 59 | 60 | set SPHINXBUILD=python -m sphinx.__init__ 61 | %SPHINXBUILD% 2> nul 62 | if errorlevel 9009 ( 63 | echo. 64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 65 | echo.installed, then set the SPHINXBUILD environment variable to point 66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 67 | echo.may add the Sphinx directory to PATH. 68 | echo. 69 | echo.If you don't have Sphinx installed, grab it from 70 | echo.http://sphinx-doc.org/ 71 | exit /b 1 72 | ) 73 | 74 | :sphinx_ok 75 | 76 | 77 | if "%1" == "html" ( 78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 79 | if errorlevel 1 exit /b 1 80 | echo. 81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 82 | goto end 83 | ) 84 | 85 | if "%1" == "dirhtml" ( 86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 87 | if errorlevel 1 exit /b 1 88 | echo. 89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 90 | goto end 91 | ) 92 | 93 | if "%1" == "singlehtml" ( 94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 95 | if errorlevel 1 exit /b 1 96 | echo. 97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 98 | goto end 99 | ) 100 | 101 | if "%1" == "pickle" ( 102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 103 | if errorlevel 1 exit /b 1 104 | echo. 105 | echo.Build finished; now you can process the pickle files. 106 | goto end 107 | ) 108 | 109 | if "%1" == "json" ( 110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 111 | if errorlevel 1 exit /b 1 112 | echo. 113 | echo.Build finished; now you can process the JSON files. 114 | goto end 115 | ) 116 | 117 | if "%1" == "htmlhelp" ( 118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 119 | if errorlevel 1 exit /b 1 120 | echo. 121 | echo.Build finished; now you can run HTML Help Workshop with the ^ 122 | .hhp project file in %BUILDDIR%/htmlhelp. 123 | goto end 124 | ) 125 | 126 | if "%1" == "qthelp" ( 127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 128 | if errorlevel 1 exit /b 1 129 | echo. 130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 131 | .qhcp project file in %BUILDDIR%/qthelp, like this: 132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\simplepso.qhcp 133 | echo.To view the help file: 134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\simplepso.ghc 135 | goto end 136 | ) 137 | 138 | if "%1" == "devhelp" ( 139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 140 | if errorlevel 1 exit /b 1 141 | echo. 142 | echo.Build finished. 143 | goto end 144 | ) 145 | 146 | if "%1" == "epub" ( 147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 148 | if errorlevel 1 exit /b 1 149 | echo. 150 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 151 | goto end 152 | ) 153 | 154 | if "%1" == "epub3" ( 155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 156 | if errorlevel 1 exit /b 1 157 | echo. 158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 159 | goto end 160 | ) 161 | 162 | if "%1" == "latex" ( 163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 164 | if errorlevel 1 exit /b 1 165 | echo. 166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdf" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "latexpdfja" ( 181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 182 | cd %BUILDDIR%/latex 183 | make all-pdf-ja 184 | cd %~dp0 185 | echo. 186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 187 | goto end 188 | ) 189 | 190 | if "%1" == "text" ( 191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 192 | if errorlevel 1 exit /b 1 193 | echo. 194 | echo.Build finished. The text files are in %BUILDDIR%/text. 195 | goto end 196 | ) 197 | 198 | if "%1" == "man" ( 199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 200 | if errorlevel 1 exit /b 1 201 | echo. 202 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 203 | goto end 204 | ) 205 | 206 | if "%1" == "texinfo" ( 207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 208 | if errorlevel 1 exit /b 1 209 | echo. 210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 211 | goto end 212 | ) 213 | 214 | if "%1" == "gettext" ( 215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 216 | if errorlevel 1 exit /b 1 217 | echo. 218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 219 | goto end 220 | ) 221 | 222 | if "%1" == "changes" ( 223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 224 | if errorlevel 1 exit /b 1 225 | echo. 226 | echo.The overview file is in %BUILDDIR%/changes. 227 | goto end 228 | ) 229 | 230 | if "%1" == "linkcheck" ( 231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 232 | if errorlevel 1 exit /b 1 233 | echo. 234 | echo.Link check complete; look for any errors in the above output ^ 235 | or in %BUILDDIR%/linkcheck/output.txt. 236 | goto end 237 | ) 238 | 239 | if "%1" == "doctest" ( 240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 241 | if errorlevel 1 exit /b 1 242 | echo. 243 | echo.Testing of doctests in the sources finished, look at the ^ 244 | results in %BUILDDIR%/doctest/output.txt. 245 | goto end 246 | ) 247 | 248 | if "%1" == "coverage" ( 249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 250 | if errorlevel 1 exit /b 1 251 | echo. 252 | echo.Testing of coverage in the sources finished, look at the ^ 253 | results in %BUILDDIR%/coverage/python.txt. 254 | goto end 255 | ) 256 | 257 | if "%1" == "xml" ( 258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 259 | if errorlevel 1 exit /b 1 260 | echo. 261 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 262 | goto end 263 | ) 264 | 265 | if "%1" == "pseudoxml" ( 266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 267 | if errorlevel 1 exit /b 1 268 | echo. 269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 270 | goto end 271 | ) 272 | 273 | if "%1" == "dummy" ( 274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy 275 | if errorlevel 1 exit /b 1 276 | echo. 277 | echo.Build finished. Dummy builder generates no files. 278 | goto end 279 | ) 280 | 281 | :end 282 | -------------------------------------------------------------------------------- /docs/ode_example/example_rob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoLab-MSM/simplePSO/37e5eb22da920d30780984dcea327686d6eacbfe/docs/ode_example/example_rob.png -------------------------------------------------------------------------------- /docs/ode_example/example_rob_no_noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoLab-MSM/simplePSO/37e5eb22da920d30780984dcea327686d6eacbfe/docs/ode_example/example_rob_no_noise.png -------------------------------------------------------------------------------- /docs/ode_example/fit_after_pso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoLab-MSM/simplePSO/37e5eb22da920d30780984dcea327686d6eacbfe/docs/ode_example/fit_after_pso.png -------------------------------------------------------------------------------- /docs/ode_example/fit_before_pso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoLab-MSM/simplePSO/37e5eb22da920d30780984dcea327686d6eacbfe/docs/ode_example/fit_before_pso.png -------------------------------------------------------------------------------- /docs/ode_example/ode_example.rst: -------------------------------------------------------------------------------- 1 | Example using PySB with ODE simulations 2 | ======================================= 3 | 4 | 5 | 6 | .. code-block:: python 7 | 8 | import matplotlib.pyplot as plt 9 | import numpy as np 10 | 11 | from pysb.examples.robertson import model 12 | from pysb.simulator import ScipyOdeSimulator 13 | from simplepso.logging import get_logger 14 | from simplepso.pso import PSO 15 | 16 | For this example, we are going to train the model to some made up data. 17 | Please refer to a PySB tutorial if you are new to PySB. This tutorial assumes 18 | you have knowledge of PySB. 19 | 20 | .. code-block:: python 21 | 22 | # setup model and simulator 23 | t = np.linspace(0, 50, 51) 24 | # observables of the model to train 25 | obs_names = ['A_total', 'C_total'] 26 | 27 | # create pysb simulator instance 28 | solver = ScipyOdeSimulator( 29 | model, 30 | t, 31 | integrator='lsoda', 32 | integrator_options={'rtol': 1e-8, 'atol': 1e-8} 33 | ) 34 | 35 | .. code-block:: python 36 | 37 | # Defining a helper functions to use 38 | def normalize(trajectories): 39 | """Rescale a matrix of model trajectories to 0-1""" 40 | ymin = trajectories.min(0) 41 | ymax = trajectories.max(0) 42 | return (trajectories - ymin) / (ymax - ymin) 43 | 44 | .. code-block:: python 45 | 46 | traj = solver.run() 47 | ysim_array = traj.dataframe[obs_names].values 48 | # normalize the values from 0-1 (see function definition below) 49 | ysim_norm = normalize(ysim_array) 50 | 51 | plt.figure(figsize=(6, 4)) 52 | if title is not None: 53 | plt.title(title) 54 | plt.plot(t, ysim_norm[:, 0], '^r', linestyle='--', label='A') 55 | plt.plot(t, ysim_norm[:, 1], 'ok', linestyle='--', label='C') 56 | plt.ylabel('Normalized concentration') 57 | plt.xlabel('Time (s)') 58 | 59 | .. image:: example_rob_no_noise.png 60 | 61 | For training purposes, we will use the trajectories above and add some random 62 | noise. 63 | 64 | .. code-block:: python 65 | 66 | noise = 0.05 67 | noisy_data_A = norm_data[:, 0] + np.random.uniform(-1 * noise, noise, len(t)) 68 | norm_noisy_data_A = normalize(noisy_data_A) 69 | 70 | noisy_data_C = norm_data[:, 1] + np.random.uniform(-noise, noise, len(t)) 71 | norm_noisy_data_C = normalize(noisy_data_C) 72 | 73 | ydata_norm = np.column_stack((norm_noisy_data_A, norm_noisy_data_C)) 74 | 75 | .. code-block:: python 76 | 77 | plt.figure(figsize=(6, 4)) 78 | plt.plot(t, ysim_norm[:, 0], '^r', linestyle='--', label='A') 79 | plt.plot(t, ysim_norm[:, 1], 'ok', linestyle='--', label='C') 80 | plt.plot(t, norm_noisy_data_A, 'r-', label='Noisy A') 81 | plt.plot(t, norm_noisy_data_C, 'k-', label='Noisy C') 82 | plt.legend(loc=0) 83 | plt.ylabel('Normalized concentration') 84 | plt.xlabel('Time (s)') 85 | 86 | .. image:: example_rob.png 87 | 88 | Next, we define the cost function. The cost function should take a parameter 89 | set as an argument and return a scalar value, where the ultimate goal is to 90 | minimize this value. To efficiently sample across large parameter space, we 91 | use log10 space for the parameters. This means before you 92 | pass them back to the simulator, you must convert them to linear space. 93 | We are also only optimizing the rate parameters from the model, not the 94 | initial conditions, so we must create a mask to identify which parameters in 95 | the model are rate versus initial conditions. 96 | .. code-block:: python 97 | 98 | rate_params = model.parameters_rules() 99 | rate_mask = np.array([p in rate_params for p in model.parameters]) 100 | param_values = np.array([p.value for p in model.parameters]) 101 | log_original_values = np.log10(param_values[rate_mask]) 102 | 103 | 104 | Here we use the chi square metric to determine the distances between the 105 | trajectory of the parameter sets and our training data. 106 | 107 | .. code-block:: python 108 | 109 | def obj_function(params): 110 | # create copy of parameters 111 | params_tmp = np.copy(params) 112 | # convert back into regular base 113 | param_values[rate_mask] = 10 ** params_tmp 114 | traj = solver.run(param_values=param_values) 115 | ysim_array = traj.dataframe[obs_names].values 116 | ysim_norm = normalize(ysim_array) 117 | # chi^2 error 118 | err = np.sum((ydata_norm - ysim_norm) ** 2) 119 | # if there are nans, return a really large number 120 | if np.isnan(err): 121 | return 1000 122 | return err 123 | 124 | 125 | 126 | .. code-block:: python 127 | 128 | # make up a random starting point 129 | start_position = log_original_values + \ 130 | np.random.uniform(-1, 1, 131 | size=len(log_original_values)) 132 | 133 | We can see that these are not ideal. 134 | 135 | .. code-block:: python 136 | 137 | param_values[rate_mask] = 10 ** start_position 138 | traj = solver.run(param_values=param_values) 139 | ysim_array = traj.dataframe[obs_names].values 140 | ysim_norm = normalize(ysim_array) 141 | 142 | plt.figure(figsize=(6, 4)) 143 | plt.plot(t, ysim_norm[:, 0], '^r', linestyle='--', label='A') 144 | plt.plot(t, ysim_norm[:, 1], 'ok', linestyle='--', label='C') 145 | plt.plot(t, norm_noisy_data_A, 'r-', label='Noisy A') 146 | plt.plot(t, norm_noisy_data_C, 'k-', label='Noisy C') 147 | plt.legend(loc=0) 148 | plt.ylabel('Normalized concentration') 149 | plt.xlabel('Time (s)') 150 | 151 | .. image:: fit_before_pso.png 152 | 153 | Finally, we get to initialize and run the PSO class. The main options to 154 | consider when running the algorithm are the `num_particles`, `num_iterations`, 155 | `num_processors`. The `num_particles` should be a multiple of the `num_processors`. 156 | You can set `num_iterations` as large as you'd like, but if you set it to a large 157 | value, you should consider setting the `max_iter_no_improv` or `stop_threshold` 158 | options. 159 | 160 | .. code-block:: python 161 | 162 | # Here we initial the class 163 | # We must provide the cost function and a starting value 164 | optimizer = PSO(start=start_position, verbose=True, shrink_steps=False) 165 | 166 | # We also must set bounds of the parameter space, and the speed PSO will 167 | # travel (max speed in either direction) 168 | optimizer.set_bounds(parameter_range=4) 169 | optimizer.set_speed(speed_min=-.05, speed_max=.05) 170 | 171 | # Now we run the pso algorithm 172 | optimizer.run(num_particles=48, num_iterations=500, num_processors=12, 173 | cost_function=obj_function, max_iter_no_improv=25) 174 | 175 | Done! We can then use `optimizer.best.pos` to access the best fit parameters. 176 | 177 | .. code-block:: python 178 | 179 | best_params = optimizer.best.pos 180 | param_values[rate_mask] = 10 ** best_params 181 | traj = solver.run(param_values=param_values) 182 | ysim_array = traj.dataframe[obs_names].values 183 | ysim_norm = normalize(ysim_array) 184 | 185 | plt.figure(figsize=(6, 4)) 186 | plt.plot(t, ysim_norm[:, 0], '^r', linestyle='--', label='A') 187 | plt.plot(t, ysim_norm[:, 1], 'ok', linestyle='--', label='C') 188 | plt.plot(t, norm_noisy_data_A, 'r-', label='Noisy A') 189 | plt.plot(t, norm_noisy_data_C, 'k-', label='Noisy C') 190 | plt.legend(loc=0) 191 | plt.ylabel('Normalized concentration') 192 | plt.xlabel('Time (s)') 193 | 194 | .. image:: fit_after_pso.png 195 | 196 | 197 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib>=1.5.0 2 | numpy>=1.11.0 3 | scipy>=0.17.1 4 | pandas 5 | numpydoc 6 | sphinx_bootstrap_theme 7 | sphinx==3.1.2 -------------------------------------------------------------------------------- /docs/training_earm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoLab-MSM/simplePSO/37e5eb22da920d30780984dcea327686d6eacbfe/docs/training_earm.gif -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Simple interface for particle swarm optimization 3 | 4 | """ 5 | -------------------------------------------------------------------------------- /examples/data/EC-RP_IMS-RP_IC-RP_data_for_models.csv: -------------------------------------------------------------------------------- 1 | Time,IC-RP,norm_IC-RP,nrm_var_IC-RP,IMS-RP,IMS-RP step,VAR,EC-RP,norm_EC-RP,nrm_var_EC-RP 2 | 180,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4451,0.0020,0.0003 3 | 360,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4460,0.0018,0.0003 4 | 540,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4459,0.0015,0.0003 5 | 720,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4458,0.0051,0.0003 6 | 900,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4474,0.0038,0.0002 7 | 1080,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4468,0.0042,0.0002 8 | 1260,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4470,0.0057,0.0003 9 | 1440,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4477,0.0022,0.0004 10 | 1620,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4461,0.0027,0.0004 11 | 1800,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4463,0.0071,0.0004 12 | 1980,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4483,0.0027,0.0005 13 | 2160,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4463,0.0060,0.0005 14 | 2340,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4478,0.0060,0.0005 15 | 2520,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4478,0.0064,0.0006 16 | 2700,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4480,0.0068,0.0007 17 | 2880,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4482,0.0046,0.0007 18 | 3060,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4472,0.0075,0.0008 19 | 3240,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4485,0.0091,0.0012 20 | 3420,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4492,0.0115,0.0009 21 | 3600,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4503,0.0144,0.0010 22 | 3780,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4516,0.0106,0.0009 23 | 3960,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4499,0.0077,0.0012 24 | 4140,0.7063,0.0000,0.0006,0.0000,0.0000,1.0000,0.4486,0.0110,0.0011 25 | 4320,0.7081,0.0035,0.0042,0.0000,0.0000,1.0000,0.4501,0.0086,0.0012 26 | 4500,0.7151,0.0170,0.0047,0.0000,0.0000,1.0000,0.4490,0.0128,0.0010 27 | 4680,0.7159,0.0186,0.0046,0.0000,0.0000,1.0000,0.4509,0.0177,0.0011 28 | 4860,0.7170,0.0207,0.0053,0.0000,0.0000,1.0000,0.4531,0.0194,0.0014 29 | 5040,0.7223,0.0310,0.0051,0.0000,0.0000,1.0000,0.4539,0.0188,0.0015 30 | 5220,0.7245,0.0352,0.0063,0.0000,0.0000,1.0000,0.4536,0.0168,0.0021 31 | 5400,0.7313,0.0484,0.0078,0.0000,0.0000,1.0000,0.4527,0.0199,0.0019 32 | 5580,0.7347,0.0550,0.0082,0.0000,0.0000,1.0000,0.4541,0.0241,0.0049 33 | 5760,0.7414,0.0680,0.0075,0.0000,0.0000,1.0000,0.4560,0.0230,0.0079 34 | 5940,0.7440,0.0730,0.0067,0.0000,0.0000,1.0000,0.4555,0.0252,0.0097 35 | 6120,0.7446,0.0742,0.0142,0.0000,0.0000,1.0000,0.4565,0.0289,0.0103 36 | 6300,0.7614,0.1067,0.0104,0.0000,0.0000,1.0000,0.4582,0.0309,0.0114 37 | 6480,0.7598,0.1036,0.0094,0.0000,0.0000,1.0000,0.4591,0.0362,0.0093 38 | 6660,0.7677,0.1189,0.0121,0.0000,0.0000,1.0000,0.4615,0.0386,0.0096 39 | 6840,0.7812,0.1450,0.0083,0.0000,0.0000,1.0000,0.4626,0.0391,0.0103 40 | 7020,0.7906,0.1632,0.0064,0.0000,0.0000,1.0000,0.4628,0.0446,0.0090 41 | 7200,0.7877,0.1576,0.0056,0.0000,0.0000,1.0000,0.4653,0.0519,0.0084 42 | 7380,0.7982,0.1779,0.0054,0.0000,0.0000,1.0000,0.4686,0.0541,0.0075 43 | 7560,0.8043,0.1897,0.0052,0.0000,0.0000,1.0000,0.4696,0.0583,0.0077 44 | 7740,0.8217,0.2234,0.0063,0.0000,0.0000,1.0000,0.4715,0.0543,0.0087 45 | 7920,0.8239,0.2277,0.0020,0.0000,0.0000,1.0000,0.4697,0.0601,0.0074 46 | 8100,0.8321,0.2436,0.0028,0.0000,0.0000,1.0000,0.4723,0.0634,0.0076 47 | 8280,0.8526,0.2833,0.0037,0.0000,0.0000,1.0000,0.4738,0.0676,0.0067 48 | 8460,0.8571,0.2920,0.0037,0.0000,0.0000,1.0000,0.4757,0.0674,0.0069 49 | 8640,0.8643,0.3059,0.0095,0.0000,0.0000,1.0000,0.4756,0.0718,0.0064 50 | 8820,0.8758,0.3282,0.0105,0.0000,0.0000,1.0000,0.4776,0.0709,0.0085 51 | 9000,0.8819,0.3400,0.0044,0.0000,0.0000,1.0000,0.4772,0.0738,0.0106 52 | 9180,0.9020,0.3789,0.0095,0.0000,0.0000,1.0000,0.4785,0.0875,0.0115 53 | 9360,0.9091,0.3926,0.0061,0.0000,0.0000,1.0000,0.4847,0.0961,0.0113 54 | 9540,0.9165,0.4070,0.0080,0.0025,0.0000,1.0000,0.4886,0.1073,0.0123 55 | 9720,0.9264,0.4261,0.0055,0.1192,0.0000,1.0000,0.4937,0.1182,0.0125 56 | 9900,0.9490,0.4699,0.0062,0.8808,1.0000,1.0000,0.4986,0.2178,0.0122 57 | 10080,0.9783,0.5266,0.0073,0.9975,1.0000,1.0000,0.5437,0.3894,0.0132 58 | 10260,1.0285,0.6238,0.0085,1.0000,1.0000,1.0000,0.6214,0.5512,0.0150 59 | 10440,1.0640,0.6925,0.0077,1.0000,1.0000,1.0000,0.6947,0.6617,0.0140 60 | 10620,1.0995,0.7613,0.0074,1.0000,1.0000,1.0000,0.7447,0.7705,0.0140 61 | 10800,1.1242,0.8091,0.0085,1.0000,1.0000,1.0000,0.7940,0.8390,0.0146 62 | 10980,1.1574,0.8734,0.0090,1.0000,1.0000,1.0000,0.8250,0.9145,0.0130 63 | 11160,1.1848,0.9264,0.0071,1.0000,1.0000,1.0000,0.8592,0.9832,0.0131 64 | 11340,1.2063,0.9681,0.0067,1.0000,1.0000,1.0000,0.8903,1.0000,0.0095 65 | 11520,1.2228,1.0000,0.0063,1.0000,1.0000,1.0000,0.8979,1.0000,0.0052 66 | 11700,1.2305,1.0000,0.0063,1.0000,1.0000,1.0000,0.9218,1.0000,0.0063 67 | 11880,1.2433,1.0000,0.0063,1.0000,1.0000,1.0000,0.9297,1.0000,0.0059 68 | 12060,1.2493,1.0000,0.0063,1.0000,1.0000,1.0000,0.9343,1.0000,0.0053 69 | 12240,1.2633,1.0000,0.0063,1.0000,1.0000,1.0000,0.9508,1.0000,0.0047 70 | 12420,1.2638,1.0000,0.0063,1.0000,1.0000,1.0000,0.9649,1.0000,0.0039 71 | 12600,1.2782,1.0000,0.0063,1.0000,1.0000,1.0000,0.9727,1.0000,0.0034 72 | 12780,1.2764,1.0000,0.0063,1.0000,1.0000,1.0000,0.9803,1.0000,0.0046 73 | 12960,1.2763,1.0000,0.0063,1.0000,1.0000,1.0000,0.9777,1.0000,0.0047 74 | 13140,1.2678,1.0000,0.0063,1.0000,1.0000,1.0000,0.9860,1.0000,0.0057 75 | 13320,1.2664,1.0000,0.0063,1.0000,1.0000,1.0000,0.9930,1.0000,0.0055 76 | 13500,1.2773,1.0000,0.0063,1.0000,1.0000,1.0000,1.0017,1.0000,0.0052 77 | 13680,1.2999,1.0000,0.0063,1.0000,1.0000,1.0000,1.0034,1.0000,0.0053 78 | 13860,1.2912,1.0000,0.0063,1.0000,1.0000,1.0000,1.0061,1.0000,0.0065 79 | 14040,1.2914,1.0000,0.0063,1.0000,1.0000,1.0000,1.0028,1.0000,0.0054 80 | 14220,1.2896,1.0000,0.0063,1.0000,1.0000,1.0000,1.0190,1.0000,0.0061 81 | 14400,1.3143,1.0000,0.0063,1.0000,1.0000,1.0000,1.0266,1.0000,0.0074 82 | 14580,1.3101,1.0000,0.0063,1.0000,1.0000,1.0000,1.0267,1.0000,0.0068 83 | 14760,1.2957,1.0000,0.0063,1.0000,1.0000,1.0000,1.0368,1.0000,0.0078 84 | 14940,1.2769,1.0000,0.0063,1.0000,1.0000,1.0000,1.0407,1.0000,0.0061 85 | 15120,1.2766,1.0000,0.0063,1.0000,1.0000,1.0000,1.0427,1.0000,0.0050 86 | 15300,1.2830,1.0000,0.0063,1.0000,1.0000,1.0000,1.0394,1.0000,0.0058 87 | 15480,1.2824,1.0000,0.0063,1.0000,1.0000,1.0000,1.0415,1.0000,0.0068 88 | 15660,1.2606,1.0000,0.0063,1.0000,1.0000,1.0000,1.0438,1.0000,0.0062 89 | 15840,1.2601,1.0000,0.0063,1.0000,1.0000,1.0000,1.0539,1.0000,0.0064 90 | 16020,1.2720,1.0000,0.0063,1.0000,1.0000,1.0000,1.0537,1.0000,0.0078 91 | 16200,1.2635,1.0000,0.0063,1.0000,1.0000,1.0000,1.0445,1.0000,0.0065 92 | 16380,1.2384,1.0000,0.0063,1.0000,1.0000,1.0000,1.0530,1.0000,0.0066 93 | 16560,1.2434,1.0000,0.0063,1.0000,1.0000,1.0000,1.0614,1.0000,0.0062 94 | 16740,1.2677,1.0000,0.0063,1.0000,1.0000,1.0000,1.0618,1.0000,0.0067 95 | 16920,1.2650,1.0000,0.0063,1.0000,1.0000,1.0000,1.0684,1.0000,0.0074 96 | 17100,1.2595,1.0000,0.0063,1.0000,1.0000,1.0000,1.0688,1.0000,0.0070 97 | 17280,1.2565,1.0000,0.0063,1.0000,1.0000,1.0000,1.0669,1.0000,0.0060 98 | 17460,1.2476,1.0000,0.0063,1.0000,1.0000,1.0000,1.0731,1.0000,0.0062 99 | 17640,1.2432,1.0000,0.0063,1.0000,1.0000,1.0000,1.0749,1.0000,0.0060 100 | 17820,1.2617,1.0000,0.0063,1.0000,1.0000,1.0000,1.0861,1.0000,0.0064 101 | 18000,1.2494,1.0000,0.0063,1.0000,1.0000,1.0000,1.0929,1.0000,0.0063 102 | 18180,1.2561,1.0000,0.0063,1.0000,1.0000,1.0000,1.0973,1.0000,0.0073 103 | 18360,1.2519,1.0000,0.0063,1.0000,1.0000,1.0000,1.1062,1.0000,0.0073 104 | 18540,1.2578,1.0000,0.0063,1.0000,1.0000,1.0000,1.1075,1.0000,0.0073 105 | 18720,1.2568,1.0000,0.0063,1.0000,1.0000,1.0000,1.1091,1.0000,0.0073 106 | 18900,1.2688,1.0000,0.0063,1.0000,1.0000,1.0000,1.1068,1.0000,0.0073 107 | 19080,1.2505,1.0000,0.0063,1.0000,1.0000,1.0000,1.1185,1.0000,0.0073 108 | 19260,1.2490,1.0000,0.0063,1.0000,1.0000,1.0000,1.1227,1.0000,0.0073 109 | 19440,1.2533,1.0000,0.0063,1.0000,1.0000,1.0000,1.1263,1.0000,0.0073 110 | 19620,1.2579,1.0000,0.0063,1.0000,1.0000,1.0000,1.1257,1.0000,0.0073 111 | 19800,1.2531,1.0000,0.0063,1.0000,1.0000,1.0000,1.1248,1.0000,0.0073 112 | 19980,1.2571,1.0000,0.0063,1.0000,1.0000,1.0000,1.1257,1.0000,0.0073 113 | 20160,1.2580,1.0000,0.0063,1.0000,1.0000,1.0000,1.1276,1.0000,0.0073 114 | -------------------------------------------------------------------------------- /examples/example_earm.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | simplePSO example using the EARM 1.0 model 4 | """ 5 | import os 6 | 7 | import matplotlib.pyplot as plt 8 | import numpy as np 9 | import pandas as pd 10 | import scipy.interpolate 11 | 12 | from pysb.examples.earm_1_0 import model 13 | from pysb.simulator import ScipyOdeSimulator 14 | from simplepso.pso import PSO 15 | 16 | # load experimental data 17 | data_path = os.path.join(os.path.dirname(__file__), 'data', 18 | 'EC-RP_IMS-RP_IC-RP_data_for_models.csv') 19 | 20 | exp_data = pd.read_csv(data_path, index_col=False) 21 | 22 | # Mean and variance of Td (delay time) and Ts (switching time) of MOMP, and 23 | # yfinal (the last value of the IMS-RP trajectory) 24 | momp_data = np.array([9810.0, 180.0, model.parameters['mSmac_0'].value]) 25 | momp_var = np.array([7245000.0, 3600.0, 1e4]) 26 | 27 | # timepoints for simulation. These must match the experimental data. 28 | tspan = exp_data['Time'].values.copy() 29 | 30 | rate_params = model.parameters_rules() 31 | param_values = np.array([p.value for p in model.parameters]) 32 | rate_mask = np.array([p in rate_params for p in model.parameters]) 33 | starting_position = np.log10(param_values[rate_mask]) 34 | 35 | solver = ScipyOdeSimulator( 36 | model, 37 | tspan, 38 | integrator='lsoda', 39 | use_analytic_jacobian=True, 40 | compiler='python', 41 | integrator_options={"rtol": 1e-6, "atol": 1e-6} 42 | ) 43 | 44 | 45 | def likelihood(position): 46 | param_values[rate_mask] = 10 ** position.copy() 47 | traj = solver.run(param_values=param_values) 48 | 49 | # normalize trajectories 50 | bid_traj = traj.observables['tBid_total'] / model.parameters['Bid_0'].value 51 | cparp_traj = traj.observables['CPARP_total'] / model.parameters[ 52 | 'PARP_0'].value 53 | momp_traj = traj.observables['cSmac_total'] 54 | 55 | # calculate chi^2 distance for each time course 56 | e1 = np.sum((exp_data['norm_IC-RP'] - bid_traj) ** 2 / 57 | (2 * exp_data['nrm_var_IC-RP'])) / len(bid_traj) 58 | 59 | e2 = np.sum((exp_data['norm_EC-RP'] - cparp_traj) ** 2 / 60 | (2 * exp_data['nrm_var_EC-RP'])) / len(cparp_traj) 61 | 62 | # Here we fit a spline to find where we get 50% release of MOMP reporter 63 | if np.nanmax(momp_traj) == 0: 64 | print('No aSmac!') 65 | e3 = 10000000 66 | else: 67 | ysim_momp_norm = momp_traj / np.nanmax(momp_traj) 68 | st, sc, sk = scipy.interpolate.splrep(tspan, ysim_momp_norm) 69 | try: 70 | t10 = scipy.interpolate.sproot((st, sc - 0.10, sk))[0] 71 | t90 = scipy.interpolate.sproot((st, sc - 0.90, sk))[0] 72 | except IndexError: 73 | t10 = 0 74 | t90 = 0 75 | 76 | # time of death = halfway point between 10 and 90% 77 | td = (t10 + t90) / 2 78 | 79 | # time of switch is time between 90 and 10 % 80 | ts = t90 - t10 81 | 82 | # final fraction of aSMAC (last value) 83 | momp_sim = [td, ts, momp_traj[-1]] 84 | 85 | e3 = np.sum((momp_data - momp_sim) ** 2 / (2 * momp_var)) / 3 86 | 87 | return e1 + e2 + e3 88 | 89 | 90 | def display(position, save_name): 91 | param_values[rate_mask] = 10 ** position 92 | traj = solver.run(param_values=param_values) 93 | 94 | # normalize trajectories 95 | bid_traj = traj.observables['tBid_total'] / model.parameters['Bid_0'].value 96 | cparp_traj = traj.observables['CPARP_total'] / model.parameters[ 97 | 'PARP_0'].value 98 | aSmac_traj = traj.observables['cSmac_total'] / model.parameters[ 99 | 'mSmac_0'].value 100 | 101 | # create all plots for each observable 102 | plt.figure(figsize=(3, 9)) 103 | 104 | # plot cleaved parp 105 | plt.subplot(311) 106 | plt.plot(tspan, bid_traj, color='r', marker='^', label='tBID sim') 107 | plt.errorbar(exp_data['Time'], exp_data['norm_IC-RP'], 108 | yerr=exp_data['nrm_var_IC-RP'] ** .5, 109 | ecolor='black', color='black', elinewidth=0.5, capsize=0) 110 | plt.legend(loc=0) 111 | 112 | # plot cleaved parp 113 | plt.subplot(312) 114 | plt.plot(tspan, cparp_traj, color='blue', marker='*', label='cPARP sim') 115 | plt.errorbar(exp_data['Time'], exp_data['norm_EC-RP'], 116 | yerr=exp_data['nrm_var_EC-RP'] ** .5, 117 | ecolor='black', color='black', elinewidth=0.5, capsize=0) 118 | plt.legend(loc=0) 119 | 120 | # plot activated SMAC 121 | plt.subplot(313) 122 | plt.plot(tspan, aSmac_traj, color='g', label='aSMAC sim') 123 | plt.axvline(momp_data[0], -0.05, 1.05, color='black', linestyle=':', 124 | label='exp aSMAC') 125 | plt.legend(loc=0) 126 | plt.savefig('{}.png'.format(save_name), bbox_inches='tight') 127 | plt.close() 128 | 129 | 130 | def create_gif_of_model_training(pso_instance): 131 | """ 132 | Create a gif showing the improvements of parameter fits from a PSO instance 133 | """ 134 | 135 | def create(position, save_name, values): 136 | param_values[rate_mask] = 10 ** position 137 | traj = solver.run(param_values=param_values) 138 | 139 | # normalize trajectories 140 | cparp_traj = traj.observables['CPARP_total'] / \ 141 | model.parameters['PARP_0'].value 142 | 143 | # create all plots for each observable 144 | plt.figure(figsize=(4, 6)) 145 | time = tspan / 3600 146 | # plot cleaved parp 147 | plt.subplot(211) 148 | plt.plot(time, cparp_traj, color='r', label='CPARP sim') 149 | plt.errorbar(time, exp_data['norm_EC-RP'], 150 | yerr=exp_data['nrm_var_EC-RP'] ** .5, label='CPARP exp', 151 | ecolor='black', color='black', elinewidth=0.5, capsize=0) 152 | plt.legend(loc=2) 153 | plt.xlabel("Time(hr)") 154 | plt.subplot(212) 155 | plt.plot(range(len(values)), np.log10(values), '-k') 156 | plt.plot(range(len(values))[save_name], np.log10(values[save_name]), 157 | 'or') 158 | 159 | plt.xlabel("Iteration number") 160 | plt.ylabel("Objective function value") 161 | plt.subplots_adjust(hspace=.3) 162 | plt.savefig('images/{}.png'.format(save_name), 163 | bbox_inches='tight', dpi=300) 164 | plt.close() 165 | 166 | try: 167 | import imageio 168 | except ImportError: 169 | raise ImportError("Please install imageio to create a gif") 170 | 171 | if not os.path.exists('images'): 172 | os.mkdir('images') 173 | for n, i in enumerate(pso_instance.history): 174 | create(i, n, pso_instance.values) 175 | 176 | images = ['images/{}.png'.format(i) for i in 177 | range(len(pso_instance.history) - 1)] 178 | 179 | imgs = [imageio.imread(i) for i in images] 180 | print("Creating gif") 181 | imageio.mimsave('training_earm.gif', imgs, duration=10 / len(images)) 182 | print("Creating mp4") 183 | imageio.mimsave('training_earm.mp4', imgs, fps=len(images) / 10) 184 | 185 | 186 | def run_example(): 187 | # create PSO object 188 | pso = PSO(save_sampled=False, verbose=True, shrink_steps=False) 189 | pso.set_start_position(starting_position) 190 | 191 | # allows particles to move +/- 2 orders of magnitude 192 | pso.set_bounds(2) 193 | # sets maximum speed that a particle can travel 194 | pso.set_speed(-.1, .1) 195 | 196 | pso.run( 197 | num_particles=24, 198 | num_iterations=100, 199 | stop_threshold=1e-5, 200 | num_processors=18, 201 | max_iter_no_improv=20, 202 | cost_function=likelihood 203 | ) 204 | 205 | display(pso.best.pos, save_name='best_fit') 206 | np.savetxt("pso_fit_for_model.csv", pso.best.pos) 207 | create_gif_of_model_training(pso) 208 | 209 | 210 | if __name__ == '__main__': 211 | # Runs PSO 212 | run_example() 213 | -------------------------------------------------------------------------------- /examples/robertson_example.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example usage of the simplePSO package 3 | """ 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | 7 | from pysb.examples.robertson import model 8 | from pysb.simulator import ScipyOdeSimulator 9 | from simplepso.logging import get_logger 10 | from simplepso.pso import PSO 11 | 12 | logger = get_logger() 13 | 14 | 15 | # Defining a few helper functions to use 16 | def normalize(trajectories): 17 | """Rescale a matrix of model trajectories to 0-1""" 18 | ymin = trajectories.min(0) 19 | ymax = trajectories.max(0) 20 | return (trajectories - ymin) / (ymax - ymin) 21 | 22 | 23 | def display(params, title=None): 24 | local_params = np.copy(params) 25 | param_values[rate_mask] = 10 ** local_params 26 | traj = solver.run(param_values=param_values) 27 | ysim_array = traj.dataframe[obs_names].values 28 | ysim_norm = normalize(ysim_array) 29 | 30 | plt.figure(figsize=(6, 4)) 31 | if title is not None: 32 | plt.title(title) 33 | plt.plot(t, ysim_norm[:, 0], '^r', linestyle='--', label='A') 34 | plt.plot(t, ysim_norm[:, 1], 'ok', linestyle='--', label='C') 35 | plt.plot(t, norm_noisy_data_A, 'r-', label='Noisy A') 36 | plt.plot(t, norm_noisy_data_C, 'k-', label='Noisy C') 37 | plt.legend(loc=0) 38 | plt.ylabel('Normalized concentration') 39 | plt.xlabel('Time (s)') 40 | 41 | 42 | ''' 43 | Here we define the cost function. We pass it the parameter vector, unlog it, 44 | and pass it to the solver. We choose a chi square cost function, but you can 45 | provide any metric of you choosing 46 | ''' 47 | 48 | 49 | def obj_function(params): 50 | # create copy of parameters 51 | params_tmp = np.copy(params) 52 | # convert back into regular base 53 | param_values[rate_mask] = 10 ** params_tmp 54 | traj = solver.run(param_values=param_values) 55 | ysim_array = traj.dataframe[obs_names].values 56 | ysim_norm = normalize(ysim_array) 57 | # chi^2 error 58 | err = np.sum((ydata_norm - ysim_norm) ** 2) 59 | if np.isnan(err): 60 | return 1000 61 | return err 62 | 63 | 64 | # setup model and simulator 65 | t = np.linspace(0, 50, 51) 66 | obs_names = ['A_total', 'C_total'] 67 | 68 | # create pysb simulator instance 69 | solver = ScipyOdeSimulator( 70 | model, 71 | t, 72 | integrator='lsoda', 73 | integrator_options={'rtol': 1e-8, 'atol': 1e-8} 74 | ) 75 | traj = solver.run() 76 | 77 | # Creating ideal data 78 | ysim_array = traj.dataframe[obs_names].values 79 | norm_data = normalize(ysim_array) 80 | 81 | noise = 0.05 82 | noisy_data_A = norm_data[:, 0] + np.random.uniform(-1 * noise, noise, len(t)) 83 | norm_noisy_data_A = normalize(noisy_data_A) 84 | 85 | noisy_data_C = norm_data[:, 1] + np.random.uniform(-noise, noise, len(t)) 86 | norm_noisy_data_C = normalize(noisy_data_C) 87 | 88 | ydata_norm = np.column_stack((norm_noisy_data_A, norm_noisy_data_C)) 89 | 90 | ''' 91 | In this example we are going to optimize the rate parameters of the model. 92 | We do this in log10 so we can span across large parameter spaces easily. 93 | ''' 94 | rate_params = model.parameters_rules() 95 | rate_mask = np.array([p in rate_params for p in model.parameters]) 96 | param_values = np.array([p.value for p in model.parameters]) 97 | log_original_values = np.log10(param_values[rate_mask]) 98 | 99 | if '__main__' == __name__: 100 | # We will use a best guess starting position for the model, 101 | start_position = log_original_values + \ 102 | np.random.uniform(-1, 1, 103 | size=len(log_original_values)) 104 | 105 | display(start_position, "Before optimization") 106 | plt.tight_layout() 107 | plt.savefig("fit_before_pso.png", bbox_inches='tight') 108 | logger.info("Saving pre-fit figure as fit_before_pso.png") 109 | 110 | # Here we initial the class 111 | # We must proivde the cost function and a starting value 112 | optimizer = PSO(start=start_position, verbose=True, shrink_steps=False) 113 | 114 | # We also must set bounds of the parameter space, and the speed PSO will 115 | # travel (max speed in either direction) 116 | optimizer.set_bounds(parameter_range=4) 117 | optimizer.set_speed(speed_min=-.05, speed_max=.05) 118 | 119 | # Now we run the pso algorithm 120 | optimizer.run(num_particles=50, num_iterations=500, num_processors=12, 121 | cost_function=obj_function, max_iter_no_improv=25) 122 | 123 | best_params = optimizer.best.pos 124 | display(best_params, "After optimization") 125 | plt.tight_layout() 126 | plt.savefig("fit_after_pso.png", bbox_inches='tight') 127 | logger.info("Saving post-fit figure as fit_after_pso.png") 128 | plt.show() 129 | -------------------------------------------------------------------------------- /examples/run_schogl_example_ssa.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | import seaborn as sns 4 | from scipy.stats import wasserstein_distance 5 | 6 | from pysb.integrate import odesolve 7 | from pysb.simulator import OpenCLSSASimulator 8 | from simplepso import PSO 9 | 10 | 11 | def run_params_for_plot(params): 12 | simulator.param_values = None 13 | simulator.initials = None 14 | traj = simulator.run(tspan, param_values=params, number_sim=num_sim) 15 | return traj.dataframe[name].unstack(0).values 16 | 17 | 18 | def obj_function(traj_dist): 19 | tmp = traj_dist[name].T.unstack('simulation').values[-1, :] 20 | return wasserstein_distance(tmp, actual) 21 | 22 | 23 | def run_pso(): 24 | optimizer = PSO(start=log10_original_values, verbose=True) 25 | optimizer.set_bounds(parameter_range=.2) 26 | optimizer.set_speed(speed_min=-.05, speed_max=.05) 27 | optimizer.run_ssa(model, num_particles=num_particles, num_iterations=200, 28 | cost_function=obj_function, num_sim=num_sim, 29 | simulator=simulator) 30 | 31 | print("Ending parameters") 32 | print(10 ** optimizer.best.pos) 33 | return optimizer.best.pos 34 | 35 | 36 | def add_subplot(traj, title, params): 37 | plt.title(title) 38 | plt.plot(tspan, traj, '0.5', lw=2, alpha=0.25) 39 | plt.plot(tspan, traj.mean(1), 'k-*', lw=3, label="Mean") 40 | plt.plot(tspan, traj.min(1), 'b--', lw=3, label="Minimum") 41 | plt.plot(tspan, traj.max(1), 'r--', lw=3, label="Maximum") 42 | y = odesolve(model, tspan, params) 43 | plt.plot(tspan, y[name], 'g--', lw=3, label="ODE") 44 | plt.ylim(0, 800) 45 | plt.xlabel('Time') 46 | plt.ylabel('X molecules') 47 | 48 | 49 | def run(): 50 | fit_params = run_pso() 51 | 52 | plt.figure(figsize=(9, 5)) 53 | plt.subplot(231) 54 | add_subplot(actual_traj, "Training data", orig_values) 55 | 56 | plt.subplot(232) 57 | orig_values[rate_mask] = 10 ** noisy_start 58 | start_sim = run_params_for_plot(orig_values) 59 | 60 | add_subplot(start_sim, "Starting state", orig_values) 61 | 62 | orig_values[rate_mask] = 10 ** fit_params 63 | best_fit_sim = run_params_for_plot(orig_values) 64 | 65 | plt.subplot(233) 66 | add_subplot(best_fit_sim, "After training", orig_values) 67 | 68 | plt.subplot(212) 69 | sns.kdeplot(actual, label='actual') 70 | sns.kdeplot(start_sim[-1, :], label='before') 71 | sns.kdeplot(best_fit_sim[-1, :], label='after') 72 | 73 | plt.legend() 74 | plt.tight_layout() 75 | savename = 'trained_model' 76 | plt.savefig("{}.png".format(savename), bbox_inches='tight', dpi=300) 77 | plt.show() 78 | plt.close() 79 | 80 | 81 | if __name__ == '__main__': 82 | from pysb.examples.schloegl import model 83 | 84 | tspan = np.linspace(0, 100, 101) 85 | model.parameters['X_0'].value = 500 86 | name = 'X_total' 87 | num_sim = 100 88 | num_particles = 8 89 | # uncomment to use CUDA device 90 | # simulator = CudaSSASimulator(model, tspan=tspan, verbose=False) 91 | 92 | simulator = OpenCLSSASimulator(model, tspan=tspan, verbose=False) 93 | 94 | actual_traj = run_params_for_plot(None) 95 | actual = actual_traj[-1, :] 96 | rate_mask = np.array( 97 | [p in model.parameters_rules() for p in model.parameters]) 98 | 99 | orig_values = np.array([p.value for p in model.parameters]) 100 | log10_original_values = np.log10(orig_values[rate_mask]) 101 | n_params = len(log10_original_values) 102 | print("Ideal parameters") 103 | print(log10_original_values) 104 | 105 | noisy_start = log10_original_values + np.random.uniform(-.1, .1, n_params) 106 | 107 | run() 108 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib>=1.5.0 2 | numpy>=1.11.0 3 | scipy>=0.17.1 4 | pandas -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | 4 | [bdist_wheel] 5 | universal=1 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | from simplepso import __version__ 4 | 5 | with open('README.md') as f: 6 | readme = f.read() 7 | 8 | 9 | setup( 10 | name='simplepso', 11 | packages=find_packages(exclude=['examples', 'docs'],), 12 | version=__version__, 13 | description='Simple usage particle swarm optimization', 14 | author='James C. Pino', 15 | author_email='james.ch.pino@gmail.com', 16 | url='https://github.com/LoLab-VU/simplePSO', 17 | keywords=['optimization', 18 | 'systems biology'], 19 | classifiers=['License :: OSI Approved :: BSD License', 20 | 'Operating System :: OS Independent', 21 | 'Programming Language :: Python :: 2', 22 | 'Programming Language :: Python :: 3'], 23 | include_package_data=True, 24 | install_requires=['matplotlib >= 1.5.0', 25 | 'numpy >= 1.11.0', 26 | 'scipy >= 0.17.1', 27 | 'pysb >= 1.1.1'], 28 | ) 29 | -------------------------------------------------------------------------------- /simplepso/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Simple interface for particle swarm optimization 3 | 4 | """ 5 | from simplepso.pso import PSO 6 | 7 | _MAJOR = 2 8 | _MINOR = 2 9 | _MICRO = 3 10 | __version__ = '%d.%d.%d' % (_MAJOR, _MINOR, _MICRO) 11 | __release__ = '%d.%d' % (_MAJOR, _MINOR) 12 | __all__ = ['PSO'] 13 | -------------------------------------------------------------------------------- /simplepso/logging.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import logging 4 | import os 5 | import platform 6 | import socket 7 | import time 8 | import warnings 9 | 10 | # code based off of pysb.logging.py 11 | 12 | SECONDS_IN_HOUR = 3600 13 | LOG_LEVEL_ENV_VAR = 'SIMPLEPSO_LOG' 14 | BASE_LOGGER_NAME = 'simplepso' 15 | EXTENDED_DEBUG = 5 16 | NAMED_LOG_LEVELS = {'NOTSET': logging.NOTSET, 17 | 'EXTENDED_DEBUG': EXTENDED_DEBUG, 18 | 'DEBUG': logging.DEBUG, 19 | 'INFO': logging.INFO, 20 | 'WARNING': logging.WARNING, 21 | 'ERROR': logging.ERROR, 22 | 'CRITICAL': logging.CRITICAL} 23 | 24 | 25 | def formatter(time_utc=False): 26 | """ 27 | Build a logging formatter using local or UTC time 28 | 29 | Parameters 30 | ---------- 31 | time_utc : bool, optional (default: False) 32 | Use local time stamps in log messages if False, or Universal 33 | Coordinated Time (UTC) if True 34 | 35 | Returns 36 | ------- 37 | A logging.Formatter object for simplePSO logging 38 | """ 39 | log_fmt = logging.Formatter('%(asctime)s.%(msecs).3d - %(name)s - ' 40 | '%(levelname)s - %(message)s', 41 | datefmt='%Y-%m-%d %H:%M:%S') 42 | if time_utc: 43 | log_fmt.converter = time.gmtime 44 | 45 | return log_fmt 46 | 47 | 48 | def setup_logger(level=logging.INFO, console_output=True, file_output=False, 49 | time_utc=False, capture_warnings=True): 50 | """ 51 | Set up a new logging.Logger for simplePSO logging 52 | 53 | Calling this method will override any existing handlers, formatters etc. 54 | attached to the simplePSO logger. Typically, :func:`get_logger` should be 55 | used instead, which returns the existing simplePSO logger if it has already 56 | been set up, and can handle simplePSO submodule namespaces. 57 | 58 | Parameters 59 | ---------- 60 | level : int 61 | Logging level, typically using a constant like logging.INFO or 62 | logging.DEBUG 63 | console_output : bool 64 | Set up a default console log handler if True (default) 65 | file_output : string 66 | Supply a filename to copy all log output to that file, or set to 67 | False to disable (default) 68 | time_utc : bool 69 | Specifies whether log entry time stamps should be in local time 70 | (when False) or UTC (True). See :func:`formatter` for more 71 | formatting options. 72 | capture_warnings : bool 73 | Capture warnings from Python's warnings module if True (default) 74 | 75 | Returns 76 | ------- 77 | A logging.Logger object for simplePSO logging. 78 | :func:`get_logger`. 79 | """ 80 | log = logging.getLogger(BASE_LOGGER_NAME) 81 | 82 | # Logging level can be overridden with environment variable 83 | if LOG_LEVEL_ENV_VAR in os.environ: 84 | try: 85 | level = int(os.environ[LOG_LEVEL_ENV_VAR]) 86 | except ValueError: 87 | # Try parsing as a name 88 | level_name = os.environ[LOG_LEVEL_ENV_VAR] 89 | if level_name in NAMED_LOG_LEVELS.keys(): 90 | level = NAMED_LOG_LEVELS[level_name] 91 | else: 92 | raise ValueError( 93 | 'Environment variable {} contains an invalid value "{}". ' 94 | 'If set, its value must be one of {} (case-sensitive) or ' 95 | 'an integer log level.'.format( 96 | LOG_LEVEL_ENV_VAR, level_name, 97 | ", ".join(NAMED_LOG_LEVELS.keys()) 98 | ) 99 | ) 100 | 101 | log.setLevel(level) 102 | 103 | # Remove default logging handler 104 | log.handlers = [] 105 | 106 | log_fmt = formatter(time_utc=time_utc) 107 | 108 | if console_output: 109 | stream_handler = logging.StreamHandler() 110 | stream_handler.setFormatter(log_fmt) 111 | log.addHandler(stream_handler) 112 | 113 | if file_output: 114 | file_handler = logging.FileHandler(file_output) 115 | file_handler.setFormatter(log_fmt) 116 | log.addHandler(file_handler) 117 | 118 | log.info('Logging started on simplepso') 119 | if time_utc: 120 | log.info('Log entry times are in UTC') 121 | else: 122 | utc_offset = time.timezone if (time.localtime().tm_isdst == 0) else \ 123 | time.altzone 124 | utc_offset = -(utc_offset / SECONDS_IN_HOUR) 125 | log.info('Log entry time offset from UTC: %.2f hours', utc_offset) 126 | 127 | log.debug('OS Platform: %s', platform.platform()) 128 | log.debug('Python version: %s', platform.python_version()) 129 | log.debug('Hostname: %s', socket.getfqdn()) 130 | 131 | logging.captureWarnings(capture_warnings) 132 | 133 | return log 134 | 135 | 136 | def get_logger(logger_name=BASE_LOGGER_NAME, log_level=None, 137 | **kwargs): 138 | """ 139 | Returns (if extant) or creates a simplePSO logger 140 | 141 | If the simplePSO base logger has already been set up, this method will 142 | return it or any of its descendant loggers without overriding the settings 143 | - i.e. any values supplied as kwargs will be ignored. 144 | 145 | Parameters 146 | ---------- 147 | logger_name : string 148 | Get a logger for a specific namespace, typically __name__ for code 149 | outside of classes or self.__module__ inside a class 150 | log_level : bool or int 151 | Override the default or preset log level for the requested logger. 152 | None or False uses the default or preset value. True evaluates to 153 | logging.DEBUG. Any integer is used directly. 154 | **kwargs : kwargs 155 | Keyword arguments to supply to :func:`setup_logger`. Only used when 156 | the simplePSO logger hasn't been set up yet (i.e. there have been no 157 | calls to this function or :func:`get_logger` directly). 158 | 159 | Returns 160 | ------- 161 | A logging.Logger object with the requested name 162 | 163 | Examples 164 | -------- 165 | 166 | >>> from simplepso.logging import get_logger 167 | >>> logger = get_logger(__name__) 168 | >>> logger.debug('Test message') 169 | """ 170 | if BASE_LOGGER_NAME not in logging.Logger.manager.loggerDict.keys(): 171 | setup_logger(**kwargs) 172 | elif kwargs: 173 | warnings.warn('simplepso logger already exists, ignoring keyword ' 174 | 'arguments to setup_logger') 175 | 176 | logger = logging.getLogger(logger_name) 177 | 178 | if log_level is not None and log_level is not False: 179 | if isinstance(log_level, bool): 180 | log_level = logging.DEBUG 181 | elif not isinstance(log_level, int): 182 | raise ValueError('log_level must be a boolean, integer or None') 183 | 184 | if logger.getEffectiveLevel() != log_level: 185 | logger.debug('Changing log_level from %d to %d' % ( 186 | logger.getEffectiveLevel(), log_level)) 187 | logger.setLevel(log_level) 188 | return logger 189 | -------------------------------------------------------------------------------- /simplepso/pso.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | from concurrent.futures import ProcessPoolExecutor, Executor, Future 4 | from copy import deepcopy 5 | 6 | import numpy as np 7 | 8 | from simplepso.logging import setup_logger 9 | 10 | # set OMP_NUM_THREADS to 1 to prevent multi-processing to expand no each core 11 | os.environ['OMP_NUM_THREADS'] = "1" 12 | 13 | 14 | class Particle(object): 15 | """ 16 | Particle to be used in the Swarm 17 | """ 18 | 19 | def __init__(self, pos, fitness=None, smin=None, smax=None): 20 | self.pos = pos 21 | self.fitness = fitness 22 | self.smin = smin 23 | self.smax = smax 24 | self.__best = None 25 | 26 | @property 27 | def best(self): 28 | return self.__best 29 | 30 | @best.setter 31 | def best(self, new_best): 32 | self.__best = Particle( 33 | deepcopy(new_best.pos), deepcopy(new_best.fitness), 34 | deepcopy(new_best.smin), deepcopy(new_best.smax) 35 | ) 36 | 37 | # Used for printing below 38 | header = '{:<10}' + "\t".join(['{:>12}'] * 5) 39 | stats_header = header.format('iteration', 'best', 'mean', 'min', 'max', 'std') 40 | stats_output = '{:<10}' + '\t'.join(['{:>12.3f}'] * 5) 41 | 42 | 43 | class PSO(object): 44 | """ 45 | Simple interface to run particle swarm optimization 46 | 47 | It can optimize parameters for a function using two run methods. 48 | run() : cost function gets a parameter vector and returns a scalar. Can 49 | be used with any callable function. 50 | run_ssa() : cost function gets multiple trajectories of a PySB model 51 | and returns a scalar. These trajectories are provided as 52 | a pandas.DataFrame. PySB dependent function. 53 | """ 54 | 55 | def __init__(self, start=None, save_sampled=False, verbose=False, 56 | shrink_steps=True): 57 | """ 58 | 59 | Parameters 60 | ---------- 61 | start : list 62 | Starting position 63 | save_sampled : bool 64 | Save each particles position and fitness over all iterations. 65 | verbose : bool 66 | shrink_steps : bool 67 | Shrink max distance traveled by each particle as the number of 68 | iterations increases 69 | """ 70 | self.save_sampled = save_sampled 71 | if start is not None: 72 | self.set_start_position(start) 73 | else: 74 | self.start = None 75 | """Starting position""" 76 | self.size = None 77 | self.verbose = verbose 78 | self.best = None 79 | """Best Particle of population""" 80 | self.max_speed = None 81 | self.min_speed = None 82 | self.lb = None 83 | self.ub = None 84 | self.bounds_set = False 85 | self.range = 2 86 | self.all_history = None 87 | """History of all particles positions over all iterations. Only saved 88 | if save_sampled=True""" 89 | self.all_fitness = None 90 | """History of all particles finesses over all iterations. Only saved 91 | if save_sampled=True""" 92 | self.population = [] 93 | """Population of particles""" 94 | self.population_fitness = [] 95 | """Fitness values of population of particles""" 96 | self.values = [] 97 | """Fitness values of the best particle for each iteration""" 98 | self.history = [] 99 | """Best particle for each iteration""" 100 | self.update_w = shrink_steps 101 | self._is_setup = False 102 | self.log = setup_logger(verbose) 103 | fi = 2.05 + 2.05 # value from kennedy paper 104 | self.w = 2.0 / np.abs(2.0 - fi - np.sqrt(np.power(fi, 2) - 4 * fi)) 105 | 106 | def get_best_value(self): 107 | """ Returns the best fitness value of the population """ 108 | return self.best.fitness 109 | 110 | def get_history(self): 111 | """ Returns the history of the run""" 112 | return self.history 113 | 114 | def _generate(self): 115 | """ Creates Particles and sets their speed """ 116 | part = Particle(np.random.uniform(self.lb, self.ub, self.size)) 117 | part.speed = np.random.uniform(self.min_speed, self.max_speed, 118 | self.size) 119 | part.smin = self.min_speed 120 | part.smax = self.max_speed 121 | return part 122 | 123 | def _setup_pso(self): 124 | """ Sets up everything for PSO. Only does once """ 125 | if self.max_speed is None or self.min_speed is None: 126 | self.set_speed() 127 | 128 | if not self.bounds_set: 129 | self.set_bounds() 130 | 131 | if self.start is None: 132 | raise Exception("Must provide a starting position\n" 133 | "Use PSO.set_start_position() \n") 134 | 135 | self._is_setup = True 136 | 137 | def _update_connected(self): 138 | """ Update the population of particles""" 139 | for part in self.population: 140 | if part.best is None or part.best.fitness > part.fitness: 141 | part.best = part 142 | # part.best = deepcopy(part) 143 | # part.best.fitness = deepcopy(part.fitness) 144 | if self.best is None or self.best.fitness > part.fitness: 145 | self.best = Particle( 146 | deepcopy(part.pos), 147 | deepcopy(part.fitness), 148 | deepcopy(part.smin), 149 | deepcopy(part.smax) 150 | ) 151 | # self.best = deepcopy(part) 152 | # self.best.fitness = deepcopy(part.fitness) 153 | 154 | def _update_particle_position(self, part): 155 | """ Updates an individual particles position """ 156 | phi1 = 2.05 157 | phi2 = 2.05 158 | v_u1 = np.random.uniform(0, 1, self.size) * phi1 * ( 159 | part.best.pos - part.pos) 160 | v_u2 = np.random.uniform(0, 1, self.size) * phi2 * ( 161 | self.best.pos - part.pos) 162 | part.speed = self.w * (part.speed + v_u1 + v_u2) 163 | np.place(part.speed, part.speed < part.smin, part.smin) 164 | np.place(part.speed, part.speed > part.smax, part.smax) 165 | part.pos += part.speed 166 | for i, pos in enumerate(part.pos): 167 | if pos < self.lb[i]: 168 | part.pos[i] = self.lb[i] 169 | elif pos > self.ub[i]: 170 | part.pos[i] = self.ub[i] 171 | 172 | def return_ranked_populations(self): 173 | """ Returns a ranked population of the particles 174 | 175 | Returns 176 | ------- 177 | np.array, np.array 178 | """ 179 | positions = [] 180 | finesses = [] 181 | for n, part in enumerate(self.population): 182 | finesses.append(part.best.fitness) 183 | positions.append(part.best.pos) 184 | 185 | positions = np.array(positions) 186 | finesses = np.array(finesses) 187 | idx = np.argsort(finesses) 188 | return finesses[idx], positions[idx] 189 | 190 | def set_start_position(self, position): 191 | """ 192 | Set the starting position for the population of particles. 193 | 194 | Parameters 195 | ---------- 196 | position : array 197 | 198 | """ 199 | self.start = np.array(position) 200 | self.size = len(position) 201 | 202 | def set_speed(self, speed_min=-10000, speed_max=10000): 203 | """ Sets the max and min speed of the particles. 204 | 205 | This is usually a fraction of the bounds set with `set_bounds`. So if 206 | one sets the bound to be +/- 1 order of magnitude, you can set the 207 | speed to be -.1 and .1, allow the particles to update in 1/10 the 208 | parameter space. This keeps particles near their local position rather 209 | than jumping across parameter space quickly. 210 | 211 | Parameters 212 | ---------- 213 | speed_min : float 214 | speed_max : float 215 | 216 | """ 217 | self.min_speed = speed_min 218 | self.max_speed = speed_max 219 | 220 | def set_bounds(self, parameter_range=None, lower=None, upper=None): 221 | """ Set the search space bounds that the parameters may search. 222 | 223 | This can be a single float, in which the particles can search plus or 224 | minus the starting values around this. It can also be an array that 225 | must be the same length of the starting position. 226 | 227 | Parameters 228 | ---------- 229 | parameter_range : float 230 | If provided parameter_range, the range will be set by the starting 231 | position +/- this value. To set each parameter manually, use 232 | `lower` and `upper` args 233 | lower : array 234 | Lower bounds for parameters 235 | upper : array 236 | Upper bounds for parameters 237 | 238 | """ 239 | if self.start is None: 240 | raise Exception("Must provide starting array: %r" % self.start) 241 | 242 | if parameter_range is None and upper is None and lower is None: 243 | raise Exception('Need to provide parameter ranges') 244 | 245 | if parameter_range is None: 246 | if self.range is None: 247 | raise Exception("Please provide parameter range") 248 | parameter_range = self.range 249 | 250 | if lower is None: 251 | lower = self.start - parameter_range 252 | else: 253 | if not self.size == len(lower): 254 | raise Exception("Size of bounds must equal size of particle") 255 | if upper is None: 256 | upper = self.start + parameter_range 257 | else: 258 | if not self.size == len(upper): 259 | raise Exception("Size of bounds must equal size of particle") 260 | self.lb = lower 261 | self.ub = upper 262 | self.bounds_set = True 263 | 264 | def run(self, num_particles, num_iterations, cost_function=None, 265 | num_processors=1, save_samples=False, stop_threshold=1e-5, 266 | max_iter_no_improv=None): 267 | """ 268 | Run optimization 269 | 270 | Parameters 271 | ---------- 272 | num_particles : int 273 | Number of particles in population, ~20 is a good starting place 274 | num_iterations : int 275 | Number of iterations to perform. 276 | cost_function : callable function 277 | Takes a parameter set and returns a scalar (particles fitness) 278 | num_processors : int 279 | Number of processors to run on. If using scipy, note that you may 280 | need to set OMP_NUM_THREADS=1 to prevent each process from using 281 | more than one CPU. 282 | save_samples : bool 283 | Save ALL positions of particles over time, can require large memory 284 | if num_particles, num_iterations, and len(parameters) is large. 285 | stop_threshold : float 286 | Standard deviation of the particles’ cost function at which the 287 | optimization is stopped 288 | max_iter_no_improv: int 289 | Maximum steps allowed without improvement before the optimization 290 | stops. 291 | """ 292 | if self._is_setup: 293 | pass 294 | else: 295 | self._setup_pso() 296 | 297 | if not callable(cost_function): 298 | raise TypeError("Provide a callable cost function") 299 | 300 | history = np.zeros((num_iterations, len(self.start))) 301 | if self.save_sampled or save_samples: 302 | self.all_history = np.zeros( 303 | (num_iterations, num_particles, len(self.start))) 304 | self.all_fitness = np.zeros((num_iterations, num_particles)) 305 | values = np.zeros(num_iterations) 306 | self.population = [self._generate() for _ in range(num_particles)] 307 | self.population_fitness = np.zeros(len(self.population)) 308 | if max_iter_no_improv is None: 309 | max_iter_no_improv = np.inf 310 | iter_without_improvement = 0 311 | best_fitness = np.inf 312 | with SerialExecutor() if num_processors == 1 else \ 313 | ProcessPoolExecutor(max_workers=num_processors) as executor: 314 | for g in range(num_iterations): 315 | if self.update_w: 316 | self.w = (num_iterations - g + 1.) / num_iterations 317 | self.population_fitness = np.array( 318 | list(executor.map( 319 | cost_function, 320 | [p.pos for p in self.population], 321 | ) 322 | ) 323 | ) 324 | 325 | for ind, fit in zip(self.population, self.population_fitness): 326 | ind.fitness = fit 327 | self._update_connected() 328 | for part in self.population: 329 | self._update_particle_position(part) 330 | values[g] = self.best.fitness 331 | history[g] = self.best.pos 332 | if self.save_sampled or save_samples: 333 | curr_fit, curr_pop = self.return_ranked_populations() 334 | self.all_history[g, :, :] = curr_pop 335 | self.all_fitness[g, :] = curr_fit 336 | 337 | if self.verbose: 338 | self.print_stats(g + 1, fitness=self.population_fitness) 339 | 340 | if self.population_fitness.std() < stop_threshold: 341 | self.log.info("Stopping. STD < stop_threshold.") 342 | break 343 | if self.best.fitness < best_fitness: 344 | best_fitness = self.best.fitness 345 | iter_without_improvement = 0 346 | else: 347 | iter_without_improvement += 1 348 | if iter_without_improvement > max_iter_no_improv: 349 | self.log.info("Stopping. max_iter_no_improv reached.") 350 | break 351 | self.values = np.array(values[:g]) 352 | self.history = np.array(history[:g, :]) 353 | 354 | def _calc_fitness_from_array(self, traj, num_sim, cost_function): 355 | index_names = traj.index.names 356 | traj.reset_index(inplace=True) 357 | for i, part in enumerate(self.population): 358 | start = i * num_sim 359 | end = (i + 1) * num_sim 360 | tmp_results = traj.loc[ 361 | traj.simulation.isin(list(range(start, end)))] 362 | tmp_results.set_index(index_names, inplace=True) 363 | error = cost_function(tmp_results) 364 | part.fitness = error 365 | self.population_fitness[i] = error 366 | 367 | def _get_parameters_from_population(self, num_sims, total_sims, n_params): 368 | """ Create param_array for GPU based simulators """ 369 | rate_vals = np.zeros((total_sims, n_params)) 370 | # create parameters for each particle, creates blocks per num_sims 371 | for i, part in enumerate(self.population): 372 | start = i * num_sims 373 | end = (i + 1) * num_sims 374 | rate_vals[start:end, :] = part.pos 375 | return rate_vals 376 | 377 | def run_ssa(self, model, num_particles, num_iterations, num_sim, 378 | cost_function, simulator, save_samples=False, 379 | stop_threshold=0): 380 | """ 381 | Run PSO for a stochastic simulator 382 | 383 | Parameters 384 | ---------- 385 | model : pysb.Model 386 | num_particles : int 387 | Number of particles in the swarm. 388 | num_iterations : int 389 | Number of iterations to perform 390 | num_sim : int 391 | Number of SSA simulations to run for each particle. 392 | cost_function : function 393 | Takes a pandas dataframe of PySB trajectories created by running 394 | multiple SSA simulations. Function must return a scalar. 395 | simulator : pysb.Simulator 396 | PySB simulator (CudaSSASimulator or OpenclSSASimulator) 397 | save_samples : bool 398 | stop_threshold : float 399 | """ 400 | if self._is_setup: 401 | pass 402 | else: 403 | self._setup_pso() 404 | if simulator is None: 405 | raise ValueError( 406 | "Must provide an SSA simulator to use this method") 407 | 408 | history = [] 409 | values = [] 410 | self.population = [self._generate() for _ in range(num_particles)] 411 | self.population_fitness = np.zeros((len(self.population))) 412 | total_sims = num_particles * num_sim 413 | rate_p = model.parameters_rules() 414 | rate_mask = np.array([p in rate_p for p in model.parameters]) 415 | nominal_values = np.array([p.value for p in model.parameters]) 416 | log10_original_values = np.log10(nominal_values[rate_mask]) 417 | all_param_vals = np.repeat([nominal_values], total_sims, axis=0) 418 | 419 | for g in range(0, num_iterations): 420 | if self.update_w: 421 | self.w = (num_iterations - g) / num_iterations 422 | rate_values = self._get_parameters_from_population( 423 | num_sim, total_sims, len(log10_original_values) 424 | ) 425 | 426 | # convert back from log10 space 427 | all_param_vals[:, rate_mask] = 10 ** rate_values 428 | # reset initials and param_values so simulator won't try to 429 | # duplicate any 430 | simulator.initials = None 431 | simulator.param_values = None 432 | traj = simulator.run(param_values=all_param_vals).dataframe 433 | self._calc_fitness_from_array(traj, num_sim, cost_function) 434 | self._update_connected() 435 | for part in self.population: 436 | self._update_particle_position(part) 437 | 438 | values.append(self.best.fitness) 439 | history.append(deepcopy(self.best.pos)) 440 | 441 | if self.save_sampled or save_samples: 442 | curr_fit, curr_pop = self.return_ranked_populations() 443 | self.all_history[g, :, :] = curr_pop 444 | self.all_fitness[g, :] = curr_fit 445 | 446 | if self.verbose: 447 | self.print_stats(g + 1, fitness=self.population_fitness) 448 | if self.population_fitness.std() < stop_threshold: 449 | self.log.info("Stopping criteria reached.") 450 | break 451 | 452 | self.values = np.array(values) 453 | self.history = np.array(history) 454 | 455 | def print_stats(self, iteration, fitness): 456 | if iteration == 1: 457 | self.log.info(stats_header) 458 | self.log.info( 459 | stats_output.format(iteration, self.best.fitness, fitness.mean(), 460 | fitness.min(), fitness.max(), fitness.std()) 461 | ) 462 | 463 | 464 | class SerialExecutor(Executor): 465 | """ Execute tasks in serial (immediately on submission) 466 | Code originally from PySB. 467 | """ 468 | 469 | def submit(self, fn, *args, **kwargs): 470 | f = Future() 471 | try: 472 | result = fn(*args, **kwargs) 473 | except BaseException as e: 474 | f.set_exception(e) 475 | else: 476 | f.set_result(result) 477 | 478 | return f 479 | -------------------------------------------------------------------------------- /simplepso/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoLab-MSM/simplePSO/37e5eb22da920d30780984dcea327686d6eacbfe/simplepso/tests/__init__.py -------------------------------------------------------------------------------- /simplepso/tests/test_pso_construction.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from nose.tools import raises 3 | 4 | from simplepso.pso import PSO 5 | 6 | 7 | def h1(individual): 8 | """ Simple two-dimensional function containing several local maxima. 9 | Found in deap.benchmarks.h1 10 | Defined range of [-100, 100] 11 | minimum is at f(8.6998, 6.7665) = 2 12 | """ 13 | num = (np.sin(individual[0] - individual[1] / 8)) ** 2 + (np.sin(individual[1] + individual[0] / 8)) ** 2 14 | denum = ((individual[0] - 8.6998) ** 2 + (individual[1] - 6.7665) ** 2) ** 0.5 + 1 15 | return -1 * num / denum 16 | 17 | 18 | def himmelblau(individual): 19 | """The Himmelblau's function is multimodal with 4 defined minimums in 20 | :math:`[-6, 6]^2. 21 | 22 | range [-6, 6] 23 | x_1 = (3.0, 2.0), = 0 24 | x_2 = (-2.805118, 3.131312), = 0 25 | x_3 = (-3.779310, -3.283186), = 0 26 | x_4 = (3.584428, -1.848126), = 0 27 | """ 28 | return (individual[0] * individual[0] + individual[1] - 11) ** 2 + \ 29 | (individual[0] + individual[1] * individual[1] - 7) ** 2 30 | 31 | 32 | def test_population_creation(): 33 | known_sol = [8.6998, 6.7665] 34 | pso = PSO(start=[0, 0], verbose=False, shrink_steps=False) 35 | pso.set_bounds(lower=[-100, -100], upper=[100, 100]) 36 | pso.set_speed(-10, 10) 37 | pso.run(num_iterations=100, num_particles=10, cost_function=h1, ) 38 | pso.return_ranked_populations() 39 | error = np.sum((pso.best.pos - known_sol) ** 2) 40 | print('True value: [8.6998, 6.7665]. Found:{0}. Error^2 = {1}'.format( 41 | pso.best.pos, error)) 42 | assert (error < 0.1) 43 | 44 | 45 | def test_himmelblau(): 46 | """ test to see if PSO can find simple minimum 47 | """ 48 | minimums = [[3.0, 2.0], 49 | [-2.805118, 3.131312], 50 | [-3.779310, -3.283186], 51 | [3.584428, -1.848126]] 52 | 53 | pso = PSO(start=[10, 0], verbose=False) 54 | pso.set_bounds(lower=[-100, -100], upper=[100, 100]) 55 | pso.run(num_iterations=100, num_particles=10, cost_function=himmelblau, ) 56 | good_min = False 57 | for i in minimums: 58 | if np.sum((pso.best.pos - i) ** 2) < .1: 59 | good_min = True 60 | error = np.sum((pso.best.pos - i) ** 2) 61 | found_min = i 62 | if good_min: 63 | print('Found minimum') 64 | print('True value: {0}. Found:{1}. Error^2 = {2}'.format( 65 | found_min, pso.best.pos, error) 66 | ) 67 | 68 | 69 | @raises(Exception) 70 | def test_missing_cost_function(): 71 | pso = PSO(start=[10, 0], verbose=False) 72 | pso.set_bounds(lower=[-100, -100], upper=[100, 100]) 73 | pso.run(num_iterations=100, num_particles=10) 74 | 75 | 76 | @raises(Exception) 77 | def test_mismatched_bounds(): 78 | pso = PSO(start=[10, 0], verbose=False) 79 | pso.set_bounds(lower=[-100, 0, -100], upper=[100, 100]) 80 | pso.run(num_iterations=100, num_particles=10, cost_function=himmelblau, ) 81 | 82 | 83 | @raises(Exception) 84 | def test_no_bounds(): 85 | pso = PSO(start=[10, 0], verbose=False) 86 | pso.run(num_iterations=100, num_particles=10, cost_function=himmelblau, ) 87 | -------------------------------------------------------------------------------- /simplepso/tests/test_robertson.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | from pysb.examples.robertson import model 4 | from pysb.integrate import odesolve 5 | import pysb 6 | 7 | 8 | 9 | 10 | t = np.linspace(0, 40,100) 11 | obs_names = ['A_total', 'C_total'] 12 | 13 | 14 | 15 | solver = pysb.integrate.Solver(model, t, integrator='vode',rtol=1e-8, atol=1e-8) 16 | 17 | solver.run() 18 | def normalize(trajectories): 19 | """Rescale a matrix of model trajectories to 0-1""" 20 | ymin = trajectories.min(0) 21 | ymax = trajectories.max(0) 22 | return (trajectories - ymin) / (ymax - ymin) 23 | 24 | def extract_records(recarray, names): 25 | """Convert a record-type array and list of names into a float array""" 26 | return np.vstack([recarray[name] for name in names]).T --------------------------------------------------------------------------------