├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── bayesloop ├── __init__.py ├── core.py ├── exceptions.py ├── fileIO.py ├── helper.py ├── jeffreys.py ├── observationModels.py ├── parser.py ├── preprocessing.py └── transitionModels.py ├── docs ├── images │ ├── example.png │ ├── favicon.ico │ ├── html_logo.png │ ├── logo_400x100px.png │ └── logo_75px.png ├── make.bat └── source │ ├── api.rst │ ├── conf.py │ ├── examples.rst │ ├── examples │ ├── anomalousdiffusion.ipynb │ └── stockmarketfluctuations.ipynb │ ├── index.rst │ ├── installation.rst │ ├── requirements.txt │ ├── tutorials.rst │ └── tutorials │ ├── changepointstudy.ipynb │ ├── customobservationmodels.ipynb │ ├── firststeps.ipynb │ ├── hyperparameteroptimization.ipynb │ ├── hyperstudy.ipynb │ ├── modelselection.ipynb │ ├── multiprocessing.ipynb │ ├── onlinestudy.ipynb │ ├── priordistributions.ipynb │ └── probabilityparser.ipynb ├── setup.cfg ├── setup.py └── tests ├── test_changepointstudy.py ├── test_fileio.py ├── test_hyperstudy.py ├── test_observationmodels.py ├── test_onlinestudy.py ├── test_parser.py ├── test_plot.py ├── test_study.py └── test_transitionmodels.py /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: 9 | - cron: "0 8 * * 1" 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | max-parallel: 4 16 | matrix: 17 | python-version: [ 3.8 ] 18 | 19 | steps: 20 | - name: checkout 21 | uses: actions/checkout@v2 22 | 23 | - name: set up python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v1 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | 28 | - name: install dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | pip install pytest 32 | pip install pytest-cov 33 | pip install . 34 | 35 | - name: run tests 36 | run: | 37 | cd tests 38 | pytest --cov=bayesloop . --cov-report=xml 39 | 40 | - name: upload coverage 41 | uses: codecov/codecov-action@v1 42 | with: 43 | token: ${{ secrets.CODECOV_TOKEN }} 44 | file: ./tests/coverage.xml 45 | flags: unittests 46 | name: codecov-umbrella 47 | fail_ci_if_error: true 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | tests/*.bl 46 | 47 | # Translations 48 | *.mo 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # PyBuilder 58 | target/ 59 | 60 | # IDE stuff 61 | .idea/ 62 | .vscode 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Christoph Mark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![bayesloop](https://raw.githubusercontent.com/christophmark/bayesloop/master/docs/images/logo_400x100px.png)](http://bayesloop.com) 2 | 3 | [![Build status](https://github.com/christophmark/bayesloop/workflows/Tests/badge.svg?branch=master)](https://github.com/christophmark/bayesloop/actions/workflows/test.yml) 4 | [![Documentation status](https://readthedocs.org/projects/bayesloop/badge/?version=latest)](http://docs.bayesloop.com) 5 | [![Coverage Status](https://codecov.io/gh/christophmark/bayesloop/branch/master/graph/badge.svg?token=637W4M2RCE)](https://codecov.io/gh/christophmark/bayesloop) 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 7 | [![DOI](https://zenodo.org/badge/41474112.svg)](https://zenodo.org/badge/latestdoi/41474112) 8 | 9 | Time series analysis today is an important cornerstone of quantitative science in many disciplines, including natural and life sciences as well as economics and social sciences. Regarding diverse phenomena like tumor cell migration, brain activity and stock trading, a similarity of these complex systems becomes apparent: the observable data we measure – cell migration paths, neuron spike rates and stock prices – are the result of a multitude of underlying processes that act over a broad range of spatial and temporal scales. It is thus to expect that the statistical properties of these systems are not constant, but themselves show stochastic or deterministic dynamics of their own. Time series models used to understand the dynamics of complex systems therefore have to account for temporal changes of the models' parameters. 10 | 11 | *bayesloop* is a python module that focuses on fitting time series models with time-varying parameters and model selection based on [Bayesian inference](https://cocosci.berkeley.edu/tom/papers/tutorial.pdf). Instead of relying on [MCMC methods](http://www.cs.ubc.ca/~arnaud/andrieu_defreitas_doucet_jordan_intromontecarlomachinelearning.pdf), *bayesloop* uses a grid-based approach to evaluate probability distributions, allowing for an efficient approximation of the [marginal likelihood (evidence)](http://alumni.media.mit.edu/~tpminka/statlearn/demo/). The marginal likelihood represents a powerful tool to objectively compare different models and/or optimize the hyper-parameters of hierarchical models. To avoid the [curse of dimensionality](https://en.wikipedia.org/wiki/Curse_of_dimensionality) when analyzing time series models with time-varying parameters, *bayesloop* employs a sequential inference algorithm that is based on the [forward-backward-algorithm](https://en.wikipedia.org/wiki/Forward%E2%80%93backward_algorithm) used in [Hidden Markov models](http://www.cs.sjsu.edu/~stamp/RUA/HMM.pdf). Here, the relevant parameter spaces are kept low-dimensional by processing time series data step by step. The module covers a large class of time series models and is easily extensible. 12 | 13 | *bayesloop* has been successfully employed in cancer research (studying the migration paths of invasive tumor cells), financial risk assessment, climate research and accident analysis. For a detailed description of these applications, see the following articles: 14 | 15 | **Bayesian model selection for complex dynamic systems**
16 | Mark C., Metzner C., Lautscham L., Strissel P.L., Strick R. and Fabry B.
17 | [*Nature Communications 9:1803 (2018)*](https://www.nature.com/articles/s41467-018-04241-5) 18 | 19 | **Superstatistical analysis and modelling of heterogeneous random walks**
20 | Metzner C., Mark C., Steinwachs J., Lautscham L., Stadler F. and Fabry B.
21 | [*Nature Communications 6:7516 (2015)*](https://www.nature.com/articles/ncomms8516) 22 | 23 | ## Features 24 | * infer time-varying parameters from time series data 25 | * compare hypotheses about parameter dynamics (model evidence) 26 | * create custom models based on SymPy and SciPy 27 | * straight-forward handling of missing data points 28 | * predict future parameter values 29 | * detect change-points and structural breaks in time series data 30 | * employ model selection to online data streams 31 | 32 | ## Getting started 33 | For a comprehensive introduction and overview of the main features that *bayesloop* provides, see the [documentation](http://docs.bayesloop.com). 34 | 35 | The following code provides a minimal example of an analysis carried out using *bayesloop*. The data here consists of the number of coal mining disasters in the UK per year from 1851 to 1962 (see this [article](http://www.dima.unige.it/~riccomag/Teaching/ProcessiStocastici/coal-mining-disaster-original%20paper.pdf) for further information). 36 | ```python 37 | import bayesloop as bl 38 | import matplotlib.pyplot as plt 39 | import seaborn as sns 40 | 41 | S = bl.HyperStudy() # start new data study 42 | S.loadExampleData() # load data array 43 | 44 | # observed number of disasters is modeled by Poisson distribution 45 | L = bl.om.Poisson('rate') 46 | 47 | # disaster rate itself may change gradually over time 48 | T = bl.tm.GaussianRandomWalk('sigma', bl.cint(0, 1.0, 20), target='rate') 49 | 50 | S.set(L, T) 51 | S.fit() # inference 52 | 53 | # plot data together with inferred parameter evolution 54 | plt.figure(figsize=(8, 3)) 55 | 56 | plt.subplot2grid((1, 3), (0, 0), colspan=2) 57 | plt.xlim([1852, 1961]) 58 | plt.bar(S.rawTimestamps, S.rawData, align='center', facecolor='r', alpha=.5) 59 | S.plot('rate') 60 | plt.xlabel('year') 61 | 62 | # plot hyper-parameter distribution 63 | plt.subplot2grid((1, 3), (0, 2)) 64 | plt.xlim([0, 1]) 65 | S.plot('sigma', facecolor='g', alpha=0.7, lw=1, edgecolor='k') 66 | plt.tight_layout() 67 | plt.show() 68 | ``` 69 | 70 | ![Analysis plot](https://raw.githubusercontent.com/christophmark/bayesloop/master/docs/images/example.png) 71 | 72 | This analysis indicates a significant improvement of safety conditions between 1880 and 1900. Check out the [documentation](http://docs.bayesloop.com) for further insights! 73 | 74 | ## Installation 75 | The easiest way to install the latest release version of *bayesloop* is via `pip`: 76 | ``` 77 | pip install bayesloop 78 | ``` 79 | Alternatively, a zipped version can be downloaded [here](https://github.com/christophmark/bayesloop/releases). The module is installed by calling `python setup.py install`. 80 | 81 | ### Development version 82 | The latest development version of *bayesloop* can be installed from the master branch using pip (requires git): 83 | ``` 84 | pip install git+https://github.com/christophmark/bayesloop 85 | ``` 86 | Alternatively, use this [zipped version](https://github.com/christophmark/bayesloop/zipball/master) or clone the repository. 87 | 88 | ## Dependencies 89 | *bayesloop* is tested on Python 3.8. It depends on NumPy, SciPy, SymPy, matplotlib, tqdm and cloudpickle. All except the last two are already included in the [Anaconda distribution](https://www.continuum.io/downloads) of Python. Windows users may also take advantage of pre-compiled binaries for all dependencies, which can be found at [Christoph Gohlke's page](http://www.lfd.uci.edu/~gohlke/pythonlibs/). 90 | 91 | ## Optional dependencies 92 | *bayesloop* supports multiprocessing for computationally expensive analyses, based on the [pathos](https://github.com/uqfoundation/pathos) module. The latest version can be obtained directly from GitHub using pip (requires git): 93 | ``` 94 | pip install git+https://github.com/uqfoundation/pathos 95 | ``` 96 | **Note**: Windows users need to install a C compiler *before* installing pathos. One possible solution for 64bit systems is to install [Microsoft Visual C++ 2008 SP1 Redistributable Package (x64)](http://www.microsoft.com/en-us/download/confirmation.aspx?id=2092) and [Microsoft Visual C++ Compiler for Python 2.7](http://www.microsoft.com/en-us/download/details.aspx?id=44266). 97 | 98 | ## License 99 | [The MIT License (MIT)](https://github.com/christophmark/bayesloop/blob/master/LICENSE) 100 | 101 | If you have any further questions, suggestions or comments, do not hesitate to contact me: bayesloop@gmail.com 102 | -------------------------------------------------------------------------------- /bayesloop/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # import study types 4 | from .core import Study, HyperStudy, ChangepointStudy, OnlineStudy 5 | 6 | # observation models and transition models need to be distinguishable 7 | from . import observationModels 8 | from . import observationModels as om # short form 9 | from . import transitionModels 10 | from . import transitionModels as tm # short form 11 | 12 | # probability parser 13 | from .parser import Parser 14 | 15 | # misc 16 | from .helper import cint, oint 17 | from .jeffreys import getJeffreysPrior, computeJeffreysPriorAR1 18 | from .fileIO import save, load 19 | -------------------------------------------------------------------------------- /bayesloop/exceptions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | This file defines custom exceptions for bayesloop. 4 | """ 5 | 6 | 7 | class ConfigurationError(Exception): 8 | """ 9 | Raised if some part of the configuration of a study instance is not consistent, e.g. non-existent parameter names 10 | are set to be optimized or the shape of a custom prior distribution does not fit the grid size. 11 | """ 12 | 13 | 14 | class PostProcessingError(Exception): 15 | """ 16 | Raised if function for post-processing fails, e.g. plotParameterEvolution() or getHyperParameterDistribution(). 17 | """ 18 | -------------------------------------------------------------------------------- /bayesloop/fileIO.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | The following functions save or load instances of all `Study` types using the Python package `dill`. 4 | """ 5 | 6 | from __future__ import division, print_function 7 | import cloudpickle 8 | 9 | 10 | def save(filename, study): 11 | """ 12 | Save an instance of a bayesloop study class to file. 13 | 14 | Args: 15 | filename(str): Path + filename to store bayesloop study 16 | study: Instance of study class (Study, HyperStudy, etc.) 17 | """ 18 | with open(filename, 'wb') as f: 19 | cloudpickle.dump(study, f) 20 | print('+ Successfully saved current study.') 21 | 22 | 23 | def load(filename): 24 | """ 25 | Load an instance of a bayesloop study class that was saved using the bayesloop.save() function. 26 | 27 | Args: 28 | filename(str): Path + filename to stored bayesloop study 29 | 30 | Returns: 31 | Study instance 32 | """ 33 | with open(filename, 'rb') as f: 34 | S = cloudpickle.load(f) 35 | print('+ Successfully loaded study.') 36 | 37 | return S 38 | -------------------------------------------------------------------------------- /bayesloop/helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | This file includes basic helper functions. 4 | """ 5 | 6 | from __future__ import division, print_function 7 | import numpy as np 8 | import matplotlib.colors as colors 9 | 10 | 11 | def assignNestedItem(lst, index, value): 12 | """ 13 | Assign a value to an item of an arbitrarily nested list. The list is manipulated inplace. 14 | 15 | Args: 16 | lst(list): nested list to assign value to 17 | index(list): list of indices defining the position of the item to set 18 | value: value to assign to list item 19 | """ 20 | x = lst 21 | for i in index[:-1]: 22 | x = x[i] 23 | x[index[-1]] = value 24 | 25 | 26 | def recursiveIndex(nestedList, query): 27 | """ 28 | Find index of element (first occurrence) in an arbitrarily nested list. 29 | 30 | Args: 31 | nestedList(list): list object to search in 32 | query: target element to find 33 | 34 | Returns: 35 | list: Position indices 36 | """ 37 | for index, element in enumerate(nestedList): 38 | if isinstance(element, (list, tuple)): 39 | path = recursiveIndex(element, query) 40 | if path: 41 | return [index] + path 42 | if element == query: 43 | return [index] 44 | return [] 45 | 46 | 47 | def flatten(lst): 48 | """ 49 | Flatten arbitrarily nested list. Returns a generator object. 50 | 51 | Args: 52 | lst(list): list to flatten 53 | 54 | Returns: 55 | Generator object for flattened list (simply call list(flatten(lst)) to get the result as a list). 56 | """ 57 | for i in lst: 58 | if isinstance(i, (list, tuple)): 59 | for j in flatten(i): 60 | yield j 61 | else: 62 | yield i 63 | 64 | 65 | def createColormap(color, min_factor=1.0, max_factor=0.95): 66 | """ 67 | Creates colormap with range 0-1 from white to arbitrary color. 68 | 69 | Args: 70 | color: Matplotlib-readable color representation. Examples: 'g', '#00FFFF', '0.5', [0.1, 0.5, 0.9] 71 | min_factor(float): Float in the range 0-1, specifying the gray-scale color of the minimal plot value. 72 | max_factor(float): Float in the range 0-1, multiplication factor of 'color' argument for maximal plot value. 73 | 74 | Returns: 75 | Colormap object to be used by matplotlib-functions 76 | """ 77 | rgb = colors.colorConverter.to_rgb(color) 78 | cdict = {'red': [(0.0, min_factor, min_factor), 79 | (1.0, max_factor*rgb[0], max_factor*rgb[0])], 80 | 81 | 'green': [(0.0, min_factor, min_factor), 82 | (1.0, max_factor*rgb[1], max_factor*rgb[1])], 83 | 84 | 'blue': [(0.0, min_factor, min_factor), 85 | (1.0, max_factor*rgb[2], max_factor*rgb[2])]} 86 | 87 | return colors.LinearSegmentedColormap('custom', cdict) 88 | 89 | 90 | def oint(start, stop, num): 91 | """ 92 | Returns evenly spaced numbers over a specified interval. The interval boundaries are NOT included, i.e. the interval 93 | is an open one. Mainly used for parameter values of the low-level (observation) model, to avoid singularities in the 94 | likelihood function. 95 | 96 | Args: 97 | start(scalar): Starting value of the sequence 98 | stop(scalar): End value of the sequence 99 | num(int): Number of evenly spaced points within the interval. 100 | 101 | Returns: 102 | ndarray: Array of evenly spaced numbers from the specified open interval. 103 | """ 104 | return np.linspace(start, stop, num+2)[1:-1] 105 | 106 | 107 | def cint(start, stop, num): 108 | """ 109 | Returns evenly spaced numbers over a specified interval. The interval boundaries are included, i.e. the interval is 110 | a closed one. Mainly used for hyper-parameter values of the high-level (transition) model. 111 | 112 | Args: 113 | start(scalar): Starting value of the sequence 114 | stop(scalar): End value of the sequence 115 | num(int): Number of evenly spaced points within the interval. 116 | 117 | Returns: 118 | ndarray: Array of evenly spaced numbers from the specified closed interval. 119 | """ 120 | return np.linspace(start, stop, num) 121 | 122 | def freeSymbols(rv): 123 | """ 124 | Extracts the free symbols/parameters of a probability distribution from a SymPy random variable, independent of the 125 | SymPy version. 126 | 127 | Note: In SymPy version <=1.0, the attribute "distribution" was found in rv._sorted_args[0].distribution, while 128 | as of version 1.1, it is found in rv._sorted_args[1].distribution. 129 | 130 | Args: 131 | rv: SymPy random variable 132 | 133 | Returns: 134 | Free symbols of a SymPy random variable 135 | """ 136 | 137 | try: 138 | symbols = rv._sorted_args[0].distribution.free_symbols 139 | except AttributeError: 140 | symbols = rv._sorted_args[1].distribution.free_symbols 141 | 142 | return list(symbols) 143 | -------------------------------------------------------------------------------- /bayesloop/jeffreys.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | This file provides a single function that uses SymPy to determine the Jeffreys prior of an arbitrary probability 4 | distribution defined within SymPy. 5 | """ 6 | 7 | from __future__ import division, print_function 8 | import numpy as np 9 | import sympy.abc as abc 10 | from sympy.stats import density 11 | from sympy import Symbol, Matrix, simplify, diff, integrate, summation, lambdify 12 | from sympy import ln, sqrt 13 | from .helper import freeSymbols 14 | from .exceptions import ConfigurationError, PostProcessingError 15 | 16 | 17 | def getJeffreysPrior(rv): 18 | """ 19 | Uses SymPy to determine the Jeffreys prior of a random variable analytically. 20 | 21 | Args: 22 | rv: SymPy RandomSymbol, corresponding to a probability distribution 23 | 24 | Returns: 25 | list: List containing Jeffreys prior in symbolic form and corresponding lambda function 26 | 27 | Example: 28 | rate = Symbol('rate', positive=True) 29 | rv = stats.Exponential('exponential', rate) 30 | print getJeffreysPrior(rv) 31 | 32 | >>> (1/rate, at 0x0000000007F79AC8>) 33 | """ 34 | 35 | # get support of random variable 36 | try: 37 | support = rv._sorted_args[0].distribution.set # SymPy version <=1.0 38 | except AttributeError: 39 | support = rv._sorted_args[1].distribution.set # SymPy version >=1.1 40 | 41 | # get list of free parameters 42 | parameters = freeSymbols(rv) 43 | x = abc.x 44 | 45 | # symbolic probability density function 46 | symPDF = density(rv)(x) 47 | 48 | # compute Fisher information matrix 49 | dim = len(parameters) 50 | G = Matrix.zeros(dim, dim) 51 | 52 | func = summation if support.is_iterable else integrate 53 | for i in range(0, dim): 54 | for j in range(0, dim): 55 | G[i, j] = func(simplify(symPDF * 56 | diff(ln(symPDF), parameters[i]) * 57 | diff(ln(symPDF), parameters[j])), 58 | (x, support.inf, support.sup)) 59 | 60 | # symbolic Jeffreys prior 61 | symJeff = simplify(sqrt(G.det())) 62 | 63 | # check if computed Jeffreys prior is equal to 0 (happens e.g. for Cauchy distribution) 64 | if symJeff == 0: 65 | raise Exception('Jeffreys prior could be computed correctly.') 66 | 67 | # return symbolic Jeffreys prior and corresponding lambda function 68 | return symJeff, lambdify(parameters, symJeff, 'numpy') 69 | 70 | 71 | def computeJeffreysPriorAR1(study, t=1): 72 | """ 73 | This function encodes the Jeffreys prior for the AR1 process as derived by Harald Uhlig in the work "On Jeffreys 74 | prior when using the exact likelihood function." (Econometric Theory 10 (1994): 633-633. Equation 31). Note that 75 | only the case of abs(r) < 1 (stationary process) is implemented at the moment. 76 | 77 | Args: 78 | study: Instance of the Study class that this prior is added to 79 | t(int): Time step that this prior is computed for (t=1 means that the data point at index 0 will be used to 80 | compute it) 81 | 82 | Returns: 83 | Array with prior probabilities. 84 | """ 85 | if str(study.observationModel) == 'Autoregressive process of first order (AR1)': 86 | r, s = study.grid 87 | elif str(study.observationModel) == 'Scaled autoregressive process of first order (AR1)': 88 | r, s = study.grid 89 | s = s*np.sqrt(1 - r**2.) 90 | else: 91 | raise ConfigurationError('Jeffreys prior for autoregressive process can only be used with AR1 and ScaledAR1 ' 92 | 'models.') 93 | 94 | # if abs(rho) >= 1., this prior cannot be used 95 | if np.any(np.abs(r) >= 1.): 96 | raise ConfigurationError('Jeffreys prior for auto-regressive process is only implemented for stationary ' 97 | 'processes. Values abs(r) >= 1 are not allowed for this implementation of the prior.') 98 | 99 | if len(study.rawData) == 0: 100 | raise ConfigurationError('Data must be loaded before computing the Jeffreys prior for the autoregressive ' 101 | 'process.') 102 | 103 | d0 = study.rawData[t-1] # first observation is accounted for in the prior 104 | n = len(study.rawData) # number of data points 105 | 106 | prior = (1/s**2.)*np.exp(-d0**2.*(1-r**2.)/(2*s**2.))*(4*(r**2.)/(1-r**2.)+2*(n+1))**.5 107 | prior /= np.sum(prior) 108 | return prior 109 | -------------------------------------------------------------------------------- /bayesloop/parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Since one might not only be interested in the individual (hyper-)parameters of a bayesloop study, but also in arbitrary 4 | arithmetic combinations of one or more (hyper-)parameters, a parser is needed to compute probability values or 5 | distributions for those derived parameters. 6 | """ 7 | 8 | from __future__ import print_function, division 9 | import pyparsing as pp 10 | import re 11 | import operator 12 | import numpy as np 13 | import scipy.special as sp 14 | from tqdm.auto import tqdm 15 | from .exceptions import ConfigurationError 16 | 17 | 18 | class Parameter(np.ndarray): 19 | """ 20 | Behaves like a Numpy array, but features additional attributes. This allows us to apply arithmetic operations to 21 | the grid of parameter values while keeping track of the corresponding probability grid and the parameter's origin. 22 | """ 23 | def __new__(cls, values, prob, name=None, time=None, study=None): 24 | obj = np.asarray(values).view(cls) 25 | obj.prob = prob 26 | obj.name = name 27 | obj.time = time 28 | obj.study = study 29 | return obj 30 | 31 | def __array_finalize__(self, obj): 32 | # see InfoArray.__array_finalize__ for comments 33 | if obj is None: 34 | return 35 | self.prob = getattr(obj, 'prob', None) 36 | self.name = getattr(obj, 'name', None) 37 | self.time = getattr(obj, 'time', None) 38 | self.study = getattr(obj, 'study', None) 39 | 40 | 41 | class HyperParameter(Parameter): 42 | """ 43 | Behaves like a Numpy array, but features additional attributes. This allows us to apply arithmetic operations to 44 | the grid of hyper-parameter values while keeping track of the corresponding probability grid and the 45 | hyper-parameter's origin. 46 | """ 47 | pass 48 | 49 | 50 | class Parser: 51 | """ 52 | Computes derived probability values and distributions based on arithmetic operations of (hyper-)parameters. 53 | 54 | Args: 55 | studies: One or more bayesloop study instances. All (hyper-)parameters in the specified study object(s) will be 56 | available to the parser. 57 | 58 | Example: 59 | :: 60 | S = bl.Study() 61 | ... 62 | P = bl.Parser(S) 63 | P('sqrt(rate@1910) > 1.') 64 | 65 | """ 66 | def __init__(self, *studies): 67 | # import all parameter names 68 | self.studies = studies 69 | if len(self.studies) == 0: 70 | raise ConfigurationError('Parser instance takes at least one Study instance as argument.') 71 | 72 | self.names = [] 73 | for study in studies: 74 | self.names.extend(study.observationModel.parameterNames) 75 | 76 | try: 77 | # OnlineStudy: loop over all transition models 78 | for names in study.hyperParameterNames: 79 | self.names.extend(names) 80 | except AttributeError: 81 | try: 82 | # Hyper/ChangepointStudy: only one transition model 83 | self.names.extend(study.flatHyperParameterNames) 84 | except AttributeError: 85 | pass 86 | 87 | if not len(np.unique(self.names)) == len(self.names): 88 | raise ConfigurationError('Specified study objects contain duplicate parameter names.') 89 | 90 | # define arithmetic operators 91 | self.arith = {'+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv, '^': operator.pow} 92 | 93 | # initialize symbols for parsing 94 | parameter = pp.oneOf(self.names) 95 | point = pp.Literal(".") 96 | e = pp.CaselessLiteral("E") 97 | fnumber = pp.Combine(pp.Word("+-" + pp.nums, pp.nums) + 98 | pp.Optional(point + pp.Optional(pp.Word(pp.nums))) + 99 | pp.Optional(e + pp.Word("+-" + pp.nums, pp.nums))) 100 | 101 | # initialize list of all numpy functions, remove functions that collide with (hyper-)parameter names 102 | self.functions = dir(np) + dir(sp) 103 | for name in self.names: 104 | try: 105 | self.functions.remove(name) 106 | print('! WARNING: Function "{}" will not be available in parser, as it collides with ' 107 | '(hyper-)parameter names.'.format(name)) 108 | except ValueError: 109 | pass 110 | 111 | # initialize operators for parsing 112 | funcop = pp.oneOf(self.functions) 113 | atop = pp.Literal('@') 114 | expop = pp.Literal('^') 115 | signop = pp.oneOf('+ -') 116 | multop = pp.oneOf('* /') 117 | plusop = pp.oneOf('+ -') 118 | 119 | # minimal symbol 120 | atom = (parameter | fnumber) 121 | 122 | # expression based on operator precedence 123 | self.expr = pp.infixNotation(atom, [(funcop, 1, pp.opAssoc.RIGHT), 124 | (atop, 2, pp.opAssoc.LEFT), 125 | (expop, 2, pp.opAssoc.RIGHT), 126 | (signop, 1, pp.opAssoc.RIGHT), 127 | (multop, 2, pp.opAssoc.LEFT), 128 | (plusop, 2, pp.opAssoc.LEFT)]) 129 | 130 | def _evaluate(self, parsedString): 131 | """ 132 | Recursive function to evaluate nested mathematical operations on (Hyper)Parameter instances. 133 | 134 | Args: 135 | parsedString(list): nested list generated from query by parser 136 | 137 | Returns: 138 | Derived Parameter instance 139 | """ 140 | # cases like "3*3*2" are split into "(3*3)*2" 141 | if len(parsedString) > 3: 142 | while len(parsedString) > 3: 143 | if parsedString[0] in self.functions: 144 | parsedString = [parsedString[:2]] + parsedString[2:] 145 | else: 146 | parsedString = [parsedString[:3]] + parsedString[3:] 147 | 148 | result = [] 149 | for e in parsedString: 150 | if isinstance(e, list): 151 | # unary minus: "-4" --> "(-1)*4" 152 | if len(e) == 2 and e[0] == '-': 153 | e = ['-1', '*', e[1]] 154 | 155 | # unary plus: "+4" --> "1*4" 156 | elif len(e) == 2 and e[0] == '+': 157 | e = ['1', '*', e[1]] 158 | 159 | # numpy function 160 | elif len(e) == 2 and isinstance(e[0], str): 161 | e = [e[0], 'func', e[1]] 162 | 163 | # recursion 164 | result.append(self._evaluate(e)) 165 | else: 166 | result.append(e) 167 | result = self._operation(result[1], result[0], result[2]) 168 | return result 169 | 170 | def _convert(self, string): 171 | """ 172 | Converts string in query to either a Parameter instance, a Numpy function, a scipy.special function or 173 | a float number. 174 | 175 | Args: 176 | string(str): string to convert 177 | 178 | Returns: 179 | Parameter instance, function or float 180 | """ 181 | if string in self.names: 182 | param = [p for p in self.parameters if p.name == string][0] 183 | return param.copy() 184 | elif isinstance(string, str) and (string in dir(np)) and callable(getattr(np, string)): 185 | return getattr(np, string) 186 | elif isinstance(string, str) and (string in dir(sp)) and callable(getattr(sp, string)): 187 | return getattr(sp, string) 188 | else: 189 | return float(string) 190 | 191 | def _operation(self, symbol, a, b): 192 | """ 193 | Handles arithmetic operations and selection of time steps for (hyper-)parameters. 194 | 195 | Args: 196 | symbol(str): operator symbol (one of '+-*/^@' or 'func') 197 | a: Parameter/HyperParameter instance, or number, or numpy function name 198 | b: Parameter/HyperParameter instance, or number 199 | 200 | Returns: 201 | Derived Parameter/HyperParameter instance, or number 202 | """ 203 | if isinstance(a, str): 204 | a = self._convert(a) 205 | if isinstance(b, str): 206 | b = self._convert(b) 207 | 208 | # time operation 209 | if symbol == '@': 210 | if (type(a) == Parameter or (type(a) == HyperParameter and len(a.prob.shape) == 2)) and \ 211 | not (type(b) == Parameter or type(b) == HyperParameter): 212 | timeIndex = list(a.study.formattedTimestamps).index(b) 213 | a.prob = a.prob[timeIndex] 214 | a.time = b 215 | return a 216 | 217 | # numpy function 218 | if symbol == 'func': 219 | return a(b) 220 | 221 | # arithmetic operation 222 | elif symbol in self.arith.keys(): 223 | # only perform arithmetic operations on parameters if timestamp is defined by "@" operator or 224 | # global time "t=..." 225 | if type(a) == Parameter and a.name != '_derived' and a.time is None: 226 | raise ConfigurationError('No timestamp defined for parameter "{}"'.format(a.name)) 227 | if type(b) == Parameter and b.name != '_derived' and b.time is None: 228 | raise ConfigurationError('No timestamp defined for parameter "{}"'.format(b.name)) 229 | 230 | # check if hyper-parameters from OnlineStudy instances have a defined time step 231 | if type(a) == HyperParameter and len(a.prob.shape) == 2 and a.time is None: 232 | raise ConfigurationError('No timestamp defined for hyper-parameter "{}"'.format(a.name)) 233 | if type(b) == HyperParameter and len(b.prob.shape) == 2 and b.time is None: 234 | raise ConfigurationError('No timestamp defined for hyper-parameter "{}"'.format(b.name)) 235 | 236 | # compute compound distribution of two (hyper-)parameters 237 | if (type(a) == Parameter and type(b) == Parameter and (not (a.study is b.study) or 238 | (a.study is None and b.study is None) or 239 | (a.name == b.name and not (a.time == b.time)))) or \ 240 | (type(a) == HyperParameter and type(b) == HyperParameter and (not (a.study is b.study) or 241 | (a.study is None and b.study is None))) or \ 242 | ((type(a) == HyperParameter) and (type(b) == Parameter) or 243 | (type(b) == HyperParameter) and (type(a) == Parameter)): 244 | 245 | valueTuples = np.array(np.meshgrid(a, b)).T.reshape(-1, 2) 246 | values = self.arith[symbol](valueTuples[:, 0], valueTuples[:, 1]) 247 | 248 | prob = np.prod(np.array(np.meshgrid(a.prob, b.prob)).T.reshape(-1, 2), axis=1) 249 | prob /= np.sum(prob) 250 | return Parameter(values, prob, name='_derived') # derived objects are always "parameters" 251 | 252 | # apply operator directly if compound distribution is not needed 253 | else: 254 | return self.arith[symbol](a, b) 255 | 256 | def __call__(self, query, t=None, silent=False): 257 | self.parameters = [] 258 | 259 | # load parameter values, probabilities 260 | if t is None: 261 | for study in self.studies: 262 | # check for OnlineStudy 263 | storeHistory = -1 264 | try: 265 | storeHistory = study.storeHistory 266 | except AttributeError: 267 | pass 268 | 269 | if storeHistory == -1 or storeHistory == 1: 270 | names = study.observationModel.parameterNames 271 | for i, name in enumerate(names): 272 | index = study.observationModel.parameterNames.index(name) 273 | self.parameters.append(Parameter(np.ravel(study.grid[index]), 274 | np.array([np.ravel(post) for post in study.posteriorSequence]), 275 | name=name, 276 | study=study)) 277 | else: 278 | names = study.observationModel.parameterNames 279 | for i, name in enumerate(names): 280 | index = study.observationModel.parameterNames.index(name) 281 | self.parameters.append(Parameter(np.ravel(study.grid[index]), 282 | np.ravel(study.marginalizedPosterior), 283 | name=name, 284 | time=study.formattedTimestamps[-1], 285 | study=study)) 286 | else: 287 | # compute index of timestamp 288 | timeIndex = list(self.studies[0].formattedTimestamps).index(t) 289 | 290 | for study in self.studies: 291 | names = study.observationModel.parameterNames 292 | for i, name in enumerate(names): 293 | index = study.observationModel.parameterNames.index(name) 294 | self.parameters.append(Parameter(np.ravel(study.grid[index]), 295 | np.ravel(study.posteriorSequence[timeIndex]), 296 | name=name, 297 | time=t, 298 | study=study)) 299 | 300 | # load hyper-parameter values, probabilities 301 | for study in self.studies: 302 | # check for OnlineStudy 303 | try: 304 | allNames = study.hyperParameterNames 305 | 306 | # loop over different transition models 307 | for j, names in enumerate(allNames): 308 | # loop over hyper-parameters in transition model 309 | for i, name in enumerate(names): 310 | index = study._getHyperParameterIndex(study.transitionModels[j], name) 311 | 312 | if t is None: 313 | if study.storeHistory: 314 | # extract sequence of only one hyper-parameter 315 | hps = [] 316 | for x in study.hyperParameterSequence: 317 | dist = x[j]/np.sum(x[j]) 318 | hps.append(dist) 319 | hps = np.array(hps) 320 | 321 | self.parameters.append(HyperParameter(study.hyperParameterValues[j][:, index], 322 | hps, 323 | name=name, 324 | study=study)) 325 | else: 326 | dist = study.hyperParameterDistribution[j]/np.sum(study.hyperParameterDistribution[j]) 327 | self.parameters.append(HyperParameter(study.hyperParameterValues[j][:, index], 328 | dist, 329 | name=name, 330 | time=study.formattedTimestamps[-1], 331 | study=study)) 332 | else: 333 | if study.storeHistory: 334 | # compute index of timestamp 335 | timeIndex = list(self.studies[0].formattedTimestamps).index(t) 336 | 337 | dist = study.hyperParameterSequence[timeIndex][j] / \ 338 | np.sum(study.hyperParameterSequence[timeIndex][j]) 339 | 340 | self.parameters.append(HyperParameter(study.hyperParameterValues[j][:, index], 341 | dist, 342 | name=name, 343 | time=t, 344 | study=study)) 345 | else: 346 | raise ConfigurationError('OnlineStudy instance is not configured to store history, ' 347 | 'cannot access t={}.'.format(t)) 348 | 349 | except AttributeError: 350 | # check for Hyper/ChangepointStudy, i.e. whether study type supports hyper-parameter inference 351 | try: 352 | names = study.flatHyperParameterNames 353 | 354 | for i, name in enumerate(names): 355 | index = study._getHyperParameterIndex(study.transitionModel, name) 356 | 357 | # probability values 358 | normedDist = study.hyperParameterDistribution / np.sum(study.hyperParameterDistribution) 359 | 360 | # hyper-parameter values 361 | try: 362 | values = study.allHyperGridValues # Changepoint-Study 363 | except AttributeError: 364 | values = study.hyperGridValues # Hyper-Study 365 | 366 | self.parameters.append(HyperParameter(values[:, index], 367 | normedDist, 368 | name=name, 369 | study=study)) 370 | except AttributeError: 371 | # do not try to access hyper-parameters of basic Study class 372 | continue 373 | 374 | # reduce equation 375 | splitQuery = re.split('>=|<=|==|>|<', query) 376 | if len(splitQuery) == 1: 377 | reducedQuery = query 378 | elif len(splitQuery) == 2: 379 | # last arithmetic may be omitted in some cases if right side is appended to the left, needs to come first 380 | #reducedQuery = '-'.join(splitQuery) 381 | reducedQuery = '-1*('+splitQuery[1]+')+'+splitQuery[0] 382 | else: 383 | raise ConfigurationError('Use exactly one operator out of (<, >, <=, >=, ==) to obtain probability value, ' 384 | 'or none to obtain derived distribution.') 385 | 386 | # evaluate left side 387 | parsedString = self.expr.parseString(reducedQuery).asList()[0] 388 | derivedParameter = self._evaluate(parsedString) 389 | 390 | # if no relational operator in query, compute derived distribution 391 | if len(splitQuery) == 1: 392 | derivedParameter[np.isinf(derivedParameter)] = np.nan 393 | dmin = np.nanmin(derivedParameter) 394 | dmax = np.nanmax(derivedParameter) 395 | 396 | # bin size is chosen as maximal difference between two derived values 397 | print(f"dmax {dmax}") 398 | print(f"dmin {dmin}") 399 | print(np.diff(np.sort(derivedParameter))) 400 | nBins = int((dmax-dmin)/(np.nanmax(np.diff(np.sort(derivedParameter))))) 401 | bins = np.linspace(dmin, dmax, nBins) 402 | binnedValues = bins[:-1] + (bins[1]-bins[0]) 403 | binnedProbs = [] 404 | 405 | if not silent: 406 | print('+ Computing distribution: {}'.format(query)) 407 | it = tqdm(zip(bins[:-1], bins[1:]), total=len(binnedValues)) 408 | else: 409 | it = zip(bins[:-1], bins[1:]) 410 | 411 | for lower, upper in it: 412 | binnedProbs.append(np.sum(derivedParameter.prob[(derivedParameter >= lower) * (derivedParameter < upper)])) 413 | binnedProbs = np.array(binnedProbs) 414 | 415 | return binnedValues, binnedProbs 416 | 417 | # if relational operator in query, compute probability value 418 | elif len(splitQuery) == 2: 419 | # assign operator 420 | if '>=' in query: 421 | op = operator.ge 422 | elif '>' in query: 423 | op = operator.gt 424 | elif '<=' in query: 425 | op = operator.le 426 | elif '<' in query: 427 | op = operator.lt 428 | elif '==' in query: 429 | op = operator.eq 430 | 431 | # compute probability 432 | mask = op(derivedParameter, 0.) 433 | p = np.sum(derivedParameter.prob[mask]) 434 | 435 | if not silent: 436 | print('P({}) = {}'.format(query, p)) 437 | return p 438 | 439 | else: 440 | raise ConfigurationError('More than one relational operator found in query.') 441 | -------------------------------------------------------------------------------- /bayesloop/preprocessing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | This file includes functions used for the preprocessing of measurement data that is to be analyzed using bayesloop. The 4 | time series data is formatted into data segments that can be fed to the inference algorithm piece-by-piece. 5 | 6 | Example: The auto-regressive process of first order needs two subsequent data points at each time step. Therefore, the 7 | data is separated into overlapping data segments containing two data points. 8 | """ 9 | 10 | from __future__ import division, print_function 11 | import numpy as np 12 | 13 | 14 | def movingWindow(rawData, n): 15 | """ 16 | Generates an array consisting of overlapping sub-sequences of raw data. 17 | 18 | Args: 19 | rawData(ndarray): Array containing time series data 20 | n(int): integer (> 0) stating the number of data points in each data segment that is passed to the algorithm 21 | 22 | Returns: 23 | ndarray: Array of data segments, each containing n overlapping data points 24 | """ 25 | data = np.array([rawData[i:i+n] for i in range(rawData.shape[0] - (n-1))]) 26 | return data 27 | -------------------------------------------------------------------------------- /bayesloop/transitionModels.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Transition models refer to stochastic or deterministic models that describe how the time-varying parameter values of a 4 | given time series model change from one time step to another. The transition model can thus be compared to the state 5 | transition matrix of Hidden Markov models. However, instead of explicitly stating transition probabilities for all 6 | possible states, a transformation is defined that alters the distribution of the model parameters in one time step 7 | according to the transition model. This altered distribution is subsequently used as a prior distribution in the next 8 | time step. 9 | """ 10 | 11 | from __future__ import division, print_function 12 | import numpy as np 13 | from scipy.signal import fftconvolve 14 | from scipy.signal import convolve2d 15 | from scipy.ndimage.filters import gaussian_filter1d 16 | from scipy.ndimage.interpolation import shift 17 | from scipy.stats import multivariate_normal 18 | from collections.abc import Iterable 19 | from copy import deepcopy 20 | from .exceptions import ConfigurationError, PostProcessingError 21 | 22 | try: 23 | from inspect import getargspec 24 | except ImportError: 25 | from inspect import getfullargspec as getargspec 26 | 27 | class TransitionModel: 28 | """ 29 | Parent class for transition models. All transition models inherit from this class. It is currently only used to 30 | identify transition models as such. 31 | """ 32 | 33 | 34 | class Static(TransitionModel): 35 | """ 36 | Constant parameters over time. This trivial model assumes no change of parameter values over time. 37 | """ 38 | def __init__(self): 39 | self.study = None 40 | self.latticeConstant = None 41 | self.hyperParameterNames = [] 42 | self.hyperParameterValues = [] 43 | self.prior = None 44 | self.tOffset = 0 # is set to the time of the last Breakpoint by SerialTransition model 45 | 46 | def __str__(self): 47 | return 'Static/constant parameter values' 48 | 49 | def computeForwardPrior(self, posterior, t): 50 | """ 51 | Compute new prior from old posterior (moving forwards in time). 52 | 53 | Args: 54 | posterior(ndarray): Parameter distribution from current time step 55 | t(int): integer time step 56 | 57 | Returns: 58 | ndarray: Prior parameter distribution for subsequent time step 59 | """ 60 | return posterior 61 | 62 | def computeBackwardPrior(self, posterior, t): 63 | return self.computeForwardPrior(posterior, t - 1) 64 | 65 | 66 | class GaussianRandomWalk(TransitionModel): 67 | """ 68 | Gaussian parameter fluctuations. This model assumes that parameter changes are Gaussian-distributed. The standard 69 | deviation can be set individually for each model parameter. 70 | 71 | Args: 72 | name(str): custom name of the hyper-parameter sigma 73 | value(float, list, tuple, ndarray): standard deviation(s) of the Gaussian random walk for target parameter 74 | target(str): parameter name of the observation model to apply transition model to 75 | prior: hyper-prior distribution that may be passed as a(lambda) function, as a SymPy random variable, or 76 | directly as a Numpy array with probability values for each hyper-parameter value 77 | """ 78 | def __init__(self, name='sigma', value=None, target=None, prior=None): 79 | if isinstance(value, (list, tuple)): # Online study expects Numpy array of values 80 | value = np.array(value) 81 | 82 | self.study = None 83 | self.latticeConstant = None 84 | self.hyperParameterNames = [name] 85 | self.hyperParameterValues = [value] 86 | self.prior = prior 87 | self.selectedParameter = target 88 | self.tOffset = 0 # is set to the time of the last Breakpoint by SerialTransition model 89 | 90 | if target is None: 91 | raise ConfigurationError('No parameter set for transition model "GaussianRandomWalk"') 92 | 93 | def __str__(self): 94 | return 'Gaussian random walk' 95 | 96 | def computeForwardPrior(self, posterior, t): 97 | """ 98 | Compute new prior from old posterior (moving forwards in time). 99 | 100 | Args: 101 | posterior(ndarray): Parameter distribution from current time step 102 | t(int): integer time step 103 | 104 | Returns: 105 | ndarray: Prior parameter distribution for subsequent time step 106 | """ 107 | axisToTransform = self.study.observationModel.parameterNames.index(self.selectedParameter) 108 | normedSigma = self.hyperParameterValues[0]/self.latticeConstant[axisToTransform] 109 | 110 | if normedSigma > 0.: 111 | newPrior = gaussian_filter1d(posterior, normedSigma, axis=axisToTransform) 112 | else: 113 | newPrior = posterior.copy() 114 | 115 | return newPrior 116 | 117 | def computeBackwardPrior(self, posterior, t): 118 | return self.computeForwardPrior(posterior, t - 1) 119 | 120 | 121 | class AlphaStableRandomWalk(TransitionModel): 122 | """ 123 | Parameter changes follow alpha-stable distribution. This model assumes that parameter changes are distributed 124 | according to the symmetric alpha-stable distribution. For each parameter, two hyper-parameters can be set: the 125 | width of the distribution (c) and the shape (alpha). 126 | 127 | Args: 128 | name1(str): custom name of the hyper-parameter c 129 | value1(float, list, tuple, ndarray): width(s) of the distribution (c >= 0). 130 | name2(str): custom name of the hyper-parameter alpha 131 | value2(float, list, tuple, ndarray): shape(s) of the distribution (0 < alpha <= 2). 132 | target(str): parameter name of the observation model to apply transition model to 133 | prior: list of two hyper-prior distributions, where each may be passed as a(lambda) function, as a SymPy random 134 | variable, or directly as a Numpy array with probability values for each hyper-parameter value 135 | """ 136 | def __init__(self, name1='c', value1=None, name2='alpha', value2=None, target=None, prior=(None, None)): 137 | if isinstance(value1, (list, tuple)): 138 | value1 = np.array(value1) 139 | if isinstance(value2, (list, tuple)): 140 | value2 = np.array(value2) 141 | 142 | self.study = None 143 | self.latticeConstant = None 144 | self.hyperParameterNames = [name1, name2] 145 | self.hyperParameterValues = [value1, value2] 146 | self.prior = prior 147 | self.selectedParameter = target 148 | self.kernel = None 149 | self.kernelParameters = None 150 | self.tOffset = 0 # is set to the time of the last Breakpoint by SerialTransition model 151 | 152 | if target is None: 153 | raise ConfigurationError('No parameter set for transition model "AlphaStableRandomWalk"') 154 | 155 | def __str__(self): 156 | return 'Alpha-stable random walk' 157 | 158 | def computeForwardPrior(self, posterior, t): 159 | """ 160 | Compute new prior from old posterior (moving forwards in time). 161 | 162 | Args: 163 | posterior(ndarray): Parameter distribution from current time step 164 | t(int): integer time step 165 | 166 | Returns: 167 | ndarray: Prior parameter distribution for subsequent time step 168 | """ 169 | 170 | # if hyper-parameter values have changed, a new convolution kernel needs to be created 171 | if not self.kernelParameters == self.hyperParameterValues: 172 | normedC = [] 173 | for lc in self.latticeConstant: 174 | normedC.append(self.hyperParameterValues[0] / lc) 175 | alpha = [self.hyperParameterValues[1]] * len(normedC) 176 | 177 | axisToTransform = self.study.observationModel.parameterNames.index(self.selectedParameter) 178 | selectedC = normedC[axisToTransform] 179 | normedC = [0.]*len(normedC) 180 | normedC[axisToTransform] = selectedC 181 | 182 | self.kernel = self.createKernel(normedC[0], alpha[0], 0) 183 | for i, (a, c) in enumerate(zip(alpha[1:], normedC[1:])): 184 | self.kernel *= self.createKernel(c, a, i+1) 185 | 186 | self.kernel = self.kernel.T 187 | self.kernelParameters = deepcopy(self.hyperParameterValues) 188 | 189 | newPrior = self.convolve(posterior) 190 | newPrior /= np.sum(newPrior) 191 | return newPrior 192 | 193 | def computeBackwardPrior(self, posterior, t): 194 | return self.computeForwardPrior(posterior, t - 1) 195 | 196 | def createKernel(self, c, alpha, axis): 197 | """ 198 | Create alpha-stable distribution on a grid as a kernel for convolution. 199 | 200 | Args: 201 | c(float): Scale parameter. 202 | alpha(float): Tail parameter (alpha = 1: Cauchy, alpha = 2: Gauss) 203 | axis(int): Axis along which the distribution is defined, for 2D-Kernels 204 | 205 | Returns: 206 | ndarray: kernel 207 | """ 208 | gs = self.study.gridSize 209 | if len(gs) == 2: 210 | if axis == 1: 211 | l1 = gs[1] 212 | l2 = gs[0] 213 | elif axis == 0: 214 | l1 = gs[0] 215 | l2 = gs[1] 216 | else: 217 | raise ConfigurationError('Transformation axis must either be 0 or 1.') 218 | elif len(gs) == 1: 219 | l1 = gs[0] 220 | l2 = 0 221 | axis = 0 222 | else: 223 | raise ConfigurationError('Parameter grid must either be 1- or 2-dimensional.') 224 | 225 | kernel_fft = np.exp(-np.abs(c*np.linspace(0, np.pi, int(3*l1/2+1)))**alpha) 226 | kernel = np.fft.irfft(kernel_fft) 227 | kernel = np.roll(kernel, int(3*l1/2-1)) 228 | 229 | if len(gs) == 2: 230 | kernel = np.array([kernel]*(3*l2)) 231 | 232 | if axis == 1: 233 | return kernel.T 234 | elif axis == 0: 235 | return kernel 236 | 237 | def convolve(self, distribution): 238 | """ 239 | Convolves distribution with alpha-stable kernel. 240 | 241 | Args: 242 | distribution(ndarray): Discrete probability distribution to convolve. 243 | 244 | Returns: 245 | ndarray: convolution 246 | """ 247 | gs = np.array(self.study.gridSize) 248 | padded_distribution = np.zeros(3*np.array(gs)) 249 | if len(gs) == 2: 250 | padded_distribution[gs[0]:2*gs[0], gs[1]:2*gs[1]] = distribution 251 | elif len(gs) == 1: 252 | padded_distribution[gs[0]:2*gs[0]] = distribution 253 | 254 | padded_convolution = fftconvolve(padded_distribution, self.kernel, mode='same') 255 | if len(gs) == 2: 256 | convolution = padded_convolution[gs[0]:2*gs[0], gs[1]:2*gs[1]] 257 | elif len(gs) == 1: 258 | convolution = padded_convolution[gs[0]:2*gs[0]] 259 | 260 | return convolution 261 | 262 | 263 | class ChangePoint(TransitionModel): 264 | """ 265 | Abrupt parameter change at a specified time step. Parameter values are allowed to change only at a single point in 266 | time, right after a specified time step (Hyper-parameter tChange). Note that a uniform parameter distribution is 267 | used at this time step to achieve this "reset" of parameter values. 268 | 269 | Args: 270 | name(str): custom name of the hyper-parameter tChange 271 | value(int, list, tuple, ndarray): Integer value(s) of the time step of the change point 272 | prior: hyper-prior distribution that may be passed as a(lambda) function, as a SymPy random variable, or 273 | directly as a Numpy array with probability values for each hyper-parameter value 274 | """ 275 | def __init__(self, name='tChange', value=None, prior=None): 276 | if isinstance(value, (list, tuple)): 277 | value = np.array(value) 278 | 279 | self.study = None 280 | self.latticeConstant = None 281 | self.hyperParameterNames = [name] 282 | self.hyperParameterValues = [value] 283 | self.prior = prior 284 | self.tOffset = 0 # is set to the time of the last Breakpoint by SerialTransition model 285 | 286 | def __str__(self): 287 | return 'Change-point' 288 | 289 | def computeForwardPrior(self, posterior, t): 290 | """ 291 | Compute new prior from old posterior (moving forwards in time). 292 | 293 | Args: 294 | posterior(ndarray): Parameter distribution from current time step 295 | t(int): integer time step 296 | 297 | Returns: 298 | ndarray: Prior parameter distribution for subsequent time step 299 | """ 300 | if t == self.hyperParameterValues[0]: 301 | # check if custom prior is used by observation model 302 | if hasattr(self.study.observationModel.prior, '__call__'): 303 | prior = self.study.observationModel.prior(*self.study.grid) 304 | elif isinstance(self.study.observationModel.prior, np.ndarray): 305 | prior = deepcopy(self.study.observationModel.prior) 306 | else: 307 | prior = np.ones(self.study.gridSize) # flat prior 308 | 309 | # normalize prior (necessary in case an improper prior is used) 310 | prior /= np.sum(prior) 311 | prior *= np.prod(self.study.latticeConstant) 312 | return prior 313 | else: 314 | return posterior 315 | 316 | def computeBackwardPrior(self, posterior, t): 317 | return self.computeForwardPrior(posterior, t - 1) 318 | 319 | 320 | class Independent(TransitionModel): 321 | """ 322 | Observations are treated as independent. This transition model restores the prior distribution for the parameters 323 | at each time step, effectively assuming independent observations. 324 | 325 | Note: 326 | Mostly used with an instance of OnlineStudy. 327 | """ 328 | def __init__(self): 329 | self.study = None 330 | self.latticeConstant = None 331 | self.hyperParameterNames = [] 332 | self.hyperParameterValues = [] 333 | self.prior = None 334 | self.tOffset = 0 # is set to the time of the last Breakpoint by SerialTransition model 335 | 336 | def __str__(self): 337 | return 'Independent observations model' 338 | 339 | def computeForwardPrior(self, posterior, t): 340 | """ 341 | Compute new prior from old posterior (moving forwards in time). 342 | 343 | Args: 344 | posterior(ndarray): Parameter distribution from current time step 345 | t(int): integer time step 346 | 347 | Returns: 348 | ndarray: Prior parameter distribution for subsequent time step 349 | """ 350 | # check if custom prior is used by observation model 351 | if hasattr(self.study.observationModel.prior, '__call__'): 352 | prior = self.study.observationModel.prior(*self.study.grid) 353 | elif isinstance(self.study.observationModel.prior, np.ndarray): 354 | prior = deepcopy(self.study.observationModel.prior) 355 | else: 356 | prior = np.ones(self.study.gridSize) # flat prior 357 | 358 | # normalize prior (necessary in case an improper prior is used) 359 | prior /= np.sum(prior) 360 | return prior 361 | 362 | def computeBackwardPrior(self, posterior, t): 363 | return self.computeForwardPrior(posterior, t - 1) 364 | 365 | 366 | class RegimeSwitch(TransitionModel): 367 | """ 368 | Small probability for a parameter jump in each time step. In case the number of change-points in a given data set 369 | is unknown, the regime-switching model may help to identify potential abrupt changes in parameter values. At each 370 | time step, all parameter values within the set boundaries are assigned a minimal probability density of being 371 | realized in the next time step, effectively allowing abrupt parameter changes at every time step. 372 | 373 | Args: 374 | name(str): custom name of the hyper-parameter log10pMin 375 | value(float, list, tuple, ndarray): Minimal probability density (log10 value) that is assigned to every 376 | parameter value 377 | prior: hyper-prior distribution that may be passed as a(lambda) function, as a SymPy random variable, or 378 | directly as a Numpy array with probability values for each hyper-parameter value 379 | """ 380 | def __init__(self, name='log10pMin', value=None, prior=None): 381 | if isinstance(value, (list, tuple)): 382 | value = np.array(value) 383 | 384 | self.study = None 385 | self.latticeConstant = None 386 | self.hyperParameterNames = [name] 387 | self.hyperParameterValues = [value] 388 | self.prior = prior 389 | self.tOffset = 0 # is set to the time of the last Breakpoint by SerialTransition model 390 | 391 | def __str__(self): 392 | return 'Regime-switching model' 393 | 394 | def computeForwardPrior(self, posterior, t): 395 | """ 396 | Compute new prior from old posterior (moving forwards in time). 397 | 398 | Parameters: 399 | posterior(ndarray): Parameter distribution from current time step 400 | t(int): integer time step 401 | 402 | Returns: 403 | ndarray: Prior parameter distribution for subsequent time step 404 | """ 405 | newPrior = posterior.copy() 406 | limit = (10.**self.hyperParameterValues[0])*np.prod(self.latticeConstant) # convert prob. density to prob. 407 | newPrior[newPrior < limit] = limit 408 | 409 | # transformation above violates proper normalization; re-normalization needed 410 | newPrior /= np.sum(newPrior) 411 | 412 | return newPrior 413 | 414 | def computeBackwardPrior(self, posterior, t): 415 | return self.computeForwardPrior(posterior, t - 1) 416 | 417 | 418 | class NotEqual(TransitionModel): 419 | """ 420 | Unlikely parameter values are preferred in the next time step. Assumes an "inverse" parameter distribution at each 421 | new time step. The new prior is derived by substracting the posterior probability values from their maximal value 422 | and subsequently re-normalizing. To assure that no parameter value is set to zero probability, one may specify a 423 | minimal probability for all parameter values. This transition model is mostly used in instances of OnlineStudy to 424 | detect time step when parameter distributions change significantly. 425 | 426 | Args: 427 | name(str): custom name of the hyper-parameter log10pMin 428 | value(float, list, tuple, ndarray): Log10-value of the minimal probability that is set to all possible 429 | parameter values of the inverted parameter distribution 430 | prior: hyper-prior distribution that may be passed as a(lambda) function, as a SymPy random variable, or 431 | directly as a Numpy array with probability values for each hyper-parameter value 432 | 433 | Note: 434 | Mostly used with an instance of OnlineStudy. 435 | """ 436 | def __init__(self, name='log10pMin', value=None, prior=None): 437 | if isinstance(value, (list, tuple)): 438 | value = np.array(value) 439 | 440 | self.study = None 441 | self.latticeConstant = None 442 | self.hyperParameterNames = [name] 443 | self.hyperParameterValues = [value] 444 | self.prior = prior 445 | self.tOffset = 0 # is set to the time of the last Breakpoint by SerialTransition model 446 | 447 | def __str__(self): 448 | return 'Not-Equal model' 449 | 450 | def computeForwardPrior(self, posterior, t): 451 | """ 452 | Compute new prior from old posterior (moving forwards in time). 453 | 454 | Parameters: 455 | posterior(ndarray): Parameter distribution from current time step 456 | t(int): integer time step 457 | 458 | Returns: 459 | ndarray: Prior parameter distribution for subsequent time step 460 | """ 461 | newPrior = posterior.copy() 462 | limit = (10**self.hyperParameterValues[0])*np.prod(self.latticeConstant) # convert prob. density to prob. 463 | 464 | newPrior = np.amax(newPrior) - newPrior 465 | newPrior /= np.sum(newPrior) 466 | newPrior[newPrior < limit] = limit 467 | 468 | # transformation above violates proper normalization; re-normalization needed 469 | newPrior /= np.sum(newPrior) 470 | 471 | return newPrior 472 | 473 | def computeBackwardPrior(self, posterior, t): 474 | return self.computeForwardPrior(posterior, t - 1) 475 | 476 | 477 | class Deterministic(TransitionModel): 478 | """ 479 | Deterministic parameter variations. Given a function with time as the first argument and further keyword-arguments 480 | as hyper-parameters, plus the name of a parameter of the observation model that is supposed to follow this function 481 | over time, this transition model shifts the parameter distribution accordingly. Note that these models are entirely 482 | deterministic, as the hyper-parameter values are entered by the user. However, the hyper-parameter distributions can 483 | be inferred using a Hyper-study or can be optimized using the 'optimize' method of the Study class. 484 | 485 | Args: 486 | function(function): A function that takes the time as its first argument and further takes keyword-arguments 487 | that correspond to the hyper-parameters of the transition model which the function defines. 488 | target(str): The observation model parameter that is manipulated according to the function defined above. 489 | prior: List of hyper-prior distributions (one for each hyper-parameter), where each may be passed as a(lambda) 490 | function, as a SymPy random variable, or directly as a Numpy array with probability values for each 491 | hyper-parameter value 492 | 493 | Example: 494 | :: 495 | def quadratic(t, a=0, b=0): 496 | return a*(t**2) + b*t 497 | 498 | S = bl.Study() 499 | ... 500 | S.setObservationModel(bl.om.WhiteNoise('std', bl.oint(0, 3, 1000))) 501 | S.setTransitionModel(bl.tm.Deterministic(quadratic, target='signal')) 502 | """ 503 | def __init__(self, function=None, target=None, prior=None): 504 | self.study = None 505 | self.latticeConstant = None 506 | self.function = function 507 | self.selectedParameter = target 508 | self.tOffset = 0 # is set to the time of the last Breakpoint by SerialTransition model 509 | 510 | if target is None: 511 | raise ConfigurationError('No parameter set for transition model "Deterministic"') 512 | 513 | # create ordered dictionary of hyper-parameters from keyword-arguments of function 514 | argspec = getargspec(self.function) 515 | 516 | # only keyword arguments are allowed 517 | if not len(argspec.args) == len(argspec.defaults)+1: 518 | raise ConfigurationError('Function to define deterministic transition model can only contain one ' 519 | 'non-keyword argument (time; first argument) and keyword-arguments ' 520 | '(hyper-parameters) with default values.') 521 | 522 | # define hyper-parameters of transition model 523 | self.hyperParameterNames = [] 524 | self.hyperParameterValues = [] 525 | for arg, default in zip(argspec.args[1:], argspec.defaults): 526 | if isinstance(default, (list, tuple)): 527 | default = np.array(default) 528 | self.hyperParameterNames.append(arg) 529 | self.hyperParameterValues.append(default) 530 | 531 | if prior is None: 532 | # provide as many "None"-priors as there are hyper-parameters 533 | self.prior = [None]*len(argspec.defaults) 534 | else: 535 | # if list of priors is supplied, check length 536 | if isinstance(prior, Iterable): 537 | if not len(prior) == len(argspec.defaults): 538 | raise ConfigurationError('{} priors are defined for transition model "{}", but model contains {}' 539 | 'hyper-parameters.' 540 | .format(len(prior), self.function.__name__, len(argspec.defaults))) 541 | # if single prior is defined, pack it in a list 542 | else: 543 | self.prior = [prior] 544 | 545 | def __str__(self): 546 | return 'Deterministic model ({})'.format(self.function.__name__) 547 | 548 | def computeForwardPrior(self, posterior, t): 549 | """ 550 | Compute new prior from old posterior (moving forwards in time). 551 | 552 | Args: 553 | posterior(ndarray): Parameter distribution from current time step 554 | t(int, float): time stamp (integer time index by default) 555 | 556 | Returns: 557 | ndarray: Prior parameter distribution for subsequent time step 558 | """ 559 | # determine grid axis along which to shift the distribution 560 | axisToTransform = self.study.observationModel.parameterNames.index(self.selectedParameter) 561 | 562 | # compute offset to shift parameter grid 563 | params = {name: value for (name, value) in zip(self.hyperParameterNames, self.hyperParameterValues)} 564 | ftp1 = self.function(t + 1 - self.tOffset, **params) 565 | ft = self.function(t-self.tOffset, **params) 566 | d = ftp1-ft 567 | 568 | # normalize offset with respect to lattice constant of parameter grid 569 | d /= self.latticeConstant[axisToTransform] 570 | 571 | # build list for all axes of parameter grid (setting only the selected axis to a non-zero value) 572 | dAll = [0] * len(self.latticeConstant) 573 | dAll[axisToTransform] = d 574 | 575 | # shift interpolated version of distribution 576 | newPrior = shift(posterior, dAll, order=3, mode='nearest') 577 | 578 | # transformation above may violate proper normalization; re-normalization needed 579 | newPrior /= np.sum(newPrior) 580 | 581 | return newPrior 582 | 583 | def computeBackwardPrior(self, posterior, t): 584 | # determine grid axis along which to shift the distribution 585 | axisToTransform = self.study.observationModel.parameterNames.index(self.selectedParameter) 586 | 587 | # compute offset to shift parameter grid 588 | params = {name: value for (name, value) in zip(self.hyperParameterNames, self.hyperParameterValues)} 589 | ftm1 = self.function(t - 1 - self.tOffset, **params) 590 | ft = self.function(t - self.tOffset, **params) 591 | d = ftm1 - ft 592 | 593 | # normalize offset with respect to lattice constant of parameter grid 594 | d /= self.latticeConstant[axisToTransform] 595 | 596 | # build list for all axes of parameter grid (setting only the selected axis to a non-zero value) 597 | dAll = [0] * len(self.latticeConstant) 598 | dAll[axisToTransform] = d 599 | 600 | # shift interpolated version of distribution 601 | newPrior = shift(posterior, dAll, order=3, mode='nearest') 602 | 603 | # transformation above may violate proper normalization; re-normalization needed 604 | newPrior /= np.sum(newPrior) 605 | 606 | return newPrior 607 | 608 | 609 | class CombinedTransitionModel(TransitionModel): 610 | """ 611 | Different models act at the same time. This class allows to combine different transition models to be 612 | able to explore more complex parameter dynamics. All sub-models are passed to this class as arguments on 613 | initialization. Note that a different order of the sub-models can result in different parameter dynamics. 614 | 615 | Args: 616 | *args: Sequence of transition models 617 | """ 618 | def __init__(self, *args): 619 | self.study = None 620 | self.latticeConstant = None 621 | self.models = args 622 | self.tOffset = 0 # is set to the time of the last Breakpoint by SerialTransition model 623 | 624 | # check if any sub-model is a break-point and raise error if so 625 | if np.any([str(arg) == 'Break-point' for arg in args]): 626 | raise ConfigurationError('The "BreakPoint" transition model can only be used with the ' 627 | '"SerialTransitionModel" class.') 628 | 629 | def __str__(self): 630 | return 'Combined transition model' 631 | 632 | def computeForwardPrior(self, posterior, t): 633 | """ 634 | Compute new prior from old posterior (moving forwards in time). 635 | 636 | Args: 637 | posterior(ndarray): Parameter distribution from current time step 638 | t(int): integer time step 639 | 640 | Returns: 641 | ndarray: Prior parameter distribution for subsequent time step 642 | """ 643 | newPrior = posterior.copy() 644 | 645 | for m in self.models: 646 | m.latticeConstant = self.latticeConstant # latticeConstant needs to be propagated to sub-models 647 | m.study = self.study # study needs to be propagated to sub-models 648 | m.tOffset = self.tOffset 649 | newPrior = m.computeForwardPrior(newPrior, t) 650 | 651 | return newPrior 652 | 653 | def computeBackwardPrior(self, posterior, t): 654 | newPrior = posterior.copy() 655 | 656 | for m in self.models: 657 | m.latticeConstant = self.latticeConstant 658 | m.study = self.study 659 | m.tOffset = self.tOffset 660 | newPrior = m.computeBackwardPrior(newPrior, t) 661 | 662 | return newPrior 663 | 664 | 665 | class SerialTransitionModel(TransitionModel): 666 | """ 667 | Different models act at different time steps. To model fundamental changes in parameter dynamics, different 668 | transition models can be serially coupled. Depending on the time step, a corresponding sub-model is chosen to 669 | compute the new prior distribution from the posterior distribution. If a break-point lies in between two transition 670 | models, the parameter values do not change abruptly at the time step of the break-point, whereas a change-point not 671 | only changes the transition model, but also allows the parameters to change (the parameter distribution is re-set to 672 | the prior distribution). 673 | 674 | Args: 675 | *args: Sequence of transition models and break-points/change-points (for n models, n-1 676 | break-points/change-points have to be provided) 677 | 678 | Example: 679 | :: 680 | T = bl.tm.SerialTransitionModel(bl.tm.Static(), 681 | bl.tm.BreakPoint('t_1', 50), 682 | bl.tm.RegimeSwitch('log10pMin', -7), 683 | bl.tm.BreakPoint('t_2', 100), 684 | bl.tm.GaussianRandomWalk('sigma', 0.2, target='x')) 685 | 686 | In this example, parameters are assumed to be constant until 't_1' (time step 50), followed by a regime-switching- 687 | process until 't_2' (time step 100). Finally, we assume Gaussian parameter fluctuations for parameter 'x' until the 688 | last time step. Note that models and time steps do not necessarily have to be passed in an alternating way. 689 | """ 690 | def __init__(self, *args): 691 | self.study = None 692 | self.latticeConstant = None 693 | 694 | # determine time steps of structural breaks and other sub-models 695 | self.hyperParameterNames = [] 696 | self.hyperParameterValues = [] 697 | self.prior = [] 698 | self.models = [] 699 | self.changePointMask = [] 700 | for arg in args: 701 | if str(arg) == 'Break-point': 702 | self.hyperParameterNames.append(arg.name) 703 | self.prior.append(arg.prior) 704 | 705 | # exclude 'all' case, conversion to list is needed to avoid future warning about element-wise comparison 706 | if isinstance(arg.value, str) and arg.value == 'all': # 'all' is passed without type change 707 | self.hyperParameterValues.append(arg.value) 708 | elif isinstance(arg.value, Iterable): # convert list/tuple in numpy array 709 | self.hyperParameterValues.append(np.array(arg.value)) 710 | else: # single values are passed without type change 711 | self.hyperParameterValues.append(arg.value) 712 | self.changePointMask.append(0) 713 | elif str(arg) == 'Change-point': 714 | name = arg.hyperParameterNames[0] 715 | value = arg.hyperParameterValues[0] 716 | self.hyperParameterNames.append(name) 717 | self.prior.append(arg.prior) 718 | 719 | # exclude 'all' case, conversion to list is needed to avoid future warning about element-wise comparison 720 | if isinstance(value, str) and value == 'all': # 'all' is passed without type change 721 | self.hyperParameterValues.append(value) 722 | elif isinstance(value, Iterable): # convert list/tuple in numpy array 723 | self.hyperParameterValues.append(np.array(value)) 724 | else: # single values are passed without type change 725 | self.hyperParameterValues.append(value) 726 | self.changePointMask.append(1) 727 | else: # sub-model 728 | self.models.append(arg) 729 | 730 | self.changePointMask = np.array(self.changePointMask).astype(bool) 731 | 732 | # check: break times have to be passed in monotonically increasing order 733 | # since multiple values can be passed for one break-point at init, we check first values only 734 | firstValues = [] 735 | for v in self.hyperParameterValues: 736 | if isinstance(v, str) and v == 'all': 737 | firstValues.append(v) 738 | elif isinstance(v, Iterable): 739 | firstValues.append(v[0]) 740 | else: 741 | firstValues.append(v) 742 | 743 | if not all(x < y if not ((isinstance(x, str) and x == 'all') or (isinstance(y, str) and y == 'all')) else True 744 | for x, y in zip(firstValues, firstValues[1:])): 745 | raise ConfigurationError('Time steps for structural breaks and/or change-points have to be passed in ' 746 | 'monotonically increasing order.') 747 | 748 | # check: n models require n-1 break times 749 | if not (len(self.models)-1 == len(self.hyperParameterValues)): 750 | raise ConfigurationError('Wrong number of structural breaks/change-points and models. For n models, n-1 ' 751 | 'structural breaks/change-points are required.') 752 | 753 | def __str__(self): 754 | return 'Serial transition model' 755 | 756 | def computeForwardPrior(self, posterior, t): 757 | """ 758 | Compute new prior from old posterior (moving forwards in time). 759 | 760 | Args: 761 | posterior(ndarray): Parameter distribution from current time step 762 | t(int): integer time step 763 | 764 | Returns: 765 | ndarray: Prior parameter distribution for subsequent time step 766 | """ 767 | # the index of the model to choose at time t is given by the number of break times <= t 768 | modelIndex = np.sum(np.array(self.hyperParameterValues) <= t) 769 | 770 | self.models[modelIndex].latticeConstant = self.latticeConstant # latticeConstant needs to be propagated 771 | self.models[modelIndex].study = self.study # study needs to be propagated 772 | self.models[modelIndex].tOffset = self.hyperParameterValues[modelIndex-1] if modelIndex > 0 else 0 773 | newPrior = self.models[modelIndex].computeForwardPrior(posterior, t) 774 | newPrior = self._forwardChangePointCheck(newPrior, t) 775 | return newPrior 776 | 777 | def computeBackwardPrior(self, posterior, t): 778 | # the index of the model to choose at time t is given by the number of break times <= t 779 | modelIndex = np.sum(np.array(self.hyperParameterValues) <= t-1) 780 | 781 | self.models[modelIndex].latticeConstant = self.latticeConstant # latticeConstant needs to be propagated 782 | self.models[modelIndex].study = self.study # study needs to be propagated 783 | self.models[modelIndex].tOffset = self.hyperParameterValues[modelIndex-1] if modelIndex > 0 else 0 784 | newPrior = self.models[modelIndex].computeBackwardPrior(posterior, t) 785 | newPrior = self._backwardChangePointCheck(newPrior, t) 786 | return newPrior 787 | 788 | def _forwardChangePointCheck(self, posterior, t): 789 | """ 790 | This function checks if a change-point is set to the current time step and replaces the posterior with the prior 791 | distribution, just like the change-point transition model. This allows to use change-points in serial transition 792 | models. 793 | 794 | Args: 795 | posterior(ndarray): Parameter distribution from current time step 796 | t(int): integer time step 797 | 798 | Returns: 799 | ndarray: Prior parameter distribution for subsequent time step 800 | """ 801 | if t in np.array(self.hyperParameterValues)[self.changePointMask]: 802 | # check if custom prior is used by observation model 803 | if hasattr(self.study.observationModel.prior, '__call__'): 804 | prior = self.study.observationModel.prior(*self.study.grid) 805 | elif isinstance(self.study.observationModel.prior, np.ndarray): 806 | prior = deepcopy(self.study.observationModel.prior) 807 | else: 808 | prior = np.ones(self.study.gridSize) # flat prior 809 | 810 | # normalize prior (necessary in case an improper prior is used) 811 | prior /= np.sum(prior) 812 | prior *= np.prod(self.study.latticeConstant) 813 | return prior 814 | else: 815 | return posterior 816 | 817 | def _backwardChangePointCheck(self, posterior, t): 818 | return self._forwardChangePointCheck(posterior, t - 1) 819 | 820 | 821 | class BreakPoint(TransitionModel): 822 | """ 823 | Break-point. This class can only be used to specify break-point within a SerialTransitionModel instance. 824 | 825 | Args: 826 | name(str): custom name of the hyper-parameter tBreak 827 | value(int, list, tuple, ndarray): Value(s) of the time step(s) of the break point 828 | prior: hyper-prior distribution that may be passed as a(lambda) function, as a SymPy random variable, or 829 | directly as a Numpy array with probability values for each hyper-parameter value 830 | """ 831 | def __init__(self, name='tBreak', value=None, prior=None): 832 | if isinstance(value, (list, tuple)): 833 | value = np.array(value) 834 | 835 | self.name = name 836 | self.value = value 837 | self.prior = prior 838 | 839 | def __str__(self): 840 | return 'Break-point' 841 | 842 | 843 | class BivariateRandomWalk(TransitionModel): 844 | """ 845 | Correlated Gaussian parameter fluctuations. This model assumes that parameter changes follow a bivariate Gaussian 846 | distribution. 847 | """ 848 | def __init__(self, name1='sigma1', value1=None, 849 | name2='sigma2', value2=None, 850 | name3='rho', value3=None, 851 | prior=(None, None, None)): 852 | 853 | if isinstance(value1, (list, tuple)): 854 | value1 = np.array(value1) 855 | if isinstance(value2, (list, tuple)): 856 | value2 = np.array(value2) 857 | if isinstance(value3, (list, tuple)): 858 | value2 = np.array(value3) 859 | 860 | self.study = None 861 | self.latticeConstant = None 862 | self.hyperParameterNames = [name1, name2, name3] 863 | self.hyperParameterValues = [value1, value2, value3] 864 | self.prior = prior 865 | self.kernel = None 866 | self.kernelParameters = None 867 | self.tOffset = 0 # is set to the time of the last Breakpoint by SerialTransition model 868 | 869 | def __str__(self): 870 | return 'Bivariate random walk' 871 | 872 | def computeForwardPrior(self, posterior, t): 873 | """ 874 | Compute new prior from old posterior (moving forwards in time). 875 | 876 | Args: 877 | posterior(ndarray): Parameter distribution from current time step 878 | t(int): integer time step 879 | 880 | Returns: 881 | ndarray: Prior parameter distribution for subsequent time step 882 | """ 883 | 884 | # if hyper-parameter values have changed, a new convolution kernel needs to be created 885 | if not self.kernelParameters == self.hyperParameterValues: 886 | normedSigma1 = self.hyperParameterValues[0] / self.latticeConstant[0] 887 | normedSigma2 = self.hyperParameterValues[1] / self.latticeConstant[1] 888 | 889 | self.kernel = self.createKernel(normedSigma1, normedSigma2, self.hyperParameterValues[2]) 890 | self.kernelParameters = deepcopy(self.hyperParameterValues) 891 | 892 | newPrior = convolve2d(posterior, self.kernel, mode='same') 893 | newPrior /= np.sum(newPrior) 894 | return newPrior 895 | 896 | def computeBackwardPrior(self, posterior, t): 897 | return self.computeForwardPrior(posterior, t - 1) 898 | 899 | @staticmethod 900 | def createKernel(sigma1, sigma2, rho): 901 | rv = multivariate_normal(cov=[[sigma1 ** 2., rho * sigma1 * sigma2], 902 | [rho * sigma1 * sigma2, sigma2 ** 2.]]) 903 | 904 | x = np.arange(-3 * np.ceil(sigma1), 3 * np.ceil(sigma1) + 1) 905 | y = np.arange(-3 * np.ceil(sigma2), 3 * np.ceil(sigma2) + 1) 906 | 907 | xv, yv = np.meshgrid(x, y, sparse=False, indexing='ij') 908 | 909 | kernel = rv.pdf(np.array([xv, yv]).T).T 910 | kernel /= np.sum(kernel) 911 | return kernel 912 | -------------------------------------------------------------------------------- /docs/images/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophmark/bayesloop/c1a6c96a175d925adce794d3d6044c09f3954df7/docs/images/example.png -------------------------------------------------------------------------------- /docs/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophmark/bayesloop/c1a6c96a175d925adce794d3d6044c09f3954df7/docs/images/favicon.ico -------------------------------------------------------------------------------- /docs/images/html_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophmark/bayesloop/c1a6c96a175d925adce794d3d6044c09f3954df7/docs/images/html_logo.png -------------------------------------------------------------------------------- /docs/images/logo_400x100px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophmark/bayesloop/c1a6c96a175d925adce794d3d6044c09f3954df7/docs/images/logo_400x100px.png -------------------------------------------------------------------------------- /docs/images/logo_75px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophmark/bayesloop/c1a6c96a175d925adce794d3d6044c09f3954df7/docs/images/logo_75px.png -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% 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. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | echo. dummy to check syntax errors of document sources 43 | goto end 44 | ) 45 | 46 | if "%1" == "clean" ( 47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 48 | del /q /s %BUILDDIR%\* 49 | goto end 50 | ) 51 | 52 | 53 | REM Check if sphinx-build is available and fallback to Python version if any 54 | %SPHINXBUILD% 1>NUL 2>NUL 55 | if errorlevel 9009 goto sphinx_python 56 | goto sphinx_ok 57 | 58 | :sphinx_python 59 | 60 | set SPHINXBUILD=python -m sphinx.__init__ 61 | %SPHINXBUILD% 2> nul 62 | if errorlevel 9009 ( 63 | echo. 64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 65 | echo.installed, then set the SPHINXBUILD environment variable to point 66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 67 | echo.may add the Sphinx directory to PATH. 68 | echo. 69 | echo.If you don't have Sphinx installed, grab it from 70 | echo.http://sphinx-doc.org/ 71 | exit /b 1 72 | ) 73 | 74 | :sphinx_ok 75 | 76 | 77 | if "%1" == "html" ( 78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 79 | if errorlevel 1 exit /b 1 80 | echo. 81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 82 | goto end 83 | ) 84 | 85 | if "%1" == "dirhtml" ( 86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 87 | if errorlevel 1 exit /b 1 88 | echo. 89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 90 | goto end 91 | ) 92 | 93 | if "%1" == "singlehtml" ( 94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 95 | if errorlevel 1 exit /b 1 96 | echo. 97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 98 | goto end 99 | ) 100 | 101 | if "%1" == "pickle" ( 102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 103 | if errorlevel 1 exit /b 1 104 | echo. 105 | echo.Build finished; now you can process the pickle files. 106 | goto end 107 | ) 108 | 109 | if "%1" == "json" ( 110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 111 | if errorlevel 1 exit /b 1 112 | echo. 113 | echo.Build finished; now you can process the JSON files. 114 | goto end 115 | ) 116 | 117 | if "%1" == "htmlhelp" ( 118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 119 | if errorlevel 1 exit /b 1 120 | echo. 121 | echo.Build finished; now you can run HTML Help Workshop with the ^ 122 | .hhp project file in %BUILDDIR%/htmlhelp. 123 | goto end 124 | ) 125 | 126 | if "%1" == "qthelp" ( 127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 128 | if errorlevel 1 exit /b 1 129 | echo. 130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 131 | .qhcp project file in %BUILDDIR%/qthelp, like this: 132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\bayesloop.qhcp 133 | echo.To view the help file: 134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\bayesloop.ghc 135 | goto end 136 | ) 137 | 138 | if "%1" == "devhelp" ( 139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 140 | if errorlevel 1 exit /b 1 141 | echo. 142 | echo.Build finished. 143 | goto end 144 | ) 145 | 146 | if "%1" == "epub" ( 147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 148 | if errorlevel 1 exit /b 1 149 | echo. 150 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 151 | goto end 152 | ) 153 | 154 | if "%1" == "epub3" ( 155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 156 | if errorlevel 1 exit /b 1 157 | echo. 158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 159 | goto end 160 | ) 161 | 162 | if "%1" == "latex" ( 163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 164 | if errorlevel 1 exit /b 1 165 | echo. 166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdf" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "latexpdfja" ( 181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 182 | cd %BUILDDIR%/latex 183 | make all-pdf-ja 184 | cd %~dp0 185 | echo. 186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 187 | goto end 188 | ) 189 | 190 | if "%1" == "text" ( 191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 192 | if errorlevel 1 exit /b 1 193 | echo. 194 | echo.Build finished. The text files are in %BUILDDIR%/text. 195 | goto end 196 | ) 197 | 198 | if "%1" == "man" ( 199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 200 | if errorlevel 1 exit /b 1 201 | echo. 202 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 203 | goto end 204 | ) 205 | 206 | if "%1" == "texinfo" ( 207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 208 | if errorlevel 1 exit /b 1 209 | echo. 210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 211 | goto end 212 | ) 213 | 214 | if "%1" == "gettext" ( 215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 216 | if errorlevel 1 exit /b 1 217 | echo. 218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 219 | goto end 220 | ) 221 | 222 | if "%1" == "changes" ( 223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 224 | if errorlevel 1 exit /b 1 225 | echo. 226 | echo.The overview file is in %BUILDDIR%/changes. 227 | goto end 228 | ) 229 | 230 | if "%1" == "linkcheck" ( 231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 232 | if errorlevel 1 exit /b 1 233 | echo. 234 | echo.Link check complete; look for any errors in the above output ^ 235 | or in %BUILDDIR%/linkcheck/output.txt. 236 | goto end 237 | ) 238 | 239 | if "%1" == "doctest" ( 240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 241 | if errorlevel 1 exit /b 1 242 | echo. 243 | echo.Testing of doctests in the sources finished, look at the ^ 244 | results in %BUILDDIR%/doctest/output.txt. 245 | goto end 246 | ) 247 | 248 | if "%1" == "coverage" ( 249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 250 | if errorlevel 1 exit /b 1 251 | echo. 252 | echo.Testing of coverage in the sources finished, look at the ^ 253 | results in %BUILDDIR%/coverage/python.txt. 254 | goto end 255 | ) 256 | 257 | if "%1" == "xml" ( 258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 259 | if errorlevel 1 exit /b 1 260 | echo. 261 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 262 | goto end 263 | ) 264 | 265 | if "%1" == "pseudoxml" ( 266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 267 | if errorlevel 1 exit /b 1 268 | echo. 269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 270 | goto end 271 | ) 272 | 273 | if "%1" == "dummy" ( 274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy 275 | if errorlevel 1 exit /b 1 276 | echo. 277 | echo.Build finished. Dummy builder generates no files. 278 | goto end 279 | ) 280 | 281 | :end 282 | -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | ************* 4 | API Reference 5 | ************* 6 | 7 | Study types 8 | ----------- 9 | 10 | .. currentmodule:: bayesloop.core 11 | .. autosummary:: 12 | 13 | Study 14 | HyperStudy 15 | ChangepointStudy 16 | OnlineStudy 17 | 18 | .. note:: 19 | 20 | These Study classes are imported directly into the module namespace for convenient access. 21 | 22 | .. code-block:: python 23 | 24 | import bayesloop as bl 25 | S = bl.Study() 26 | 27 | .. automodule:: bayesloop.core 28 | :members: 29 | 30 | Observation models 31 | ------------------ 32 | 33 | .. currentmodule:: bayesloop.observationModels 34 | .. autosummary:: 35 | 36 | SymPy 37 | SciPy 38 | NumPy 39 | Bernoulli 40 | Poisson 41 | Gaussian 42 | GaussianMean 43 | WhiteNoise 44 | AR1 45 | ScaledAR1 46 | 47 | .. note:: 48 | 49 | You can use the short-form `om` to access all observation models: 50 | 51 | .. code-block:: python 52 | 53 | import bayesloop as bl 54 | L = bl.om.SymPy(...) 55 | 56 | .. automodule:: bayesloop.observationModels 57 | :members: 58 | 59 | Transition models 60 | ----------------- 61 | 62 | .. currentmodule:: bayesloop.transitionModels 63 | .. autosummary:: 64 | 65 | Static 66 | Deterministic 67 | GaussianRandomWalk 68 | AlphaStableRandomWalk 69 | ChangePoint 70 | RegimeSwitch 71 | Independent 72 | NotEqual 73 | CombinedTransitionModel 74 | SerialTransitionModel 75 | 76 | .. note:: 77 | 78 | You can use the short-form `tm` to access all transition models: 79 | 80 | .. code-block:: python 81 | 82 | import bayesloop as bl 83 | T = bl.tm.ChangePoint(...) 84 | 85 | .. automodule:: bayesloop.transitionModels 86 | :members: 87 | 88 | File I/O 89 | -------- 90 | 91 | .. automodule:: bayesloop.fileIO 92 | :members: 93 | 94 | .. note:: 95 | 96 | Both file I/O functions are imported directly into the module namespace for convenient access. 97 | 98 | .. code-block:: python 99 | 100 | import bayesloop as bl 101 | S = bl.Study() 102 | ... 103 | bl.save('test.bl', S) 104 | ... 105 | S = bl.load('test.bl') 106 | 107 | Probability Parser 108 | ------------------ 109 | 110 | .. autoclass:: bayesloop.Parser -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # bayesloop documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Aug 05 16:44:10 2016. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | 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 | 23 | sys.path.insert(0, os.path.abspath('../../bayesloop/')) 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | #needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'nbsphinx', 35 | 'sphinx.ext.autodoc', 36 | 'sphinx.ext.autosummary', 37 | 'sphinx.ext.napoleon', 38 | 'sphinx.ext.intersphinx', 39 | 'sphinx.ext.mathjax', 40 | 'sphinx.ext.ifconfig', 41 | 'sphinx.ext.viewcode', 42 | 'IPython.sphinxext.ipython_console_highlighting' 43 | ] 44 | 45 | # Add any paths that contain templates here, relative to this directory. 46 | templates_path = ['_templates'] 47 | 48 | # The suffix(es) of source filenames. 49 | # You can specify multiple suffix as a list of string: 50 | # source_suffix = ['.rst', '.md'] 51 | source_suffix = '.rst' 52 | 53 | # The encoding of source files. 54 | #source_encoding = 'utf-8-sig' 55 | 56 | # The master toctree document. 57 | master_doc = 'index' 58 | 59 | # General information about the project. 60 | project = u'bayesloop' 61 | copyright = u'2016-2017, Christoph Mark' 62 | author = u'Christoph Mark' 63 | 64 | # The version info for the project you're documenting, acts as replacement for 65 | # |version| and |release|, also used in various other places throughout the 66 | # built documents. 67 | # 68 | # The short X.Y version. 69 | version = u'1.4' 70 | # The full version, including alpha/beta/rc tags. 71 | release = u'1.4' 72 | 73 | # The language for content autogenerated by Sphinx. Refer to documentation 74 | # for a list of supported languages. 75 | # 76 | # This is also used if you do content translation via gettext catalogs. 77 | # Usually you set "language" from the command line for these cases. 78 | language = None 79 | 80 | # There are two options for replacing |today|: either, you set today to some 81 | # non-false value, then it is used: 82 | #today = '' 83 | # Else, today_fmt is used as the format for a strftime call. 84 | #today_fmt = '%B %d, %Y' 85 | 86 | # List of patterns, relative to source directory, that match files and 87 | # directories to ignore when looking for source files. 88 | # This patterns also effect to html_static_path and html_extra_path 89 | exclude_patterns = ['**.ipynb_checkpoints'] 90 | 91 | nbsphinx_execute = 'never' 92 | 93 | # The reST default role (used for this markup: `text`) to use for all 94 | # documents. 95 | #default_role = None 96 | 97 | # If true, '()' will be appended to :func: etc. cross-reference text. 98 | #add_function_parentheses = True 99 | 100 | # If true, the current module name will be prepended to all description 101 | # unit titles (such as .. function::). 102 | #add_module_names = True 103 | 104 | # If true, sectionauthor and moduleauthor directives will be shown in the 105 | # output. They are ignored by default. 106 | #show_authors = False 107 | 108 | # The name of the Pygments (syntax highlighting) style to use. 109 | pygments_style = 'sphinx' 110 | 111 | # A list of ignored prefixes for module index sorting. 112 | #modindex_common_prefix = [] 113 | 114 | # If true, keep warnings as "system message" paragraphs in the built documents. 115 | #keep_warnings = False 116 | 117 | # If true, `todo` and `todoList` produce output, else they produce nothing. 118 | todo_include_todos = False 119 | 120 | 121 | # -- Options for HTML output ---------------------------------------------- 122 | 123 | # The theme to use for HTML and HTML Help pages. See the documentation for 124 | # a list of builtin themes. 125 | #html_theme = 'alabaster' 126 | html_theme = 'sphinx_rtd_theme' 127 | 128 | # Theme options are theme-specific and customize the look and feel of a theme 129 | # further. For a list of options available for each theme, see the 130 | # documentation. 131 | #html_theme_options = {} 132 | 133 | # Add any paths that contain custom themes here, relative to this directory. 134 | #html_theme_path = [] 135 | 136 | # The name for this set of Sphinx documents. 137 | # " v documentation" by default. 138 | #html_title = u'bayesloop 1.4' 139 | 140 | # A shorter title for the navigation bar. Default is the same as html_title. 141 | #html_short_title = None 142 | 143 | # The name of an image file (relative to this directory) to place at the top 144 | # of the sidebar. 145 | html_logo = '../images/html_logo.png' 146 | 147 | # The name of an image file (relative to this directory) to use as a favicon of 148 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 149 | # pixels large. 150 | html_favicon = '../images/favicon.ico' 151 | 152 | # Add any paths that contain custom static files (such as style sheets) here, 153 | # relative to this directory. They are copied after the builtin static files, 154 | # so a file named "default.css" will overwrite the builtin "default.css". 155 | html_static_path = ['_static'] 156 | 157 | # Add any extra paths that contain custom files (such as robots.txt or 158 | # .htaccess) here, relative to this directory. These files are copied 159 | # directly to the root of the documentation. 160 | #html_extra_path = [] 161 | 162 | # If not None, a 'Last updated on:' timestamp is inserted at every page 163 | # bottom, using the given strftime format. 164 | # The empty string is equivalent to '%b %d, %Y'. 165 | #html_last_updated_fmt = None 166 | 167 | # If true, SmartyPants will be used to convert quotes and dashes to 168 | # typographically correct entities. 169 | #html_use_smartypants = True 170 | 171 | # Custom sidebar templates, maps document names to template names. 172 | #html_sidebars = {} 173 | 174 | # Additional templates that should be rendered to pages, maps page names to 175 | # template names. 176 | #html_additional_pages = {} 177 | 178 | # If false, no module index is generated. 179 | #html_domain_indices = True 180 | 181 | # If false, no index is generated. 182 | #html_use_index = True 183 | 184 | # If true, the index is split into individual pages for each letter. 185 | #html_split_index = False 186 | 187 | # If true, links to the reST sources are added to the pages. 188 | #html_show_sourcelink = True 189 | 190 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 191 | #html_show_sphinx = True 192 | 193 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 194 | #html_show_copyright = True 195 | 196 | # If true, an OpenSearch description file will be output, and all pages will 197 | # contain a tag referring to it. The value of this option must be the 198 | # base URL from which the finished HTML is served. 199 | #html_use_opensearch = '' 200 | 201 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 202 | #html_file_suffix = None 203 | 204 | # Language to be used for generating the HTML full-text search index. 205 | # Sphinx supports the following languages: 206 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 207 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' 208 | #html_search_language = 'en' 209 | 210 | # A dictionary with options for the search language support, empty by default. 211 | # 'ja' uses this config value. 212 | # 'zh' user can custom change `jieba` dictionary path. 213 | #html_search_options = {'type': 'default'} 214 | 215 | # The name of a javascript file (relative to the configuration directory) that 216 | # implements a search results scorer. If empty, the default will be used. 217 | #html_search_scorer = 'scorer.js' 218 | 219 | # Output file base name for HTML help builder. 220 | htmlhelp_basename = 'bayesloopdoc' 221 | 222 | # -- Options for LaTeX output --------------------------------------------- 223 | 224 | latex_elements = { 225 | # The paper size ('letterpaper' or 'a4paper'). 226 | #'papersize': 'letterpaper', 227 | 228 | # The font size ('10pt', '11pt' or '12pt'). 229 | #'pointsize': '10pt', 230 | 231 | # Additional stuff for the LaTeX preamble. 232 | #'preamble': '', 233 | 234 | # Latex figure (float) alignment 235 | #'figure_align': 'htbp', 236 | } 237 | 238 | # Grouping the document tree into LaTeX files. List of tuples 239 | # (source start file, target name, title, 240 | # author, documentclass [howto, manual, or own class]). 241 | latex_documents = [ 242 | (master_doc, 'bayesloop.tex', u'bayesloop Documentation', 243 | u'Christoph Mark', 'manual'), 244 | ] 245 | 246 | # The name of an image file (relative to this directory) to place at the top of 247 | # the title page. 248 | #latex_logo = None 249 | 250 | # For "manual" documents, if this is true, then toplevel headings are parts, 251 | # not chapters. 252 | #latex_use_parts = False 253 | 254 | # If true, show page references after internal links. 255 | #latex_show_pagerefs = False 256 | 257 | # If true, show URL addresses after external links. 258 | #latex_show_urls = False 259 | 260 | # Documents to append as an appendix to all manuals. 261 | #latex_appendices = [] 262 | 263 | # If false, no module index is generated. 264 | #latex_domain_indices = True 265 | 266 | 267 | # -- Options for manual page output --------------------------------------- 268 | 269 | # One entry per manual page. List of tuples 270 | # (source start file, name, description, authors, manual section). 271 | man_pages = [ 272 | (master_doc, 'bayesloop', u'bayesloop Documentation', 273 | [author], 1) 274 | ] 275 | 276 | # If true, show URL addresses after external links. 277 | #man_show_urls = False 278 | 279 | 280 | # -- Options for Texinfo output ------------------------------------------- 281 | 282 | # Grouping the document tree into Texinfo files. List of tuples 283 | # (source start file, target name, title, author, 284 | # dir menu entry, description, category) 285 | texinfo_documents = [ 286 | (master_doc, 'bayesloop', u'bayesloop Documentation', 287 | author, 'bayesloop', 'Model selection for time-varying parameter models', 288 | 'Miscellaneous'), 289 | ] 290 | 291 | # Documents to append as an appendix to all manuals. 292 | #texinfo_appendices = [] 293 | 294 | # If false, no module index is generated. 295 | #texinfo_domain_indices = True 296 | 297 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 298 | #texinfo_show_urls = 'footnote' 299 | 300 | # If true, do not generate a @detailmenu in the "Top" node's menu. 301 | #texinfo_no_detailmenu = False 302 | 303 | 304 | # Example configuration for intersphinx: refer to the Python standard library. 305 | intersphinx_mapping = {'https://docs.python.org/': None} 306 | -------------------------------------------------------------------------------- /docs/source/examples.rst: -------------------------------------------------------------------------------- 1 | .. _examples: 2 | 3 | ******** 4 | Examples 5 | ******** 6 | 7 | .. toctree:: 8 | examples/anomalousdiffusion.ipynb 9 | examples/stockmarketfluctuations.ipynb 10 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. bayesloop documentation master file, created by 2 | sphinx-quickstart on Fri Aug 05 16:44:10 2016. 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 bayesloop's documentation! 7 | ===================================== 8 | 9 | |logo| 10 | 11 | .. |logo| image:: https://raw.githubusercontent.com/christophmark/bayesloop/master/docs/images/logo_400x100px.png 12 | 13 | **Probabilistic programming framework that facilitates objective model selection for time-varying parameter models.** 14 | 15 | .. seealso:: 16 | 17 | If you want to contribute to the project or just browse the source code, visit the `Github repository `__ of *bayesloop*. 18 | 19 | 20 | Contents 21 | -------- 22 | 23 | .. toctree:: 24 | :maxdepth: 3 25 | 26 | installation 27 | tutorials 28 | examples 29 | api 30 | 31 | 32 | Indices and tables 33 | ------------------ 34 | 35 | * :ref:`genindex` 36 | * :ref:`modindex` 37 | * :ref:`search` 38 | 39 | -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | 3 | ************ 4 | Installation 5 | ************ 6 | 7 | The easiest way to install the latest release version of *bayesloop* is via ``pip``: 8 | 9 | :: 10 | 11 | pip install bayesloop 12 | 13 | Alternatively, a zipped version can be downloaded `here `__. The module is installed by calling ``python setup.py install``. 14 | 15 | Development version 16 | ------------------- 17 | 18 | The latest development version of *bayesloop* can be installed from the master branch using pip (requires git): 19 | 20 | :: 21 | 22 | pip install git+https://github.com/christophmark/bayesloop 23 | 24 | Alternatively, use this `zipped version `__ or clone the repository. 25 | 26 | Dependencies 27 | ------------ 28 | 29 | *bayesloop* is tested on Python 2.7, 3.5 and 3.6. It depends on NumPy, SciPy, SymPy, matplotlib, tqdm and dill. All except the last two are already included in the `Anaconda distribution `__ of Python. Windows users may also take advantage of pre-compiled binaries for all dependencies, which can be found at `Christoph Gohlke's page `__. 30 | 31 | Optional dependencies 32 | --------------------- 33 | 34 | *bayesloop* supports multiprocessing for computationally expensive analyses, based on the `pathos `__ module. The latest version can be obtained directly from GitHub using pip (requires git): 35 | 36 | :: 37 | 38 | pip install git+https://github.com/uqfoundation/pathos 39 | 40 | .. note:: 41 | 42 | Windows users need to install a C compiler *before* installing pathos. One possible solution for 64bit systems is to install `Microsoft Visual C++ 2008 SP1 Redistributable Package (x64) `__ and `Microsoft Visual C++ Compiler for Python 2.7 `__. 43 | -------------------------------------------------------------------------------- /docs/source/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | scipy 3 | sympy 4 | matplotlib 5 | ipython 6 | sphinx 7 | jupyter_client 8 | nbsphinx 9 | nbconvert==6.5.1 -------------------------------------------------------------------------------- /docs/source/tutorials.rst: -------------------------------------------------------------------------------- 1 | .. _tutorials: 2 | 3 | ********* 4 | Tutorials 5 | ********* 6 | 7 | .. toctree:: 8 | tutorials/firststeps.ipynb 9 | tutorials/modelselection.ipynb 10 | tutorials/hyperparameteroptimization.ipynb 11 | tutorials/hyperstudy.ipynb 12 | tutorials/changepointstudy.ipynb 13 | tutorials/onlinestudy.ipynb 14 | tutorials/priordistributions.ipynb 15 | tutorials/customobservationmodels.ipynb 16 | tutorials/probabilityparser.ipynb 17 | tutorials/multiprocessing.ipynb 18 | -------------------------------------------------------------------------------- /docs/source/tutorials/multiprocessing.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Multiprocessing\n", 8 | "\n", 9 | "Conducting extensive data studies based on the `HyperStudy` or `ChangepointStudy` classes may involve several 10.000 or 100.000 individual fits (see e.g. [here](changepointstudy.html#Analyzing-structural-breaks-in-time-series-models)). Since these individual fits with different hyper-parameter values are independent of each other, the computational workload may be distributed among the individual cores of a multi-core processor. To keep things simple, *bayesloop* uses [object serialization](https://docs.python.org/2/library/pickle.html) to create duplicates of the current `HyperStudy` or `ChangepointStudy` instance and distributes them across the predefined number of cores. In general, this procedure may be handled by the built-in Python module [multiprocessing](https://docs.python.org/2/library/multiprocessing.html). However, *multiprocessing* relies on the built-in module [pickle](https://docs.python.org/2/library/pickle.html) for object serialization, which fails to serialize the classes defined in *bayesloop*. We therefore use a different version of the *multiprocessing* module that is part of the [pathos](https://github.com/uqfoundation/pathos) module.\n", 10 | "\n", 11 | "The latest version of *pathos* can be installed directly via [pip](https://pypi.python.org/pypi/pip), but requires [git](https://de.wikipedia.org/wiki/Git):\n", 12 | "```\n", 13 | "pip install git+https://github.com/uqfoundation/pathos\n", 14 | "```\n", 15 | "**Note**: Windows users need to install a C compiler *before* installing pathos. One possible solution for 64-bit systems is to install [Microsoft Visual C++ 2008 SP1 Redistributable Package (x64)](http://www.microsoft.com/en-us/download/confirmation.aspx?id=2092) and [Microsoft Visual C++ Compiler for Python 2.7](http://www.microsoft.com/en-us/download/details.aspx?id=44266).\n", 16 | "\n", 17 | "Once installed correctly, the number of cores to use in a hyper-study or change-point study can be specified by using the keyword argument `nJobs` within the `fit` method. Example:\n", 18 | "```\n", 19 | "S.fit(silent=True, nJobs=4)\n", 20 | "```" 21 | ] 22 | } 23 | ], 24 | "metadata": { 25 | "anaconda-cloud": {}, 26 | "kernelspec": { 27 | "display_name": "Python 2", 28 | "language": "python", 29 | "name": "python2" 30 | }, 31 | "language_info": { 32 | "codemirror_mode": { 33 | "name": "ipython", 34 | "version": 2 35 | }, 36 | "file_extension": ".py", 37 | "mimetype": "text/x-python", 38 | "name": "python", 39 | "nbconvert_exporter": "python", 40 | "pygments_lexer": "ipython2", 41 | "version": "2.7.10" 42 | } 43 | }, 44 | "nbformat": 4, 45 | "nbformat_minor": 0 46 | } 47 | -------------------------------------------------------------------------------- /docs/source/tutorials/onlinestudy.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Online study\n", 8 | "\n", 9 | "All study types of *bayesloop* introduced so far are used for retrospective data analysis, i.e. the complete data set is already available at the time of the analysis. Many applications, however, from algorithmic trading to the monitoring of heart function or blood sugar levels call for on-line analysis methods that can take into account new information as it arrives from external sources. For this purpose, *bayesloop* provides the class `OnlineStudy`, which enables the inference of time-varying parameters in a sequential fashion, much like a [particle filter](https://en.wikipedia.org/wiki/Particle_filter). In contrast to particle filters, the `OnlineStudy` can account for different *scenarios* of parameter dynamics (i.e. different transition models) and can apply on-line model selection to objectively determine which scenario is more likely to describe the current data point, or all past data points.\n", 10 | "\n", 11 | "In this case, we avoid constructing some artificial usage example and directly point the reader at this [case study on stock market fluctuations](../examples/stoackmarketfluctuations.html). In this detailed example, we investigate the intra-day price fluctuations of the exchange-traded fund SPY. Based on two different transition models, one for *normal* market function and a second one for *chaotic* market fluctuations, we identify price corrections that are induced by news announcements of economic indicators." 12 | ] 13 | } 14 | ], 15 | "metadata": { 16 | "anaconda-cloud": {}, 17 | "kernelspec": { 18 | "display_name": "Python 2", 19 | "language": "python", 20 | "name": "python2" 21 | }, 22 | "language_info": { 23 | "codemirror_mode": { 24 | "name": "ipython", 25 | "version": 2 26 | }, 27 | "file_extension": ".py", 28 | "mimetype": "text/x-python", 29 | "name": "python", 30 | "nbconvert_exporter": "python", 31 | "pygments_lexer": "ipython2", 32 | "version": "2.7.10" 33 | } 34 | }, 35 | "nbformat": 4, 36 | "nbformat_minor": 0 37 | } 38 | -------------------------------------------------------------------------------- /docs/source/tutorials/priordistributions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Prior distributions\n", 8 | "\n", 9 | "One important aspect of Bayesian inference has not yet been discussed in this tutorial: [prior distributions](https://en.wikipedia.org/wiki/Prior_probability). In Bayesian statistics, one has to provide probability (density) values for every possible parameter value *before* taking into account the data at hand. This prior distribution thus reflects all *prior* knowledge of the system that is to be investigated. In the case that no prior knowledge is available, a *non-informative* prior in the form of the so-called [Jeffreys prior](https://en.wikipedia.org/wiki/Jeffreys_prior) allows to minimize the effect of the prior on the results. The next two sub-sections discuss how one can set custom prior distributions for the parameters of the observation model and for hyper-parameters in a hyper-study or change-point study." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": { 16 | "collapsed": false 17 | }, 18 | "outputs": [ 19 | { 20 | "name": "stdout", 21 | "output_type": "stream", 22 | "text": [ 23 | "+ Created new study.\n", 24 | "+ Successfully imported example data.\n" 25 | ] 26 | } 27 | ], 28 | "source": [ 29 | "%matplotlib inline\n", 30 | "import matplotlib.pyplot as plt # plotting\n", 31 | "import seaborn as sns # nicer plots\n", 32 | "sns.set_style('whitegrid') # plot styling\n", 33 | "\n", 34 | "import numpy as np\n", 35 | "import bayesloop as bl\n", 36 | "\n", 37 | "# prepare study for coal mining data\n", 38 | "S = bl.Study()\n", 39 | "S.loadExampleData()" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "## Parameter prior\n", 47 | "\n", 48 | "*bayesloop* employs a forward-backward algorithm that is based on [Hidden Markov models](http://www.cs.sjsu.edu/~stamp/RUA/HMM.pdf). This inference algorithm iteratively produces a parameter distribution for each time step, but it has to start these iterations from a specified probability distribution - the parameter prior. All built-in observation models already have a predefined prior, stored in the attribute `prior`. Here, the prior distribution is stored as a Python function that takes as many arguments as there are parameters in the observation model. The prior distributions can be looked up directly within `observationModels.py`. For the `Poisson` model discussed in this tutorial, the default prior distribution is defined in a method called `jeffreys` as\n", 49 | "```\n", 50 | "def jeffreys(x):\n", 51 | " return np.sqrt(1. / x)\n", 52 | "```\n", 53 | "corresponding to the non-informative Jeffreys prior, $p(\\lambda) \\propto 1/\\sqrt{\\lambda}$. This type of prior can also be determined automatically for arbitrary user-defined observation models, see [here](customobservationmodels.html#Sympy.stats-random-variables).\n", 54 | "\n", 55 | "### Prior functions and arrays\n", 56 | "\n", 57 | "To change the predefined prior of a given observation model, one can add the keyword argument `prior` when defining an observation model. There are different ways of defining a parameter prior in *bayesloop*: If `prior=None` is set, *bayesloop* will assign equal probability to all parameter values, resulting in a uniform prior distribution within the specified parameter boundaries. One can also directly supply a Numpy array with prior probability (density) values. The shape of the array must match the shape of the parameter grid! Another way to define a custom prior is to provide a function that takes exactly as many arguments as there are parameters in the defined observation model. *bayesloop* will then evaluate the function for all parameter values and assign the corresponding probability values.\n", 58 | "\n", 59 | "
\n", 60 | "**Note:** In all of the cases described above, *bayesloop* will re-normalize the provided prior values, so they do not need to be passed in a normalized form. Below, we describe the possibility of using probability distributions from the SymPy stats module as prior distributions, which are not re-normalized by *bayesloop*.\n", 61 | "
\n", 62 | "\n", 63 | "Next, we illustrate the difference between the Jeffreys prior and a flat, uniform prior with a very simple inference example: We fit the coal mining example data set using the `Poisson` observation model and further assume the rate parameter to be static:" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 2, 69 | "metadata": { 70 | "collapsed": false 71 | }, 72 | "outputs": [ 73 | { 74 | "name": "stdout", 75 | "output_type": "stream", 76 | "text": [ 77 | "+ Transition model: Static/constant parameter values. Hyper-Parameter(s): []\n", 78 | "Fit with built-in Jeffreys prior:\n", 79 | "+ Observation model: Poisson. Parameter(s): ['accident_rate']\n", 80 | "+ Started new fit:\n", 81 | " + Formatted data.\n", 82 | " + Set prior (function): jeffreys. Values have been re-normalized.\n", 83 | "\n", 84 | " + Finished forward pass.\n", 85 | " + Log10-evidence: -88.00564\n", 86 | "\n", 87 | " + Finished backward pass.\n", 88 | " + Computed mean parameter values.\n", 89 | "-----\n", 90 | "\n", 91 | "Fit with custom flat prior:\n", 92 | "+ Observation model: Poisson. Parameter(s): ['accident_rate']\n", 93 | "+ Started new fit:\n", 94 | " + Formatted data.\n", 95 | " + Set prior (function): . Values have been re-normalized.\n", 96 | "\n", 97 | " + Finished forward pass.\n", 98 | " + Log10-evidence: -87.98915\n", 99 | "\n", 100 | " + Finished backward pass.\n", 101 | " + Computed mean parameter values.\n" 102 | ] 103 | } 104 | ], 105 | "source": [ 106 | "# we assume a static rate parameter for simplicity\n", 107 | "S.set(bl.tm.Static())\n", 108 | "\n", 109 | "print 'Fit with built-in Jeffreys prior:'\n", 110 | "S.set(bl.om.Poisson('accident_rate', bl.oint(0, 6, 1000)))\n", 111 | "S.fit()\n", 112 | "jeffreys_mean = S.getParameterMeanValues('accident_rate')[0]\n", 113 | "print('-----\\n')\n", 114 | " \n", 115 | "print 'Fit with custom flat prior:'\n", 116 | "S.set(bl.om.Poisson('accident_rate', bl.oint(0, 6, 1000), \n", 117 | " prior=lambda x: 1.))\n", 118 | "# alternatives: prior=None, prior=np.ones(1000)\n", 119 | "S.fit()\n", 120 | "flat_mean = S.getParameterMeanValues('accident_rate')[0]" 121 | ] 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "metadata": {}, 126 | "source": [ 127 | "First note that the model evidence indeed slightly changes due to the different choices of the parameter prior. Second, one may notice that the posterior mean value of the flat-prior-fit does not exactly match the arithmetic mean of the data. This small deviation shows that a flat/uniform prior is not completely non-informative for a Poisson model! The fit using the Jeffreys prior, however, succeeds in reproducing the *frequentist* estimate, i.e. the arithmetic mean:" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 3, 133 | "metadata": { 134 | "collapsed": false 135 | }, 136 | "outputs": [ 137 | { 138 | "name": "stdout", 139 | "output_type": "stream", 140 | "text": [ 141 | "arithmetic mean = 1.69090909091\n", 142 | "flat-prior mean = 1.7\n", 143 | "Jeffreys prior mean = 1.69090909091\n" 144 | ] 145 | } 146 | ], 147 | "source": [ 148 | "print('arithmetic mean = {}'.format(np.mean(S.rawData)))\n", 149 | "print('flat-prior mean = {}'.format(flat_mean))\n", 150 | "print('Jeffreys prior mean = {}'.format(jeffreys_mean))" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "metadata": {}, 156 | "source": [ 157 | "### SymPy prior\n", 158 | "\n", 159 | "The second option is based on the [SymPy](http://www.sympy.org/en/index.html) module that introduces symbolic mathematics to Python. Its sub-module [sympy.stats](http://docs.sympy.org/dev/modules/stats.html) covers a wide range of discrete and continuous random variables. The keyword argument `prior` also accepts a list of `sympy.stats` random variables, one for each parameter (if there is only one parameter, the list can be omitted). The multiplicative joint probability density of these random variables is then used as the prior distribution. The following example defines an exponential prior for the `Poisson` model, favoring small values of the rate parameter: " 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": 4, 165 | "metadata": { 166 | "collapsed": false 167 | }, 168 | "outputs": [ 169 | { 170 | "name": "stdout", 171 | "output_type": "stream", 172 | "text": [ 173 | "+ Observation model: Poisson. Parameter(s): ['accident_rate']\n", 174 | "+ Started new fit:\n", 175 | " + Formatted data.\n", 176 | " + Set prior (sympy): exp(-x)\n", 177 | "\n", 178 | " + Finished forward pass.\n", 179 | " + Log10-evidence: -87.94640\n", 180 | "\n", 181 | " + Finished backward pass.\n", 182 | " + Computed mean parameter values.\n" 183 | ] 184 | } 185 | ], 186 | "source": [ 187 | "import sympy.stats\n", 188 | "S.set(bl.om.Poisson('accident_rate', bl.oint(0, 6, 1000), \n", 189 | " prior=sympy.stats.Exponential('expon', 1)))\n", 190 | "S.fit()" 191 | ] 192 | }, 193 | { 194 | "cell_type": "markdown", 195 | "metadata": {}, 196 | "source": [ 197 | "Note that one needs to assign a name to each `sympy.stats` variable. In this case, the output of *bayesloop* shows the mathematical formula that defines the prior. This is possible because of the symbolic representation of the prior by `SymPy`.\n", 198 | "\n", 199 | "
\n", 200 | "**Note:** The support interval of a prior distribution defined via SymPy can deviate from the parameter interval specified in *bayesloop*. In the example above, we specified the parameter interval ]0, 6[, while the exponential prior has the support ]0, $\\infty$[. SymPy priors are not re-normalized with respect to the specified parameter interval. Be aware that the resulting model evidence value will only be correct if no parameter values outside of the parameter boundaries gain significant probability values. In most cases, one can simply check whether the parameter distribution has sufficiently *fallen off* at the parameter boundaries.\n", 201 | "
\n", 202 | "\n", 203 | "## Hyper-parameter priors\n", 204 | "\n", 205 | "As shown before, [hyper-studies](hyperstudy.html) and [change-point studies](changepointstudy.html) can be used to determine the full distribution of hyper-parameters (the parameters of the transition model). As for the time-varying parameters of the observation model, one might have prior knowledge about the values of certain hyper-parameters that can be included into the study to refine the resulting distribution of these hyper-parameters. Hyper-parameter priors can be defined just as regular priors, either by an arbitrary function or by a list of `sympy.stats` random variables.\n", 206 | "\n", 207 | "In a first example, we return to the simple change-point model of the coal-mining data set and perform to fits of the change-point: first, we specify no hyper-prior for the time step of our change-point, assuming equal probability for each year in our data set. Second, we define a Normal distribution around the year 1920 with a (rather unrealistic) standard deviation of 5 years as the hyper-prior using a SymPy random variable. For both fits, we plot the change-point distribution to show the differences induced by the different priors:" 208 | ] 209 | }, 210 | { 211 | "cell_type": "code", 212 | "execution_count": 5, 213 | "metadata": { 214 | "collapsed": false 215 | }, 216 | "outputs": [ 217 | { 218 | "name": "stdout", 219 | "output_type": "stream", 220 | "text": [ 221 | "Fit with flat hyper-prior:\n", 222 | "+ Created new study.\n", 223 | " --> Hyper-study\n", 224 | " --> Change-point analysis\n", 225 | "+ Successfully imported example data.\n", 226 | "+ Observation model: Poisson. Parameter(s): ['accident_rate']\n", 227 | "+ Transition model: Change-point. Hyper-Parameter(s): ['tChange']\n", 228 | "+ Detected 1 change-point(s) in transition model: ['tChange']\n", 229 | "+ Set hyper-prior(s): ['uniform']\n", 230 | "+ Started new fit.\n", 231 | " + 109 analyses to run.\n", 232 | "\n", 233 | " + Computed average posterior sequence\n", 234 | " + Computed hyper-parameter distribution\n", 235 | " + Log10-evidence of average model: -75.71555\n", 236 | " + Computed local evidence of average model\n", 237 | " + Computed mean parameter values.\n", 238 | "+ Finished fit.\n" 239 | ] 240 | }, 241 | { 242 | "data": { 243 | "image/png": "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\n", 244 | "text/plain": [ 245 | "" 246 | ] 247 | }, 248 | "metadata": {}, 249 | "output_type": "display_data" 250 | }, 251 | { 252 | "name": "stdout", 253 | "output_type": "stream", 254 | "text": [ 255 | "-----\n", 256 | "\n", 257 | "Fit with custom normal prior:\n", 258 | "+ Transition model: Change-point. Hyper-Parameter(s): ['tChange']\n", 259 | "+ Detected 1 change-point(s) in transition model: ['tChange']\n", 260 | "+ Set hyper-prior(s): ['sqrt(2)*exp(-(x - 1920)**2/50)/(10*sqrt(pi))']\n", 261 | "+ Started new fit.\n", 262 | " + 109 analyses to run.\n", 263 | "\n", 264 | " + Computed average posterior sequence\n", 265 | " + Computed hyper-parameter distribution\n", 266 | " + Log10-evidence of average model: -80.50692\n", 267 | " + Computed local evidence of average model\n", 268 | " + Computed mean parameter values.\n", 269 | "+ Finished fit.\n" 270 | ] 271 | }, 272 | { 273 | "data": { 274 | "image/png": "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\nzz5rPfbYY5ZlWdZLL71kLV68uPUG3sqiqVN1dbU1atQoq66uzqqqqrJGjx5tWZZZdbKs6GpVVlZm\nTZw40QoGg1Ztba01efJk68iRI0bV6tQ6TZkyxSopKbEsy7Iefvhha9OmTdb+/fut8ePHW0ePHrVq\namqs8ePHW8FgkDqdUifLsqy33nrLmjRpkpWenm7V1tZalsW/vZNr9cgjj1ibNm2yvvrqK2vq1Knh\nPtnZ2VZZWZlRtYrmM3XgwAFr/Pjx1rFjxyyfz2ddeeWVlmWd3meqXc8EpKSkhJOgJA0YMEAHDx6U\nZVny+/1yuX7c8DAQCOixxx4Lf5vzer0aOXKkJGnkyJF67733WnfwrSiaOsXFxSk5OVl+v1+HDx8O\n78JoUp2k6Gq1Z88eXXbZZeratavcbrdSUlK0e/duo2p1ap2+++47DRo0SJKUlpamHTt2aOfOnUpP\nT5fL5ZLH41Hfvn2p0yl1OrGRWZcuXfTcc88pISEh/FiT6iRFrtXgwYPl9Xp13nnn6emnnw4/5tix\nY4qJiTGqVtF8ps466yy9+OKLcjqd2r9/v2Jiju92ejp1atchYOzYserSpUv4dt++fZWXl6drrrlG\nBw4c0GWXXRZu+9e//qXf/e534X9kPp8vfHGiM888s94WxZ1NtHVKSkrS73//e02dOjU81WRSnaTo\napWamqodO3bo8OHDOnjwoEpKShQIBIyq1al16tOnj3bs2CFJKi4u1pEjRxq9/ofP55Pf76dOOl6n\nQOD4NrxXXHGFEhISZJ20LYtJnycpulp16dJFiYmJkqS//e1vuuiii5SSkmJUraL9TDmdTq1Zs0ZZ\nWVmaMGGCpNP7TNl67YCWlpeXp7Vr1+qCCy7QmjVr9MADD2j+/PmSpM2bN+uxxx4LP/bkaxCcelGi\nzq6xOg0fPlzff/+9iouLZVmWcnJyNHjwYMXHxxtbJ6npz9SMGTN04403qlevXho4cKDOOusso2u1\nZMkS5eXl6dixY0pPT1dMTIzi4+MbXP+jW7duRv/ba6xOJ3M4HOGfTa6T1HStgsGg5s2bp/j4eC1Y\nsECS2bWK9Jm69tprlZWVpRtvvFHbt28/rf+j2vVMwKkSExPDKScpKUnV1dWSjqefo0ePKikpKfzY\ntLQ0bdu2TZK0bds2DRkypPUH3EYaq1NCQoJiY2PDU9wn/gM3uU5S47U6ePCg/H6/1q5dq4ULF2rv\n3r1KTU3V4MGDja3Vtm3b9NBDD+nZZ5/VoUOHNGzYMP3qV7+S1+tVMBhUTU2NPvvsM/Xv3586nVKn\nk508E2D6v72manXLLbdowIABys3NDYcmk2vVWJ0+//xz3X777ZKOH2qKiYlRly5dTqtOHWomYNGi\nRbrzzjvlcrnkdru1aNEiSdLnn3+u5OTkeo+dPn267rnnHs2YMUNut1sPPfRQWwy5TTRWp/POO0+X\nXHKJpk2bJqfTqfT0dA0bNkxpaWnG1klqvFZnnXWW9uzZo4yMDLndbt19991yOBxGf6ZSUlJ03XXX\nKS4uTpdffnn4uOOsWbM0Y8YMWZalu+66S263mzo1UqcTTp4JMLlOUuO12rJli3bs2KGjR49q27Zt\ncjgcmjt3rtG1auozdeGFFyorK0sOh0MjR47UkCFDdMkll/zkOnHtAAAADNWhDgcAAICWQwgAAMBQ\nhAAAAAxFCAAAwFCEAAAADEUIAADAUIQAwBA+n0+33nqrJOno0aN65JFH9Ic//EGTJ09WdnZ2eJ/x\niooKjRkzpi2HCqCVdKjNggCcvkOHDmn37t2SpL/85S+KjY3Vhg0b5Ha7VV5erjlz5mjFihWKjY2t\nt6kNgM6LzYIAQ9xyyy16++23deWVV+rdd9/Ve++9V28f8g8++EDJycmyLEvTpk3T0KFDVV5eroSE\nBD3++ONKSEjQ6tWrtWnTJgUCATmdTj388MM6//zzNWbMGE2cOFFvv/22jhw5Er74S3l5uebNm6dQ\nKKT09HS9+eabevXVV/XDDz9o/vz52rt3r5xOp+666y5dccUVbVgdwEwcDgAMcd999+mcc87RhAkT\n1L9//wYXt7n00kt13nnnSZIOHDigG264QZs3b1b37t310ksvyefzaevWrVq9erU2b96sq666SmvX\nrg337969u9avX6+srCw9+eSTko7PONx555164YUX1Lt3bx07dkzS8Qs3ZWRkaMOGDSooKND8+fN1\n+PDhVqoEgBM4HAAYxul0KhQKRXxMUlKSLrnkEklS//79dfDgQXk8Hi1dulRFRUX64osv9NZbb2nA\ngAHhPsOHDw8//rXXXlNVVZUqKio0YsQISVJGRoZWrVolSXr33Xf1+eef6x//+Iek49eN/+qrr3Th\nhRe2+OsF0DRCAGCYSy65RHv27FEwGJTb7Q7fv2LFCvXs2VODBg2qdz1zh8Mhy7K0d+9ezZo1SzNn\nztTIkSPVo0cPffLJJ+HHnZhZOPH4k5/jVKFQSCtWrFC3bt0kSfv27VPPnj1b+qUCaAaHAwBDuFwu\nHTt2TOdbGReuAAABRElEQVSee65Gjx6tRYsWKRgMSpI+/vhjPf3000pNTZVU/5K3J3z44YfhK5oN\nHDhQb775ZsQZBY/Ho5SUFL311luSpE2bNoUXHA4dOlRr1qyRJP3vf//ThAkTFAgEWvT1AmgeMwGA\nIc4++2yde+65uu6667R8+XL9/e9/18SJExUTE6PY2FgtXbpUv/jFL1RRUdHo2QHDhw/XunXrdM01\n1ygmJkYDBw7Up59+KklNnk2Qn5+ve++9Vw8//LB++ctfKjY2VtLx9Qnz58/XhAkTJElLly7VGWec\nYdMrB9AUzg4AYJvHH39cWVlZ6tGjh1577TVt3rxZjz76aFsPC8D/x0wAANucd955uuGGG+RyuZSQ\nkKC8vLy2HhKAkzATAACAoVgYCACAoQgBAAAYihAAAIChCAEAABiKEAAAgKEIAQAAGOr/AY0EBcdb\nKFqMAAAAAElFTkSuQmCC\n", 275 | "text/plain": [ 276 | "" 277 | ] 278 | }, 279 | "metadata": {}, 280 | "output_type": "display_data" 281 | } 282 | ], 283 | "source": [ 284 | "print 'Fit with flat hyper-prior:'\n", 285 | "S = bl.ChangepointStudy()\n", 286 | "S.loadExampleData()\n", 287 | "\n", 288 | "L = bl.om.Poisson('accident_rate', bl.oint(0, 6, 1000))\n", 289 | "T = bl.tm.ChangePoint('tChange', 'all')\n", 290 | "\n", 291 | "S.set(L, T)\n", 292 | "S.fit()\n", 293 | "\n", 294 | "plt.figure(figsize=(8,4))\n", 295 | "S.plot('tChange', facecolor='g', alpha=0.7)\n", 296 | "plt.xlim([1870, 1930])\n", 297 | "plt.show()\n", 298 | "print('-----\\n')\n", 299 | " \n", 300 | "print 'Fit with custom normal prior:'\n", 301 | "T = bl.tm.ChangePoint('tChange', 'all', prior=sympy.stats.Normal('norm', 1920, 5))\n", 302 | "S.set(T)\n", 303 | "S.fit()\n", 304 | "\n", 305 | "plt.figure(figsize=(8,4))\n", 306 | "S.plot('tChange', facecolor='g', alpha=0.7)\n", 307 | "plt.xlim([1870, 1930]);" 308 | ] 309 | }, 310 | { 311 | "cell_type": "markdown", 312 | "metadata": {}, 313 | "source": [ 314 | "Since we used a quite narrow prior (containing a lot of information) in the second case, the resulting distribution is strongly shifted towards the prior. The following example revisits the two break-point-model from [here](changepointstudy.html#Analyzing-structural-breaks-in-time-series-models) and a linear decrease with a varying slope as a hyper-parameter. Here, we define a Gaussian prior for the slope hyper-parameter, which is centered around the value -0.2 with a standard deviation of 0.4, via a lambda-function. For simplification, we set the break-points to fixed years." 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": 6, 320 | "metadata": { 321 | "collapsed": false 322 | }, 323 | "outputs": [ 324 | { 325 | "name": "stdout", 326 | "output_type": "stream", 327 | "text": [ 328 | "+ Created new study.\n", 329 | " --> Hyper-study\n", 330 | "+ Successfully imported example data.\n", 331 | "+ Observation model: Poisson. Parameter(s): ['accident_rate']\n", 332 | "+ Transition model: Serial transition model. Hyper-Parameter(s): ['slope', 't_1', 't_2']\n", 333 | "+ Set hyper-prior(s): [' (re-normalized)', 'uniform', 'uniform']\n", 334 | "+ Started new fit.\n", 335 | " + 30 analyses to run.\n", 336 | "\n", 337 | " + Computed average posterior sequence\n", 338 | " + Computed hyper-parameter distribution\n", 339 | " + Log10-evidence of average model: -74.84129\n", 340 | " + Computed local evidence of average model\n", 341 | " + Computed mean parameter values.\n", 342 | "+ Finished fit.\n" 343 | ] 344 | } 345 | ], 346 | "source": [ 347 | "S = bl.HyperStudy()\n", 348 | "S.loadExampleData()\n", 349 | "\n", 350 | "L = bl.om.Poisson('accident_rate', bl.oint(0, 6, 1000))\n", 351 | "T = bl.tm.SerialTransitionModel(bl.tm.Static(),\n", 352 | " bl.tm.BreakPoint('t_1', 1880),\n", 353 | " bl.tm.Deterministic(lambda t, slope=np.linspace(-2.0, 0.0, 30): t*slope, \n", 354 | " target='accident_rate',\n", 355 | " prior=lambda slope: np.exp(-0.5*((slope + 0.2)/(2*0.4))**2)/0.4),\n", 356 | " bl.tm.BreakPoint('t_2', 1900),\n", 357 | " bl.tm.Static()\n", 358 | " )\n", 359 | "\n", 360 | "S.set(L, T)\n", 361 | "S.fit()" 362 | ] 363 | }, 364 | { 365 | "cell_type": "markdown", 366 | "metadata": {}, 367 | "source": [ 368 | "Finally, note that you can mix SymPy- and function-based hyper-priors for nested transition models." 369 | ] 370 | } 371 | ], 372 | "metadata": { 373 | "anaconda-cloud": {}, 374 | "kernelspec": { 375 | "display_name": "Python 2", 376 | "language": "python", 377 | "name": "python2" 378 | }, 379 | "language_info": { 380 | "codemirror_mode": { 381 | "name": "ipython", 382 | "version": 2 383 | }, 384 | "file_extension": ".py", 385 | "mimetype": "text/x-python", 386 | "name": "python", 387 | "nbconvert_exporter": "python", 388 | "pygments_lexer": "ipython2", 389 | "version": "2.7.10" 390 | } 391 | }, 392 | "nbformat": 4, 393 | "nbformat_minor": 0 394 | } 395 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | long_description = file: README.md 3 | long_description_content_type = text/markdown 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | setup( 6 | name='bayesloop', 7 | packages=['bayesloop'], 8 | version='1.5.7', 9 | description='Probabilistic programming framework that facilitates objective model selection for time-varying parameter models.', 10 | url='http://bayesloop.com', 11 | download_url = 'https://github.com/christophmark/bayesloop/tarball/1.5.7', 12 | author='Christoph Mark', 13 | author_email='christoph.mark@fau.de', 14 | license='The MIT License (MIT)', 15 | install_requires=['numpy>=1.11.0', 'scipy>=0.17.1', 'sympy>=1.0', 'matplotlib>=1.5.1', 'tqdm>=4.10.0', 'cloudpickle'], 16 | keywords=['bayes', 'inference', 'fitting', 'model selection', 'hypothesis testing', 'time series', 'time-varying', 'marginal likelihood'], 17 | classifiers=[], 18 | ) 19 | -------------------------------------------------------------------------------- /tests/test_changepointstudy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function, division 4 | import bayesloop as bl 5 | import numpy as np 6 | import sympy.stats as stats 7 | 8 | 9 | class TestTwoParameterModel: 10 | def test_fit_1cp_1bp_2hp(self): 11 | # carry out fit 12 | S = bl.ChangepointStudy() 13 | S.loadData(np.array([1, 2, 3, 4, 5])) 14 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1/s**3)) 15 | 16 | T = bl.tm.SerialTransitionModel( 17 | bl.tm.Static(), 18 | bl.tm.ChangePoint('ChangePoint', [0, 1]), 19 | bl.tm.CombinedTransitionModel( 20 | bl.tm.GaussianRandomWalk('sigma', bl.cint(0, 0.2, 2), target='mean'), 21 | bl.tm.RegimeSwitch('log10pMin', [-3, -1]) 22 | ), 23 | bl.tm.BreakPoint('BreakPoint', 'all'), 24 | bl.tm.Static() 25 | ) 26 | 27 | S.setTM(T) 28 | S.fit() 29 | 30 | # test parameter distributions 31 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 32 | [0.012437, 0.030168, 0.01761 , 0.001731, 0.001731], 33 | rtol=1e-02, err_msg='Erroneous posterior distribution values.') 34 | 35 | # test parameter mean values 36 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 37 | [0.968022, 1.956517, 3.476958, 4.161028, 4.161028], 38 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 39 | 40 | # test model evidence value 41 | np.testing.assert_almost_equal(S.logEvidence, -15.072007461556161, decimal=5, 42 | err_msg='Erroneous log-evidence value.') 43 | 44 | # test hyper-parameter distribution 45 | x, p = S.getHyperParameterDistribution('sigma') 46 | np.testing.assert_allclose(np.array([x, p]), 47 | [[0., 0.2], [0.4963324, 0.5036676]], 48 | rtol=1e-02, err_msg='Erroneous values in hyper-parameter distribution.') 49 | 50 | # test duration distribution 51 | d, p = S.getDurationDistribution(['ChangePoint', 'BreakPoint']) 52 | np.testing.assert_allclose(np.array([d, p]), 53 | [[1., 2., 3.], [0.01039273, 0.49395867, 0.49564861]], 54 | rtol=1e-02, err_msg='Erroneous values in duration distribution.') 55 | 56 | def test_fit_hyperpriors(self): 57 | # carry out fit 58 | S = bl.ChangepointStudy() 59 | S.loadData(np.array([1, 2, 3, 4, 5])) 60 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1/s**3)) 61 | 62 | T = bl.tm.SerialTransitionModel( 63 | bl.tm.Static(), 64 | bl.tm.ChangePoint('ChangePoint', [0, 1], prior=np.array([0.3, 0.7])), 65 | bl.tm.CombinedTransitionModel( 66 | bl.tm.GaussianRandomWalk('sigma', bl.oint(0, 0.2, 2), target='mean', prior=lambda s: 1./s), 67 | bl.tm.RegimeSwitch('log10pMin', [-3, -1]) 68 | ), 69 | bl.tm.BreakPoint('BreakPoint', 'all', prior=stats.Normal('Normal', 3., 1.)), 70 | bl.tm.Static() 71 | ) 72 | 73 | S.setTM(T) 74 | S.fit() 75 | 76 | # test parameter distributions 77 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 78 | [0.033729, 0.050869, 0.020636, 0.001647, 0.001647], 79 | rtol=1e-02, err_msg='Erroneous posterior distribution values.') 80 | 81 | # test parameter mean values 82 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 83 | [0.98944 , 1.927195, 3.349921, 4.213695, 4.213695], 84 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 85 | 86 | # test model evidence value 87 | np.testing.assert_almost_equal(S.logEvidence, -15.709534690217343, decimal=5, 88 | err_msg='Erroneous log-evidence value.') 89 | 90 | # test hyper-parameter distribution 91 | x, p = S.getHyperParameterDistribution('sigma') 92 | np.testing.assert_allclose(np.array([x, p]), 93 | [[0.06666667, 0.13333333], [0.66515107, 0.33484893]], 94 | rtol=1e-02, err_msg='Erroneous values in hyper-parameter distribution.') 95 | 96 | # test duration distribution 97 | d, p = S.getDurationDistribution(['ChangePoint', 'BreakPoint']) 98 | np.testing.assert_allclose(np.array([d, p]), 99 | [[1., 2., 3.], [0.00373717, 0.40402616, 0.59223667]], 100 | rtol=1e-02, err_msg='Erroneous values in duration distribution.') 101 | -------------------------------------------------------------------------------- /tests/test_fileio.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function, division 4 | import bayesloop as bl 5 | import numpy as np 6 | 7 | 8 | class TestFileIO: 9 | def test_save_load(self): 10 | S = bl.HyperStudy() 11 | S.loadData(np.array([1, 2, 3, 4, 5])) 12 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1/s**3)) 13 | S.setTM(bl.tm.Static()) 14 | S.fit() 15 | 16 | bl.save('study.bl', S) 17 | S = bl.load('study.bl') 18 | -------------------------------------------------------------------------------- /tests/test_hyperstudy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function, division 4 | import bayesloop as bl 5 | import numpy as np 6 | import sympy.stats as stats 7 | 8 | 9 | class TestTwoParameterModel: 10 | def test_fit_0hp(self): 11 | # carry out fit (this test is designed to fall back on the fit method of the Study class) 12 | S = bl.HyperStudy() 13 | S.loadData(np.array([1, 2, 3, 4, 5])) 14 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1/s**3)) 15 | S.setTM(bl.tm.Static()) 16 | S.fit() 17 | 18 | # test parameter distributions 19 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 20 | [0.013349, 0.013349, 0.013349, 0.013349, 0.013349], 21 | rtol=1e-04, err_msg='Erroneous posterior distribution values.') 22 | 23 | # test parameter mean values 24 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 25 | [3., 3., 3., 3., 3.], 26 | rtol=1e-05, err_msg='Erroneous posterior mean values.') 27 | 28 | # test model evidence value 29 | np.testing.assert_almost_equal(S.logEvidence, -16.1946904707, decimal=5, 30 | err_msg='Erroneous log-evidence value.') 31 | 32 | def test_fit_1hp(self): 33 | # carry out fit 34 | S = bl.HyperStudy() 35 | S.loadData(np.array([1, 2, 3, 4, 5])) 36 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1/s**3)) 37 | S.setTM(bl.tm.GaussianRandomWalk('sigma', bl.cint(0, 0.2, 2), target='mean')) 38 | S.fit() 39 | 40 | # test parameter distributions 41 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 42 | [0.017242, 0.014581, 0.012691, 0.011705, 0.011586], 43 | rtol=1e-04, err_msg='Erroneous posterior distribution values.') 44 | 45 | # test parameter mean values 46 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 47 | [2.92089 , 2.952597, 3. , 3.047403, 3.07911 ], 48 | rtol=1e-05, err_msg='Erroneous posterior mean values.') 49 | 50 | # test model evidence value 51 | np.testing.assert_almost_equal(S.logEvidence, -16.0629517262, decimal=5, 52 | err_msg='Erroneous log-evidence value.') 53 | 54 | # test hyper-parameter distribution 55 | x, p = S.getHyperParameterDistribution('sigma') 56 | print(np.array([x, p])) 57 | np.testing.assert_allclose(np.array([x, p]), 58 | [[0., 0.2], [0.43828499, 0.56171501]], 59 | rtol=1e-05, err_msg='Erroneous values in hyper-parameter distribution.') 60 | 61 | def test_fit_2hp(self): 62 | # carry out fit 63 | S = bl.HyperStudy() 64 | S.loadData(np.array([1, 2, 3, 4, 5])) 65 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1/s**3)) 66 | 67 | T = bl.tm.CombinedTransitionModel(bl.tm.GaussianRandomWalk('sigma', bl.cint(0, 0.2, 2), target='mean'), 68 | bl.tm.RegimeSwitch('log10pMin', [-3, -1])) 69 | 70 | S.setTM(T) 71 | S.fit() 72 | 73 | # test parameter distributions 74 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 75 | [0.005589, 0.112966, 0.04335 , 0.00976 , 0.002909], 76 | rtol=1e-04, err_msg='Erroneous posterior distribution values.') 77 | 78 | # test parameter mean values 79 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 80 | [0.963756, 2.105838, 2.837739, 3.734359, 4.595412], 81 | rtol=1e-05, err_msg='Erroneous posterior mean values.') 82 | 83 | # test model evidence value 84 | np.testing.assert_almost_equal(S.logEvidence, -10.7601875492, decimal=5, 85 | err_msg='Erroneous log-evidence value.') 86 | 87 | # test hyper-parameter distribution 88 | x, p = S.getHyperParameterDistribution('sigma') 89 | np.testing.assert_allclose(np.array([x, p]), 90 | [[0., 0.2], [0.48943645, 0.51056355]], 91 | rtol=1e-05, err_msg='Erroneous values in hyper-parameter distribution.') 92 | 93 | # test joint hyper-parameter distribution 94 | x, y, p = S.getJointHyperParameterDistribution(['log10pMin', 'sigma']) 95 | np.testing.assert_allclose(np.array([x, y]), 96 | [[-3., -1.], [0., 0.2]], 97 | rtol=1e-05, err_msg='Erroneous parameter values in joint hyper-parameter ' 98 | 'distribution.') 99 | 100 | np.testing.assert_allclose(p, 101 | [[0.00701834, 0.0075608], [0.48241812, 0.50300274]], 102 | rtol=1e-05, err_msg='Erroneous probability values in joint hyper-parameter ' 103 | 'distribution.') 104 | 105 | def test_fit_hyperprior_array(self): 106 | # carry out fit 107 | S = bl.HyperStudy() 108 | S.loadData(np.array([1, 2, 3, 4, 5])) 109 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1/s**3)) 110 | S.setTM(bl.tm.GaussianRandomWalk('sigma', bl.cint(0, 0.2, 2), target='mean', prior=np.array([0.2, 0.8]))) 111 | S.fit() 112 | 113 | # test parameter distributions 114 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 115 | [0.019149, 0.015184, 0.012369, 0.0109 , 0.010722], 116 | rtol=1e-04, err_msg='Erroneous posterior distribution values.') 117 | 118 | # test parameter mean values 119 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 120 | [2.882151, 2.929385, 3. , 3.070615, 3.117849], 121 | rtol=1e-04, err_msg='Erroneous posterior mean values.') 122 | 123 | # test model evidence value 124 | np.testing.assert_almost_equal(S.logEvidence, -15.9915077133, decimal=5, 125 | err_msg='Erroneous log-evidence value.') 126 | 127 | # test hyper-parameter distribution 128 | x, p = S.getHyperParameterDistribution('sigma') 129 | np.testing.assert_allclose(np.array([x, p]), 130 | [[0., 0.2], [0.16322581, 0.83677419]], 131 | rtol=1e-05, err_msg='Erroneous values in hyper-parameter distribution.') 132 | 133 | def test_fit_hyperprior_function(self): 134 | # carry out fit 135 | S = bl.HyperStudy() 136 | S.loadData(np.array([1, 2, 3, 4, 5])) 137 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1/s**3)) 138 | S.setTM(bl.tm.GaussianRandomWalk('sigma', bl.cint(0.1, 0.3, 2), target='mean', prior=lambda s: 1./s)) 139 | S.fit() 140 | 141 | # test parameter distributions 142 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 143 | [0.025476, 0.015577, 0.012088, 0.010889, 0.010749], 144 | rtol=1e-04, err_msg='Erroneous posterior distribution values.') 145 | 146 | # test parameter mean values 147 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 148 | [2.858477, 2.915795, 3. , 3.084205, 3.141523], 149 | rtol=1e-04, err_msg='Erroneous posterior mean values.') 150 | 151 | # test model evidence value 152 | np.testing.assert_almost_equal(S.logEvidence, -15.9898700147, decimal=5, 153 | err_msg='Erroneous log-evidence value.') 154 | 155 | # test hyper-parameter distribution 156 | x, p = S.getHyperParameterDistribution('sigma') 157 | np.testing.assert_allclose(np.array([x, p]), 158 | [[0.1, 0.3], [0.61609973, 0.38390027]], 159 | rtol=1e-05, err_msg='Erroneous values in hyper-parameter distribution.') 160 | 161 | def test_fit_hyperprior_sympy(self): 162 | # carry out fit 163 | S = bl.HyperStudy() 164 | S.loadData(np.array([1, 2, 3, 4, 5])) 165 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1/s**3)) 166 | S.setTM(bl.tm.GaussianRandomWalk('sigma', bl.cint(0, 0.2, 2), target='mean', prior=stats.Exponential('e', 1.))) 167 | S.fit() 168 | 169 | # test parameter distributions 170 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 171 | [0.016898, 0.014472, 0.012749, 0.011851, 0.011742], 172 | rtol=1e-04, err_msg='Erroneous posterior distribution values.') 173 | 174 | # test parameter mean values 175 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 176 | [2.927888, 2.95679 , 3. , 3.04321 , 3.072112], 177 | rtol=1e-04, err_msg='Erroneous posterior mean values.') 178 | 179 | # test model evidence value 180 | np.testing.assert_almost_equal(S.logEvidence, -17.0866290887, decimal=5, 181 | err_msg='Erroneous log-evidence value.') 182 | 183 | # test hyper-parameter distribution 184 | x, p = S.getHyperParameterDistribution('sigma') 185 | np.testing.assert_allclose(np.array([x, p]), 186 | [[0., 0.2], [0.487971, 0.512029]], 187 | rtol=1e-05, err_msg='Erroneous values in hyper-parameter distribution.') 188 | -------------------------------------------------------------------------------- /tests/test_observationmodels.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function, division 4 | import bayesloop as bl 5 | import numpy as np 6 | import scipy.stats 7 | import sympy.stats 8 | from sympy import Symbol 9 | 10 | 11 | class TestSymPy: 12 | def test_sympy_1p(self): 13 | # carry out fit 14 | S = bl.Study() 15 | S.loadData(np.array([1, 2, 3, 4, 5])) 16 | 17 | rate = Symbol('rate', positive=True) 18 | poisson = sympy.stats.Poisson('poisson', rate) 19 | L = bl.om.SymPy(poisson, 'rate', bl.oint(0, 7, 100)) 20 | 21 | S.setOM(L) 22 | S.setTM(bl.tm.Static()) 23 | S.fit() 24 | 25 | # test model evidence value 26 | np.testing.assert_almost_equal(S.logEvidence, -10.238278174965238, decimal=5, 27 | err_msg='Erroneous log-evidence value.') 28 | 29 | def test_sympy_2p(self): 30 | # carry out fit 31 | S = bl.Study() 32 | S.loadData(np.array([1, 2, 3, 4, 5])) 33 | 34 | mu = Symbol('mu') 35 | std = Symbol('std', positive=True) 36 | normal = sympy.stats.Normal('norm', mu, std) 37 | 38 | L = bl.om.SymPy(normal, 'mu', bl.cint(0, 7, 200), 'std', bl.oint(0, 1, 200), prior=lambda x, y: 1.) 39 | 40 | S.setOM(L) 41 | S.setTM(bl.tm.Static()) 42 | S.fit() 43 | 44 | # test model evidence value 45 | np.testing.assert_almost_equal(S.logEvidence, -13.663836264357226, decimal=5, 46 | err_msg='Erroneous log-evidence value.') 47 | 48 | 49 | class TestSciPy: 50 | def test_scipy_1p(self): 51 | # carry out fit 52 | S = bl.Study() 53 | S.loadData(np.array([1, 2, 3, 4, 5])) 54 | 55 | L = bl.om.SciPy(scipy.stats.poisson, 'mu', bl.oint(0, 7, 100), fixedParameters={'loc': 0}) 56 | 57 | S.setOM(L) 58 | S.setTM(bl.tm.Static()) 59 | S.fit() 60 | 61 | # test model evidence value 62 | np.testing.assert_almost_equal(S.logEvidence, -10.238278174965238, decimal=5, 63 | err_msg='Erroneous log-evidence value.') 64 | 65 | def test_scipy_2p(self): 66 | # carry out fit 67 | S = bl.Study() 68 | S.loadData(np.array([1, 2, 3, 4, 5])) 69 | 70 | L = bl.om.SciPy(scipy.stats.norm, 'loc', bl.cint(0, 7, 200), 'scale', bl.oint(0, 1, 200)) 71 | 72 | S.setOM(L) 73 | S.setTM(bl.tm.Static()) 74 | S.fit() 75 | 76 | # test model evidence value 77 | np.testing.assert_almost_equal(S.logEvidence, -13.663836264357225, decimal=5, 78 | err_msg='Erroneous log-evidence value.') 79 | 80 | 81 | class TestNumPy: 82 | def test_numpy_1p(self): 83 | # carry out fit 84 | S = bl.Study() 85 | S.loadData(np.array([[1, 0.5], [2, 0.5], [3, 0.5], [4, 1.], [5, 1.]])) 86 | 87 | def likelihood(data, mu): 88 | x, std = data 89 | 90 | pdf = np.exp((x - mu) ** 2. / (2 * std ** 2.)) / np.sqrt(2 * np.pi * std ** 2.) 91 | return pdf 92 | 93 | L = bl.om.NumPy(likelihood, 'mu', bl.oint(0, 7, 100)) 94 | 95 | S.setOM(L) 96 | S.setTM(bl.tm.Static()) 97 | S.fit() 98 | 99 | # test model evidence value 100 | np.testing.assert_almost_equal(S.logEvidence, 148.92056578058387, decimal=5, 101 | err_msg='Erroneous log-evidence value.') 102 | 103 | def test_scipy_2p(self): 104 | # carry out fit 105 | S = bl.Study() 106 | S.loadData(np.array([1, 2, 3, 4, 5])) 107 | 108 | def likelihood(data, mu, std): 109 | x = data 110 | 111 | pdf = np.exp((x - mu) ** 2. / (2 * std ** 2.)) / np.sqrt(2 * np.pi * std ** 2.) 112 | return pdf 113 | 114 | L = bl.om.NumPy(likelihood, 'mu', bl.oint(0, 7, 100), 'std', bl.oint(1, 2, 100)) 115 | 116 | S.setOM(L) 117 | S.setTM(bl.tm.Static()) 118 | S.fit() 119 | 120 | # test model evidence value 121 | np.testing.assert_almost_equal(S.logEvidence, 29.792823521784587, decimal=5, 122 | err_msg='Erroneous log-evidence value.') 123 | 124 | 125 | class TestBuiltin: 126 | def test_bernoulli(self): 127 | S = bl.Study() 128 | S.loadData(np.array([1, 0, 1, 0, 0])) 129 | 130 | L = bl.om.Bernoulli('p', bl.oint(0, 1, 100)) 131 | T = bl.tm.Static() 132 | S.set(L, T) 133 | 134 | S.fit() 135 | np.testing.assert_almost_equal(S.logEvidence, -4.3494298741972859, decimal=5, 136 | err_msg='Erroneous log-evidence value.') 137 | 138 | def test_poisson(self): 139 | S = bl.Study() 140 | S.loadData(np.array([1, 0, 1, 0, 0])) 141 | 142 | L = bl.om.Poisson('rate', bl.oint(0, 1, 100)) 143 | T = bl.tm.Static() 144 | S.set(L, T) 145 | 146 | S.fit() 147 | np.testing.assert_almost_equal(S.logEvidence, -4.433708287229158, decimal=5, 148 | err_msg='Erroneous log-evidence value.') 149 | 150 | def test_gaussian(self): 151 | S = bl.Study() 152 | S.loadData(np.array([1, 0, 1, 0, 0])) 153 | 154 | L = bl.om.Gaussian('mu', bl.oint(0, 1, 100), 'std', bl.oint(0, 1, 100), prior=lambda m, s: 1/s**3) 155 | T = bl.tm.Static() 156 | S.set(L, T) 157 | 158 | S.fit() 159 | np.testing.assert_almost_equal(S.logEvidence, -12.430583625665736, decimal=5, 160 | err_msg='Erroneous log-evidence value.') 161 | 162 | def test_laplace(self): 163 | S = bl.Study() 164 | S.load(np.array([1, 0, 1, 0, 0])) 165 | 166 | L = bl.om.Laplace('mu', None, 'b', None) 167 | T = bl.tm.Static() 168 | S.set(L, T) 169 | 170 | S.fit() 171 | np.testing.assert_almost_equal(S.logEvidence, -10.658573159, decimal=5, 172 | err_msg='Erroneous log-evidence value.') 173 | 174 | def test_gaussianmean(self): 175 | S = bl.Study() 176 | S.loadData(np.array([[1, 0.5], [0, 0.4], [1, 0.3], [0, 0.2], [0, 0.1]])) 177 | 178 | L = bl.om.GaussianMean('mu', bl.oint(0, 1, 100)) 179 | T = bl.tm.Static() 180 | S.set(L, T) 181 | 182 | S.fit() 183 | np.testing.assert_almost_equal(S.logEvidence, -6.3333705075036226, decimal=5, 184 | err_msg='Erroneous log-evidence value.') 185 | 186 | def test_whitenoise(self): 187 | S = bl.Study() 188 | S.loadData(np.array([1, 0, 1, 0, 0])) 189 | 190 | L = bl.om.WhiteNoise('std', bl.oint(0, 1, 100)) 191 | T = bl.tm.Static() 192 | S.set(L, T) 193 | 194 | S.fit() 195 | np.testing.assert_almost_equal(S.logEvidence, -6.8161638661444073, decimal=5, 196 | err_msg='Erroneous log-evidence value.') 197 | 198 | def test_ar1(self): 199 | S = bl.Study() 200 | S.loadData(np.array([1, 0, 1, 0, 0])) 201 | 202 | L = bl.om.AR1('rho', bl.oint(-1, 1, 100), 'sigma', bl.oint(0, 1, 100)) 203 | T = bl.tm.Static() 204 | S.set(L, T) 205 | 206 | S.fit() 207 | np.testing.assert_almost_equal(S.logEvidence, -4.3291291450463421, decimal=5, 208 | err_msg='Erroneous log-evidence value.') 209 | 210 | def test_scaledar1(self): 211 | S = bl.Study() 212 | S.loadData(np.array([1, 0, 1, 0, 0])) 213 | 214 | L = bl.om.ScaledAR1('rho', bl.oint(-1, 1, 100), 'sigma', bl.oint(0, 1, 100)) 215 | T = bl.tm.Static() 216 | S.set(L, T) 217 | 218 | S.fit() 219 | np.testing.assert_almost_equal(S.logEvidence, -4.4178639067800738, decimal=5, 220 | err_msg='Erroneous log-evidence value.') 221 | -------------------------------------------------------------------------------- /tests/test_onlinestudy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function, division 4 | import bayesloop as bl 5 | import numpy as np 6 | import sympy.stats as stats 7 | 8 | 9 | class TestTwoParameterModel: 10 | def test_step_set1TM_0hp(self): 11 | # carry out fit 12 | S = bl.OnlineStudy(storeHistory=True) 13 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1/s**3)) 14 | S.setTM(bl.tm.Static()) 15 | 16 | data = np.array([1, 2, 3, 4, 5]) 17 | for d in data: 18 | S.step(d) 19 | 20 | # test parameter distributions 21 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 22 | [0.0053811, 0.38690331, 0.16329865, 0.04887604, 0.01334921], 23 | rtol=1e-05, err_msg='Erroneous posterior distribution values.') 24 | 25 | # test parameter mean values 26 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 27 | [0.96310103, 1.5065597, 2.00218465, 2.500366, 3.], 28 | rtol=1e-05, err_msg='Erroneous posterior mean values.') 29 | 30 | # test model evidence value 31 | np.testing.assert_almost_equal(S.logEvidence, -16.1946904707, decimal=5, 32 | err_msg='Erroneous log-evidence value.') 33 | 34 | def test_step_add2TM_2hp_prior_hyperpriors_TMprior(self): 35 | # carry out fit 36 | S = bl.OnlineStudy(storeHistory=True) 37 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1./s)) 38 | 39 | T1 = bl.tm.CombinedTransitionModel(bl.tm.GaussianRandomWalk('s1', [0.25, 0.5], 40 | target='mean', 41 | prior=stats.Exponential('e', 0.5)), 42 | bl.tm.GaussianRandomWalk('s2', bl.cint(0, 0.2, 2), 43 | target='sigma', 44 | prior=np.array([0.2, 0.8])) 45 | ) 46 | 47 | T2 = bl.tm.Independent() 48 | 49 | S.addTransitionModel('T1', T1) 50 | S.addTransitionModel('T2', T2) 51 | 52 | S.setTransitionModelPrior([0.9, 0.1]) 53 | 54 | data = np.array([1, 2, 3, 4, 5]) 55 | for d in data: 56 | S.step(d) 57 | 58 | # test transition model distributions 59 | np.testing.assert_allclose(S.getCurrentTransitionModelDistribution(local=False)[1], 60 | [0.49402616, 0.50597384], 61 | rtol=1e-05, err_msg='Erroneous transition model probabilities.') 62 | 63 | np.testing.assert_allclose(S.getCurrentTransitionModelDistribution(local=True)[1], 64 | [0.81739495, 0.18260505], 65 | rtol=1e-05, err_msg='Erroneous local transition model probabilities.') 66 | 67 | # test hyper-parameter distributions 68 | np.testing.assert_allclose(S.getCurrentHyperParameterDistribution('s2')[1], 69 | [0.19047162, 0.80952838], 70 | rtol=1e-05, err_msg='Erroneous hyper-parameter distribution.') 71 | 72 | # test parameter distributions 73 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 74 | [0.05825921, 0.20129444, 0.07273516, 0.02125759, 0.0039255], 75 | rtol=1e-05, err_msg='Erroneous posterior distribution values.') 76 | 77 | # test parameter mean values 78 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 79 | [1.0771838, 1.71494272, 2.45992376, 3.34160617, 4.39337253], 80 | rtol=1e-05, err_msg='Erroneous posterior mean values.') 81 | 82 | # test model evidence value 83 | np.testing.assert_almost_equal(S.logEvidence, -9.46900822686, decimal=5, 84 | err_msg='Erroneous log-evidence value.') 85 | -------------------------------------------------------------------------------- /tests/test_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function, division 4 | import bayesloop as bl 5 | import numpy as np 6 | 7 | 8 | class TestParameterParsing: 9 | def test_inequality(self): 10 | S = bl.Study() 11 | S.loadData(np.array([1, 2, 3, 4, 5])) 12 | S.setOM(bl.om.Poisson('rate', bl.oint(0, 6, 50))) 13 | S.setTM(bl.tm.Static()) 14 | S.fit() 15 | 16 | S2 = bl.Study() 17 | S2.loadData(np.array([1, 2, 3, 4, 5])) 18 | S2.setOM(bl.om.Poisson('rate2', bl.oint(0, 6, 50))) 19 | S2.setTM(bl.tm.GaussianRandomWalk('sigma', 0.2, target='rate2')) 20 | S2.fit() 21 | 22 | P = bl.Parser(S, S2) 23 | P('log(rate2*2*1.2) + 4 + rate^2 > 20', t=3) 24 | np.testing.assert_almost_equal(P('log(rate2@1*2*1.2) + 4 + rate@2^2 > 20'), 0.19606860326174191, decimal=5, 25 | err_msg='Erroneous parsing result for inequality.') 26 | np.testing.assert_almost_equal(P('log(rate2*2*1.2) + 4 + rate^2 > 20', t=3), 0.19772797081330246, decimal=5, 27 | err_msg='Erroneous parsing result for inequality with fixed timestamp.') 28 | 29 | def test_distribution(self): 30 | S = bl.Study() 31 | S.loadData(np.array([1, 2, 3, 4, 5])) 32 | S.setOM(bl.om.Poisson('rate', bl.oint(0, 6, 50))) 33 | S.setTM(bl.tm.Static()) 34 | S.fit() 35 | 36 | S2 = bl.Study() 37 | S2.loadData(np.array([1, 2, 3, 4, 5])) 38 | S2.setOM(bl.om.Poisson('rate2', bl.oint(0, 6, 50))) 39 | S2.setTM(bl.tm.GaussianRandomWalk('sigma', 0.2, target='rate2')) 40 | S2.fit() 41 | 42 | P = bl.Parser(S, S2) 43 | x, p = P('log(rate2@1*2*1.2)+ 4 + rate@2^2') 44 | np.testing.assert_allclose(p[100:105], 45 | [0.00732 , 0.007495, 0.005775, 0.003511, 0.003949], 46 | rtol=1e-03, err_msg='Erroneous derived probability distribution.') 47 | 48 | 49 | class TestHyperParameterParsing: 50 | def test_statichyperparameter(self): 51 | S = bl.HyperStudy() 52 | S.loadData(np.array([1, 2, 3, 4, 5])) 53 | S.setOM(bl.om.Poisson('rate', bl.oint(0, 6, 50))) 54 | S.setTM(bl.tm.GaussianRandomWalk('sigma', bl.cint(0, 0.2, 5), target='rate')) 55 | S.fit() 56 | 57 | p = S.eval('exp(0.99*log(sigma))+1 > 1.1') 58 | 59 | np.testing.assert_almost_equal(p, 0.60696006616644793, decimal=5, 60 | err_msg='Erroneous parsing result for inequality.') 61 | 62 | def test_dynamichyperparameter(self): 63 | S = bl.OnlineStudy(storeHistory=True) 64 | S.setOM(bl.om.Poisson('rate', bl.oint(0, 6, 50))) 65 | S.add('gradual', bl.tm.GaussianRandomWalk('sigma', bl.cint(0, 0.2, 5), target='rate')) 66 | S.add('static', bl.tm.Static()) 67 | 68 | for d in np.arange(5): 69 | S.step(d) 70 | 71 | p = S.eval('exp(0.99*log(sigma@2))+1 > 1.1') 72 | 73 | np.testing.assert_almost_equal(p, 0.61228433813735061, decimal=5, 74 | err_msg='Erroneous parsing result for inequality.') 75 | 76 | S = bl.OnlineStudy(storeHistory=False) 77 | S.setOM(bl.om.Poisson('rate', bl.oint(0, 6, 50))) 78 | S.add('gradual', bl.tm.GaussianRandomWalk('sigma', bl.cint(0, 0.2, 5), target='rate')) 79 | S.add('static', bl.tm.Static()) 80 | 81 | for d in np.arange(3): 82 | S.step(d) 83 | 84 | p = S.eval('exp(0.99*log(sigma))+1 > 1.1') 85 | 86 | np.testing.assert_almost_equal(p, 0.61228433813735061, decimal=5, 87 | err_msg='Erroneous parsing result for inequality.') 88 | -------------------------------------------------------------------------------- /tests/test_plot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function, division 4 | import bayesloop as bl 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | 8 | 9 | class TestPlot: 10 | def test_plot_study(self): 11 | S = bl.Study() 12 | S.loadData(np.array([1, 2, 3, 4, 5])) 13 | 14 | L = bl.om.Poisson('rate', bl.oint(0, 6, 100)) 15 | T = bl.tm.Static() 16 | S.set(L, T) 17 | 18 | S.fit() 19 | 20 | S.plot('rate') 21 | plt.close() 22 | 23 | S.plot('rate', t=2) 24 | plt.close() 25 | 26 | def test_plot_hyperstudy(self): 27 | S = bl.HyperStudy() 28 | S.loadData(np.array([1, 2, 3, 4, 5])) 29 | 30 | L = bl.om.Poisson('rate', bl.oint(0, 6, 100)) 31 | T = bl.tm.GaussianRandomWalk('sigma', bl.cint(0, 0.2, 5), target='rate') 32 | S.set(L, T) 33 | 34 | S.fit() 35 | 36 | S.plot('rate') 37 | plt.close() 38 | 39 | S.plot('rate', t=2) 40 | plt.close() 41 | 42 | S.plot('sigma') 43 | plt.close() 44 | 45 | def test_plot_changepointstudy(self): 46 | S = bl.ChangepointStudy() 47 | S.loadData(np.array([1, 2, 3, 4, 5])) 48 | 49 | L = bl.om.Poisson('rate', bl.oint(0, 6, 100)) 50 | T = bl.tm.SerialTransitionModel(bl.tm.Static(), 51 | bl.tm.ChangePoint('t1', 'all'), 52 | bl.tm.GaussianRandomWalk('sigma', bl.cint(0, 0.2, 3), target='rate'), 53 | bl.tm.ChangePoint('t2', 'all'), 54 | bl.tm.Static()) 55 | S.set(L, T) 56 | 57 | S.fit() 58 | 59 | S.plot('rate') 60 | plt.close() 61 | 62 | S.plot('rate', t=2) 63 | plt.close() 64 | 65 | S.plot('sigma') 66 | plt.close() 67 | 68 | S.getDD(['t1', 't2'], plot=True) 69 | plt.close() 70 | 71 | def test_plot_onlinestudy(self): 72 | S = bl.OnlineStudy(storeHistory=True) 73 | S.setOM(bl.om.Poisson('rate', bl.oint(0, 6, 50))) 74 | S.add('gradual', bl.tm.GaussianRandomWalk('sigma', bl.cint(0, 0.2, 5), target='rate')) 75 | S.add('static', bl.tm.Static()) 76 | 77 | for d in np.arange(5): 78 | S.step(d) 79 | 80 | S.plot('rate') 81 | plt.close() 82 | 83 | S.plot('rate', t=2) 84 | plt.close() 85 | 86 | S.plot('sigma') 87 | plt.close() 88 | 89 | S.plot('sigma', t=2) 90 | plt.close() 91 | 92 | S.plot('gradual') 93 | plt.close() 94 | 95 | S.plot('gradual', local=True) 96 | plt.close() 97 | -------------------------------------------------------------------------------- /tests/test_study.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function, division 4 | import bayesloop as bl 5 | import numpy as np 6 | import sympy.stats as stats 7 | 8 | 9 | class TestOneParameterModel: 10 | def test_fit_0hp(self): 11 | # carry out fit 12 | S = bl.Study() 13 | S.loadData(np.array([1, 2, 3, 4, 5])) 14 | S.setOM(bl.om.Poisson('rate')) 15 | S.setTM(bl.tm.Static()) 16 | S.fit() 17 | 18 | # test parameter distributions 19 | np.testing.assert_allclose(S.getParameterDistributions('rate', density=False)[1][:, 250], 20 | [0.00034, 0.00034, 0.00034, 0.00034, 0.00034], 21 | rtol=1e-3, err_msg='Erroneous posterior distribution values.') 22 | 23 | # test parameter mean values 24 | np.testing.assert_allclose(S.getParameterMeanValues('rate'), 25 | [3.09761, 3.09761, 3.09761, 3.09761, 3.09761], 26 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 27 | 28 | # test model evidence value 29 | np.testing.assert_almost_equal(S.logEvidence, -10.4463425036, decimal=2, 30 | err_msg='Erroneous log-evidence value.') 31 | 32 | def test_fit_1hp(self): 33 | # carry out fit 34 | S = bl.Study() 35 | S.loadData(np.array([1, 2, 3, 4, 5])) 36 | S.setOM(bl.om.Poisson('rate')) 37 | S.setTM(bl.tm.GaussianRandomWalk('sigma', 0.1, target='rate')) 38 | S.fit() 39 | 40 | # test parameter distributions 41 | np.testing.assert_allclose(S.getParameterDistributions('rate', density=False)[1][:, 250], 42 | [0.000417, 0.000386, 0.000356, 0.000336, 0.000332], 43 | rtol=1e-02, err_msg='Erroneous posterior distribution values.') 44 | 45 | # test parameter mean values 46 | np.testing.assert_allclose(S.getParameterMeanValues('rate'), 47 | [3.073534, 3.08179 , 3.093091, 3.104016, 3.111173], 48 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 49 | 50 | # test model evidence value 51 | np.testing.assert_almost_equal(S.logEvidence, -10.4337420351, decimal=2, 52 | err_msg='Erroneous log-evidence value.') 53 | 54 | def test_fit_2hp(self): 55 | # carry out fit 56 | S = bl.Study() 57 | S.loadData(np.array([1, 2, 3, 4, 5])) 58 | S.setOM(bl.om.Poisson('rate')) 59 | 60 | T = bl.tm.CombinedTransitionModel(bl.tm.GaussianRandomWalk('sigma', 0.1, target='rate'), 61 | bl.tm.RegimeSwitch('log10pMin', -3)) 62 | 63 | S.setTM(T) 64 | S.fit() 65 | 66 | # test parameter distributions 67 | np.testing.assert_allclose(S.getParameterDistributions('rate', density=False)[1][:, 250], 68 | [0.000412, 0.000376, 0.000353, 0.000336, 0.000332], 69 | rtol=1e-02, err_msg='Erroneous posterior distribution values.') 70 | 71 | # test parameter mean values 72 | np.testing.assert_allclose(S.getParameterMeanValues('rate'), 73 | [2.942708, 3.002756, 3.071995, 3.103038, 3.111179], 74 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 75 | 76 | # test model evidence value 77 | np.testing.assert_almost_equal(S.logEvidence, -10.4342948181, decimal=2, 78 | err_msg='Erroneous log-evidence value.') 79 | 80 | def test_fit_prior_array(self): 81 | # carry out fit 82 | S = bl.Study() 83 | S.loadData(np.array([1, 2, 3, 4, 5])) 84 | S.setOM(bl.om.Poisson('rate', bl.oint(0, 6, 1000), prior=np.ones(1000))) 85 | S.setTM(bl.tm.GaussianRandomWalk('sigma', 0.1, target='rate')) 86 | S.fit() 87 | 88 | # test parameter distributions 89 | np.testing.assert_allclose(S.getParameterDistributions('rate', density=False)[1][:, 250], 90 | [0.000221, 0.000202, 0.000184, 0.000172, 0.000172], 91 | rtol=1e-02, err_msg='Erroneous posterior distribution values.') 92 | 93 | # test parameter mean values 94 | np.testing.assert_allclose(S.getParameterMeanValues('rate'), 95 | [3.174159, 3.180812, 3.190743, 3.200642, 3.20722 ], 96 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 97 | 98 | # test model evidence value 99 | np.testing.assert_almost_equal(S.logEvidence, -10.0866227472, decimal=2, 100 | err_msg='Erroneous log-evidence value.') 101 | 102 | def test_fit_prior_function(self): 103 | # carry out fit 104 | S = bl.Study() 105 | S.loadData(np.array([1, 2, 3, 4, 5])) 106 | S.setOM(bl.om.Poisson('rate', bl.oint(0, 6, 1000), prior=lambda x: 1./x)) 107 | S.setTM(bl.tm.GaussianRandomWalk('sigma', 0.1, target='rate')) 108 | S.fit() 109 | 110 | # test parameter distributions 111 | np.testing.assert_allclose(S.getParameterDistributions('rate', density=False)[1][:, 250], 112 | [0.000437, 0.000401, 0.000366, 0.000342, 0.000337], 113 | rtol=1e-02, err_msg='Erroneous posterior distribution values.') 114 | 115 | # test parameter mean values 116 | np.testing.assert_allclose(S.getParameterMeanValues('rate'), 117 | [2.967834, 2.977838, 2.990624, 3.002654, 3.010419], 118 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 119 | 120 | # test model evidence value 121 | np.testing.assert_almost_equal(S.logEvidence, -11.3966589329, decimal=2, 122 | err_msg='Erroneous log-evidence value.') 123 | 124 | def test_fit_prior_sympy(self): 125 | # carry out fit 126 | S = bl.Study() 127 | S.loadData(np.array([1, 2, 3, 4, 5])) 128 | S.setOM(bl.om.Poisson('rate', bl.oint(0, 6, 1000), prior=stats.Exponential('expon', 1.))) 129 | S.setTM(bl.tm.GaussianRandomWalk('sigma', 0.1, target='rate')) 130 | S.fit() 131 | 132 | # test parameter distributions 133 | np.testing.assert_allclose(S.getParameterDistributions('rate', density=False)[1][:, 250], 134 | [0.000881, 0.00081 , 0.00074 , 0.00069 , 0.000674], 135 | rtol=1e-02, err_msg='Erroneous posterior distribution values.') 136 | 137 | # test parameter mean values 138 | np.testing.assert_allclose(S.getParameterMeanValues('rate'), 139 | [2.627709, 2.643611, 2.661415, 2.677185, 2.687023], 140 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 141 | 142 | # test model evidence value 143 | np.testing.assert_almost_equal(S.logEvidence, -11.1819034242, decimal=2, 144 | err_msg='Erroneous log-evidence value.') 145 | 146 | def test_optimize(self): 147 | # carry out fit 148 | S = bl.Study() 149 | S.loadData(np.array([1, 2, 3, 4, 5])) 150 | S.setOM(bl.om.Poisson('rate', bl.oint(0, 6, 1000), prior=stats.Exponential('expon', 1.))) 151 | 152 | T = bl.tm.CombinedTransitionModel(bl.tm.GaussianRandomWalk('sigma', 2.1, target='rate'), 153 | bl.tm.RegimeSwitch('log10pMin', -3)) 154 | 155 | S.setTM(T) 156 | S.optimize() 157 | 158 | # test parameter distributions 159 | np.testing.assert_allclose(S.getParameterDistributions('rate', density=False)[1][:, 250], 160 | [1.820641e-03, 2.083830e-03, 7.730833e-04, 1.977125e-04, 9.441302e-05], 161 | rtol=1e-02, err_msg='Erroneous posterior distribution values.') 162 | 163 | # test parameter mean values 164 | np.testing.assert_allclose(S.getParameterMeanValues('rate'), 165 | [1.015955, 2.291846, 3.36402 , 4.113622, 4.390356], 166 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 167 | 168 | # test model evidence value 169 | np.testing.assert_almost_equal(S.logEvidence, -9.47362827569, decimal=2, 170 | err_msg='Erroneous log-evidence value.') 171 | 172 | # test optimized hyper-parameter values 173 | np.testing.assert_almost_equal(S.getHyperParameterValue('sigma'), 2.11216289063, decimal=2, 174 | err_msg='Erroneous log-evidence value.') 175 | np.testing.assert_almost_equal(S.getHyperParameterValue('log10pMin'), -3.0, decimal=3, 176 | err_msg='Erroneous log-evidence value.') 177 | 178 | 179 | class TestTwoParameterModel: 180 | def test_fit_0hp(self): 181 | # carry out fit 182 | S = bl.Study() 183 | S.loadData(np.array([1, 2, 3, 4, 5])) 184 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1/s**3)) 185 | S.setTM(bl.tm.Static()) 186 | S.fit() 187 | 188 | # test parameter distributions 189 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 190 | [0.013349, 0.013349, 0.013349, 0.013349, 0.013349], 191 | rtol=1e-02, err_msg='Erroneous posterior distribution values.') 192 | 193 | # test parameter mean values 194 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 195 | [3., 3., 3., 3., 3.], 196 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 197 | 198 | # test model evidence value 199 | np.testing.assert_almost_equal(S.logEvidence, -16.1946904707, decimal=2, 200 | err_msg='Erroneous log-evidence value.') 201 | 202 | def test_fit_1hp(self): 203 | # carry out fit 204 | S = bl.Study() 205 | S.loadData(np.array([1, 2, 3, 4, 5])) 206 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1/s**3)) 207 | S.setTM(bl.tm.GaussianRandomWalk('sigma', 0.1, target='mean')) 208 | S.fit() 209 | 210 | # test parameter distributions 211 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 212 | [0.013547, 0.013428, 0.013315, 0.013241, 0.013232], 213 | rtol=1e-02, err_msg='Erroneous posterior distribution values.') 214 | 215 | # test parameter mean values 216 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 217 | [2.995242, 2.997088, 3. , 3.002912, 3.004758], 218 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 219 | 220 | # test model evidence value 221 | np.testing.assert_almost_equal(S.logEvidence, -16.1865343702, decimal=2, 222 | err_msg='Erroneous log-evidence value.') 223 | 224 | def test_fit_2hp(self): 225 | # carry out fit 226 | S = bl.Study() 227 | S.loadData(np.array([1, 2, 3, 4, 5])) 228 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1/s**3)) 229 | 230 | T = bl.tm.CombinedTransitionModel(bl.tm.GaussianRandomWalk('sigma', 0.1, target='mean'), 231 | bl.tm.RegimeSwitch('log10pMin', -3)) 232 | 233 | S.setTM(T) 234 | S.fit() 235 | 236 | # test parameter distributions 237 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 238 | [0.018848, 0.149165, 0.025588, 0.006414, 0.005426], 239 | rtol=1e-02, err_msg='Erroneous posterior distribution values.') 240 | 241 | # test parameter mean values 242 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 243 | [1.005987, 2.710129, 3.306985, 3.497192, 3.527645], 244 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 245 | 246 | # test model evidence value 247 | np.testing.assert_almost_equal(S.logEvidence, -14.3305753098, decimal=2, 248 | err_msg='Erroneous log-evidence value.') 249 | 250 | def test_fit_prior_array(self): 251 | # carry out fit 252 | S = bl.Study() 253 | S.loadData(np.array([1, 2, 3, 4, 5])) 254 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=np.ones((20, 20)))) 255 | S.setTM(bl.tm.GaussianRandomWalk('sigma', 0.1, target='mean')) 256 | S.fit() 257 | 258 | # test parameter distributions 259 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 260 | [0.02045 , 0.020327, 0.020208, 0.020128, 0.020115], 261 | rtol=1e-02, err_msg='Erroneous posterior distribution values.') 262 | 263 | # test parameter mean values 264 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 265 | [2.99656 , 2.997916, 3. , 3.002084, 3.00344 ], 266 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 267 | 268 | # test model evidence value 269 | np.testing.assert_almost_equal(S.logEvidence, -10.9827282104, decimal=2, 270 | err_msg='Erroneous log-evidence value.') 271 | 272 | def test_fit_prior_function(self): 273 | # carry out fit 274 | S = bl.Study() 275 | S.loadData(np.array([1, 2, 3, 4, 5])) 276 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1./s)) 277 | S.setTM(bl.tm.GaussianRandomWalk('sigma', 0.1, target='mean')) 278 | S.fit() 279 | 280 | # test parameter distributions 281 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 282 | [0.018242, 0.018119, 0.018001, 0.017921, 0.01791 ], 283 | rtol=1e-02, err_msg='Erroneous posterior distribution values.') 284 | 285 | # test parameter mean values 286 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 287 | [2.996202, 2.997693, 3. , 3.002307, 3.003798], 288 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 289 | 290 | # test model evidence value 291 | np.testing.assert_almost_equal(S.logEvidence, -11.9842221343, decimal=2, 292 | err_msg='Erroneous log-evidence value.') 293 | 294 | def test_fit_prior_sympy(self): 295 | # carry out fit 296 | S = bl.Study() 297 | S.loadData(np.array([1, 2, 3, 4, 5])) 298 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), 299 | prior=[stats.Uniform('u', 0, 6), stats.Exponential('e', 2.)])) 300 | S.setTM(bl.tm.GaussianRandomWalk('sigma', 0.1, target='mean')) 301 | S.fit() 302 | 303 | # test parameter distributions 304 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 305 | [0.014305, 0.014183, 0.014066, 0.01399 , 0.01398 ], 306 | rtol=1e-02, err_msg='Erroneous posterior distribution values.') 307 | 308 | # test parameter mean values 309 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 310 | [2.995526, 2.997271, 3. , 3.002729, 3.004474], 311 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 312 | 313 | # test model evidence value 314 | np.testing.assert_almost_equal(S.logEvidence, -12.4324853153, decimal=2, 315 | err_msg='Erroneous log-evidence value.') 316 | 317 | def test_optimize(self): 318 | # carry out fit 319 | S = bl.Study() 320 | S.loadData(np.array([1, 2, 3, 4, 5])) 321 | S.setOM(bl.om.Gaussian('mean', bl.cint(0, 6, 20), 'sigma', bl.oint(0, 2, 20), prior=lambda m, s: 1/s**3)) 322 | 323 | T = bl.tm.CombinedTransitionModel(bl.tm.GaussianRandomWalk('sigma', 1.07, target='mean'), 324 | bl.tm.RegimeSwitch('log10pMin', -3.90)) 325 | 326 | S.setTM(T) 327 | S.optimize() 328 | 329 | # test parameter distributions 330 | np.testing.assert_allclose(S.getParameterDistributions('mean', density=False)[1][:, 5], 331 | [9.903855e-03, 1.887901e-02, 8.257234e-05, 5.142727e-06, 2.950377e-06], 332 | rtol=1e-02, err_msg='Erroneous posterior distribution values.') 333 | 334 | # test parameter mean values 335 | np.testing.assert_allclose(S.getParameterMeanValues('mean'), 336 | [0.979099, 1.951689, 3.000075, 4.048376, 5.020886], 337 | rtol=1e-02, err_msg='Erroneous posterior mean values.') 338 | 339 | # test model evidence value 340 | np.testing.assert_almost_equal(S.logEvidence, -8.010466752050611, decimal=2, 341 | err_msg='Erroneous log-evidence value.') 342 | 343 | # test optimized hyper-parameter values 344 | np.testing.assert_almost_equal(S.getHyperParameterValue('sigma'), 1.065854087589326, decimal=2, 345 | err_msg='Erroneous log-evidence value.') 346 | np.testing.assert_almost_equal(S.getHyperParameterValue('log10pMin'), -4.039735868499399, decimal=2, 347 | err_msg='Erroneous log-evidence value.') 348 | -------------------------------------------------------------------------------- /tests/test_transitionmodels.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function, division 4 | import bayesloop as bl 5 | import numpy as np 6 | 7 | 8 | class TestBuiltin: 9 | def test_static(self): 10 | S = bl.Study() 11 | S.loadData(np.array([1, 2, 3, 4, 5])) 12 | 13 | L = bl.om.Poisson('rate', bl.oint(0, 6, 100)) 14 | T = bl.tm.Static() 15 | S.set(L, T) 16 | 17 | S.fit() 18 | 19 | # test model evidence value 20 | np.testing.assert_almost_equal(S.logEvidence, -10.372209708143769, decimal=5, 21 | err_msg='Erroneous log-evidence value.') 22 | 23 | def test_deterministic(self): 24 | S = bl.HyperStudy() 25 | S.loadData(np.array([1, 2, 3, 4, 5])) 26 | 27 | def linear(t, a=[1, 2]): 28 | return 0.5 + 0.2*a*t 29 | 30 | L = bl.om.Poisson('rate', bl.oint(0, 6, 100)) 31 | T = bl.tm.Deterministic(linear, target='rate') 32 | S.set(L, T) 33 | 34 | S.fit() 35 | 36 | # test model evidence value 37 | np.testing.assert_almost_equal(S.logEvidence, -9.4050089375418136, decimal=3, 38 | err_msg='Erroneous log-evidence value.') 39 | 40 | def test_gaussianrandomwalk(self): 41 | S = bl.Study() 42 | S.loadData(np.array([1, 2, 3, 4, 5])) 43 | 44 | L = bl.om.Poisson('rate', bl.oint(0, 6, 100)) 45 | T = bl.tm.GaussianRandomWalk('sigma', 0.2, target='rate') 46 | S.set(L, T) 47 | 48 | S.fit() 49 | 50 | # test model evidence value 51 | np.testing.assert_almost_equal(S.logEvidence, -10.323144246611964, decimal=5, 52 | err_msg='Erroneous log-evidence value.') 53 | 54 | def test_bivariaterandomwalk(self): 55 | S = bl.Study() 56 | S.loadData(np.array([1, 2, 3, 4, 5])) 57 | 58 | L = bl.om.Gaussian('mu', bl.oint(0, 6, 20), 'sigma', bl.oint(0, 2, 20)) 59 | T = bl.tm.BivariateRandomWalk('sigma1', 1., 'sigma2', 0.1, 'rho', 0.5) 60 | S.set(L, T) 61 | 62 | S.fit() 63 | 64 | # test model evidence value 65 | np.testing.assert_almost_equal(S.logEvidence, -7.330706514472251, decimal=5, 66 | err_msg='Erroneous log-evidence value.') 67 | 68 | def test_alphastablerandomwalk(self): 69 | S = bl.Study() 70 | S.loadData(np.array([1, 2, 3, 4, 5])) 71 | 72 | L = bl.om.Poisson('rate', bl.oint(0, 6, 100)) 73 | T = bl.tm.AlphaStableRandomWalk('c', 0.2, 'alpha', 1.5, target='rate') 74 | S.set(L, T) 75 | 76 | S.fit() 77 | 78 | # test model evidence value 79 | np.testing.assert_almost_equal(S.logEvidence, -10.122384638661309, decimal=5, 80 | err_msg='Erroneous log-evidence value.') 81 | 82 | def test_changepoint(self): 83 | S = bl.Study() 84 | S.loadData(np.array([1, 2, 3, 4, 5])) 85 | 86 | L = bl.om.Poisson('rate', bl.oint(0, 6, 100)) 87 | T = bl.tm.ChangePoint('t_change', 2) 88 | S.set(L, T) 89 | 90 | S.fit() 91 | 92 | # test model evidence value 93 | np.testing.assert_almost_equal(S.logEvidence, -12.894336092378385, decimal=5, 94 | err_msg='Erroneous log-evidence value.') 95 | 96 | def test_regimeswitch(self): 97 | S = bl.Study() 98 | S.loadData(np.array([1, 2, 3, 4, 5])) 99 | 100 | L = bl.om.Poisson('rate', bl.oint(0, 6, 100)) 101 | T = bl.tm.RegimeSwitch('p_min', -3) 102 | S.set(L, T) 103 | 104 | S.fit() 105 | 106 | # test model evidence value 107 | np.testing.assert_almost_equal(S.logEvidence, -10.372866559561402, decimal=5, 108 | err_msg='Erroneous log-evidence value.') 109 | 110 | def test_independent(self): 111 | S = bl.Study() 112 | S.loadData(np.array([1, 2, 3, 4, 5])) 113 | 114 | L = bl.om.Poisson('rate', bl.oint(0, 6, 100)) 115 | T = bl.tm.Independent() 116 | S.set(L, T) 117 | 118 | S.fit() 119 | 120 | # test model evidence value 121 | np.testing.assert_almost_equal(S.logEvidence, -11.087360077190617, decimal=5, 122 | err_msg='Erroneous log-evidence value.') 123 | 124 | def test_notequal(self): 125 | S = bl.Study() 126 | S.loadData(np.array([1, 2, 3, 4, 5])) 127 | 128 | L = bl.om.Poisson('rate', bl.oint(0, 6, 100)) 129 | T = bl.tm.NotEqual('p_min', -3) 130 | S.set(L, T) 131 | 132 | S.fit() 133 | 134 | # test model evidence value 135 | np.testing.assert_almost_equal(S.logEvidence, -10.569099863134156, decimal=5, 136 | err_msg='Erroneous log-evidence value.') 137 | 138 | 139 | class TestNested: 140 | def test_nested(self): 141 | S = bl.Study() 142 | S.loadData(np.array([1, 2, 3, 4, 5])) 143 | 144 | L = bl.om.Poisson('rate', bl.oint(0, 6, 100)) 145 | T = bl.tm.SerialTransitionModel( 146 | bl.tm.Static(), 147 | bl.tm.ChangePoint('t_change', 1), 148 | bl.tm.CombinedTransitionModel( 149 | bl.tm.GaussianRandomWalk('sigma', 0.2, target='rate'), 150 | bl.tm.RegimeSwitch('p_min', -3) 151 | ), 152 | bl.tm.BreakPoint('t_break', 3), 153 | bl.tm.Independent() 154 | ) 155 | S.set(L, T) 156 | 157 | S.fit() 158 | 159 | # test model evidence value 160 | np.testing.assert_almost_equal(S.logEvidence, -13.269918024215237, decimal=5, 161 | err_msg='Erroneous log-evidence value.') 162 | --------------------------------------------------------------------------------