├── .gitignore
├── LICENSE.txt
├── README.md
├── doc
├── Makefile
├── make.bat
└── source
│ ├── conf.py
│ ├── images
│ ├── simple_model_mcmc.png
│ ├── simple_model_mcmc_right.png
│ ├── simple_model_pdf.png
│ ├── simple_model_pdf_family.png
│ └── simple_model_smc.png
│ ├── index.rst
│ ├── install.rst
│ ├── math.rst
│ ├── reference.rst
│ ├── tutorial.rst
│ └── videos
│ └── smc_movie.mp4
├── examples
├── diffusion_inverse.py
├── diffusion_inverse_model.py
├── diffusion_solver.py
├── observed_data
├── reaction_kinetics_data.pickle
├── reaction_kinetics_model.py
├── reaction_kinetics_plot.py
├── reaction_kinetics_run.py
├── reaction_kinetics_run_nompi.py
├── reaction_kinetics_solver.py
├── simple_model.py
├── simple_model_run.py
├── simple_model_run_mpi.py
├── test_lognormal.py
├── true_data
├── true_locations
└── true_noise
├── pysmc
├── __init__.py
├── _db.py
├── _mcmc_wrapper.py
├── _misc.py
├── _mpi.py
├── _particle_approximation.py
├── _plot.py
├── _smc.py
└── _step_methods.py
├── setup.cfg
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.~
2 | *.dot
3 | *.txt
4 | *.pyc
5 | *.html
6 | *.pickle
7 | *.inv
8 | *.js
9 | *.out
10 | *doctree*
11 | *.so
12 | *.swp
13 | src/CMakeFiles/
14 | src/Makefile
15 | src/cmake_install.cmake
16 | build
17 | backup
18 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | pysmc
2 | =====
3 |
4 | Sequential Monte Carlo working on top of pymc.
5 |
6 | **Compatible with Python3.**
7 |
8 | The complete documentation can be found here: http://predictivesciencelab.github.io/pysmc/
9 |
--------------------------------------------------------------------------------
/doc/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 = $(PREFIX)/src/pysmcdocs
9 |
10 | # User-friendly check for sphinx-build
11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
13 | endif
14 |
15 | # Internal variables.
16 | PAPEROPT_a4 = -D latex_paper_size=a4
17 | PAPEROPT_letter = -D latex_paper_size=letter
18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
19 | # the i18n builder cannot share the environment and doctrees with the others
20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
21 |
22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
23 |
24 | help:
25 | @echo "Please use \`make ' where is one of"
26 | @echo " html to make standalone HTML files"
27 | @echo " dirhtml to make HTML files named index.html in directories"
28 | @echo " singlehtml to make a single large HTML file"
29 | @echo " pickle to make pickle files"
30 | @echo " json to make JSON files"
31 | @echo " htmlhelp to make HTML files and a HTML help project"
32 | @echo " qthelp to make HTML files and a qthelp project"
33 | @echo " devhelp to make HTML files and a Devhelp project"
34 | @echo " epub to make an epub"
35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
36 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
38 | @echo " text to make text files"
39 | @echo " man to make manual pages"
40 | @echo " texinfo to make Texinfo files"
41 | @echo " info to make Texinfo files and run them through makeinfo"
42 | @echo " gettext to make PO message catalogs"
43 | @echo " changes to make an overview of all changed/added/deprecated items"
44 | @echo " xml to make Docutils-native XML files"
45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
46 | @echo " linkcheck to check all external links for integrity"
47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
48 |
49 | clean:
50 | rm -rf $(BUILDDIR)/*
51 |
52 | html:
53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
54 | @echo
55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
56 |
57 | dirhtml:
58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
59 | @echo
60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
61 |
62 | singlehtml:
63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
64 | @echo
65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
66 |
67 | pickle:
68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
69 | @echo
70 | @echo "Build finished; now you can process the pickle files."
71 |
72 | json:
73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
74 | @echo
75 | @echo "Build finished; now you can process the JSON files."
76 |
77 | htmlhelp:
78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
79 | @echo
80 | @echo "Build finished; now you can run HTML Help Workshop with the" \
81 | ".hhp project file in $(BUILDDIR)/htmlhelp."
82 |
83 | qthelp:
84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
85 | @echo
86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pysmc.qhcp"
89 | @echo "To view the help file:"
90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pysmc.qhc"
91 |
92 | devhelp:
93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
94 | @echo
95 | @echo "Build finished."
96 | @echo "To view the help file:"
97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/pysmc"
98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pysmc"
99 | @echo "# devhelp"
100 |
101 | epub:
102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
103 | @echo
104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
105 |
106 | latex:
107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
108 | @echo
109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
111 | "(use \`make latexpdf' here to do that automatically)."
112 |
113 | latexpdf:
114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
115 | @echo "Running LaTeX files through pdflatex..."
116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
118 |
119 | latexpdfja:
120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
121 | @echo "Running LaTeX files through platex and dvipdfmx..."
122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
124 |
125 | text:
126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
127 | @echo
128 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
129 |
130 | man:
131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
132 | @echo
133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
134 |
135 | texinfo:
136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
137 | @echo
138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
139 | @echo "Run \`make' in that directory to run these through makeinfo" \
140 | "(use \`make info' here to do that automatically)."
141 |
142 | info:
143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
144 | @echo "Running Texinfo files through makeinfo..."
145 | make -C $(BUILDDIR)/texinfo info
146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
147 |
148 | gettext:
149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
150 | @echo
151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
152 |
153 | changes:
154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
155 | @echo
156 | @echo "The overview file is in $(BUILDDIR)/changes."
157 |
158 | linkcheck:
159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
160 | @echo
161 | @echo "Link check complete; look for any errors in the above output " \
162 | "or in $(BUILDDIR)/linkcheck/output.txt."
163 |
164 | doctest:
165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
166 | @echo "Testing of doctests in the sources finished, look at the " \
167 | "results in $(BUILDDIR)/doctest/output.txt."
168 |
169 | xml:
170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
171 | @echo
172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
173 |
174 | pseudoxml:
175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
176 | @echo
177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
178 |
--------------------------------------------------------------------------------
/doc/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% source
10 | set I18NSPHINXOPTS=%SPHINXOPTS% source
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. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. xml to make Docutils-native XML files
37 | echo. pseudoxml to make pseudoxml-XML files for display purposes
38 | echo. linkcheck to check all external links for integrity
39 | echo. doctest to run all doctests embedded in the documentation if enabled
40 | goto end
41 | )
42 |
43 | if "%1" == "clean" (
44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
45 | del /q /s %BUILDDIR%\*
46 | goto end
47 | )
48 |
49 |
50 | %SPHINXBUILD% 2> nul
51 | if errorlevel 9009 (
52 | echo.
53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
54 | echo.installed, then set the SPHINXBUILD environment variable to point
55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
56 | echo.may add the Sphinx directory to PATH.
57 | echo.
58 | echo.If you don't have Sphinx installed, grab it from
59 | echo.http://sphinx-doc.org/
60 | exit /b 1
61 | )
62 |
63 | if "%1" == "html" (
64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
68 | goto end
69 | )
70 |
71 | if "%1" == "dirhtml" (
72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
76 | goto end
77 | )
78 |
79 | if "%1" == "singlehtml" (
80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
84 | goto end
85 | )
86 |
87 | if "%1" == "pickle" (
88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can process the pickle files.
92 | goto end
93 | )
94 |
95 | if "%1" == "json" (
96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
97 | if errorlevel 1 exit /b 1
98 | echo.
99 | echo.Build finished; now you can process the JSON files.
100 | goto end
101 | )
102 |
103 | if "%1" == "htmlhelp" (
104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
105 | if errorlevel 1 exit /b 1
106 | echo.
107 | echo.Build finished; now you can run HTML Help Workshop with the ^
108 | .hhp project file in %BUILDDIR%/htmlhelp.
109 | goto end
110 | )
111 |
112 | if "%1" == "qthelp" (
113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
114 | if errorlevel 1 exit /b 1
115 | echo.
116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
117 | .qhcp project file in %BUILDDIR%/qthelp, like this:
118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pysmc.qhcp
119 | echo.To view the help file:
120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pysmc.ghc
121 | goto end
122 | )
123 |
124 | if "%1" == "devhelp" (
125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished.
129 | goto end
130 | )
131 |
132 | if "%1" == "epub" (
133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
137 | goto end
138 | )
139 |
140 | if "%1" == "latex" (
141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
145 | goto end
146 | )
147 |
148 | if "%1" == "latexpdf" (
149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
150 | cd %BUILDDIR%/latex
151 | make all-pdf
152 | cd %BUILDDIR%/..
153 | echo.
154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
155 | goto end
156 | )
157 |
158 | if "%1" == "latexpdfja" (
159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
160 | cd %BUILDDIR%/latex
161 | make all-pdf-ja
162 | cd %BUILDDIR%/..
163 | echo.
164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
165 | goto end
166 | )
167 |
168 | if "%1" == "text" (
169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
170 | if errorlevel 1 exit /b 1
171 | echo.
172 | echo.Build finished. The text files are in %BUILDDIR%/text.
173 | goto end
174 | )
175 |
176 | if "%1" == "man" (
177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
178 | if errorlevel 1 exit /b 1
179 | echo.
180 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
181 | goto end
182 | )
183 |
184 | if "%1" == "texinfo" (
185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
186 | if errorlevel 1 exit /b 1
187 | echo.
188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
189 | goto end
190 | )
191 |
192 | if "%1" == "gettext" (
193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
194 | if errorlevel 1 exit /b 1
195 | echo.
196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
197 | goto end
198 | )
199 |
200 | if "%1" == "changes" (
201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
202 | if errorlevel 1 exit /b 1
203 | echo.
204 | echo.The overview file is in %BUILDDIR%/changes.
205 | goto end
206 | )
207 |
208 | if "%1" == "linkcheck" (
209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
210 | if errorlevel 1 exit /b 1
211 | echo.
212 | echo.Link check complete; look for any errors in the above output ^
213 | or in %BUILDDIR%/linkcheck/output.txt.
214 | goto end
215 | )
216 |
217 | if "%1" == "doctest" (
218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
219 | if errorlevel 1 exit /b 1
220 | echo.
221 | echo.Testing of doctests in the sources finished, look at the ^
222 | results in %BUILDDIR%/doctest/output.txt.
223 | goto end
224 | )
225 |
226 | if "%1" == "xml" (
227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
228 | if errorlevel 1 exit /b 1
229 | echo.
230 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
231 | goto end
232 | )
233 |
234 | if "%1" == "pseudoxml" (
235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
236 | if errorlevel 1 exit /b 1
237 | echo.
238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
239 | goto end
240 | )
241 |
242 | :end
243 |
--------------------------------------------------------------------------------
/doc/source/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # pysmc documentation build configuration file, created by
4 | # sphinx-quickstart on Thu Sep 26 09:29:15 2013.
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 | import sys
16 | import os
17 |
18 | # If extensions (or modules to document with autodoc) are in another directory,
19 | # add these directories to sys.path here. If the directory is relative to the
20 | # documentation root, use os.path.abspath to make it absolute, like shown here.
21 | #sys.path.insert(0, os.path.abspath('.'))
22 | sys.path.insert(0, os.path.abspath('../../'))
23 |
24 | # -- General configuration ------------------------------------------------
25 |
26 | # If your documentation needs a minimal Sphinx version, state it here.
27 | #needs_sphinx = '1.0'
28 |
29 | # Add any Sphinx extension module names here, as strings. They can be
30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
31 | # ones.
32 | extensions = [
33 | 'sphinx.ext.autodoc',
34 | 'sphinx.ext.intersphinx',
35 | 'sphinx.ext.pngmath'
36 | ]
37 |
38 | # Add any paths that contain templates here, relative to this directory.
39 | templates_path = ['_templates']
40 |
41 | # The suffix of source filenames.
42 | source_suffix = '.rst'
43 |
44 | # The encoding of source files.
45 | #source_encoding = 'utf-8-sig'
46 |
47 | # The master toctree document.
48 | master_doc = 'index'
49 |
50 | # General information about the project.
51 | project = u'pysmc'
52 | copyright = u'2013, Ilias Bilionis'
53 |
54 | # The version info for the project you're documenting, acts as replacement for
55 | # |version| and |release|, also used in various other places throughout the
56 | # built documents.
57 | #
58 | # The short X.Y version.
59 | version = '0.0'
60 | # The full version, including alpha/beta/rc tags.
61 | release = '0.0'
62 |
63 | # The language for content autogenerated by Sphinx. Refer to documentation
64 | # for a list of supported languages.
65 | #language = None
66 |
67 | # There are two options for replacing |today|: either, you set today to some
68 | # non-false value, then it is used:
69 | #today = ''
70 | # Else, today_fmt is used as the format for a strftime call.
71 | #today_fmt = '%B %d, %Y'
72 |
73 | # List of patterns, relative to source directory, that match files and
74 | # directories to ignore when looking for source files.
75 | exclude_patterns = []
76 |
77 | # The reST default role (used for this markup: `text`) to use for all
78 | # documents.
79 | #default_role = None
80 |
81 | # If true, '()' will be appended to :func: etc. cross-reference text.
82 | #add_function_parentheses = True
83 |
84 | # If true, the current module name will be prepended to all description
85 | # unit titles (such as .. function::).
86 | #add_module_names = True
87 |
88 | # If true, sectionauthor and moduleauthor directives will be shown in the
89 | # output. They are ignored by default.
90 | #show_authors = False
91 |
92 | # The name of the Pygments (syntax highlighting) style to use.
93 | pygments_style = 'sphinx'
94 |
95 | # A list of ignored prefixes for module index sorting.
96 | #modindex_common_prefix = []
97 |
98 | # If true, keep warnings as "system message" paragraphs in the built documents.
99 | #keep_warnings = False
100 |
101 |
102 | # -- Options for HTML output ----------------------------------------------
103 |
104 | # The theme to use for HTML and HTML Help pages. See the documentation for
105 | # a list of builtin themes.
106 | html_theme = 'pyramid'
107 |
108 | # Theme options are theme-specific and customize the look and feel of a theme
109 | # further. For a list of options available for each theme, see the
110 | # documentation.
111 | #html_theme_options = {}
112 |
113 | # Add any paths that contain custom themes here, relative to this directory.
114 | #html_theme_path = []
115 |
116 | # The name for this set of Sphinx documents. If None, it defaults to
117 | # " v documentation".
118 | #html_title = None
119 |
120 | # A shorter title for the navigation bar. Default is the same as html_title.
121 | #html_short_title = None
122 |
123 | # The name of an image file (relative to this directory) to place at the top
124 | # of the sidebar.
125 | #html_logo = None
126 |
127 | # The name of an image file (within the static path) to use as favicon of the
128 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
129 | # pixels large.
130 | #html_favicon = None
131 |
132 | # Add any paths that contain custom static files (such as style sheets) here,
133 | # relative to this directory. They are copied after the builtin static files,
134 | # so a file named "default.css" will overwrite the builtin "default.css".
135 | html_static_path = ['_static']
136 |
137 | # Add any extra paths that contain custom files (such as robots.txt or
138 | # .htaccess) here, relative to this directory. These files are copied
139 | # directly to the root of the documentation.
140 | #html_extra_path = []
141 |
142 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
143 | # using the given strftime format.
144 | #html_last_updated_fmt = '%b %d, %Y'
145 |
146 | # If true, SmartyPants will be used to convert quotes and dashes to
147 | # typographically correct entities.
148 | #html_use_smartypants = True
149 |
150 | # Custom sidebar templates, maps document names to template names.
151 | #html_sidebars = {}
152 |
153 | # Additional templates that should be rendered to pages, maps page names to
154 | # template names.
155 | #html_additional_pages = {}
156 |
157 | # If false, no module index is generated.
158 | #html_domain_indices = True
159 |
160 | # If false, no index is generated.
161 | #html_use_index = True
162 |
163 | # If true, the index is split into individual pages for each letter.
164 | #html_split_index = False
165 |
166 | # If true, links to the reST sources are added to the pages.
167 | #html_show_sourcelink = True
168 |
169 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
170 | #html_show_sphinx = True
171 |
172 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
173 | #html_show_copyright = True
174 |
175 | # If true, an OpenSearch description file will be output, and all pages will
176 | # contain a tag referring to it. The value of this option must be the
177 | # base URL from which the finished HTML is served.
178 | #html_use_opensearch = ''
179 |
180 | # This is the file name suffix for HTML files (e.g. ".xhtml").
181 | #html_file_suffix = None
182 |
183 | # Output file base name for HTML help builder.
184 | htmlhelp_basename = 'pysmcdoc'
185 |
186 |
187 | # -- Options for LaTeX output ---------------------------------------------
188 |
189 | latex_elements = {
190 | # The paper size ('letterpaper' or 'a4paper').
191 | #'papersize': 'letterpaper',
192 |
193 | # The font size ('10pt', '11pt' or '12pt').
194 | #'pointsize': '10pt',
195 |
196 | # Additional stuff for the LaTeX preamble.
197 | #'preamble': '',
198 | }
199 |
200 | # Grouping the document tree into LaTeX files. List of tuples
201 | # (source start file, target name, title,
202 | # author, documentclass [howto/manual]).
203 | latex_documents = [
204 | ('index', 'pysmc.tex', u'pysmc Documentation',
205 | u'Ilias Bilionis', 'manual'),
206 | ]
207 |
208 | # The name of an image file (relative to this directory) to place at the top of
209 | # the title page.
210 | #latex_logo = None
211 |
212 | # For "manual" documents, if this is true, then toplevel headings are parts,
213 | # not chapters.
214 | #latex_use_parts = False
215 |
216 | # If true, show page references after internal links.
217 | #latex_show_pagerefs = False
218 |
219 | # If true, show URL addresses after external links.
220 | #latex_show_urls = False
221 |
222 | # Documents to append as an appendix to all manuals.
223 | #latex_appendices = []
224 |
225 | # If false, no module index is generated.
226 | #latex_domain_indices = True
227 |
228 |
229 | # -- Options for manual page output ---------------------------------------
230 |
231 | # One entry per manual page. List of tuples
232 | # (source start file, name, description, authors, manual section).
233 | man_pages = [
234 | ('index', 'pysmc', u'pysmc Documentation',
235 | [u'Ilias Bilionis'], 1)
236 | ]
237 |
238 | # If true, show URL addresses after external links.
239 | #man_show_urls = False
240 |
241 |
242 | # -- Options for Texinfo output -------------------------------------------
243 |
244 | # Grouping the document tree into Texinfo files. List of tuples
245 | # (source start file, target name, title, author,
246 | # dir menu entry, description, category)
247 | texinfo_documents = [
248 | ('index', 'pysmc', u'pysmc Documentation',
249 | u'Ilias Bilionis', 'pysmc', 'One line description of project.',
250 | 'Miscellaneous'),
251 | ]
252 |
253 | # Documents to append as an appendix to all manuals.
254 | #texinfo_appendices = []
255 |
256 | # If false, no module index is generated.
257 | #texinfo_domain_indices = True
258 |
259 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
260 | #texinfo_show_urls = 'footnote'
261 |
262 | # If true, do not generate a @detailmenu in the "Top" node's menu.
263 | #texinfo_no_detailmenu = False
264 |
265 |
266 | # Example configuration for intersphinx: refer to the Python standard library.
267 | intersphinx_mapping = {'http://docs.python.org/': None}
268 |
--------------------------------------------------------------------------------
/doc/source/images/simple_model_mcmc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PredictiveScienceLab/pysmc/fc6f1b2473d9067c108d6b3d7f27f29d3b521228/doc/source/images/simple_model_mcmc.png
--------------------------------------------------------------------------------
/doc/source/images/simple_model_mcmc_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PredictiveScienceLab/pysmc/fc6f1b2473d9067c108d6b3d7f27f29d3b521228/doc/source/images/simple_model_mcmc_right.png
--------------------------------------------------------------------------------
/doc/source/images/simple_model_pdf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PredictiveScienceLab/pysmc/fc6f1b2473d9067c108d6b3d7f27f29d3b521228/doc/source/images/simple_model_pdf.png
--------------------------------------------------------------------------------
/doc/source/images/simple_model_pdf_family.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PredictiveScienceLab/pysmc/fc6f1b2473d9067c108d6b3d7f27f29d3b521228/doc/source/images/simple_model_pdf_family.png
--------------------------------------------------------------------------------
/doc/source/images/simple_model_smc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PredictiveScienceLab/pysmc/fc6f1b2473d9067c108d6b3d7f27f29d3b521228/doc/source/images/simple_model_smc.png
--------------------------------------------------------------------------------
/doc/source/index.rst:
--------------------------------------------------------------------------------
1 | .. pysmc documentation master file, created by
2 | sphinx-quickstart on Thu Sep 26 09:29:15 2013.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to pysmc's documentation!
7 | =================================
8 |
9 | :mod:`pysmc` is a Python package for sampling complicated probability
10 | densities using the celebrated Sequential Monte Carlo method.
11 | The core of this package was written during my PhD at
12 | `Cornell University `_
13 | at the
14 | `Materials Process Design and Controll Laboratory `_
15 | under the
16 | supervision of Prof. `Nicholas Zabaras `_. It is part of the
17 | `Bayesian Exploration Statistical Toolbox (BEST)
18 | `_ initiative.
19 | The coupling of the code with `PyMC `_
20 | took part at
21 | the `Mathematics and Computer Science Division `_
22 | at `Argonne National Laboratory `_.
23 |
24 | Contents:
25 |
26 | .. toctree::
27 | :maxdepth: 3
28 |
29 | install
30 | tutorial
31 | reference
32 | math
33 |
34 |
35 | Indices and tables
36 | ==================
37 |
38 | * :ref:`genindex`
39 | * :ref:`modindex`
40 | * :ref:`search`
41 |
42 | .. _PyMC:
43 | http://pymc-devs.github.io/pymc/
44 |
--------------------------------------------------------------------------------
/doc/source/install.rst:
--------------------------------------------------------------------------------
1 | .. _install:
2 |
3 | ============
4 | Installation
5 | ============
6 |
7 | We describe the necessary steps required to succesfully install
8 | :mod:`pysmc`. The most important prior step is to satisfy the dependencies
9 | of the code.
10 |
11 |
12 | .. _depend:
13 |
14 | -----------
15 | Dependecies
16 | -----------
17 |
18 | There are two different categories of packages on which we rely. The
19 | :ref:`required` ones have to be there no matter what. The :ref:`optional`
20 | ones can be skipped but without them you will loose some of the
21 | functionality of :mod:`pysmc` or/and you will not be able to run all the
22 | examples in the :mod:`tutorial`.
23 |
24 | .. _required:
25 |
26 | Required
27 | ++++++++
28 | The following packages are required:
29 |
30 | + `Numpy `_ for linear algebra.
31 | + `SciPy `_ for some root finding methods.
32 | + `PyMC `_ for general probabilistic
33 | model definition and MCMC sampling. We suggest that you install the
34 | `latest PyMC version from GitHub `_
35 | .
36 |
37 |
38 | .. _optional:
39 |
40 | Optional
41 | ++++++++
42 | The following packages are optional but highly recommended:
43 |
44 | + `MPI4PY `_ to enable the parallel capabilities of
45 | :mod:`pysmc`.
46 | + `matplotlib `_ for plotting.
47 | + `FiPy `_ in order to run the
48 | :ref:`diffusion_example`.
49 |
50 | .. _final_steps:
51 |
52 | -----------
53 | Final Steps
54 | -----------
55 | As soon as you are done installing the packages above, you can fetch
56 | :mod:`pysmc` from `GitHub `_ by::
57 |
58 | git clone https://github.com/ebilionis/pysmc.git
59 |
60 | Then, all you have to do is enter the ``pysmc`` directory that was created
61 | and run::
62 |
63 | python setup.py install
64 |
65 | If you want to put the code in an non default location, simply do::
66 |
67 | python setup.py install --prefix=/path/to/your/directory
68 |
69 | If you do the latter, make sure you update your ``PYTHONPATH`` variable::
70 |
71 | export PYTHONPATH=/path/to/your/directory/lib/python2.7/site-packages:$PYTHONPATH
72 |
73 |
--------------------------------------------------------------------------------
/doc/source/math.rst:
--------------------------------------------------------------------------------
1 | .. _math:
2 |
3 | ====================
4 | Mathematical Details
5 | ====================
6 |
--------------------------------------------------------------------------------
/doc/source/reference.rst:
--------------------------------------------------------------------------------
1 | .. _reference:
2 |
3 | =========
4 | Reference
5 | =========
6 |
7 | The goal of :mod:`pysmc` is to implement Sequential Monte Carlo (SMC)
8 | techniques on top of the Monte Carlo (MC) package
9 | `PyMC `_. The manual assumes that the user
10 | is already familiar with the way PyMC works. You are advised to read their
11 | tutorial before going on. A nice place to start with :mod:`pysmc` is
12 | our :ref:`tutorial`.
13 |
14 | .. automodule:: pysmc
15 |
--------------------------------------------------------------------------------
/doc/source/tutorial.rst:
--------------------------------------------------------------------------------
1 | .. _tutorial:
2 |
3 | ========
4 | Tutorial
5 | ========
6 |
7 |
8 | Before moving forward, make sure you understand:
9 |
10 | + What is probability? That's a big question... If you feel like it,
11 | I suggest you skim through `E. T. Jaynes`_'s book
12 | `Probability Theory: The Logic of Science`_.
13 | + What is `MCMC`_?
14 | + Read the tutorial of `PyMC`_.
15 | To the very least, you need to be able to construct probabilistic
16 | models using this package. For advanced applications, you need to be
17 | able to construct your own `MCMC step methods`_.
18 | + Of course, I have to assume some familiarity with the so called
19 | Sequential Monte Carlo (SMC) or Particle Methods. Many resources can
20 | be found online at `Arnaud Doucet's collection`_ or in his book
21 | `Sequential Monte Carlo Methods in Practice`_. What exactly :mod:`pysmc`
22 | does is documented in :ref:`math`.
23 |
24 |
25 | .. _what is smc:
26 |
27 | -------------------------------
28 | What is Sequential Monte Carlo?
29 | -------------------------------
30 |
31 | Sequential Monte Carlo (SMC) is a very efficient and effective way to sample
32 | from complicated probability distributions known up to a normalizing constant.
33 | The most important complicating factor is multi-modality. That is, probability
34 | distributions that do not look at all like Gaussians.
35 |
36 | The complete details can be found in :ref:`math`. However, let us give some
37 | insights on what is really going on. Assume that we want so sample
38 | from a probability distribution :math:`p(x)`, known up to a normalizing
39 | constant:
40 |
41 | .. math::
42 |
43 | p(x) \propto \pi(x).
44 |
45 | Normally, we construct a variant of `MCMC`_ to sample from this
46 | distribution. If :math:`p(x)` is multi-modal, it is certain that the Markov
47 | Chain will get attracted to one of the modes. Theoretically, if the modes are
48 | connected, it is guaranteed that they will all be visited
49 | *as the number of MCMC steps goes to infinity*. However, depending on the
50 | probability of the paths that connect the modes, the chain might
51 | never escape during the finite number of MCMC steps that we can actually afford
52 | to perform.
53 |
54 | SMC attempts to alleviate this problem. The way it does it is similar to the
55 | ideas found in `Simulated Annealing`_. The user defines a family of probability
56 | densities:
57 |
58 | .. math::
59 | p_{i}(x) \propto \pi_{i}(x),\;i=1,\dots,n,
60 | :label: smc_sequence
61 |
62 | such that:
63 |
64 | + it is easy to sample from :math:`p_0(x)` (either directly or using MCMC),
65 | + the probability densities :math:`p_i(x)` and :math:`p_{i+1}(x)` are
66 | *similar*,
67 | + the last probability density of the sequence is the target, i.e.,
68 | :math:`p_n(x) = p(x)`.
69 |
70 | There are many ways to define such a sequence. Usually, the exact sequence that
71 | needs to be followed is obvious from the definition of the problem. An obvious
72 | choice is:
73 |
74 | .. math::
75 | p_i(x) \propto \pi^{\gamma_i}(x),\;i=1,\dots,n,
76 | :label: smc_sequence_power
77 |
78 | where :math:`\gamma_0` is a non-negative number that makes :math:`p_i(x)` look
79 | flat (e.g., if :math:`p(x)` has a compact support, you may choose
80 | :math:`\gamma_0=0` which makes :math:`p_0(x)` the uniform density. For
81 | the general case a choice like :math:`\gamma_0=10^{-3}` would still do a good
82 | job) and :math:`\gamma_n=1`. If :math:`n` is chosen sufficiently large and
83 | :math:`gamma_i < \gamma_{i+1}` then indeed :math:`p_i(x)` and :math:`p_{i+1}(x)`
84 | will look similar.
85 |
86 | Now we are in a position to discuss what SMC does. We represent each one of the
87 | probability densities :math:`p_i(x)`
88 | :eq:`smc_sequence` with a *particle approximation*
89 | :math:`\left\{\left(w^{(j)_i}, x^{(j)_i}\right)\right\}_{j=1}^N`, where:
90 |
91 | + :math:`N` is known as the *number of particles*,
92 | + :math:`w^{(j)}_i` is known as the *weight* of particle :math:`j`
93 | (normalized so that :math:`\sum_{j=1}^Nw^{(j)}_i=1`),
94 | + :math:`x^{(j)}_i` is known as the *particle* :math:`j`.
95 |
96 | Typically we write:
97 |
98 | .. math::
99 | p_i(x) \approx \sum_{j=1}^Nw^{(j)}_i\delta\left(x - x^{(j)}_i\right),
100 | :label: smc_approx
101 |
102 | but what we really mean is that for any measurable
103 | function of the state space :math:`f(x)` the following holds:
104 |
105 | .. math::
106 | \lim_{N\rightarrow\infty}\sum_{j=1}^Nw_i^{(j)}f\left(x^{(j)}_i\right) = \
107 | \int f(x) p_i(x)dx,
108 | :label: smc_approx_def
109 |
110 | almost surely.
111 |
112 | So far so good. The only issue here is actually constructing a particle
113 | approximation satisfying :eq:`smc_approx_def`. This is a little bit involved
114 | and thus described in :ref:`math`. Here it suffices to say that it more or less
115 | goes like this:
116 |
117 | 1. Start with :math:`i=0` (i.e., the easy to sample distribution).
118 | 2. Sample :math:`x_0^{(j)}` from :math:`p_0(x)` either directly (if possible) or
119 | using MCMC and set the weights equal to :math:`w_0^{(j)} = 1 / N`. Then
120 | :eq:`smc_approx_def` is satisfied for :math:`i=0`.
121 | 3. Compute the weights :math:`w_{i+1}(j)` and sample -using an appropriate MCMC
122 | kernel- the particles of the next step :math:`x_i^{(j+1)}` so that they
123 | corresponding particle approximation satisfies :eq:`smc_approx_def`.
124 | 4. Set :math:`i=i+1`.
125 | 5. If :math:`i=n` stop. Otherwise go to 3.
126 |
127 | .. _what is in pysmc:
128 |
129 | ------------------------------------
130 | What is implemented in :mod:`pysmc`?
131 | ------------------------------------
132 |
133 | :mod:`pysmc` implements something a little bit more complicated than what is
134 | described in :ref:`what is smc`. The full description can be found in
135 | :ref:`math`. Basically, we assume that the user has defined a one-parameter
136 | family of probability densities:
137 |
138 | .. math::
139 | p_{\gamma}(x) \propto \pi_{\gamma}(x).
140 | :label: p_gamma
141 |
142 | The code must be initialized with a particle approximation at a desired value
143 | of :math:`\gamma=\gamma_0`. This can be done either manually by the user or
144 | automatically by :mod:`pysmc` (e.g. by direct sampling or MCMC).
145 | Having constructed an initial particle approximation, the code can be instructed
146 | to move it to another :math:`\gamma=\gamma_1`. If the two probability densities
147 | :math:`p_{\gamma_0}(x)` and :math:`p_{\gamma_1}(x)` are close, then the code
148 | will jump directly into the construction of the particle approximation at
149 | :math:`\gamma=\gamma_1`. If not, then it will adaptively construct a finite
150 | sequence of :math:`\gamma`'s connecting :math:`\gamma_0` and :math:`\gamma_1`
151 | and jump from one to the other. Therefore, the user only needs to specify:
152 |
153 | + the initial, easy-to-sample-from probability density,
154 | + the target density,
155 | + a one-parametric family of densities that connect the two.
156 |
157 | We will see how this can be achieved through a bunch of examples.
158 |
159 |
160 | .. _simple example:
161 |
162 | ----------------
163 | A Simple Example
164 | ----------------
165 |
166 | We will start with a probability density with two modes, namely a mixture of
167 | two normal densities:
168 |
169 | .. math::
170 | p(x) = \pi_1 \mathcal{N}\left(x | \mu_1, \sigma_1^2 \right) + \
171 | \pi_2 \mathcal{N}\left(x | \mu_2, \sigma_2^2 \right),
172 | :label: simple_model_pdf
173 |
174 | where :math:`\mathcal{N}(x|\mu, \sigma^2)` denotes the probability density of a
175 | normal random variable with mean :math:`\mu` and variance :math:`\sigma^2`.
176 | :math:`\pi_i>0` is the weight given to the :math:`i`-th normal
177 | (:math:`\pi_1 + \pi_2 = 1`) and :math:`\mu_i, \sigma_i^2` are the corresponding
178 | mean and variance. We pick the following parameters:
179 |
180 | + :math:`\pi_1=0.2, \mu_1=-1, \sigma_1=0.01`,
181 | + :math:`\pi_2=0.8, \mu_2=2, \sigma_2=0.01`.
182 |
183 | This probability density is shown in `Simple Example PDF Figure`_. It is obvious
184 | that sampling this probability density using MCMC will be very problematic.
185 |
186 | .. _Simple Example PDF Figure:
187 | .. figure:: images/simple_model_pdf.png
188 | :align: center
189 |
190 | Plot of :eq:`simple_model_pdf` with
191 | :math:`\pi_1=0.2, \mu_1=-1, \sigma_1=0.01` and
192 | :math:`\pi_2=0.8, \mu_2=2, \sigma_2=0.01`.
193 |
194 | .. _simple example pdf family:
195 |
196 | ++++++++++++++++++++++++++++++++++++++++++++++++++
197 | Defining a family of probability densities for SMC
198 | ++++++++++++++++++++++++++++++++++++++++++++++++++
199 |
200 | Remember that our goal is to sample :eq:`simple_model_pdf` using SMC. Towards
201 | this goal we need to define a one-parameter family of probability densities
202 | :eq:`p_gamma` starting from a simple one to our target. The simplest choice
203 | is probably this:
204 |
205 | .. math::
206 | \pi_{\gamma}(x) = p^{\gamma}(x).
207 | :label: simple_model_pdf_family
208 |
209 | Notice that: 1) for :math:`\gamma=1` we obtain :math:`p_\gamma(x)` and 2) for
210 | :math:`\gamma` small (say :math:`\gamma=10^{-2}`) we obtain a relatively flat
211 | probability density. See `Simple Example Family of PDF's Figure`_.
212 |
213 | .. _Simple Example Family of PDF's Figure:
214 | .. figure:: images/simple_model_pdf_family.png
215 | :align: center
216 |
217 | Plot of :math:`\pi_\gamma(x)` of :eq:`simple_model_pdf_family` for
218 | various :math:`\gamma`'s.
219 |
220 | .. _simple example model:
221 |
222 | ++++++++++++++++++++++++
223 | Defining a `PyMC`_ model
224 | ++++++++++++++++++++++++
225 |
226 | Since, this is our very first example we will use it as an opportunity to show
227 | how `PyMC`_ can be used to define probabilistic models as well as MCMC sampling
228 | algorithms. First of all let us mention that a `PyMC` model has to be packaged
229 | either in a class or in a module. For the simple example we are considering, we
230 | choose to use the module approach (see
231 | :download:`examples/simple_model.py <../../examples/simple_model.py>`).
232 | The model can be trivially defined using `PyMC` decorators. All we
233 | have to do is define the logarithm of :math:`\pi_{\gamma}(x)`. We will call it
234 | ``mixture``. The contents of that module are:
235 |
236 | .. code-block:: python
237 | :linenos:
238 | :emphasize-lines: 6,7
239 |
240 | import pymc
241 | import numpy as np
242 | import math
243 |
244 | @pymc.stochastic(dtype=float)
245 | def mixture(value=1., gamma=1., pi=[0.2, 0.8], mu=[-1., 2.],
246 | sigma=[0.01, 0.01]):
247 | """
248 | The log probability of a mixture of normal densities.
249 |
250 | :param value: The point of evaluation.
251 | :type value : float
252 | :param gamma: The parameter characterizing the SMC one-parameter
253 | family.
254 | :type gamma : float
255 | :param pi : The weights of the components.
256 | :type pi : 1D :class:`numpy.ndarray`
257 | :param mu : The mean of each component.
258 | :type mu : 1D :class:`numpy.ndarray`
259 | :param sigma: The standard deviation of each component.
260 | :type sigma : 1D :class:`numpy.ndarray`
261 | """
262 | # Make sure everything is a numpy array
263 | pi = np.array(pi)
264 | mu = np.array(mu)
265 | sigma = np.array(sigma)
266 | # The number of components in the mixture
267 | n = pi.shape[0]
268 | # pymc.normal_like requires the precision not the variance:
269 | tau = np.sqrt(1. / sigma ** 2)
270 | # The following looks a little bit awkward because of the need for
271 | # numerical stability:
272 | p = np.log(pi)
273 | p += np.array([pymc.normal_like(value, mu[i], tau[i])
274 | for i in range(n)])
275 | p = math.fsum(np.exp(p))
276 | # logp should never be negative, but it can be zero...
277 | if p <= 0.:
278 | return -np.inf
279 | return gamma * math.log(p)
280 |
281 | This might look a little bit complicated but unfortunately one has to take care
282 | of round-off errors when sump small numbers...
283 | Notice that, we have defined pretty much every part of the mixture as an
284 | independent variable. The essential variable that defines the family of
285 | :eq:`simple_model_pdf_family` is ``gamma``. Well, you don't actually have to
286 | call it ``gamma``, but we will talk about this later...
287 |
288 | Let's import that module and see what we can do with it::
289 |
290 | >>> import simple_model as model
291 | >>> print model.mixture.parents
292 | {'mu': [-1.0, 2.0], 'pi': [0.2, 0.8], 'sigma': [0.01, 0.01], 'gamma': 1.0}
293 |
294 | The final command shows you all the parents of the stochastic variable
295 | ``mixture``.
296 | The stochastic variable mixture was assigned a value by default (see line 4
297 | at the code block above). You can see the current value of the stochastic
298 | variable at any time by doing::
299 |
300 | >>> print model.mixture.value
301 | 1.0
302 |
303 | If we started a `MCMC` chain at this point, this would be the initial value of
304 | the chain. You can change it to anything you want by simply doing::
305 |
306 | >>> model.mixture.value = 0.5
307 | >>> print model.mixture.value
308 | 0.5
309 |
310 | To see the logarithm of the probability at the current state of the stochastic
311 | variable, do::
312 |
313 | >>> print model.mixture.logp
314 | -111.11635344
315 |
316 | Now, if you want to change, let's say, ``gamma`` to ``0.5`` all
317 | you have to do is::
318 |
319 | >>> model.mixture.parents['gamma'] = 0.5
320 | >>> print model.mixture.gamma
321 | 0.5
322 |
323 | The logarithm of the probability should have changed also::
324 |
325 | >>> print model.mixture.logp
326 | -55.5581767201
327 |
328 | .. _mcmc_attempt:
329 |
330 | ++++++++++++++++++++++++
331 | Attempting to do `MCMC`_
332 | ++++++++++++++++++++++++
333 |
334 | Let's load the model again and attempt to do `MCMC`_ using `PyMC`_'s
335 | functionality::
336 |
337 | >>> import simple_model as model
338 | >>> import pymc
339 | >>> mcmc_sampler = pymc.MCMC(model)
340 | >>> mcmc_sampler.sample(1000000, thin=1000, burn=1000)
341 |
342 | You should see a progress bar measuring the number of samples taken. It should
343 | take about a minute to finish. We are actually doing :math:`10^6` `MCMC`_ steps,
344 | we burn the first ``burn = 1000`` samples and we are looking at the chain
345 | every ``thin = 1000`` samples (i.e., we are dropping everything in between).
346 | `PyMC`_ automatically picks a proposal (see `MCMC step methods`_) for you. For
347 | this particular example it should have picked
348 | :class:`pymc.step_methods.Metropolis` which corresponds to a simple random walk
349 | proposal. There is no need to tune the parameters of the random walk since
350 | `PyMC`_ is supposed to do that for you. In any case, it is possible to find the
351 | right variance for the random walk, but you need to know exactly how far apart
352 | the modes are...
353 |
354 | You may look at the samples we've got by doing::
355 |
356 | >>> print mcmc_sampler.trace('mixture')[:]
357 | [ 1.9915846 1.93300521 2.09291872 2.05159841 2.06620882 1.88901709
358 | 1.89521431 1.9631256 2.0363258 1.9756637 2.04818845 1.85036634
359 | 1.98907666 1.82212356 1.97678175 1.99854311 1.92124829 2.02077581
360 | 2.08536334 2.16664208 2.08328293 2.05378638 1.89437676 2.09555348
361 | ...
362 |
363 | Now, let us plot the results::
364 |
365 | >>> import matplotlib.pyplot as plt
366 | >>> pymc.plot(mcmc_sampler)
367 | >>> plt.show()
368 |
369 | The results are shown in `Simple Example MCMC Figure`_. Unless, you are
370 | extremely lucky, you should have missed one of the modes...
371 |
372 | .. _Simple Example MCMC Figure:
373 | .. figure:: images/simple_model_mcmc.png
374 | :align: center
375 |
376 | MCMC fails to capture one of the modes of :eq:`simple_model_pdf`.
377 |
378 | Now, let's see what it takes to make it work. Basically, what we need to
379 | do is find the right step for the random walk proposal. Looking at
380 | `Simple Example Family of PDF's Figure`_, it is easy to guess that the
381 | right step is :math:`3`. So, let's try this::
382 |
383 | >>> import simple_model as model
384 | >>> import pymc
385 | >>> mcmc_sampler = pymc.MCMC(simple_model)
386 | >>> proposal = pymc.Metropolis(model.mixture, proposal_sd=3.)
387 | >>> mcmc_sampler.step_method_dict[model.mixture][0] = proposal
388 | >>> mcmc_sampler.sample(1000000, thin=1000, burn=0,
389 | tune_throughout=False)
390 |
391 | For more details on selecting/tuning step methods see
392 | `MCMC step methods`_. In the last line we have asked `PyMC`_ not to tune
393 | the parameters of the step method. If we didn't do that, it would be
394 | fooled again. The results are shown in
395 | `Simple Example MCMC Picked Proposal Figure`_.
396 |
397 | .. _Simple Example MCMC Picked Proposal Figure:
398 | .. figure:: images/simple_model_mcmc_right.png
399 | :align: center
400 |
401 | Multi-modal distributions can be captured by simple `MCMC`_ only if
402 | you have significant prior knowledge about them.
403 |
404 | You see that the two modes can be captured by plain `MCMC`_ if we
405 | actually know how far appart they are. Of course, this is completely
406 | useless in a real problem. Most of the times, we are not be able to
407 | draw the probability density function and see where the modes are.
408 | SMC is here to save the day!
409 |
410 | .. _smc_attempt:
411 |
412 | +++++++++
413 | Doing SMC
414 | +++++++++
415 |
416 | To finish this example, let's just see how SMC behaves. As we mentioned
417 | earlier, SMC requires:
418 |
419 | + a one-parameter family of probability densities connecting a simple
420 | probability density to our target (we created this in
421 | :ref:`simple_example_model`), and
422 | + an `MCMC` sampler (we saw how to create one in :ref:`mcmc_attempt`).
423 |
424 | Now, let's put everything together using the functionality of
425 | :class:`pysmc.SMC`:
426 |
427 | .. code-block:: python
428 | :linenos:
429 |
430 | import simple_model as model
431 | import pymc
432 | import pysmc
433 |
434 | # Construct the MCMC sampler
435 | mcmc_sampler = pymc.MCMC(model)
436 | # Construct the SMC sampler
437 | smc_sampler = pysmc.SMC(mcmc_sampler, num_particles=1000,
438 | num_mcmc=10, verbose=1)
439 | # Initialize SMC at gamma = 0.01
440 | smc_sampler.initialize(0.01)
441 | # Move the particles to gamma = 1.0
442 | smc_sampler.move_to(1.0)
443 | # Get a particle approximation
444 | p = smc_sampler.get_particle_approximation()
445 | # Plot a histogram
446 | pysmc.hist(p, 'mixture')
447 | plt.show()
448 |
449 | This code can be found in
450 | :download:`examples/simple_model_run.py <../../examples/simple_model_run.py>`.
451 |
452 | In lines 8-9, we initialize the SMC class. Of course, it requires a
453 | ``mcmc_sampler`` which is a :class:`pymc.MCMC` object.
454 | ``num_particles`` specifies the number of particles we wish to use and
455 | ``num_mcmc`` the number of `MCMC`_ steps we are going to perform at each
456 | different value of :math:`\gamma`. The ``verbose`` parameter specifies
457 | the amount of text the algorithm prints to the standard output.
458 | There are many more parameters which are fully documented in
459 | :class:`pysmc.SMC`. In line 11, we initialize the algorithm at
460 | :math:`\gamma=10^{-2}`. This essentially performs a number of `MCMC`_
461 | steps at this easy-to-sample-from probability density. It constructs the
462 | initialial particle approximation. See
463 | :meth:`pysmc.SMC.initialize()` the complete list of arguments.
464 | In line 13, we instruct the object to move the particle
465 | approximation to :math:`\gamma=1`, i.e., to the target probability
466 | density of this particular example. To see the weights of the final
467 | particle approximation, we use :attr:`pysmc.SMC.weights`
468 | (e.g., ``smc_sampler.weights``). To get the particles themselves we may
469 | use :attr:`pysmc.SMC.particles` (e.g., ``smc_sampler.particles``) which
470 | returns a dictionary of the particles. However, it is usually most
471 | convenient to access them via :meth:`pysmc.SMC.get_particle_approximation()`
472 | which returns a :class:`pysmc.ParticleApproximation` object.
473 |
474 | The output of the algorithm looks like this::
475 |
476 | ------------------------
477 | START SMC Initialization
478 | ------------------------
479 | - initializing at gamma : 0.01
480 | - initializing by sampling from the prior: FAILURE
481 | - initializing via MCMC
482 | - taking a total of 10000
483 | - creating a particle every 10
484 | [---------------- 43% ] 4392 of 10000 complete in 0.5 sec
485 | [-----------------83%----------- ] 8322 of 10000 complete in 1.0 sec
486 | ----------------------
487 | END SMC Initialization
488 | ----------------------
489 | -----------------
490 | START SMC MOVE TO
491 | -----------------
492 | initial gamma : 0.01
493 | final gamma : 1.0
494 | ess reduction: 0.9
495 | - moving to gamma : 0.0204271802459
496 | - performing 10 MCMC steps per particle
497 | [------------ 31% ] 3182 of 10000 complete in 0.5 sec
498 | [-----------------62%--- ] 6292 of 10000 complete in 1.0 sec
499 | [-----------------93%--------------- ] 9322 of 10000 complete in 1.5 sec
500 | - moving to gamma : 0.0382316662144
501 | - performing 10 MCMC steps per particle
502 | [---------- 28% ] 2822 of 10000 complete in 0.5 sec
503 | [-----------------55%- ] 5542 of 10000 complete in 1.0 sec
504 | [-----------------81%----------- ] 8162 of 10000 complete in 1.5 sec
505 | - moving to gamma : 0.0677677161458
506 | - performing 10 MCMC steps per particle
507 | [--------- 24% ] 2442 of 10000 complete in 0.5 sec
508 | [-----------------47% ] 4732 of 10000 complete in 1.0 sec
509 | [-----------------70%------ ] 7032 of 10000 complete in 1.5 sec
510 | [-----------------91%-------------- ] 9172 of 10000 complete in 2.0 sec
511 | - moving to gamma : 0.118156872826
512 | - performing 10 MCMC steps per particle
513 | [-------- 21% ] 2122 of 10000 complete in 0.5 sec
514 | [--------------- 42% ] 4202 of 10000 complete in 1.0 sec
515 | [-----------------62%--- ] 6232 of 10000 complete in 1.5 sec
516 | [-----------------81%----------- ] 8172 of 10000 complete in 2.0 sec
517 | - moving to gamma : 0.206496296978
518 | - performing 10 MCMC steps per particle
519 | [----- 15% ] 1502 of 10000 complete in 0.5 sec
520 | [------------ 33% ] 3332 of 10000 complete in 1.0 sec
521 | [-----------------48% ] 4882 of 10000 complete in 1.5 sec
522 | [-----------------66%----- ] 6602 of 10000 complete in 2.0 sec
523 | [-----------------82%----------- ] 8252 of 10000 complete in 2.5 sec
524 | [-----------------96%---------------- ] 9692 of 10000 complete in 3.0 sec
525 | - moving to gamma : 0.326593174468
526 | - performing 10 MCMC steps per particle
527 | [------ 16% ] 1642 of 10000 complete in 0.5 sec
528 | [------------ 32% ] 3252 of 10000 complete in 1.0 sec
529 | [-----------------48% ] 4842 of 10000 complete in 1.5 sec
530 | [-----------------64%---- ] 6412 of 10000 complete in 2.0 sec
531 | [-----------------79%---------- ] 7942 of 10000 complete in 2.5 sec
532 | [-----------------94%--------------- ] 9432 of 10000 complete in 3.0 sec
533 | - moving to gamma : 0.494246909688
534 | - performing 10 MCMC steps per particle
535 | [----- 14% ] 1402 of 10000 complete in 0.5 sec
536 | [---------- 28% ] 2832 of 10000 complete in 1.0 sec
537 | [---------------- 42% ] 4232 of 10000 complete in 1.5 sec
538 | [-----------------56%- ] 5622 of 10000 complete in 2.0 sec
539 | [-----------------69%------ ] 6992 of 10000 complete in 2.5 sec
540 | [-----------------83%----------- ] 8342 of 10000 complete in 3.0 sec
541 | [-----------------96%---------------- ] 9662 of 10000 complete in 3.5 sec
542 | - moving to gamma : 0.680964613537
543 | - performing 10 MCMC steps per particle
544 | [---- 13% ] 1302 of 10000 complete in 0.5 sec
545 | [--------- 25% ] 2552 of 10000 complete in 1.0 sec
546 | [-------------- 38% ] 3832 of 10000 complete in 1.5 sec
547 | [-----------------50% ] 5032 of 10000 complete in 2.0 sec
548 | [-----------------62%--- ] 6272 of 10000 complete in 2.5 sec
549 | [-----------------75%-------- ] 7502 of 10000 complete in 3.0 sec
550 | [-----------------86%------------ ] 8672 of 10000 complete in 3.5 sec
551 | [-----------------98%----------------- ] 9842 of 10000 complete in 4.0 sec
552 | - moving to gamma : 1.0
553 | - performing 10 MCMC steps per particle
554 | [---- 11% ] 1172 of 10000 complete in 0.5 sec
555 | [-------- 23% ] 2302 of 10000 complete in 1.0 sec
556 | [------------ 34% ] 3412 of 10000 complete in 1.5 sec
557 | [-----------------44% ] 4482 of 10000 complete in 2.0 sec
558 | [-----------------55%- ] 5582 of 10000 complete in 2.5 sec
559 | [-----------------67%----- ] 6702 of 10000 complete in 3.0 sec
560 | [-----------------77%--------- ] 7792 of 10000 complete in 3.5 sec
561 | [-----------------88%------------- ] 8892 of 10000 complete in 4.0 sec
562 | [-----------------99%----------------- ] 9972 of 10000 complete in 4.5 sec
563 | ---------------
564 | END SMC MOVE TO
565 | ---------------
566 |
567 | The figure you should see is shown in
568 | `Simple Example SMC Histogram Figure`_. You see that SMC has no problem
569 | discovering the two modes, even though we have not hand-picked the
570 | parameters of the `MCMC`_ proposal.
571 |
572 | .. _Simple Example SMC Histogram Figure:
573 | .. figure:: images/simple_model_smc.png
574 | :align: center
575 |
576 | SMC easily discovers both modes of :eq:`simple_model_pdf`
577 |
578 | .. _smc_without_mcmc:
579 |
580 | ++++++++++++++++++++++++++++++++++++
581 | Initializing SMC with just the model
582 | ++++++++++++++++++++++++++++++++++++
583 |
584 | It is also possible to initialize SMC without explicitly specifying a
585 | :class:`pymc.MCMC`. This allows `PyMC`_ to select everything for you.
586 | It can be done like this:
587 |
588 | .. code-block:: python
589 | :linenos:
590 |
591 | import simple_model as model
592 | import pysmc
593 |
594 | # Construct the SMC sampler
595 | smc_sampler = pysmc.SMC(model, num_particles=1000,
596 | num_mcmc=10, verbose=1)
597 | # Do whatever you want to smc_sampler...
598 |
599 | .. _database:
600 |
601 | ++++++++++++++++++++++++++++++
602 | Dumping the data to a database
603 | ++++++++++++++++++++++++++++++
604 |
605 | At the moment we support only a very simple database (see
606 | :class:`pysmc.DataBase`) which is built on :mod:`cPickle`. There are two
607 | options of :class:`pysmc.SMC` that you need to specify when you want to use
608 | the database functionality: ``db_filename`` and ``update_db``. See the
609 | documentation of :class:`pysmc.SMC` for their complete details. If you are
610 | using a database, you can force the current state of SMC to be written to
611 | it by calling :meth:`pysmc.commit()`. If you want to access a database,
612 | the all you have to do is use the attribute :attr:`pysmc.db`.
613 | Here is a very simple illustration of what you can do with this:
614 |
615 | .. code-block:: python
616 | :linenos:
617 |
618 | import simple_model as model
619 | import pysmc
620 |
621 | smc_sampler = pysmc.SMC(model, num_particles=1000,
622 | num_mcmc=10, verbose=1,
623 | db_filename='db.pickle',
624 | update_db=True)
625 | smc_sampler.initialize(0.01)
626 | smc_sampler.move(0.5)
627 |
628 | As you have probably grasped until now, this will construct an initial
629 | particle approximation at :math:`\gamma=0.01` and it will move it to
630 | :math:`\gamma=0.5`. The ``db_filename`` option specifies the file on which
631 | the database will write data. Let us assume that the file does not exist.
632 | Then, it will be created. The option ``update_db`` specifies that SMC should
633 | commit data to the database everytime it moves to another :math:`\gamma`.
634 | Now, assume that you quit python. The database is saved in ``db.pickle``.
635 | If you want to continue from the point you had stopped, all you have to do
636 | is:
637 |
638 | .. code-block:: python
639 | :linenos:
640 |
641 | import simple_model as model
642 | import pysmc
643 |
644 | smc_sampler = pysmc.SMC(model, num_particles=1000,
645 | num_mcmc=10, verbose=1,
646 | db_filename='db.pickle',
647 | update_db=True)
648 | smc_sampler.move(1.)
649 |
650 | Now, we have just skipped the initialization line. Sine the database already
651 | exists, :class:`pysmc.SMC` looks at it and initializes the particle
652 | approximation from the last state stored in it. Therefore, you actually
653 | start moving from :math:`\gamma=0.5` to :math:`\gamma=1`.
654 |
655 | .. _db_movie:
656 |
657 | +++++++++++++++++++++++++++++++
658 | Making a movie of the particles
659 | +++++++++++++++++++++++++++++++
660 |
661 | If you have stored the particle approximations of the various
662 | :math:`\gamma`'s in a database, then it is possible to make a movie out of
663 | them using :func:`pysmc.make_movie_from_db()`. Here is how:
664 |
665 | .. code-block:: python
666 | :linenos:
667 |
668 | import pysmc
669 | import matplotlib.pyplot as plt
670 |
671 | # Load the database
672 | db = pysmc.DataBase.load('db.pickle')
673 | # Make the movie
674 | movie = make_movie_from_db(db, 'mixture')
675 | # See the movie
676 | plt.show()
677 | # Save the movie
678 | movie.save('smc_movie.mp4')
679 |
680 | The movie created in this way can be downloaded from
681 | :download:`videos/smc_movie.mp4`.
682 |
683 | .. _parallel_sampling:
684 |
685 | --------------------
686 | Sampling in Parallel
687 | --------------------
688 |
689 | With :mod:`pysmc` it is possible to carry out the sampling procedure in
690 | parallel. As is well known, SMC is embarransingly parallelizable. Taking it to
691 | the extreme, its process has to deal with only one particle. Communication is
692 | only necessary when there is a need to resample the particle approximation
693 | and for finding the next :math:`\gamma` in the sequence of probability
694 | densities. Parallelization in :mod:`pysmc` is achieved with the help of
695 | `mpi4py `_. Here is a simple example:
696 |
697 | .. code-block:: python
698 | :linenos:
699 | :emphasize-lines: 3, 9, 18, 19
700 |
701 | import pysmc
702 | import simple_model as model
703 | import mpi4py.MPI as mpi
704 | import matplotlib.pyplot as plt
705 |
706 | # Construct the SMC sampler
707 | smc_sampler = pysmc.SMC(model, num_particles=1000,
708 | num_mcmc=10, verbose=1,
709 | mpi=mpi, gamma_is_an_exponent=True)
710 | # Initialize SMC at gamma = 0.01
711 | smc_sampler.initialize(0.01)
712 | # Move the particles to gamma = 1.0
713 | smc_sampler.move_to(1.)
714 | # Get a particle approximation
715 | p = smc_sampler.get_particle_approximation()
716 | # Plot a histogram
717 | pysmc.hist(p, 'mixture')
718 | if mpi.COMM_WORLD.Get_rank() == 0:
719 | plt.show()
720 |
721 | As you can see the only difference is at line 3 where :mod:`mpi4py` is
722 | instantiated, line 8 where mpi is passed as an argument to :class:`pysmc.SMC`
723 | and the last two lines where we simply specify that only one process should
724 | attempt to plot the histogram.
725 |
726 | .. _E. T. Jaynes:
727 | E. T. Jaynes' http://en.wikipedia.org/wiki/Edwin_Thompson_Jaynes>
728 | .. _Probability Theory\: The Logic of Science:
729 | http://omega.albany.edu:8008/JaynesBook.html
730 | .. _MCMC:
731 | http://en.wikipedia.org/wiki/Markov_chain_Monte_Carlo
732 | .. _PyMC:
733 | http://pymc-devs.github.io/pymc/
734 | .. _MCMC step methods:
735 | http://pymc-devs.github.io/pymc/extending.html#user-defined-step-methods
736 | .. _Arnaud Doucet's collection:
737 | http://www.stats.ox.ac.uk/~doucet/smc_resources.html
738 | .. _Sequential Monte Carlo Methods in Practice:
739 | http://books.google.com/books/about/Sequential_Monte_Carlo_Methods_in_Practi.html?id=BnWAcgAACAAJ
740 | .. _Simulated Annealing:
741 | http://en.wikipedia.org/wiki/Simulated_annealing
742 |
--------------------------------------------------------------------------------
/doc/source/videos/smc_movie.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PredictiveScienceLab/pysmc/fc6f1b2473d9067c108d6b3d7f27f29d3b521228/doc/source/videos/smc_movie.mp4
--------------------------------------------------------------------------------
/examples/diffusion_inverse.py:
--------------------------------------------------------------------------------
1 | """
2 | Example: Solving an inverse problem with MCMC.
3 | ----------------------------------------------
4 | """
5 |
6 |
7 | import warnings
8 | warnings.filterwarnings("ignore")
9 | import diffusion_inverse_model
10 | import matplotlib.pyplot as plt
11 | import sys
12 | import os
13 | sys.path.insert(0, os.path.abspath('..'))
14 | import pysmc as ps
15 | import pymc as pm
16 | import mpi4py.MPI as mpi
17 |
18 |
19 | if __name__ == '__main__':
20 | model = diffusion_inverse_model.make_model()
21 | mcmc = pm.MCMC(model)
22 | mcmc.use_step_method(ps.GaussianMixtureStep, model['location'])
23 | mcmc.use_step_method(ps.LognormalRandomWalk, model['alpha'],
24 | proposal_sd=1.)
25 | mcmc.use_step_method(ps.LognormalRandomWalk, model['beta'],
26 | proposal_sd=1.)
27 | mcmc.use_step_method(ps.LognormalRandomWalk, model['tau'],
28 | proposal_sd=1.)
29 | try:
30 | smc_sampler = ps.SMC(mcmc, num_particles=64, num_mcmc=1,
31 | verbose=3, mpi=mpi,
32 | db_filename='diffusion_inverse.pickle',
33 | update_db=True,
34 | gamma_is_an_exponent=True)
35 | smc_sampler.initialize(0.)
36 | smc_sampler.move_to(1.)
37 | except Exception as e:
38 | print(str(e))
39 | mpi.COMM_WORLD.Abort(1)
40 | p = smc_sampler.get_particle_approximation()
41 | m = p.mean
42 | v = p.variance
43 | if mpi.COMM_WORLD.Get_rank() == 0:
44 | print(m)
45 | print(v)
46 |
--------------------------------------------------------------------------------
/examples/diffusion_inverse_model.py:
--------------------------------------------------------------------------------
1 | import pymc
2 | import numpy as np
3 | from diffusion_solver import DiffusionSourceLocationOnly as Solver
4 |
5 | def make_model():
6 | # Construct the prior term
7 | location = pymc.Uniform('location', lower=[0,0], upper=[1,1])
8 | # The locations of the sensors
9 | X = [[0., 0.], [0., 1.], [1., 0.], [1., 1.]]
10 | # The output of the model
11 | solver = Solver(X=X)
12 | @pymc.deterministic(plot=False)
13 | def model_output(value=None, loc=location):
14 | return solver(loc)
15 | # The hyper-parameters of the noise
16 | alpha = pymc.Exponential('alpha', beta=1.)
17 | beta = pymc.Exponential('beta', beta=1.)
18 | tau = pymc.Gamma('tau', alpha=alpha, beta=beta)
19 | # Load the observed data
20 | data = np.loadtxt('observed_data')
21 | # The observations at the sensor locations
22 | @pymc.stochastic(dtype=float, observed=True)
23 | def sensors(value=data, mu=model_output, tau=tau, gamma=1.):
24 | """The value of the response at the sensors."""
25 | return gamma * pymc.normal_like(value, mu=mu, tau=tau)
26 | return locals()
27 |
--------------------------------------------------------------------------------
/examples/diffusion_solver.py:
--------------------------------------------------------------------------------
1 | """A class that solves the following diffusion problem:
2 |
3 | dphi/dt = div(grad(phi)) + S(x,t),
4 |
5 | where S(x, t) is the source term and it is modelled as:
6 |
7 | S(x, t) = s / (2 * PI * h ** 2) * exp( - ||xc - x||^2/(2 * h ** 2), for
8 | t in [0, tau]
9 | S(x, t) = 0, for t > tau.
10 |
11 | Author:
12 | Ilias Bilionis
13 |
14 | Date:
15 | 1/26/2013
16 | """
17 |
18 | from fipy import Grid2D
19 | from fipy import CellVariable
20 | from fipy import TransientTerm
21 | from fipy import DiffusionTerm
22 | from fipy import ExplicitDiffusionTerm
23 | from fipy import numerix
24 | import numpy as np
25 | import math
26 |
27 |
28 | class Diffusion(object):
29 | """Solve the Diffusion problem."""
30 |
31 | # Number of cells
32 | _num_cells = None
33 |
34 | # The discretization step
35 | _dx = None
36 |
37 | # The mesh
38 | _mesh = None
39 |
40 | # The solution vector
41 | _phi = None
42 |
43 | # The source term
44 | _source = None
45 |
46 | # The equation
47 | _eq = None
48 |
49 | # The time step
50 | _dt = None
51 |
52 | # The maximum solution time
53 | _max_time = None
54 |
55 | # The indices of the variables you want to observe
56 | _idx = None
57 |
58 | # The sensor times
59 | _T = None
60 |
61 | @property
62 | def num_cells(self):
63 | """Get the number of cells."""
64 | return self._num_cells
65 |
66 | @property
67 | def dx(self):
68 | """Get the discretization steps."""
69 | return self._dx
70 |
71 | @property
72 | def mesh(self):
73 | """Get the mesh."""
74 | return self._mesh
75 |
76 | @property
77 | def phi(self):
78 | """Get the solution."""
79 | return self._phi
80 |
81 | @property
82 | def source(self):
83 | """Get the source term."""
84 | return self._source
85 |
86 | @property
87 | def eq(self):
88 | """Get the equation."""
89 | return self._eq
90 |
91 | @property
92 | def dt(self):
93 | """Get the timestep."""
94 | return self._dt
95 |
96 | @property
97 | def idx(self):
98 | """Get the indices of the observed variables."""
99 | return self._idx
100 |
101 | @property
102 | def max_time(self):
103 | """Get the maximum solution time."""
104 | return self._max_time
105 |
106 | @property
107 | def T(self):
108 | """Get the sensor measurement times."""
109 | return self._T
110 |
111 | def __init__(self, X=None, T=None, time_steps=5, max_time=0.4, num_cells=25, L=1.):
112 | """Initialize the objet.
113 |
114 | Keyword Arguments:
115 | X --- The sensor locations.
116 | T --- The sensor measurment times.
117 | time_steps --- How many timesteps do you want to measure.
118 | max_time --- The maximum solution time.
119 | num_cells --- The number of cells per dimension.
120 | L --- The size of the computational domain.
121 | """
122 | assert isinstance(num_cells, int)
123 | self._num_cells = num_cells
124 | assert isinstance(L, float) and L > 0.
125 | self._dx = L / self.num_cells
126 | self._mesh = Grid2D(dx=self.dx, dy=self.dx, nx=self.num_cells,
127 | ny=self.num_cells)
128 | self._phi = CellVariable(name='solution variable', mesh=self.mesh)
129 | self._source = CellVariable(name='source term', mesh=self.mesh,
130 | hasOld=True)
131 | self._eqX = TransientTerm() == ExplicitDiffusionTerm(coeff=1.) + self.source
132 | self._eqI = TransientTerm() == DiffusionTerm(coeff=1.) + self.source
133 | self._eq = self._eqX + self._eqI
134 | assert isinstance(max_time, float) and max_time > 0.
135 | self._max_time = max_time
136 | #self.max_time / time_steps #.
137 | if X is None:
138 | idx = range(self.num_cells ** 2)
139 | else:
140 | idx = []
141 | x1, x2 = self.mesh.cellCenters
142 | for x in X:
143 | dist = (x1 - x[0]) ** 2 + (x2 - x[1]) ** 2
144 | idx.append(np.argmin(dist))
145 | self._idx = idx
146 | if T is None:
147 | T = np.linspace(0, self.max_time, time_steps)[1:]
148 | self._max_time = T[-1]
149 | self._T = T
150 | self._dt = self.T[0] / time_steps
151 | self.num_input = 5
152 | self.num_output = len(self.T) * len(self.idx)
153 |
154 | def __call__(self, x):
155 | """Evaluate the solver."""
156 | self.phi.value = 0.
157 | x_center = x[:2]
158 | tau = x[2]
159 | h = x[3]
160 | s = x[4]
161 | x, y = self.mesh.cellCenters
162 | self.source.value = 0.5 * s / (math.pi * h ** 2) * numerix.exp(-0.5 *
163 | ((x_center[0] - x) ** 2 + (x_center[1] - y) ** 2)
164 | / (2. * h ** 2))
165 | y_all = []
166 | t = 0.
167 | self._times = []
168 | next_time = 0
169 | while t < self.max_time:
170 | t += self.dt
171 | add = False
172 | if next_time < len(self.T) and t >= self.T[next_time]:
173 | t = self.T[next_time]
174 | next_time += 1
175 | add = True
176 | if t >= tau:
177 | self.source.value = 0.
178 | self.eq.solve(var=self.phi, dt=self.dt)
179 | if add:
180 | self._times.append(t)
181 | y_all.append([self.phi.value[i] for i in self.idx])
182 | y = np.hstack(y_all)
183 | y = y.reshape((self.T.shape[0], len(self.idx))).flatten(order='F')
184 | return y
185 |
186 |
187 | class DiffusionSourceLocationOnly(Diffusion):
188 | """A Diffusion solver that allows only the source location to vary."""
189 |
190 | # The source signal
191 | _s = None
192 |
193 | # The source spread
194 | _h = None
195 |
196 | # The source time
197 | _tau = None
198 |
199 | @property
200 | def s(self):
201 | """Get the source signal."""
202 | return self._s
203 |
204 | @property
205 | def h(self):
206 | """Get the source spread."""
207 | return self._h
208 |
209 | @property
210 | def tau(self):
211 | """Get the source time."""
212 | return self._tau
213 |
214 | def __init__(self, s=2., h=0.05, tau=0.3, X=None, T=None, time_steps=5,
215 | max_time=0.4, num_cells=25, L=1.):
216 | """Initialize the object.
217 |
218 | Keyword Arguments:
219 | s --- The source signal.
220 | h --- The source spread.
221 | tau --- The source time.
222 | The rest are as in Diffusion.
223 | """
224 | assert isinstance(s, float) and s > 0.
225 | self._s = s
226 | assert isinstance(h, float) and h > 0.
227 | self._h = h
228 | assert isinstance(tau, float) and tau > 0.
229 | self._tau = tau
230 | super(DiffusionSourceLocationOnly, self).__init__(X=X, T=T,
231 | time_steps=time_steps, max_time=max_time, num_cells=num_cells,
232 | L=L)
233 | self.num_input = 2
234 |
235 | def __call__(self, x):
236 | """Evaluate the object."""
237 | x = np.array(x)
238 | x = np.hstack([x, [self.tau, self.h, self.s]])
239 | return super(DiffusionSourceLocationOnly, self).__call__(x)
240 |
--------------------------------------------------------------------------------
/examples/observed_data:
--------------------------------------------------------------------------------
1 | 1.922305257888734520e+00
2 | 2.770166710198965543e+00
3 | 2.809706095142195270e+00
4 | 1.658624254362745409e+00
5 | 4.336902295949712077e-02
6 | 4.133754103287081705e-01
7 | 7.306368605789490545e-01
8 | 1.111944228636938758e+00
9 | 1.871047490616701281e-01
10 | 5.710760870963726976e-01
11 | 9.882620229541744239e-01
12 | 1.004500133058143341e+00
13 | 9.912941162989732935e-02
14 | 2.509745863203715066e-01
15 | 5.015556993810352804e-01
16 | 7.083945323962459462e-01
17 |
--------------------------------------------------------------------------------
/examples/reaction_kinetics_data.pickle:
--------------------------------------------------------------------------------
1 | (dp1
2 | S'y_obs'
3 | p2
4 | cnumpy.core.multiarray
5 | _reconstruct
6 | p3
7 | (cnumpy
8 | ndarray
9 | p4
10 | (I0
11 | tS'b'
12 | tRp5
13 | (I1
14 | (I10
15 | tcnumpy
16 | dtype
17 | p6
18 | (S'f8'
19 | I0
20 | I1
21 | tRp7
22 | (I3
23 | S'<'
24 | NNNI-1
25 | I-1
26 | I0
27 | tbI00
28 | S'L\x07\x15\xabYU\xe5?\x05\xfd\xdcZUU\xe5?\x80v\xa0ZUU\xe5?\xe5\x85\x9d\x8eUU\xe5??\xdc\xbb[UU\xe5?h\xf1\xd5\xa9LU\xd5?\xf8\x05FJUU\xd5?\x02\x13\xbfJUU\xd5?9\xf4\xc4\xe2TU\xd5?\x85G\x88HUU\xd5?'
29 | tbs.
--------------------------------------------------------------------------------
/examples/reaction_kinetics_model.py:
--------------------------------------------------------------------------------
1 | """
2 | The definition of the posterior of the reaction kinetics problem.
3 | """
4 |
5 |
6 | import pymc
7 | import warnings
8 | warnings.simplefilter('ignore')
9 | from reaction_kinetics_solver import *
10 |
11 |
12 | def make_model():
13 | import pickle
14 | with open('reaction_kinetics_data.pickle', 'rb') as fd:
15 | data = pickle.load(fd, encoding='latin1')
16 | y_obs = data['y_obs']
17 | # The priors for the reaction rates:
18 | k1 = pymc.Lognormal('k1', mu=2, tau=1./(10. ** 2), value=5.)
19 | k2 = pymc.Lognormal('k2', mu=4, tau=1./(10. ** 2), value=5.)
20 | # The noise term
21 | #sigma = pymc.Uninformative('sigma', value=1.)
22 | sigma = pymc.Exponential('sigma', beta=1.)
23 | # The forward model
24 | re_solver = ReactionKineticsSolver()
25 | @pymc.deterministic
26 | def model_output(value=None, k1=k1, k2=k2):
27 | return re_solver(k1, k2)
28 | # The likelihood term
29 | @pymc.stochastic(observed=True)
30 | def output(value=y_obs, mod_out=model_output, sigma=sigma, gamma=1.):
31 | return gamma * pymc.normal_like(y_obs, mu=mod_out, tau=1/sigma ** 2)
32 | return locals()
33 |
34 |
35 | if __name__ == '__main__':
36 | # Generate the observations
37 | import pickle
38 | re_solver = ReactionKineticsSolver()
39 | k1 = 2
40 | k2 = 4
41 | data = {}
42 | data['y_obs'] = re_solver(k1, k2)
43 | with open('reaction_kinetics_data.pickle', 'wb') as fd:
44 | pickle.dump(data, fd)
45 |
--------------------------------------------------------------------------------
/examples/reaction_kinetics_plot.py:
--------------------------------------------------------------------------------
1 | """
2 | Plots the results of the reaction kinetics example.
3 | """
4 |
5 |
6 | import sys
7 | import os
8 | sys.path.insert(0, os.path.abspath('..'))
9 | import pysmc
10 | import matplotlib.pyplot as plt
11 | import pickle as pickle
12 |
13 |
14 | if __name__ == '__main__':
15 | with open('reaction_kinetics_particle_approximation.pickle', 'rb') as fd:
16 | p = pickle.load(fd)
17 | print(p.num_particles)
18 | plt.plot(p.k1, p.k2, '.')
19 | plt.show()
20 |
--------------------------------------------------------------------------------
/examples/reaction_kinetics_run.py:
--------------------------------------------------------------------------------
1 | """
2 | Solve the reaction kinetics inverse problem.
3 | """
4 |
5 |
6 | import reaction_kinetics_model
7 | import sys
8 | import os
9 | sys.path.insert(0, os.path.abspath('..'))
10 | import pysmc
11 | import mpi4py.MPI as mpi
12 | import matplotlib.pyplot as plt
13 | import pickle
14 |
15 |
16 | if __name__ == '__main__':
17 | model = reaction_kinetics_model.make_model()
18 | # Construct the SMC sampler
19 | smc_sampler = pysmc.SMC(model, num_particles=100,
20 | num_mcmc=1, verbose=1,
21 | mpi=mpi, gamma_is_an_exponent=True)
22 | # Initialize SMC at gamma = 0.01
23 | smc_sampler.initialize(0.)
24 | # Move the particles to gamma = 1.0
25 | smc_sampler.move_to(1.)
26 | # Get a particle approximation
27 | p = smc_sampler.get_particle_approximation()
28 | #m = p.mean
29 | #v = p.variance
30 | #if mpi.COMM_WORLD.Get_rank() == 0:
31 | # print m
32 | # print v
33 | #lp = p.allgather()
34 | # Plot a histogram
35 | #pysmc.hist(p, 'mixture')
36 | p_l = p.allgather()
37 | if mpi.COMM_WORLD.Get_rank() == 0:
38 | with open('reaction_kinetics_particle_approximation.pickle', 'wb') as fd:
39 | pickle.dump(p_l, fd, pickle.HIGHEST_PROTOCOL)
40 |
--------------------------------------------------------------------------------
/examples/reaction_kinetics_run_nompi.py:
--------------------------------------------------------------------------------
1 | """
2 | Solve the reaction kinetics inverse problem.
3 | """
4 |
5 |
6 | import reaction_kinetics_model
7 | import sys
8 | import os
9 | import pymc
10 | sys.path.insert(0, os.path.abspath('..'))
11 | import pysmc
12 | import matplotlib.pyplot as plt
13 | import pickle
14 |
15 |
16 | if __name__ == '__main__':
17 | model = reaction_kinetics_model.make_model()
18 | # Construct the SMC sampler
19 | mcmc = pymc.MCMC(model)
20 | mcmc.use_step_method(pysmc.LognormalRandomWalk, model['k1'])
21 | mcmc.use_step_method(pysmc.LognormalRandomWalk, model['k2'])
22 | mcmc.use_step_method(pysmc.LognormalRandomWalk, model['sigma'])
23 | smc_sampler = pysmc.SMC(mcmc, num_particles=100,
24 | num_mcmc=1, verbose=1,
25 | gamma_is_an_exponent=True)
26 | # Initialize SMC at gamma = 0.01
27 | smc_sampler.initialize(0.0)
28 | # Move the particles to gamma = 1.0
29 | smc_sampler.move_to(1.)
30 | # Get a particle approximation
31 | p = smc_sampler.get_particle_approximation()
32 | print(p.mean)
33 | print(p.variance)
34 |
35 | data = [p.particles[i]['stochastics']['k1'] for i in range(p.num_particles)]
36 | data = np.array(data)
37 | plt.plot(data, np.zeros(data.shape), 'ro', markersize=10)
38 | pysmc.hist(p, 'mixture')
39 |
40 | data = [p.particles[i]['stochastics']['k2'] for i in range(p.num_particles)]
41 | data = np.array(data)
42 | plt.plot(data, np.zeros(data.shape), 'bo', markersize=10)
43 | pysmc.hist(p, 'mixture')
44 |
--------------------------------------------------------------------------------
/examples/reaction_kinetics_solver.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | .. _exa_rk:
4 |
5 | +++++++++++++++++
6 | Reaction Kinetics
7 | +++++++++++++++++
8 |
9 | We are trying to infer the forward and reverse rates of reaction
10 | (:math:`k_1, k_2`) in a chemical system. The forward model is given by a set
11 | of differential equations:
12 |
13 | .. math::
14 | \\frac{du}{dt} = -k_1 u + k_2 v,
15 | \\frac{dv}{dt} = k_1 u - k_2 v.
16 |
17 | The initial condition is fixed at :math:`u(0) = 1` and :math:`v(0) = 0`.
18 | """
19 |
20 |
21 | __all__ = ['ReactionKineticsSolver']
22 |
23 |
24 | import numpy as np
25 | from scipy.integrate import ode
26 |
27 |
28 | class ReactionKineticsSolver(object):
29 |
30 | """
31 | Implements the forward model.
32 | """
33 |
34 | # Observation times
35 | _t = None
36 |
37 | @property
38 | def t(self):
39 | """
40 | :getter: Get the observation times.
41 | :type: 1D numpy.ndarray
42 | """
43 | return self._t
44 |
45 | def __init__(self, t=[2, 4, 5, 8, 10]):
46 | """
47 | Initialize the model.
48 | """
49 | self._t = np.array(t, dtype='float32')
50 |
51 | def __call__(self, k1, k2):
52 | """
53 | Evaluate the solver at ``k1`` and ``k2``.
54 | """
55 | def f(t, y, k1, k2):
56 | return [-k1 * y[0] + k2 * y[1], k1 * y[0] - k2 * y[1]]
57 | def jac(t, y, k1, k2):
58 | return [[-k1, k2], [k1, -k2]]
59 | r = ode(f).set_integrator('dopri5')
60 | r.set_initial_value([1, 0], 0).set_f_params(k1, k2)
61 | dt = 1e-2
62 | y = []
63 | for t in self.t:
64 | r.integrate(t)
65 | y.append(r.y)
66 | return np.vstack(y).flatten(order='F')
67 |
68 |
69 | if __name__ == '__main__':
70 | """
71 | This main simply produces the obseved data for the inverse problem.
72 | """
73 | import cPickle as pickle
74 | re_solver = ReactionKineticsSolver()
75 | # The **real** reaction rates
76 | k1 = 2
77 | k2 = 4
78 | # The **true** response
79 | y = re_solver(k1, k2)
80 | # Add some noise to it
81 | noise = 0.1
82 | y_obs = y + noise * np.random.rand(*y.shape)
83 | # Save these to a file
84 | data = {}
85 | data['k1'] = k1
86 | data['k2'] = k2
87 | data['y'] = y
88 | data['noise'] = noise
89 | data['y_obs'] = y_obs
90 | with open('reaction_kinetics_data.pickle', 'wb') as fd:
91 | pickle.dump(data, fd, pickle.HIGHEST_PROTOCOL)
92 |
--------------------------------------------------------------------------------
/examples/simple_model.py:
--------------------------------------------------------------------------------
1 | """
2 | A simple mixture model to test the capabilities of SMC.
3 |
4 | Author:
5 | Ilias Bilionis
6 |
7 | Date:
8 | 9/22/2013
9 | """
10 |
11 |
12 | import pymc
13 | import numpy as np
14 | import math
15 |
16 |
17 | def make_model():
18 | # The gamma parameter
19 | gamma = 1.
20 |
21 | @pymc.stochastic(dtype=float)
22 | def mixture(value=1., gamma=gamma, pi=[0.2, 0.8], mu=[-2., 3.],
23 | sigma=[0.01, 0.01]):
24 | """
25 | The log probability of a mixture of normal densities.
26 |
27 | :param value: The point of evaluation.
28 | :type value : float
29 | :param gamma: The parameter characterizing the SMC one-parameter
30 | family.
31 | :type gamma : float
32 | :param pi : The weights of the components.
33 | :type pi : 1D :class:`numpy.ndarray`
34 | :param mu : The mean of each component.
35 | :type mu : 1D :class:`numpy.ndarray`
36 | :param sigma: The standard deviation of each component.
37 | :type sigma : 1D :class:`numpy.ndarray`
38 | """
39 | # Make sure everything is a numpy array
40 | pi = np.array(pi)
41 | mu = np.array(mu)
42 | sigma = np.array(sigma)
43 | # The number of components in the mixture
44 | n = pi.shape[0]
45 | # pymc.normal_like requires the precision not the variance:
46 | tau = np.sqrt(1. / sigma ** 2)
47 | # The following looks a little bit awkward because of the need for
48 | # numerical stability:
49 | p = np.log(pi)
50 | p += np.array([pymc.normal_like(value, mu[i], tau[i])
51 | for i in range(n)])
52 | p = math.fsum(np.exp(p))
53 | # logp should never be negative, but it can be zero...
54 | if p <= 0.:
55 | return -np.inf
56 | return gamma * math.log(p)
57 |
58 | return locals()
59 |
60 |
61 | def eval_stochastic_variable(var, values):
62 | """
63 | Evaluate the logarithm of the probability of ``var`` at ``values``.
64 |
65 | :param var : The stochastic variable whose probability should be
66 | evaluated.
67 | :type var : :class:`pymc.Stochastic`
68 | :param values: The points of evalulation.
69 | :type values : list or :class:`numpy.ndarray`
70 | :returns : The logarithm of the probabilities of the variable
71 | at all ``values``.
72 | :rtype : 1D :class:`numpy.ndarray`
73 | """
74 | n = len(values)
75 | y = np.zeros(n)
76 | for i in range(n):
77 | var.value = values[i]
78 | y[i] = math.exp(var.logp)
79 | return y
80 |
81 | if __name__ == '__main__':
82 | import matplotlib.pyplot as plt
83 | x = np.linspace(-2, 3, 200)
84 |
85 | # Plot the original probability density
86 | m = make_model()
87 | y = eval_stochastic_variable(m['mixture'], x)
88 | plt.plot(x, y, linewidth=2)
89 | plt.xlabel('$x$', fontsize=16)
90 | plt.ylabel('$p(x)$', fontsize=16)
91 | plt.savefig('../doc/source/images/simple_model_pdf.png')
92 |
93 | # Plot some members of the one-parameter SMC family of probability densities
94 | plt.clf()
95 | gammas = [1., 0.7, 0.5, 0.1, 0.05, 0.01]
96 | for gamma in gammas:
97 | # m['mixture'].parents['gamma'] = gamma
98 | m['gamma'] = gamma
99 | y = eval_stochastic_variable(m['mixture'], x)
100 | plt.plot(x, y, linewidth=2)
101 | plt.xlabel('$x$', fontsize=16)
102 | plt.ylabel('$\pi_\gamma(x)$', fontsize=16)
103 | legend_labels = ['$\gamma = %1.2f$' % gamma for gamma in gammas]
104 | plt.legend(legend_labels, loc='upper left')
105 | plt.show()
106 | # plt.savefig('../doc/source/images/simple_model_pdf_family.png')
107 |
--------------------------------------------------------------------------------
/examples/simple_model_run.py:
--------------------------------------------------------------------------------
1 | """
2 | Run SMC on the simple model.
3 |
4 | Author:
5 | Ilias Bilionis
6 |
7 | Date:
8 | 9/28/2013
9 |
10 | """
11 |
12 |
13 | import simple_model
14 | import pymc
15 | import sys
16 | import os
17 | sys.path.insert(0, os.path.abspath('..'))
18 | import pysmc
19 | import matplotlib.pyplot as plt
20 | import pickle
21 | import numpy as np
22 |
23 |
24 | if __name__ == '__main__':
25 | # Construct the SMC sampler
26 | model = simple_model.make_model()
27 | mcmc = pymc.MCMC(model)
28 | mcmc.use_step_method(pysmc.RandomWalk, model['mixture'])
29 | smc_sampler = pysmc.SMC(mcmc, num_particles=1000,
30 | num_mcmc=1, verbose=4)
31 | # Initialize SMC at gamma = 0.01
32 | smc_sampler.initialize(0.001)
33 | # Move the particles to gamma = 1.0
34 | smc_sampler.move_to(1.)
35 | print(smc_sampler.log_Zs)
36 | # Get a particle approximation
37 | p = smc_sampler.get_particle_approximation()
38 | print(p.mean)
39 | print(p.variance)
40 | # Plot a histogram
41 | data = [p.particles[i]['stochastics']['mixture'] for i in range(p.num_particles)]
42 | data = np.array(data)
43 | plt.plot(data, np.zeros(data.shape), 'ro', markersize=10)
44 | pysmc.hist(p, 'mixture')
45 | plt.show()
46 |
--------------------------------------------------------------------------------
/examples/simple_model_run_mpi.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """
3 | Run SMC on the simple model.
4 |
5 | Author:
6 | Ilias Bilionis
7 |
8 | Date:
9 | 9/28/2013
10 |
11 | """
12 |
13 |
14 | import simple_model
15 | import sys
16 | import os
17 | import pymc as pm
18 | sys.path.insert(0, os.path.abspath('..'))
19 | import pysmc as ps
20 | import mpi4py.MPI as mpi
21 | import matplotlib.pyplot as plt
22 | import numpy as np
23 |
24 |
25 | if __name__ == '__main__':
26 | do_init = not os.path.exists('test_db.pickle')
27 | # Construct the SMC sampler
28 | model = simple_model.make_model()
29 | mcmc = pm.MCMC(model)
30 | mcmc.use_step_method(ps.RandomWalk, model['mixture'])
31 | smc_sampler = ps.SMC(mcmc, num_particles=512,
32 | num_mcmc=1, verbose=1,
33 | mpi=mpi, gamma_is_an_exponent=True,
34 | ess_reduction=0.9,
35 | db_filename='test_db.pickle',
36 | update_db=True)
37 | # Initialize SMC at gamma = 0.01
38 | if do_init:
39 | smc_sampler.initialize(0.001, num_mcmc_per_particle=100)
40 | # Move the particles to gamma = 1.0
41 | smc_sampler.move_to(1.)
42 | # Get a particle approximation
43 | p = smc_sampler.get_particle_approximation().gather()
44 | # Plot a histogram
45 | #if mpi.COMM_WORLD.Get_rank() == 0:
46 | # x = np.linspace(-5, 5, 200)
47 | # y = simple_model.eval_stochastic_variable(model['mixture'], x)
48 | # plt.plot(x, y, linewidth=2)
49 | # ps.hist(p, 'mixture')
50 | # plt.show()
51 |
--------------------------------------------------------------------------------
/examples/test_lognormal.py:
--------------------------------------------------------------------------------
1 | """
2 | Test the step method when trying to sample a lognormal random variable.
3 | """
4 |
5 |
6 | import os
7 | import sys
8 | sys.path.insert(0, os.path.abspath('..'))
9 | import pysmc
10 | import pymc
11 | import math
12 | import matplotlib.pyplot as plt
13 | import numpy as np
14 |
15 |
16 | def make_model():
17 | #c = pymc.Container([pymc.Lognormal('x', mu=math.log(1.), tau=1.),
18 | # pymc.Lognormal('z', mu=math.log(2.), tau=3.)])
19 | #x = pymc.Exponential('x', beta=0.1)
20 | x = pymc.Lognormal('x', mu=np.array([math.log(1.), math.log(2.)]),
21 | tau=np.array([1., 3.]))
22 | @pymc.stochastic(observed=True)
23 | def y(value=0.001, x=x):
24 | return pymc.lognormal_like(value, mu=x[0], tau=1.)
25 | return locals()
26 |
27 |
28 | if __name__ == '__main__':
29 | m = make_model()
30 | mcmc = pymc.MCMC(m)
31 | mcmc.assign_step_methods()
32 | print(mcmc.step_method_dict)
33 | mcmc.sample(100000, thin=100, burn=10000)
34 | s = mcmc.step_method_dict[m['x']][0]
35 | print('\n', s.accepted / (s.accepted + s.rejected))
36 | pymc.Matplot.plot(mcmc)
37 | plt.show()
38 |
--------------------------------------------------------------------------------
/examples/true_data:
--------------------------------------------------------------------------------
1 | 1.866507885606724315e+00
2 | 2.740475085017091139e+00
3 | 2.790475185620288290e+00
4 | 1.591504738028788779e+00
5 | 7.680130622515170258e-02
6 | 4.543750582891544210e-01
7 | 8.283730407227198889e-01
8 | 1.085951974233539730e+00
9 | 1.122672110118582978e-01
10 | 5.308306121373320696e-01
11 | 9.158601489019295716e-01
12 | 1.132437310917811635e+00
13 | 1.343241885575042864e-02
14 | 2.063628880834675883e-01
15 | 5.045938472307064382e-01
16 | 8.283356974967712727e-01
17 |
--------------------------------------------------------------------------------
/examples/true_locations:
--------------------------------------------------------------------------------
1 | 2.000000000000000111e-01
2 | 1.300000000000000044e-01
3 |
--------------------------------------------------------------------------------
/examples/true_noise:
--------------------------------------------------------------------------------
1 | 1.000000000000000056e-01
2 |
--------------------------------------------------------------------------------
/pysmc/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | .. moduleauthor:: Ilias Bilionis
3 |
4 | .. _classes:
5 |
6 | -------
7 | Classes
8 | -------
9 | Here is complete reference of all the classes included in :mod:`pysmc`.
10 |
11 | .. automodule:: pysmc._mcmc_wrapper
12 | .. autoclass:: pysmc.MCMCWrapper
13 | :members:
14 |
15 | .. automodule:: pysmc._mpi
16 | .. autoclass:: pysmc.DistributedObject
17 | :members:
18 |
19 | .. automodule:: pysmc._particle_approximation
20 | .. autoclass:: pysmc.ParticleApproximation
21 | :members:
22 |
23 | .. automodule:: pysmc._smc
24 | .. autoclass:: pysmc.SMC
25 | :members:
26 |
27 | .. automodule:: pysmc._db
28 | .. autoclass:: pysmc.DataBase
29 | :members:
30 |
31 | .. automodule:: pysmc._step_methods
32 | .. autoclass:: pysmc.LognormalRandomWalk
33 | :members:
34 |
35 | .. _methods:
36 |
37 | -------
38 | Methods
39 | -------
40 |
41 | .. automodule:: pysmc._plot
42 | .. autofunction:: pysmc.hist
43 |
44 | .. automodule:: pysmc._misc
45 | .. autofunction:: pysmc.try_to_array
46 | .. autofunction:: pysmc.hist
47 | .. autofunction:: pysmc.make_movie_from_db
48 | .. autofunction:: pysmc.multinomial_resample
49 | .. autofunction:: pysmc.kde
50 |
51 | """
52 |
53 |
54 | __docformat__ = 'reStructuredText'
55 |
56 |
57 | __all__ = ['MCMCWrapper', 'SMC', 'ParticleApproximation', 'try_to_array',
58 | 'get_var_from_particle_list', 'DataBase', 'DistributedObject',
59 | 'hist', 'make_movie_from_db', 'multinomial_resample', 'kde',
60 | 'LognormalRandomWalk']
61 |
62 |
63 | from pysmc._misc import *
64 | from pysmc._mpi import *
65 | from pysmc._mcmc_wrapper import *
66 | from pysmc._step_methods import *
67 | from pysmc._particle_approximation import *
68 | from pysmc._db import *
69 | from pysmc._smc import *
70 | from pysmc._plot import *
71 |
--------------------------------------------------------------------------------
/pysmc/_db.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | .. _db:
4 |
5 | ++++++++
6 | DataBase
7 | ++++++++
8 |
9 | The class :class:`pysmc.DataBase` implements a simple database for dumping
10 | SMC steps.
11 | """
12 |
13 |
14 | __all__ = ['DataBase', 'SerialDataBase']
15 |
16 |
17 | import pickle
18 | import os
19 | import warnings
20 |
21 |
22 | class DataBase(object):
23 |
24 | """
25 | A data base storing the evolution of SMC particles as gamma changes.
26 |
27 | :param gamma_name: The name we are using for gamma.
28 | :type gamma_name: str
29 | :param filename: The desired filename for the output. If ``None``
30 | then everything is stored on the ram.
31 | :type filename: str
32 | """
33 |
34 | # The gammas visited so far (list)
35 | _gammas = None
36 |
37 | # The name we used for gamma (str)
38 | _gamma_name = None
39 |
40 | # The particle approximations associated with each gamma (list)
41 | _particle_approximations = None
42 |
43 | # The parameters of each step method
44 | _step_method_params = None
45 |
46 | # The filename you have selected for dumping the data (str)
47 | _filename = None
48 |
49 | # The file handler
50 | _fd = None
51 |
52 | # The Pickler
53 | _pickler = None
54 |
55 | # Last commited particle approximation
56 | _last_commited = None
57 |
58 | @property
59 | def gammas(self):
60 | """
61 | The list of gammas we have visited.
62 |
63 | :getter: Get the list of gammas we have visited.
64 | :type: list
65 | """
66 | return self._gammas
67 |
68 | @property
69 | def gamma_name(self):
70 | """
71 | The name we used for gamma.
72 |
73 | :getter: Get the name we used for gamma.
74 | :type: str
75 | """
76 | return self._gamma_name
77 |
78 | @property
79 | def particle_approximations(self):
80 | """
81 | The particle approximations associated with each gamma.
82 |
83 | :getter: Get the particle approximations associated with each gamma.
84 | :type: list
85 | """
86 | return self._particle_approximations
87 |
88 | @property
89 | def step_method_params(self):
90 | """
91 | The adaptive scale factors of each step method.
92 |
93 | :getter: Get the adaptive scale factors associated with each
94 | MCMC step method.
95 | :type: list
96 | """
97 | return self._step_method_params
98 |
99 | @property
100 | def num_gammas(self):
101 | """
102 | The number of gammas added to the database.
103 |
104 | :getter: Get the number of gammas added to the data base.
105 | :type: int
106 | """
107 | return len(self._gammas)
108 |
109 | @property
110 | def filename(self):
111 | """
112 | The filename you have selected for dumping the data.
113 |
114 | :getter: Get the filename you have selected for dumping the data.
115 | :type: str
116 | """
117 | return self._filename
118 |
119 | @property
120 | def write_to_disk(self):
121 | """
122 | ``True`` if the class writes data to disk, ``False`` otherwise.
123 | """
124 | return not self.filename is None
125 |
126 | @property
127 | def gamma(self):
128 | """
129 | The current gamma of the database.
130 |
131 | :getter: Get the current gamma of the database.
132 | :type: unknown
133 | """
134 | if self.num_gammas == 0:
135 | raise RuntimeError(
136 | 'The db is empty!')
137 | return self.gammas[-1]
138 |
139 | @property
140 | def particle_approximation(self):
141 | """
142 | The current particle approximation of the database.
143 |
144 | :getter: Get the current particle approximation of the database.
145 | :type: unknown
146 | """
147 | return self._particle_approximations[-1]
148 |
149 | @property
150 | def step_method_param(self):
151 | return self._step_method_params[-1]
152 |
153 | def __init__(self, gamma_name=None, filename=None):
154 | """
155 | Initialize the object.
156 |
157 | See doc string for parameters.
158 | """
159 | if gamma_name is not None:
160 | self._initialize(gamma_name, filename)
161 |
162 | def _initialize(self, gamma_name, filename):
163 | """
164 | Initialize the object given a name for gamma and a filename.
165 | """
166 | if not isinstance(gamma_name, str):
167 | raise TypeError(
168 | 'The name of the \'gamma\' parameter must be a str object.')
169 | self._gamma_name = gamma_name
170 | if filename is not None:
171 | if not isinstance(filename, str):
172 | raise TypeError(
173 | 'The filename has to be a str object.')
174 | filename = os.path.abspath(filename)
175 | if os.path.exists(filename):
176 | warnings.warn(
177 | 'Filename already exists. I will overwrite it!\n'
178 | + ' Use pysmc.Database.load(filename) if your intentions are to'
179 | + ' append the file with new data!')
180 | self._filename = filename
181 | if self.write_to_disk:
182 | with open(self.filename, 'wb') as fd:
183 | pickle.dump(gamma_name, fd,
184 | protocol=pickle.HIGHEST_PROTOCOL)
185 | self._gammas = []
186 | self._particle_approximations = []
187 | self._step_method_params = []
188 | self._last_commited = 0
189 |
190 | def add(self, gamma, particle_approximation,
191 | step_method_params):
192 | """
193 | Add the ``particle_approximation`` corresponding to ``gamma`` to the
194 | database.
195 |
196 | :param gamma: The gamma parameter.
197 | :type gamma: any
198 | :param particle_approximation: particle_approximation
199 | :type particle_approximation: any
200 | """
201 | self._gammas.append(gamma)
202 | self._particle_approximations.append(particle_approximation)
203 | self._step_method_params.append(step_method_params)
204 |
205 | def _dump_part_of_list(self, idx, values, fd):
206 | """
207 | Dump part of a list to fd.
208 | """
209 | for i in range(len(idx)):
210 | pickle.dump(values[idx[i]], fd,
211 | protocol=pickle.HIGHEST_PROTOCOL)
212 |
213 | def commit(self):
214 | """
215 | Commit everything we have so far to the database.
216 | """
217 | if self.write_to_disk:
218 | idx = list(range(self._last_commited, self.num_gammas))
219 | if len(idx) == 0:
220 | return
221 | with open(self.filename, 'ab') as fd:
222 | self._dump_part_of_list(idx, self.gammas, fd)
223 | self._dump_part_of_list(idx, self.particle_approximations, fd)
224 | self._dump_part_of_list(idx, self.step_method_params, fd)
225 | self._last_commited += len(idx)
226 |
227 | @staticmethod
228 | def load(filename):
229 | """
230 | This is a static method. It loads a database from ``filename``.
231 | """
232 | filename = os.path.abspath(filename)
233 | with open(filename, 'rb') as fd:
234 | try:
235 | gamma_name = pickle.load(fd)
236 | except:
237 | raise RuntimeError('File %s: Not a valid database!' % filename)
238 | gammas = []
239 | particle_approximations = []
240 | step_method_params = []
241 | while True:
242 | try:
243 | gammas.append(pickle.load(fd))
244 | particle_approximations.append(pickle.load(fd))
245 | step_method_params.append(pickle.load(fd))
246 | except EOFError:
247 | break
248 | last_commited = len(gammas)
249 | db = DataBase()
250 | db._gamma_name = gamma_name
251 | db._gammas = gammas
252 | db._particle_approximations = particle_approximations
253 | db._step_method_params = step_method_params
254 | db._last_commited = last_commited
255 | db._filename = filename
256 | return db
257 |
258 |
259 | class SerialDataBase(object):
260 |
261 | """
262 | Just like DataBase but stores in memory only the latest particle
263 | approximation.
264 |
265 | :param gamma_name: The name we are using for gamma.
266 | :type gamma_name: str
267 | :param filename: The desired filename for the output. If ``None``
268 | then everything is stored on the ram.
269 | :type filename: str
270 | """
271 |
272 | # The last gamma
273 | _gamma = None
274 |
275 | # The name we used for gamma (str)
276 | _gamma_name = None
277 |
278 | # The particle approximations associated with gamma
279 | _particle_approximation = None
280 |
281 | # The parameters of each step method
282 | _step_method_param = None
283 |
284 | # The filename you have selected for dumping the data (str)
285 | _filename = None
286 |
287 | # The file handler
288 | _fd = None
289 |
290 | # The Pickler
291 | _pickler = None
292 |
293 | # Last commited particle approximation
294 | _last_commited = None
295 |
296 | # The total number of gammas
297 | _num_gammas = None
298 |
299 | @property
300 | def gamma_name(self):
301 | """
302 | The name we used for gamma.
303 |
304 | :getter: Get the name we used for gamma.
305 | :type: str
306 | """
307 | return self._gamma_name
308 |
309 | @property
310 | def num_gammas(self):
311 | """
312 | The number of gammas added to the database.
313 |
314 | :getter: Get the number of gammas added to the data base.
315 | :type: int
316 | """
317 | return self._num_gammas
318 |
319 | @property
320 | def filename(self):
321 | """
322 | The filename you have selected for dumping the data.
323 |
324 | :getter: Get the filename you have selected for dumping the data.
325 | :type: str
326 | """
327 | return self._filename
328 |
329 | @property
330 | def gamma(self):
331 | """
332 | The current gamma of the database.
333 |
334 | :getter: Get the current gamma of the database.
335 | :type: unknown
336 | """
337 | return self._gamma
338 |
339 | @property
340 | def particle_approximation(self):
341 | """
342 | The current particle approximation of the database.
343 |
344 | :getter: Get the current particle approximation of the database.
345 | :type: unknown
346 | """
347 | return self._particle_approximation
348 |
349 | @property
350 | def step_method_param(self):
351 | return self._step_method_param
352 |
353 | def __init__(self, gamma_name=None, filename=None):
354 | """
355 | Initialize the object.
356 |
357 | See doc string for parameters.
358 | """
359 | if gamma_name is not None:
360 | self._initialize(gamma_name, filename)
361 | self._num_gammas = 0
362 |
363 | def _initialize(self, gamma_name, filename):
364 | """
365 | Initialize the object given a name for gamma and a filename.
366 | """
367 | if not isinstance(gamma_name, str):
368 | raise TypeError(
369 | 'The name of the \'gamma\' parameter must be a str object.')
370 | self._gamma_name = gamma_name
371 | if filename is not None:
372 | if not isinstance(filename, str):
373 | raise TypeError(
374 | 'The filename has to be a str object.')
375 | filename = os.path.abspath(filename)
376 | if os.path.exists(filename):
377 | warnings.warn(
378 | 'Filename already exists. I will overwrite it!\n'
379 | + ' Use pysmc.Database.load(filename) if your intentions are to'
380 | + ' append the file with new data!')
381 | self._filename = filename
382 | with open(self.filename, 'wb') as fd:
383 | pickle.dump(gamma_name, fd, protocol=pickle.HIGHEST_PROTOCOL)
384 |
385 | def add(self, gamma, particle_approximation,
386 | step_method_params):
387 | """
388 | Add the ``particle_approximation`` corresponding to ``gamma`` to the
389 | database.
390 |
391 | :param gamma: The gamma parameter.
392 | :type gamma: any
393 | :param particle_approximation: particle_approximation
394 | :type particle_approximation: any
395 | """
396 | self._gamma = gamma
397 | self._particle_approximations = particle_approximation
398 | self._step_method_param = step_method_params
399 | self._num_gammas += 1
400 |
401 | def _dump_part_of_list(self, idx, values, fd):
402 | """
403 | Dump part of a list to fd.
404 | """
405 | for i in range(len(idx)):
406 | pickle.dump(values[idx[i]], fd,
407 | protocol=pickle.HIGHEST_PROTOCOL)
408 |
409 | def commit(self):
410 | """
411 | Commit everything we have so far to the database.
412 | """
413 | with open(self.filename, 'ab') as fd:
414 | pickle.dump(self.gamma, fd, protocol=pickle.HIGHEST_PROTOCOL)
415 | pickle.dump(self.particle_approximation, fd,
416 | protocol=pickle.HIGHEST_PROTOCOL)
417 | pickle.dump(self.step_method_param, fd,
418 | protocol=pickle.HIGHEST_PROTOCOL)
419 |
420 | @staticmethod
421 | def load(filename, go_to_last=True):
422 | """
423 | This is a static method. It loads a database from ``filename``.
424 | """
425 | filename = os.path.abspath(filename)
426 | fd = open(filename, 'rb')
427 | try:
428 | gamma_name = pickle.load(fd)
429 | except:
430 | raise RuntimeError('File %s: Not a valid database!' % filename)
431 | gamma = pickle.load(fd)
432 | particle_approximation = pickle.load(fd)
433 | step_method_param = pickle.load(fd)
434 | db = SerialDataBase()
435 | db._fd = fd
436 | if go_to_last:
437 | db.go_to_last()
438 | return db
439 |
440 |
441 | def __next__(self):
442 | """
443 | Read the next state from the file opened file.
444 | """
445 | if self._fd is None:
446 | return False
447 | try:
448 | gamma = pickle.load(self._fd)
449 | particle_approximation = pickle.load(self._fd)
450 | step_method_param = pickle.load(self._fd)
451 | except EOFError:
452 | self._fd.close()
453 | self._fd = None
454 | return False
455 | if self.num_gammas >= 1:
456 | del self._gamma
457 | del self._particle_approximation
458 | del self._step_method_param
459 | self._gamma = gamma
460 | self._particle_approximation = particle_approximation
461 | self._step_method_param = step_method_param
462 | self._num_gammas += 1
463 | return True
464 |
465 | def go_to_last(self):
466 | """
467 | Go through all the values of the database until you reach the last one.
468 | """
469 | while next(self):
470 | pass
471 |
--------------------------------------------------------------------------------
/pysmc/_mcmc_wrapper.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | .. _mcmc_wrapper:
4 |
5 | +++++++++++
6 | MCMCWrapper
7 | +++++++++++
8 |
9 | :class:`pysmc.MCMCWrapper`
10 | provides exactly the same functionality as :class:`pymc.MCMC`.
11 | The only new thing is that it has the ability to get and set the current
12 | state of MCMC from a dictionary describing the state. Basically, you should
13 | not construct this class your self, :class:`pymc.SMC` will do it
14 | automatically.
15 |
16 | .. note:: It does not inherit from :class:`pymc.MCMC`. It simply stores
17 | a reference to a :class:`pymc.MCMC` object internally.
18 |
19 | Here is a complete reference of the public members:
20 | """
21 |
22 |
23 | __all__ = ['MCMCWrapper']
24 |
25 |
26 | from pymc import MCMC
27 | import itertools
28 | import warnings
29 |
30 |
31 | class MCMCWrapper(object):
32 |
33 | """
34 | This is a wrapper class for :class:`pymc.MCMC`.
35 |
36 | :param mcmc_sampler: The underlying MCMC sampler. If ``None``,
37 | then it **must** be specified before using
38 | an object of this class.
39 | :type mcmc_sampler: :class:`pymc.MCMC`
40 | """
41 |
42 | # The underlying pymc.MCMC object.
43 | _mcmc_sampler = None
44 |
45 | # The step methods
46 | _step_methods = None
47 |
48 | @property
49 | def mcmc_sampler(self):
50 | """
51 | The underlying :class:`pymc.MCMC` object.
52 |
53 | :getter: Get the underlying MCMC object.
54 | :setter: Set the underlying MCMC object.
55 | :raises: :exc:`exceptions.TypeError`
56 | :type: :class:`pymc.MCMC`
57 | """
58 | return self._mcmc_sampler
59 |
60 | @mcmc_sampler.setter
61 | def mcmc_sampler(self, value):
62 | if not isinstance(value, MCMC):
63 | raise TypeError('You must provide a pymc.MCMC object!')
64 | value.assign_step_methods()
65 | self._mcmc_sampler = value
66 |
67 |
68 | @property
69 | def nodes(self):
70 | """The nodes of the model."""
71 | return self.mcmc_sampler.nodes
72 |
73 | @property
74 | def stochastics(self):
75 | """The stochastic variables of the model."""
76 | return self.mcmc_sampler.stochastics
77 |
78 | @property
79 | def deterministics(self):
80 | """The deterministic variables of the model."""
81 | return self.mcmc_sampler.deterministics
82 |
83 | @property
84 | def db(self):
85 | """The database of the MCMC sampler."""
86 | return self.mcmc_sampler.db
87 |
88 | @property
89 | def logp(self):
90 | """
91 | The log of the probability of the current state of the MCMC sampler.
92 | """
93 | return self.mcmc_sampler.logp
94 |
95 | @property
96 | def step_methods(self):
97 | """The step methods of the MCMC sampler."""
98 | return self._step_methods
99 |
100 | def __init__(self, mcmc_sampler=None, comm=None):
101 | """See doc of class."""
102 | self.mcmc_sampler = mcmc_sampler
103 | if comm is None:
104 | self._step_methods = self.mcmc_sampler.step_methods
105 | return
106 | rank = comm.Get_rank()
107 | if rank == 0:
108 | names = [var.__name__ for var in self.stochastics]
109 | names.sort()
110 | else:
111 | names = None
112 | names = comm.bcast(names)
113 | sms = []
114 | for name in names:
115 | for var in self.mcmc_sampler.step_method_dict.keys():
116 | if var.__name__ == name:
117 | sms.append(self.mcmc_sampler.step_method_dict[var])
118 | sms_flat = []
119 | for sm in sms:
120 | for s in sm:
121 | sms_flat.append(s)
122 | self._step_methods = sms_flat
123 |
124 | def get_state(self):
125 | """
126 | Get a dictionary describing the state of the sampler.
127 |
128 | Keep in mind that we do not copy the internal parameters of the
129 | sampler (i.e., the step methods, their parameters, etc.). We
130 | only copy the values of all the Stochastics and all the
131 | Deterministics. On contrast pymc.MCMC.get_state() copies the
132 | internal parameters and the Stochastics. It does not copy the
133 | Deterministics. The deterministics are needed because in most
134 | of our examples they are going to be very expensive to
135 | revaluate.
136 |
137 | :returns: A dictionary ``state`` containing the current state of
138 | MCMC. The keys of the dictionary are as follows:
139 |
140 | - ``state['stochastics']``: A dictionary keeping the values of
141 | all stochastic variables.
142 | - ``state['deterministics']``: A dictionary keeping the values
143 | of all deterministic variables.
144 |
145 | :rtype: :class:`dict`
146 |
147 | """
148 | state = dict(stochastics={}, deterministics={})
149 |
150 | # The state of each stochastic parameter
151 | for s in self.stochastics:
152 | state['stochastics'][s.__name__] = s.value
153 |
154 | # The state of each deterministic
155 | for d in self.deterministics:
156 | state['deterministics'][d.__name__] = d.value
157 |
158 | return state
159 |
160 | def set_state(self, state):
161 | """
162 | Set the state of the sampler.
163 |
164 | :parameter state: A dictionary describing the state of the
165 | sampler. Look at
166 | :meth:`pysmc.MCMCWrapper.get_state()` for the
167 | appropriate format.
168 | :type state: :class:`dict`
169 | """
170 | # Restore stochastic parameters state
171 | stoch_state = state.get('stochastics', {})
172 | for sm in self.stochastics:
173 | try:
174 | sm.value = stoch_state[sm.__name__]
175 | except:
176 | warnings.warn(
177 | 'Failed to restore state of stochastic %s from %s backend' %
178 | (sm.__name__, self.db.__name__))
179 |
180 | # Restore the deterministics
181 | det_state = state.get('deterministics', {})
182 | for dm in self.deterministics:
183 | try:
184 | dm._value.force_cache(det_state[dm.__name__])
185 | except:
186 | warnings.warn(
187 | 'Failed to restore state of deterministic %s from %s backend' %
188 | (dm.__name__, self.db.__name__))
189 |
190 | def get_params(self, comm=None):
191 | """
192 | Get a list of dictionaries describing the parameters of each
193 | step method.
194 | """
195 | states = []
196 | for sm in self.step_methods:
197 | states.append(sm.get_params(comm=comm))
198 | return states
199 |
200 | def set_params(self, states):
201 | """
202 | Set the parameters of each step method.
203 | """
204 | for sm, state in itertools.izip(self.step_methods, states):
205 | sm.set_params(state)
206 |
207 | def sample(self, iter, burn=0, thin=None, tune_interval=1,
208 | tune_throughout=False, save_interval=None,
209 | burn_till_tuned=False, stop_tuning_after=0,
210 | verbose=0, progress_bar=False):
211 | """
212 | Sample ``iter`` times from the model.
213 |
214 | This is basically exactly the same call as
215 | :meth:`pymc.MCMC.sample()` but with defaults that are more suitable
216 | for :class:`pysmc.SMC`. For example, you typically do not want to allow
217 | :meth:`pymc.MCMC.sample()`
218 | to tune the parameters of the step methods.
219 | It is :class:`SMC` that should do this.
220 | This is because the transition kernels need to retain their
221 | invariance properties. They can't have parameters that change
222 | on the fly.
223 |
224 | :param iter: The number of iterations to be run.
225 | :type iter: int
226 | :param burn: The number of samples to be burned before we
227 | start storing things to the database. There
228 | is no point in :class:`pysmc.SMC` to have this
229 | equal to anything else than 0.
230 | :type burn: int
231 | :param thin: Store to the database every ``thin``
232 | samples. If ``None`` then we just store the
233 | last sample. That is the method will set
234 | ``thin = iter``.
235 | :type thin: int
236 | :param tune_interval: The tuning interval. The default is not to
237 | tune anything. Do not change it.
238 | :type tune_interval: int
239 | :param tune_throughout: Tune during all the samples we take. The
240 | default is ``False``. Do not change it.
241 | :type tune_throughout: bool
242 | :param burn_till_tuned: Burn samples until the parameters get tuned.
243 | The default is no. Do not change it.
244 | :type burn_till_tuned: bool
245 | :param stop_tuning_after: Stop tuning after a certain number of
246 | iterations. No point in setting this.
247 | :type stop_tuning_after: int
248 | :param verbose: How much verbosity you like.
249 | :type verbose: int
250 | :param progress_bar: Show the progress bar or not.
251 | :type progress_bar: bool
252 | """
253 | if thin is None:
254 | thin = iter
255 | self.mcmc_sampler.sample(iter, burn=burn, thin=thin,
256 | tune_interval=tune_interval,
257 | tune_throughout=tune_throughout,
258 | save_interval=save_interval,
259 | burn_till_tuned=burn_till_tuned,
260 | stop_tuning_after=stop_tuning_after,
261 | verbose=verbose,
262 | progress_bar=progress_bar)
263 |
264 | def draw_from_prior(self):
265 | """
266 | Draw from the prior of the model.
267 |
268 | :raises: :exc:`exceptions.AttributeError` if the action is not
269 | possible.
270 | """
271 | self.mcmc_sampler.draw_from_prior()
272 |
--------------------------------------------------------------------------------
/pysmc/_misc.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | .. _misc:
4 |
5 | ++++++++++++++++++++++
6 | Miscellaneous routines
7 | ++++++++++++++++++++++
8 |
9 | This contains methods that do not fit into the other sections of the
10 | reference manual.
11 |
12 | """
13 |
14 |
15 | __all__ = ['try_to_array', 'get_var_from_particle_list',
16 | 'multinomial_resample', 'kde']
17 |
18 |
19 | import numpy as np
20 | from scipy.stats import gaussian_kde
21 |
22 |
23 | def try_to_array(data):
24 | """
25 | Try to turn the data into a numpy array.
26 |
27 | :returns: If possible, a :class:`numpy.ndarray` containing the
28 | data. Otherwise, it just returns the data.
29 | :rtype: :class:`numpy.ndarray` or ``type(data)``
30 | """
31 | try:
32 | return np.array(data)
33 | except:
34 | return data
35 |
36 |
37 | def get_var_from_particle_list(particle_list, var_name, type_of_var):
38 | """
39 | Get the particles pertaining to variable ``var_name`` of type
40 | ``type_of_var``.
41 |
42 | :param var_name: The name of the variable whose particles you want to
43 | get.
44 | :type var_name: str
45 | :param type_of_var: The type of variables you want to get. This can be
46 | either 'stochastics' or 'deterministics' if you are
47 | are using :mod:`pymc`. The default type is 'stochastics'.
48 | However, I do not restrict its value, in case you
49 | would like to define other types by extending
50 | :mod:`pymc`.
51 | :type type_of_var: str
52 | """
53 | data = [particle_list[i][type_of_var][var_name]
54 | for i in range(len(particle_list))]
55 | return try_to_array(data)
56 |
57 |
58 | def multinomial_resample(p):
59 | """
60 | Sample the multinomial according to ``p``.
61 |
62 | :param p: A numpy array of positive numbers that sum to one.
63 | :type p: 1D :class:`numpy.ndarray`
64 | :returns: A set of indices sampled according to p.
65 | :rtype: 1D :class:`numpy.ndarray` of int
66 | """
67 | p = np.array(p)
68 | assert p.ndim == 1
69 | assert (p >= 0.).all()
70 | births = np.random.multinomial(p.shape[0], p)
71 | idx_list = []
72 | for i in range(p.shape[0]):
73 | idx_list += [i] * births[i]
74 | return np.array(idx_list, dtype='i')
75 |
76 |
77 | def kde(particle_approximation, var_name):
78 | """
79 | Construct a kernel density approximation of the probability density
80 | of ``var_name`` of ``particle_approximation``.
81 |
82 | :param particle_approximation: A particle approximation.
83 | :type particle_approximation: :class:`pysmc.ParticleApproximation`
84 | :returns: A kernel density approximation.
85 | :rtype: :class:`scipy.stats.gaussian_kde`
86 | """
87 | x = getattr(particle_approximation, var_name)
88 | x = np.atleast_2d(x).T
89 | w = particle_approximation.weights
90 | idx = multinomial_resample(w)
91 | return gaussian_kde(x[idx, :].T)
92 |
--------------------------------------------------------------------------------
/pysmc/_mpi.py:
--------------------------------------------------------------------------------
1 | """
2 | .. _distributed_object:
3 |
4 | +++++++++++++++++
5 | DistributedObject
6 | +++++++++++++++++
7 |
8 | The :class:`pysmc.DistributedObject` class implements some basic
9 | functionality that is used by every distributed object in :mod:`pysmc`.
10 |
11 | Here follows its complete reference:
12 |
13 | """
14 |
15 |
16 | __docformat__ = 'reStructuredText'
17 |
18 |
19 | __all__ = ['DistributedObject']
20 |
21 |
22 | class DistributedObject(object):
23 |
24 | """
25 | This is a class that represents an object that is (potentially)
26 | distributed in parallel.
27 | """
28 |
29 | # The mpi class
30 | _mpi = None
31 |
32 | # The communicator
33 | _comm = None
34 |
35 | # The rank
36 | _rank = None
37 |
38 | # The size
39 | _size = None
40 |
41 | @property
42 | def mpi(self):
43 | """
44 | The MPI class.
45 |
46 | :getter: Get the MPI class.
47 | :type: :class:`mpi4py.MPI`
48 | """
49 | return self._mpi
50 |
51 | @property
52 | def comm(self):
53 | """
54 | The MPI communicator.
55 |
56 | :getter: Get the MPI communicator.
57 | :type: :class:`mpi4py.COMM`
58 | """
59 | return self._comm
60 |
61 | @property
62 | def size(self):
63 | """
64 | The size of the MPI pool.
65 |
66 | :getter: Get the size of MPI pool.
67 | :type: int
68 | """
69 | return self._size
70 |
71 | @property
72 | def rank(self):
73 | """
74 | The rank of this process.
75 |
76 | :getter: Get the rank of this process.
77 | :type: int
78 | """
79 | return self._rank
80 |
81 | @property
82 | def use_mpi(self):
83 | """
84 | Check if MPI is being used.
85 |
86 | :returns: ``True`` if MPI is used and ``False`` otherwise.
87 | :rtype: bool
88 | """
89 | return self.comm is not None
90 |
91 | def __init__(self, mpi=None, comm=None):
92 | """
93 | Initialize the object.
94 |
95 | See docstring for full details.
96 | """
97 | self._mpi = mpi
98 | if self.mpi is not None and comm is None:
99 | comm = self.mpi.COMM_WORLD
100 | self._comm = comm
101 | if self.use_mpi:
102 | self._rank = self.comm.Get_rank()
103 | self._size = self.comm.Get_size()
104 | else:
105 | self._rank = 0
106 | self._size = 1
107 |
--------------------------------------------------------------------------------
/pysmc/_particle_approximation.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | .. _particle_approx:
4 |
5 | +++++++++++++++++++++
6 | ParticleApproximation
7 | +++++++++++++++++++++
8 |
9 | :class:`pysmc.ParticleApproximation` is a class that implements a
10 | a particle approximation.
11 |
12 | Here is the complete reference of the public members:
13 |
14 | """
15 |
16 |
17 | __all__ = ['ParticleApproximation']
18 |
19 |
20 | from pysmc._mpi import DistributedObject
21 | from pysmc._misc import get_var_from_particle_list
22 | import numpy as np
23 | import warnings
24 | from copy import deepcopy
25 | import math
26 |
27 |
28 | class ParticleApproximation(DistributedObject):
29 |
30 | """
31 | Initialize a particle approximation.
32 |
33 | If :math:`x` denotes collectively all the variables involved in the
34 | particle approximation, then this object represents :math:`p(x)` as
35 | discussed in the :ref:`tutorial`.
36 |
37 | **Base class:** :class:`pysmc.DistributedObject`
38 |
39 | :param log_w: The logarithms of the weights of the particle
40 | approximation.
41 | :type log_w: 1D :class:`numpy.ndarray`
42 | :param particles: The particles.
43 | :type particles: list of dict
44 | :param mpi: Specify this if you are creating a distributed particle
45 | approximation.
46 | :type mpi: :class:`mpi4py.MPI`
47 | :param comm: The MPI communicator.
48 | :type comm: :class:`mpi4py.COMM`
49 |
50 | .. note::
51 |
52 | When creating a distributed object, the particles must already be
53 | scattered.
54 |
55 | """
56 | # The logarithms of the weights
57 | _log_w = None
58 |
59 | # The weights of the approximation
60 | _weights = None
61 |
62 | # The particles as returned from pysmc.SMC
63 | _particles = None
64 |
65 | # The mean of the approximation
66 | _mean = None
67 |
68 | # The variance of the approximation
69 | _variance = None
70 |
71 | @property
72 | def log_w(self):
73 | """
74 | The logarithms of the weights of the particle approximation.
75 |
76 | :getter: Get the logarithms of the weights of the particle
77 | approximation.
78 | :type: 1D :class:`numpy.ndarray`
79 | """
80 | return self._log_w
81 |
82 | @property
83 | def weights(self):
84 | """
85 | The weights of the particle approximation.
86 |
87 | :getter: Get the weights of the particle approximation.
88 | :type: 1D :class:`numpy.ndarray`
89 | """
90 | return self._weights
91 |
92 | @property
93 | def particles(self):
94 | """
95 | The particles of the particle approximation.
96 |
97 | :getter: Get the particles of the particle approximation.
98 | :type: A list of whatever types the approximation has.
99 | """
100 | return self._particles
101 |
102 | @property
103 | def my_num_particles(self):
104 | """
105 | The number of particles owned by this process.
106 |
107 | :getter: Get the number of particles owned by this process.
108 | :type: int
109 | """
110 | return len(self.particles)
111 |
112 | @property
113 | def num_particles(self):
114 | """
115 | The number of particles.
116 |
117 | :getter: Get the number of particles.
118 | :type: int
119 | """
120 | return self.my_num_particles * self.size
121 |
122 | @property
123 | def mean(self):
124 | """
125 | The mean of the variables of all types of the particle approximation.
126 |
127 | The mean of a variable :math:`x` is computed as:
128 |
129 | .. math::
130 | m(x) := \\sum_{j=1}^N w^{(j)} x^{(j)}.
131 | :label: x_mean
132 |
133 | :getter: Get the mean of the particles.
134 | :type: dict
135 | """
136 | self.compute_all_means()
137 | return self._mean
138 |
139 | @property
140 | def variance(self):
141 | """
142 | The variance of all the variables of all types of the particle
143 | approximation.
144 |
145 | The variance of a variable :math:`x` is computed as:
146 |
147 | .. math::
148 | v(x) := \\sum_{j=1}^N w^{(j)} \\left(x^{(j)}\\right)^2 - m^2(x),
149 | :label: x_var
150 |
151 | where :math:`m(x)` is given in :eq:`x_mean`.
152 |
153 | :getter: Get the variance of all particles.
154 | :type: dict
155 | """
156 | self.compute_all_variances()
157 | return self._variance
158 |
159 | def _fix_particles_of_type_and_name(self, type_of_var, var_name):
160 | """
161 | Expose the particles themselves.
162 | """
163 | var_data = get_var_from_particle_list(self.particles, var_name,
164 | type_of_var=type_of_var)
165 | if (var_data.ndim == 2 and (var_data.shape[0] == 1
166 | or var_data.shape[1] == 0)):
167 | var_data = var_data.flatten()
168 | setattr(self, var_name, var_data)
169 | getattr(self, type_of_var)[var_name] = var_data
170 | self._mean[type_of_var][var_name] = None
171 | self._variance[type_of_var][var_name] = None
172 |
173 | def _fix_particles_of_type(self, type_of_var):
174 | """
175 | Expose the dictionary of the type of vars.
176 | """
177 | setattr(self, type_of_var, dict())
178 | self._mean[type_of_var] = {}
179 | self._variance[type_of_var] = {}
180 | for var_name in list(self.particles[0][type_of_var].keys()):
181 | self._fix_particles_of_type_and_name(type_of_var, var_name)
182 |
183 | def _fix_particles(self):
184 | """
185 | Fix the local variables based on the particles stored locally.
186 | """
187 | self._mean = dict()
188 | self._variance = dict()
189 | for type_of_var in list(self.particles[0].keys()):
190 | self._fix_particles_of_type(type_of_var)
191 |
192 | def __init__(self, log_w=None, particles=None, mpi=None, comm=None):
193 | """
194 | Initialize the particle approximation.
195 |
196 | See the doc of this class for further details.
197 | """
198 | super(ParticleApproximation, self).__init__(mpi=mpi, comm=comm)
199 | if particles is not None:
200 | self._particles = particles[:]
201 | if log_w is None:
202 | log_w = (np.ones(self.my_num_particles) *
203 | (-math.log(self.num_particles)))
204 | self._log_w = log_w[:]
205 | self._weights = np.exp(log_w)
206 | self._fix_particles()
207 |
208 | def __getstate__(self):
209 | """
210 | Get the state of the object so that it can be stored.
211 | """
212 | state = dict()
213 | state['log_w'] = self.log_w
214 | state['particles'] = self.particles
215 | return state
216 |
217 | def __setstate__(self, state):
218 | """
219 | Set the state of the object.
220 | """
221 | self.__init__(log_w=state['log_w'],
222 | particles=state['particles'])
223 |
224 | def _check_if_valid_type_of_var(self, type_of_var):
225 | """
226 | Check if ``type_of_var`` is a valid type of variable.
227 | """
228 | if type_of_var not in self.particles[0]:
229 | raise RuntimeError(
230 | 'The particles do not have a \'%s\' type of variables!' % type_of_var)
231 |
232 | def _check_if_valid_var(self, var_name, type_of_var):
233 | """
234 | Check if ``var_name`` of type ``type_of_var`` exists.
235 | """
236 | self._check_if_valid_type_of_var(type_of_var)
237 | if var_name not in self.particles[0][type_of_var]:
238 | raise RuntimeError(
239 | 'The particles do not have a \'%s\' variable of type \'%s\'!'
240 | % (var_name, type_of_var))
241 |
242 | def get_particle_approximation_of(self, func, var_name,
243 | type_of_var='stochastics',
244 | func_name='func'):
245 | """
246 | Returns the particle approximation of a function of ``var_name``
247 | variable of type ``type_of_var`` of the particle approximation.
248 |
249 | Let the variable and the function we are referring to be :math:`x` and
250 | :math:`f(x)`, respectively. Then, let :math:`y = f(x)` denote the
251 | induced random variable when we pass :math:`x` through the function.
252 | The method returns the following particle approximation to the
253 | probability density of :math:`y`:
254 |
255 | .. math::
256 | p(y) \\approx \\sum_{j=1}^N w^{(j)}
257 | \\delta\\left(y - f\\left(x^{(j)} \\right)\\right)
258 |
259 | :param func: A function of the desired variable.
260 | :type func: function
261 | :param var_name: The name of the desired variable.
262 | :type var_name: str
263 | :param type_of_var: The type of the variable.
264 | :type type_of_var: str
265 | :param func_name: A name for the function. The new variable will be
266 | named ``func_name + '_' + var_name``.
267 | :type func_name: str
268 | :returns: A particle approximation representing the random
269 | variable ``func(var_name)``.
270 | :rtype: :class:`pysmc.ParticleApproximation`
271 | """
272 | self._check_if_valid_var(var_name, type_of_var)
273 | func_var_name = func_name + '_' + var_name
274 | weights = self.weights
275 | particles = [dict() for i in range(self.my_num_particles)]
276 | for i in range(self.my_num_particles):
277 | particles[i][type_of_var] = dict()
278 | func_part_i = func(self.particles[i][type_of_var][var_name])
279 | particles[i][type_of_var][func_var_name] = func_part_i
280 | return ParticleApproximation(weights=weights, particles=particles)
281 |
282 | def get_mean_of_func(self, func, var_name, type_of_var):
283 | """
284 | Get the mean of the ``func`` applied on ``var_name`` which is of type
285 | ``type_of_var``.
286 |
287 | Let the variable and the function we are referring to be :math:`x` and
288 | :math:`f(x)`, respectively. Then the method computes and returns:
289 |
290 | .. math::
291 | \sum_{j=1}^Nw^{(j)}f\left(x^{(j)}\\right).
292 |
293 | :param func: A function of one variable.
294 | :type func: function
295 | :param var_name: The name of the variable.
296 | :type var_name: str
297 | :param type_of_var: The type of the variable.
298 | :type type_of_var: str
299 | :returns: The mean of the random variable :math:`y = f(x)`.
300 | :rtype: unknown
301 | """
302 | self._check_if_valid_var(var_name, type_of_var)
303 | res = 0.
304 | for i in range(self.my_num_particles):
305 | res += (self.weights[i] *
306 | func(self.particles[i][type_of_var][var_name]))
307 | if self.use_mpi:
308 | res = self.comm.reduce(res, op=self.mpi.SUM)
309 | return res
310 |
311 | def compute_mean_of_var(self, var_name, type_of_var,
312 | force_calculation=False):
313 | """
314 | Compute the mean of the particle approximation.
315 |
316 | :param var_name: The name of the variable.
317 | :type var_name: str
318 | :param type_of_var: The type of the variable.
319 | :type type_of_var: str
320 | :param force_calculation: Computes the statistics even if a previous
321 | calculation was already made.
322 | :type force_calculation: bool
323 | """
324 | self._check_if_valid_var(var_name, type_of_var)
325 | if (self._mean[type_of_var][var_name] is not None
326 | and not force_calculation):
327 | return
328 | self._mean[type_of_var][var_name] = self.get_mean_of_func(
329 | lambda x: x, var_name, type_of_var)
330 |
331 | def compute_all_means_of_type(self, type_of_var,
332 | force_calculation=False):
333 | """
334 | Compute the means of every variable of a type ``type_of_var``.
335 |
336 | :param type_of_var: The type of the variable.
337 | :type type_of_var: str
338 | :param force_calculation: Computes the statistics even if a previous
339 | calculation was already made.
340 | :type force_calculation: bool
341 | """
342 | var_names = list(self.particles[0][type_of_var].keys())
343 | var_names.sort()
344 | for var_name in var_names:
345 | self.compute_mean_of_var(var_name, type_of_var,
346 | force_calculation=force_calculation)
347 |
348 | def compute_all_means(self, force_calculation=False):
349 | """
350 | Compute all the means associated with the particle approximation.
351 |
352 | :param force_calculation: Computes the statistics even if a previous
353 | calculation was already made.
354 | :type force_calculation: bool
355 | """
356 | type_of_vars = list(self.particles[0].keys())
357 | type_of_vars.sort()
358 | for type_of_var in type_of_vars:
359 | self.compute_all_means_of_type(type_of_var,
360 | force_calculation=force_calculation)
361 |
362 | def compute_variance_of_var(self, var_name, type_of_var,
363 | force_calculation=False):
364 | """
365 | Compute the variance of ``var_name``.
366 |
367 | :param var_name: The name of the variable.
368 | :type var_name: str
369 | :param type_of_var: The type of the variable.
370 | :type type_of_var: str
371 |
372 | :param force_calculation: Computes the statistics even if a previous
373 | calculation was already made.
374 | :type force_calculation: bool
375 | """
376 | self._check_if_valid_var(var_name, type_of_var)
377 | if (self._variance[type_of_var][var_name] is not None
378 | and not force_calculation):
379 | return
380 | self._variance[type_of_var][var_name] = self.get_mean_of_func(
381 | lambda x: x ** 2, var_name, type_of_var)
382 | self.compute_mean_of_var(var_name, type_of_var,
383 | force_calculation=force_calculation)
384 | if self.rank == 0:
385 | self._variance[type_of_var][var_name] -= (
386 | self._mean[type_of_var][var_name] ** 2)
387 |
388 | def compute_all_variances_of_type(self, type_of_var,
389 | force_calculation=False):
390 | """
391 | Compute all variances of type ``type_of_var``.
392 |
393 | :param type_of_var: The type of the variable.
394 | :type type_of_var: str
395 | :param force_calculation: Computes the statistics even if a previous
396 | calculation was already made.
397 | :type force_calculation: bool
398 | """
399 | var_names = list(self.particles[0][type_of_var].keys())
400 | var_names.sort()
401 | for var_name in var_names:
402 | self.compute_variance_of_var(var_name, type_of_var,
403 | force_calculation=force_calculation)
404 |
405 | def compute_all_variances(self, force_calculation=False):
406 | """
407 | Compute all the variances.
408 |
409 | :param force_calculation: Computes the statistics even if a previous
410 | calculation was already made.
411 | :type force_calculation: bool
412 | """
413 | type_of_vars = list(self.particles[0].keys())
414 | type_of_vars.sort()
415 | for type_of_var in type_of_vars:
416 | self.compute_all_variances_of_type(type_of_var,
417 | force_calculation=force_calculation)
418 |
419 | def compute_all_statistics(self, force_calculation=False):
420 | """
421 | Compute all the statistics of the particle approximation.
422 |
423 | :param force_calculation: Computes the statistics even if a previous
424 | calculation was already made.
425 | :type force_calculation: bool
426 | """
427 | self.compute_all_means(force_calculation=force_calculation)
428 | self.compute_all_variances(force_calculation=force_calculation)
429 |
430 | def resample(self):
431 | """
432 | Resample the particles. After calling this, all particles will have
433 | the same weight.
434 | """
435 | idx_list = []
436 | log_w_all = np.ndarray(self.num_particles)
437 | if self.use_mpi:
438 | self.comm.Gather([self.log_w, self.mpi.DOUBLE],
439 | [log_w_all, self.mpi.DOUBLE])
440 | else:
441 | log_w_all = self.log_w
442 | if self.rank == 0:
443 | births = np.random.multinomial(self.num_particles,
444 | np.exp(log_w_all))
445 | for i in range(self.num_particles):
446 | idx_list += [i] * births[i]
447 | if self.rank == 0:
448 | idx = np.array(idx_list, 'i')
449 | else:
450 | idx = np.ndarray(self.num_particles, 'i')
451 | if self.use_mpi:
452 | self.comm.Bcast([idx, self.mpi.INT])
453 | self.comm.barrier()
454 | num_particles = self.num_particles
455 | my_num_particles = self.my_num_particles
456 | old_particles = self._particles
457 | self._particles = []
458 | for i in range(num_particles):
459 | to_whom = i // my_num_particles
460 | from_whom = idx[i] // my_num_particles
461 | if from_whom == to_whom and to_whom == self.rank:
462 | my_idx = idx[i] % my_num_particles
463 | self._particles.append(old_particles[my_idx].copy())
464 | elif to_whom == self.rank:
465 | self._particles.append(self.comm.recv(
466 | source=from_whom, tag=i))
467 | elif from_whom == self.rank:
468 | my_idx = idx[i] % my_num_particles
469 | self.comm.send(old_particles[my_idx], dest=to_whom, tag=i)
470 | self.comm.barrier()
471 | else:
472 | self._particles = [self._particles[i].copy() for i in idx]
473 | self.log_w.fill(-math.log(self.num_particles))
474 | self._weights = np.exp(self.log_w)
475 | self._fix_particles()
476 |
477 | def copy(self):
478 | """
479 | Copy the particle approximation.
480 |
481 | :returns: A copy of the current particle approximation.
482 | :rtype: :class:`pysmc.ParticleApproximation`
483 | """
484 | new_pa = ParticleApproximation(self.log_w, self.particles,
485 | mpi=self.mpi, comm=self.comm)
486 | new_pa._mean = deepcopy(self.mean)
487 | new_pa._variance = deepcopy(self.variance)
488 | return new_pa
489 |
490 | def allgather(self):
491 | """
492 | Get a particle approximation on every process.
493 |
494 | If we are not using MPI, it will simply return a copy of the object.
495 |
496 | :returns: A fully functional particle approximation on a single
497 | process.
498 | :rtype: :class:`pysmc.ParticleApproximation`
499 | """
500 | if not self.use_mpi:
501 | return self.copy()
502 | log_w = np.hstack(self.comm.allgather(self._log_w))
503 | tmp = self.comm.allgather(self.particles)
504 | particles = [t[i] for t in tmp for i in range(len(t))]
505 | return ParticleApproximation(log_w=log_w, particles=particles)
506 |
507 | def gather(self, root=0):
508 | """
509 | Get a particle approximation on the root process.
510 |
511 | If we are not using MPI, it will simply return a copy of the object.
512 |
513 | :param root: The id of the root process.
514 | :returns: A fully functional particle approximation on a single
515 | process.
516 | :rtype: :class:`pysmc.ParticleApproximation`
517 | """
518 | if not self.use_mpi:
519 | return self.copy()
520 | log_w = self.comm.gather(self._log_w)
521 | tmp = self.comm.gather(self.particles)
522 | if self.rank == 0:
523 | log_w = np.hstack(log_w)
524 | particles = [t[i] for t in tmp for i in range(len(t))]
525 | return ParticleApproximation(log_w=log_w, particles=particles)
526 | else:
527 | return None
528 |
529 | @staticmethod
530 | def scatter(pa, mpi=None, comm=None):
531 | """
532 | Scatter the particle approximation across the tasks.
533 |
534 | Assuming here that only the root processor (rank == 0) has it.
535 | """
536 | if mpi is None:
537 | return pa
538 | if comm is None:
539 | comm = mpi.COMM_WORLD
540 | rank = comm.Get_rank()
541 | size = comm.Get_size()
542 | if rank == 0:
543 | chunk = pa.num_particles / size
544 | log_w = [pa.log_w[i * chunk:(i + 1) * chunk]
545 | for i in range(size)]
546 | particles = [pa.particles[i * chunk:(i + 1) * chunk]
547 | for i in range(size)]
548 | else:
549 | log_w = None
550 | particles = None
551 | my_log_w = comm.scatter(log_w)
552 | my_particles = comm.scatter(particles)
553 | return ParticleApproximation(log_w=my_log_w, particles=my_particles,
554 | mpi=mpi, comm=comm)
555 |
--------------------------------------------------------------------------------
/pysmc/_plot.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | .. _plot:
4 |
5 | ++++++++
6 | Plotting
7 | ++++++++
8 |
9 | """
10 |
11 |
12 | __all__ = ['hist', 'make_movie_from_db']
13 |
14 |
15 | import os
16 | import numpy as np
17 | import matplotlib.pyplot as plt
18 | import matplotlib.animation as animation
19 | from pysmc._misc import multinomial_resample
20 | from pysmc._misc import kde
21 |
22 |
23 | def hist(particle_approximation, var_name, normed=True):
24 | """
25 | Plot the histogram of variable of a particle approximation.
26 |
27 | :param particle_approximation: A particle approximation.
28 | :type particle_approximation: :class:`pysmc.ParticleApproximation`
29 | :param var_name: The name of the variable you want to plot.
30 | :type var_name: str
31 | :param bins: The number of bins you want to use.
32 | :type bins: int
33 | :param normed: ``True`` if you want the histogram to be normalized,
34 | ``False`` otherwise.
35 | :type normed: bool
36 | """
37 | x = getattr(particle_approximation, var_name)
38 | w = particle_approximation.weights
39 | bins = w.shape[0] // 10
40 | plt.xlabel(var_name, fontsize=16)
41 | plt.ylabel('p(%s)' % var_name, fontsize=16)
42 | return plt.hist(x, weights=w, bins=bins, density=normed)
43 |
44 |
45 | def make_movie_from_db(db, var_name):
46 | """
47 | Make a movie from a database.
48 | """
49 | k = kde(db.particle_approximations[0], var_name)
50 | x01 = k.dataset.min()
51 | x02 = k.dataset.max()
52 | x0 = np.linspace(x01, x02, 100)
53 | y0 = k(x0)
54 | k = kde(db.particle_approximations[-1], var_name)
55 | yl = k(x0)
56 | yl1 = yl.min()
57 | yl2 = yl.max()
58 | fig = plt.figure()
59 | ax = fig.add_subplot(111, autoscale_on=False,
60 | xlim=(x01, x02), ylim=(yl1, yl2))
61 | line, = ax.plot([], [], linewidth=2)
62 | particles, = ax.plot([], [], 'ro', markersize=5)
63 | gamma_text = ax.text(0.02, 0.95, '', transform=ax.transAxes,
64 | fontsize=16)
65 | ax.set_xlabel(var_name, fontsize=16)
66 | ax.set_ylabel('p(%s)' % var_name, fontsize=16)
67 |
68 | def init():
69 | line.set_data([], [])
70 | particles.set_data([], [])
71 | gamma_text.set_text('')
72 | return line, particles, gamma_text
73 |
74 | def animate(i):
75 | k = kde(db.particle_approximations[i], var_name)
76 | line.set_data(x0, k(x0))
77 | p = getattr(db.particle_approximations[i], var_name)
78 | particles.set_data(p, np.zeros(p.shape) + yl1 + 0.01 * (yl2 - yl1))
79 | gamma_text.set_text('%s = %1.4f' % (db.gamma_name, db.gammas[i]))
80 | return line, particles, gamma_text
81 |
82 | ani = animation.FuncAnimation(fig, animate, frames=db.num_gammas,
83 | interval=200, blit=True, init_func=init)
84 | return ani
85 |
--------------------------------------------------------------------------------
/pysmc/_smc.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | .. _smc:
4 |
5 | +++
6 | SMC
7 | +++
8 |
9 | :class:`pysmc.SMC` is the class that makes everything happen.
10 |
11 | Here is a complete reference of the public members:
12 | """
13 |
14 |
15 | __all__ = ['SMC']
16 |
17 |
18 | from pysmc._mcmc_wrapper import MCMCWrapper
19 | from pysmc._mpi import DistributedObject
20 | from pysmc._particle_approximation import ParticleApproximation
21 | from pysmc._db import DataBase
22 | from pysmc._db import SerialDataBase
23 | import pymc
24 | import numpy as np
25 | from scipy.optimize import brentq
26 | import math
27 | import itertools
28 | import sys
29 | import warnings
30 | import os
31 |
32 |
33 | class SMC(DistributedObject):
34 | """
35 | Use Sequential Monte Carlo (SMC) to sample from a distribution.
36 |
37 | **Base class:** :class:`pysmc.DistributedObject`
38 |
39 | :param mcmc_sampler: This is an essential part in initializing the
40 | object. It can either be a ready to go
41 | MCMC sampler or a module/class representing
42 | a :mod:`pymc` model. In the latter case, the
43 | MCMC sampler will be initialized automatically.
44 | :type mcmc_sampler: :class:`pymc.MCMC`, :class:`pysmc.MCMCWrapper`
45 | or a :mod:`pymc` model
46 | :param num_particles: The number of particles.
47 | :type num_particles: int
48 | :param num_mcmc: The number of MCMC steps per gamma.
49 | :type num_mcmc: int
50 | :param ess_threshold: The ESS threshold below which resampling
51 | takes place.
52 | :type ess_threshold: float
53 | :param ess_reduction: The ESS reduction that adaptively specifies
54 | the next ``gamma``.
55 | :type ess_reduction: float
56 | :param adapt_proposal_step: Adapt or not the proposal step by
57 | monitoring the acceptance rate.
58 | :type adapt_proposal_step: bool
59 | :param verbose: How much output to print (1, 2 and 3).
60 | :type verbose: int
61 | :param gamma_name: The name with which the ``gamma`` parameter is
62 | refered to in your :mod:`pymc` model. The
63 | default value is ``'gamma'``, but you can
64 | change it to whatever you want.
65 | :type gamma_name: str
66 | :param gamma_is_an_exponent: A flag that should be ``True`` if ``gamma``
67 | appears as an exponent in the probability
68 | density, e.g.,
69 | :math:`p(x | y) \\propto p(y | x)^{\\gamma} p(x)`.
70 | The default value is ``False``. However, if
71 | your model is of the right form **it pays off**
72 | to set it to ``True``. Then we can solve the
73 | problem of finding the next :math:``\gamma``
74 | in the sequence a lot faster.
75 | :type gamma_is_an_exponent: bool
76 | :param db_filename: The filename of a database for the object. If
77 | the database exists and is a valid one, then
78 | the object will be initialized at each last
79 | state. If the parameter ``update_db`` is also
80 | set, then the algorithm will dump the state of
81 | each ``gamma`` it visits and commit it to the
82 | data base. Otherwise, commits can be forced by
83 | calling :meth:`pysmc.SMC.commit()`.
84 | :type db_filename: str
85 | :param mpi: The MPI class (see :mod:`mpi4py` and
86 | :ref:`mpi_example`). If ``None``, then no
87 | parallelism is used.
88 | :param comm: Set this to the MPI communicator. If ``None``,
89 | then ``mpi.COMM_WORLD`` is used.
90 | """
91 |
92 | # The logarithm of the weights
93 | _log_w = None
94 |
95 | # The current effective sample size
96 | _ess = None
97 |
98 | # The number of MCMC steps for each gamma
99 | _num_mcmc = None
100 |
101 | # The thresshold of the effective sample size (percentage)
102 | _ess_threshold = None
103 |
104 | # The reduction of the effective sample size per gamma step
105 | _ess_reduction = None
106 |
107 | # Do you want to adaptively select the MCMC proposal step?
108 | _adapt_proposal_step = None
109 |
110 | # The amount of verbosity
111 | _verbose = None
112 |
113 | # The monte carlo sampler
114 | _mcmc_sampler = None
115 |
116 | # The particles
117 | _particles = None
118 |
119 | # The underlying MCMC sampler
120 | _mcmc_sampler = None
121 |
122 | # The observed random variable
123 | _gamma_rv = None
124 |
125 | # The true name of the gamma parameter
126 | _gamma_name = None
127 |
128 | # A database containing all the particles at all gammas
129 | _db = None
130 |
131 | # Update the database or not
132 | _update_db = False
133 |
134 | # Count the total number of MCMC samples taken so far
135 | _total_num_mcmc = None
136 |
137 | # Does gamma appear as an exponent in the probability density?
138 | _gamma_is_an_exponent = None
139 |
140 | @property
141 | def gamma_is_an_exponent(self):
142 | """
143 | The Flag that determines if gamma is an exponent in the probability
144 | density.
145 |
146 | :getter: Get the flag.
147 | :setter: Set the flag.
148 | :type: bool
149 | """
150 | return self._gamma_is_an_exponent
151 |
152 | @gamma_is_an_exponent.setter
153 | def gamma_is_an_exponent(self, value):
154 | """Set the value of the flag."""
155 | value = bool(value)
156 | self._gamma_is_an_exponent = value
157 |
158 | @property
159 | def my_num_particles(self):
160 | """
161 | :getter: The number of particles associated with each process.
162 | :type: int
163 |
164 | .. note::
165 |
166 | If not using MPI, then it is the same as
167 | :meth:`pysmc.SMC.num_particles`. Otherwise is it is equal to
168 | ``num_particles / size``, where ``size`` is the total number of MPI
169 | processes.
170 |
171 | """
172 | return len(self.particles)
173 |
174 | @property
175 | def num_particles(self):
176 | """
177 | :getter: Get the number of particles.
178 | :type: int
179 | """
180 | return self.my_num_particles * self.size
181 |
182 | @property
183 | def log_w(self):
184 | """
185 | :getter: The logarithm of the weights of the particles.
186 | :type: 1D :class:`numpy.ndarray`
187 | """
188 | return self._log_w
189 |
190 | @property
191 | def ess(self):
192 | """
193 | :getter: The current Effective Sample Size.
194 | :type: float
195 | """
196 | return self._ess
197 |
198 | @property
199 | def num_mcmc(self):
200 | """
201 | :getter: Get the number of MCMC steps per SMC step.
202 | :setter: Set the number of MCMC steps per SMC step.
203 | :type: int
204 | :raises: :exc:`exceptions.ValueError`
205 | """
206 | return self._num_mcmc
207 |
208 | @num_mcmc.setter
209 | def num_mcmc(self, value):
210 | """Set the number of MCMC steps per gamma."""
211 | value = int(value)
212 | if value <= 0:
213 | raise ValueError('num_mcmc <= 0!')
214 | self._num_mcmc = value
215 |
216 | @property
217 | def ess_threshold(self):
218 | """
219 | The threshold of the Effective Sample Size is a number between 0 and 1
220 | representing the percentage of the total number of particles. If the
221 | Effective Sample Size falls bellow ``ess_threshold * num_particles``,
222 | then the particles are automatically resampled.
223 |
224 | :getter: Get the threshold of the Effective Sample Size.
225 | :setter: Set the threshold of the Effective Sample Size.
226 | :type: float
227 | :raises: :exc:`exceptions.ValueError`
228 | """
229 | return self._ess_threshold
230 |
231 | @ess_threshold.setter
232 | def ess_threshold(self, value):
233 | """Set the threshold of the effective sample size."""
234 | value = float(value)
235 | if value <= 0. or value >= 1.:
236 | raise ValueError('The ESS threshold must be in (0, 1).')
237 | self._ess_threshold = value
238 |
239 | @property
240 | def ess_reduction(self):
241 | """
242 | It is a number between 0 and 1 representing the desired
243 | percent reduction
244 | of the effective sample size when we perform a SMC step.
245 | The next ``gamma`` will be selected adaptively so that the prescribed
246 | reduction is achieved.
247 |
248 | :getter: Get the reduction of the Effective Sample Size per SMC
249 | step.
250 | :setter: Set the reduction of the Effective Sample Size per SMC
251 | step.
252 | :type: float
253 | :raises: :exc:`exceptions.ValueError`
254 | """
255 | return self._ess_reduction
256 |
257 | @ess_reduction.setter
258 | def ess_reduction(self, value):
259 | """Set the reduction of the effective sample size per SMC step."""
260 | value = float(value)
261 | if value <= 0. or value >= 1.:
262 | raise ValueError('The ESS reduction must be in (0, 1).')
263 | self._ess_reduction = value
264 |
265 | @property
266 | def adapt_proposal_step(self):
267 | """
268 | If the ``adapt proposal step`` is set to ``True``, each of the step
269 | methods of the underlying :class:`pymc.MCMC` class are adaptively
270 | tuned by monitoring the acceptance rate.
271 |
272 | :getter: Get the adapt flag.
273 | :setter: Set the adapt flag.
274 | :type: bool
275 | """
276 | return self._adapt_proposal_step
277 |
278 | @adapt_proposal_step.setter
279 | def adapt_proposal_step(self, value):
280 | """Set the adapt flag."""
281 | value = bool(value)
282 | self._adapt_proposal_step = value
283 |
284 | @property
285 | def verbose(self):
286 | """
287 | Specify the amount of output printed by the class. There are three
288 | levels:
289 |
290 | + 0: Print nothing.
291 | + 1: Print info from methods you call.
292 | + 2: Print info from methods the methods you call call...
293 | + 3: Guess what...
294 |
295 | :getter: Get the verbosity flag.
296 | :setter: Set the verbosity flag.
297 | :type: int
298 | """
299 | if self.rank == 0:
300 | return self._verbose
301 | else:
302 | return 0
303 |
304 | @verbose.setter
305 | def verbose(self, value):
306 | """Set the verbosity flag."""
307 | value = int(value)
308 | self._verbose = value
309 |
310 | @property
311 | def mcmc_sampler(self):
312 | """
313 | The underlying :class:`pymc.MCMC` object.
314 |
315 | :getter: Get the underlying MCMC object.
316 | :setter: Set the underlying MCMC object.
317 | :raises: :exc:`exceptions.TypeError`
318 | :type: :class:`pymc.MCMC`
319 | """
320 | return self._mcmc_sampler
321 |
322 | @property
323 | def db(self):
324 | """
325 | The database containing info about all the particles we visited.
326 |
327 | :getter: Get the database.
328 | :type: dict
329 | """
330 | return self._db
331 |
332 | @property
333 | def update_db(self):
334 | """
335 | Update the database or not.
336 |
337 | :getter: Get the ``update_db`` flag.
338 | :setter: Set the ``update_db`` flag.
339 | :type: bool
340 | """
341 | return self._update_db
342 |
343 | @update_db.setter
344 | def update_db(self, value):
345 | """
346 | Set the ``update_db`` flag.
347 | """
348 | value = bool(value)
349 | self._update_db = value
350 |
351 | def _update_gamma_rv(self):
352 | """Update the variable that points to the observed rv."""
353 | self._gamma_rv = []
354 | for rv in self.mcmc_sampler.nodes:
355 | if self.gamma_name in rv.parents:
356 | self._gamma_rv.append(rv)
357 |
358 | @property
359 | def gamma_rv(self):
360 | """
361 | The random variable of the :mod:`pymc` model that has a parameter named
362 | ``gamma_name``.
363 |
364 | :getter: Get the value of the ``gamma_name`` parameter.
365 | :type: :class:`pymc.Stochastic`
366 | """
367 | return self._gamma_rv
368 |
369 | @property
370 | def gamma(self):
371 | """
372 | The value of the ``gamma_name`` parameter.
373 |
374 | :getter: Get the value of the ``gamma_name`` parameter.
375 | :setter: Set the value of the ``gamma_name`` parameter.
376 | :type: float
377 | """
378 | return self._gamma_rv[0].parents[self.gamma_name]
379 |
380 | def _set_gamma(self, value):
381 | """
382 | Set the value of gamma.
383 | """
384 | for rv in self._gamma_rv:
385 | rv.parents[self.gamma_name] = value
386 |
387 | @property
388 | def gamma_name(self):
389 | """
390 | The true name of the gamma parameter in the :mod:`pymc` model.
391 |
392 | :getter: Get the name of the gamma parameter.
393 | :setter: Set the name of the gamma parameter.
394 | :type: str
395 | """
396 | return self._gamma_name
397 |
398 | @gamma_name.setter
399 | def gamma_name(self, value):
400 | """Set the true name of the gamma parameter."""
401 | self._gamma_name = gamma_name
402 | if self._mcmc_sampler is not None:
403 | self._update_gamma_rv()
404 |
405 | @property
406 | def particles(self):
407 | """
408 | The SMC particles.
409 |
410 | :getter: Get the SMC particles.
411 | :type: list of whatever objects your method supports.
412 | """
413 | return self._particles
414 |
415 | @property
416 | def total_num_mcmc(self):
417 | """
418 | The total number of MCMC steps performed so far.
419 | This is zeroed, everytime you call :meth:`pysmc.SMC.initialize()`.
420 |
421 | :getter: The total number of MCMC steps performed so far.
422 | :type: int
423 | """
424 | if self.use_mpi:
425 | return self.comm.allreduce(self._total_num_mcmc, op=self.mpi.SUM)
426 | else:
427 | return self._total_num_mcmc
428 |
429 | def _logsumexp(self, log_x):
430 | """Perform the log-sum-exp of the weights."""
431 | my_max_exp = log_x.max()
432 | if self.use_mpi:
433 | max_exp = self.comm.allreduce(my_max_exp, op=self.mpi.MAX)
434 | else:
435 | max_exp = my_max_exp
436 | my_sum = np.exp(log_x - max_exp).sum()
437 | if self.use_mpi:
438 | all_sum = self.comm.allreduce(my_sum)
439 | else:
440 | all_sum = my_sum
441 | return math.log(all_sum) + max_exp
442 |
443 | def _normalize(self, log_w):
444 | """Normalize the weights."""
445 | c = self._logsumexp(log_w)
446 | return log_w - c
447 |
448 | def _get_ess_at(self, log_w):
449 | """Calculate the ESS at given the log weights.
450 |
451 | Precondition
452 | ------------
453 | The weights are assumed to be normalized.
454 | """
455 | log_w_all = log_w
456 | if self.use_mpi:
457 | log_w_all = np.ndarray(self.num_particles)
458 | self.comm.Gather([log_w, self.mpi.DOUBLE],
459 | [log_w_all, self.mpi.DOUBLE])
460 | if self.rank == 0:
461 | ess = 1. / math.fsum(np.exp(2. * log_w_all))
462 | else:
463 | ess = None
464 | if self.use_mpi:
465 | ess = self.comm.bcast(ess)
466 | return ess
467 |
468 | def _get_log_of_weight_factor_at(self, gamma):
469 | """Return the log of the weight factor when going to the new gamma."""
470 | if self.gamma_is_an_exponent:
471 | return (gamma - self.gamma)* self._loglike
472 | logp_new = self._get_logp_at_gamma(gamma)
473 | return logp_new - self._logp_prev
474 |
475 | def _get_unormalized_weights_at(self, gamma):
476 | """Return the unormalized weights at a given gamma."""
477 | return self.log_w + self._get_log_of_weight_factor_at(gamma)
478 |
479 | def _get_ess_given_gamma(self, gamma):
480 | """Calculate the ESS at a given gamma.
481 |
482 | Returns
483 | -------
484 | The ess and the normalized weights corresponding to that
485 | gamma.
486 | """
487 | log_w = self._get_unormalized_weights_at(gamma)
488 | log_w_normalized = self._normalize(log_w)
489 | return self._get_ess_at(log_w_normalized)
490 |
491 | def _resample(self):
492 | """Resample the particles.
493 |
494 | Precondition
495 | ------------
496 | The weights are assumed to be normalized.
497 | """
498 | if self.verbose > 1:
499 | sys.stdout.write('- resampling: ')
500 | idx_list = []
501 | log_w_all = np.ndarray(self.num_particles)
502 | if self.use_mpi:
503 | self.comm.Gather([self.log_w, self.mpi.DOUBLE],
504 | [log_w_all, self.mpi.DOUBLE])
505 | else:
506 | log_w_all = self.log_w
507 | if self.rank == 0:
508 | births = np.random.multinomial(self.num_particles,
509 | np.exp(log_w_all))
510 | for i in range(self.num_particles):
511 | idx_list += [i] * births[i]
512 | if self.rank == 0:
513 | idx = np.array(idx_list, 'i')
514 | else:
515 | idx = np.ndarray(self.num_particles, 'i')
516 | if self.use_mpi:
517 | self.comm.Bcast([idx, self.mpi.INT])
518 | self.comm.barrier()
519 | num_particles = self.num_particles
520 | my_num_particles = self.my_num_particles
521 | old_particles = self._particles
522 | self._particles = []
523 | for i in range(num_particles):
524 | to_whom = i // my_num_particles
525 | from_whom = idx[i] // my_num_particles
526 | if from_whom == to_whom and to_whom == self.rank:
527 | my_idx = idx[i] % my_num_particles
528 | self._particles.append(old_particles[my_idx].copy())
529 | elif to_whom == self.rank:
530 | self._particles.append(self.comm.recv(
531 | source=from_whom, tag=i))
532 | elif from_whom == self.rank:
533 | my_idx = idx[i] % my_num_particles
534 | self.comm.send(old_particles[my_idx], dest=to_whom, tag=i)
535 | self.comm.barrier()
536 | else:
537 | self._particles = [self._particles[i].copy() for i in idx]
538 | self.log_w.fill(-math.log(self.num_particles))
539 | self._ess = self.num_particles
540 | if self.verbose > 1:
541 | sys.stdout.write('SUCCESS\n')
542 |
543 | def _tune(self):
544 | """Tune the parameters of the proposals.."""
545 | # TODO: Make sure this actually works!
546 | if self.verbose > 1:
547 | print('- tuning the MCMC parameters:')
548 | for sm in self.mcmc_sampler.step_methods:
549 | if self.verbose > 1:
550 | sys.stdout.write('\t- tuning step method: %s\n' % str(sm))
551 | if sm.tune(self.get_particle_approximation(),
552 | comm=self.comm,
553 | verbose=self.verbose):
554 | if self.verbose > 1:
555 | sys.stdout.write('\n\t\tSUCCESS\n')
556 | else:
557 | if self.verbose > 1:
558 | sys.stdout.write('\n\t\tFAILURE\n')
559 |
560 | def _get_logp_of_particle(self, i):
561 | """
562 | Get the logp of a particle.
563 | """
564 | self.mcmc_sampler.set_state(self.particles[i])
565 | return self.mcmc_sampler.logp
566 |
567 | def _get_logp_of_particles(self):
568 | """
569 | Get the logp of all particles.
570 | """
571 | return np.array([self._get_logp_of_particle(i)
572 | for i in range(self.my_num_particles)])
573 |
574 | def _get_logp_at_gamma(self, gamma):
575 | """
576 | Get the logp at gamma.
577 | """
578 | if gamma is not self.gamma:
579 | old_gamma = self.gamma
580 | self._set_gamma(gamma)
581 | logp = self._get_logp_of_particles()
582 | self._set_gamma(old_gamma)
583 | else:
584 | logp = self._get_logp_of_particles()
585 | return logp
586 |
587 | def _get_loglike(self, gamma0, gamma1):
588 | """
589 | Get the log likelihood assuming that gamma appears in the exponent.
590 | """
591 | logp0 = self._get_logp_at_gamma(gamma0)
592 | logp1 = self._get_logp_at_gamma(gamma1)
593 | return (logp1 - logp0) / (gamma1 - gamma0)
594 |
595 | def _find_next_gamma(self, gamma):
596 | """
597 | Find the next gamma.
598 |
599 | Parameters
600 | ----------
601 | gamma : float
602 | The next gamma is between the current one and ``gamma``.
603 |
604 | Returns
605 | -------
606 | The next gamma.
607 |
608 | """
609 | if self.verbose > 1:
610 | print('- finding next gamma.')
611 |
612 | if self.gamma_is_an_exponent:
613 | self._loglike = self._get_loglike(self.gamma, gamma)
614 | else:
615 | self._logp_prev = self._get_logp_at_gamma(self.gamma)
616 |
617 | # Define the function whoose root we are seeking
618 | def f(test_gamma, args):
619 | ess_test_gamma = args._get_ess_given_gamma(test_gamma)
620 | return ess_test_gamma - args.ess_reduction * args.ess
621 |
622 | if f(gamma, self) > 0:
623 | if self.verbose > 1:
624 | print('- \twe can move directly to the target gamma...')
625 | return gamma
626 | else:
627 | # Solve for the optimal gamma using the bisection algorithm
628 | next_gamma, r = brentq(f, self.gamma, gamma, self, disp=False,
629 | full_output=True)
630 | if self.use_mpi:
631 | self.comm.barrier()
632 | return next_gamma
633 |
634 | def _initialize_db(self, db_filename, update_db):
635 | """
636 | Initialize the database.
637 | """
638 | if db_filename is not None:
639 | if self.rank == 0:
640 | db_filename = os.path.abspath(db_filename)
641 | if self.verbose > 0:
642 | print('- db: ' + db_filename)
643 | if os.path.exists(db_filename):
644 | if self.verbose > 0:
645 | print('- db exists')
646 | print('- assuming this is a restart run')
647 | self._db = DataBase.load(db_filename)
648 | db_exists = True
649 | else:
650 | if self.verbose > 0:
651 | print('- db does not exist')
652 | print('- creating db file')
653 | self._db = DataBase(gamma_name=self.gamma_name,
654 | filename=db_filename)
655 | db_exists = False
656 | else:
657 | db_exists = None
658 | if self.use_mpi:
659 | db_exists = self.comm.bcast(db_exists)
660 | if db_exists:
661 | if self.rank == 0:
662 | if self.verbose > 0:
663 | print('- db:')
664 | print('\t- num. of %s: %d' % (self.gamma_name,
665 | self.db.num_gammas))
666 | print('\t- first %s: %1.4f' % (self.gamma_name,
667 | self.db.gammas[0]))
668 | print('\t- last %s: %1.4f' % (self.gamma_name,
669 | self.db.gammas[-1]))
670 | print('- initializing the object at the last state of db')
671 | gamma = self.db.gamma
672 | pa = self.db.particle_approximation
673 | sm_param = self.db.step_method_param
674 | else:
675 | gamma = None
676 | pa = None
677 | sm_param = None
678 | if self.use_mpi:
679 | gamma = self.comm.bcast(gamma)
680 | pa = ParticleApproximation.scatter(pa, mpi=self.mpi,
681 | comm=self.comm)
682 | sm_param = self.comm.bcast(sm_param)
683 | self.mcmc_sampler.set_params(sm_param)
684 | self.initialize(gamma, particle_approximation=pa)
685 | if self.verbose > 0:
686 | if update_db:
687 | print('- commiting to the database at every step')
688 | else:
689 | print('- manually commiting to the database')
690 | elif update_db:
691 | if self.verbose > 0:
692 | warnings.warn(
693 | '- update_db flag is on but no db_filename was specified\n'
694 | + '- setting the update_db flag to off')
695 | update_db = False
696 | self._update_db = update_db
697 |
698 | def _set_gamma_name(self, gamma_name):
699 | """
700 | Safely, set the gamma_name parameter.
701 | """
702 | if not isinstance(gamma_name, str):
703 | raise TypeError('The \'gamma_name\' parameter must be a str!')
704 | self._gamma_name = gamma_name
705 |
706 | def _set_mcmc_sampler(self, mcmc_sampler):
707 | """
708 | Safely, set the MCMC sampler.
709 | """
710 | if (not isinstance(mcmc_sampler, pymc.MCMC)
711 | and not isinstance(mcmc_sampler, MCMCWrapper)):
712 | # Try to make an MCMC sampler out of it (it will work if it is a
713 | # valid model).
714 | try:
715 | if self.rank == 0:
716 | warnings.warn(
717 | '- mcmc_sampler is not a pymc.MCMC.\n'
718 | + '- attempting to make it one!')
719 | mcmc_sampler = pymc.MCMC(mcmc_sampler)
720 | except:
721 | raise RuntimeError(
722 | 'The mcmc_sampler object could not be converted to a pymc.MCMC!')
723 | if not isinstance(mcmc_sampler, MCMCWrapper):
724 | mcmc_sampler = MCMCWrapper(mcmc_sampler, comm=self.comm)
725 | self._mcmc_sampler = mcmc_sampler
726 | self._update_gamma_rv()
727 |
728 | def _set_initial_particles(self, num_particles):
729 | """
730 | Safely, set the initial particles.
731 | """
732 | num_particles = int(num_particles)
733 | if num_particles <= 0:
734 | raise ValueError('num_particles <= 0!')
735 | my_num_particles = int(num_particles / self.size)
736 | if my_num_particles * self.size < num_particles:
737 | warnings.warn(
738 | '- number of particles (%d) not supported on %d mpi processes' %
739 | (num_particles, self.size))
740 | num_particles = my_num_particles * self.size
741 | warnings.warn(
742 | '- changing the number of particles to %d' % num_particles)
743 | self._particles = [None for i in range(my_num_particles)]
744 | self._log_w = (np.ones(my_num_particles)
745 | * (-math.log(num_particles)))
746 |
747 | def __init__(self, mcmc_sampler=None,
748 | num_particles=10, num_mcmc=10,
749 | ess_threshold=0.67,
750 | ess_reduction=0.90,
751 | adapt_proposal_step=True,
752 | verbose=0,
753 | mpi=None,
754 | comm=None,
755 | gamma_name='gamma',
756 | db_filename=None,
757 | update_db=False,
758 | gamma_is_an_exponent=False):
759 | """
760 | Initialize the object.
761 |
762 | See the doc of the class for the description.
763 | """
764 | super(SMC, self).__init__(mpi=mpi, comm=comm)
765 | self._set_gamma_name(gamma_name)
766 | self._set_mcmc_sampler(mcmc_sampler)
767 | self._set_initial_particles(num_particles)
768 | self.num_mcmc = num_mcmc
769 | self.ess_threshold = ess_threshold
770 | self.ess_reduction = ess_reduction
771 | self.verbose = verbose
772 | self.adapt_proposal_step = adapt_proposal_step
773 | self.gamma_is_an_exponent = gamma_is_an_exponent
774 | self._initialize_db(db_filename, update_db)
775 |
776 | def initialize(self, gamma, particle_approximation=None,
777 | num_mcmc_per_particle=10):
778 | """
779 | Initialize SMC at a particular ``gamma``.
780 |
781 | The method has basically three ways of initializing the particles:
782 |
783 | + If ``particles_approximation`` is not ``None``,
784 | then it is assumed to contain the
785 | particles at the corresponding value of ``gamma``.
786 | + If ``particles_approximation`` is ``None`` and the
787 | MCMC sampler class has a method
788 | called ``draw_from_prior()`` that works, then it is called to
789 | initialize the particles.
790 | + In any other case, MCMC sampling is used to initialize the particles.
791 | We are assuming that the MCMC sampler has already been tuned for
792 | that particular gamma and that a sufficient burning period has past.
793 | Then we record the current state as the first particle, we sample
794 | ``num_mcmc_per_particle`` times and record the second particle, and
795 | so on.
796 |
797 | :param gamma: The initial ``gamma`` parameter. It must, of
798 | course, be within the right range of
799 | ``gamma``.
800 | :type gamma: float
801 | :param particles_approximation: A dictionary of MCMC states representing
802 | the particles. When using MPI, we are
803 | assuming that each one of the CPU's
804 | has each own collection of particles.
805 | :type particles_approximation: :class:`pysmc.ParticleApproximation`
806 | :param num_mcmc_per_particle: This parameter is ignored if
807 | ``particles`` is not ``None``. If the
808 | only way to initialize the particles is
809 | to use MCMC, then this is the number of
810 | of mcmc samples we drop before getting
811 | a SMC particle.
812 | """
813 | if self.verbose > 0:
814 | print('------------------------')
815 | print('START SMC Initialization')
816 | print('------------------------')
817 | print('- initializing at', self.gamma_name, ':', gamma)
818 | # Zero out the MCMC step counter
819 | self._total_num_mcmc = 0
820 | # Set gamma
821 | self._set_gamma(gamma)
822 | # Set the weights and ESS
823 | self.log_w.fill(-math.log(self.num_particles))
824 | self._ess = float(self.num_particles)
825 | if particle_approximation is not None:
826 | if self.verbose > 0:
827 | print('- initializing with a particle approximation.')
828 | self._particles = particle_approximation.particles
829 | self._log_w = particle_approximation.log_w
830 | self._ess = self._get_ess_at(self.log_w)
831 | return
832 | else:
833 | self.particles[0] = self.mcmc_sampler.get_state()
834 | try:
835 | if self.verbose > 0:
836 | sys.stdout.write(
837 | '- initializing by sampling from the prior: ')
838 | if not gamma == 0.:
839 | raise AttributeError()
840 | for i in range(1, self.my_num_particles):
841 | self.mcmc_sampler.draw_from_prior()
842 | self.particles[i] = self.mcmc_sampler.get_state()
843 | if self.verbose > 0:
844 | sys.stdout.write('SUCCESS\n')
845 | except AttributeError:
846 | if self.verbose > 0:
847 | sys.stdout.write('FAILURE\n')
848 | print('- initializing via MCMC')
849 | if self.use_mpi:
850 | total_samples = (self.my_num_particles
851 | * num_mcmc_per_particle)
852 | print('- taking a total of', total_samples, 'samples per process')
853 | else:
854 | total_samples = (self.num_particles
855 | * num_mcmc_per_particle)
856 | print('- taking a total of', total_samples, 'samples')
857 | print('- creating a particle every', num_mcmc_per_particle)
858 | if self.verbose > 0:
859 | pb = pymc.progressbar.ProgressBar(self.num_particles *
860 | num_mcmc_per_particle)
861 | # Only rank 0 keeps the first particle
862 | if self.rank == 0:
863 | start_idx = 1
864 | else:
865 | start_idx = 0
866 | for i in range(start_idx, self.my_num_particles):
867 | self.mcmc_sampler.sample(num_mcmc_per_particle)
868 | self.particles[i] = self.mcmc_sampler.get_state()
869 | self._total_num_mcmc += num_mcmc_per_particle
870 | # TODO: Find bug in PyMC bar
871 | #if self.verbose > 0:
872 | # pb.update((i + 2) * self.size * num_mcmc_per_particle)
873 | if self.verbose > 0:
874 | print('')
875 | pa = self.get_particle_approximation().gather()
876 | sm_params = self.mcmc_sampler.get_params(comm=self.comm)
877 | if self.update_db and self.rank == 0:
878 | self.db.add(self.gamma, pa, sm_params)
879 | self.db.commit()
880 | if self.verbose > 0:
881 | print('----------------------')
882 | print('END SMC Initialization')
883 | print('----------------------')
884 |
885 |
886 | def move_to(self, gamma):
887 | """
888 | Move the current particle approximation to ``gamma``.
889 |
890 | :param gamma: The new ``gamma`` you wish to reach.
891 | :type gamma: float
892 |
893 | .. note::
894 |
895 | There must already be a valid particle approximation. See
896 | :meth:`pysmc.SMC.initialize()` for ways of doing this.
897 |
898 | """
899 | if self.verbose > 0:
900 | print('-----------------')
901 | print('START SMC MOVE TO')
902 | print('-----------------')
903 | print('initial ', self.gamma_name, ':', self.gamma)
904 | print('final', self.gamma_name, ':', gamma)
905 | print('ess reduction: ', self.ess_reduction)
906 | self.log_Zs = []
907 | while self.gamma < gamma:
908 | if self.adapt_proposal_step:
909 | self._tune()
910 | new_gamma = self._find_next_gamma(gamma)
911 | log_w = self._get_unormalized_weights_at(new_gamma)
912 | self.log_Z2_Z1 = self._logsumexp(log_w)
913 | self.log_Zs.append(self.log_Z2_Z1)
914 | self._log_w = self._normalize(log_w)
915 | self._ess = self._get_ess_at(self.log_w)
916 | self._set_gamma(new_gamma)
917 | if self.ess < self.ess_threshold * self.num_particles:
918 | if self.verbose > 0:
919 | print('- resampling')
920 | self._resample()
921 | if self.verbose > 0:
922 | print('- moving to', self.gamma_name, ':', self.gamma)
923 | pb = pymc.progressbar.progress_bar((self.num_particles-1) * self.num_mcmc)
924 | print('- performing', self.num_mcmc, 'MCMC step(s) per particle')
925 | print('- ESS = {0:3.2f} %'.format(self.ess / self.num_particles * 100))
926 | print('- logZ = {0:1.3e}'.format(self.log_Z2_Z1))
927 | for i in range(self.my_num_particles):
928 | self.mcmc_sampler.set_state(self.particles[i])
929 | self.mcmc_sampler.sample(self.num_mcmc)
930 | self.particles[i] = self.mcmc_sampler.get_state()
931 | self._total_num_mcmc += self.num_mcmc
932 | if self.verbose > 0:
933 | pb.update(i * self.size * self.num_mcmc)
934 | if self.verbose > 0:
935 | print('')
936 | if self.update_db:
937 | p = self.get_particle_approximation().gather()
938 | sm_params = self.mcmc_sampler.get_params(comm=self.comm)
939 | if self.rank == 0:
940 | self.db.add(self.gamma, p, sm_params)
941 | self.db.commit()
942 | for sm in self.mcmc_sampler.step_methods:
943 | acc_rate = sm.get_acceptance_rate(comm=self.comm)
944 | if self.verbose > 1:
945 | print('- acceptance rate for each step method:')
946 | print('\t-', str(sm), ':', acc_rate)
947 | total_num_mcmc = self.total_num_mcmc
948 | if self.verbose > 0:
949 | print('- total number of MCMC steps:', total_num_mcmc)
950 | print('---------------')
951 | print('END SMC MOVE TO')
952 | print('---------------')
953 |
954 | def get_particle_approximation(self):
955 | """
956 | Get a :class:`pysmc.ParticleApproximation` representing the current
957 | state of SMC.
958 |
959 | :returns: A particle approximation of the current state.
960 | :rtype: :class:`pysmc.ParticleApproximation`
961 | """
962 | return ParticleApproximation(log_w=self.log_w, particles=self.particles,
963 | mpi=self.mpi, comm=self.comm)
964 |
--------------------------------------------------------------------------------
/pysmc/_step_methods.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | .. _step_methods:
4 |
5 | ============
6 | Step Methods
7 | ============
8 |
9 | In PySMC we define a few step methods for the Metropolis-Hastings algorithm
10 | that extend the capabilities of PyMC.
11 |
12 | Here is a list of what we offer:
13 |
14 | """
15 |
16 |
17 | __all__ = ['RandomWalk', 'LognormalRandomWalk', 'DiscreteRandomWalk',
18 | 'GaussianMixtureStep']
19 |
20 |
21 | import pymc as pm
22 | import numpy as np
23 | from numpy.random import poisson as rpoisson
24 | from sklearn import mixture
25 |
26 |
27 | class RandomWalk(pm.Metropolis):
28 |
29 | _adapt_increase_factor = None
30 |
31 | _adapt_decrease_factor = None
32 |
33 | _adapt_upper_ac_rate = None
34 |
35 | _adapt_lower_ac_rate = None
36 |
37 | _min_adaptive_scale_factor = None
38 |
39 | _max_adaptive_scale_factor = None
40 |
41 | _adaptive_scale_factor = None
42 |
43 | _STATE_VARIABLES = None
44 |
45 | _accepted = None
46 |
47 | _rejected = None
48 |
49 | @property
50 | def STATE_VARIABLES(self):
51 | """
52 | Get the names of the variables required to store the state of the
53 | object.
54 | """
55 | return self._STATE_VARIABLES
56 |
57 | @property
58 | def adapt_increase_factor(self):
59 | return self._adapt_increase_factor
60 |
61 | @property
62 | def adapt_decrease_factor(self):
63 | return self._adapt_decrease_factor
64 |
65 | @property
66 | def adapt_upper_ac_rate(self):
67 | return self._adapt_upper_ac_rate
68 |
69 | @property
70 | def adapt_lower_ac_rate(self):
71 | return self._adapt_lower_ac_rate
72 |
73 | @property
74 | def min_adaptive_scale_factor(self):
75 | return self._min_adaptive_scale_factor
76 |
77 | @property
78 | def max_adaptive_scale_factor(self):
79 | return self._max_adaptive_scale_factor
80 |
81 | def __init__(self, stochastic,
82 | adapt_increase_factor=1.1,
83 | adapt_decrease_factor=0.7,
84 | adapt_upper_ac_rate=0.7,
85 | adapt_lower_ac_rate=0.3,
86 | min_adaptive_scale_factor=1e-32,
87 | max_adaptive_scale_factor=1e99,
88 | *args, **kwargs):
89 | """Initialize the object."""
90 | assert adapt_decrease_factor <= 1.
91 | self._adapt_decrease_factor = adapt_decrease_factor
92 | assert adapt_increase_factor >= 1.
93 | self._adapt_increase_factor = adapt_increase_factor
94 | assert adapt_upper_ac_rate <= 1.
95 | assert adapt_lower_ac_rate >= 0.
96 | assert adapt_lower_ac_rate <= adapt_upper_ac_rate
97 | self._adapt_upper_ac_rate = adapt_upper_ac_rate
98 | self._adapt_lower_ac_rate = adapt_lower_ac_rate
99 | assert min_adaptive_scale_factor > 0.
100 | assert min_adaptive_scale_factor <= max_adaptive_scale_factor
101 | self._min_adaptive_scale_factor = min_adaptive_scale_factor
102 | self._max_adaptive_scale_factor = max_adaptive_scale_factor
103 | pm.Metropolis.__init__(self, stochastic, *args, **kwargs)
104 | #super(RandomWalk, self).__init__(stochastic, *args, **kwargs)
105 | self._adaptive_scale_factor = self.adaptive_scale_factor
106 | self._STATE_VARIABLES = ['_adapt_increase_factor',
107 | '_adapt_decrease_factor',
108 | '_adapt_upper_ac_rate',
109 | '_adapt_lower_ac_rate',
110 | '_min_adaptive_scale_factor',
111 | '_max_adaptive_scale_factor',
112 | '_adaptive_scale_factor',
113 | 'adaptive_scale_factor',
114 | 'proposal_sd',
115 | '_old_accepted',
116 | '_old_rejected'
117 | ]
118 | self._old_accepted = 0.
119 | self._old_rejected = 0.
120 | self.proposal_sd = 1e-1
121 | self.adaptive_scale_factor = 1.
122 |
123 | def tune(self, pa=None, comm=None, divergence_threshold=1e10, verbose=0):
124 | ac = self.get_acceptance_rate(comm=comm)
125 | if ac == -1:
126 | return False
127 | self.reset_counters()
128 | use_mpi = comm is not None
129 | rank = comm.Get_rank() if use_mpi else 0
130 | if ac <= self.adapt_lower_ac_rate:
131 | self._adaptive_scale_factor = max(self._adaptive_scale_factor *
132 | self.adapt_decrease_factor,
133 | self.min_adaptive_scale_factor)
134 | elif ac >= self.adapt_upper_ac_rate:
135 | self._adaptive_scale_factor = min(self._adaptive_scale_factor *
136 | self.adapt_increase_factor,
137 | self.max_adaptive_scale_factor)
138 | self.adaptive_scale_factor = self._adaptive_scale_factor
139 | if verbose >= 2 and rank == 0:
140 | print(('\n\t\tadaptive_scale_factor:', self.adaptive_scale_factor))
141 | return True
142 |
143 | @staticmethod
144 | def competence(s):
145 | """
146 | Tell PyMC that this step method is better than its random walk.
147 | """
148 | return 3
149 |
150 | def get_params(self, comm=None):
151 | """
152 | Get the state of the step method.
153 | """
154 | state = {}
155 | for var in self.STATE_VARIABLES:
156 | state[var] = getattr(self, var)
157 | if comm is not None:
158 | state['_old_accepted'] = comm.allreduce(state['_old_accepted'])
159 | state['_old_rejected'] = comm.allreduce(state['_old_rejected'])
160 | size = comm.Get_size()
161 | state['_old_accepted'] /= size
162 | state['_old_rejected'] /= size
163 | return state
164 |
165 | def set_params(self, state):
166 | """
167 | Set the state from a dictionary.
168 | """
169 | for var in list(state.keys()):
170 | setattr(self, var, state[var])
171 | self._old_accepted *= -1.
172 | self._old_rejected *= -1.
173 |
174 | def reset_counters(self):
175 | """
176 | Reset the counters that count accepted and rejected steps.
177 | """
178 | self._old_accepted = self.accepted
179 | self._old_rejected = self.rejected
180 |
181 | def get_acceptance_rate(self, comm=None):
182 | """
183 | Get the acceptance rate of the step method sm.
184 | """
185 | accepted = self.accepted - self._old_accepted
186 | rejected = self.rejected - self._old_rejected
187 | if comm is not None:
188 | accepted = comm.allreduce(accepted)
189 | rejected = comm.allreduce(rejected)
190 | if (accepted + rejected) == 0.:
191 | return -1
192 | return accepted / (accepted + rejected)
193 |
194 |
195 | class LognormalRandomWalk(RandomWalk):
196 | """
197 | This is a step method class that is good for positive random variables.
198 | It is a essentially a random walk in the logarithmic scale.
199 |
200 | **Base class:** :class:`pm.Metropolis`
201 | """
202 |
203 | def __init__(self, stochastic,
204 | adapt_increase_factor=1.3,
205 | adapt_decrease_factor=0.7,
206 | adapt_upper_ac_rate=0.7,
207 | adapt_lower_ac_rate=0.3,
208 | min_adaptive_scale_factor=1e-32,
209 | max_adaptive_scale_factor=1e99,
210 | *args, **kwargs):
211 | """Initialize the object."""
212 | super(LognormalRandomWalk, self).__init__(
213 | stochastic,
214 | adapt_increase_factor=adapt_increase_factor,
215 | adapt_decrease_factor=adapt_decrease_factor,
216 | adapt_upper_ac_rate=adapt_upper_ac_rate,
217 | adapt_lower_ac_rate=adapt_lower_ac_rate,
218 | min_adaptive_scale_factor=min_adaptive_scale_factor,
219 | max_adaptive_scale_factor=max_adaptive_scale_factor,
220 | *args, **kwargs)
221 | @staticmethod
222 | def competence(s):
223 | """
224 | Tell PyMC that this step method is better than its random walk.
225 | """
226 | if isinstance(s, pm.Lognormal) or \
227 | isinstance(s, pm.Exponential):
228 | return 5
229 |
230 | def propose(self):
231 | """
232 | Propose a move.
233 | """
234 | tau = (self.adaptive_scale_factor * self.proposal_sd) ** 2
235 | self.stochastic.value = \
236 | pm.rlognormal(np.log(self.stochastic.value), tau)
237 |
238 | def hastings_factor(self):
239 | """
240 | Compute the hastings factor.
241 | """
242 | tau = (self.adaptive_scale_factor * self.proposal_sd) ** 2
243 | cur_val = self.stochastic.value
244 | last_val = self.stochastic.last_value
245 |
246 | lp_for = pm.lognormal_like(cur_val, mu=np.log(last_val), tau=tau)
247 | lp_bak = pm.lognormal_like(last_val, mu=np.log(cur_val), tau=tau)
248 |
249 | if self.verbose > 1:
250 | print((self._id + ': Hastings factor %f' % (lp_bak - lp_for)))
251 | return lp_bak - lp_for
252 |
253 |
254 | class GaussianMixtureStep(RandomWalk):
255 | """
256 | This is a test.
257 | """
258 |
259 | # A gaussian mixtures model
260 | _gmm = None
261 |
262 | # Covariance type
263 | _covariance_type = None
264 |
265 | # The valid covariance types
266 | _VALID_COVARIANCE_TYPES = None
267 |
268 | # Maximum number of components for the Gaussian mixture
269 | _n_components = None
270 |
271 | # The number of iterations we should do while training the
272 | # Gaussian mixture
273 | _n_iter = None
274 |
275 | # The mean when we do scale
276 | _mean = None
277 |
278 | # The std when we do scale
279 | _std = None
280 |
281 | @property
282 | def n_iter(self):
283 | """
284 | Get the number of Gaussian mixture training iterations.
285 | """
286 | return self._n_iter
287 |
288 | @property
289 | def n_components(self):
290 | """
291 | Get the maximum number of Gaussian mixture components.
292 | """
293 | return self._n_components
294 |
295 | @property
296 | def VALID_COVARIANCE_TYPES(self):
297 | """
298 | Get the valid covariance types.
299 | """
300 | return self._VALID_COVARIANCE_TYPES
301 |
302 | @property
303 | def covariance_type(self):
304 | """
305 | Get the covariance type.
306 | """
307 | return self._covariance_type
308 |
309 | @property
310 | def gmm(self):
311 | """
312 | Get the Gaussian process model.
313 | """
314 | return self._gmm
315 |
316 | def __init__(self, stochastic,
317 | adapt_upper_ac_rate=1.,
318 | adapt_lower_ac_rate=0.5,
319 | covariance_type='full',
320 | n_components=50,
321 | n_iter=1000,
322 | *args, **kwargs):
323 | """Initialize the object."""
324 | RandomWalk.__init__(self,
325 | stochastic,
326 | adapt_upper_ac_rate=adapt_upper_ac_rate,
327 | adapt_lower_ac_rate=adapt_lower_ac_rate,
328 | *args, **kwargs)
329 | self._VALID_COVARIANCE_TYPES = ['diag', 'full', 'spherical', 'tied']
330 | assert covariance_type in self.VALID_COVARIANCE_TYPES
331 | self._covariance_type = covariance_type
332 | n_components = int(n_components)
333 | assert n_components >= 1
334 | self._n_components = n_components
335 | n_iter = int(n_iter)
336 | assert n_iter >= 0
337 | self._n_iter = n_iter
338 | self._tuned = False
339 | self._STATE_VARIABLES += ['_covariance_type',
340 | '_n_components',
341 | '_n_iter',
342 | '_tuned',
343 | '_gmm',
344 | '_mean',
345 | '_std']
346 | self._accepted = 0.
347 | self._rejected = 0.
348 |
349 | def propose(self):
350 | """
351 | Propose a move.
352 | """
353 | if not self._tuned:
354 | return super(GaussianMixtureStep, self).propose()
355 | x = self.gmm.sample()
356 | self.stochastic.value = x.flatten('F')
357 |
358 | def hastings_factor(self):
359 | """
360 | Compute the hastings factor.
361 | """
362 |
363 | if not self._tuned:
364 | return super(GaussianMixtureStep, self).hastings_factor()
365 |
366 | cur_val = np.atleast_2d(self.stochastic.value)
367 | last_val = np.atleast_2d(self.stochastic.last_value)
368 |
369 | lp_for = self._gmm.score(cur_val)[0]
370 | lp_bak = self._gmm.score(last_val)[0]
371 |
372 | if self.verbose > 1:
373 | print((self._id + ': Hastings factor %f' % (lp_bak - lp_for)))
374 | return lp_bak - lp_for
375 |
376 | def tune(self, pa=None, comm=None, divergence_threshold=1e10, verbose=0):
377 | """
378 | Tune the step...
379 | """
380 | if pa is None:
381 | raise RuntimeError('This step method works only in pysmc.')
382 | ac = self.get_acceptance_rate(comm=comm)
383 | if ac == -1:
384 | return False
385 | self.reset_counters()
386 | if (self._tuned and
387 | ac >= self.adapt_lower_ac_rate and
388 | ac <= self.adapt_upper_ac_rate):
389 | return False
390 | use_mpi = comm is not None
391 | if use_mpi:
392 | rank = comm.Get_rank()
393 | size = comm.Get_size()
394 | else:
395 | rank = 0
396 | size = 1
397 | pa = pa.gather()
398 | # Only the root should run train the mixture
399 | if rank == 0:
400 | pa.resample()
401 | data = [pa.particles[i]['stochastics'][self.stochastic.__name__]
402 | for i in range(pa.num_particles)]
403 | data = np.array(data, dtype='float')
404 | if data.ndim == 1:
405 | data = np.atleast_2d(data).T
406 | self._gmm = mixture.DPGMM(n_components=self.n_components,
407 | covariance_type=self.covariance_type,
408 | n_iter=self.n_iter)
409 | self.gmm.fit(data)
410 | Y_ = self.gmm.predict(data)
411 | n_comp = 0
412 | for i in range(self.n_components):
413 | if np.any(Y_ == i):
414 | n_comp += 1
415 | self._gmm = mixture.GMM(n_components=n_comp,
416 | covariance_type=self.covariance_type,
417 | n_iter=self.n_iter)
418 | self.gmm.fit(data)
419 | Y_ = self.gmm.predict(data)
420 | if verbose >= 2:
421 | for i, (mean, covar) in enumerate(zip(
422 | self.gmm.means_, self.gmm._get_covars())):
423 | if not np.any(Y_ == i):
424 | continue
425 | print(('\n', mean, covar))
426 | if use_mpi:
427 | self._gmm = comm.bcast(self._gmm)
428 | self.gmm.covars_ = self.gmm._get_covars()
429 | self._tuned = True
430 | return True
431 |
432 |
433 | class DiscreteRandomWalk(RandomWalk):
434 | """
435 | This is a step method class that is good for discrete random variables.
436 |
437 | Good only for non-negative discrete random variables.
438 |
439 | **Base class:** :class:`pysmc.RandomWalk`
440 | """
441 |
442 | def __init__(self, stochastic,
443 | prop_dist='poisson',
444 | adapt_increase_factor=1.3,
445 | adapt_decrease_factor=0.7,
446 | adapt_upper_ac_rate=0.7,
447 | adapt_lower_ac_rate=0.3,
448 | min_adaptive_scale_factor=1e-32,
449 | max_adaptive_scale_factor=1e99,
450 | *args, **kwargs):
451 | """Initialize the object."""
452 | super(DiscreteRandomWalk, self).__init__(
453 | stochastic,
454 | adapt_increase_factor=adapt_increase_factor,
455 | adapt_decrease_factor=adapt_decrease_factor,
456 | adapt_upper_ac_rate=adapt_upper_ac_rate,
457 | adapt_lower_ac_rate=adapt_lower_ac_rate,
458 | min_adaptive_scale_factor=min_adaptive_scale_factor,
459 | max_adaptive_scale_factor=max_adaptive_scale_factor,
460 | *args, **kwargs)
461 | self.prop_dist = prop_dist
462 | self._STATE_VARIABLES.append('prop_dist')
463 |
464 | def propose(self):
465 | """
466 | Propose a move.
467 | """
468 | if self.prop_dist == 'poisson':
469 | k = self.stochastic.value.shape
470 | new_val = self.stochastic.value + rpoisson(
471 | self.adaptive_scale_factor * self.proposal_sd) * (
472 | -np.ones(k)) ** (np.random.random(k) > 0.5)
473 | self.stochastic.value = np.abs(new_val)
474 | elif self.prop_dist == 'prior':
475 | self.stochastic.random()
476 |
477 |
478 | # Assign methods to the registry of pymc
479 | pm.StepMethodRegistry = []
480 | for method in __all__:
481 | pm.StepMethodRegistry.append(eval(method))
482 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.md
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 |
4 | from numpy.distutils.core import setup
5 |
6 |
7 | setup(name='PySMC',
8 | description='Sequential Monte Carlo (SMC) for sampling complicated probability densities.',
9 | author='Ilias Bilionis',
10 | author_email='ibilion@purdue.edu',
11 | url='https://github.com/ebilionis/pysmc',
12 | download_url='https://github.com/ebilionis/pysmc/tarball/1.0',
13 | keywords=['sequential monte carlo', 'markov chain monte carlo', 'metropolis-hastings',
14 | 'multimodal probability densities', 'particle methods'],
15 | version='2.1',
16 | packages=['pysmc'])
17 |
--------------------------------------------------------------------------------